bunderstack 0.23.4 → 0.24.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.
Files changed (137) hide show
  1. package/CHANGELOG.md +56 -0
  2. package/README.md +6 -4
  3. package/dist/api/builder.d.ts +13 -11
  4. package/dist/api/builder.d.ts.map +1 -1
  5. package/dist/api/builder.js.map +1 -1
  6. package/dist/api/context.d.ts +6 -6
  7. package/dist/api/context.d.ts.map +1 -1
  8. package/dist/api/context.js +1 -1
  9. package/dist/api/context.js.map +1 -1
  10. package/dist/api/crud-router.d.ts +6 -6
  11. package/dist/api/realtime-router.d.ts +1 -1
  12. package/dist/api/storage-router.d.ts +5 -5
  13. package/dist/backend-internals.d.ts +5 -13
  14. package/dist/backend-internals.d.ts.map +1 -1
  15. package/dist/backend-internals.js.map +1 -1
  16. package/dist/backend.d.ts +39 -5
  17. package/dist/backend.d.ts.map +1 -1
  18. package/dist/backend.js +37 -46
  19. package/dist/backend.js.map +1 -1
  20. package/dist/blueprint-generator.d.ts +1 -0
  21. package/dist/blueprint-generator.d.ts.map +1 -1
  22. package/dist/blueprint-generator.js +29 -1
  23. package/dist/blueprint-generator.js.map +1 -1
  24. package/dist/blueprint.d.ts +2 -1
  25. package/dist/blueprint.d.ts.map +1 -1
  26. package/dist/blueprint.js +9 -2
  27. package/dist/blueprint.js.map +1 -1
  28. package/dist/cli.d.ts.map +1 -1
  29. package/dist/cli.js +14 -2
  30. package/dist/cli.js.map +1 -1
  31. package/dist/config.d.ts +13 -19
  32. package/dist/config.d.ts.map +1 -1
  33. package/dist/config.js +1 -1
  34. package/dist/config.js.map +1 -1
  35. package/dist/email/smtp.d.ts +7 -1
  36. package/dist/email/smtp.d.ts.map +1 -1
  37. package/dist/email/smtp.js +5 -1
  38. package/dist/email/smtp.js.map +1 -1
  39. package/dist/email.d.ts +3 -29
  40. package/dist/email.d.ts.map +1 -1
  41. package/dist/email.js +1 -194
  42. package/dist/email.js.map +1 -1
  43. package/dist/env-probe.d.ts +9 -0
  44. package/dist/env-probe.d.ts.map +1 -0
  45. package/dist/env-probe.js +73 -0
  46. package/dist/env-probe.js.map +1 -0
  47. package/dist/env.d.ts +2 -5
  48. package/dist/env.d.ts.map +1 -1
  49. package/dist/env.js +2 -9
  50. package/dist/env.js.map +1 -1
  51. package/dist/hosted-contract.d.ts +11 -0
  52. package/dist/hosted-contract.d.ts.map +1 -0
  53. package/dist/hosted-contract.js +46 -0
  54. package/dist/hosted-contract.js.map +1 -0
  55. package/dist/index.d.ts +3 -2
  56. package/dist/index.d.ts.map +1 -1
  57. package/dist/index.js +1 -1
  58. package/dist/index.js.map +1 -1
  59. package/dist/inspect.d.ts +15 -0
  60. package/dist/inspect.d.ts.map +1 -0
  61. package/dist/inspect.js +43 -0
  62. package/dist/inspect.js.map +1 -0
  63. package/dist/internal-tables-pg.d.ts +43 -94
  64. package/dist/internal-tables-pg.d.ts.map +1 -1
  65. package/dist/internal-tables-pg.js +14 -17
  66. package/dist/internal-tables-pg.js.map +1 -1
  67. package/dist/internal-tables.d.ts +213 -488
  68. package/dist/internal-tables.d.ts.map +1 -1
  69. package/dist/internal-tables.js +33 -33
  70. package/dist/internal-tables.js.map +1 -1
  71. package/dist/jobs/define.d.ts +20 -20
  72. package/dist/jobs/define.d.ts.map +1 -1
  73. package/dist/jobs/define.js.map +1 -1
  74. package/dist/manifest-diff.d.ts +6 -0
  75. package/dist/manifest-diff.d.ts.map +1 -0
  76. package/dist/manifest-diff.js +42 -0
  77. package/dist/manifest-diff.js.map +1 -0
  78. package/dist/manifest.d.ts +10 -2
  79. package/dist/manifest.d.ts.map +1 -1
  80. package/dist/manifest.js +27 -29
  81. package/dist/manifest.js.map +1 -1
  82. package/dist/messaging/email.d.ts +16 -0
  83. package/dist/messaging/email.d.ts.map +1 -0
  84. package/dist/messaging/email.js +8 -0
  85. package/dist/messaging/email.js.map +1 -0
  86. package/dist/messaging/index.d.ts +8 -0
  87. package/dist/messaging/index.d.ts.map +1 -0
  88. package/dist/messaging/index.js +4 -0
  89. package/dist/messaging/index.js.map +1 -0
  90. package/dist/messaging/journal.d.ts +4 -0
  91. package/dist/messaging/journal.d.ts.map +1 -0
  92. package/dist/messaging/journal.js +19 -0
  93. package/dist/messaging/journal.js.map +1 -0
  94. package/dist/messaging/runtime.d.ts +16 -0
  95. package/dist/messaging/runtime.d.ts.map +1 -0
  96. package/dist/messaging/runtime.js +213 -0
  97. package/dist/messaging/runtime.js.map +1 -0
  98. package/dist/messaging/standalone.d.ts +27 -0
  99. package/dist/messaging/standalone.d.ts.map +1 -0
  100. package/dist/messaging/standalone.js +18 -0
  101. package/dist/messaging/standalone.js.map +1 -0
  102. package/dist/messaging/telegram.d.ts +16 -0
  103. package/dist/messaging/telegram.d.ts.map +1 -0
  104. package/dist/messaging/telegram.js +5 -0
  105. package/dist/messaging/telegram.js.map +1 -0
  106. package/dist/messaging/types.d.ts +22 -0
  107. package/dist/messaging/types.d.ts.map +1 -0
  108. package/dist/messaging/types.js +17 -0
  109. package/dist/messaging/types.js.map +1 -0
  110. package/dist/runtime.d.ts +11 -10
  111. package/dist/runtime.d.ts.map +1 -1
  112. package/dist/runtime.js +19 -31
  113. package/dist/runtime.js.map +1 -1
  114. package/dist/schema-export-pg.d.ts +1 -1
  115. package/dist/schema-export-pg.d.ts.map +1 -1
  116. package/dist/schema-export-pg.js +1 -1
  117. package/dist/schema-export-pg.js.map +1 -1
  118. package/dist/schema-export.d.ts +1 -1
  119. package/dist/schema-export.d.ts.map +1 -1
  120. package/dist/schema-export.js +1 -1
  121. package/dist/schema-export.js.map +1 -1
  122. package/dist/testing/fixture.d.ts +2 -2
  123. package/dist/testing/fixture.d.ts.map +1 -1
  124. package/dist/testing/fixture.js +11 -9
  125. package/dist/testing/fixture.js.map +1 -1
  126. package/dist/testing/messaging.d.ts +21 -0
  127. package/dist/testing/messaging.d.ts.map +1 -0
  128. package/dist/testing/messaging.js +34 -0
  129. package/dist/testing/messaging.js.map +1 -0
  130. package/dist/testing.d.ts +1 -0
  131. package/dist/testing.d.ts.map +1 -1
  132. package/dist/testing.js.map +1 -1
  133. package/package.json +6 -2
  134. package/skills/creating-bunderstack-apps/references/application-structure.md +16 -7
  135. package/skills/migrating-to-bunderstack/SKILL.md +72 -50
  136. package/skills/migrating-to-bunderstack/references/audit-checklist.md +2 -2
  137. package/skills/migrating-to-bunderstack/references/runtime-replacements.md +16 -12
package/dist/manifest.js CHANGED
@@ -1,7 +1,8 @@
1
1
  import { getTableName, isTable } from 'drizzle-orm';
2
2
  import * as v from 'valibot';
3
- import { bunderstackEmailEvents, bunderstackEmails, bunderstackFiles, bunderstackIdempotency, bunderstackJobs, } from './internal-tables.js';
3
+ import { bunderstackMessageEvents, bunderstackMessages, bunderstackFiles, bunderstackIdempotency, bunderstackJobs, } from './internal-tables.js';
4
4
  import { parseCron } from './jobs/cron.js';
5
+ import { isMessagingDescriptor } from './messaging/types.js';
5
6
  import { StandardSchemaValidationError, validateStandardSchema, } from './standard-schema.js';
6
7
  const MAX_ENV_DESCRIPTION = 200;
7
8
  const nonEmpty = v.pipe(v.string(), v.minLength(1));
@@ -18,7 +19,7 @@ const cronSchedule = v.pipe(nonEmpty, v.check((value) => {
18
19
  }
19
20
  }, 'invalid cron schedule'));
20
21
  const manifestSchema = v.strictObject({
21
- version: v.literal(3),
22
+ version: v.literal(4),
22
23
  database: v.strictObject({
23
24
  dialect: v.picklist(['sqlite', 'pg']),
24
25
  migrationsDirectory: migrationDirectory,
@@ -36,6 +37,9 @@ const manifestSchema = v.strictObject({
36
37
  })),
37
38
  }),
38
39
  realtime: v.strictObject({ required: v.boolean() }),
40
+ messaging: v.strictObject({
41
+ channels: v.array(v.strictObject({ name: nonEmpty, kind: nonEmpty, provider: nonEmpty })),
42
+ }),
39
43
  environment: v.array(v.strictObject({
40
44
  key: nonEmpty,
41
45
  required: v.boolean(),
@@ -122,13 +126,13 @@ function describeSection(section, scope, meta) {
122
126
  function systemTables() {
123
127
  return [
124
128
  {
125
- exportName: '_system.emailEvents',
126
- physicalName: getTableName(bunderstackEmailEvents),
129
+ exportName: '_system.messageEvents',
130
+ physicalName: getTableName(bunderstackMessageEvents),
127
131
  system: true,
128
132
  },
129
133
  {
130
- exportName: '_system.emails',
131
- physicalName: getTableName(bunderstackEmails),
134
+ exportName: '_system.messages',
135
+ physicalName: getTableName(bunderstackMessages),
132
136
  system: true,
133
137
  },
134
138
  {
@@ -154,6 +158,7 @@ export function parseManifest(value) {
154
158
  rejectDuplicates('database export table', manifest.database.tables.map((entry) => entry.exportName));
155
159
  rejectDuplicates('storage bucket', manifest.storage.buckets.map((entry) => entry.name));
156
160
  rejectDuplicates('environment key', manifest.environment.map((entry) => entry.key));
161
+ rejectDuplicates('messaging channel', manifest.messaging.channels.map((entry) => entry.name));
157
162
  rejectDuplicates('api operation', manifest.api.operations.map((entry) => entry.handle));
158
163
  rejectDuplicates('background job', manifest.background.jobs.map((entry) => entry.name));
159
164
  rejectDuplicates('background cron', manifest.background.cron.map((entry) => entry.name));
@@ -161,6 +166,14 @@ export function parseManifest(value) {
161
166
  return manifest;
162
167
  }
163
168
  export function buildManifest(args) {
169
+ for (const [name, descriptor] of Object.entries(args.messaging ?? {})) {
170
+ if (!name.trim()) {
171
+ throw new Error('[bunderstack] messaging channel names cannot be empty');
172
+ }
173
+ if (!isMessagingDescriptor(descriptor)) {
174
+ throw new Error(`[bunderstack] messaging.${name} is not a provider descriptor`);
175
+ }
176
+ }
164
177
  const declaredKeys = new Set([
165
178
  ...Object.keys(args.envConfig?.server ?? {}),
166
179
  ...Object.keys(args.envConfig?.client ?? {}),
@@ -172,32 +185,10 @@ export function buildManifest(args) {
172
185
  const environment = [
173
186
  ...describeSection(args.envConfig?.server, 'server', args.envConfig?.meta),
174
187
  ...describeSection(args.envConfig?.client, 'client', args.envConfig?.meta),
175
- ...(args.emailProvider === 'resend'
176
- ? [
177
- {
178
- key: 'RESEND_API_KEY',
179
- required: true,
180
- scope: 'server',
181
- sensitive: true,
182
- description: 'Resend API key used to send transactional email',
183
- },
184
- ]
185
- : []),
186
- ...(args.emailProvider === 'smtp'
187
- ? [
188
- {
189
- key: 'SMTP_URL',
190
- required: true,
191
- scope: 'server',
192
- sensitive: true,
193
- description: 'SMTP connection URL used to send transactional email',
194
- },
195
- ]
196
- : []),
197
188
  ];
198
189
  rejectDuplicates('environment key', environment.map((entry) => entry.key));
199
190
  return parseManifest({
200
- version: 3,
191
+ version: 4,
201
192
  database: {
202
193
  dialect: args.dialect,
203
194
  migrationsDirectory: args.migrationsDirectory,
@@ -211,6 +202,13 @@ export function buildManifest(args) {
211
202
  })), (bucket) => bucket.name),
212
203
  },
213
204
  realtime: { required: args.realtime },
205
+ messaging: {
206
+ channels: sortBy(Object.entries(args.messaging ?? {}).map(([name, descriptor]) => ({
207
+ name,
208
+ kind: descriptor.kind,
209
+ provider: descriptor.provider,
210
+ })), (entry) => entry.name),
211
+ },
214
212
  environment: sortBy(environment, (entry) => entry.key),
215
213
  api: {
216
214
  operations: [...args.api].sort((left, right) => left.handle.localeCompare(right.handle)),
@@ -1 +1 @@
1
- {"version":3,"file":"manifest.js","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"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","sourcesContent":["import type { StandardSchemaV1 } from '@standard-schema/spec'\n\nimport { getTableName, isTable } from 'drizzle-orm'\nimport * as v from 'valibot'\n\nimport type { Dialect } from './dialect'\nimport type { EnvConfigInput, EnvVarMeta } from './env'\nimport type { JobsDefs } from './jobs/define'\nimport type { ResolvedBucket, ResolvedStorageBuckets } from './storage/buckets'\n\nimport {\n bunderstackEmailEvents,\n bunderstackEmails,\n bunderstackFiles,\n bunderstackIdempotency,\n bunderstackJobs,\n} from './internal-tables'\nimport { parseCron } from './jobs/cron'\nimport {\n StandardSchemaValidationError,\n validateStandardSchema,\n} from './standard-schema'\n\nexport type ManifestEnvVar = {\n key: string\n required: boolean\n scope: 'server' | 'client'\n sensitive: boolean\n description?: string\n}\n\nexport type ApiOperationEffect = 'read' | 'mutation' | 'unknown'\n\n/**\n * One application-declared procedure. `effect` is derived from the declared\n * HTTP method; `unknown` means the application declared no route, and a host\n * must treat it as at least as dangerous as a mutation.\n */\nexport type ApiOperation = {\n handle: string\n operationId: string\n effect: ApiOperationEffect\n method?: string\n path?: string\n summary?: string\n}\n\nexport type BunderstackManifest = {\n version: 3\n database: {\n dialect: Dialect\n migrationsDirectory: string\n tables: { exportName: string; physicalName: string; system: boolean }[]\n }\n storage: {\n defaultBucket: string\n buckets: { name: string; visibility: ResolvedBucket['visibility'] }[]\n }\n realtime: { required: boolean }\n environment: ManifestEnvVar[]\n api: { operations: ApiOperation[] }\n background: {\n jobs: { name: string }[]\n cron: { name: string; schedule: string; timezone: 'UTC' }[]\n maintenance: {\n name: 'storage-sweep'\n schedule: string\n timezone: 'UTC'\n }[]\n }\n}\n\nconst MAX_ENV_DESCRIPTION = 200\n\nconst nonEmpty = v.pipe(v.string(), v.minLength(1))\nconst migrationDirectory = v.pipe(\n nonEmpty,\n v.check(\n (value) =>\n value.startsWith('/') ||\n (!value.includes('\\\\') &&\n value.split('/').every((part) => part !== '..' && part !== '')),\n 'migrationsDirectory must be an absolute path or a relative path without traversal',\n ),\n)\nconst cronSchedule = v.pipe(\n nonEmpty,\n v.check((value) => {\n try {\n parseCron(value)\n return true\n } catch {\n return false\n }\n }, 'invalid cron schedule'),\n)\n\nconst manifestSchema = v.strictObject({\n version: v.literal(3),\n database: v.strictObject({\n dialect: v.picklist(['sqlite', 'pg']),\n migrationsDirectory: migrationDirectory,\n tables: v.array(\n v.strictObject({\n exportName: nonEmpty,\n physicalName: nonEmpty,\n system: v.boolean(),\n }),\n ),\n }),\n storage: v.strictObject({\n defaultBucket: nonEmpty,\n buckets: v.array(\n v.strictObject({\n name: nonEmpty,\n visibility: v.picklist(['public', 'private']),\n }),\n ),\n }),\n realtime: v.strictObject({ required: v.boolean() }),\n environment: v.array(\n v.strictObject({\n key: nonEmpty,\n required: v.boolean(),\n scope: v.picklist(['server', 'client']),\n sensitive: v.boolean(),\n description: v.optional(\n v.pipe(nonEmpty, v.maxLength(MAX_ENV_DESCRIPTION)),\n ),\n }),\n ),\n api: v.strictObject({\n operations: v.array(\n v.strictObject({\n handle: nonEmpty,\n operationId: nonEmpty,\n effect: v.picklist(['read', 'mutation', 'unknown']),\n method: v.optional(nonEmpty),\n path: v.optional(nonEmpty),\n summary: v.optional(v.pipe(nonEmpty, v.maxLength(200))),\n }),\n ),\n }),\n background: v.strictObject({\n jobs: v.array(v.strictObject({ name: nonEmpty })),\n cron: v.array(\n v.strictObject({\n name: nonEmpty,\n schedule: cronSchedule,\n timezone: v.literal('UTC'),\n }),\n ),\n maintenance: v.array(\n v.strictObject({\n name: v.literal('storage-sweep'),\n schedule: cronSchedule,\n timezone: v.literal('UTC'),\n }),\n ),\n }),\n})\n\nfunction sortBy<T>(entries: readonly T[], key: (entry: T) => string): T[] {\n return [...entries].sort((left, right) => key(left).localeCompare(key(right)))\n}\n\nfunction rejectDuplicates(collection: string, values: readonly string[]): void {\n const seen = new Set<string>()\n for (const value of values) {\n if (seen.has(value))\n throw new Error(`[bunderstack] duplicate ${collection} \"${value}\"`)\n seen.add(value)\n }\n}\n\nfunction describeTables(schema: Record<string, unknown>) {\n const systemNames = new Set<string>(\n systemTables().map((table) => table.physicalName),\n )\n return sortBy(\n Object.entries(schema).flatMap(([exportName, value]) =>\n isTable(value) && !systemNames.has(getTableName(value))\n ? [{ exportName, physicalName: getTableName(value), system: false }]\n : [],\n ),\n (entry) => entry.physicalName,\n )\n}\n\nfunction envDescription(key: string, value: string): string {\n const description = value.trim().replace(/\\s+/g, ' ')\n if (!description)\n throw new Error(\n `[bunderstack] env.meta.${key}.description must not be empty`,\n )\n if (description.length > MAX_ENV_DESCRIPTION)\n throw new Error(\n `[bunderstack] env.meta.${key}.description must be at most ${MAX_ENV_DESCRIPTION} characters`,\n )\n return description\n}\n\nfunction describeSection(\n section: Record<string, StandardSchemaV1> | undefined,\n scope: ManifestEnvVar['scope'],\n meta: Record<string, EnvVarMeta> | undefined,\n): ManifestEnvVar[] {\n return Object.entries(section ?? {}).map(([key, schema]) => {\n let required = false\n try {\n validateStandardSchema(schema, undefined, 'env')\n } catch (error) {\n if (!(error instanceof StandardSchemaValidationError)) throw error\n required = true\n }\n const entry = meta?.[key]\n const sensitive = entry?.sensitive ?? scope === 'server'\n if (scope === 'client' && sensitive) {\n throw new Error(\n `[bunderstack] env.meta.${key}.sensitive must be false: client values ship to the browser`,\n )\n }\n return {\n key,\n required,\n scope,\n sensitive,\n ...(entry?.description === undefined\n ? {}\n : { description: envDescription(key, entry.description) }),\n }\n })\n}\n\nfunction systemTables() {\n return [\n {\n exportName: '_system.emailEvents',\n physicalName: getTableName(bunderstackEmailEvents),\n system: true,\n },\n {\n exportName: '_system.emails',\n physicalName: getTableName(bunderstackEmails),\n system: true,\n },\n {\n exportName: '_system.files',\n physicalName: getTableName(bunderstackFiles),\n system: true,\n },\n {\n exportName: '_system.idempotency',\n physicalName: getTableName(bunderstackIdempotency),\n system: true,\n },\n {\n exportName: '_system.jobs',\n physicalName: getTableName(bunderstackJobs),\n system: true,\n },\n ]\n}\n\nexport function parseManifest(value: unknown): BunderstackManifest {\n const manifest = validateStandardSchema(\n manifestSchema,\n value,\n 'manifest',\n ) as BunderstackManifest\n rejectDuplicates(\n 'database physical table',\n manifest.database.tables.map((entry) => entry.physicalName),\n )\n rejectDuplicates(\n 'database export table',\n manifest.database.tables.map((entry) => entry.exportName),\n )\n rejectDuplicates(\n 'storage bucket',\n manifest.storage.buckets.map((entry) => entry.name),\n )\n rejectDuplicates(\n 'environment key',\n manifest.environment.map((entry) => entry.key),\n )\n rejectDuplicates(\n 'api operation',\n manifest.api.operations.map((entry) => entry.handle),\n )\n rejectDuplicates(\n 'background job',\n manifest.background.jobs.map((entry) => entry.name),\n )\n rejectDuplicates(\n 'background cron',\n manifest.background.cron.map((entry) => entry.name),\n )\n rejectDuplicates(\n 'background maintenance',\n manifest.background.maintenance.map((entry) => entry.name),\n )\n return manifest\n}\n\nexport function buildManifest(args: {\n schema: Record<string, unknown>\n dialect: Dialect\n migrationsDirectory: string\n storage: ResolvedStorageBuckets\n envConfig: EnvConfigInput | undefined\n emailProvider: string | undefined\n realtime: boolean\n jobs: JobsDefs | undefined\n api: ApiOperation[]\n}): BunderstackManifest {\n const declaredKeys = new Set([\n ...Object.keys(args.envConfig?.server ?? {}),\n ...Object.keys(args.envConfig?.client ?? {}),\n ])\n for (const key of Object.keys(args.envConfig?.meta ?? {})) {\n if (!declaredKeys.has(key))\n throw new Error(\n `[bunderstack] env.meta references undeclared key \"${key}\"`,\n )\n }\n const environment = [\n ...describeSection(args.envConfig?.server, 'server', args.envConfig?.meta),\n ...describeSection(args.envConfig?.client, 'client', args.envConfig?.meta),\n ...(args.emailProvider === 'resend'\n ? [\n {\n key: 'RESEND_API_KEY',\n required: true,\n scope: 'server' as const,\n sensitive: true,\n description: 'Resend API key used to send transactional email',\n },\n ]\n : []),\n ...(args.emailProvider === 'smtp'\n ? [\n {\n key: 'SMTP_URL',\n required: true,\n scope: 'server' as const,\n sensitive: true,\n description: 'SMTP connection URL used to send transactional email',\n },\n ]\n : []),\n ]\n rejectDuplicates(\n 'environment key',\n environment.map((entry) => entry.key),\n )\n\n return parseManifest({\n version: 3,\n database: {\n dialect: args.dialect,\n migrationsDirectory: args.migrationsDirectory,\n tables: sortBy(\n [...systemTables(), ...describeTables(args.schema)],\n (entry) => entry.physicalName,\n ),\n },\n storage: {\n defaultBucket: args.storage.defaultBucket,\n buckets: sortBy(\n [...args.storage.buckets.values()].map((bucket) => ({\n name: bucket.name,\n visibility: bucket.visibility,\n })),\n (bucket) => bucket.name,\n ),\n },\n realtime: { required: args.realtime },\n environment: sortBy(environment, (entry) => entry.key),\n api: {\n operations: [...args.api].sort((left, right) =>\n left.handle.localeCompare(right.handle),\n ),\n },\n background: {\n jobs: sortBy(\n Object.entries(args.jobs ?? {})\n .filter(([, def]) => def.kind === 'job')\n .map(([name]) => ({ name })),\n (entry) => entry.name,\n ),\n cron: sortBy(\n Object.entries(args.jobs ?? {})\n .filter(([, def]) => def.kind === 'cron')\n .map(([name, def]) => ({\n name,\n schedule: def.kind === 'cron' ? def.schedule : '',\n timezone: 'UTC' as const,\n })),\n (entry) => entry.name,\n ),\n maintenance: [\n {\n name: 'storage-sweep',\n schedule: '0 4 * * *',\n timezone: 'UTC' as const,\n },\n ],\n },\n })\n}\n"]}
1
+ {"version":3,"file":"manifest.js","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"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","sourcesContent":["import type { StandardSchemaV1 } from '@standard-schema/spec'\n\nimport { getTableName, isTable } from 'drizzle-orm'\nimport * as v from 'valibot'\n\nimport type { Dialect } from './dialect'\nimport type { EnvConfigInput, EnvVarMeta } from './env'\nimport type { JobsDefs } from './jobs/define'\nimport type { MessagingConfig } from './messaging'\nimport type { ResolvedBucket, ResolvedStorageBuckets } from './storage/buckets'\n\nimport {\n bunderstackMessageEvents,\n bunderstackMessages,\n bunderstackFiles,\n bunderstackIdempotency,\n bunderstackJobs,\n} from './internal-tables'\nimport { parseCron } from './jobs/cron'\nimport { isMessagingDescriptor } from './messaging/types'\nimport {\n StandardSchemaValidationError,\n validateStandardSchema,\n} from './standard-schema'\n\nexport type ManifestEnvVar = {\n key: string\n required: boolean\n scope: 'server' | 'client'\n sensitive: boolean\n description?: string\n}\n\nexport type ApiOperationEffect = 'read' | 'mutation' | 'unknown'\n\n/**\n * One application-declared procedure. `effect` is derived from the declared\n * HTTP method; `unknown` means the application declared no route, and a host\n * must treat it as at least as dangerous as a mutation.\n */\nexport type ApiOperation = {\n handle: string\n operationId: string\n effect: ApiOperationEffect\n method?: string\n path?: string\n summary?: string\n}\n\nexport type BunderstackManifest = {\n version: 4\n database: {\n dialect: Dialect\n migrationsDirectory: string\n tables: { exportName: string; physicalName: string; system: boolean }[]\n }\n storage: {\n defaultBucket: string\n buckets: { name: string; visibility: ResolvedBucket['visibility'] }[]\n }\n realtime: { required: boolean }\n messaging: {\n channels: { name: string; kind: string; provider: string }[]\n }\n environment: ManifestEnvVar[]\n api: { operations: ApiOperation[] }\n background: {\n jobs: { name: string }[]\n cron: { name: string; schedule: string; timezone: 'UTC' }[]\n maintenance: {\n name: 'storage-sweep'\n schedule: string\n timezone: 'UTC'\n }[]\n }\n}\n\nconst MAX_ENV_DESCRIPTION = 200\n\nconst nonEmpty = v.pipe(v.string(), v.minLength(1))\nconst migrationDirectory = v.pipe(\n nonEmpty,\n v.check(\n (value) =>\n value.startsWith('/') ||\n (!value.includes('\\\\') &&\n value.split('/').every((part) => part !== '..' && part !== '')),\n 'migrationsDirectory must be an absolute path or a relative path without traversal',\n ),\n)\nconst cronSchedule = v.pipe(\n nonEmpty,\n v.check((value) => {\n try {\n parseCron(value)\n return true\n } catch {\n return false\n }\n }, 'invalid cron schedule'),\n)\n\nconst manifestSchema = v.strictObject({\n version: v.literal(4),\n database: v.strictObject({\n dialect: v.picklist(['sqlite', 'pg']),\n migrationsDirectory: migrationDirectory,\n tables: v.array(\n v.strictObject({\n exportName: nonEmpty,\n physicalName: nonEmpty,\n system: v.boolean(),\n }),\n ),\n }),\n storage: v.strictObject({\n defaultBucket: nonEmpty,\n buckets: v.array(\n v.strictObject({\n name: nonEmpty,\n visibility: v.picklist(['public', 'private']),\n }),\n ),\n }),\n realtime: v.strictObject({ required: v.boolean() }),\n messaging: v.strictObject({\n channels: v.array(\n v.strictObject({ name: nonEmpty, kind: nonEmpty, provider: nonEmpty }),\n ),\n }),\n environment: v.array(\n v.strictObject({\n key: nonEmpty,\n required: v.boolean(),\n scope: v.picklist(['server', 'client']),\n sensitive: v.boolean(),\n description: v.optional(\n v.pipe(nonEmpty, v.maxLength(MAX_ENV_DESCRIPTION)),\n ),\n }),\n ),\n api: v.strictObject({\n operations: v.array(\n v.strictObject({\n handle: nonEmpty,\n operationId: nonEmpty,\n effect: v.picklist(['read', 'mutation', 'unknown']),\n method: v.optional(nonEmpty),\n path: v.optional(nonEmpty),\n summary: v.optional(v.pipe(nonEmpty, v.maxLength(200))),\n }),\n ),\n }),\n background: v.strictObject({\n jobs: v.array(v.strictObject({ name: nonEmpty })),\n cron: v.array(\n v.strictObject({\n name: nonEmpty,\n schedule: cronSchedule,\n timezone: v.literal('UTC'),\n }),\n ),\n maintenance: v.array(\n v.strictObject({\n name: v.literal('storage-sweep'),\n schedule: cronSchedule,\n timezone: v.literal('UTC'),\n }),\n ),\n }),\n})\n\nfunction sortBy<T>(entries: readonly T[], key: (entry: T) => string): T[] {\n return [...entries].sort((left, right) => key(left).localeCompare(key(right)))\n}\n\nfunction rejectDuplicates(collection: string, values: readonly string[]): void {\n const seen = new Set<string>()\n for (const value of values) {\n if (seen.has(value))\n throw new Error(`[bunderstack] duplicate ${collection} \"${value}\"`)\n seen.add(value)\n }\n}\n\nfunction describeTables(schema: Record<string, unknown>) {\n const systemNames = new Set<string>(\n systemTables().map((table) => table.physicalName),\n )\n return sortBy(\n Object.entries(schema).flatMap(([exportName, value]) =>\n isTable(value) && !systemNames.has(getTableName(value))\n ? [{ exportName, physicalName: getTableName(value), system: false }]\n : [],\n ),\n (entry) => entry.physicalName,\n )\n}\n\nfunction envDescription(key: string, value: string): string {\n const description = value.trim().replace(/\\s+/g, ' ')\n if (!description)\n throw new Error(\n `[bunderstack] env.meta.${key}.description must not be empty`,\n )\n if (description.length > MAX_ENV_DESCRIPTION)\n throw new Error(\n `[bunderstack] env.meta.${key}.description must be at most ${MAX_ENV_DESCRIPTION} characters`,\n )\n return description\n}\n\nfunction describeSection(\n section: Record<string, StandardSchemaV1> | undefined,\n scope: ManifestEnvVar['scope'],\n meta: Record<string, EnvVarMeta> | undefined,\n): ManifestEnvVar[] {\n return Object.entries(section ?? {}).map(([key, schema]) => {\n let required = false\n try {\n validateStandardSchema(schema, undefined, 'env')\n } catch (error) {\n if (!(error instanceof StandardSchemaValidationError)) throw error\n required = true\n }\n const entry = meta?.[key]\n const sensitive = entry?.sensitive ?? scope === 'server'\n if (scope === 'client' && sensitive) {\n throw new Error(\n `[bunderstack] env.meta.${key}.sensitive must be false: client values ship to the browser`,\n )\n }\n return {\n key,\n required,\n scope,\n sensitive,\n ...(entry?.description === undefined\n ? {}\n : { description: envDescription(key, entry.description) }),\n }\n })\n}\n\nfunction systemTables() {\n return [\n {\n exportName: '_system.messageEvents',\n physicalName: getTableName(bunderstackMessageEvents),\n system: true,\n },\n {\n exportName: '_system.messages',\n physicalName: getTableName(bunderstackMessages),\n system: true,\n },\n {\n exportName: '_system.files',\n physicalName: getTableName(bunderstackFiles),\n system: true,\n },\n {\n exportName: '_system.idempotency',\n physicalName: getTableName(bunderstackIdempotency),\n system: true,\n },\n {\n exportName: '_system.jobs',\n physicalName: getTableName(bunderstackJobs),\n system: true,\n },\n ]\n}\n\nexport function parseManifest(value: unknown): BunderstackManifest {\n const manifest = validateStandardSchema(\n manifestSchema,\n value,\n 'manifest',\n ) as BunderstackManifest\n rejectDuplicates(\n 'database physical table',\n manifest.database.tables.map((entry) => entry.physicalName),\n )\n rejectDuplicates(\n 'database export table',\n manifest.database.tables.map((entry) => entry.exportName),\n )\n rejectDuplicates(\n 'storage bucket',\n manifest.storage.buckets.map((entry) => entry.name),\n )\n rejectDuplicates(\n 'environment key',\n manifest.environment.map((entry) => entry.key),\n )\n rejectDuplicates(\n 'messaging channel',\n manifest.messaging.channels.map((entry) => entry.name),\n )\n rejectDuplicates(\n 'api operation',\n manifest.api.operations.map((entry) => entry.handle),\n )\n rejectDuplicates(\n 'background job',\n manifest.background.jobs.map((entry) => entry.name),\n )\n rejectDuplicates(\n 'background cron',\n manifest.background.cron.map((entry) => entry.name),\n )\n rejectDuplicates(\n 'background maintenance',\n manifest.background.maintenance.map((entry) => entry.name),\n )\n return manifest\n}\n\nexport function buildManifest(args: {\n schema: Record<string, unknown>\n dialect: Dialect\n migrationsDirectory: string\n storage: ResolvedStorageBuckets\n envConfig: EnvConfigInput | undefined\n messaging: MessagingConfig | undefined\n realtime: boolean\n jobs: JobsDefs | undefined\n api: ApiOperation[]\n}): BunderstackManifest {\n for (const [name, descriptor] of Object.entries(args.messaging ?? {})) {\n if (!name.trim()) {\n throw new Error('[bunderstack] messaging channel names cannot be empty')\n }\n if (!isMessagingDescriptor(descriptor)) {\n throw new Error(\n `[bunderstack] messaging.${name} is not a provider descriptor`,\n )\n }\n }\n const declaredKeys = new Set([\n ...Object.keys(args.envConfig?.server ?? {}),\n ...Object.keys(args.envConfig?.client ?? {}),\n ])\n for (const key of Object.keys(args.envConfig?.meta ?? {})) {\n if (!declaredKeys.has(key))\n throw new Error(\n `[bunderstack] env.meta references undeclared key \"${key}\"`,\n )\n }\n const environment = [\n ...describeSection(args.envConfig?.server, 'server', args.envConfig?.meta),\n ...describeSection(args.envConfig?.client, 'client', args.envConfig?.meta),\n ]\n rejectDuplicates(\n 'environment key',\n environment.map((entry) => entry.key),\n )\n\n return parseManifest({\n version: 4,\n database: {\n dialect: args.dialect,\n migrationsDirectory: args.migrationsDirectory,\n tables: sortBy(\n [...systemTables(), ...describeTables(args.schema)],\n (entry) => entry.physicalName,\n ),\n },\n storage: {\n defaultBucket: args.storage.defaultBucket,\n buckets: sortBy(\n [...args.storage.buckets.values()].map((bucket) => ({\n name: bucket.name,\n visibility: bucket.visibility,\n })),\n (bucket) => bucket.name,\n ),\n },\n realtime: { required: args.realtime },\n messaging: {\n channels: sortBy(\n Object.entries(args.messaging ?? {}).map(([name, descriptor]) => ({\n name,\n kind: descriptor.kind,\n provider: descriptor.provider,\n })),\n (entry) => entry.name,\n ),\n },\n environment: sortBy(environment, (entry) => entry.key),\n api: {\n operations: [...args.api].sort((left, right) =>\n left.handle.localeCompare(right.handle),\n ),\n },\n background: {\n jobs: sortBy(\n Object.entries(args.jobs ?? {})\n .filter(([, def]) => def.kind === 'job')\n .map(([name]) => ({ name })),\n (entry) => entry.name,\n ),\n cron: sortBy(\n Object.entries(args.jobs ?? {})\n .filter(([, def]) => def.kind === 'cron')\n .map(([name, def]) => ({\n name,\n schedule: def.kind === 'cron' ? def.schedule : '',\n timezone: 'UTC' as const,\n })),\n (entry) => entry.name,\n ),\n maintenance: [\n {\n name: 'storage-sweep',\n schedule: '0 4 * * *',\n timezone: 'UTC' as const,\n },\n ],\n },\n })\n}\n"]}
@@ -0,0 +1,16 @@
1
+ import type { EmailAdapter, EmailMessage, SentEmail } from '../email.js';
2
+ import { type MessagingDescriptor } from './types.js';
3
+ export type ResendConfig = {
4
+ apiKey?: string;
5
+ from?: string;
6
+ };
7
+ export type ResendDescriptor = MessagingDescriptor<'email', 'resend', EmailMessage, SentEmail, ResendConfig>;
8
+ export declare function resend(config?: ResendConfig): ResendDescriptor;
9
+ export type CustomEmailConfig = {
10
+ adapter?: EmailAdapter | EmailAdapter['send'];
11
+ from?: string;
12
+ provider?: string;
13
+ };
14
+ export type CustomEmailDescriptor = MessagingDescriptor<'email', 'custom', EmailMessage, SentEmail, CustomEmailConfig>;
15
+ export declare function customEmail(config?: CustomEmailConfig): CustomEmailDescriptor;
16
+ //# sourceMappingURL=email.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"email.d.ts","sourceRoot":"","sources":["../../src/messaging/email.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,UAAU,CAAA;AAErE,OAAO,EAAc,KAAK,mBAAmB,EAAE,MAAM,SAAS,CAAA;AAE9D,MAAM,MAAM,YAAY,GAAG;IACzB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,IAAI,CAAC,EAAE,MAAM,CAAA;CACd,CAAA;AAED,MAAM,MAAM,gBAAgB,GAAG,mBAAmB,CAChD,OAAO,EACP,QAAQ,EACR,YAAY,EACZ,SAAS,EACT,YAAY,CACb,CAAA;AAED,wBAAgB,MAAM,CAAC,MAAM,GAAE,YAAiB,GAAG,gBAAgB,CAElE;AAED,MAAM,MAAM,iBAAiB,GAAG;IAC9B,OAAO,CAAC,EAAE,YAAY,GAAG,YAAY,CAAC,MAAM,CAAC,CAAA;IAC7C,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB,CAAA;AAED,MAAM,MAAM,qBAAqB,GAAG,mBAAmB,CACrD,OAAO,EACP,QAAQ,EACR,YAAY,EACZ,SAAS,EACT,iBAAiB,CAClB,CAAA;AAED,wBAAgB,WAAW,CACzB,MAAM,GAAE,iBAAsB,GAC7B,qBAAqB,CAEvB"}
@@ -0,0 +1,8 @@
1
+ import { descriptor } from './types.js';
2
+ export function resend(config = {}) {
3
+ return descriptor('email', 'resend', config);
4
+ }
5
+ export function customEmail(config = {}) {
6
+ return descriptor('email', 'custom', config);
7
+ }
8
+ //# sourceMappingURL=email.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"email.js","sourceRoot":"","sources":["../../src/messaging/email.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,UAAU,EAA4B,MAAM,SAAS,CAAA;AAe9D,MAAM,UAAU,MAAM,CAAC,SAAuB,EAAE;IAC9C,OAAO,UAAU,CAAC,OAAO,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAA;AAC9C,CAAC;AAgBD,MAAM,UAAU,WAAW,CACzB,SAA4B,EAAE;IAE9B,OAAO,UAAU,CAAC,OAAO,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAA;AAC9C,CAAC","sourcesContent":["import type { EmailAdapter, EmailMessage, SentEmail } from '../email'\n\nimport { descriptor, type MessagingDescriptor } from './types'\n\nexport type ResendConfig = {\n apiKey?: string\n from?: string\n}\n\nexport type ResendDescriptor = MessagingDescriptor<\n 'email',\n 'resend',\n EmailMessage,\n SentEmail,\n ResendConfig\n>\n\nexport function resend(config: ResendConfig = {}): ResendDescriptor {\n return descriptor('email', 'resend', config)\n}\n\nexport type CustomEmailConfig = {\n adapter?: EmailAdapter | EmailAdapter['send']\n from?: string\n provider?: string\n}\n\nexport type CustomEmailDescriptor = MessagingDescriptor<\n 'email',\n 'custom',\n EmailMessage,\n SentEmail,\n CustomEmailConfig\n>\n\nexport function customEmail(\n config: CustomEmailConfig = {},\n): CustomEmailDescriptor {\n return descriptor('email', 'custom', config)\n}\n"]}
@@ -0,0 +1,8 @@
1
+ export { resend, customEmail } from './email.js';
2
+ export { createMessaging } from './standalone.js';
3
+ export type { MessagingContext } from './standalone.js';
4
+ export type { CustomEmailConfig, CustomEmailDescriptor, ResendConfig, ResendDescriptor, } from './email.js';
5
+ export { telegram } from './telegram.js';
6
+ export type { SentTelegramMessage, TelegramConfig, TelegramDescriptor, TelegramMessage, } from './telegram.js';
7
+ export type { AnyMessagingDescriptor, MessagingConfig, MessagingDescriptor, MessagingFacade, MessagingFacades, MessagingFacadesFor, } from './types.js';
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/messaging/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AAC7C,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAA;AAC9C,YAAY,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAA;AACpD,YAAY,EACV,iBAAiB,EACjB,qBAAqB,EACrB,YAAY,EACZ,gBAAgB,GACjB,MAAM,SAAS,CAAA;AAChB,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AACrC,YAAY,EACV,mBAAmB,EACnB,cAAc,EACd,kBAAkB,EAClB,eAAe,GAChB,MAAM,YAAY,CAAA;AACnB,YAAY,EACV,sBAAsB,EACtB,eAAe,EACf,mBAAmB,EACnB,eAAe,EACf,gBAAgB,EAChB,mBAAmB,GACpB,MAAM,SAAS,CAAA"}
@@ -0,0 +1,4 @@
1
+ export { resend, customEmail } from './email.js';
2
+ export { createMessaging } from './standalone.js';
3
+ export { telegram } from './telegram.js';
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/messaging/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AAC7C,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAA;AAQ9C,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA","sourcesContent":["export { resend, customEmail } from './email'\nexport { createMessaging } from './standalone'\nexport type { MessagingContext } from './standalone'\nexport type {\n CustomEmailConfig,\n CustomEmailDescriptor,\n ResendConfig,\n ResendDescriptor,\n} from './email'\nexport { telegram } from './telegram'\nexport type {\n SentTelegramMessage,\n TelegramConfig,\n TelegramDescriptor,\n TelegramMessage,\n} from './telegram'\nexport type {\n AnyMessagingDescriptor,\n MessagingConfig,\n MessagingDescriptor,\n MessagingFacade,\n MessagingFacades,\n MessagingFacadesFor,\n} from './types'\n"]}
@@ -0,0 +1,4 @@
1
+ import type { AnyDb } from '../dialect.js';
2
+ export declare function insertMessage(db: AnyDb | undefined, value: Record<string, unknown>): Promise<void>;
3
+ export declare function updateMessage(db: AnyDb | undefined, id: string, value: Record<string, unknown>, expectedStatus?: string): Promise<void>;
4
+ //# sourceMappingURL=journal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"journal.d.ts","sourceRoot":"","sources":["../../src/messaging/journal.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAA;AAIvC,wBAAsB,aAAa,CACjC,EAAE,EAAE,KAAK,GAAG,SAAS,EACrB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,iBAI/B;AAED,wBAAsB,aAAa,CACjC,EAAE,EAAE,KAAK,GAAG,SAAS,EACrB,EAAE,EAAE,MAAM,EACV,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,cAAc,CAAC,EAAE,MAAM,iBAYxB"}
@@ -0,0 +1,19 @@
1
+ import { and, eq } from 'drizzle-orm';
2
+ import { messagesTableFor } from '../internal-tables.js';
3
+ export async function insertMessage(db, value) {
4
+ if (!db)
5
+ return;
6
+ await db.insert(messagesTableFor(db)).values(value);
7
+ }
8
+ export async function updateMessage(db, id, value, expectedStatus) {
9
+ if (!db)
10
+ return;
11
+ const table = messagesTableFor(db);
12
+ await db
13
+ .update(table)
14
+ .set(value)
15
+ .where(expectedStatus
16
+ ? and(eq(table.id, id), eq(table.status, expectedStatus))
17
+ : eq(table.id, id));
18
+ }
19
+ //# sourceMappingURL=journal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"journal.js","sourceRoot":"","sources":["../../src/messaging/journal.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,EAAE,EAAE,MAAM,aAAa,CAAA;AAIrC,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AAErD,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,EAAqB,EACrB,KAA8B;IAE9B,IAAI,CAAC,EAAE;QAAE,OAAM;IACf,MAAM,EAAE,CAAC,MAAM,CAAC,gBAAgB,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;AACrD,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,EAAqB,EACrB,EAAU,EACV,KAA8B,EAC9B,cAAuB;IAEvB,IAAI,CAAC,EAAE;QAAE,OAAM;IACf,MAAM,KAAK,GAAG,gBAAgB,CAAC,EAAE,CAAC,CAAA;IAClC,MAAM,EAAE;SACL,MAAM,CAAC,KAAK,CAAC;SACb,GAAG,CAAC,KAAK,CAAC;SACV,KAAK,CACJ,cAAc;QACZ,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;QACzD,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,CAAC,CACrB,CAAA;AACL,CAAC","sourcesContent":["import { and, eq } from 'drizzle-orm'\n\nimport type { AnyDb } from '../dialect'\n\nimport { messagesTableFor } from '../internal-tables'\n\nexport async function insertMessage(\n db: AnyDb | undefined,\n value: Record<string, unknown>,\n) {\n if (!db) return\n await db.insert(messagesTableFor(db)).values(value)\n}\n\nexport async function updateMessage(\n db: AnyDb | undefined,\n id: string,\n value: Record<string, unknown>,\n expectedStatus?: string,\n) {\n if (!db) return\n const table = messagesTableFor(db)\n await db\n .update(table)\n .set(value)\n .where(\n expectedStatus\n ? and(eq(table.id, id), eq(table.status, expectedStatus))\n : eq(table.id, id),\n )\n}\n"]}
@@ -0,0 +1,16 @@
1
+ import type { AnyDb } from '../dialect.js';
2
+ import type { BaseEnv } from '../env.js';
3
+ import type { MessagingConfig, MessagingFacades } from './types.js';
4
+ export type MessagingAdapter = {
5
+ send(input: any): Promise<{
6
+ id?: string;
7
+ }>;
8
+ };
9
+ export type CreateMessagingOptions = {
10
+ env: BaseEnv;
11
+ db?: AnyDb;
12
+ fetchFn?: typeof fetch;
13
+ adapterOverrides?: Record<string, MessagingAdapter>;
14
+ };
15
+ export declare function createMessaging<const TConfig extends MessagingConfig>(config: TConfig, options: CreateMessagingOptions): MessagingFacades<TConfig>;
16
+ //# sourceMappingURL=runtime.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../src/messaging/runtime.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAA;AAEvC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAA;AAErC,OAAO,KAAK,EAEV,eAAe,EACf,gBAAgB,EACjB,MAAM,SAAS,CAAA;AAKhB,MAAM,MAAM,gBAAgB,GAAG;IAC7B,IAAI,CAAC,KAAK,EAAE,GAAG,GAAG,OAAO,CAAC;QAAE,EAAE,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAC3C,CAAA;AAED,MAAM,MAAM,sBAAsB,GAAG;IACnC,GAAG,EAAE,OAAO,CAAA;IACZ,EAAE,CAAC,EAAE,KAAK,CAAA;IACV,OAAO,CAAC,EAAE,OAAO,KAAK,CAAA;IACtB,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAA;CACpD,CAAA;AA+ID,wBAAgB,eAAe,CAAC,KAAK,CAAC,OAAO,SAAS,eAAe,EACnE,MAAM,EAAE,OAAO,EACf,OAAO,EAAE,sBAAsB,GAC9B,gBAAgB,CAAC,OAAO,CAAC,CA6H3B"}
@@ -0,0 +1,213 @@
1
+ import { insertMessage, updateMessage } from './journal.js';
2
+ import { isMessagingDescriptor } from './types.js';
3
+ const nonEmpty = (value) => typeof value === 'string' && value.trim().length > 0;
4
+ const array = (value) => value === undefined ? [] : Array.isArray(value) ? value : [value];
5
+ function managedConfig(raw) {
6
+ if (!nonEmpty(raw))
7
+ return {};
8
+ let value;
9
+ try {
10
+ value = JSON.parse(raw);
11
+ }
12
+ catch {
13
+ throw new Error('[bunderstack] BUNDERSTACK_MESSAGING_CONFIG must be valid JSON');
14
+ }
15
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
16
+ throw new Error('[bunderstack] BUNDERSTACK_MESSAGING_CONFIG must be an object');
17
+ }
18
+ return value;
19
+ }
20
+ function consoleCapture(channel, kind, input) {
21
+ console.log(`\n${'─'.repeat(60)}\n` +
22
+ `💬 ${channel} (${kind}, captured — not sent)\n` +
23
+ `${'─'.repeat(60)}\n` +
24
+ `${JSON.stringify(input, null, 2)}\n` +
25
+ `${'─'.repeat(60)}`);
26
+ }
27
+ function emailContent(input, from) {
28
+ return {
29
+ recipients: {
30
+ to: array(input.to),
31
+ cc: array(input.cc),
32
+ bcc: array(input.bcc),
33
+ },
34
+ content: {
35
+ subject: input.subject,
36
+ html: input.html,
37
+ text: input.text,
38
+ from,
39
+ replyTo: input.replyTo,
40
+ },
41
+ };
42
+ }
43
+ function telegramContent(input) {
44
+ return {
45
+ recipients: { to: [String(input.to)] },
46
+ content: { text: input.text, parseMode: input.parseMode },
47
+ };
48
+ }
49
+ function createResendAdapter(apiKey, fetchFn) {
50
+ return {
51
+ async send(input) {
52
+ const response = await fetchFn('https://api.resend.com/emails', {
53
+ method: 'POST',
54
+ headers: {
55
+ Authorization: `Bearer ${apiKey}`,
56
+ 'Content-Type': 'application/json',
57
+ },
58
+ body: JSON.stringify({
59
+ from: input.from,
60
+ to: array(input.to),
61
+ subject: input.subject,
62
+ html: input.html,
63
+ text: input.text,
64
+ reply_to: input.replyTo,
65
+ cc: array(input.cc),
66
+ bcc: array(input.bcc),
67
+ tags: input.tags,
68
+ }),
69
+ });
70
+ if (!response.ok) {
71
+ throw new Error(`resend API error (${response.status}): ${await response.text()}`);
72
+ }
73
+ return (await response.json());
74
+ },
75
+ };
76
+ }
77
+ function createTelegramAdapter(token, fetchFn) {
78
+ return {
79
+ async send(input) {
80
+ const response = await fetchFn(`https://api.telegram.org/bot${token}/sendMessage`, {
81
+ method: 'POST',
82
+ headers: { 'Content-Type': 'application/json' },
83
+ body: JSON.stringify({
84
+ chat_id: input.to,
85
+ text: input.text,
86
+ parse_mode: input.parseMode,
87
+ }),
88
+ });
89
+ if (!response.ok) {
90
+ throw new Error(`telegram API error (${response.status}): ${await response.text()}`);
91
+ }
92
+ const body = (await response.json());
93
+ return { id: body.result?.message_id?.toString() };
94
+ },
95
+ };
96
+ }
97
+ function providerConfig(descriptor, managed) {
98
+ const merged = { ...managed[descriptor.provider] };
99
+ for (const [key, value] of Object.entries(descriptor.config)) {
100
+ if (typeof value === 'string' ? nonEmpty(value) : value !== undefined) {
101
+ merged[key] = value;
102
+ }
103
+ }
104
+ return merged;
105
+ }
106
+ export function createMessaging(config, options) {
107
+ const managed = managedConfig(options.env.BUNDERSTACK_MESSAGING_CONFIG);
108
+ const entries = Object.entries(config).map(([channel, descriptor]) => {
109
+ if (!channel.trim())
110
+ throw new Error('[bunderstack] messaging channel names cannot be empty');
111
+ if (!isMessagingDescriptor(descriptor)) {
112
+ throw new Error(`[bunderstack] messaging.${channel} is not a provider descriptor`);
113
+ }
114
+ const merged = providerConfig(descriptor, managed);
115
+ const override = options.adapterOverrides?.[channel];
116
+ let adapter = override;
117
+ let source = override ? 'capture' : 'explicit';
118
+ let from;
119
+ if (!adapter &&
120
+ descriptor.kind === 'email' &&
121
+ descriptor.provider === 'resend') {
122
+ const own = descriptor.config;
123
+ const apiKey = merged.apiKey;
124
+ from = nonEmpty(merged.from) ? merged.from : undefined;
125
+ if (nonEmpty(apiKey) && from) {
126
+ source = nonEmpty(own.apiKey) ? 'explicit' : 'managed';
127
+ adapter = createResendAdapter(apiKey, options.fetchFn ?? fetch);
128
+ }
129
+ }
130
+ else if (!adapter && descriptor.kind === 'email') {
131
+ const own = descriptor.config;
132
+ from = nonEmpty(merged.from) ? merged.from : undefined;
133
+ if (own.adapter && from) {
134
+ adapter =
135
+ typeof own.adapter === 'function'
136
+ ? { send: own.adapter }
137
+ : own.adapter;
138
+ }
139
+ }
140
+ else if (!adapter && descriptor.kind === 'telegram') {
141
+ const own = descriptor.config;
142
+ const token = merged.botToken;
143
+ if (nonEmpty(token)) {
144
+ source = nonEmpty(own.botToken) ? 'explicit' : 'managed';
145
+ adapter = createTelegramAdapter(token, options.fetchFn ?? fetch);
146
+ }
147
+ }
148
+ const capture = !adapter || Boolean(override);
149
+ if (capture)
150
+ source = 'capture';
151
+ return [
152
+ channel,
153
+ {
154
+ async send(input) {
155
+ if (descriptor.kind === 'email' &&
156
+ !input.html &&
157
+ !input.text) {
158
+ throw new Error('email message needs html or text content');
159
+ }
160
+ const resolvedFrom = descriptor.kind === 'email'
161
+ ? (input.from ?? from ?? 'unknown@localhost')
162
+ : undefined;
163
+ const normalized = descriptor.kind === 'email'
164
+ ? emailContent(input, resolvedFrom)
165
+ : telegramContent(input);
166
+ const id = `message_${crypto.randomUUID()}`;
167
+ const now = Date.now();
168
+ await insertMessage(options.db, {
169
+ id,
170
+ channel,
171
+ kind: descriptor.kind,
172
+ provider: descriptor.provider,
173
+ credentialSource: source,
174
+ status: capture ? 'captured' : 'sending',
175
+ recipientsJson: JSON.stringify(normalized.recipients),
176
+ contentJson: JSON.stringify(normalized.content),
177
+ createdAt: now,
178
+ updatedAt: now,
179
+ });
180
+ if (capture) {
181
+ await adapter?.send(input);
182
+ if (!override && !options.env.BUNDERHOST_ENVIRONMENT_ID) {
183
+ consoleCapture(channel, descriptor.kind, input);
184
+ }
185
+ return { id };
186
+ }
187
+ try {
188
+ const outgoing = descriptor.kind === 'email'
189
+ ? { ...input, from: resolvedFrom }
190
+ : input;
191
+ const sent = await adapter.send(outgoing);
192
+ await updateMessage(options.db, id, {
193
+ providerId: sent.id || undefined,
194
+ status: 'sent',
195
+ updatedAt: Date.now(),
196
+ }, 'sending');
197
+ return { id, providerId: sent.id || undefined };
198
+ }
199
+ catch (error) {
200
+ await updateMessage(options.db, id, {
201
+ status: 'failed',
202
+ error: error instanceof Error ? error.message : String(error),
203
+ updatedAt: Date.now(),
204
+ }, 'sending');
205
+ throw error;
206
+ }
207
+ },
208
+ },
209
+ ];
210
+ });
211
+ return Object.fromEntries(entries);
212
+ }
213
+ //# sourceMappingURL=runtime.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime.js","sourceRoot":"","sources":["../../src/messaging/runtime.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AnyDb } from '../dialect'\nimport type { EmailAdapter, EmailMessage } from '../email'\nimport type { BaseEnv } from '../env'\nimport type { TelegramMessage } from './telegram'\nimport type {\n AnyMessagingDescriptor,\n MessagingConfig,\n MessagingFacades,\n} from './types'\n\nimport { insertMessage, updateMessage } from './journal'\nimport { isMessagingDescriptor } from './types'\n\nexport type MessagingAdapter = {\n send(input: any): Promise<{ id?: string }>\n}\n\nexport type CreateMessagingOptions = {\n env: BaseEnv\n db?: AnyDb\n fetchFn?: typeof fetch\n adapterOverrides?: Record<string, MessagingAdapter>\n}\n\ntype ManagedConfig = Record<string, Record<string, unknown>>\ntype CredentialSource = 'explicit' | 'managed' | 'capture'\n\nconst nonEmpty = (value: unknown): value is string =>\n typeof value === 'string' && value.trim().length > 0\n\nconst array = (value: string | string[] | undefined) =>\n value === undefined ? [] : Array.isArray(value) ? value : [value]\n\nfunction managedConfig(raw: string | undefined): ManagedConfig {\n if (!nonEmpty(raw)) return {}\n let value: unknown\n try {\n value = JSON.parse(raw)\n } catch {\n throw new Error(\n '[bunderstack] BUNDERSTACK_MESSAGING_CONFIG must be valid JSON',\n )\n }\n if (!value || typeof value !== 'object' || Array.isArray(value)) {\n throw new Error(\n '[bunderstack] BUNDERSTACK_MESSAGING_CONFIG must be an object',\n )\n }\n return value as ManagedConfig\n}\n\nfunction consoleCapture(channel: string, kind: string, input: unknown) {\n console.log(\n `\\n${'─'.repeat(60)}\\n` +\n `💬 ${channel} (${kind}, captured — not sent)\\n` +\n `${'─'.repeat(60)}\\n` +\n `${JSON.stringify(input, null, 2)}\\n` +\n `${'─'.repeat(60)}`,\n )\n}\n\nfunction emailContent(input: EmailMessage, from: string) {\n return {\n recipients: {\n to: array(input.to),\n cc: array(input.cc),\n bcc: array(input.bcc),\n },\n content: {\n subject: input.subject,\n html: input.html,\n text: input.text,\n from,\n replyTo: input.replyTo,\n },\n }\n}\n\nfunction telegramContent(input: TelegramMessage) {\n return {\n recipients: { to: [String(input.to)] },\n content: { text: input.text, parseMode: input.parseMode },\n }\n}\n\nfunction createResendAdapter(\n apiKey: string,\n fetchFn: typeof fetch,\n): MessagingAdapter {\n return {\n async send(input: EmailMessage & { from: string; tags?: unknown }) {\n const response = await fetchFn('https://api.resend.com/emails', {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${apiKey}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n from: input.from,\n to: array(input.to),\n subject: input.subject,\n html: input.html,\n text: input.text,\n reply_to: input.replyTo,\n cc: array(input.cc),\n bcc: array(input.bcc),\n tags: input.tags,\n }),\n })\n if (!response.ok) {\n throw new Error(\n `resend API error (${response.status}): ${await response.text()}`,\n )\n }\n return (await response.json()) as { id?: string }\n },\n }\n}\n\nfunction createTelegramAdapter(\n token: string,\n fetchFn: typeof fetch,\n): MessagingAdapter {\n return {\n async send(input: TelegramMessage) {\n const response = await fetchFn(\n `https://api.telegram.org/bot${token}/sendMessage`,\n {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n chat_id: input.to,\n text: input.text,\n parse_mode: input.parseMode,\n }),\n },\n )\n if (!response.ok) {\n throw new Error(\n `telegram API error (${response.status}): ${await response.text()}`,\n )\n }\n const body = (await response.json()) as {\n result?: { message_id?: number }\n }\n return { id: body.result?.message_id?.toString() }\n },\n }\n}\n\nfunction providerConfig(\n descriptor: AnyMessagingDescriptor,\n managed: ManagedConfig,\n): Record<string, unknown> {\n const merged = { ...managed[descriptor.provider] }\n for (const [key, value] of Object.entries(\n descriptor.config as Record<string, unknown>,\n )) {\n if (typeof value === 'string' ? nonEmpty(value) : value !== undefined) {\n merged[key] = value\n }\n }\n return merged\n}\n\nexport function createMessaging<const TConfig extends MessagingConfig>(\n config: TConfig,\n options: CreateMessagingOptions,\n): MessagingFacades<TConfig> {\n const managed = managedConfig(options.env.BUNDERSTACK_MESSAGING_CONFIG)\n const entries = Object.entries(config).map(([channel, descriptor]) => {\n if (!channel.trim())\n throw new Error('[bunderstack] messaging channel names cannot be empty')\n if (!isMessagingDescriptor(descriptor)) {\n throw new Error(\n `[bunderstack] messaging.${channel} is not a provider descriptor`,\n )\n }\n const merged = providerConfig(descriptor, managed)\n const override = options.adapterOverrides?.[channel]\n let adapter: MessagingAdapter | undefined = override\n let source: CredentialSource = override ? 'capture' : 'explicit'\n let from: string | undefined\n if (\n !adapter &&\n descriptor.kind === 'email' &&\n descriptor.provider === 'resend'\n ) {\n const own = descriptor.config as { apiKey?: string; from?: string }\n const apiKey = merged.apiKey\n from = nonEmpty(merged.from) ? merged.from : undefined\n if (nonEmpty(apiKey) && from) {\n source = nonEmpty(own.apiKey) ? 'explicit' : 'managed'\n adapter = createResendAdapter(apiKey, options.fetchFn ?? fetch)\n }\n } else if (!adapter && descriptor.kind === 'email') {\n const own = descriptor.config as {\n adapter?: EmailAdapter | EmailAdapter['send']\n from?: string\n }\n from = nonEmpty(merged.from) ? merged.from : undefined\n if (own.adapter && from) {\n adapter =\n typeof own.adapter === 'function'\n ? { send: own.adapter }\n : own.adapter\n }\n } else if (!adapter && descriptor.kind === 'telegram') {\n const own = descriptor.config as { botToken?: string }\n const token = merged.botToken\n if (nonEmpty(token)) {\n source = nonEmpty(own.botToken) ? 'explicit' : 'managed'\n adapter = createTelegramAdapter(token, options.fetchFn ?? fetch)\n }\n }\n const capture = !adapter || Boolean(override)\n if (capture) source = 'capture'\n\n return [\n channel,\n {\n async send(input: EmailMessage | TelegramMessage) {\n if (\n descriptor.kind === 'email' &&\n !(input as EmailMessage).html &&\n !(input as EmailMessage).text\n ) {\n throw new Error('email message needs html or text content')\n }\n const resolvedFrom =\n descriptor.kind === 'email'\n ? ((input as EmailMessage).from ?? from ?? 'unknown@localhost')\n : undefined\n const normalized =\n descriptor.kind === 'email'\n ? emailContent(input as EmailMessage, resolvedFrom!)\n : telegramContent(input as TelegramMessage)\n const id = `message_${crypto.randomUUID()}`\n const now = Date.now()\n await insertMessage(options.db, {\n id,\n channel,\n kind: descriptor.kind,\n provider: descriptor.provider,\n credentialSource: source,\n status: capture ? 'captured' : 'sending',\n recipientsJson: JSON.stringify(normalized.recipients),\n contentJson: JSON.stringify(normalized.content),\n createdAt: now,\n updatedAt: now,\n })\n if (capture) {\n await adapter?.send(input)\n if (!override && !options.env.BUNDERHOST_ENVIRONMENT_ID) {\n consoleCapture(channel, descriptor.kind, input)\n }\n return { id }\n }\n try {\n const outgoing =\n descriptor.kind === 'email'\n ? { ...input, from: resolvedFrom }\n : input\n const sent = await adapter!.send(outgoing)\n await updateMessage(\n options.db,\n id,\n {\n providerId: sent.id || undefined,\n status: 'sent',\n updatedAt: Date.now(),\n },\n 'sending',\n )\n return { id, providerId: sent.id || undefined }\n } catch (error) {\n await updateMessage(\n options.db,\n id,\n {\n status: 'failed',\n error: error instanceof Error ? error.message : String(error),\n updatedAt: Date.now(),\n },\n 'sending',\n )\n throw error\n }\n },\n },\n ]\n })\n return Object.fromEntries(entries) as MessagingFacades<TConfig>\n}\n"]}
@@ -0,0 +1,27 @@
1
+ import type { AnyDb } from '../dialect.js';
2
+ import type { BaseEnv } from '../env.js';
3
+ import type { MessagingConfig, MessagingFacades } from './types.js';
4
+ /**
5
+ * What a standalone messaging facade needs. `db` is the application's own
6
+ * connection: pass it so captured and sent messages reach the same journal the
7
+ * app writes, and omit it only when there is no database to write to.
8
+ */
9
+ export type MessagingContext = {
10
+ env: BaseEnv;
11
+ db?: AnyDb;
12
+ };
13
+ /**
14
+ * Builds channel facades outside a started app, for code that runs before or
15
+ * beside it — a Better Auth builder sending an invitation, a script, a
16
+ * one-off worker. Channels behave exactly as they do on `app.messaging`,
17
+ * capture included.
18
+ *
19
+ * ```ts
20
+ * export const auth = defineAuth(schema, ({ db, env }) => {
21
+ * const messaging = createMessaging({ email: resend({ from: env.EMAIL_FROM }) }, { env, db })
22
+ * return { …, sendInvitation: (to) => messaging.email.send({ to, … }) }
23
+ * })
24
+ * ```
25
+ */
26
+ export declare function createMessaging<const TConfig extends MessagingConfig>(channels: TConfig, context: MessagingContext): MessagingFacades<TConfig>;
27
+ //# sourceMappingURL=standalone.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"standalone.d.ts","sourceRoot":"","sources":["../../src/messaging/standalone.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAA;AACvC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAA;AACrC,OAAO,KAAK,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAIhE;;;;GAIG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC7B,GAAG,EAAE,OAAO,CAAA;IACZ,EAAE,CAAC,EAAE,KAAK,CAAA;CACX,CAAA;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,eAAe,CAAC,KAAK,CAAC,OAAO,SAAS,eAAe,EACnE,QAAQ,EAAE,OAAO,EACjB,OAAO,EAAE,gBAAgB,GACxB,gBAAgB,CAAC,OAAO,CAAC,CAE3B"}