bunderstack 0.17.0-beta.0 → 0.17.0-beta.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +227 -0
- package/README.md +11 -1
- package/dist/access.d.ts +116 -0
- package/dist/access.d.ts.map +1 -0
- package/dist/access.js +325 -0
- package/dist/access.js.map +1 -0
- package/dist/api/builder.d.ts +145 -0
- package/dist/api/builder.d.ts.map +1 -0
- package/dist/api/builder.js +28 -0
- package/dist/api/builder.js.map +1 -0
- package/dist/api/context.d.ts +34 -0
- package/dist/api/context.d.ts.map +1 -0
- package/dist/api/context.js +32 -0
- package/dist/api/context.js.map +1 -0
- package/dist/api/crud-router.d.ts +290 -0
- package/dist/api/crud-router.d.ts.map +1 -0
- package/dist/api/crud-router.js +249 -0
- package/dist/api/crud-router.js.map +1 -0
- package/dist/api/openapi.d.ts +6 -0
- package/dist/api/openapi.d.ts.map +1 -0
- package/dist/api/openapi.js +146 -0
- package/dist/api/openapi.js.map +1 -0
- package/dist/api/realtime-router.d.ts +76 -0
- package/dist/api/realtime-router.d.ts.map +1 -0
- package/dist/api/realtime-router.js +41 -0
- package/dist/api/realtime-router.js.map +1 -0
- package/dist/api/registry.d.ts +30 -0
- package/dist/api/registry.d.ts.map +1 -0
- package/dist/api/registry.js +239 -0
- package/dist/api/registry.js.map +1 -0
- package/dist/api/router.d.ts +9 -0
- package/dist/api/router.d.ts.map +1 -0
- package/dist/api/router.js +19 -0
- package/dist/api/router.js.map +1 -0
- package/dist/api/storage-router.d.ts +279 -0
- package/dist/api/storage-router.d.ts.map +1 -0
- package/dist/api/storage-router.js +155 -0
- package/dist/api/storage-router.js.map +1 -0
- package/dist/api/types.d.ts +55 -0
- package/dist/api/types.d.ts.map +1 -0
- package/dist/api/types.js +2 -0
- package/dist/api/types.js.map +1 -0
- package/dist/auth.d.ts +31 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +102 -0
- package/dist/auth.js.map +1 -0
- package/dist/blueprint-generator.d.ts +18 -0
- package/dist/blueprint-generator.d.ts.map +1 -0
- package/dist/blueprint-generator.js +125 -0
- package/dist/blueprint-generator.js.map +1 -0
- package/dist/blueprint.d.ts +46 -0
- package/dist/blueprint.d.ts.map +1 -0
- package/dist/blueprint.js +162 -0
- package/dist/blueprint.js.map +1 -0
- package/dist/cli.d.ts +8 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +68 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +76 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +81 -0
- package/dist/config.js.map +1 -0
- package/dist/cron.d.ts +4 -0
- package/dist/cron.d.ts.map +1 -0
- package/dist/cron.js +3 -0
- package/dist/cron.js.map +1 -0
- package/dist/crud-operations.d.ts +43 -0
- package/dist/crud-operations.d.ts.map +1 -0
- package/dist/crud-operations.js +238 -0
- package/dist/crud-operations.js.map +1 -0
- package/dist/database/adapter.d.ts +20 -0
- package/dist/database/adapter.d.ts.map +1 -0
- package/dist/database/adapter.js +2 -0
- package/dist/database/adapter.js.map +1 -0
- package/dist/database/bun-sql.d.ts +3 -0
- package/dist/database/bun-sql.d.ts.map +1 -0
- package/dist/database/bun-sql.js +21 -0
- package/dist/database/bun-sql.js.map +1 -0
- package/dist/database/libsql.d.ts +3 -0
- package/dist/database/libsql.d.ts.map +1 -0
- package/dist/database/libsql.js +22 -0
- package/dist/database/libsql.js.map +1 -0
- package/dist/database/pglite.d.ts +3 -0
- package/dist/database/pglite.d.ts.map +1 -0
- package/dist/database/pglite.js +27 -0
- package/dist/database/pglite.js.map +1 -0
- package/dist/database/postgres-js.d.ts +3 -0
- package/dist/database/postgres-js.d.ts.map +1 -0
- package/dist/database/postgres-js.js +21 -0
- package/dist/database/postgres-js.js.map +1 -0
- package/dist/db.d.ts +18 -0
- package/dist/db.d.ts.map +1 -0
- package/dist/db.js +26 -0
- package/dist/db.js.map +1 -0
- package/dist/dialect.d.ts +17 -0
- package/dist/dialect.d.ts.map +1 -0
- package/dist/dialect.js +22 -0
- package/dist/dialect.js.map +1 -0
- package/dist/email/smtp.d.ts +14 -0
- package/dist/email/smtp.d.ts.map +1 -0
- package/dist/email/smtp.js +27 -0
- package/dist/email/smtp.js.map +1 -0
- package/dist/email.d.ts +39 -0
- package/dist/email.d.ts.map +1 -0
- package/dist/email.js +97 -0
- package/dist/email.js.map +1 -0
- package/dist/env.d.ts +47 -0
- package/dist/env.d.ts.map +1 -0
- package/dist/env.js +99 -0
- package/dist/env.js.map +1 -0
- package/dist/errors.d.ts +125 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +92 -0
- package/dist/errors.js.map +1 -0
- package/dist/handler.d.ts +9 -0
- package/dist/handler.d.ts.map +1 -0
- package/dist/handler.js +22 -0
- package/dist/handler.js.map +1 -0
- package/dist/idempotency.d.ts +17 -0
- package/dist/idempotency.d.ts.map +1 -0
- package/dist/idempotency.js +65 -0
- package/dist/idempotency.js.map +1 -0
- package/dist/index.d.ts +134 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +477 -0
- package/dist/index.js.map +1 -0
- package/dist/internal-tables-pg.d.ts +481 -0
- package/dist/internal-tables-pg.d.ts.map +1 -0
- package/{src/internal-tables-pg.ts → dist/internal-tables-pg.js} +10 -34
- package/dist/internal-tables-pg.js.map +1 -0
- package/dist/internal-tables.d.ts +2509 -0
- package/dist/internal-tables.d.ts.map +1 -0
- package/dist/internal-tables.js +108 -0
- package/dist/internal-tables.js.map +1 -0
- package/dist/jobs/cron.d.ts +15 -0
- package/dist/jobs/cron.d.ts.map +1 -0
- package/dist/jobs/cron.js +74 -0
- package/dist/jobs/cron.js.map +1 -0
- package/dist/jobs/define.d.ts +133 -0
- package/dist/jobs/define.d.ts.map +1 -0
- package/dist/jobs/define.js +74 -0
- package/dist/jobs/define.js.map +1 -0
- package/dist/jobs/index.d.ts +11 -0
- package/dist/jobs/index.d.ts.map +1 -0
- package/dist/jobs/index.js +8 -0
- package/dist/jobs/index.js.map +1 -0
- package/dist/jobs/queue.d.ts +6 -0
- package/dist/jobs/queue.d.ts.map +1 -0
- package/dist/jobs/queue.js +55 -0
- package/dist/jobs/queue.js.map +1 -0
- package/dist/jobs/runtime.d.ts +13 -0
- package/dist/jobs/runtime.d.ts.map +1 -0
- package/dist/jobs/runtime.js +46 -0
- package/dist/jobs/runtime.js.map +1 -0
- package/dist/jobs/slots.d.ts +25 -0
- package/dist/jobs/slots.d.ts.map +1 -0
- package/dist/jobs/slots.js +41 -0
- package/dist/jobs/slots.js.map +1 -0
- package/dist/jobs/worker.d.ts +12 -0
- package/dist/jobs/worker.d.ts.map +1 -0
- package/dist/jobs/worker.js +311 -0
- package/dist/jobs/worker.js.map +1 -0
- package/dist/lifecycle.d.ts +10 -0
- package/dist/lifecycle.d.ts.map +1 -0
- package/dist/lifecycle.js +37 -0
- package/dist/lifecycle.js.map +1 -0
- package/dist/list-query.d.ts +62 -0
- package/dist/list-query.d.ts.map +1 -0
- package/dist/list-query.js +194 -0
- package/dist/list-query.js.map +1 -0
- package/dist/manifest.d.ts +59 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/manifest.js +167 -0
- package/dist/manifest.js.map +1 -0
- package/dist/provision-internals.d.ts +24 -0
- package/dist/provision-internals.d.ts.map +1 -0
- package/dist/provision-internals.js +7 -0
- package/dist/provision-internals.js.map +1 -0
- package/dist/provision.d.ts +23 -0
- package/dist/provision.d.ts.map +1 -0
- package/dist/provision.js +96 -0
- package/dist/provision.js.map +1 -0
- package/dist/rate-limit.d.ts +7 -0
- package/dist/rate-limit.d.ts.map +1 -0
- package/dist/rate-limit.js +49 -0
- package/dist/rate-limit.js.map +1 -0
- package/dist/realtime/facade.d.ts +18 -0
- package/dist/realtime/facade.d.ts.map +1 -0
- package/dist/realtime/facade.js +36 -0
- package/dist/realtime/facade.js.map +1 -0
- package/dist/realtime/filter.d.ts +13 -0
- package/dist/realtime/filter.d.ts.map +1 -0
- package/dist/realtime/filter.js +48 -0
- package/dist/realtime/filter.js.map +1 -0
- package/dist/realtime/heartbeat.d.ts +14 -0
- package/dist/realtime/heartbeat.d.ts.map +1 -0
- package/dist/realtime/heartbeat.js +64 -0
- package/dist/realtime/heartbeat.js.map +1 -0
- package/dist/realtime/publisher.d.ts +21 -0
- package/dist/realtime/publisher.d.ts.map +1 -0
- package/dist/realtime/publisher.js +17 -0
- package/dist/realtime/publisher.js.map +1 -0
- package/dist/schema-export-pg.d.ts +2 -0
- package/dist/schema-export-pg.d.ts.map +1 -0
- package/dist/schema-export-pg.js +4 -0
- package/dist/schema-export-pg.js.map +1 -0
- package/dist/schema-export.d.ts +2 -0
- package/dist/schema-export.d.ts.map +1 -0
- package/dist/schema-export.js +2 -0
- package/dist/schema-export.js.map +1 -0
- package/dist/scope.d.ts +4 -0
- package/dist/scope.d.ts.map +1 -0
- package/dist/scope.js +13 -0
- package/dist/scope.js.map +1 -0
- package/dist/standard-schema.d.ts +13 -0
- package/dist/standard-schema.d.ts.map +1 -0
- package/dist/standard-schema.js +34 -0
- package/dist/standard-schema.js.map +1 -0
- package/dist/storage/buckets.d.ts +80 -0
- package/dist/storage/buckets.d.ts.map +1 -0
- package/dist/storage/buckets.js +178 -0
- package/dist/storage/buckets.js.map +1 -0
- package/dist/storage/delete.d.ts +11 -0
- package/dist/storage/delete.d.ts.map +1 -0
- package/dist/storage/delete.js +18 -0
- package/dist/storage/delete.js.map +1 -0
- package/dist/storage/file-meta.d.ts +37 -0
- package/dist/storage/file-meta.d.ts.map +1 -0
- package/dist/storage/file-meta.js +166 -0
- package/dist/storage/file-meta.js.map +1 -0
- package/dist/storage/index.d.ts +28 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/index.js +5 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/storage/local.d.ts +15 -0
- package/dist/storage/local.d.ts.map +1 -0
- package/dist/storage/local.js +56 -0
- package/dist/storage/local.js.map +1 -0
- package/dist/storage/operations.d.ts +54 -0
- package/dist/storage/operations.d.ts.map +1 -0
- package/dist/storage/operations.js +250 -0
- package/dist/storage/operations.js.map +1 -0
- package/dist/storage/registry.d.ts +10 -0
- package/dist/storage/registry.d.ts.map +1 -0
- package/dist/storage/registry.js +23 -0
- package/dist/storage/registry.js.map +1 -0
- package/dist/storage/s3.d.ts +28 -0
- package/dist/storage/s3.d.ts.map +1 -0
- package/dist/storage/s3.js +71 -0
- package/dist/storage/s3.js.map +1 -0
- package/dist/storage/sweep.d.ts +9 -0
- package/dist/storage/sweep.d.ts.map +1 -0
- package/dist/storage/sweep.js +17 -0
- package/dist/storage/sweep.js.map +1 -0
- package/dist/storage/thumbnails.d.ts +11 -0
- package/dist/storage/thumbnails.d.ts.map +1 -0
- package/dist/storage/thumbnails.js +60 -0
- package/dist/storage/thumbnails.js.map +1 -0
- package/dist/testing.d.ts +27 -0
- package/dist/testing.d.ts.map +1 -0
- package/dist/testing.js +11 -0
- package/dist/testing.js.map +1 -0
- package/dist/typeid-pg.d.ts +19 -0
- package/dist/typeid-pg.d.ts.map +1 -0
- package/{src/typeid-pg.ts → dist/typeid-pg.js} +10 -13
- package/dist/typeid-pg.js.map +1 -0
- package/dist/typeid.d.ts +45 -0
- package/dist/typeid.d.ts.map +1 -0
- package/dist/typeid.js +100 -0
- package/dist/typeid.js.map +1 -0
- package/package.json +81 -29
- package/src/access.ts +0 -552
- package/src/api/api-types.types.ts +0 -106
- package/src/api/builder.ts +0 -52
- package/src/api/context.ts +0 -83
- package/src/api/crud-router.ts +0 -321
- package/src/api/openapi.ts +0 -184
- package/src/api/realtime-router.ts +0 -75
- package/src/api/registry.ts +0 -338
- package/src/api/router.ts +0 -34
- package/src/api/storage-router.ts +0 -224
- package/src/api/types.ts +0 -84
- package/src/auth.ts +0 -117
- package/src/blueprint-generator.ts +0 -203
- package/src/blueprint.ts +0 -264
- package/src/cli.ts +0 -90
- package/src/config.ts +0 -215
- package/src/cron.ts +0 -3
- package/src/crud-operations.ts +0 -488
- package/src/database/adapter.ts +0 -26
- package/src/database/bun-sql.ts +0 -23
- package/src/database/libsql.ts +0 -28
- package/src/database/pglite.ts +0 -33
- package/src/database/postgres-js.ts +0 -33
- package/src/db.ts +0 -61
- package/src/dialect.ts +0 -38
- package/src/email/smtp.ts +0 -45
- package/src/email.ts +0 -154
- package/src/env.ts +0 -180
- package/src/errors.ts +0 -118
- package/src/handler.ts +0 -30
- package/src/idempotency.ts +0 -97
- package/src/index.ts +0 -822
- package/src/internal-tables.ts +0 -149
- package/src/jobs/cron.ts +0 -87
- package/src/jobs/define.ts +0 -283
- package/src/jobs/index.ts +0 -37
- package/src/jobs/queue.ts +0 -67
- package/src/jobs/runtime.ts +0 -58
- package/src/jobs/slots.ts +0 -52
- package/src/jobs/worker.ts +0 -369
- package/src/lifecycle.ts +0 -49
- package/src/list-query.ts +0 -416
- package/src/manifest.ts +0 -297
- package/src/provision-internals.ts +0 -29
- package/src/provision.ts +0 -128
- package/src/rate-limit.ts +0 -71
- package/src/realtime/facade.ts +0 -55
- package/src/realtime/filter.ts +0 -77
- package/src/realtime/heartbeat.ts +0 -80
- package/src/realtime/publisher.ts +0 -46
- package/src/schema-export-pg.ts +0 -7
- package/src/schema-export.ts +0 -5
- package/src/scope.ts +0 -17
- package/src/standard-schema.ts +0 -59
- package/src/storage/buckets.ts +0 -308
- package/src/storage/delete.ts +0 -26
- package/src/storage/file-meta.ts +0 -222
- package/src/storage/index.ts +0 -39
- package/src/storage/local.ts +0 -69
- package/src/storage/operations.ts +0 -398
- package/src/storage/registry.ts +0 -40
- package/src/storage/s3.ts +0 -99
- package/src/storage/sweep.ts +0 -25
- package/src/storage/thumbnails.ts +0 -65
- package/src/testing.ts +0 -31
- package/src/typeid.ts +0 -116
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import type { Driver } from './db'
|
|
2
|
-
// src/provision-internals.ts
|
|
3
|
-
import type { AnyDb, Dialect } from './dialect'
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Hidden handle connecting `createBunderstack()` to the optional
|
|
7
|
-
* `bunderstack/provision` entry. Lives in its own module so the main entry
|
|
8
|
-
* never imports provision code (and its drizzle-kit reference).
|
|
9
|
-
*/
|
|
10
|
-
export const PROVISION_INTERNALS: unique symbol = Symbol.for(
|
|
11
|
-
'bunderstack.provision-internals',
|
|
12
|
-
)
|
|
13
|
-
|
|
14
|
-
export interface ProvisionInternals {
|
|
15
|
-
/** Runtime db typed over the MERGED schema (user + internal tables). */
|
|
16
|
-
db: AnyDb
|
|
17
|
-
/** Merged schema used for push. */
|
|
18
|
-
schema: Record<string, unknown>
|
|
19
|
-
databaseUrl: string
|
|
20
|
-
/** Resolved migrations folder (config `database.migrations`). */
|
|
21
|
-
migrationsFolder: string
|
|
22
|
-
dialect: Dialect
|
|
23
|
-
driver: Driver
|
|
24
|
-
adapter: import('./database/adapter').DatabaseAdapter
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export interface WithProvisionInternals {
|
|
28
|
-
[PROVISION_INTERNALS]?: ProvisionInternals
|
|
29
|
-
}
|
package/src/provision.ts
DELETED
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
// src/provision.ts
|
|
2
|
-
import { access, mkdir } from 'node:fs/promises'
|
|
3
|
-
import { dirname, join } from 'node:path'
|
|
4
|
-
|
|
5
|
-
import type { AnyDb, Dialect } from './dialect'
|
|
6
|
-
|
|
7
|
-
import { detectDialect } from './dialect'
|
|
8
|
-
import {
|
|
9
|
-
PROVISION_INTERNALS,
|
|
10
|
-
type WithProvisionInternals,
|
|
11
|
-
} from './provision-internals'
|
|
12
|
-
|
|
13
|
-
/** Create the local backing directory for file-based urls, per dialect. */
|
|
14
|
-
async function ensureLocalDataDir(
|
|
15
|
-
url: string,
|
|
16
|
-
dialect: Dialect,
|
|
17
|
-
): Promise<void> {
|
|
18
|
-
if (dialect === 'pg') {
|
|
19
|
-
// PGlite data dir: `file:<dir>` or a bare path; server/memory urls need nothing.
|
|
20
|
-
if (/^postgres(ql)?:\/\//.test(url)) return
|
|
21
|
-
const raw = url.startsWith('file:') ? url.slice('file:'.length) : url
|
|
22
|
-
if (raw === ':memory:' || raw.startsWith('memory://')) return
|
|
23
|
-
await mkdir(raw, { recursive: true })
|
|
24
|
-
return
|
|
25
|
-
}
|
|
26
|
-
const match = /^file:(.+)$/.exec(url)
|
|
27
|
-
if (!match) return
|
|
28
|
-
const filePath = match[1]!
|
|
29
|
-
if (filePath === ':memory:') return
|
|
30
|
-
await mkdir(dirname(filePath), { recursive: true })
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
async function exists(path: string): Promise<boolean> {
|
|
34
|
-
try {
|
|
35
|
-
await access(path)
|
|
36
|
-
return true
|
|
37
|
-
} catch {
|
|
38
|
-
return false
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
const DRIZZLE_KIT_HINT =
|
|
43
|
-
'[bunderstack] Schema push requires drizzle-kit, which is not installed.\n' +
|
|
44
|
-
' Development: run `bun add -d drizzle-kit` — provision() will push schema changes to the database on startup.\n' +
|
|
45
|
-
' Production: generate migrations locally with `bunx drizzle-kit generate` and commit the folder — provision() applies them without drizzle-kit.'
|
|
46
|
-
|
|
47
|
-
/** Push the merged schema to the database via drizzle-kit/api. */
|
|
48
|
-
export async function provisionSchema<TSchema extends Record<string, unknown>>(
|
|
49
|
-
db: AnyDb,
|
|
50
|
-
schema: TSchema,
|
|
51
|
-
options?: { force?: boolean; databaseUrl?: string },
|
|
52
|
-
): Promise<void> {
|
|
53
|
-
const dialect = detectDialect(schema)
|
|
54
|
-
if (options?.databaseUrl) {
|
|
55
|
-
await ensureLocalDataDir(options.databaseUrl, dialect)
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
let kit: typeof import('drizzle-kit/api')
|
|
59
|
-
try {
|
|
60
|
-
kit = await import('drizzle-kit/api')
|
|
61
|
-
} catch (cause) {
|
|
62
|
-
throw new Error(DRIZZLE_KIT_HINT, { cause })
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const result =
|
|
66
|
-
dialect === 'pg'
|
|
67
|
-
? await kit.pushSchema(schema, db as never)
|
|
68
|
-
: await kit.pushSQLiteSchema(schema, db as never)
|
|
69
|
-
|
|
70
|
-
if (result.hasDataLoss && !options?.force) {
|
|
71
|
-
throw new Error(
|
|
72
|
-
'[bunderstack] Schema push would cause data loss. Run `bunx drizzle-kit push` or call provision(app, { force: true }).',
|
|
73
|
-
)
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
if (result.warnings.length > 0) {
|
|
77
|
-
for (const warning of result.warnings) {
|
|
78
|
-
console.warn(`[bunderstack] ${warning}`)
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
if (result.statementsToExecute.length === 0) return
|
|
83
|
-
|
|
84
|
-
await result.apply()
|
|
85
|
-
|
|
86
|
-
console.log(
|
|
87
|
-
`[bunderstack] provisioned ${result.statementsToExecute.length} schema change(s)`,
|
|
88
|
-
)
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Provision the database for a Bunderstack app.
|
|
93
|
-
*
|
|
94
|
-
* The migrations folder is the mode switch:
|
|
95
|
-
* - `<migrations>/meta/_journal.json` exists → apply committed migrations via
|
|
96
|
-
* drizzle-orm's migrator. Pure runtime; drizzle-kit is never imported, so a
|
|
97
|
-
* fresh clone deploys with `bun install --production`.
|
|
98
|
-
* - No migrations → development: push the schema straight to the database via
|
|
99
|
-
* drizzle-kit (install it with `bun add -d drizzle-kit`).
|
|
100
|
-
*
|
|
101
|
-
* Once you run `bunx drizzle-kit generate` for the first time, provision stops
|
|
102
|
-
* pushing and every schema change goes through an explicit `generate`.
|
|
103
|
-
*/
|
|
104
|
-
export async function provision(
|
|
105
|
-
app: object,
|
|
106
|
-
options?: { force?: boolean },
|
|
107
|
-
): Promise<void> {
|
|
108
|
-
if (process.env.BUNDERSTACK_INTROSPECT === '1') return
|
|
109
|
-
|
|
110
|
-
const internals = (app as WithProvisionInternals)[PROVISION_INTERNALS]
|
|
111
|
-
if (!internals) {
|
|
112
|
-
throw new Error(
|
|
113
|
-
'[bunderstack] provision() expects the app returned by createBunderstack().',
|
|
114
|
-
)
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
const { db, schema, databaseUrl, migrationsFolder, dialect, adapter } =
|
|
118
|
-
internals
|
|
119
|
-
const journal = join(migrationsFolder, 'meta', '_journal.json')
|
|
120
|
-
|
|
121
|
-
if (await exists(journal)) {
|
|
122
|
-
await ensureLocalDataDir(databaseUrl, dialect)
|
|
123
|
-
await adapter.migrate(db as never, migrationsFolder)
|
|
124
|
-
return
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
await provisionSchema(db, schema, { force: options?.force, databaseUrl })
|
|
128
|
-
}
|
package/src/rate-limit.ts
DELETED
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
export type RateLimitConfig = {
|
|
2
|
-
windowMs?: number
|
|
3
|
-
max?: number
|
|
4
|
-
skip?: (req: Request) => boolean
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
type Bucket = {
|
|
8
|
-
count: number
|
|
9
|
-
resetAt: number
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
const buckets = new Map<string, Bucket>()
|
|
13
|
-
|
|
14
|
-
function resolveConfig(
|
|
15
|
-
config: boolean | RateLimitConfig | undefined,
|
|
16
|
-
): RateLimitConfig | null {
|
|
17
|
-
if (!config) return null
|
|
18
|
-
if (config === true) return {}
|
|
19
|
-
return config
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function clientKey(req: Request): string {
|
|
23
|
-
const forwarded = req.headers.get('x-forwarded-for')
|
|
24
|
-
if (forwarded) return forwarded.split(',')[0]!.trim()
|
|
25
|
-
return req.headers.get('x-real-ip') ?? 'local'
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export function createRateLimiter(
|
|
29
|
-
config: boolean | RateLimitConfig | undefined,
|
|
30
|
-
): (req: Request) => Promise<Response | null> {
|
|
31
|
-
const resolved = resolveConfig(config)
|
|
32
|
-
if (!resolved) {
|
|
33
|
-
return async (_req: Request) => null
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const windowMs = resolved.windowMs ?? 60_000
|
|
37
|
-
const max = resolved.max ?? 100
|
|
38
|
-
|
|
39
|
-
return async (req: Request): Promise<Response | null> => {
|
|
40
|
-
if (resolved.skip?.(req)) return null
|
|
41
|
-
|
|
42
|
-
const key = `${clientKey(req)}:${new URL(req.url).pathname}`
|
|
43
|
-
const now = Date.now()
|
|
44
|
-
let bucket = buckets.get(key)
|
|
45
|
-
|
|
46
|
-
if (!bucket || bucket.resetAt <= now) {
|
|
47
|
-
bucket = { count: 0, resetAt: now + windowMs }
|
|
48
|
-
buckets.set(key, bucket)
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
bucket.count += 1
|
|
52
|
-
if (bucket.count > max) {
|
|
53
|
-
const retryAfter = Math.ceil((bucket.resetAt - now) / 1000)
|
|
54
|
-
return new Response(
|
|
55
|
-
JSON.stringify({
|
|
56
|
-
error: 'Too many requests',
|
|
57
|
-
code: 'RATE_LIMITED',
|
|
58
|
-
}),
|
|
59
|
-
{
|
|
60
|
-
status: 429,
|
|
61
|
-
headers: {
|
|
62
|
-
'Content-Type': 'application/json',
|
|
63
|
-
'Retry-After': String(retryAfter),
|
|
64
|
-
},
|
|
65
|
-
},
|
|
66
|
-
)
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
return null
|
|
70
|
-
}
|
|
71
|
-
}
|
package/src/realtime/facade.ts
DELETED
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import { getTableName, type InferSelectModel, type Table } from 'drizzle-orm'
|
|
2
|
-
|
|
3
|
-
import type {
|
|
4
|
-
RealtimeAction,
|
|
5
|
-
RealtimePublisher,
|
|
6
|
-
} from './publisher'
|
|
7
|
-
|
|
8
|
-
export type RealtimeTransport = 'disabled' | 'memory' | 'redis'
|
|
9
|
-
|
|
10
|
-
export type SchemaTable<TSchema extends Record<string, unknown>> = Extract<
|
|
11
|
-
TSchema[keyof TSchema],
|
|
12
|
-
Table
|
|
13
|
-
>
|
|
14
|
-
|
|
15
|
-
export interface RealtimeFacade<
|
|
16
|
-
TSchema extends Record<string, unknown> = Record<string, unknown>,
|
|
17
|
-
> {
|
|
18
|
-
readonly enabled: boolean
|
|
19
|
-
readonly transport: RealtimeTransport
|
|
20
|
-
|
|
21
|
-
publish<TTable extends SchemaTable<TSchema>>(
|
|
22
|
-
table: TTable,
|
|
23
|
-
action: RealtimeAction,
|
|
24
|
-
record: InferSelectModel<TTable>,
|
|
25
|
-
): Promise<void>
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export function createRealtimeFacade<TSchema extends Record<string, unknown>>(
|
|
29
|
-
publisher?: RealtimePublisher,
|
|
30
|
-
transport: RealtimeTransport = publisher ? 'memory' : 'disabled',
|
|
31
|
-
): RealtimeFacade<TSchema> {
|
|
32
|
-
if (!publisher && transport !== 'disabled') {
|
|
33
|
-
throw new Error(
|
|
34
|
-
'[bunderstack] an enabled realtime transport requires a publisher',
|
|
35
|
-
)
|
|
36
|
-
}
|
|
37
|
-
if (publisher && transport === 'disabled') {
|
|
38
|
-
throw new Error(
|
|
39
|
-
'[bunderstack] a realtime publisher cannot use the disabled transport',
|
|
40
|
-
)
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
return {
|
|
44
|
-
enabled: publisher !== undefined,
|
|
45
|
-
transport,
|
|
46
|
-
async publish(table, action, record) {
|
|
47
|
-
if (!publisher) return
|
|
48
|
-
await publisher.publish('change', {
|
|
49
|
-
table: getTableName(table),
|
|
50
|
-
action,
|
|
51
|
-
record: record as unknown as Record<string, unknown>,
|
|
52
|
-
})
|
|
53
|
-
},
|
|
54
|
-
}
|
|
55
|
-
}
|
package/src/realtime/filter.ts
DELETED
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
import { getEventMeta, withEventMeta } from '@orpc/server'
|
|
2
|
-
|
|
3
|
-
import type {
|
|
4
|
-
AccessUser,
|
|
5
|
-
ResolvedAccess,
|
|
6
|
-
} from '../access'
|
|
7
|
-
import type { RealtimeChange } from './publisher'
|
|
8
|
-
|
|
9
|
-
import {
|
|
10
|
-
checkAccess,
|
|
11
|
-
rowMatchesScope,
|
|
12
|
-
tableEntryForName,
|
|
13
|
-
} from '../access'
|
|
14
|
-
|
|
15
|
-
export interface FilterRealtimeChangesOptions {
|
|
16
|
-
subscriptions: readonly string[]
|
|
17
|
-
access: ResolvedAccess
|
|
18
|
-
request: Request
|
|
19
|
-
getSession: () => Promise<{
|
|
20
|
-
user: AccessUser | null
|
|
21
|
-
activeOrganizationId: string | null
|
|
22
|
-
}>
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export async function* filterRealtimeChanges(
|
|
26
|
-
source: AsyncIterable<RealtimeChange>,
|
|
27
|
-
options: FilterRealtimeChangesOptions,
|
|
28
|
-
): AsyncGenerator<RealtimeChange, void, void> {
|
|
29
|
-
const subscriptions = new Set(options.subscriptions)
|
|
30
|
-
let sessionPromise:
|
|
31
|
-
| ReturnType<FilterRealtimeChangesOptions['getSession']>
|
|
32
|
-
| undefined
|
|
33
|
-
const getSession = () => (sessionPromise ??= options.getSession())
|
|
34
|
-
|
|
35
|
-
for await (const change of source) {
|
|
36
|
-
const entry = tableEntryForName(options.access, change.table)
|
|
37
|
-
if (!entry?.enabled) continue
|
|
38
|
-
|
|
39
|
-
const recordId = change.record.id
|
|
40
|
-
if (
|
|
41
|
-
!subscriptions.has(change.table) &&
|
|
42
|
-
(recordId == null ||
|
|
43
|
-
!subscriptions.has(`${change.table}/${String(recordId)}`))
|
|
44
|
-
) {
|
|
45
|
-
continue
|
|
46
|
-
}
|
|
47
|
-
if (entry.get === 'deny') continue
|
|
48
|
-
|
|
49
|
-
const needsSession = entry.get !== 'public' || entry.readScope !== undefined
|
|
50
|
-
const session = needsSession
|
|
51
|
-
? await getSession()
|
|
52
|
-
: { user: null, activeOrganizationId: null }
|
|
53
|
-
const context = {
|
|
54
|
-
request: options.request,
|
|
55
|
-
user: session.user,
|
|
56
|
-
row: change.record,
|
|
57
|
-
session: { activeOrganizationId: session.activeOrganizationId },
|
|
58
|
-
}
|
|
59
|
-
if (!(await checkAccess(entry.get, context, entry.ownerColumn)).allowed) {
|
|
60
|
-
continue
|
|
61
|
-
}
|
|
62
|
-
if (
|
|
63
|
-
entry.readScope &&
|
|
64
|
-
!rowMatchesScope(change.record, entry.readScope(context))
|
|
65
|
-
) {
|
|
66
|
-
continue
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const projected: RealtimeChange = {
|
|
70
|
-
table: change.table,
|
|
71
|
-
action: change.action,
|
|
72
|
-
record: change.record,
|
|
73
|
-
}
|
|
74
|
-
const meta = getEventMeta(change)
|
|
75
|
-
yield meta ? withEventMeta(projected, meta) : projected
|
|
76
|
-
}
|
|
77
|
-
}
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
export const REALTIME_HEARTBEAT_INTERVAL_MS = 5_000
|
|
2
|
-
|
|
3
|
-
export type RealtimeHeartbeat = { type: 'heartbeat' }
|
|
4
|
-
|
|
5
|
-
type SourceState<T> =
|
|
6
|
-
| { status: 'pending' }
|
|
7
|
-
| { status: 'ready'; result: IteratorResult<T> }
|
|
8
|
-
| { status: 'error'; error: unknown }
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Emits a transport-only event whenever the source has been idle for an
|
|
12
|
-
* interval. Heartbeats are deliberately not published, persisted, or assigned
|
|
13
|
-
* event IDs, so they do not affect replay and resume semantics.
|
|
14
|
-
*/
|
|
15
|
-
export async function* withRealtimeHeartbeat<T>(
|
|
16
|
-
source: AsyncIterable<T>,
|
|
17
|
-
options: { intervalMs?: number; signal?: AbortSignal },
|
|
18
|
-
): AsyncGenerator<T | RealtimeHeartbeat, void, void> {
|
|
19
|
-
const intervalMs = Math.max(
|
|
20
|
-
1,
|
|
21
|
-
options.intervalMs ?? REALTIME_HEARTBEAT_INTERVAL_MS,
|
|
22
|
-
)
|
|
23
|
-
const iterator = source[Symbol.asyncIterator]()
|
|
24
|
-
let state: SourceState<T> = { status: 'pending' }
|
|
25
|
-
let wake: (() => void) | undefined
|
|
26
|
-
const getState = (): SourceState<T> => state
|
|
27
|
-
|
|
28
|
-
const requestNext = () => {
|
|
29
|
-
state = { status: 'pending' }
|
|
30
|
-
void iterator.next().then(
|
|
31
|
-
(result) => {
|
|
32
|
-
state = { status: 'ready', result }
|
|
33
|
-
wake?.()
|
|
34
|
-
},
|
|
35
|
-
(error: unknown) => {
|
|
36
|
-
state = { status: 'error', error }
|
|
37
|
-
wake?.()
|
|
38
|
-
},
|
|
39
|
-
)
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
requestNext()
|
|
43
|
-
try {
|
|
44
|
-
while (!options.signal?.aborted) {
|
|
45
|
-
let current = getState()
|
|
46
|
-
if (current.status === 'pending') {
|
|
47
|
-
await new Promise<void>((resolve) => {
|
|
48
|
-
let settled = false
|
|
49
|
-
const finish = () => {
|
|
50
|
-
if (settled) return
|
|
51
|
-
settled = true
|
|
52
|
-
clearTimeout(timer)
|
|
53
|
-
options.signal?.removeEventListener('abort', finish)
|
|
54
|
-
resolve()
|
|
55
|
-
}
|
|
56
|
-
const timer = setTimeout(finish, intervalMs)
|
|
57
|
-
options.signal?.addEventListener('abort', finish, { once: true })
|
|
58
|
-
wake = finish
|
|
59
|
-
})
|
|
60
|
-
wake = undefined
|
|
61
|
-
|
|
62
|
-
if (options.signal?.aborted) break
|
|
63
|
-
current = getState()
|
|
64
|
-
if (current.status === 'pending') {
|
|
65
|
-
yield { type: 'heartbeat' }
|
|
66
|
-
continue
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
if (current.status === 'error') throw current.error
|
|
71
|
-
if (current.result.done) break
|
|
72
|
-
|
|
73
|
-
yield current.result.value
|
|
74
|
-
requestNext()
|
|
75
|
-
}
|
|
76
|
-
} finally {
|
|
77
|
-
wake = undefined
|
|
78
|
-
await iterator.return?.()
|
|
79
|
-
}
|
|
80
|
-
}
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import type { Publisher } from '@orpc/publisher'
|
|
2
|
-
import type { RedisClient } from 'bun'
|
|
3
|
-
|
|
4
|
-
import { BunRedisPublisher } from '@orpc/bun'
|
|
5
|
-
import { MemoryPublisher } from '@orpc/publisher/memory'
|
|
6
|
-
|
|
7
|
-
export type RealtimeAction = 'create' | 'update' | 'delete'
|
|
8
|
-
|
|
9
|
-
export interface RealtimeChange {
|
|
10
|
-
table: string
|
|
11
|
-
action: RealtimeAction
|
|
12
|
-
record: Record<string, unknown>
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export interface RealtimeEvents extends Record<string, object> {
|
|
16
|
-
change: RealtimeChange
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export type RealtimePublisher = Publisher<RealtimeEvents>
|
|
20
|
-
|
|
21
|
-
export interface RealtimePublisherOptions {
|
|
22
|
-
maxBufferedEvents?: number
|
|
23
|
-
resumeSeconds?: number
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export function createMemoryRealtimePublisher(
|
|
27
|
-
options: RealtimePublisherOptions = {},
|
|
28
|
-
): RealtimePublisher {
|
|
29
|
-
return new MemoryPublisher<RealtimeEvents>({
|
|
30
|
-
maxBufferedEvents: options.maxBufferedEvents,
|
|
31
|
-
resume: { enabled: true, seconds: options.resumeSeconds ?? 300 },
|
|
32
|
-
})
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export function createRedisRealtimePublisher(
|
|
36
|
-
redis: RedisClient,
|
|
37
|
-
subscriber: RedisClient | Promise<RedisClient>,
|
|
38
|
-
options: RealtimePublisherOptions & { prefix?: string } = {},
|
|
39
|
-
): RealtimePublisher {
|
|
40
|
-
return new BunRedisPublisher<RealtimeEvents>(redis, {
|
|
41
|
-
subscriber,
|
|
42
|
-
prefix: options.prefix ?? 'bunderstack:',
|
|
43
|
-
maxBufferedEvents: options.maxBufferedEvents,
|
|
44
|
-
resume: { enabled: true, seconds: options.resumeSeconds ?? 300 },
|
|
45
|
-
})
|
|
46
|
-
}
|
package/src/schema-export-pg.ts
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
// src/schema-export-pg.ts — pg twins under the same names bunderstack/schema
|
|
2
|
-
// uses, so `export * from 'bunderstack/schema/pg'` mirrors the sqlite setup.
|
|
3
|
-
export {
|
|
4
|
-
bunderstackFilesPg as bunderstackFiles,
|
|
5
|
-
bunderstackIdempotencyPg as bunderstackIdempotency,
|
|
6
|
-
bunderstackJobsPg as bunderstackJobs,
|
|
7
|
-
} from './internal-tables-pg'
|
package/src/schema-export.ts
DELETED
package/src/scope.ts
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { and, eq, getTableColumns, inArray, type SQL } from 'drizzle-orm'
|
|
2
|
-
|
|
3
|
-
import type { ScopeMap } from './access'
|
|
4
|
-
|
|
5
|
-
export function buildScopeWhere(
|
|
6
|
-
table: Parameters<typeof getTableColumns>[0],
|
|
7
|
-
scope: ScopeMap,
|
|
8
|
-
): SQL | undefined {
|
|
9
|
-
const columns = getTableColumns(table)
|
|
10
|
-
const conditions: SQL[] = []
|
|
11
|
-
for (const [name, value] of Object.entries(scope)) {
|
|
12
|
-
const col = columns[name]
|
|
13
|
-
if (!col) continue
|
|
14
|
-
conditions.push(Array.isArray(value) ? inArray(col, value) : eq(col, value))
|
|
15
|
-
}
|
|
16
|
-
return conditions.length ? and(...conditions) : undefined
|
|
17
|
-
}
|
package/src/standard-schema.ts
DELETED
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
import type { StandardSchemaV1 } from '@standard-schema/spec'
|
|
2
|
-
|
|
3
|
-
export type StandardSchema = StandardSchemaV1
|
|
4
|
-
export type InferStandardOutput<TSchema extends StandardSchemaV1> =
|
|
5
|
-
StandardSchemaV1.InferOutput<TSchema>
|
|
6
|
-
|
|
7
|
-
export type StandardSchemaIssue = {
|
|
8
|
-
path: PropertyKey[]
|
|
9
|
-
message: string
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export class StandardSchemaValidationError extends Error {
|
|
13
|
-
readonly issues: StandardSchemaIssue[]
|
|
14
|
-
|
|
15
|
-
constructor(label: string, issues: readonly StandardSchemaV1.Issue[]) {
|
|
16
|
-
const normalized = issues.map((issue) => ({
|
|
17
|
-
path: [...(issue.path ?? [])].map((segment) =>
|
|
18
|
-
typeof segment === 'object' ? segment.key : segment,
|
|
19
|
-
),
|
|
20
|
-
message: issue.message,
|
|
21
|
-
}))
|
|
22
|
-
super(
|
|
23
|
-
normalized
|
|
24
|
-
.map((issue) => {
|
|
25
|
-
const path = issue.path.map(String).join('.')
|
|
26
|
-
return `${label}${path ? `.${path}` : ''}: ${issue.message}`
|
|
27
|
-
})
|
|
28
|
-
.join('\n'),
|
|
29
|
-
)
|
|
30
|
-
this.name = 'StandardSchemaValidationError'
|
|
31
|
-
this.issues = normalized
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function isPromiseLike(value: unknown): value is PromiseLike<unknown> {
|
|
36
|
-
return (
|
|
37
|
-
typeof value === 'object' &&
|
|
38
|
-
value !== null &&
|
|
39
|
-
'then' in value &&
|
|
40
|
-
typeof value.then === 'function'
|
|
41
|
-
)
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export function validateStandardSchema<TSchema extends StandardSchemaV1>(
|
|
45
|
-
schema: TSchema,
|
|
46
|
-
value: unknown,
|
|
47
|
-
label: string,
|
|
48
|
-
): StandardSchemaV1.InferOutput<TSchema> {
|
|
49
|
-
const result = schema['~standard'].validate(value)
|
|
50
|
-
if (isPromiseLike(result)) {
|
|
51
|
-
throw new Error(
|
|
52
|
-
`[bunderstack] ${label} schema validation must be synchronous`,
|
|
53
|
-
)
|
|
54
|
-
}
|
|
55
|
-
if (result.issues) {
|
|
56
|
-
throw new StandardSchemaValidationError(label, result.issues)
|
|
57
|
-
}
|
|
58
|
-
return result.value
|
|
59
|
-
}
|