mirror of
https://github.com/pockethost/pockethost.git
synced 2025-03-30 15:08:30 +00:00
PBScript integration
This commit is contained in:
parent
a3c522fca3
commit
a0fe228c81
1
packages/cli/.gitignore
vendored
Normal file
1
packages/cli/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
dist
|
3
packages/cli/.npmignore
Normal file
3
packages/cli/.npmignore
Normal file
@ -0,0 +1,3 @@
|
||||
src
|
||||
tsconfig.json
|
||||
dist/index.*
|
58
packages/cli/package.json
Normal file
58
packages/cli/package.json
Normal file
@ -0,0 +1,58 @@
|
||||
{
|
||||
"name": "pockethost",
|
||||
"version": "0.0.5",
|
||||
"main": "./dist/index.js",
|
||||
"bin": {
|
||||
"pbscript": "./dist/pbscript"
|
||||
},
|
||||
"author": {
|
||||
"name": "Ben Allfree",
|
||||
"url": "https://github.com/benallfree"
|
||||
},
|
||||
"repository": {
|
||||
"url": "https://github.com/benallfree/pockethost"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "chokidar package.json 'src/**' './node_modules/**' -c 'yarn build' --initial",
|
||||
"build": "parcel build --no-cache && yarn build:shebang && npm i -g --force .",
|
||||
"build:shebang": "echo \"#!/usr/bin/env node\"|cat - ./dist/index.js > ./dist/pbscript",
|
||||
"publish": "npm publish --access public"
|
||||
},
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"parcel": "^2.7.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/eventsource": "^1.1.9",
|
||||
"@types/node": "^18.8.2",
|
||||
"@types/prompts": "^2.4.1",
|
||||
"@types/tmp": "^0.2.3",
|
||||
"commander": "^9.4.0",
|
||||
"cross-fetch": "^3.1.5",
|
||||
"eventsource": "^2.0.2",
|
||||
"find-up": "^6.3.0",
|
||||
"pocketbase": "^0.7.1",
|
||||
"prompts": "^2.4.2",
|
||||
"tmp": "^0.2.1"
|
||||
},
|
||||
"targets": {
|
||||
"node": {
|
||||
"engines": {
|
||||
"node": ">= 12"
|
||||
},
|
||||
"source": "./src/index.ts",
|
||||
"context": "node",
|
||||
"outputFormat": "commonjs",
|
||||
"includeNodeModules": [
|
||||
"@s-libs/micro-dash",
|
||||
"find-up",
|
||||
"locate-path",
|
||||
"path-exists",
|
||||
"p-locate",
|
||||
"p-limit",
|
||||
"yocto-queue",
|
||||
"pocketbase"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
25
packages/cli/readme.md
Normal file
25
packages/cli/readme.md
Normal file
@ -0,0 +1,25 @@
|
||||
# TS/JS Cloud Functions for PocketBase
|
||||
|
||||
[PBScript](https://github.com/benallfree/pbscript) allows you to write [PocketBase](https://pocketbase.io) server-side functions in Typescript or Javascript without recompiling.
|
||||
|
||||
This package is the CLI tool.
|
||||
|
||||
```bash
|
||||
npm i -g pbscript
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```bash
|
||||
npx pbscript --help
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```bash
|
||||
npm i -D pbscript
|
||||
```
|
||||
|
||||
And then reference from `package.json`.
|
||||
|
||||
See official docs at the [PBScript repo](https://github.com/benallfree/pbscript).
|
90
packages/cli/src/commands/build.ts
Normal file
90
packages/cli/src/commands/build.ts
Normal file
@ -0,0 +1,90 @@
|
||||
import { Parcel } from '@parcel/core'
|
||||
import { debounce } from '@s-libs/micro-dash'
|
||||
import chokidar from 'chokidar'
|
||||
import { Command } from 'commander'
|
||||
import { mkdirSync } from 'fs'
|
||||
import { join } from 'path'
|
||||
import { cwd } from 'process'
|
||||
import { getProjectRoot, readSettings } from '../util/project'
|
||||
|
||||
export type BuildConfig = {
|
||||
dist: string
|
||||
src: string
|
||||
}
|
||||
|
||||
export const addDevCommand = (program: Command) => {
|
||||
program
|
||||
.command('build')
|
||||
.description('Build the JS bundle')
|
||||
.option(
|
||||
'--src <path>',
|
||||
`Path to source (default: <project>/src/index.{ts|js})`
|
||||
)
|
||||
.option('--dist <path>', `Path to dist (default: <project>/dist/index.js)`)
|
||||
.action(async (options) => {
|
||||
const defaultSrc = options.src
|
||||
const defaultDist = options.dist
|
||||
|
||||
const config: BuildConfig = {
|
||||
src: join(getProjectRoot(), './src/index.ts'),
|
||||
dist: join(getProjectRoot(), './dist/index.js'),
|
||||
...readSettings('build'),
|
||||
}
|
||||
if (defaultDist) config.dist = defaultDist
|
||||
if (defaultSrc) config.src = defaultSrc
|
||||
|
||||
const { src, dist } = config
|
||||
mkdirSync(dist, { recursive: true })
|
||||
console.log(cwd())
|
||||
|
||||
const bundler = new Parcel({
|
||||
entries: './src/index.ts',
|
||||
defaultConfig: '@parcel/config-default',
|
||||
mode: 'production',
|
||||
defaultTargetOptions: {
|
||||
distDir: join(cwd(), 'dist'),
|
||||
outputFormat: 'global',
|
||||
sourceMaps: false,
|
||||
},
|
||||
shouldDisableCache: true,
|
||||
// targets: {
|
||||
// iife: {
|
||||
// distDir: './dist',
|
||||
// source: './src/index.ts',
|
||||
// context: 'browser',
|
||||
// outputFormat: 'global',
|
||||
// sourceMap: {
|
||||
// inline: true,
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
})
|
||||
|
||||
const build = debounce(() => {
|
||||
console.log(`Building...`)
|
||||
bundler
|
||||
.run()
|
||||
.then((e) => {
|
||||
console.log(`Build succeeded`, e)
|
||||
console.log(e.bundleGraph.getBundles({ includeInline: true }))
|
||||
let { bundleGraph } = e
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error(`Build failed with ${e}`)
|
||||
})
|
||||
}, 100)
|
||||
|
||||
build()
|
||||
console.log(join(getProjectRoot(), 'src/**'))
|
||||
chokidar
|
||||
.watch([
|
||||
join(getProjectRoot(), 'src/**'),
|
||||
join(getProjectRoot(), 'node_modules/**'),
|
||||
])
|
||||
.on('all', (event, path) => {
|
||||
build()
|
||||
})
|
||||
|
||||
console.log('here')
|
||||
})
|
||||
}
|
99
packages/cli/src/commands/dev.ts
Normal file
99
packages/cli/src/commands/dev.ts
Normal file
@ -0,0 +1,99 @@
|
||||
import { Parcel } from '@parcel/core'
|
||||
import { debounce } from '@s-libs/micro-dash'
|
||||
import chokidar from 'chokidar'
|
||||
import { Command } from 'commander'
|
||||
import { join } from 'path'
|
||||
import { cwd } from 'process'
|
||||
import tmp from 'tmp'
|
||||
import { DEFAULT_PB_DEV_URL } from '../constants'
|
||||
import { SessionState } from '../providers/CustomAuthStore'
|
||||
import { ensureAdminClient } from '../util/ensureAdminClient'
|
||||
import { getProjectRoot, readSettings } from '../util/project'
|
||||
|
||||
export type DevConfig = {
|
||||
session: SessionState
|
||||
host: string
|
||||
src: string
|
||||
dist: string
|
||||
}
|
||||
export const addDevCommand = (program: Command) => {
|
||||
program
|
||||
.command('dev')
|
||||
.description('Watch for source code changes in development mode')
|
||||
.option(
|
||||
'--src <path>',
|
||||
`Path to source (default: <project>/src/index.{ts|js})`
|
||||
)
|
||||
.option('--dist <path>', `Path to dist (default: <project>/dist/index.js)`)
|
||||
.option('--host', 'PocketBase host', DEFAULT_PB_DEV_URL)
|
||||
.action(async (options) => {
|
||||
const defaultSrc = options.src
|
||||
const defaultDist = options.dist
|
||||
const defaultHost = options.host
|
||||
|
||||
const config: DevConfig = {
|
||||
session: { token: '', model: null },
|
||||
host: DEFAULT_PB_DEV_URL,
|
||||
src: join(getProjectRoot(), './src/index.ts'),
|
||||
dist: join(getProjectRoot(), './dist/index.js'),
|
||||
...readSettings('dev'),
|
||||
}
|
||||
if (defaultDist) config.dist = defaultDist
|
||||
if (defaultSrc) config.src = defaultSrc
|
||||
|
||||
const client = await ensureAdminClient('dev', config)
|
||||
|
||||
const distDir = tmp.dirSync().name
|
||||
console.log(cwd())
|
||||
|
||||
const bundler = new Parcel({
|
||||
entries: './src/index.ts',
|
||||
defaultConfig: '@parcel/config-default',
|
||||
mode: 'production',
|
||||
defaultTargetOptions: {
|
||||
distDir: join(cwd(), 'dist'),
|
||||
outputFormat: 'global',
|
||||
sourceMaps: false,
|
||||
},
|
||||
shouldDisableCache: true,
|
||||
// targets: {
|
||||
// iife: {
|
||||
// distDir: './dist',
|
||||
// source: './src/index.ts',
|
||||
// context: 'browser',
|
||||
// outputFormat: 'global',
|
||||
// sourceMap: {
|
||||
// inline: true,
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
})
|
||||
|
||||
const build = debounce(() => {
|
||||
console.log(`Building...`)
|
||||
bundler
|
||||
.run()
|
||||
.then((e) => {
|
||||
console.log(`Build succeeded`, e)
|
||||
console.log(e.bundleGraph.getBundles({ includeInline: true }))
|
||||
let { bundleGraph } = e
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error(`Build failed with ${e}`)
|
||||
})
|
||||
}, 100)
|
||||
|
||||
build()
|
||||
console.log(join(getProjectRoot(), 'src/**'))
|
||||
chokidar
|
||||
.watch([
|
||||
join(getProjectRoot(), 'src/**'),
|
||||
join(getProjectRoot(), 'node_modules/**'),
|
||||
])
|
||||
.on('all', (event, path) => {
|
||||
build()
|
||||
})
|
||||
|
||||
console.log('here')
|
||||
})
|
||||
}
|
100
packages/cli/src/commands/publish.ts
Normal file
100
packages/cli/src/commands/publish.ts
Normal file
@ -0,0 +1,100 @@
|
||||
import { Command } from 'commander'
|
||||
import { existsSync, readFileSync } from 'fs'
|
||||
import { join } from 'path'
|
||||
import pocketbaseEs from 'pocketbase'
|
||||
import { DEFAULT_PB_DEV_URL } from '../constants'
|
||||
import { SessionState } from '../providers/CustomAuthStore'
|
||||
import { die } from '../util/die'
|
||||
import { ensureAdminClient } from '../util/ensureAdminClient'
|
||||
import { getProjectRoot, readSettings } from '../util/project'
|
||||
|
||||
export type PublishConfig = {
|
||||
session: SessionState
|
||||
host: string
|
||||
dist: string
|
||||
}
|
||||
|
||||
const migrate = async (client: pocketbaseEs) => {
|
||||
{
|
||||
// VERSION 1
|
||||
const res = await client.collections.getList(1, 1, {
|
||||
filter: `name='pbscript'`,
|
||||
})
|
||||
const [item] = res.items
|
||||
if (!item) {
|
||||
await client.collections.create({
|
||||
name: 'pbscript',
|
||||
schema: [
|
||||
{
|
||||
name: 'type',
|
||||
type: 'text',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'isActive',
|
||||
type: 'bool',
|
||||
},
|
||||
{
|
||||
name: 'data',
|
||||
type: 'json',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const publish = async (client: pocketbaseEs, fname: string) => {
|
||||
const js = readFileSync(fname).toString()
|
||||
const url = `${client.baseUrl}/api/pbscript/deploy`
|
||||
const res = await client
|
||||
.send(`api/pbscript/deploy`, {
|
||||
method: 'post',
|
||||
body: JSON.stringify({
|
||||
source: js,
|
||||
}),
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error(e)
|
||||
throw e
|
||||
})
|
||||
}
|
||||
|
||||
export const addPublishCommand = (program: Command) => {
|
||||
const _srcDefault = join(getProjectRoot(), './dist/index.js')
|
||||
const _hostDefault = DEFAULT_PB_DEV_URL
|
||||
program
|
||||
.command('publish')
|
||||
.description('Publish JS bundle to PBScript-enabled PocketBase instance')
|
||||
.option(
|
||||
'--dist <src>',
|
||||
`Path to dist bundle (default: <project>/dist/index.js)`
|
||||
)
|
||||
.option('--host <host>', `PocketBase host (default: ${DEFAULT_PB_DEV_URL})`)
|
||||
.action(async (options) => {
|
||||
const defaultHost = options.host
|
||||
const defaultDist = options.dist
|
||||
|
||||
const config: PublishConfig = {
|
||||
session: { token: '', model: null },
|
||||
host: DEFAULT_PB_DEV_URL,
|
||||
dist: join(getProjectRoot(), './dist/index.js'),
|
||||
...readSettings<PublishConfig>('publish'),
|
||||
}
|
||||
if (defaultHost) config.host = defaultHost
|
||||
if (defaultDist) config.dist = defaultDist
|
||||
|
||||
const { host, dist } = config
|
||||
|
||||
if (!existsSync(dist)) {
|
||||
die(`${dist} does not exist. Nothing to publish.`)
|
||||
}
|
||||
|
||||
const client = await ensureAdminClient('publish', config)
|
||||
console.log(`Deploying from ${dist} to ${host}`)
|
||||
await migrate(client)
|
||||
await publish(client, dist)
|
||||
})
|
||||
}
|
1
packages/cli/src/constants.ts
Normal file
1
packages/cli/src/constants.ts
Normal file
@ -0,0 +1 @@
|
||||
export const DEFAULT_PB_DEV_URL = `http://127.0.0.1:8090`
|
38
packages/cli/src/helpers/RealtimeSubscriptionManager.ts
Normal file
38
packages/cli/src/helpers/RealtimeSubscriptionManager.ts
Normal file
@ -0,0 +1,38 @@
|
||||
import { client } from '../client'
|
||||
import {
|
||||
Pb_Any_Record_Db,
|
||||
Pb_CollectionName,
|
||||
Pb_PkId,
|
||||
Pb_Untrusted_Db,
|
||||
} from '../schema/base'
|
||||
|
||||
export const createRealtimeSubscriptionManager = () => {
|
||||
const subscriptions: { [_: string]: number } = {}
|
||||
|
||||
const subscribe = <TRec extends Pb_Any_Record_Db>(
|
||||
collectionName: Pb_CollectionName,
|
||||
cb: (rec: Pb_Untrusted_Db<TRec>) => void,
|
||||
id?: Pb_PkId
|
||||
) => {
|
||||
const slug = id ? `${collectionName}/${id}` : collectionName
|
||||
|
||||
if (subscriptions[slug]) {
|
||||
subscriptions[slug]++
|
||||
} else {
|
||||
subscriptions[slug] = 1
|
||||
client.realtime.subscribe(slug, (e) => {
|
||||
console.log(`Realtime update`, { e })
|
||||
cb(e.record as unknown as Pb_Untrusted_Db<TRec>)
|
||||
})
|
||||
}
|
||||
return () => {
|
||||
subscriptions[slug]--
|
||||
if (subscriptions[slug] === 0) {
|
||||
console.log(`Realtime unsub`)
|
||||
client.realtime.unsubscribe(slug)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return subscribe
|
||||
}
|
13
packages/cli/src/helpers/buildQueryFilter.ts
Normal file
13
packages/cli/src/helpers/buildQueryFilter.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { map } from '@s-libs/micro-dash'
|
||||
import { Pb_Any_Record_Db, Pb_QueryParams } from '../schema/base'
|
||||
|
||||
export type FieldStruct<TRec extends Pb_Any_Record_Db> = Partial<{
|
||||
[_ in keyof TRec]: TRec[_]
|
||||
}>
|
||||
|
||||
export const buildQueryFilter = <TRec extends Pb_Any_Record_Db>(
|
||||
fields: FieldStruct<TRec>
|
||||
): Pb_QueryParams => {
|
||||
const filter = map(fields, (v, k) => `${k.toString()} = "${v}"`).join(' and ')
|
||||
return { filter }
|
||||
}
|
22
packages/cli/src/helpers/getOne.ts
Normal file
22
packages/cli/src/helpers/getOne.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import { assertTruthy } from 'util/assert'
|
||||
import { client } from '../client'
|
||||
import {
|
||||
Pb_Any_Collection_Name,
|
||||
Pb_Any_Record_Db,
|
||||
Pb_Untrusted_Db,
|
||||
} from '../schema/base'
|
||||
import { buildQueryFilter, FieldStruct } from './buildQueryFilter'
|
||||
|
||||
export const getOne = async <
|
||||
TRec extends Pb_Any_Record_Db,
|
||||
TFields extends FieldStruct<TRec> = FieldStruct<TRec>
|
||||
>(
|
||||
collectionName: Pb_Any_Collection_Name,
|
||||
fields: TFields
|
||||
) => {
|
||||
const queryParams = buildQueryFilter(fields)
|
||||
const recs = await client.records.getList(collectionName, 1, 2, queryParams)
|
||||
assertTruthy(recs.totalItems < 2, `Expected exactly 0 or 1 items here`)
|
||||
const [item] = recs.items
|
||||
return item ? (recs.items[0] as unknown as Pb_Untrusted_Db<TRec>) : null
|
||||
}
|
6
packages/cli/src/helpers/index.ts
Normal file
6
packages/cli/src/helpers/index.ts
Normal file
@ -0,0 +1,6 @@
|
||||
export * from './getOne'
|
||||
export * from './onAuthStateChanged'
|
||||
export * from './pbUid'
|
||||
export * from './RealtimeSubscriptionManager'
|
||||
export * from './signInAnonymously'
|
||||
export * from './upsert'
|
27
packages/cli/src/helpers/mergeDeep.ts
Normal file
27
packages/cli/src/helpers/mergeDeep.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import { forEach, isObject } from '@s-libs/micro-dash'
|
||||
|
||||
export const mergeDeep = <TObject>(dst: any, src: TObject) => {
|
||||
forEach(src, (v, k) => {
|
||||
if (isObject(v)) {
|
||||
if (dst[k] === undefined) dst[k] = {}
|
||||
if (!isObject(dst[k])) {
|
||||
throw new Error(
|
||||
`${k.toString()} is an object in default, but not in target`
|
||||
)
|
||||
}
|
||||
dst[k] = mergeDeep(dst[k], v)
|
||||
} else {
|
||||
if (isObject(dst[k])) {
|
||||
throw new Error(
|
||||
`${k.toString()} is an object in target, but not in default`
|
||||
)
|
||||
}
|
||||
// The magic: if the target has no value for this field, use the
|
||||
// default value
|
||||
if (dst[k] === undefined) {
|
||||
dst[k] = v
|
||||
}
|
||||
}
|
||||
})
|
||||
return dst as TObject
|
||||
}
|
11
packages/cli/src/helpers/onAuthStateChanged.ts
Normal file
11
packages/cli/src/helpers/onAuthStateChanged.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { UnsubFunc } from 'store/backend/types'
|
||||
import { client } from '../client'
|
||||
|
||||
export const onAuthStateChanged = (
|
||||
cb: (user: typeof client.authStore.model) => void
|
||||
): UnsubFunc => {
|
||||
setTimeout(() => cb(client.authStore.model), 0)
|
||||
return client.authStore.onChange(() => {
|
||||
cb(client.authStore.model)
|
||||
})
|
||||
}
|
9
packages/cli/src/helpers/pbUid.ts
Normal file
9
packages/cli/src/helpers/pbUid.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import { identity } from 'ts-brand'
|
||||
import { client } from '../client'
|
||||
import { Pb_UserId } from '../schema/base'
|
||||
|
||||
export const pbUid = () => {
|
||||
const { id } = client.authStore.model || {}
|
||||
if (!id) return
|
||||
return identity<Pb_UserId>(id)
|
||||
}
|
22
packages/cli/src/helpers/signInAnonymously.ts
Normal file
22
packages/cli/src/helpers/signInAnonymously.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import { customAlphabet } from 'nanoid'
|
||||
import { identity } from 'ts-brand'
|
||||
import { client } from '../client'
|
||||
import { Email, Password, PbCreds } from '../schema/base'
|
||||
|
||||
export const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz')
|
||||
|
||||
export const signInAnonymously = () => {
|
||||
const { email, password } = (() => {
|
||||
const credsJson = localStorage.getItem('__pb_creds')
|
||||
if (credsJson) {
|
||||
return JSON.parse(credsJson) as PbCreds
|
||||
}
|
||||
const email = identity<Email>(`${nanoid()}@harvest.io`)
|
||||
const password = identity<Password>(nanoid())
|
||||
return { email, password }
|
||||
})()
|
||||
|
||||
return client.users.authViaEmail(email, password).catch((e) => {
|
||||
console.error(`Couldn't long in anonymously: ${e}`)
|
||||
})
|
||||
}
|
49
packages/cli/src/helpers/upsert.ts
Normal file
49
packages/cli/src/helpers/upsert.ts
Normal file
@ -0,0 +1,49 @@
|
||||
import { reduce } from '@s-libs/micro-dash'
|
||||
import produce, { Draft, produceWithPatches } from 'immer'
|
||||
import { assertExists, assertTruthy } from 'util/assert'
|
||||
import { client } from '../client'
|
||||
import {
|
||||
Pb_Any_Collection_Name,
|
||||
Pb_Any_Record_Db,
|
||||
Pb_Untrusted_Db,
|
||||
Pb_UserFields,
|
||||
} from '../schema/base'
|
||||
import { buildQueryFilter, FieldStruct } from './buildQueryFilter'
|
||||
import { mergeDeep } from './mergeDeep'
|
||||
|
||||
export const upsert = async <TRow extends Pb_Any_Record_Db>(
|
||||
collectionName: Pb_Any_Collection_Name,
|
||||
filterFields: FieldStruct<TRow>,
|
||||
mutate: (draft: Draft<Pb_UserFields<TRow>>) => void,
|
||||
defaultRec: Pb_UserFields<TRow>
|
||||
) => {
|
||||
const queryParams = buildQueryFilter(filterFields)
|
||||
const recs = await client.records.getList(collectionName, 1, 2, queryParams)
|
||||
assertTruthy(recs.totalItems < 2, `Expected exactly 0 or 1 item to upsert`)
|
||||
if (recs.totalItems === 0) {
|
||||
// Insert
|
||||
client.records.create(collectionName, produce(defaultRec, mutate))
|
||||
} else {
|
||||
// Update
|
||||
const [item] = recs.items as unknown as Pb_Untrusted_Db<TRow>[]
|
||||
assertExists(item, `Expected item here`)
|
||||
const { id } = item
|
||||
const safeItem = mergeDeep(item, defaultRec)
|
||||
const [rec, patches] = produceWithPatches(safeItem, mutate)
|
||||
console.log({ patches })
|
||||
const final = reduce(
|
||||
patches,
|
||||
(carry, patch) => {
|
||||
if (patch.op === 'add' || patch.op === 'replace') {
|
||||
const [key] = patch.path
|
||||
assertExists(key, `Expected key here`)
|
||||
const _key = `${key}` as keyof Pb_UserFields<TRow>
|
||||
carry[_key] = rec[_key]
|
||||
}
|
||||
return carry
|
||||
},
|
||||
{} as Partial<Pb_UserFields<TRow>>
|
||||
)
|
||||
client.records.update(collectionName, id, final)
|
||||
}
|
||||
}
|
20
packages/cli/src/index.ts
Normal file
20
packages/cli/src/index.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import { program } from 'commander'
|
||||
|
||||
declare global {
|
||||
let __go_app: any
|
||||
}
|
||||
|
||||
import 'cross-fetch/polyfill'
|
||||
import 'eventsource'
|
||||
import packagex from '../package.json'
|
||||
import { addPublishCommand } from './commands/publish'
|
||||
console.log(`PBScript ${packagex.version}`)
|
||||
|
||||
program
|
||||
.name('pbscript')
|
||||
.description('CLI for JavaScript extensions for PocketBase ')
|
||||
.version('0.0.1')
|
||||
addPublishCommand(program)
|
||||
// addDevCommand(program)
|
||||
|
||||
program.parse()
|
57
packages/cli/src/providers/CustomAuthStore.ts
Normal file
57
packages/cli/src/providers/CustomAuthStore.ts
Normal file
@ -0,0 +1,57 @@
|
||||
import { Admin, BaseAuthStore, User } from 'pocketbase'
|
||||
|
||||
export interface SerializeOptions {
|
||||
encode?: (val: string | number | boolean) => string
|
||||
maxAge?: number
|
||||
domain?: string
|
||||
path?: string
|
||||
expires?: Date
|
||||
httpOnly?: boolean
|
||||
secure?: boolean
|
||||
priority?: string
|
||||
sameSite?: boolean | string
|
||||
}
|
||||
|
||||
export type SessionState = { token: string; model: User | Admin | null }
|
||||
export type SessionStateSaver = (state: SessionState) => void
|
||||
|
||||
export class CustomAuthStore extends BaseAuthStore {
|
||||
_save: SessionStateSaver
|
||||
|
||||
constructor(state: SessionState, _save: SessionStateSaver) {
|
||||
super()
|
||||
const { token, model } = state
|
||||
this.baseToken = token
|
||||
this.baseModel = model
|
||||
this._save = _save
|
||||
}
|
||||
get get() {
|
||||
// console.log(`Get token`)
|
||||
return this.baseToken
|
||||
}
|
||||
get model(): User | Admin | null {
|
||||
// console.log(`get model`)
|
||||
return this.baseModel
|
||||
}
|
||||
get isValid(): boolean {
|
||||
// console.log(`isValid`)
|
||||
return !!this.baseToken
|
||||
}
|
||||
save(token: string, model: User | Admin | null) {
|
||||
this._save({ token, model })
|
||||
this.baseModel = model
|
||||
this.baseToken = token
|
||||
}
|
||||
clear(): void {
|
||||
throw new Error(`Unsupported clear()`)
|
||||
}
|
||||
loadFromCookie(cookie: string, key?: string | undefined): void {
|
||||
throw new Error(`Unsupported loadFromCookie()`)
|
||||
}
|
||||
exportToCookie(
|
||||
options?: SerializeOptions | undefined,
|
||||
key?: string | undefined
|
||||
): string {
|
||||
throw new Error(`Unsupported exportToCookie()`)
|
||||
}
|
||||
}
|
8
packages/cli/src/util/assert.ts
Normal file
8
packages/cli/src/util/assert.ts
Normal file
@ -0,0 +1,8 @@
|
||||
export function assertExists<TType>(
|
||||
v: TType,
|
||||
message = `Value does not exist`
|
||||
): asserts v is NonNullable<TType> {
|
||||
if (typeof v === 'undefined') {
|
||||
throw new Error(message)
|
||||
}
|
||||
}
|
4
packages/cli/src/util/die.ts
Normal file
4
packages/cli/src/util/die.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export function die(msg: string): never {
|
||||
console.error(msg)
|
||||
process.exit(1)
|
||||
}
|
71
packages/cli/src/util/ensureAdminClient.ts
Normal file
71
packages/cli/src/util/ensureAdminClient.ts
Normal file
@ -0,0 +1,71 @@
|
||||
import prompts from 'prompts'
|
||||
import { SessionState } from '../providers/CustomAuthStore'
|
||||
import { die } from './die'
|
||||
import { isAdmin, pbClient } from './pbClient'
|
||||
import { mkProjectSaver } from './project'
|
||||
|
||||
function isEmail(email: string) {
|
||||
var emailFormat = /^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$/
|
||||
if (email !== '' && email.match(emailFormat)) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
export type ConnectionConfig = {
|
||||
session: SessionState
|
||||
host: string
|
||||
}
|
||||
|
||||
export const ensureAdminClient = async (
|
||||
slug: string,
|
||||
config: ConnectionConfig
|
||||
) => {
|
||||
const saver = mkProjectSaver<ConnectionConfig>(slug)
|
||||
const client = pbClient(config, (session) =>
|
||||
saver((config) => ({ ...config, session }))
|
||||
)
|
||||
const _isAdmin = await isAdmin(client)
|
||||
if (_isAdmin) {
|
||||
return client
|
||||
}
|
||||
|
||||
const { host } = config
|
||||
|
||||
console.log(
|
||||
`You must be logged in to ${host}/_ as a PocketBase admin to continue.`
|
||||
)
|
||||
|
||||
while (true) {
|
||||
const response = await prompts(
|
||||
[
|
||||
{
|
||||
type: 'text',
|
||||
name: 'username',
|
||||
message: 'Username (email):',
|
||||
validate: (value: string) =>
|
||||
isEmail(value) ? true : `Enter an email address`,
|
||||
},
|
||||
{
|
||||
type: 'password',
|
||||
name: 'password',
|
||||
message: 'Password:',
|
||||
validate: (value: string) =>
|
||||
value.length > 0 ? true : `Enter a password`,
|
||||
},
|
||||
],
|
||||
{ onCancel: () => die(`Exited.`) }
|
||||
)
|
||||
const { username, password } = response
|
||||
try {
|
||||
await client.admins.authViaEmail(username, password)
|
||||
saver((config) => ({ ...config, host }))
|
||||
console.log(`Successfully logged in as ${username} for project `)
|
||||
break
|
||||
} catch (e) {
|
||||
console.error(`Login failed for ${username}. Try again.`)
|
||||
}
|
||||
}
|
||||
return client
|
||||
}
|
47
packages/cli/src/util/pbClient.ts
Normal file
47
packages/cli/src/util/pbClient.ts
Normal file
@ -0,0 +1,47 @@
|
||||
import { default as PocketBase, default as pocketbaseEs } from 'pocketbase'
|
||||
import {
|
||||
CustomAuthStore,
|
||||
SessionStateSaver,
|
||||
} from '../providers/CustomAuthStore'
|
||||
import { assertExists } from './assert'
|
||||
import { die } from './die'
|
||||
import { ConnectionConfig } from './ensureAdminClient'
|
||||
|
||||
export const pbClient = (
|
||||
config: ConnectionConfig,
|
||||
saver: SessionStateSaver
|
||||
) => {
|
||||
const { host, session } = config
|
||||
const client = new PocketBase(
|
||||
host,
|
||||
'en-US',
|
||||
new CustomAuthStore(session, saver)
|
||||
)
|
||||
return client
|
||||
}
|
||||
|
||||
export const isAdmin = async (client: pocketbaseEs) => {
|
||||
if (!client.authStore.isValid) return false
|
||||
const { model } = client.authStore
|
||||
if (!model) return false
|
||||
const res = await client.admins.getOne(model.id)
|
||||
if (!res) return false
|
||||
return true
|
||||
}
|
||||
|
||||
export const adminPbClient = async (
|
||||
config: ConnectionConfig,
|
||||
saver: SessionStateSaver
|
||||
) => {
|
||||
const client = pbClient(config, saver)
|
||||
if (!client.authStore.isValid) {
|
||||
die(`Must be logged in to PocketBase as an admin.`)
|
||||
}
|
||||
const { model } = client.authStore
|
||||
assertExists(model, `Expected a valid model here`)
|
||||
const res = await client.admins.getOne(model.id)
|
||||
if (!res) {
|
||||
die(`User must be an admin user.`)
|
||||
}
|
||||
return client
|
||||
}
|
52
packages/cli/src/util/project.ts
Normal file
52
packages/cli/src/util/project.ts
Normal file
@ -0,0 +1,52 @@
|
||||
import { findUpSync } from 'find-up'
|
||||
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs'
|
||||
import { dirname, join } from 'path'
|
||||
import { cwd } from 'process'
|
||||
import { SessionState } from '../providers/CustomAuthStore'
|
||||
|
||||
export type TConfigProjectConfig = {
|
||||
dev: {
|
||||
session: SessionState
|
||||
host: string
|
||||
}
|
||||
publish: {
|
||||
session: SessionState
|
||||
host: string
|
||||
}
|
||||
src: string
|
||||
dist: string
|
||||
}
|
||||
|
||||
export const CACHE_FNAME = '.pbcache'
|
||||
|
||||
export const mkProjectSaver = <TConfig>(slug: string) => {
|
||||
type ConfigMutator = (config: Partial<TConfig>) => Partial<TConfig>
|
||||
return (m: ConfigMutator) => {
|
||||
const root = getProjectRoot()
|
||||
const cachePath = join(root, CACHE_FNAME)
|
||||
if (!existsSync(cachePath)) {
|
||||
mkdirSync(cachePath, { recursive: true })
|
||||
}
|
||||
const currentConfig = readSettings<TConfig>(slug)
|
||||
const nextConfig = m(currentConfig)
|
||||
const fname = join(cachePath, slug)
|
||||
const json = JSON.stringify(nextConfig, null, 2)
|
||||
console.log(`Saving to ${fname}`, json)
|
||||
writeFileSync(`${fname}.json`, json)
|
||||
}
|
||||
}
|
||||
|
||||
export const getProjectRoot = () => {
|
||||
const root = findUpSync(`package.json`)
|
||||
if (!root) return cwd()
|
||||
return dirname(root)
|
||||
}
|
||||
|
||||
export const readSettings = <TConfig>(name: string): Partial<TConfig> => {
|
||||
const root = getProjectRoot()
|
||||
const fname = join(root, CACHE_FNAME, `${name}.json`)
|
||||
if (!existsSync(fname)) return {}
|
||||
const json = readFileSync(fname).toString()
|
||||
const settings = JSON.parse(json)
|
||||
return settings as Partial<TConfig>
|
||||
}
|
18
packages/cli/tsconfig.json
Normal file
18
packages/cli/tsconfig.json
Normal file
@ -0,0 +1,18 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"allowJs": true,
|
||||
"checkJs": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"module": "ESNext",
|
||||
"target": "ESNext",
|
||||
"moduleResolution": "node",
|
||||
"noUncheckedIndexedAccess": true,
|
||||
"strictNullChecks": true
|
||||
},
|
||||
"include": ["./src"]
|
||||
}
|
1
packages/js-cloud-funcs/.gitignore
vendored
Normal file
1
packages/js-cloud-funcs/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
dist
|
2
packages/js-cloud-funcs/.npmignore
Normal file
2
packages/js-cloud-funcs/.npmignore
Normal file
@ -0,0 +1,2 @@
|
||||
src
|
||||
tsconfig.json
|
40
packages/js-cloud-funcs/package.json
Normal file
40
packages/js-cloud-funcs/package.json
Normal file
@ -0,0 +1,40 @@
|
||||
{
|
||||
"name": "@pockethost/cloud-functions",
|
||||
"version": "0.0.3",
|
||||
"source": "src/index.ts",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"author": {
|
||||
"name": "Ben Allfree",
|
||||
"url": "https://github.com/benallfree"
|
||||
},
|
||||
"repository": {
|
||||
"url": "https://github.com/benallfree/pockethost"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "chokidar '**' --ignore 'dist' -c 'parcel build --no-cache' --initial",
|
||||
"build": "parcel build --no-cache"
|
||||
},
|
||||
"devDependencies": {
|
||||
"parcel": "^2.7.0",
|
||||
"typescript": "^4.8.3"
|
||||
},
|
||||
"targets": {
|
||||
"main": {
|
||||
"isLibrary": true,
|
||||
"context": "browser",
|
||||
"outputFormat": "esmodule",
|
||||
"sourceMap": true,
|
||||
"includeNodeModules": true
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@s-libs/micro-dash": "^14.1.0",
|
||||
"nanoevents": "^7.0.1",
|
||||
"nanoid": "^4.0.0",
|
||||
"svelte": "^3.51.0",
|
||||
"ts-brand": "^0.0.2"
|
||||
}
|
||||
}
|
282
packages/js-cloud-funcs/readme.md
Normal file
282
packages/js-cloud-funcs/readme.md
Normal file
@ -0,0 +1,282 @@
|
||||
# TS/JS Cloud Functions for PocketBase
|
||||
|
||||
**_Write all your PocketBase server-side logic in TS/JS_**
|
||||
|
||||
PBScript allows you to write [PocketBase](https://pocketbase.io) server-side functions in Typescript or Javascript without recompiling.
|
||||
|
||||
With PBScript, you can:
|
||||
|
||||
- ✅ Write your server-side logic in Typescript or JS
|
||||
- ✅ Access models, collections, transactions, hooks, and all server-side features of PocketBase
|
||||
- ✅ Communicate with PocketBase using a streamlined JavaScript-style API
|
||||
- ✅ Deploy and alter cloud functions without rebuilding _or even restarting_ PocketBase
|
||||
- ✅ Roll back to previous versions
|
||||
- ✅ Use the `pbscript` CLI tool for intelligent dev mode, live deployment, and rollback
|
||||
|
||||
<h3>Table of Contents</h3>
|
||||
|
||||
<!-- @import "[TOC]" {cmd="toc" depthFrom=2 depthTo=6 orderedList=false} -->
|
||||
|
||||
<!-- code_chunk_output -->
|
||||
|
||||
- [Introduction](#introduction)
|
||||
- [Getting Started](#getting-started)
|
||||
- [Developing your script](#developing-your-script)
|
||||
- [Building and deploying your script](#building-and-deploying-your-script)
|
||||
- [API](#api)
|
||||
- [`__go.ping()`](#__goping)
|
||||
- [`__go.addRoute()`](#__goaddroute)
|
||||
- [`__go.app`](#__goapp)
|
||||
- [Advanced](#advanced)
|
||||
- [Upgrading an Existing Custom PocketBase](#upgrading-an-existing-custom-pocketbase)
|
||||
|
||||
<!-- /code_chunk_output -->
|
||||
|
||||
## Introduction
|
||||
|
||||
PBScript extends PocketBase with an [ES-5.1 (ECMA 262)](https://262.ecma-international.org/5.1/) scripting engine powered by [goja](https://github.com/dop251/goja).
|
||||
|
||||
Code executes in a secure sandboxed environment without a [Node.js API](https://nodejs.org/docs/latest/api/) or [Web APIs](https://developer.mozilla.org/en-US/docs/Web/API). Instead, the runtime environment is PocketBase-specific, with full access to PocketBase's [native Go extension API](https://pocketbase.io/docs/use-as-framework/) and includes streamlined functions and helper utilities written in JavaScript (@pbscript/core).
|
||||
|
||||
Use `pbscript` command line tool to build and publish cloud functions to any PBScript-enabled PocketBase instance.
|
||||
|
||||
## Getting Started
|
||||
|
||||
<h3>1. Create a PocketBase instance</h3>
|
||||
|
||||
To run JS functions, you need to run a PocketBase instance which has been enhanced with PBScript.
|
||||
|
||||
You can do it any way you like, just as long as you end up with an admin login for the next section.
|
||||
|
||||
**Option 1 (free): run fully managed on [pockethost.io](https://pockethost.io)**
|
||||
|
||||
The absolute easiest way to provision a new PocketBase instance enhanced with PBScript is to use [pockethost.io](https://pockethost.io). You'll be up and running with a PocketBase URL in under 30 seconds. This is as close to a Firebase/Supabase BaaS experience as you can get.
|
||||
|
||||
**Option 2 (free): run self-managed on fly.io**
|
||||
|
||||
If you'd rather manage your resources yourself, you can follow the instructions in [this thread](https://github.com/pocketbase/pocketbase/discussions/537) to get up and running on fly.io.
|
||||
|
||||
This option takes about 30 minutes to set up.
|
||||
|
||||
**Option 3 (free): run a custom build locally**
|
||||
|
||||
If you just want to run locally or have a special use case, you can create your own build.
|
||||
|
||||
Create `pocketbase.go`:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
pocketbase "github.com/benallfree/pbscript/modules/pbscript"
|
||||
)
|
||||
|
||||
|
||||
func main() {
|
||||
app := pocketbase.New()
|
||||
|
||||
if err := app.Start(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
On the command line, run:
|
||||
|
||||
```bash
|
||||
go get github.com/benallfree/pbscript/modules/pbscript
|
||||
go run pocketbase.go serve
|
||||
|
||||
> Server started at: http://127.0.0.1:8090
|
||||
- REST API: http://127.0.0.1:8090/api/
|
||||
- Admin UI: http://127.0.0.1:8090/_/
|
||||
```
|
||||
|
||||
<h3>2. Create a new JS/TS project</h3>
|
||||
|
||||
You can create any type of TS/JS project you want, but here's the `package.json` we recommend. We also have a [sample PBScript project](https://github.com/benallfree/pbscript/tree/master/packages/sample) you can check out.
|
||||
|
||||
The important part is that your script gets bundled as ES5:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "sample",
|
||||
"version": "0.0.1",
|
||||
"dependencies": {
|
||||
"@pbscript/core": "^0.0.1"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "parcel build --no-cache",
|
||||
"deploy:local": "pbscript deploy --host 'http://127.0.0.1:8090'",
|
||||
"dev": "chokidar 'src/**' './node_modules/**' -c 'yarn build && yarn deploy:local' --initial"
|
||||
},
|
||||
"targets": {
|
||||
"iife": {
|
||||
"source": "./src/index.ts",
|
||||
"context": "browser",
|
||||
"outputFormat": "global",
|
||||
"sourceMap": {
|
||||
"inline": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"chokidar-cli": "^3.0.0",
|
||||
"parcel": "^2.7.0"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<h3>3. Use the `pbscript` CLI tool to log in</h3>
|
||||
|
||||
Enter your project directory and log in to your PocketBase instance using the admin account you created in Step #1:
|
||||
|
||||
```
|
||||
npx pbscript login <username> <password> --host <pocketbase URL>
|
||||
```
|
||||
|
||||
This will create a file named `.pbcache`. Add it to `.gitignore`.
|
||||
|
||||
## Developing your script
|
||||
|
||||
In your command shell, run:
|
||||
|
||||
```bash
|
||||
npx pbscript dev
|
||||
```
|
||||
|
||||
PBScript is designed for a fast development cycle. If you used our `package.json`, this command will watch for changes (in `./dist`) and re-deploy your script on every change.
|
||||
|
||||
PBScript updates do not require a PocketBase restart. Old versions of your script are kept in an archive table for easy rollbacks.
|
||||
|
||||
## Building and deploying your script
|
||||
|
||||
`pbscript` knows how to deploy to any PBScript-enabled PocketBase instance.
|
||||
|
||||
**Connect to your live site**
|
||||
|
||||
First, connect to your live site. `pbscript` will remember your credentials for future commands.
|
||||
|
||||
```bash
|
||||
npx pbscript login <username> <password> --host https://yourproject.pockethost.io
|
||||
Saved to .pbcache
|
||||
```
|
||||
|
||||
**Build your `./dist/index.js` bundle**
|
||||
|
||||
You can build your script bundle however you want, just make sure you end up with ONE bundle file in `./dist/index.js`. If you use source maps, inline them. `pbscript` only deploys this one file.
|
||||
|
||||
**Deploy your latest changes**
|
||||
|
||||
You can deploy changes at any time without restarting PocketBase. All realtime connections and users will remain connected.
|
||||
|
||||
```bash
|
||||
pbscript deploy --host <your pocketbase URL>
|
||||
```
|
||||
|
||||
Or, add it to `package.json`:
|
||||
|
||||
```json
|
||||
"scripts": {
|
||||
"deploy": "pbscript deploy --host https://yourproject.pockethost.io"
|
||||
}
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
PBScript runs in a secure sandboxed environment inside PocketBase. A simplified subset of PocketBase's hooks are available. Complete Typescript definitions are included.
|
||||
|
||||
You might be accustomed to using the [NodeJS API](https://nodejs.org/docs/latest/api/) or the browser [Web API](https://developer.mozilla.org/en-US/docs/Web/API), but those APIs are not core features of ECMAScript. They are not safe or allowed in the PocketBase execution environment.
|
||||
|
||||
Instead, your script runs in the `PocketBaseApi` execution environment. `PocketBaseApi` set of API calls to interact with PocketBase. With it, you can do CRUD, transactions, hook into PocketBase events, new API endpoints, and generally extend PocketBase.
|
||||
|
||||
PBScript imports a `__go` variable containing low-level access to the PocketBase native API and other helpers implemented in Go. @pbscript/core uses `__go` internally, but if there is something missing, you may be able to accomplish it yourself by accessing `__go` directly.
|
||||
|
||||
### `__go.ping()`
|
||||
|
||||
Test function that should return `Hello from Go!`
|
||||
|
||||
Example:
|
||||
|
||||
```ts
|
||||
console.log(__go.ping())
|
||||
```
|
||||
|
||||
### `__go.addRoute()`
|
||||
|
||||
Add an API route.
|
||||
|
||||
Example:
|
||||
|
||||
```ts
|
||||
__go.addRoute({
|
||||
method: HttpMethods.Get,
|
||||
path: `/api/hello`
|
||||
handler: (context)=>{
|
||||
context.send(HttpStatus.Ok, `Hello back!`)
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### `__go.app`
|
||||
|
||||
Low-level primitive providing direct access to the `PocketBase` app instance. Normally you will not access this directly. The @pbscript/core library is built on top of this.
|
||||
|
||||
## Advanced
|
||||
|
||||
### Upgrading an Existing Custom PocketBase
|
||||
|
||||
The easiest way to get PBScript is to use our custom PocketBase module [github.com/benallfree/pbscript/modules/pocketbase](https://github.com/benallfree/pbscript/modules/pocketbase):
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/benallfree/pbscript/packages/pocketbase" // Notice this is a custom version of the PocketBase module
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := pocketbase.New()
|
||||
|
||||
if err := app.Start(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If you are already using a custom PocketBase build, just swap out `github.com/pocketbase/pocketbase` with `github.com/benallfree/pbscript/packages/pocketbase` and everything will work as expected.
|
||||
|
||||
Or, if you prefer, you can do exactly what our custom module does:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/pocketbase/pocketbase"
|
||||
|
||||
"github.com/benallfree/pbscript/packages/pbscript"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := pocketbase.New()
|
||||
pbscript.StartPBScript(app) // Magic line to enable PBScript
|
||||
|
||||
if err := app.Start(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Changelog
|
||||
|
||||
### 0.0.2
|
||||
|
||||
- Transaction support
|
||||
|
||||
### 0.0.1
|
||||
|
||||
- Initial release
|
38
packages/js-cloud-funcs/src/FunctionMarshaler.ts
Normal file
38
packages/js-cloud-funcs/src/FunctionMarshaler.ts
Normal file
@ -0,0 +1,38 @@
|
||||
import { PackedData } from './index'
|
||||
import { assertExists } from './util/assert'
|
||||
import { isFunction } from './util/isFunction'
|
||||
|
||||
// JSON encoder to tokenize functions and store their references
|
||||
|
||||
export type FunctionToken = string
|
||||
export const createFunctionMarshaler = () => {
|
||||
// Create a unique ID for this instance
|
||||
const nanoid = (() => {
|
||||
let i = 0
|
||||
return () => i++
|
||||
})()
|
||||
|
||||
const funcCache: { [_: FunctionToken]: () => any } = {}
|
||||
const encode = (key: string, value: any) => {
|
||||
if (isFunction(value)) {
|
||||
const uuid = `fn_${nanoid()}`
|
||||
funcCache[uuid] = value
|
||||
return uuid
|
||||
}
|
||||
if (value === undefined) {
|
||||
return `ü`
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
const exec = (tok: FunctionToken) => {
|
||||
const fn = funcCache[tok]
|
||||
assertExists(fn, `Function ${tok} does not exist`)
|
||||
const ret = fn()
|
||||
return pack(ret)
|
||||
}
|
||||
|
||||
const pack = (o: any) => JSON.stringify(o, encode) as PackedData
|
||||
|
||||
return { pack, exec }
|
||||
}
|
42
packages/js-cloud-funcs/src/OnBeforeServe.ts
Normal file
42
packages/js-cloud-funcs/src/OnBeforeServe.ts
Normal file
@ -0,0 +1,42 @@
|
||||
import { emitter, NativePocketBaseEvents } from './index'
|
||||
|
||||
|
||||
export const CoreMiddleware = {
|
||||
requireAdminOrUserAuth: () => 'RequireAdminOrUserAuth',
|
||||
}
|
||||
|
||||
export type JsMiddlewareToken = string
|
||||
|
||||
export type JsHttpRoutePath = string
|
||||
|
||||
export type JsAddRouteConfig = {
|
||||
method: HttpMethods
|
||||
path: JsHttpRoutePath
|
||||
handler: (context: HttpRequestContext) => void
|
||||
middlewares: JsMiddlewareToken[]
|
||||
}
|
||||
|
||||
export type OnBeforeServeEvent = {
|
||||
Router: {
|
||||
addRoute: (config: JsAddRouteConfig) => void
|
||||
}
|
||||
}
|
||||
|
||||
export const onBeforeServe = (cb: (e: OnBeforeServeEvent) => void) => {
|
||||
emitter.on(NativePocketBaseEvents.OnBeforeServe, cb)
|
||||
}
|
||||
|
||||
|
||||
export const dispatchObBeforeServe = ()=>{
|
||||
case NativePocketBaseEvents.OnBeforeServe:
|
||||
const e: OnBeforeServeEvent = {
|
||||
Router: {
|
||||
addRoute: (config) => {
|
||||
const packed = pack(config)
|
||||
console.log(`Sending config back ${packed}`)
|
||||
__go_addRoute(packed)
|
||||
},
|
||||
},
|
||||
}
|
||||
emitter.emit(eventName, e)
|
||||
}
|
7
packages/js-cloud-funcs/src/constants.ts
Normal file
7
packages/js-cloud-funcs/src/constants.ts
Normal file
@ -0,0 +1,7 @@
|
||||
export enum HttpMethods {
|
||||
Get = 'GET',
|
||||
}
|
||||
|
||||
export enum HttpResponseStatuses {
|
||||
Ok = 200,
|
||||
}
|
616
packages/js-cloud-funcs/src/events.ts
Normal file
616
packages/js-cloud-funcs/src/events.ts
Normal file
@ -0,0 +1,616 @@
|
||||
import { createEvent } from './util/event'
|
||||
|
||||
export type ModelBeforeCreateEvent = {}
|
||||
const [onModelBeforeCreate, fireModelBeforeCreate] =
|
||||
createEvent<ModelBeforeCreateEvent>(`OnModelBeforeCreate`)
|
||||
export { onModelBeforeCreate, fireModelBeforeCreate }
|
||||
export { onModelAfterCreate, fireModelAfterCreate }
|
||||
export { onModelBeforeUpdate, fireModelBeforeUpdate }
|
||||
export { onModelAfterUpdate, fireModelAfterUpdate }
|
||||
export { onModelBeforeDelete, fireModelBeforeDelete }
|
||||
export { onModelAfterDelete, fireModelAfterDelete }
|
||||
export {
|
||||
onMailerBeforeAdminResetPasswordSend,
|
||||
fireMailerBeforeAdminResetPasswordSend,
|
||||
}
|
||||
export {
|
||||
onMailerAfterAdminResetPasswordSend,
|
||||
fireMailerAfterAdminResetPasswordSend,
|
||||
}
|
||||
export {
|
||||
onMailerBeforeUserResetPasswordSend,
|
||||
fireMailerBeforeUserResetPasswordSend,
|
||||
}
|
||||
export {
|
||||
onMailerAfterUserResetPasswordSend,
|
||||
fireMailerAfterUserResetPasswordSend,
|
||||
}
|
||||
export {
|
||||
onMailerBeforeUserVerificationSend,
|
||||
fireMailerBeforeUserVerificationSend,
|
||||
}
|
||||
export {
|
||||
onMailerAfterUserVerificationSend,
|
||||
fireMailerAfterUserVerificationSend,
|
||||
}
|
||||
export {
|
||||
onMailerBeforeUserChangeEmailSend,
|
||||
fireMailerBeforeUserChangeEmailSend,
|
||||
}
|
||||
export { onMailerAfterUserChangeEmailSend, fireMailerAfterUserChangeEmailSend }
|
||||
export { onRealtimeConnectRequest, fireRealtimeConnectRequest }
|
||||
export { onRealtimeBeforeSubscribeRequest, fireRealtimeBeforeSubscribeRequest }
|
||||
export { onRealtimeAfterSubscribeRequest, fireRealtimeAfterSubscribeRequest }
|
||||
export { onSettingsListRequest, fireSettingsListRequest }
|
||||
export { onSettingsBeforeUpdateRequest, fireSettingsBeforeUpdateRequest }
|
||||
export { onSettingsAfterUpdateRequest, fireSettingsAfterUpdateRequest }
|
||||
export { onFileDownloadRequest, fireFileDownloadRequest }
|
||||
export { onAdminsListRequest, fireAdminsListRequest }
|
||||
export { onAdminViewRequest, fireAdminViewRequest }
|
||||
export { onAdminBeforeCreateRequest, fireAdminBeforeCreateRequest }
|
||||
export { onAdminAfterCreateRequest, fireAdminAfterCreateRequest }
|
||||
export { onAdminBeforeUpdateRequest, fireAdminBeforeUpdateRequest }
|
||||
export { onAdminAfterUpdateRequest, fireAdminAfterUpdateRequest }
|
||||
export { onAdminBeforeDeleteRequest, fireAdminBeforeDeleteRequest }
|
||||
export { onAdminAfterDeleteRequest, fireAdminAfterDeleteRequest }
|
||||
export { onAdminAuthRequest, fireAdminAuthRequest }
|
||||
export { onUsersListRequest, fireUsersListRequest }
|
||||
export { onUserViewRequest, fireUserViewRequest }
|
||||
export { onUserBeforeCreateRequest, fireUserBeforeCreateRequest }
|
||||
export { onUserAfterCreateRequest, fireUserAfterCreateRequest }
|
||||
export { onUserBeforeUpdateRequest, fireUserBeforeUpdateRequest }
|
||||
export { onUserAfterUpdateRequest, fireUserAfterUpdateRequest }
|
||||
export { onUserBeforeDeleteRequest, fireUserBeforeDeleteRequest }
|
||||
export { onUserAfterDeleteRequest, fireUserAfterDeleteRequest }
|
||||
export { onUserAuthRequest, fireUserAuthRequest }
|
||||
export { onUserListExternalAuths, fireUserListExternalAuths }
|
||||
export {
|
||||
onUserBeforeUnlinkExternalAuthRequest,
|
||||
fireUserBeforeUnlinkExternalAuthRequest,
|
||||
}
|
||||
export {
|
||||
onUserAfterUnlinkExternalAuthRequest,
|
||||
fireUserAfterUnlinkExternalAuthRequest,
|
||||
}
|
||||
export { onRecordsListRequest, fireRecordsListRequest }
|
||||
export { onRecordViewRequest, fireRecordViewRequest }
|
||||
export { onRecordBeforeCreateRequest, fireRecordBeforeCreateRequest }
|
||||
export { onRecordAfterCreateRequest, fireRecordAfterCreateRequest }
|
||||
export { onRecordBeforeUpdateRequest, fireRecordBeforeUpdateRequest }
|
||||
export { onRecordAfterUpdateRequest, fireRecordAfterUpdateRequest }
|
||||
export { onRecordBeforeDeleteRequest, fireRecordBeforeDeleteRequest }
|
||||
export { onRecordAfterDeleteRequest, fireRecordAfterDeleteRequest }
|
||||
export { onCollectionsListRequest, fireCollectionsListRequest }
|
||||
export { onCollectionViewRequest, fireCollectionViewRequest }
|
||||
export { onCollectionBeforeCreateRequest, fireCollectionBeforeCreateRequest }
|
||||
export { onCollectionAfterCreateRequest, fireCollectionAfterCreateRequest }
|
||||
export { onCollectionBeforeUpdateRequest, fireCollectionBeforeUpdateRequest }
|
||||
export { onCollectionAfterUpdateRequest, fireCollectionAfterUpdateRequest }
|
||||
export { onCollectionBeforeDeleteRequest, fireCollectionBeforeDeleteRequest }
|
||||
export { onCollectionAfterDeleteRequest, fireCollectionAfterDeleteRequest }
|
||||
export { onCollectionsBeforeImportRequest, fireCollectionsBeforeImportRequest }
|
||||
export { onCollectionsAfterImportRequest, fireCollectionsAfterImportRequest }
|
||||
export { onBeforeServe, fireBeforeServe }
|
||||
|
||||
// OnModelAfterCreate hook is triggered after successfully
|
||||
// inserting a new entry in the DB.
|
||||
export type ModelAfterCreateEvent = {}
|
||||
const [onModelAfterCreate, fireModelAfterCreate] =
|
||||
createEvent<ModelAfterCreateEvent>(`OnModelAfterCreate`)
|
||||
|
||||
// OnModelBeforeUpdate hook is triggered before updating existing
|
||||
// entry in the DB, allowing you to modify or validate the stored data.
|
||||
export type ModelBeforeUpdateEvent = {}
|
||||
const [onModelBeforeUpdate, fireModelBeforeUpdate] =
|
||||
createEvent<ModelBeforeUpdateEvent>(`OnModelBeforeUpdate`)
|
||||
|
||||
// OnModelAfterUpdate hook is triggered after successfully updating
|
||||
// existing entry in the DB.
|
||||
export type ModelAfterUpdateEvent = {}
|
||||
const [onModelAfterUpdate, fireModelAfterUpdate] =
|
||||
createEvent<ModelAfterUpdateEvent>(`OnModelAfterUpdate`)
|
||||
|
||||
// OnModelBeforeDelete hook is triggered before deleting an
|
||||
// existing entry from the DB.
|
||||
export type ModelBeforeDeleteEvent = {}
|
||||
const [onModelBeforeDelete, fireModelBeforeDelete] =
|
||||
createEvent<ModelBeforeDeleteEvent>(`OnModelBeforeDelete`)
|
||||
|
||||
// OnModelAfterDelete is triggered after successfully deleting an
|
||||
// existing entry from the DB.
|
||||
export type ModelAfterDeleteEvent = {}
|
||||
const [onModelAfterDelete, fireModelAfterDelete] =
|
||||
createEvent<ModelAfterDeleteEvent>(`OnModelAfterDelete`)
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
// Mailer event hooks
|
||||
// ---------------------------------------------------------------
|
||||
|
||||
// OnMailerBeforeAdminResetPasswordSend hook is triggered right before
|
||||
// sending a password reset email to an admin.
|
||||
//
|
||||
// Could be used to send your own custom email template if
|
||||
// [hook.StopPropagation] is returned in one of its listeners.
|
||||
export type MailerBeforeAdminResetPasswordSendEvent = {}
|
||||
const [
|
||||
onMailerBeforeAdminResetPasswordSend,
|
||||
fireMailerBeforeAdminResetPasswordSend,
|
||||
] = createEvent<MailerBeforeAdminResetPasswordSendEvent>(
|
||||
`OnMailerBeforeAdminResetPasswordSend`
|
||||
)
|
||||
|
||||
// OnMailerAfterAdminResetPasswordSend hook is triggered after
|
||||
// admin password reset email was successfully sent.
|
||||
export type MailerAfterAdminResetPasswordSendEvent = {}
|
||||
const [
|
||||
onMailerAfterAdminResetPasswordSend,
|
||||
fireMailerAfterAdminResetPasswordSend,
|
||||
] = createEvent<MailerAfterAdminResetPasswordSendEvent>(
|
||||
`OnMailerBeforeAdminResetPasswordSend`
|
||||
)
|
||||
|
||||
// OnMailerBeforeUserResetPasswordSend hook is triggered right before
|
||||
// sending a password reset email to a user.
|
||||
//
|
||||
// Could be used to send your own custom email template if
|
||||
// [hook.StopPropagation] is returned in one of its listeners.
|
||||
export type MailerBeforeUserResetPasswordSendEvent = {}
|
||||
const [
|
||||
onMailerBeforeUserResetPasswordSend,
|
||||
fireMailerBeforeUserResetPasswordSend,
|
||||
] = createEvent<MailerBeforeUserResetPasswordSendEvent>(
|
||||
`OnMailerBeforeUserResetPasswordSend`
|
||||
)
|
||||
|
||||
// OnMailerAfterUserResetPasswordSend hook is triggered after
|
||||
// a user password reset email was successfully sent.
|
||||
export type MailerAfterUserResetPasswordSendEvent = {}
|
||||
const [
|
||||
onMailerAfterUserResetPasswordSend,
|
||||
fireMailerAfterUserResetPasswordSend,
|
||||
] = createEvent<MailerAfterUserResetPasswordSendEvent>(
|
||||
`OnMailerAfterUserResetPasswordSend`
|
||||
)
|
||||
|
||||
// OnMailerBeforeUserVerificationSend hook is triggered right before
|
||||
// sending a verification email to a user.
|
||||
//
|
||||
// Could be used to send your own custom email template if
|
||||
// [hook.StopPropagation] is returned in one of its listeners.
|
||||
export type MailerBeforeUserVerificationSendEvent = {}
|
||||
const [
|
||||
onMailerBeforeUserVerificationSend,
|
||||
fireMailerBeforeUserVerificationSend,
|
||||
] = createEvent<MailerBeforeUserVerificationSendEvent>(
|
||||
`OnMailerBeforeUserVerificationSend`
|
||||
)
|
||||
|
||||
// OnMailerAfterUserVerificationSend hook is triggered after a user
|
||||
// verification email was successfully sent.
|
||||
export type MailerAfterUserVerificationSendEvent = {}
|
||||
const [onMailerAfterUserVerificationSend, fireMailerAfterUserVerificationSend] =
|
||||
createEvent<MailerAfterUserVerificationSendEvent>(
|
||||
`OnMailerAfterUserVerificationSend`
|
||||
)
|
||||
|
||||
// OnMailerBeforeUserChangeEmailSend hook is triggered right before
|
||||
// sending a confirmation new address email to a a user.
|
||||
//
|
||||
// Could be used to send your own custom email template if
|
||||
// [hook.StopPropagation] is returned in one of its listeners.
|
||||
export type MailerBeforeUserChangeEmailSendEvent = {}
|
||||
const [onMailerBeforeUserChangeEmailSend, fireMailerBeforeUserChangeEmailSend] =
|
||||
createEvent<MailerBeforeUserChangeEmailSendEvent>(
|
||||
`OnMailerBeforeUserChangeEmailSend`
|
||||
)
|
||||
|
||||
// OnMailerAfterUserChangeEmailSend hook is triggered after a user
|
||||
// change address email was successfully sent.
|
||||
export type MailerAfterUserChangeEmailSendEvent = {}
|
||||
const [onMailerAfterUserChangeEmailSend, fireMailerAfterUserChangeEmailSend] =
|
||||
createEvent<MailerAfterUserChangeEmailSendEvent>(
|
||||
`OnMailerAfterUserChangeEmailSend`
|
||||
)
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
// Realtime API event hooks
|
||||
// ---------------------------------------------------------------
|
||||
|
||||
// OnRealtimeConnectRequest hook is triggered right before establishing
|
||||
// the SSE client connection.
|
||||
export type RealtimeConnectRequestEvent = {}
|
||||
const [onRealtimeConnectRequest, fireRealtimeConnectRequest] =
|
||||
createEvent<RealtimeConnectRequestEvent>(`OnRealtimeConnectRequest`)
|
||||
|
||||
// OnRealtimeBeforeSubscribeRequest hook is triggered before changing
|
||||
// the client subscriptions, allowing you to further validate and
|
||||
// modify the submitted change.
|
||||
export type RealtimeBeforeSubscribeRequestEvent = {}
|
||||
const [onRealtimeBeforeSubscribeRequest, fireRealtimeBeforeSubscribeRequest] =
|
||||
createEvent<RealtimeBeforeSubscribeRequestEvent>(
|
||||
`OnRealtimeBeforeSubscribeRequest`
|
||||
)
|
||||
|
||||
// OnRealtimeAfterSubscribeRequest hook is triggered after the client
|
||||
// subscriptions were successfully changed.
|
||||
export type RealtimeAfterSubscribeRequestEvent = {}
|
||||
const [onRealtimeAfterSubscribeRequest, fireRealtimeAfterSubscribeRequest] =
|
||||
createEvent<RealtimeAfterSubscribeRequestEvent>(
|
||||
`OnRealtimeAfterSubscribeRequest`
|
||||
)
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
// Settings API event hooks
|
||||
// ---------------------------------------------------------------
|
||||
|
||||
// OnSettingsListRequest hook is triggered on each successful
|
||||
// API Settings list request.
|
||||
//
|
||||
// Could be used to validate or modify the response before
|
||||
// returning it to the client.
|
||||
export type SettingsListRequestEvent = {}
|
||||
const [onSettingsListRequest, fireSettingsListRequest] =
|
||||
createEvent<SettingsListRequestEvent>(`OnSettingsListRequest`)
|
||||
|
||||
// OnSettingsBeforeUpdateRequest hook is triggered before each API
|
||||
// Settings update request (after request data load and before settings persistence).
|
||||
//
|
||||
// Could be used to additionally validate the request data or
|
||||
// implement completely different persistence behavior
|
||||
// (returning [hook.StopPropagation]).
|
||||
export type SettingsBeforeUpdateRequestEvent = {}
|
||||
const [onSettingsBeforeUpdateRequest, fireSettingsBeforeUpdateRequest] =
|
||||
createEvent<SettingsBeforeUpdateRequestEvent>(`OnSettingsBeforeUpdateRequest`)
|
||||
|
||||
// OnSettingsAfterUpdateRequest hook is triggered after each
|
||||
// successful API Settings update request.
|
||||
export type SettingsAfterUpdateRequestEvent = {}
|
||||
const [onSettingsAfterUpdateRequest, fireSettingsAfterUpdateRequest] =
|
||||
createEvent<SettingsAfterUpdateRequestEvent>(`OnSettingsAfterUpdateRequest`)
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
// File API event hooks
|
||||
// ---------------------------------------------------------------
|
||||
|
||||
// OnFileDownloadRequest hook is triggered before each API File download request.
|
||||
//
|
||||
// Could be used to validate or modify the file response before
|
||||
// returning it to the client.
|
||||
export type FileDownloadRequestEvent = {}
|
||||
const [onFileDownloadRequest, fireFileDownloadRequest] =
|
||||
createEvent<FileDownloadRequestEvent>(`OnFileDownloadRequest`)
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
// Admin API event hooks
|
||||
// ---------------------------------------------------------------
|
||||
|
||||
// OnAdminsListRequest hook is triggered on each API Admins list request.
|
||||
//
|
||||
// Could be used to validate or modify the response before returning it to the client.
|
||||
export type AdminsListRequestEvent = {}
|
||||
const [onAdminsListRequest, fireAdminsListRequest] =
|
||||
createEvent<AdminsListRequestEvent>(`OnAdminsListRequest`)
|
||||
|
||||
// OnAdminViewRequest hook is triggered on each API Admin view request.
|
||||
//
|
||||
// Could be used to validate or modify the response before returning it to the client.
|
||||
export type AdminViewRequestEvent = {}
|
||||
const [onAdminViewRequest, fireAdminViewRequest] =
|
||||
createEvent<AdminViewRequestEvent>(`OnAdminViewRequest`)
|
||||
|
||||
// OnAdminBeforeCreateRequest hook is triggered before each API
|
||||
// Admin create request (after request data load and before model persistence).
|
||||
//
|
||||
// Could be used to additionally validate the request data or implement
|
||||
// completely different persistence behavior (returning [hook.StopPropagation]).
|
||||
export type AdminBeforeCreateRequestEvent = {}
|
||||
const [onAdminBeforeCreateRequest, fireAdminBeforeCreateRequest] =
|
||||
createEvent<AdminBeforeCreateRequestEvent>(`OnAdminBeforeCreateRequest`)
|
||||
|
||||
// OnAdminAfterCreateRequest hook is triggered after each
|
||||
// successful API Admin create request.
|
||||
export type AdminAfterCreateRequestEvent = {}
|
||||
const [onAdminAfterCreateRequest, fireAdminAfterCreateRequest] =
|
||||
createEvent<AdminAfterCreateRequestEvent>(`OnAdminAfterCreateRequest`)
|
||||
|
||||
// OnAdminBeforeUpdateRequest hook is triggered before each API
|
||||
// Admin update request (after request data load and before model persistence).
|
||||
//
|
||||
// Could be used to additionally validate the request data or implement
|
||||
// completely different persistence behavior (returning [hook.StopPropagation]).
|
||||
export type AdminBeforeUpdateRequestEvent = {}
|
||||
const [onAdminBeforeUpdateRequest, fireAdminBeforeUpdateRequest] =
|
||||
createEvent<AdminBeforeUpdateRequestEvent>(`OnAdminBeforeUpdateRequest`)
|
||||
|
||||
// OnAdminAfterUpdateRequest hook is triggered after each
|
||||
// successful API Admin update request.
|
||||
export type AdminAfterUpdateRequestEvent = {}
|
||||
const [onAdminAfterUpdateRequest, fireAdminAfterUpdateRequest] =
|
||||
createEvent<AdminAfterUpdateRequestEvent>(`OnAdminAfterUpdateRequest`)
|
||||
|
||||
// OnAdminBeforeDeleteRequest hook is triggered before each API
|
||||
// Admin delete request (after model load and before actual deletion).
|
||||
//
|
||||
// Could be used to additionally validate the request data or implement
|
||||
// completely different delete behavior (returning [hook.StopPropagation]).
|
||||
export type AdminBeforeDeleteRequestEvent = {}
|
||||
const [onAdminBeforeDeleteRequest, fireAdminBeforeDeleteRequest] =
|
||||
createEvent<AdminBeforeDeleteRequestEvent>(`OnAdminBeforeDeleteRequest`)
|
||||
|
||||
// OnAdminAfterDeleteRequest hook is triggered after each
|
||||
// successful API Admin delete request.
|
||||
export type AdminAfterDeleteRequestEvent = {}
|
||||
const [onAdminAfterDeleteRequest, fireAdminAfterDeleteRequest] =
|
||||
createEvent<AdminAfterDeleteRequestEvent>(`OnAdminAfterDeleteRequest`)
|
||||
|
||||
// OnAdminAuthRequest hook is triggered on each successful API Admin
|
||||
// authentication request (sign-in, token refresh, etc.).
|
||||
//
|
||||
// Could be used to additionally validate or modify the
|
||||
// authenticated admin data and token.
|
||||
export type AdminAuthRequestEvent = {}
|
||||
const [onAdminAuthRequest, fireAdminAuthRequest] =
|
||||
createEvent<AdminAuthRequestEvent>(`OnAdminAuthRequest`)
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
// User API event hooks
|
||||
// ---------------------------------------------------------------
|
||||
|
||||
// OnUsersListRequest hook is triggered on each API Users list request.
|
||||
//
|
||||
// Could be used to validate or modify the response before returning it to the client.
|
||||
export type UsersListRequestEvent = {}
|
||||
const [onUsersListRequest, fireUsersListRequest] =
|
||||
createEvent<UsersListRequestEvent>(`OnUsersListRequest`)
|
||||
|
||||
// OnUserViewRequest hook is triggered on each API User view request.
|
||||
//
|
||||
// Could be used to validate or modify the response before returning it to the client.
|
||||
export type UserViewRequestEvent = {}
|
||||
const [onUserViewRequest, fireUserViewRequest] =
|
||||
createEvent<UserViewRequestEvent>(`OnUserViewRequest`)
|
||||
|
||||
// OnUserBeforeCreateRequest hook is triggered before each API User
|
||||
// create request (after request data load and before model persistence).
|
||||
//
|
||||
// Could be used to additionally validate the request data or implement
|
||||
// completely different persistence behavior (returning [hook.StopPropagation]).
|
||||
export type UserBeforeCreateRequestEvent = {}
|
||||
const [onUserBeforeCreateRequest, fireUserBeforeCreateRequest] =
|
||||
createEvent<UserBeforeCreateRequestEvent>(`OnUserBeforeCreateRequest`)
|
||||
|
||||
// OnUserAfterCreateRequest hook is triggered after each
|
||||
// successful API User create request.
|
||||
export type UserAfterCreateRequestEvent = {}
|
||||
const [onUserAfterCreateRequest, fireUserAfterCreateRequest] =
|
||||
createEvent<UserAfterCreateRequestEvent>(`OnUserAfterCreateRequest`)
|
||||
|
||||
// OnUserBeforeUpdateRequest hook is triggered before each API User
|
||||
// update request (after request data load and before model persistence).
|
||||
//
|
||||
// Could be used to additionally validate the request data or implement
|
||||
// completely different persistence behavior (returning [hook.StopPropagation]).
|
||||
export type UserBeforeUpdateRequestEvent = {}
|
||||
const [onUserBeforeUpdateRequest, fireUserBeforeUpdateRequest] =
|
||||
createEvent<UserBeforeUpdateRequestEvent>(`OnUserBeforeUpdateRequest`)
|
||||
|
||||
// OnUserAfterUpdateRequest hook is triggered after each
|
||||
// successful API User update request.
|
||||
export type UserAfterUpdateRequestEvent = {}
|
||||
const [onUserAfterUpdateRequest, fireUserAfterUpdateRequest] =
|
||||
createEvent<UserAfterUpdateRequestEvent>(`OnUserAfterUpdateRequest`)
|
||||
|
||||
// OnUserBeforeDeleteRequest hook is triggered before each API User
|
||||
// delete request (after model load and before actual deletion).
|
||||
//
|
||||
// Could be used to additionally validate the request data or implement
|
||||
// completely different delete behavior (returning [hook.StopPropagation]).
|
||||
export type UserBeforeDeleteRequestEvent = {}
|
||||
const [onUserBeforeDeleteRequest, fireUserBeforeDeleteRequest] =
|
||||
createEvent<UserBeforeDeleteRequestEvent>(`OnUserBeforeDeleteRequest`)
|
||||
|
||||
// OnUserAfterDeleteRequest hook is triggered after each
|
||||
// successful API User delete request.
|
||||
export type UserAfterDeleteRequestEvent = {}
|
||||
const [onUserAfterDeleteRequest, fireUserAfterDeleteRequest] =
|
||||
createEvent<UserAfterDeleteRequestEvent>(`OnUserAfterDeleteRequest`)
|
||||
|
||||
// OnUserAuthRequest hook is triggered on each successful API User
|
||||
// authentication request (sign-in, token refresh, etc.).
|
||||
//
|
||||
// Could be used to additionally validate or modify the
|
||||
// authenticated user data and token.
|
||||
export type UserAuthRequestEvent = {}
|
||||
const [onUserAuthRequest, fireUserAuthRequest] =
|
||||
createEvent<UserAuthRequestEvent>(`OnUserAuthRequest`)
|
||||
|
||||
// OnUserListExternalAuths hook is triggered on each API user's external auths list request.
|
||||
//
|
||||
// Could be used to validate or modify the response before returning it to the client.
|
||||
export type UserListExternalAuthsEvent = {}
|
||||
const [onUserListExternalAuths, fireUserListExternalAuths] =
|
||||
createEvent<UserListExternalAuthsEvent>(`OnUserListExternalAuths`)
|
||||
|
||||
// OnUserBeforeUnlinkExternalAuthRequest hook is triggered before each API user's
|
||||
// external auth unlink request (after models load and before the actual relation deletion).
|
||||
//
|
||||
// Could be used to additionally validate the request data or implement
|
||||
// completely different delete behavior (returning [hook.StopPropagation]).
|
||||
export type UserBeforeUnlinkExternalAuthRequestEvent = {}
|
||||
const [
|
||||
onUserBeforeUnlinkExternalAuthRequest,
|
||||
fireUserBeforeUnlinkExternalAuthRequest,
|
||||
] = createEvent<UserBeforeUnlinkExternalAuthRequestEvent>(
|
||||
`OnUserBeforeUnlinkExternalAuthRequest`
|
||||
)
|
||||
|
||||
// OnUserAfterUnlinkExternalAuthRequest hook is triggered after each
|
||||
// successful API user's external auth unlink request.
|
||||
export type UserAfterUnlinkExternalAuthRequestEvent = {}
|
||||
const [
|
||||
onUserAfterUnlinkExternalAuthRequest,
|
||||
fireUserAfterUnlinkExternalAuthRequest,
|
||||
] = createEvent<UserAfterUnlinkExternalAuthRequestEvent>(
|
||||
`OnUserAfterUnlinkExternalAuthRequest`
|
||||
)
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
// Record API event hooks
|
||||
// ---------------------------------------------------------------
|
||||
|
||||
// OnRecordsListRequest hook is triggered on each API Records list request.
|
||||
//
|
||||
// Could be used to validate or modify the response before returning it to the client.
|
||||
export type RecordsListRequestEvent = {}
|
||||
const [onRecordsListRequest, fireRecordsListRequest] =
|
||||
createEvent<RecordsListRequestEvent>(`OnRecordsListRequest`)
|
||||
|
||||
// OnRecordViewRequest hook is triggered on each API Record view request.
|
||||
//
|
||||
// Could be used to validate or modify the response before returning it to the client.
|
||||
export type RecordViewRequestEvent = {}
|
||||
const [onRecordViewRequest, fireRecordViewRequest] =
|
||||
createEvent<RecordViewRequestEvent>(`OnRecordViewRequest`)
|
||||
|
||||
// OnRecordBeforeCreateRequest hook is triggered before each API Record
|
||||
// create request (after request data load and before model persistence).
|
||||
//
|
||||
// Could be used to additionally validate the request data or implement
|
||||
// completely different persistence behavior (returning [hook.StopPropagation]).
|
||||
export type RecordBeforeCreateRequestEvent = {}
|
||||
const [onRecordBeforeCreateRequest, fireRecordBeforeCreateRequest] =
|
||||
createEvent<RecordBeforeCreateRequestEvent>(`OnRecordBeforeCreateRequest`)
|
||||
|
||||
// OnRecordAfterCreateRequest hook is triggered after each
|
||||
// successful API Record create request.
|
||||
export type RecordAfterCreateRequestEvent = {}
|
||||
const [onRecordAfterCreateRequest, fireRecordAfterCreateRequest] =
|
||||
createEvent<RecordAfterCreateRequestEvent>(`OnRecordAfterCreateRequest`)
|
||||
|
||||
// OnRecordBeforeUpdateRequest hook is triggered before each API Record
|
||||
// update request (after request data load and before model persistence).
|
||||
//
|
||||
// Could be used to additionally validate the request data or implement
|
||||
// completely different persistence behavior (returning [hook.StopPropagation]).
|
||||
export type RecordBeforeUpdateRequestEvent = {}
|
||||
const [onRecordBeforeUpdateRequest, fireRecordBeforeUpdateRequest] =
|
||||
createEvent<RecordBeforeUpdateRequestEvent>(`OnRecordBeforeUpdateRequest`)
|
||||
|
||||
// OnRecordAfterUpdateRequest hook is triggered after each
|
||||
// successful API Record update request.
|
||||
export type RecordAfterUpdateRequestEvent = {}
|
||||
const [onRecordAfterUpdateRequest, fireRecordAfterUpdateRequest] =
|
||||
createEvent<RecordAfterUpdateRequestEvent>(`OnRecordAfterUpdateRequest`)
|
||||
|
||||
// OnRecordBeforeDeleteRequest hook is triggered before each API Record
|
||||
// delete request (after model load and before actual deletion).
|
||||
//
|
||||
// Could be used to additionally validate the request data or implement
|
||||
// completely different delete behavior (returning [hook.StopPropagation]).
|
||||
export type RecordBeforeDeleteRequestEvent = {}
|
||||
const [onRecordBeforeDeleteRequest, fireRecordBeforeDeleteRequest] =
|
||||
createEvent<RecordBeforeDeleteRequestEvent>(`OnRecordBeforeDeleteRequest`)
|
||||
|
||||
// OnRecordAfterDeleteRequest hook is triggered after each
|
||||
// successful API Record delete request.
|
||||
export type RecordAfterDeleteRequestEvent = {}
|
||||
const [onRecordAfterDeleteRequest, fireRecordAfterDeleteRequest] =
|
||||
createEvent<RecordAfterDeleteRequestEvent>(`OnRecordAfterDeleteRequest`)
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
// Collection API event hooks
|
||||
// ---------------------------------------------------------------
|
||||
|
||||
// OnCollectionsListRequest hook is triggered on each API Collections list request.
|
||||
//
|
||||
// Could be used to validate or modify the response before returning it to the client.
|
||||
export type CollectionsListRequestEvent = {}
|
||||
const [onCollectionsListRequest, fireCollectionsListRequest] =
|
||||
createEvent<CollectionsListRequestEvent>(`OnCollectionsListRequest`)
|
||||
|
||||
// OnCollectionViewRequest hook is triggered on each API Collection view request.
|
||||
//
|
||||
// Could be used to validate or modify the response before returning it to the client.
|
||||
export type CollectionViewRequestEvent = {}
|
||||
const [onCollectionViewRequest, fireCollectionViewRequest] =
|
||||
createEvent<CollectionViewRequestEvent>(`OnCollectionViewRequest`)
|
||||
|
||||
// OnCollectionBeforeCreateRequest hook is triggered before each API Collection
|
||||
// create request (after request data load and before model persistence).
|
||||
//
|
||||
// Could be used to additionally validate the request data or implement
|
||||
// completely different persistence behavior (returning [hook.StopPropagation]).
|
||||
export type CollectionBeforeCreateRequestEvent = {}
|
||||
const [onCollectionBeforeCreateRequest, fireCollectionBeforeCreateRequest] =
|
||||
createEvent<CollectionBeforeCreateRequestEvent>(
|
||||
`OnCollectionBeforeCreateRequest`
|
||||
)
|
||||
|
||||
// OnCollectionAfterCreateRequest hook is triggered after each
|
||||
// successful API Collection create request.
|
||||
export type CollectionAfterCreateRequestEvent = {}
|
||||
const [onCollectionAfterCreateRequest, fireCollectionAfterCreateRequest] =
|
||||
createEvent<CollectionAfterCreateRequestEvent>(
|
||||
`OnCollectionAfterCreateRequest`
|
||||
)
|
||||
|
||||
// OnCollectionBeforeUpdateRequest hook is triggered before each API Collection
|
||||
// update request (after request data load and before model persistence).
|
||||
//
|
||||
// Could be used to additionally validate the request data or implement
|
||||
// completely different persistence behavior (returning [hook.StopPropagation]).
|
||||
export type CollectionBeforeUpdateRequestEvent = {}
|
||||
const [onCollectionBeforeUpdateRequest, fireCollectionBeforeUpdateRequest] =
|
||||
createEvent<CollectionBeforeUpdateRequestEvent>(
|
||||
`OnCollectionBeforeUpdateRequest`
|
||||
)
|
||||
|
||||
// OnCollectionAfterUpdateRequest hook is triggered after each
|
||||
// successful API Collection update request.
|
||||
export type CollectionAfterUpdateRequestEvent = {}
|
||||
const [onCollectionAfterUpdateRequest, fireCollectionAfterUpdateRequest] =
|
||||
createEvent<CollectionAfterUpdateRequestEvent>(
|
||||
`OnCollectionAfterUpdateRequest`
|
||||
)
|
||||
|
||||
// OnCollectionBeforeDeleteRequest hook is triggered before each API
|
||||
// Collection delete request (after model load and before actual deletion).
|
||||
//
|
||||
// Could be used to additionally validate the request data or implement
|
||||
// completely different delete behavior (returning [hook.StopPropagation]).
|
||||
export type CollectionBeforeDeleteRequestEvent = {}
|
||||
const [onCollectionBeforeDeleteRequest, fireCollectionBeforeDeleteRequest] =
|
||||
createEvent<CollectionBeforeDeleteRequestEvent>(
|
||||
`OnCollectionBeforeDeleteRequest`
|
||||
)
|
||||
|
||||
// OnCollectionAfterDeleteRequest hook is triggered after each
|
||||
// successful API Collection delete request.
|
||||
export type CollectionAfterDeleteRequestEvent = {}
|
||||
const [onCollectionAfterDeleteRequest, fireCollectionAfterDeleteRequest] =
|
||||
createEvent<CollectionAfterDeleteRequestEvent>(
|
||||
`OnCollectionAfterDeleteRequest`
|
||||
)
|
||||
|
||||
// OnCollectionsBeforeImportRequest hook is triggered before each API
|
||||
// collections import request (after request data load and before the actual import).
|
||||
//
|
||||
// Could be used to additionally validate the imported collections or
|
||||
// to implement completely different import behavior (returning [hook.StopPropagation]).
|
||||
export type CollectionsBeforeImportRequestEvent = {}
|
||||
const [onCollectionsBeforeImportRequest, fireCollectionsBeforeImportRequest] =
|
||||
createEvent<CollectionsBeforeImportRequestEvent>(
|
||||
`OnCollectionsBeforeImportRequest`
|
||||
)
|
||||
|
||||
// OnCollectionsAfterImportRequest hook is triggered after each
|
||||
// successful API collections import request.
|
||||
export type CollectionsAfterImportRequestEvent = {}
|
||||
const [onCollectionsAfterImportRequest, fireCollectionsAfterImportRequest] =
|
||||
createEvent<CollectionsAfterImportRequestEvent>(
|
||||
`OnCollectionsAfterImportRequest`
|
||||
)
|
||||
|
||||
export type BeforeServeEvent = {}
|
||||
const [onBeforeServe, fireBeforeServe] =
|
||||
createEvent<BeforeServeEvent>(`OnBeforeServe`)
|
11
packages/js-cloud-funcs/src/index.ts
Normal file
11
packages/js-cloud-funcs/src/index.ts
Normal file
@ -0,0 +1,11 @@
|
||||
export * from './routes'
|
||||
export * from './transaction'
|
||||
export * from './types'
|
||||
|
||||
import { PBScriptApi } from './types/PBScriptApi'
|
||||
|
||||
const api: PBScriptApi = {
|
||||
ping: () => 'Hello from PBScript!',
|
||||
}
|
||||
|
||||
registerJsFuncs(api)
|
71
packages/js-cloud-funcs/src/routes.ts
Normal file
71
packages/js-cloud-funcs/src/routes.ts
Normal file
@ -0,0 +1,71 @@
|
||||
import {
|
||||
EchoHttpMethods,
|
||||
EchoHttpResponseStatuses,
|
||||
EchoMiddlewareFunc,
|
||||
EchoRoute,
|
||||
User,
|
||||
} from './types'
|
||||
|
||||
export type HandlerApi = {
|
||||
ok: (s: string) => void
|
||||
error: (s: string) => void
|
||||
user: () => User
|
||||
}
|
||||
|
||||
export type HandlerFunc = (api: HandlerApi) => void
|
||||
|
||||
export type Route = {
|
||||
method: EchoHttpMethods
|
||||
path: string
|
||||
handler: HandlerFunc
|
||||
middlewares: EchoMiddlewareFunc[]
|
||||
}
|
||||
|
||||
export const addRoute = (route: Route) => {
|
||||
// Ensure path begins with `/api/`
|
||||
const { path } = route
|
||||
if (!path.startsWith(`/api/`)) {
|
||||
console.log(`API error ${path}`)
|
||||
throw new Error(`All routes must take the form /api/<path...>`)
|
||||
}
|
||||
|
||||
const _route: EchoRoute = {
|
||||
method: route.method,
|
||||
path: route.path,
|
||||
handler: (c) => {
|
||||
const api: HandlerApi = {
|
||||
ok: (message) => {
|
||||
c.string(EchoHttpResponseStatuses.Ok, message)
|
||||
},
|
||||
error: (message) => {
|
||||
c.string(EchoHttpResponseStatuses.Error, message)
|
||||
},
|
||||
user: () => c.get<User>('user'),
|
||||
}
|
||||
route.handler(api)
|
||||
},
|
||||
middlewares: route.middlewares,
|
||||
}
|
||||
return __go.addRoute(_route)
|
||||
}
|
||||
|
||||
export const auth = __go.requireAdminOrUserAuth()
|
||||
|
||||
export const get = (
|
||||
path: string,
|
||||
handler: HandlerFunc,
|
||||
middlewares?: EchoMiddlewareFunc[]
|
||||
) => {
|
||||
addRoute({
|
||||
method: EchoHttpMethods.Get,
|
||||
path: `/api${path}`,
|
||||
handler: (api) => {
|
||||
try {
|
||||
handler(api)
|
||||
} catch (e) {
|
||||
api.error(`${e}`)
|
||||
}
|
||||
},
|
||||
middlewares: middlewares || [],
|
||||
})
|
||||
}
|
36
packages/js-cloud-funcs/src/transaction.ts
Normal file
36
packages/js-cloud-funcs/src/transaction.ts
Normal file
@ -0,0 +1,36 @@
|
||||
export type TransactionApi = {
|
||||
execute: (sql: string) => void
|
||||
all: <TRow>(sql: string) => TRow[]
|
||||
one: <TRow>(sql: string) => TRow
|
||||
}
|
||||
|
||||
export type TransactionCallback = (api: TransactionApi) => void
|
||||
|
||||
export const runInTransaction = (cb: TransactionCallback) => {
|
||||
__go.app.dao().runInTransaction((txDao) => {
|
||||
const execute = (sql: string) => {
|
||||
const q = txDao.dB().newQuery(sql)
|
||||
return q.execute()
|
||||
}
|
||||
const all = <TRow>(sql: string): TRow[] => {
|
||||
const q = txDao.dB().newQuery(sql)
|
||||
const rowPtr = __go.newNullStringMapArrayPtr<TRow>()
|
||||
q.all(rowPtr)
|
||||
console.log({ rowPtr })
|
||||
return []
|
||||
}
|
||||
const one = <TRow>(sql: string): TRow => {
|
||||
const q = txDao.dB().newQuery(sql)
|
||||
const row = __go.newNullStringMap<TRow>()
|
||||
q.one(row)
|
||||
return row
|
||||
}
|
||||
|
||||
const api: TransactionApi = {
|
||||
execute,
|
||||
all,
|
||||
one,
|
||||
}
|
||||
cb(api)
|
||||
})
|
||||
}
|
3
packages/js-cloud-funcs/src/types/ConsoleApi.ts
Normal file
3
packages/js-cloud-funcs/src/types/ConsoleApi.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export type ConsoleApi = {
|
||||
log: (...args: any) => void
|
||||
}
|
17
packages/js-cloud-funcs/src/types/GoApi.ts
Normal file
17
packages/js-cloud-funcs/src/types/GoApi.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { Dao } from './go-namespaces/Dao'
|
||||
import { EchoMiddlewareFunc, EchoRoute } from './go-namespaces/Echo'
|
||||
|
||||
export type GoApi = {
|
||||
app: {
|
||||
dao: () => Dao
|
||||
}
|
||||
addRoute: (route: EchoRoute) => void
|
||||
ping: () => string
|
||||
onModelBeforeCreate: any
|
||||
requireAdminAuth: () => EchoMiddlewareFunc
|
||||
requireAdminAuthOnlyIfAny: () => EchoMiddlewareFunc
|
||||
requireAdminOrOwnerAuth: () => EchoMiddlewareFunc
|
||||
requireAdminOrUserAuth: () => EchoMiddlewareFunc
|
||||
newNullStringMapArrayPtr: <TFields>() => TFields[]
|
||||
newNullStringMap: <TFields>() => TFields
|
||||
}
|
4
packages/js-cloud-funcs/src/types/PBScriptApi.ts
Normal file
4
packages/js-cloud-funcs/src/types/PBScriptApi.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export type PingResult = string
|
||||
export type PBScriptApi = {
|
||||
ping: () => PingResult
|
||||
}
|
9
packages/js-cloud-funcs/src/types/__global.ts
Normal file
9
packages/js-cloud-funcs/src/types/__global.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import { ConsoleApi } from './ConsoleApi'
|
||||
import { GoApi } from './GoApi'
|
||||
import { PBScriptApi } from './PBScriptApi'
|
||||
|
||||
declare global {
|
||||
let console: ConsoleApi
|
||||
function registerJsFuncs(api: PBScriptApi): void
|
||||
let __go: GoApi
|
||||
}
|
47
packages/js-cloud-funcs/src/types/database/User.ts
Normal file
47
packages/js-cloud-funcs/src/types/database/User.ts
Normal file
@ -0,0 +1,47 @@
|
||||
import { IsoDate, RecordId, Url } from '.'
|
||||
|
||||
export type ProfileId = RecordId
|
||||
export type Email = string
|
||||
export type UserId = RecordId
|
||||
export type Token = string
|
||||
export type PasswordHash = string
|
||||
|
||||
export type User = {
|
||||
baseAccount: {
|
||||
baseModel: {
|
||||
id: UserId
|
||||
created: IsoDate
|
||||
updated: IsoDate
|
||||
}
|
||||
id: UserId
|
||||
created: IsoDate
|
||||
updated: IsoDate
|
||||
email: Email
|
||||
tokenKey: Token
|
||||
passwordHash: PasswordHash
|
||||
lastResetSentAt: IsoDate
|
||||
}
|
||||
baseModel: {
|
||||
id: UserId
|
||||
created: IsoDate
|
||||
updated: IsoDate
|
||||
}
|
||||
id: UserId
|
||||
created: IsoDate
|
||||
updated: IsoDate
|
||||
email: Email
|
||||
tokenKey: Token
|
||||
passwordHash: PasswordHash
|
||||
lastResetSentAt: IsoDate
|
||||
verified: boolean
|
||||
lastVerificationSentAt: IsoDate
|
||||
profile: {
|
||||
avatar: Url
|
||||
created: IsoDate
|
||||
|
||||
id: ProfileId
|
||||
name: string
|
||||
updated: IsoDate
|
||||
userId: UserId
|
||||
}
|
||||
}
|
5
packages/js-cloud-funcs/src/types/database/index.ts
Normal file
5
packages/js-cloud-funcs/src/types/database/index.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export type RecordId = string
|
||||
export type IsoDate = string
|
||||
export type Url = string
|
||||
|
||||
export * from './User'
|
9
packages/js-cloud-funcs/src/types/go-namespaces/Dao.ts
Normal file
9
packages/js-cloud-funcs/src/types/go-namespaces/Dao.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import { DbxBuilder } from './Dbx'
|
||||
|
||||
export type DaoTransactionCallback = (txDao: Dao) => void
|
||||
|
||||
export type Dao = {
|
||||
// https://pkg.go.dev/github.com/pocketbase/pocketbase@v0.7.9/daos
|
||||
runInTransaction: (cb: DaoTransactionCallback) => void
|
||||
dB: () => DbxBuilder
|
||||
}
|
14
packages/js-cloud-funcs/src/types/go-namespaces/Dbx.ts
Normal file
14
packages/js-cloud-funcs/src/types/go-namespaces/Dbx.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { SqlResult } from './Sql'
|
||||
|
||||
export type DbxQuery = {
|
||||
// https://pkg.go.dev/github.com/pocketbase/dbx#Query
|
||||
// https://pkg.go.dev/github.com/pocketbase/dbx#Query.Execute
|
||||
execute: () => SqlResult
|
||||
all: <TRow>(rows: TRow[]) => void
|
||||
one: <TRow>(row: TRow) => void
|
||||
}
|
||||
|
||||
export type DbxBuilder = {
|
||||
// https://pkg.go.dev/github.com/pocketbase/dbx#Builder
|
||||
newQuery: (sql: string) => DbxQuery
|
||||
}
|
25
packages/js-cloud-funcs/src/types/go-namespaces/Echo.ts
Normal file
25
packages/js-cloud-funcs/src/types/go-namespaces/Echo.ts
Normal file
@ -0,0 +1,25 @@
|
||||
export enum EchoHttpMethods {
|
||||
Get = 'GET',
|
||||
}
|
||||
|
||||
export enum EchoHttpResponseStatuses {
|
||||
Ok = 200,
|
||||
Error = 400,
|
||||
}
|
||||
|
||||
export type EchoHandlerFunc = (context: EchoContext) => void
|
||||
|
||||
export type EchoMiddlewareFunc = (next: EchoHandlerFunc) => EchoHandlerFunc
|
||||
|
||||
export type EchoContext = {
|
||||
get: <T>(key: string) => T
|
||||
string: (code: number, s: string) => void
|
||||
json: (status: EchoHttpResponseStatuses, data: object) => void
|
||||
}
|
||||
|
||||
export type EchoRoute = {
|
||||
method: EchoHttpMethods
|
||||
path: string
|
||||
handler: EchoHandlerFunc
|
||||
middlewares: EchoMiddlewareFunc[]
|
||||
}
|
10
packages/js-cloud-funcs/src/types/go-namespaces/Sql.ts
Normal file
10
packages/js-cloud-funcs/src/types/go-namespaces/Sql.ts
Normal file
@ -0,0 +1,10 @@
|
||||
export type SqlResult = {
|
||||
// https://pkg.go.dev/database/sql#Result
|
||||
// statements varies.
|
||||
lastInsertId: () => number
|
||||
|
||||
// RowsAffected returns the number of rows affected by an
|
||||
// update, insert, or delete. Not every database or database
|
||||
// driver may support this.
|
||||
rowsAffected: () => number
|
||||
}
|
4
packages/js-cloud-funcs/src/types/go-namespaces/index.ts
Normal file
4
packages/js-cloud-funcs/src/types/go-namespaces/index.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export * from './Dao'
|
||||
export * from './Dbx'
|
||||
export * from './Echo'
|
||||
export * from './Sql'
|
5
packages/js-cloud-funcs/src/types/index.ts
Normal file
5
packages/js-cloud-funcs/src/types/index.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export * from './database'
|
||||
export * from './go-namespaces'
|
||||
export * from './GoApi'
|
||||
export * from './PBScriptApi'
|
||||
export * from './__global'
|
8
packages/js-cloud-funcs/src/util/assert.ts
Normal file
8
packages/js-cloud-funcs/src/util/assert.ts
Normal file
@ -0,0 +1,8 @@
|
||||
export function assertExists<TType>(
|
||||
v: TType,
|
||||
message = `Value does not exist`
|
||||
): asserts v is NonNullable<TType> {
|
||||
if (typeof v === 'undefined') {
|
||||
throw new Error(message)
|
||||
}
|
||||
}
|
34
packages/js-cloud-funcs/src/util/event.ts
Normal file
34
packages/js-cloud-funcs/src/util/event.ts
Normal file
@ -0,0 +1,34 @@
|
||||
import { createNanoEvents } from 'nanoevents'
|
||||
|
||||
const emitter = createNanoEvents()
|
||||
|
||||
export type Event<TPayload extends any = void> = TPayload
|
||||
|
||||
export type EventListenerCallback<TEvent extends Event<any>> = (
|
||||
event: TEvent
|
||||
) => void
|
||||
|
||||
export type EventDispatcher<TEvent extends Event<any> = Event> = (
|
||||
event: TEvent
|
||||
) => void
|
||||
|
||||
export type EventUnsubscriber = () => void
|
||||
|
||||
export type EventSubscriber<TEvent extends Event<any>> = (
|
||||
callback: EventListenerCallback<TEvent>
|
||||
) => EventUnsubscriber
|
||||
|
||||
export const createEvent = <TEvent extends Event<any>>(
|
||||
eventName: string
|
||||
): [EventSubscriber<TEvent>, EventDispatcher<TEvent>] => {
|
||||
const fire: EventDispatcher<TEvent> = (event) => {
|
||||
emitter.emit(eventName, event)
|
||||
}
|
||||
|
||||
const listen: EventSubscriber<TEvent> = (cb) => {
|
||||
const unsub = emitter.on(eventName, cb)
|
||||
return unsub
|
||||
}
|
||||
|
||||
return [listen, fire]
|
||||
}
|
3
packages/js-cloud-funcs/src/util/isFunction.ts
Normal file
3
packages/js-cloud-funcs/src/util/isFunction.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export function isFunction(value: any) {
|
||||
return typeof value === 'function'
|
||||
}
|
19
packages/js-cloud-funcs/tsconfig.json
Normal file
19
packages/js-cloud-funcs/tsconfig.json
Normal file
@ -0,0 +1,19 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"allowJs": true,
|
||||
"checkJs": true,
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"module": "ES2015",
|
||||
"moduleResolution": "node",
|
||||
"noUncheckedIndexedAccess": true,
|
||||
"strictNullChecks": true,
|
||||
"typeRoots": ["./node_modules/@types"],
|
||||
"inlineSourceMap": true,
|
||||
"lib": ["ES2015"]
|
||||
},
|
||||
"include": ["./src"]
|
||||
}
|
376
packages/pocketbase-cloud-funcs/engine/pbscript.go
Normal file
376
packages/pocketbase-cloud-funcs/engine/pbscript.go
Normal file
@ -0,0 +1,376 @@
|
||||
package engine
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"unsafe"
|
||||
|
||||
"github.com/benallfree/pbscript/modules/pbscript/event"
|
||||
|
||||
"github.com/goccy/go-json"
|
||||
|
||||
"github.com/dop251/goja"
|
||||
"github.com/labstack/echo/v5"
|
||||
"github.com/pocketbase/dbx"
|
||||
"github.com/pocketbase/pocketbase"
|
||||
"github.com/pocketbase/pocketbase/apis"
|
||||
"github.com/pocketbase/pocketbase/core"
|
||||
"github.com/pocketbase/pocketbase/daos"
|
||||
"github.com/pocketbase/pocketbase/models"
|
||||
"github.com/pocketbase/pocketbase/models/schema"
|
||||
)
|
||||
|
||||
var app *pocketbase.PocketBase
|
||||
var router *echo.Echo
|
||||
var vm *goja.Runtime
|
||||
var cleanups = []func(){}
|
||||
var __go_apis *goja.Object
|
||||
|
||||
const (
|
||||
colorReset = "\033[0m"
|
||||
colorRed = "\033[31m"
|
||||
colorGreen = "\033[32m"
|
||||
colorYellow = "\033[33m"
|
||||
colorBlue = "\033[34m"
|
||||
colorPurple = "\033[35m"
|
||||
colorCyan = "\033[36m"
|
||||
colorWhite = "\033[37m"
|
||||
)
|
||||
|
||||
func logErrorf(format string, args ...any) (n int, err error) {
|
||||
|
||||
s := append(args, string(colorReset))
|
||||
fmt.Print(colorRed)
|
||||
res, err := fmt.Printf(format, s...)
|
||||
fmt.Print(colorReset)
|
||||
return res, err
|
||||
}
|
||||
|
||||
func bindApis() {
|
||||
__go_apis = vm.NewObject()
|
||||
__go_apis.Set("addRoute", func(route echo.Route) {
|
||||
method := route.Method
|
||||
path := route.Path
|
||||
fmt.Printf("Adding route: %s %s\n", method, path)
|
||||
|
||||
router.AddRoute(route)
|
||||
cleanup(
|
||||
fmt.Sprintf("route %s %s", method, path),
|
||||
func() {
|
||||
router.Router().Remove(method, path)
|
||||
})
|
||||
})
|
||||
__go_apis.Set("onModelBeforeCreate", func(cb func(e *core.ModelEvent)) {
|
||||
fmt.Println("Listening in Go for onModelBeforeCreate")
|
||||
unsub := event.On(event.EVT_ON_MODEL_BEFORE_CREATE, func(e *event.UnknownPayload) {
|
||||
// fmt.Println("syntheticevent: OnModelBeforeCreate")
|
||||
// fmt.Println("e", e)
|
||||
// fmt.Println("cb", cb)
|
||||
cb((*core.ModelEvent)(unsafe.Pointer(e)))
|
||||
})
|
||||
cleanup("onModelBeforeCreate", unsub)
|
||||
})
|
||||
__go_apis.Set("onModelAfterCreate", func(cb func(e *core.ModelEvent)) {
|
||||
fmt.Println("Listening in Go for onModelAfterCreate")
|
||||
unsub := event.On(event.EVT_ON_MODEL_AFTER_CREATE, func(e *event.UnknownPayload) {
|
||||
// fmt.Println("syntheticevent: OnModelAfterCreate")
|
||||
// fmt.Println("e", e)
|
||||
// fmt.Println("cb", cb)
|
||||
cb((*core.ModelEvent)(unsafe.Pointer(e)))
|
||||
})
|
||||
cleanup("onModelAfterCreate", unsub)
|
||||
})
|
||||
|
||||
// type TransactionApi struct {
|
||||
// Execute func(sql string)
|
||||
// }
|
||||
// __go_apis.Set("withTransaction", func(cb func(e *TransactionApi)) {
|
||||
// app.Dao().RunInTransaction(func(txDao *daos.Dao) error {
|
||||
// var api = TransactionApi{
|
||||
// Execute: func(sql string) error {
|
||||
// res, err := txDao.DB().Select().NewQuery(sql).Execute()
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
|
||||
// }}
|
||||
// })
|
||||
|
||||
// })
|
||||
|
||||
__go_apis.Set("requireAdminAuth", apis.RequireAdminAuth)
|
||||
__go_apis.Set("requireAdminAuthOnlyIfAny", apis.RequireAdminAuthOnlyIfAny)
|
||||
__go_apis.Set("requireAdminOrOwnerAuth", apis.RequireAdminOrOwnerAuth)
|
||||
__go_apis.Set("requireAdminOrUserAuth", apis.RequireAdminOrUserAuth)
|
||||
__go_apis.Set("app", app)
|
||||
__go_apis.Set("ping", func() string {
|
||||
return "Hello from Go!"
|
||||
})
|
||||
__go_apis.Set("newNullStringMapArrayPtr", func() *[]dbx.NullStringMap {
|
||||
var users2 []dbx.NullStringMap
|
||||
return &users2
|
||||
})
|
||||
__go_apis.Set("newNullStringMap", func() dbx.NullStringMap {
|
||||
var users2 dbx.NullStringMap
|
||||
return users2
|
||||
})
|
||||
}
|
||||
|
||||
func cleanup(msg string, cb func()) {
|
||||
fmt.Printf("adding cleanup: %s\n", msg)
|
||||
cleanups = append(cleanups, func() {
|
||||
fmt.Printf("executing cleanup: %s\n", msg)
|
||||
cb()
|
||||
})
|
||||
}
|
||||
|
||||
func loadActiveScript() (string, error) {
|
||||
|
||||
collection, err := app.Dao().FindCollectionByNameOrId("pbscript")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
recs, err := app.Dao().FindRecordsByExpr(collection, dbx.HashExp{"type": "script", "isActive": true})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(recs) > 1 {
|
||||
return "", fmt.Errorf("expected one active script record but got %d", len(recs))
|
||||
}
|
||||
if len(recs) == 0 {
|
||||
return "", nil // Empty script
|
||||
}
|
||||
rec := recs[0]
|
||||
jsonData := rec.GetStringDataValue("data")
|
||||
type Data struct {
|
||||
Source string `json:"source"`
|
||||
}
|
||||
var json_map Data
|
||||
err = json.Unmarshal([]byte(jsonData), &json_map)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
script := json_map.Source
|
||||
fmt.Printf("Script has been loaded.\n")
|
||||
return script, nil
|
||||
|
||||
}
|
||||
|
||||
func reloadVm() error {
|
||||
fmt.Println("Initializing PBScript engine")
|
||||
vm = goja.New()
|
||||
vm.SetFieldNameMapper(goja.UncapFieldNameMapper())
|
||||
|
||||
// Clean up all handlers
|
||||
fmt.Println("Executing cleanups")
|
||||
for i := 0; i < len(cleanups); i++ {
|
||||
cleanups[i]()
|
||||
}
|
||||
cleanups = nil
|
||||
|
||||
// Load the main script
|
||||
fmt.Println("Loading JS")
|
||||
script, err := loadActiveScript()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Console proxy
|
||||
fmt.Println("Creating console proxy")
|
||||
console := vm.NewObject()
|
||||
console.Set("log", func(s ...goja.Value) {
|
||||
for _, v := range s {
|
||||
fmt.Printf("%s ", v.String())
|
||||
}
|
||||
fmt.Print("\n")
|
||||
})
|
||||
vm.Set("console", console)
|
||||
|
||||
fmt.Println("Creating apis proxy")
|
||||
bindApis()
|
||||
vm.Set("__go", __go_apis)
|
||||
|
||||
fmt.Println("Go initialization complete. Running script.")
|
||||
source := fmt.Sprintf(`
|
||||
console.log('Top of PBScript bootstrap')
|
||||
let __jsfuncs = {ping: ()=>'Hello from PBScript!'}
|
||||
function registerJsFuncs(funcs) {
|
||||
__jsfuncs = {__jsfuncs, ...funcs }
|
||||
}
|
||||
%s
|
||||
console.log('Pinging Go')
|
||||
console.log('Pinging Go succeeded with:', __go.ping())
|
||||
console.log('Bottom of PBScript bootstrap')
|
||||
`, script)
|
||||
_, err = vm.RunString(source)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// js api wireup
|
||||
fmt.Println("Wiring up JS API")
|
||||
type S struct {
|
||||
Ping func() (string, *goja.Exception) `json:"ping"`
|
||||
}
|
||||
jsFuncs := S{}
|
||||
err = vm.ExportTo(vm.Get("__jsfuncs"), &jsFuncs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
{
|
||||
fmt.Println("Pinging JS")
|
||||
res, err := jsFuncs.Ping()
|
||||
if err != nil {
|
||||
return fmt.Errorf("ping() failed with %s", err.Value().Export())
|
||||
} else {
|
||||
fmt.Printf("Ping succeeded with: %s\n", res)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func migrate() error {
|
||||
fmt.Println("Finding collection")
|
||||
_, err := app.Dao().FindCollectionByNameOrId("anything")
|
||||
fmt.Println("Finished collection")
|
||||
if err != nil {
|
||||
err = app.Dao().SaveCollection(&models.Collection{
|
||||
Name: "pbscript",
|
||||
Schema: schema.NewSchema(
|
||||
&schema.SchemaField{
|
||||
Type: schema.FieldTypeText,
|
||||
Name: "type",
|
||||
},
|
||||
&schema.SchemaField{
|
||||
Type: schema.FieldTypeBool,
|
||||
Name: "isActive",
|
||||
},
|
||||
&schema.SchemaField{
|
||||
Type: schema.FieldTypeJson,
|
||||
Name: "data",
|
||||
},
|
||||
),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func watchForScriptChanges() {
|
||||
app.OnModelAfterUpdate().Add(func(e *core.ModelEvent) error {
|
||||
if e.Model.TableName() == "pbscript" {
|
||||
reloadVm()
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
app.OnModelAfterCreate().Add(func(e *core.ModelEvent) error {
|
||||
if e.Model.TableName() == "pbscript" {
|
||||
reloadVm()
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
|
||||
// add new "GET /api/hello" route
|
||||
|
||||
e.Router.AddRoute(echo.Route{
|
||||
Method: http.MethodPost,
|
||||
Path: "/api/pbscript/deploy",
|
||||
Handler: func(c echo.Context) error {
|
||||
json_map := make(map[string]interface{})
|
||||
err := json.NewDecoder(c.Request().Body).Decode(&json_map)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
//json_map has the JSON Payload decoded into a map
|
||||
src := json_map["source"]
|
||||
|
||||
err = app.Dao().RunInTransaction(func(txDao *daos.Dao) error {
|
||||
fmt.Println("Deactivating active script")
|
||||
_, err := txDao.DB().
|
||||
NewQuery("UPDATE pbscript SET isActive=false WHERE type='script'").Execute()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Println("Packaging new record data")
|
||||
bytes, err := json.Marshal(dbx.Params{"source": src})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_json := string(bytes)
|
||||
|
||||
fmt.Println("Saving new model")
|
||||
collection, err := txDao.FindCollectionByNameOrId("pbscript")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
record := models.NewRecord(collection)
|
||||
record.SetDataValue("type", "script")
|
||||
record.SetDataValue("isActive", "true")
|
||||
record.SetDataValue("data", _json)
|
||||
err = txDao.SaveRecord(record)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println(("Record saved"))
|
||||
// _, err = txDao.DB().
|
||||
// NewQuery("INSERT INTO pbscript (type,isActive,data) values ('script', true, {data})").Bind(dbx.Params{"data": _json}).Execute()
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return c.String(http.StatusOK, "ok")
|
||||
|
||||
},
|
||||
Middlewares: []echo.MiddlewareFunc{
|
||||
apis.RequireAdminAuth(),
|
||||
},
|
||||
})
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func initAppEvents() {
|
||||
app.OnModelBeforeCreate().Add(func(e *core.ModelEvent) error {
|
||||
fmt.Println("event: OnModelBeforeCreate")
|
||||
event.Fire(event.EVT_ON_MODEL_BEFORE_CREATE, (*event.UnknownPayload)(unsafe.Pointer(e)))
|
||||
return nil
|
||||
})
|
||||
app.OnModelAfterCreate().Add(func(e *core.ModelEvent) error {
|
||||
fmt.Println("event: OnModelAfterCreate")
|
||||
event.Fire(event.EVT_ON_MODEL_AFTER_CREATE, (*event.UnknownPayload)(unsafe.Pointer(e)))
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func StartPBScript(_app *pocketbase.PocketBase) error {
|
||||
app = _app
|
||||
|
||||
watchForScriptChanges()
|
||||
|
||||
app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
|
||||
migrate()
|
||||
initAppEvents()
|
||||
router = e.Router
|
||||
err := reloadVm()
|
||||
if err != nil {
|
||||
logErrorf("Error loading VM: %s\n", err)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
return nil
|
||||
|
||||
}
|
49
packages/pocketbase-cloud-funcs/event/event.go
Normal file
49
packages/pocketbase-cloud-funcs/event/event.go
Normal file
@ -0,0 +1,49 @@
|
||||
package event
|
||||
|
||||
import "fmt"
|
||||
|
||||
type UnknownPayload any
|
||||
|
||||
var inc = 0
|
||||
var events = map[string]map[int]func(payload *UnknownPayload){}
|
||||
|
||||
const (
|
||||
EVT_ON_MODEL_BEFORE_CREATE = "OnModelBeforeCreate"
|
||||
EVT_ON_MODEL_AFTER_CREATE = "OnModelAfterCreate"
|
||||
)
|
||||
|
||||
func isValid(eventName string) bool {
|
||||
return eventName == EVT_ON_MODEL_BEFORE_CREATE || eventName== EVT_ON_MODEL_AFTER_CREATE
|
||||
}
|
||||
|
||||
func ensureEvent(eventName string) {
|
||||
if !isValid((eventName)) {
|
||||
panic(fmt.Sprintf("%s is not a valid event name", eventName))
|
||||
}
|
||||
if _, ok := events[eventName]; !ok {
|
||||
fmt.Printf("Creating collection for %s\n", eventName)
|
||||
events[eventName] = make(map[int]func(payload *UnknownPayload))
|
||||
}
|
||||
}
|
||||
|
||||
func On(eventName string, cb func(payload *UnknownPayload)) func() {
|
||||
ensureEvent(eventName)
|
||||
|
||||
inc++
|
||||
idx := inc
|
||||
events[eventName][idx] = cb
|
||||
fmt.Printf("Adding %d to %s\n", idx, eventName)
|
||||
return func() {
|
||||
delete(events[eventName], idx)
|
||||
}
|
||||
}
|
||||
|
||||
func Fire(eventName string, payload *UnknownPayload) {
|
||||
ensureEvent(eventName)
|
||||
|
||||
fmt.Printf("Firing %s\n", eventName)
|
||||
for fnId, v := range events[eventName] {
|
||||
fmt.Printf("Dispatching %s to %d\n", eventName, fnId)
|
||||
v(payload)
|
||||
}
|
||||
}
|
90
packages/pocketbase-cloud-funcs/go.mod
Normal file
90
packages/pocketbase-cloud-funcs/go.mod
Normal file
@ -0,0 +1,90 @@
|
||||
module github.com/benallfree/pbscript/modules/pbscript
|
||||
|
||||
go 1.19
|
||||
|
||||
require (
|
||||
github.com/dop251/goja v0.0.0-20220927172339-ea66e911853d
|
||||
github.com/goccy/go-json v0.9.11
|
||||
github.com/labstack/echo/v5 v5.0.0-20220201181537-ed2888cfa198
|
||||
github.com/pocketbase/dbx v1.6.0
|
||||
github.com/pocketbase/pocketbase v0.7.5
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/AlecAivazis/survey/v2 v2.3.5 // indirect
|
||||
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect
|
||||
github.com/aws/aws-sdk-go v1.44.85 // indirect
|
||||
github.com/aws/aws-sdk-go-v2 v1.16.11 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/config v1.17.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.12.14 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.12 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.27 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.18 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.12 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.19 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.9 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.5 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.13 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.12 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.12 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.27.5 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.11.17 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.16.13 // indirect
|
||||
github.com/aws/smithy-go v1.12.1 // indirect
|
||||
github.com/disintegration/imaging v1.6.2 // indirect
|
||||
github.com/dlclark/regexp2 v1.7.0 // indirect
|
||||
github.com/domodwyer/mailyak/v3 v3.3.4 // indirect
|
||||
github.com/fatih/color v1.13.0 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.1 // indirect
|
||||
github.com/ganigeorgiev/fexpr v0.1.1 // indirect
|
||||
github.com/go-ozzo/ozzo-validation/v4 v4.3.0 // indirect
|
||||
github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect
|
||||
github.com/golang-jwt/jwt/v4 v4.4.2 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/google/wire v0.5.0 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.5.1 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.1 // indirect
|
||||
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.16 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.15 // indirect
|
||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect
|
||||
github.com/spf13/cast v1.5.0 // indirect
|
||||
github.com/spf13/cobra v1.5.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/valyala/fasttemplate v1.2.1 // indirect
|
||||
go.opencensus.io v0.23.0 // indirect
|
||||
gocloud.dev v0.26.0 // indirect
|
||||
golang.org/x/crypto v0.0.0-20220824171710-5757bc0c5503 // indirect
|
||||
golang.org/x/image v0.0.0-20220722155232-062f8c9fd539 // indirect
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
|
||||
golang.org/x/net v0.0.0-20220822230855-b0a4917ee28c // indirect
|
||||
golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094 // indirect
|
||||
golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64 // indirect
|
||||
golang.org/x/term v0.0.0-20220722155259-a9ba230a4035 // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9 // indirect
|
||||
golang.org/x/tools v0.1.12 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f // indirect
|
||||
google.golang.org/api v0.94.0 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/genproto v0.0.0-20220822174746-9e6da59bd2fc // indirect
|
||||
google.golang.org/grpc v1.49.0 // indirect
|
||||
google.golang.org/protobuf v1.28.1 // indirect
|
||||
lukechampine.com/uint128 v1.2.0 // indirect
|
||||
modernc.org/cc/v3 v3.36.3 // indirect
|
||||
modernc.org/ccgo/v3 v3.16.9 // indirect
|
||||
modernc.org/libc v1.17.0 // indirect
|
||||
modernc.org/mathutil v1.5.0 // indirect
|
||||
modernc.org/memory v1.2.0 // indirect
|
||||
modernc.org/opt v0.1.3 // indirect
|
||||
modernc.org/sqlite v1.18.1 // indirect
|
||||
modernc.org/strutil v1.1.2 // indirect
|
||||
modernc.org/token v1.0.0 // indirect
|
||||
)
|
1158
packages/pocketbase-cloud-funcs/go.sum
Normal file
1158
packages/pocketbase-cloud-funcs/go.sum
Normal file
File diff suppressed because it is too large
Load Diff
12
packages/pocketbase-cloud-funcs/pocketbase.go
Normal file
12
packages/pocketbase-cloud-funcs/pocketbase.go
Normal file
@ -0,0 +1,12 @@
|
||||
package pocketbase
|
||||
|
||||
import (
|
||||
"github.com/benallfree/pbscript/modules/pbscript/engine"
|
||||
"github.com/pocketbase/pocketbase"
|
||||
)
|
||||
|
||||
func New() *pocketbase.PocketBase {
|
||||
app := pocketbase.New()
|
||||
engine.StartPBScript(app)
|
||||
return app
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user