bunderstack 0.6.1 → 0.8.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.
@@ -35,6 +35,8 @@ export type JobsRuntimeFacade = {
35
35
  tick(now?: number): Promise<void>
36
36
  }
37
37
 
38
+ import type { RealtimeFacade } from '../realtime/facade'
39
+
38
40
  export type JobContext<
39
41
  TSchema extends Record<string, unknown> = Record<string, unknown>,
40
42
  TEnvResult = Record<string, unknown>,
@@ -44,6 +46,7 @@ export type JobContext<
44
46
  email: EmailFacade
45
47
  storage: StorageFacade
46
48
  jobs: JobsRuntimeFacade
49
+ realtime: RealtimeFacade<TSchema>
47
50
  }
48
51
 
49
52
  export type QueueJobDefinition<
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
@@ -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
 
@@ -0,0 +1,36 @@
1
+ import { getTableName, type InferSelectModel, type Table } from 'drizzle-orm'
2
+
3
+ import type { RealtimeAction, RealtimeBroker } from './index'
4
+
5
+ export type SchemaTable<TSchema extends Record<string, unknown>> = Extract<
6
+ TSchema[keyof TSchema],
7
+ Table
8
+ >
9
+
10
+ export interface RealtimeFacade<
11
+ TSchema extends Record<string, unknown> = Record<string, unknown>,
12
+ > {
13
+ readonly enabled: boolean
14
+
15
+ publish<TTable extends SchemaTable<TSchema>>(
16
+ table: TTable,
17
+ action: RealtimeAction,
18
+ record: InferSelectModel<TTable>,
19
+ ): Promise<void>
20
+ }
21
+
22
+ export function createRealtimeFacade<TSchema extends Record<string, unknown>>(
23
+ broker?: RealtimeBroker,
24
+ ): RealtimeFacade<TSchema> {
25
+ return {
26
+ enabled: broker !== undefined,
27
+ async publish(table, action, record) {
28
+ if (!broker) return
29
+ await broker.publish(
30
+ getTableName(table),
31
+ action,
32
+ record as unknown as Record<string, unknown>,
33
+ )
34
+ },
35
+ }
36
+ }
package/src/trpc.ts CHANGED
@@ -6,6 +6,7 @@ import type { AccessUser } from './access'
6
6
  import type { DbFor } from './db'
7
7
  import type { EmailFacade } from './email'
8
8
  import type { JobsRuntimeFacade } from './jobs/index'
9
+ import type { RealtimeFacade } from './realtime/facade'
9
10
 
10
11
  export type TRPCContext<
11
12
  TSchema extends Record<string, unknown>,
@@ -16,6 +17,7 @@ export type TRPCContext<
16
17
  env: TEnvResult
17
18
  email: EmailFacade
18
19
  jobs: JobsRuntimeFacade
20
+ realtime: RealtimeFacade<TSchema>
19
21
  req: Request
20
22
  }
21
23
 
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 {