bunderstack 0.7.0 → 0.9.0

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/src/lifecycle.ts CHANGED
@@ -30,13 +30,18 @@ export class Lifecycle {
30
30
  this.#closePromise = (async () => {
31
31
  const cleanups = [...this.#cleanups].reverse()
32
32
  this.#cleanups.clear()
33
- const results = await Promise.allSettled(cleanups.map((cleanup) => cleanup()))
33
+ const results = await Promise.allSettled(
34
+ cleanups.map(async (cleanup) => cleanup()),
35
+ )
34
36
  this.#status = 'closed'
35
37
  const errors = results.flatMap((result) =>
36
38
  result.status === 'rejected' ? [result.reason] : [],
37
39
  )
38
40
  if (errors.length > 0) {
39
- throw new AggregateError(errors, '[bunderstack] lifecycle cleanup failed')
41
+ throw new AggregateError(
42
+ errors,
43
+ '[bunderstack] lifecycle cleanup failed',
44
+ )
40
45
  }
41
46
  })()
42
47
  return this.#closePromise
package/src/manifest.ts CHANGED
@@ -8,6 +8,7 @@ import { getTableName, isTable } from 'drizzle-orm'
8
8
  import type { Dialect } from './dialect'
9
9
  import type { EnvConfigInput } from './env'
10
10
  import type { JobsDefs } from './jobs/define'
11
+ import type { RealtimeTransport } from './realtime/facade'
11
12
  import type { ResolvedBucket, ResolvedStorageBuckets } from './storage/buckets'
12
13
  import {
13
14
  bunderstackCronRuns,
@@ -29,6 +30,7 @@ export type BunderstackManifest = {
29
30
  defaultBucket: string
30
31
  buckets: { name: string; visibility: ResolvedBucket['visibility'] }[]
31
32
  realtime: boolean
33
+ realtimeTransport: RealtimeTransport
32
34
  env: { server: ManifestEnvVar[]; client: ManifestEnvVar[] }
33
35
  background: {
34
36
  jobs: { name: string }[]
@@ -60,6 +62,7 @@ export function buildManifest(args: {
60
62
  storage: ResolvedStorageBuckets
61
63
  envConfig: EnvConfigInput | undefined
62
64
  realtime: boolean
65
+ realtimeTransport: RealtimeTransport
63
66
  jobs: JobsDefs | undefined
64
67
  }): BunderstackManifest {
65
68
  return {
@@ -78,6 +81,7 @@ export function buildManifest(args: {
78
81
  visibility: bucket.visibility,
79
82
  })),
80
83
  realtime: args.realtime,
84
+ realtimeTransport: args.realtimeTransport,
81
85
  env: {
82
86
  server: describeSection(args.envConfig?.server),
83
87
  client: describeSection(args.envConfig?.client),
@@ -1,6 +1,6 @@
1
+ import type { Driver } from './db'
1
2
  // src/provision-internals.ts
2
3
  import type { AnyDb, Dialect } from './dialect'
3
- import type { Driver } from './db'
4
4
 
5
5
  /**
6
6
  * Hidden handle connecting `createBunderstack()` to the optional
@@ -21,6 +21,7 @@ export interface ProvisionInternals {
21
21
  migrationsFolder: string
22
22
  dialect: Dialect
23
23
  driver: Driver
24
+ adapter: import('./database/adapter').DatabaseAdapter
24
25
  }
25
26
 
26
27
  export interface WithProvisionInternals {
package/src/provision.ts CHANGED
@@ -11,7 +11,10 @@ import {
11
11
  } from './provision-internals'
12
12
 
13
13
  /** Create the local backing directory for file-based urls, per dialect. */
14
- async function ensureLocalDataDir(url: string, dialect: Dialect): Promise<void> {
14
+ async function ensureLocalDataDir(
15
+ url: string,
16
+ dialect: Dialect,
17
+ ): Promise<void> {
15
18
  if (dialect === 'pg') {
16
19
  // PGlite data dir: `file:<dir>` or a bare path; server/memory urls need nothing.
17
20
  if (/^postgres(ql)?:\/\//.test(url)) return
@@ -54,11 +57,7 @@ export async function provisionSchema<TSchema extends Record<string, unknown>>(
54
57
 
55
58
  let kit: typeof import('drizzle-kit/api')
56
59
  try {
57
- // Ignore comments keep bundlers (vite/nitro, webpack) from resolving
58
- // drizzle-kit at build time — this branch only runs in development.
59
- kit = await import(
60
- /* @vite-ignore */ /* webpackIgnore: true */ 'drizzle-kit/api'
61
- )
60
+ kit = await import('drizzle-kit/api')
62
61
  } catch (cause) {
63
62
  throw new Error(DRIZZLE_KIT_HINT, { cause })
64
63
  }
@@ -89,13 +88,6 @@ export async function provisionSchema<TSchema extends Record<string, unknown>>(
89
88
  )
90
89
  }
91
90
 
92
- const MIGRATOR_MODULES = {
93
- libsql: 'drizzle-orm/libsql/migrator',
94
- pglite: 'drizzle-orm/pglite/migrator',
95
- 'bun-sql': 'drizzle-orm/bun-sql/migrator',
96
- 'postgres-js': 'drizzle-orm/postgres-js/migrator',
97
- } as const
98
-
99
91
  /**
100
92
  * Provision the database for a Bunderstack app.
101
93
  *
@@ -120,16 +112,13 @@ export async function provision(
120
112
  )
121
113
  }
122
114
 
123
- const { db, schema, databaseUrl, migrationsFolder, dialect, driver } =
115
+ const { db, schema, databaseUrl, migrationsFolder, dialect, adapter } =
124
116
  internals
125
117
  const journal = join(migrationsFolder, 'meta', '_journal.json')
126
118
 
127
119
  if (await exists(journal)) {
128
120
  await ensureLocalDataDir(databaseUrl, dialect)
129
- const { migrate } = (await import(
130
- /* @vite-ignore */ /* webpackIgnore: true */ MIGRATOR_MODULES[driver]
131
- )) as { migrate: (db: never, cfg: { migrationsFolder: string }) => Promise<void> }
132
- await migrate(db as never, { migrationsFolder })
121
+ await adapter.migrate(db as never, migrationsFolder)
133
122
  return
134
123
  }
135
124
 
@@ -2,6 +2,8 @@ import { getTableName, type InferSelectModel, type Table } from 'drizzle-orm'
2
2
 
3
3
  import type { RealtimeAction, RealtimeBroker } from './index'
4
4
 
5
+ export type RealtimeTransport = 'disabled' | 'memory' | 'redis'
6
+
5
7
  export type SchemaTable<TSchema extends Record<string, unknown>> = Extract<
6
8
  TSchema[keyof TSchema],
7
9
  Table
@@ -11,6 +13,7 @@ export interface RealtimeFacade<
11
13
  TSchema extends Record<string, unknown> = Record<string, unknown>,
12
14
  > {
13
15
  readonly enabled: boolean
16
+ readonly transport: RealtimeTransport
14
17
 
15
18
  publish<TTable extends SchemaTable<TSchema>>(
16
19
  table: TTable,
@@ -21,9 +24,22 @@ export interface RealtimeFacade<
21
24
 
22
25
  export function createRealtimeFacade<TSchema extends Record<string, unknown>>(
23
26
  broker?: RealtimeBroker,
27
+ transport: RealtimeTransport = broker ? 'memory' : 'disabled',
24
28
  ): RealtimeFacade<TSchema> {
29
+ if (!broker && transport !== 'disabled') {
30
+ throw new Error(
31
+ '[bunderstack] an enabled realtime transport requires a broker',
32
+ )
33
+ }
34
+ if (broker && transport === 'disabled') {
35
+ throw new Error(
36
+ '[bunderstack] a realtime broker cannot use the disabled transport',
37
+ )
38
+ }
39
+
25
40
  return {
26
41
  enabled: broker !== undefined,
42
+ transport,
27
43
  async publish(table, action, record) {
28
44
  if (!broker) return
29
45
  await broker.publish(
package/src/typeid.ts CHANGED
@@ -16,10 +16,10 @@ const PREFIX_RE = /^[a-z]([a-z_]{0,61}[a-z])?$/
16
16
  // 26 base32 chars from the alphabet above.
17
17
  const SUFFIX_RE = /^[0-9a-hjkmnp-tv-z]{26}$/
18
18
 
19
- declare const brand: unique symbol
19
+ declare const typeIdBrand: unique symbol
20
20
 
21
21
  /** A branded TypeID string. `TypeId<'post'>` is incompatible with `TypeId<'user'>`. */
22
- export type TypeId<P extends string> = string & { readonly [brand]: P }
22
+ export type TypeId<P extends string> = string & { readonly [typeIdBrand]?: P }
23
23
 
24
24
  /** Encode a 16-byte UUID into the 26-character base32 suffix. */
25
25
  export function encode(bytes: Uint8Array): string {