bunderstack 0.22.1 → 0.23.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 (43) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/dist/api/catalog.d.ts +8 -0
  3. package/dist/api/catalog.d.ts.map +1 -0
  4. package/dist/api/catalog.js +56 -0
  5. package/dist/api/catalog.js.map +1 -0
  6. package/dist/api/registry.d.ts.map +1 -1
  7. package/dist/api/registry.js +1 -0
  8. package/dist/api/registry.js.map +1 -1
  9. package/dist/api/router.d.ts +3 -0
  10. package/dist/api/router.d.ts.map +1 -1
  11. package/dist/api/router.js +7 -2
  12. package/dist/api/router.js.map +1 -1
  13. package/dist/backend.d.ts.map +1 -1
  14. package/dist/backend.js +9 -1
  15. package/dist/backend.js.map +1 -1
  16. package/dist/blueprint.d.ts +21 -1
  17. package/dist/blueprint.d.ts.map +1 -1
  18. package/dist/blueprint.js +45 -17
  19. package/dist/blueprint.js.map +1 -1
  20. package/dist/env.d.ts +18 -0
  21. package/dist/env.d.ts.map +1 -1
  22. package/dist/env.js +1 -0
  23. package/dist/env.js.map +1 -1
  24. package/dist/index.d.ts +3 -1
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +1 -0
  27. package/dist/index.js.map +1 -1
  28. package/dist/manifest.d.ts +20 -0
  29. package/dist/manifest.d.ts.map +1 -1
  30. package/dist/manifest.js +68 -6
  31. package/dist/manifest.js.map +1 -1
  32. package/dist/readiness-probes.d.ts +4 -0
  33. package/dist/readiness-probes.d.ts.map +1 -0
  34. package/dist/readiness-probes.js +23 -0
  35. package/dist/readiness-probes.js.map +1 -0
  36. package/dist/readiness.d.ts +39 -0
  37. package/dist/readiness.d.ts.map +1 -0
  38. package/dist/readiness.js +72 -0
  39. package/dist/readiness.js.map +1 -0
  40. package/dist/runtime.d.ts.map +1 -1
  41. package/dist/runtime.js +13 -0
  42. package/dist/runtime.js.map +1 -1
  43. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -2,6 +2,34 @@
2
2
 
3
3
  All notable changes to `bunderstack` will be documented in this file.
4
4
 
5
+ ## [0.23.0] - 2026-08-29
6
+
7
+ ### Added
8
+
9
+ - **Environment metadata.** `env.meta` declares `sensitive` and `description` per
10
+ key. Server keys default to sensitive and client keys can never be — `PUBLIC_*`
11
+ values ship to the browser. Both reach the manifest and the blueprint; values
12
+ never do.
13
+ - **Application operation catalog.** The blueprint gains `api.operations`, listing
14
+ the procedures an application declared itself with an `effect` of `read`,
15
+ `mutation`, or `unknown`. Generated CRUD, storage, and realtime routes stay
16
+ derivable from `resources`.
17
+ - **Readiness endpoint.** `GET /api/readiness` reports database reachability,
18
+ schema provisioning, and background-queue backlog, and echoes
19
+ `BUNDERSTACK_REVISION` as `revision`. `GET /api/health` is unchanged.
20
+ - **`isSensitiveEnvVar(entry)`** exported from `bunderstack/blueprint`, encoding
21
+ the scope-based default for blueprints written before this release.
22
+
23
+ ### Changed
24
+
25
+ - **Forward-compatible blueprint parsing.** Every object in the blueprint schema
26
+ now preserves unknown keys instead of rejecting the file, so a host can read a
27
+ blueprint written by a newer generator. Run `bunderstack blueprint` to
28
+ regenerate; the new sections only appear after you do.
29
+ - The application router passed as a callback is now resolved once when
30
+ `bunderstack(config)` is called rather than at `start()`, so the manifest and
31
+ the running app describe the same router.
32
+
5
33
  ## [0.22.1] - 2026-08-28
6
34
 
7
35
  ### Added
@@ -0,0 +1,8 @@
1
+ import type { ApiOperation } from '../manifest.js';
2
+ /**
3
+ * Describes the procedures an application declared itself. Generated CRUD,
4
+ * storage, and realtime routes are excluded: a host derives those from the
5
+ * blueprint's `resources`.
6
+ */
7
+ export declare function describeApiOperations(router: unknown): ApiOperation[];
8
+ //# sourceMappingURL=catalog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalog.d.ts","sourceRoot":"","sources":["../../src/api/catalog.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AA2D/C;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,OAAO,GAAG,YAAY,EAAE,CAKrE"}
@@ -0,0 +1,56 @@
1
+ // src/api/catalog.ts — static description of the application's own procedures.
2
+ import { getOpenAPIMeta } from '@orpc/openapi';
3
+ const READ_METHODS = new Set(['GET', 'HEAD', 'OPTIONS']);
4
+ const MAX_SUMMARY = 200;
5
+ function summaryText(value) {
6
+ if (typeof value !== 'string')
7
+ return undefined;
8
+ const collapsed = value.trim().replace(/\s+/g, ' ');
9
+ if (!collapsed)
10
+ return undefined;
11
+ return collapsed.length > MAX_SUMMARY
12
+ ? `${collapsed.slice(0, MAX_SUMMARY - 1)}…`
13
+ : collapsed;
14
+ }
15
+ function walk(router, segments) {
16
+ const operations = [];
17
+ for (const [key, value] of Object.entries(router)) {
18
+ if (!value || typeof value !== 'object')
19
+ continue;
20
+ const current = [...segments, key];
21
+ if (!('~orpc' in value)) {
22
+ operations.push(...walk(value, current));
23
+ continue;
24
+ }
25
+ const meta = (getOpenAPIMeta(value) ?? {});
26
+ const handle = current.join('.');
27
+ const method = typeof meta.method === 'string' ? meta.method.toUpperCase() : undefined;
28
+ const summary = summaryText(meta.summary);
29
+ operations.push({
30
+ handle,
31
+ operationId: typeof meta.operationId === 'string' ? meta.operationId : handle,
32
+ // A procedure that declares no method could be anything. Calling it a
33
+ // read would tell a host it is safe to invoke without asking anyone.
34
+ effect: method === undefined
35
+ ? 'unknown'
36
+ : READ_METHODS.has(method)
37
+ ? 'read'
38
+ : 'mutation',
39
+ ...(method === undefined ? {} : { method }),
40
+ ...(typeof meta.path === 'string' ? { path: meta.path } : {}),
41
+ ...(summary === undefined ? {} : { summary }),
42
+ });
43
+ }
44
+ return operations;
45
+ }
46
+ /**
47
+ * Describes the procedures an application declared itself. Generated CRUD,
48
+ * storage, and realtime routes are excluded: a host derives those from the
49
+ * blueprint's `resources`.
50
+ */
51
+ export function describeApiOperations(router) {
52
+ if (!router || typeof router !== 'object')
53
+ return [];
54
+ return walk(router, []).sort((left, right) => left.handle.localeCompare(right.handle));
55
+ }
56
+ //# sourceMappingURL=catalog.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalog.js","sourceRoot":"","sources":["../../src/api/catalog.ts"],"names":[],"mappings":"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","sourcesContent":["// src/api/catalog.ts — static description of the application's own procedures.\nimport { getOpenAPIMeta } from '@orpc/openapi'\n\nimport type { ApiOperation } from '../manifest'\n\nconst READ_METHODS = new Set(['GET', 'HEAD', 'OPTIONS'])\nconst MAX_SUMMARY = 200\n\nfunction summaryText(value: unknown): string | undefined {\n if (typeof value !== 'string') return undefined\n const collapsed = value.trim().replace(/\\s+/g, ' ')\n if (!collapsed) return undefined\n return collapsed.length > MAX_SUMMARY\n ? `${collapsed.slice(0, MAX_SUMMARY - 1)}…`\n : collapsed\n}\n\nfunction walk(\n router: Record<string, unknown>,\n segments: string[],\n): ApiOperation[] {\n const operations: ApiOperation[] = []\n\n for (const [key, value] of Object.entries(router)) {\n if (!value || typeof value !== 'object') continue\n const current = [...segments, key]\n\n if (!('~orpc' in value)) {\n operations.push(...walk(value as Record<string, unknown>, current))\n continue\n }\n\n const meta = (getOpenAPIMeta(value as never) ?? {}) as Record<\n string,\n unknown\n >\n const handle = current.join('.')\n const method =\n typeof meta.method === 'string' ? meta.method.toUpperCase() : undefined\n const summary = summaryText(meta.summary)\n\n operations.push({\n handle,\n operationId:\n typeof meta.operationId === 'string' ? meta.operationId : handle,\n // A procedure that declares no method could be anything. Calling it a\n // read would tell a host it is safe to invoke without asking anyone.\n effect:\n method === undefined\n ? 'unknown'\n : READ_METHODS.has(method)\n ? 'read'\n : 'mutation',\n ...(method === undefined ? {} : { method }),\n ...(typeof meta.path === 'string' ? { path: meta.path } : {}),\n ...(summary === undefined ? {} : { summary }),\n })\n }\n\n return operations\n}\n\n/**\n * Describes the procedures an application declared itself. Generated CRUD,\n * storage, and realtime routes are excluded: a host derives those from the\n * blueprint's `resources`.\n */\nexport function describeApiOperations(router: unknown): ApiOperation[] {\n if (!router || typeof router !== 'object') return []\n return walk(router as Record<string, unknown>, []).sort((left, right) =>\n left.handle.localeCompare(right.handle),\n )\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/api/registry.ts"],"names":[],"mappings":"AAkBA,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAA;IACd,WAAW,EAAE,MAAM,CAAA;IACnB,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,MAAM,CAAA;CACf;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,gBAAgB,EAAE,CAAA;CAC5B;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC7B,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,WAAW,uBAAuB;IACtC,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IACtC,YAAY,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,gBAAgB,CAAC,CAAA;IAChE,gFAAgF;IAChF,mBAAmB,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,CAAA;CAC1C;AAYD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CActE;AAED,MAAM,WAAW,kBAAkB;IACjC,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED,wBAAgB,2BAA2B,CACzC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,OAAO,GAAE,kBAAuB,GAC/B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAqBzB;AAED,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAChC,MAAM,GAAE,MAAM,EAAO,GACpB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAmCzB;AAiHD,wBAAsB,gBAAgB,CACpC,OAAO,GAAE,uBAA4B,GACpC,OAAO,CAAC,WAAW,CAAC,CAgFtB"}
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/api/registry.ts"],"names":[],"mappings":"AAmBA,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAA;IACd,WAAW,EAAE,MAAM,CAAA;IACnB,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,MAAM,CAAA;CACf;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,gBAAgB,EAAE,CAAA;CAC5B;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC7B,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,WAAW,uBAAuB;IACtC,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IACtC,YAAY,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,gBAAgB,CAAC,CAAA;IAChE,gFAAgF;IAChF,mBAAmB,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,CAAA;CAC1C;AAYD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CActE;AAED,MAAM,WAAW,kBAAkB;IACjC,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED,wBAAgB,2BAA2B,CACzC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,OAAO,GAAE,kBAAuB,GAC/B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAqBzB;AAED,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAChC,MAAM,GAAE,MAAM,EAAO,GACpB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAmCzB;AAiHD,wBAAsB,gBAAgB,CACpC,OAAO,GAAE,uBAA4B,GACpC,OAAO,CAAC,WAAW,CAAC,CAgFtB"}
@@ -1,6 +1,7 @@
1
1
  import { getOpenAPIMeta } from '@orpc/openapi';
2
2
  const RESERVED_EXACT = new Set([
3
3
  '/api/health',
4
+ '/api/readiness',
4
5
  '/api/openapi.json',
5
6
  '/api/realtime',
6
7
  '/api/rpc',
@@ -1 +1 @@
1
- {"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/api/registry.ts"],"names":[],"mappings":"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","sourcesContent":["import { getOpenAPIMeta } from '@orpc/openapi'\n\nconst RESERVED_EXACT = new Set([\n '/api/health',\n '/api/openapi.json',\n '/api/realtime',\n '/api/rpc',\n '/api/auth',\n '/api/files',\n])\nconst RESERVED_PREFIXES = ['/api/rpc/', '/api/auth/', '/api/files/']\n\nfunction collisionForBunderstackPath(path: string): string | undefined {\n if (RESERVED_EXACT.has(path)) return 'it is reserved by bunderstack'\n const prefix = RESERVED_PREFIXES.find((value) => path.startsWith(value))\n return prefix ? `\"${prefix}*\" is reserved by bunderstack` : undefined\n}\n\nexport interface ApiRegistryEntry {\n handle: string\n operationId: string\n method: string\n path: string\n source: string\n}\n\nexport interface ApiRegistry {\n entries: ApiRegistryEntry[]\n}\n\nexport interface ForeignSpecEntry {\n spec: Record<string, unknown>\n prefix?: string\n source?: string\n}\n\nexport interface BuildApiRegistryOptions {\n nativeRouter?: Record<string, unknown>\n foreignSpecs?: Array<Record<string, unknown> | ForeignSpecEntry>\n /** Native procedures generated by Bunderstack itself may own reserved paths. */\n reservedCoreHandles?: ReadonlySet<string>\n}\n\nconst HTTP_METHODS = new Set([\n 'GET',\n 'POST',\n 'PUT',\n 'PATCH',\n 'DELETE',\n 'HEAD',\n 'OPTIONS',\n])\n\nexport function normalizeApiPath(path: string, prefix?: string): string {\n let cleanPath = path.startsWith('/') ? path : '/' + path\n if (prefix) {\n let cleanPrefix = prefix.startsWith('/') ? prefix : '/' + prefix\n if (cleanPrefix.endsWith('/')) {\n cleanPrefix = cleanPrefix.slice(0, -1)\n }\n if (cleanPath === cleanPrefix || cleanPath.startsWith(cleanPrefix + '/')) {\n // Path already starts with prefix\n } else {\n cleanPath = cleanPrefix + cleanPath\n }\n }\n return cleanPath.replace(/\\/+/g, '/')\n}\n\nexport interface ForeignSpecOptions {\n prefix?: string\n source?: string\n}\n\nexport function normalizeForeignOpenAPISpec(\n spec: Record<string, unknown>,\n options: ForeignSpecOptions = {},\n): Record<string, unknown> {\n const prefix = options.prefix\n const source = options.source\n const cloned: Record<string, unknown> = JSON.parse(JSON.stringify(spec))\n\n if (cloned.paths && typeof cloned.paths === 'object') {\n const normalizedPaths: Record<string, unknown> = {}\n for (const [routePath, pathItem] of Object.entries(\n cloned.paths as Record<string, unknown>,\n )) {\n const canonicalPath = normalizeApiPath(routePath, prefix)\n normalizedPaths[canonicalPath] = pathItem\n }\n cloned.paths = normalizedPaths\n }\n\n if (source) {\n cloned['x-bunderstack-source'] = source\n }\n\n return cloned\n}\n\nexport function mergeApiRoutersStrict(\n target: Record<string, unknown>,\n source?: Record<string, unknown>,\n prefix: string[] = [],\n): Record<string, unknown> {\n if (!source) return { ...target }\n const result: Record<string, unknown> = { ...target }\n\n for (const [key, val] of Object.entries(source)) {\n const currentPath = [...prefix, key]\n const handle = currentPath.join('.')\n const targetVal = result[key]\n\n if (targetVal !== undefined) {\n const isTargetNamespace =\n typeof targetVal === 'object' &&\n targetVal !== null &&\n !('~orpc' in targetVal)\n\n const isSourceNamespace =\n typeof val === 'object' && val !== null && !('~orpc' in val)\n\n if (isTargetNamespace && isSourceNamespace) {\n result[key] = mergeApiRoutersStrict(\n targetVal as Record<string, unknown>,\n val as Record<string, unknown>,\n currentPath,\n )\n } else {\n throw new Error(\n `[bunderstack] Router merge collision at handle \"${handle}\": duplicate procedure or namespace collision`,\n )\n }\n } else {\n result[key] = val\n }\n }\n\n return result\n}\n\nfunction normalizePathSegments(path: string): string[] {\n return path.split('/').filter(Boolean)\n}\n\nfunction isParamSegment(segment: string): boolean {\n return (\n (segment.startsWith('{') && segment.endsWith('}')) ||\n segment.startsWith(':')\n )\n}\n\nfunction normalizePathForCollision(path: string): string {\n const segments = normalizePathSegments(path)\n const normalized = segments.map((seg) =>\n isParamSegment(seg) ? '{param}' : seg,\n )\n return '/' + normalized.join('/')\n}\n\nfunction walkNativeRouter(\n obj: Record<string, unknown>,\n pathSegments: string[] = [],\n): ApiRegistryEntry[] {\n const entries: ApiRegistryEntry[] = []\n\n for (const [key, value] of Object.entries(obj)) {\n if (!value || typeof value !== 'object') continue\n const currentSegments = [...pathSegments, key]\n\n if ('~orpc' in value) {\n const meta = (getOpenAPIMeta(value as any) || {}) as Record<\n string,\n unknown\n >\n const handle = currentSegments.join('.')\n const method = ((meta.method as string) || 'GET').toUpperCase()\n const routePath = (meta.path as string) || '/' + currentSegments.join('/')\n const operationId =\n typeof meta.operationId === 'string' ? meta.operationId : handle\n\n entries.push({\n handle,\n operationId,\n method,\n path: routePath,\n source: 'native',\n })\n } else {\n entries.push(\n ...walkNativeRouter(value as Record<string, unknown>, currentSegments),\n )\n }\n }\n\n return entries\n}\n\nfunction extractForeignEntries(\n specs: Array<Record<string, unknown> | ForeignSpecEntry>,\n): ApiRegistryEntry[] {\n const entries: ApiRegistryEntry[] = []\n\n for (const item of specs) {\n if (!item) continue\n let specObj: Record<string, unknown>\n let source = 'foreign'\n\n if ('spec' in item && typeof item.spec === 'object' && item.spec !== null) {\n const entry = item as ForeignSpecEntry\n source = entry.source || 'foreign'\n specObj = normalizeForeignOpenAPISpec(entry.spec, {\n prefix: entry.prefix,\n source,\n })\n } else {\n const rawSpec = item as Record<string, unknown>\n source = (rawSpec['x-bunderstack-source'] as string) || 'foreign'\n specObj = normalizeForeignOpenAPISpec(rawSpec, { source })\n }\n\n if (!specObj || typeof specObj.paths !== 'object' || !specObj.paths)\n continue\n const paths = specObj.paths as Record<string, Record<string, unknown>>\n\n for (const [routePath, pathItem] of Object.entries(paths)) {\n if (!pathItem || typeof pathItem !== 'object') continue\n\n for (const [methodKey, operation] of Object.entries(pathItem)) {\n const uppercaseMethod = methodKey.toUpperCase()\n if (!HTTP_METHODS.has(uppercaseMethod)) continue\n if (!operation || typeof operation !== 'object') continue\n\n const op = operation as Record<string, unknown>\n const handle = `foreign:${source}:${uppercaseMethod}:${routePath}`\n const operationId =\n typeof op.operationId === 'string' ? op.operationId : handle\n\n entries.push({\n handle,\n operationId,\n method: uppercaseMethod,\n path: routePath,\n source,\n })\n }\n }\n }\n\n return entries\n}\n\nexport async function buildApiRegistry(\n options: BuildApiRegistryOptions = {},\n): Promise<ApiRegistry> {\n const entries: ApiRegistryEntry[] = []\n\n if (options.nativeRouter) {\n entries.push(...walkNativeRouter(options.nativeRouter))\n }\n\n if (options.foreignSpecs) {\n entries.push(...extractForeignEntries(options.foreignSpecs))\n }\n\n const errors: string[] = []\n\n for (const entry of entries) {\n if (entry.source !== 'native') continue\n if (options.reservedCoreHandles?.has(entry.handle)) continue\n const reason = collisionForBunderstackPath(entry.path)\n if (reason) {\n errors.push(\n `Reserved route collision on ${entry.method} ${entry.path}: native handle \"${entry.handle}\" conflicts because ${reason}`,\n )\n }\n }\n\n // Check duplicate handles\n const handleMap = new Map<string, ApiRegistryEntry>()\n for (const entry of entries) {\n const existing = handleMap.get(entry.handle)\n if (existing) {\n errors.push(\n `Duplicate handle \"${entry.handle}\": collision between ${existing.source} (${existing.method} ${existing.path}) and ${entry.source} (${entry.method} ${entry.path})`,\n )\n } else {\n handleMap.set(entry.handle, entry)\n }\n }\n\n // Check duplicate operation IDs\n const opIdMap = new Map<string, ApiRegistryEntry>()\n for (const entry of entries) {\n const existing = opIdMap.get(entry.operationId)\n if (existing) {\n errors.push(\n `Duplicate operation ID \"${entry.operationId}\": collision between ${existing.source} (${existing.handle}) and ${entry.source} (${entry.handle})`,\n )\n } else {\n opIdMap.set(entry.operationId, entry)\n }\n }\n\n // Check method/path collisions (exact and parameter ambiguity)\n const routePatternMap = new Map<string, ApiRegistryEntry>()\n for (const entry of entries) {\n const normalizedPath = normalizePathForCollision(entry.path)\n const routeKey = `${entry.method} ${normalizedPath}`\n\n const existing = routePatternMap.get(routeKey)\n if (existing) {\n if (existing.path === entry.path) {\n errors.push(\n `Exact method/path collision on ${entry.method} ${entry.path}: collision between ${existing.source} (${existing.handle}) and ${entry.source} (${entry.handle})`,\n )\n } else {\n errors.push(\n `Ambiguous parameter path collision on ${entry.method} (${existing.path} vs ${entry.path}): collision between ${existing.source} (${existing.handle}) and ${entry.source} (${entry.handle})`,\n )\n }\n } else {\n routePatternMap.set(routeKey, entry)\n }\n }\n\n if (errors.length > 0) {\n throw new Error(\n `[bunderstack] Route registry validation failed (${errors.length} error(s)):\\n` +\n errors.join('\\n'),\n )\n }\n\n return { entries }\n}\n"]}
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/api/registry.ts"],"names":[],"mappings":"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","sourcesContent":["import { getOpenAPIMeta } from '@orpc/openapi'\n\nconst RESERVED_EXACT = new Set([\n '/api/health',\n '/api/readiness',\n '/api/openapi.json',\n '/api/realtime',\n '/api/rpc',\n '/api/auth',\n '/api/files',\n])\nconst RESERVED_PREFIXES = ['/api/rpc/', '/api/auth/', '/api/files/']\n\nfunction collisionForBunderstackPath(path: string): string | undefined {\n if (RESERVED_EXACT.has(path)) return 'it is reserved by bunderstack'\n const prefix = RESERVED_PREFIXES.find((value) => path.startsWith(value))\n return prefix ? `\"${prefix}*\" is reserved by bunderstack` : undefined\n}\n\nexport interface ApiRegistryEntry {\n handle: string\n operationId: string\n method: string\n path: string\n source: string\n}\n\nexport interface ApiRegistry {\n entries: ApiRegistryEntry[]\n}\n\nexport interface ForeignSpecEntry {\n spec: Record<string, unknown>\n prefix?: string\n source?: string\n}\n\nexport interface BuildApiRegistryOptions {\n nativeRouter?: Record<string, unknown>\n foreignSpecs?: Array<Record<string, unknown> | ForeignSpecEntry>\n /** Native procedures generated by Bunderstack itself may own reserved paths. */\n reservedCoreHandles?: ReadonlySet<string>\n}\n\nconst HTTP_METHODS = new Set([\n 'GET',\n 'POST',\n 'PUT',\n 'PATCH',\n 'DELETE',\n 'HEAD',\n 'OPTIONS',\n])\n\nexport function normalizeApiPath(path: string, prefix?: string): string {\n let cleanPath = path.startsWith('/') ? path : '/' + path\n if (prefix) {\n let cleanPrefix = prefix.startsWith('/') ? prefix : '/' + prefix\n if (cleanPrefix.endsWith('/')) {\n cleanPrefix = cleanPrefix.slice(0, -1)\n }\n if (cleanPath === cleanPrefix || cleanPath.startsWith(cleanPrefix + '/')) {\n // Path already starts with prefix\n } else {\n cleanPath = cleanPrefix + cleanPath\n }\n }\n return cleanPath.replace(/\\/+/g, '/')\n}\n\nexport interface ForeignSpecOptions {\n prefix?: string\n source?: string\n}\n\nexport function normalizeForeignOpenAPISpec(\n spec: Record<string, unknown>,\n options: ForeignSpecOptions = {},\n): Record<string, unknown> {\n const prefix = options.prefix\n const source = options.source\n const cloned: Record<string, unknown> = JSON.parse(JSON.stringify(spec))\n\n if (cloned.paths && typeof cloned.paths === 'object') {\n const normalizedPaths: Record<string, unknown> = {}\n for (const [routePath, pathItem] of Object.entries(\n cloned.paths as Record<string, unknown>,\n )) {\n const canonicalPath = normalizeApiPath(routePath, prefix)\n normalizedPaths[canonicalPath] = pathItem\n }\n cloned.paths = normalizedPaths\n }\n\n if (source) {\n cloned['x-bunderstack-source'] = source\n }\n\n return cloned\n}\n\nexport function mergeApiRoutersStrict(\n target: Record<string, unknown>,\n source?: Record<string, unknown>,\n prefix: string[] = [],\n): Record<string, unknown> {\n if (!source) return { ...target }\n const result: Record<string, unknown> = { ...target }\n\n for (const [key, val] of Object.entries(source)) {\n const currentPath = [...prefix, key]\n const handle = currentPath.join('.')\n const targetVal = result[key]\n\n if (targetVal !== undefined) {\n const isTargetNamespace =\n typeof targetVal === 'object' &&\n targetVal !== null &&\n !('~orpc' in targetVal)\n\n const isSourceNamespace =\n typeof val === 'object' && val !== null && !('~orpc' in val)\n\n if (isTargetNamespace && isSourceNamespace) {\n result[key] = mergeApiRoutersStrict(\n targetVal as Record<string, unknown>,\n val as Record<string, unknown>,\n currentPath,\n )\n } else {\n throw new Error(\n `[bunderstack] Router merge collision at handle \"${handle}\": duplicate procedure or namespace collision`,\n )\n }\n } else {\n result[key] = val\n }\n }\n\n return result\n}\n\nfunction normalizePathSegments(path: string): string[] {\n return path.split('/').filter(Boolean)\n}\n\nfunction isParamSegment(segment: string): boolean {\n return (\n (segment.startsWith('{') && segment.endsWith('}')) ||\n segment.startsWith(':')\n )\n}\n\nfunction normalizePathForCollision(path: string): string {\n const segments = normalizePathSegments(path)\n const normalized = segments.map((seg) =>\n isParamSegment(seg) ? '{param}' : seg,\n )\n return '/' + normalized.join('/')\n}\n\nfunction walkNativeRouter(\n obj: Record<string, unknown>,\n pathSegments: string[] = [],\n): ApiRegistryEntry[] {\n const entries: ApiRegistryEntry[] = []\n\n for (const [key, value] of Object.entries(obj)) {\n if (!value || typeof value !== 'object') continue\n const currentSegments = [...pathSegments, key]\n\n if ('~orpc' in value) {\n const meta = (getOpenAPIMeta(value as any) || {}) as Record<\n string,\n unknown\n >\n const handle = currentSegments.join('.')\n const method = ((meta.method as string) || 'GET').toUpperCase()\n const routePath = (meta.path as string) || '/' + currentSegments.join('/')\n const operationId =\n typeof meta.operationId === 'string' ? meta.operationId : handle\n\n entries.push({\n handle,\n operationId,\n method,\n path: routePath,\n source: 'native',\n })\n } else {\n entries.push(\n ...walkNativeRouter(value as Record<string, unknown>, currentSegments),\n )\n }\n }\n\n return entries\n}\n\nfunction extractForeignEntries(\n specs: Array<Record<string, unknown> | ForeignSpecEntry>,\n): ApiRegistryEntry[] {\n const entries: ApiRegistryEntry[] = []\n\n for (const item of specs) {\n if (!item) continue\n let specObj: Record<string, unknown>\n let source = 'foreign'\n\n if ('spec' in item && typeof item.spec === 'object' && item.spec !== null) {\n const entry = item as ForeignSpecEntry\n source = entry.source || 'foreign'\n specObj = normalizeForeignOpenAPISpec(entry.spec, {\n prefix: entry.prefix,\n source,\n })\n } else {\n const rawSpec = item as Record<string, unknown>\n source = (rawSpec['x-bunderstack-source'] as string) || 'foreign'\n specObj = normalizeForeignOpenAPISpec(rawSpec, { source })\n }\n\n if (!specObj || typeof specObj.paths !== 'object' || !specObj.paths)\n continue\n const paths = specObj.paths as Record<string, Record<string, unknown>>\n\n for (const [routePath, pathItem] of Object.entries(paths)) {\n if (!pathItem || typeof pathItem !== 'object') continue\n\n for (const [methodKey, operation] of Object.entries(pathItem)) {\n const uppercaseMethod = methodKey.toUpperCase()\n if (!HTTP_METHODS.has(uppercaseMethod)) continue\n if (!operation || typeof operation !== 'object') continue\n\n const op = operation as Record<string, unknown>\n const handle = `foreign:${source}:${uppercaseMethod}:${routePath}`\n const operationId =\n typeof op.operationId === 'string' ? op.operationId : handle\n\n entries.push({\n handle,\n operationId,\n method: uppercaseMethod,\n path: routePath,\n source,\n })\n }\n }\n }\n\n return entries\n}\n\nexport async function buildApiRegistry(\n options: BuildApiRegistryOptions = {},\n): Promise<ApiRegistry> {\n const entries: ApiRegistryEntry[] = []\n\n if (options.nativeRouter) {\n entries.push(...walkNativeRouter(options.nativeRouter))\n }\n\n if (options.foreignSpecs) {\n entries.push(...extractForeignEntries(options.foreignSpecs))\n }\n\n const errors: string[] = []\n\n for (const entry of entries) {\n if (entry.source !== 'native') continue\n if (options.reservedCoreHandles?.has(entry.handle)) continue\n const reason = collisionForBunderstackPath(entry.path)\n if (reason) {\n errors.push(\n `Reserved route collision on ${entry.method} ${entry.path}: native handle \"${entry.handle}\" conflicts because ${reason}`,\n )\n }\n }\n\n // Check duplicate handles\n const handleMap = new Map<string, ApiRegistryEntry>()\n for (const entry of entries) {\n const existing = handleMap.get(entry.handle)\n if (existing) {\n errors.push(\n `Duplicate handle \"${entry.handle}\": collision between ${existing.source} (${existing.method} ${existing.path}) and ${entry.source} (${entry.method} ${entry.path})`,\n )\n } else {\n handleMap.set(entry.handle, entry)\n }\n }\n\n // Check duplicate operation IDs\n const opIdMap = new Map<string, ApiRegistryEntry>()\n for (const entry of entries) {\n const existing = opIdMap.get(entry.operationId)\n if (existing) {\n errors.push(\n `Duplicate operation ID \"${entry.operationId}\": collision between ${existing.source} (${existing.handle}) and ${entry.source} (${entry.handle})`,\n )\n } else {\n opIdMap.set(entry.operationId, entry)\n }\n }\n\n // Check method/path collisions (exact and parameter ambiguity)\n const routePatternMap = new Map<string, ApiRegistryEntry>()\n for (const entry of entries) {\n const normalizedPath = normalizePathForCollision(entry.path)\n const routeKey = `${entry.method} ${normalizedPath}`\n\n const existing = routePatternMap.get(routeKey)\n if (existing) {\n if (existing.path === entry.path) {\n errors.push(\n `Exact method/path collision on ${entry.method} ${entry.path}: collision between ${existing.source} (${existing.handle}) and ${entry.source} (${entry.handle})`,\n )\n } else {\n errors.push(\n `Ambiguous parameter path collision on ${entry.method} (${existing.path} vs ${entry.path}): collision between ${existing.source} (${existing.handle}) and ${entry.source} (${entry.handle})`,\n )\n }\n } else {\n routePatternMap.set(routeKey, entry)\n }\n }\n\n if (errors.length > 0) {\n throw new Error(\n `[bunderstack] Route registry validation failed (${errors.length} error(s)):\\n` +\n errors.join('\\n'),\n )\n }\n\n return { entries }\n}\n"]}
@@ -1,5 +1,6 @@
1
1
  import '@orpc/openapi/extensions/route';
2
2
  import type { AnyMiddleware } from '@orpc/server';
3
+ import type { ReadinessReport } from '../readiness.js';
3
4
  export interface BuildApiRouterOptions {
4
5
  crud: Record<string, unknown>;
5
6
  storage: Record<string, unknown>;
@@ -7,6 +8,8 @@ export interface BuildApiRouterOptions {
7
8
  custom?: Record<string, unknown>;
8
9
  /** Applied to every procedure in the graph, outermost first. */
9
10
  middleware?: AnyMiddleware[];
11
+ /** Deployment readiness probe. Always answers 200; read `status` in the body. */
12
+ readiness: () => Promise<ReadinessReport>;
10
13
  }
11
14
  export declare function buildApiRouter(options: BuildApiRouterOptions): Record<string, unknown>;
12
15
  //# sourceMappingURL=router.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../../src/api/router.ts"],"names":[],"mappings":"AAAA,OAAO,gCAAgC,CAAA;AACvC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;AAOjD,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC7B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAChC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAClC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAChC,gEAAgE;IAChE,UAAU,CAAC,EAAE,aAAa,EAAE,CAAA;CAC7B;AAED,wBAAgB,cAAc,CAAC,OAAO,EAAE,qBAAqB,2BAiC5D"}
1
+ {"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../../src/api/router.ts"],"names":[],"mappings":"AAAA,OAAO,gCAAgC,CAAA;AACvC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;AAIjD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAA;AAMnD,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC7B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAChC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAClC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAChC,gEAAgE;IAChE,UAAU,CAAC,EAAE,aAAa,EAAE,CAAA;IAC5B,iFAAiF;IACjF,SAAS,EAAE,MAAM,OAAO,CAAC,eAAe,CAAC,CAAA;CAC1C;AAED,wBAAgB,cAAc,CAAC,OAAO,EAAE,qBAAqB,2BAsC5D"}
@@ -1,5 +1,6 @@
1
1
  import '@orpc/openapi/extensions/route';
2
2
  import * as v from 'valibot';
3
+ import { readinessReportSchema } from '../readiness.js';
3
4
  import { createApiBuilder } from './builder.js';
4
5
  import { mergeApiRoutersStrict } from './registry.js';
5
6
  export function buildApiRouter(options) {
@@ -8,8 +9,12 @@ export function buildApiRouter(options) {
8
9
  .route({ method: 'GET', path: '/api/health', tags: ['system'] })
9
10
  .output(v.strictObject({ status: v.literal('ok') }))
10
11
  .handler(() => ({ status: 'ok' }));
12
+ const readiness = builder.public
13
+ .route({ method: 'GET', path: '/api/readiness', tags: ['system'] })
14
+ .output(readinessReportSchema)
15
+ .handler(() => options.readiness());
11
16
  const merged = [
12
- { health },
17
+ { health, readiness },
13
18
  options.crud,
14
19
  options.storage,
15
20
  options.realtime,
@@ -19,7 +24,7 @@ export function buildApiRouter(options) {
19
24
  if (middleware.length === 0)
20
25
  return merged;
21
26
  // `.router()` applies the builder's middleware to every procedure inside, so
22
- // one list covers CRUD, storage, realtime, health, and custom procedures
27
+ // one list covers CRUD, storage, realtime, health, readiness, and custom procedures
23
28
  // instead of only the bases an application happens to declare.
24
29
  const withMiddleware = middleware.reduce((acc, mw) => acc.use(mw), builder.public);
25
30
  return withMiddleware.router(merged);
@@ -1 +1 @@
1
- {"version":3,"file":"router.js","sourceRoot":"","sources":["../../src/api/router.ts"],"names":[],"mappings":"AAAA,OAAO,gCAAgC,CAAA;AAGvC,OAAO,KAAK,CAAC,MAAM,SAAS,CAAA;AAE5B,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAA;AAC5C,OAAO,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAA;AAWlD,MAAM,UAAU,cAAc,CAAC,OAA8B;IAC3D,MAAM,OAAO,GAAG,gBAAgB,EAG7B,CAAA;IACH,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM;SAC1B,KAAK,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC;SAC/D,MAAM,CAAC,CAAC,CAAC,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;SACnD,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,IAAa,EAAE,CAAC,CAAC,CAAA;IAE7C,MAAM,MAAM,GAAG;QACb,EAAE,MAAM,EAAE;QACV,OAAO,CAAC,IAAI;QACZ,OAAO,CAAC,OAAO;QACf,OAAO,CAAC,QAAQ;QAChB,OAAO,CAAC,MAAM;KACf,CAAC,MAAM,CACN,CAAC,MAAM,EAAE,QAAQ,EAAE,EAAE,CAAC,qBAAqB,CAAC,MAAM,EAAE,QAAQ,CAAC,EAC7D,EAAE,CACH,CAAA;IAED,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,IAAI,EAAE,CAAA;IAC3C,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,MAAM,CAAA;IAE1C,6EAA6E;IAC7E,yEAAyE;IACzE,+DAA+D;IAC/D,MAAM,cAAc,GAAG,UAAU,CAAC,MAAM,CACtC,CAAC,GAAG,EAAE,EAAE,EAAE,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,EAAW,CAAC,EACjC,OAAO,CAAC,MAAM,CACf,CAAA;IAED,OAAO,cAAc,CAAC,MAAM,CAAC,MAAe,CAA4B,CAAA;AAC1E,CAAC","sourcesContent":["import '@orpc/openapi/extensions/route'\nimport type { AnyMiddleware } from '@orpc/server'\n\nimport * as v from 'valibot'\n\nimport { createApiBuilder } from './builder'\nimport { mergeApiRoutersStrict } from './registry'\n\nexport interface BuildApiRouterOptions {\n crud: Record<string, unknown>\n storage: Record<string, unknown>\n realtime?: Record<string, unknown>\n custom?: Record<string, unknown>\n /** Applied to every procedure in the graph, outermost first. */\n middleware?: AnyMiddleware[]\n}\n\nexport function buildApiRouter(options: BuildApiRouterOptions) {\n const builder = createApiBuilder<\n Record<string, unknown>,\n Record<string, unknown>\n >()\n const health = builder.public\n .route({ method: 'GET', path: '/api/health', tags: ['system'] })\n .output(v.strictObject({ status: v.literal('ok') }))\n .handler(() => ({ status: 'ok' as const }))\n\n const merged = [\n { health },\n options.crud,\n options.storage,\n options.realtime,\n options.custom,\n ].reduce<Record<string, unknown>>(\n (router, addition) => mergeApiRoutersStrict(router, addition),\n {},\n )\n\n const middleware = options.middleware ?? []\n if (middleware.length === 0) return merged\n\n // `.router()` applies the builder's middleware to every procedure inside, so\n // one list covers CRUD, storage, realtime, health, and custom procedures\n // instead of only the bases an application happens to declare.\n const withMiddleware = middleware.reduce(\n (acc, mw) => acc.use(mw as never),\n builder.public,\n )\n\n return withMiddleware.router(merged as never) as Record<string, unknown>\n}\n"]}
1
+ {"version":3,"file":"router.js","sourceRoot":"","sources":["../../src/api/router.ts"],"names":[],"mappings":"AAAA,OAAO,gCAAgC,CAAA;AAGvC,OAAO,KAAK,CAAC,MAAM,SAAS,CAAA;AAI5B,OAAO,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAA;AACpD,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAA;AAC5C,OAAO,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAA;AAalD,MAAM,UAAU,cAAc,CAAC,OAA8B;IAC3D,MAAM,OAAO,GAAG,gBAAgB,EAG7B,CAAA;IACH,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM;SAC1B,KAAK,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC;SAC/D,MAAM,CAAC,CAAC,CAAC,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;SACnD,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,IAAa,EAAE,CAAC,CAAC,CAAA;IAE7C,MAAM,SAAS,GAAG,OAAO,CAAC,MAAM;SAC7B,KAAK,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,gBAAgB,EAAE,IAAI,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC;SAClE,MAAM,CAAC,qBAAqB,CAAC;SAC7B,OAAO,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,SAAS,EAAE,CAAC,CAAA;IAErC,MAAM,MAAM,GAAG;QACb,EAAE,MAAM,EAAE,SAAS,EAAE;QACrB,OAAO,CAAC,IAAI;QACZ,OAAO,CAAC,OAAO;QACf,OAAO,CAAC,QAAQ;QAChB,OAAO,CAAC,MAAM;KACf,CAAC,MAAM,CACN,CAAC,MAAM,EAAE,QAAQ,EAAE,EAAE,CAAC,qBAAqB,CAAC,MAAM,EAAE,QAAQ,CAAC,EAC7D,EAAE,CACH,CAAA;IAED,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,IAAI,EAAE,CAAA;IAC3C,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,MAAM,CAAA;IAE1C,6EAA6E;IAC7E,oFAAoF;IACpF,+DAA+D;IAC/D,MAAM,cAAc,GAAG,UAAU,CAAC,MAAM,CACtC,CAAC,GAAG,EAAE,EAAE,EAAE,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,EAAW,CAAC,EACjC,OAAO,CAAC,MAAM,CACf,CAAA;IAED,OAAO,cAAc,CAAC,MAAM,CAAC,MAAe,CAA4B,CAAA;AAC1E,CAAC","sourcesContent":["import '@orpc/openapi/extensions/route'\nimport type { AnyMiddleware } from '@orpc/server'\n\nimport * as v from 'valibot'\n\nimport type { ReadinessReport } from '../readiness'\n\nimport { readinessReportSchema } from '../readiness'\nimport { createApiBuilder } from './builder'\nimport { mergeApiRoutersStrict } from './registry'\n\nexport interface BuildApiRouterOptions {\n crud: Record<string, unknown>\n storage: Record<string, unknown>\n realtime?: Record<string, unknown>\n custom?: Record<string, unknown>\n /** Applied to every procedure in the graph, outermost first. */\n middleware?: AnyMiddleware[]\n /** Deployment readiness probe. Always answers 200; read `status` in the body. */\n readiness: () => Promise<ReadinessReport>\n}\n\nexport function buildApiRouter(options: BuildApiRouterOptions) {\n const builder = createApiBuilder<\n Record<string, unknown>,\n Record<string, unknown>\n >()\n const health = builder.public\n .route({ method: 'GET', path: '/api/health', tags: ['system'] })\n .output(v.strictObject({ status: v.literal('ok') }))\n .handler(() => ({ status: 'ok' as const }))\n\n const readiness = builder.public\n .route({ method: 'GET', path: '/api/readiness', tags: ['system'] })\n .output(readinessReportSchema)\n .handler(() => options.readiness())\n\n const merged = [\n { health, readiness },\n options.crud,\n options.storage,\n options.realtime,\n options.custom,\n ].reduce<Record<string, unknown>>(\n (router, addition) => mergeApiRoutersStrict(router, addition),\n {},\n )\n\n const middleware = options.middleware ?? []\n if (middleware.length === 0) return merged\n\n // `.router()` applies the builder's middleware to every procedure inside, so\n // one list covers CRUD, storage, realtime, health, readiness, and custom procedures\n // instead of only the bases an application happens to declare.\n const withMiddleware = middleware.reduce(\n (acc, mw) => acc.use(mw as never),\n builder.public,\n )\n\n return withMiddleware.router(merged as never) as Record<string, unknown>\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"backend.d.ts","sourceRoot":"","sources":["../src/backend.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"backend.d.ts","sourceRoot":"","sources":["../src/backend.ts"],"names":[],"mappings":"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"}
package/dist/backend.js CHANGED
@@ -1,3 +1,5 @@
1
+ import { createApiBuilder } from './api/builder.js';
2
+ import { describeApiOperations } from './api/catalog.js';
1
3
  import { BACKEND_INTERNALS } from './backend-internals.js';
2
4
  import { detectDialect } from './dialect.js';
3
5
  import { emailProviderTag } from './email.js';
@@ -18,6 +20,11 @@ export function bunderstack(config) {
18
20
  : undefined;
19
21
  if (jobsDefs)
20
22
  validateJobsDefs(jobsDefs);
23
+ // Resolved here rather than at start: the manifest has to describe the same
24
+ // router the runtime will serve, and `bunderstack blueprint` never starts.
25
+ const customApiRouter = typeof config.api === 'function'
26
+ ? config.api(createApiBuilder())
27
+ : config.api;
21
28
  const manifest = buildManifest({
22
29
  schema: config.schema,
23
30
  dialect,
@@ -26,6 +33,7 @@ export function bunderstack(config) {
26
33
  envConfig: config.env,
27
34
  emailProvider: emailProviderTag(config.email),
28
35
  realtime: Boolean(config.realtime),
36
+ api: describeApiOperations(customApiRouter),
29
37
  jobs: config.storage
30
38
  ? {
31
39
  ...jobsDefs,
@@ -37,7 +45,7 @@ export function bunderstack(config) {
37
45
  }
38
46
  : jobsDefs,
39
47
  });
40
- const start = (source, overrides = {}) => materializeBunderstack({ ...config, jobs: jobsDefs }, source, overrides);
48
+ const start = (source, overrides = {}) => materializeBunderstack({ ...config, jobs: jobsDefs, api: customApiRouter }, source, overrides);
41
49
  let backend;
42
50
  const test = (async (options) => {
43
51
  const testing = await import('./testing.js');
@@ -1 +1 @@
1
- {"version":3,"file":"backend.js","sourceRoot":"","sources":["../src/backend.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AnyRouter as AnyORPCRouter } from '@orpc/server'\n\nimport type { TableAccessInput } from './access'\nimport type { BunderstackConfig } from './config'\nimport type { EnvConfigInput, ValidatedEnv } from './env'\nimport type { BunderstackJobsBuilder, JobsDefs } from './jobs'\nimport type { BunderstackManifest } from './manifest'\nimport type { BunderstackApp, BucketNamesOf, RuntimeOverrides } from './runtime'\nimport type { StorageConfigInput } from './storage/buckets'\nimport type { TestMethod, TestOptions } from './testing/fixture'\n\nimport { BACKEND_INTERNALS, type BackendInternals } from './backend-internals'\nimport { detectDialect } from './dialect'\nimport { emailProviderTag } from './email'\nimport { createJobsBuilder, validateJobsDefs } from './jobs'\nimport { buildManifest } from './manifest'\nimport { materializeBunderstack } from './runtime'\nimport {\n STORAGE_SWEEP_JOB_NAME,\n STORAGE_SWEEP_SCHEDULE,\n} from './storage/background'\nimport { resolveBuckets } from './storage/buckets'\n\nexport type StartOptions = {\n env?: Record<string, string | undefined>\n}\n\nexport type BunderstackBackend<TApp> = {\n readonly manifest: BunderstackManifest\n start(options?: StartOptions): Promise<TApp>\n test: TestMethod<TApp>\n readonly [BACKEND_INTERNALS]: BackendInternals<TApp>\n}\n\nexport type BunderstackDefinitionConfig<\n TSchema extends Record<string, unknown>,\n TAccess extends Record<string, TableAccessInput> | undefined = undefined,\n TStorage extends StorageConfigInput | undefined = undefined,\n TEnv extends EnvConfigInput | undefined = undefined,\n TJobsDefs extends JobsDefs | undefined = undefined,\n TCustomApiRouter extends AnyORPCRouter | undefined = undefined,\n TRealtime = undefined,\n> = Omit<\n BunderstackConfig<TSchema, TAccess, TStorage, TEnv, TCustomApiRouter>,\n 'realtime'\n> & {\n realtime?: TRealtime\n jobs?:\n | TJobsDefs\n | ((\n builder: BunderstackJobsBuilder<TSchema, ValidatedEnv<TEnv>>,\n ) => TJobsDefs)\n}\n\nexport function isBunderstackBackend(\n value: unknown,\n): value is BunderstackBackend<unknown> {\n return (\n typeof value === 'object' && value !== null && BACKEND_INTERNALS in value\n )\n}\n\nexport function bunderstack<\n TSchema extends Record<string, unknown>,\n const TAccess extends Record<string, TableAccessInput> | undefined =\n undefined,\n const TStorage extends StorageConfigInput | undefined = undefined,\n const TEnv extends EnvConfigInput | undefined = undefined,\n const TJobsDefs extends JobsDefs | undefined = undefined,\n TCustomApiRouter extends AnyORPCRouter | undefined = undefined,\n const TRealtime extends BunderstackConfig<\n TSchema,\n TAccess,\n TStorage,\n TEnv,\n TCustomApiRouter\n >['realtime'] = undefined,\n>(\n config: BunderstackDefinitionConfig<\n TSchema,\n TAccess,\n TStorage,\n TEnv,\n TJobsDefs,\n TCustomApiRouter,\n TRealtime\n >,\n): BunderstackBackend<\n BunderstackApp<\n TSchema,\n TAccess,\n BucketNamesOf<TStorage>,\n TEnv,\n TJobsDefs,\n TCustomApiRouter,\n TRealtime\n >\n> {\n const dialect = detectDialect(config.schema)\n const jobsDefs = config.jobs\n ? typeof config.jobs === 'function'\n ? config.jobs(createJobsBuilder<TSchema, ValidatedEnv<TEnv>>())\n : config.jobs\n : undefined\n if (jobsDefs) validateJobsDefs(jobsDefs)\n\n const manifest = buildManifest({\n schema: config.schema,\n dialect,\n migrationsDirectory: config.database.migrations ?? './migrations',\n storage: resolveBuckets(config.storage, {}),\n envConfig: config.env,\n emailProvider: emailProviderTag(config.email),\n realtime: Boolean(config.realtime),\n jobs: config.storage\n ? {\n ...jobsDefs,\n [STORAGE_SWEEP_JOB_NAME]: {\n kind: 'cron',\n schedule: STORAGE_SWEEP_SCHEDULE,\n handler: () => {},\n },\n }\n : jobsDefs,\n })\n\n type App = BunderstackApp<\n TSchema,\n TAccess,\n BucketNamesOf<TStorage>,\n TEnv,\n TJobsDefs,\n TCustomApiRouter,\n TRealtime\n >\n\n const start = (\n source: Record<string, string | undefined>,\n overrides: RuntimeOverrides = {},\n ): Promise<App> =>\n materializeBunderstack(\n { ...config, jobs: jobsDefs } as never,\n source,\n overrides,\n ) as Promise<App>\n\n let backend: BunderstackBackend<App>\n const test = (async (options) => {\n const testing = await import('./testing')\n return testing.createTestApp(backend, options)\n }) as TestMethod<App>\n test.configure = (options) =>\n (async (overrides: TestOptions = {}) => {\n const testing = await import('./testing')\n return testing.configureTestApp(backend, options)(overrides)\n }) as never\n backend = {\n manifest,\n start: ({ env } = {}) =>\n start(env ?? (process.env as Record<string, string | undefined>)),\n test,\n [BACKEND_INTERNALS]: { declaration: { config, jobsDefs }, start },\n }\n return backend\n}\n"]}
1
+ {"version":3,"file":"backend.js","sourceRoot":"","sources":["../src/backend.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AnyRouter as AnyORPCRouter } from '@orpc/server'\n\nimport type { TableAccessInput } from './access'\nimport type { BunderstackApiBuilder } from './api/builder'\nimport type { BunderstackConfig } from './config'\nimport type { EnvConfigInput, ValidatedEnv } from './env'\nimport type { BunderstackJobsBuilder, JobsDefs } from './jobs'\nimport type { BunderstackManifest } from './manifest'\nimport type { BunderstackApp, BucketNamesOf, RuntimeOverrides } from './runtime'\nimport type { StorageConfigInput } from './storage/buckets'\nimport type { TestMethod, TestOptions } from './testing/fixture'\n\nimport { createApiBuilder } from './api/builder'\nimport { describeApiOperations } from './api/catalog'\nimport { BACKEND_INTERNALS, type BackendInternals } from './backend-internals'\nimport { detectDialect } from './dialect'\nimport { emailProviderTag } from './email'\nimport { createJobsBuilder, validateJobsDefs } from './jobs'\nimport { buildManifest } from './manifest'\nimport { materializeBunderstack } from './runtime'\nimport {\n STORAGE_SWEEP_JOB_NAME,\n STORAGE_SWEEP_SCHEDULE,\n} from './storage/background'\nimport { resolveBuckets } from './storage/buckets'\n\nexport type StartOptions = {\n env?: Record<string, string | undefined>\n}\n\nexport type BunderstackBackend<TApp> = {\n readonly manifest: BunderstackManifest\n start(options?: StartOptions): Promise<TApp>\n test: TestMethod<TApp>\n readonly [BACKEND_INTERNALS]: BackendInternals<TApp>\n}\n\nexport type BunderstackDefinitionConfig<\n TSchema extends Record<string, unknown>,\n TAccess extends Record<string, TableAccessInput> | undefined = undefined,\n TStorage extends StorageConfigInput | undefined = undefined,\n TEnv extends EnvConfigInput | undefined = undefined,\n TJobsDefs extends JobsDefs | undefined = undefined,\n TCustomApiRouter extends AnyORPCRouter | undefined = undefined,\n TRealtime = undefined,\n> = Omit<\n BunderstackConfig<TSchema, TAccess, TStorage, TEnv, TCustomApiRouter>,\n 'realtime'\n> & {\n realtime?: TRealtime\n jobs?:\n | TJobsDefs\n | ((\n builder: BunderstackJobsBuilder<TSchema, ValidatedEnv<TEnv>>,\n ) => TJobsDefs)\n}\n\nexport function isBunderstackBackend(\n value: unknown,\n): value is BunderstackBackend<unknown> {\n return (\n typeof value === 'object' && value !== null && BACKEND_INTERNALS in value\n )\n}\n\nexport function bunderstack<\n TSchema extends Record<string, unknown>,\n const TAccess extends Record<string, TableAccessInput> | undefined =\n undefined,\n const TStorage extends StorageConfigInput | undefined = undefined,\n const TEnv extends EnvConfigInput | undefined = undefined,\n const TJobsDefs extends JobsDefs | undefined = undefined,\n TCustomApiRouter extends AnyORPCRouter | undefined = undefined,\n const TRealtime extends BunderstackConfig<\n TSchema,\n TAccess,\n TStorage,\n TEnv,\n TCustomApiRouter\n >['realtime'] = undefined,\n>(\n config: BunderstackDefinitionConfig<\n TSchema,\n TAccess,\n TStorage,\n TEnv,\n TJobsDefs,\n TCustomApiRouter,\n TRealtime\n >,\n): BunderstackBackend<\n BunderstackApp<\n TSchema,\n TAccess,\n BucketNamesOf<TStorage>,\n TEnv,\n TJobsDefs,\n TCustomApiRouter,\n TRealtime\n >\n> {\n const dialect = detectDialect(config.schema)\n const jobsDefs = config.jobs\n ? typeof config.jobs === 'function'\n ? config.jobs(createJobsBuilder<TSchema, ValidatedEnv<TEnv>>())\n : config.jobs\n : undefined\n if (jobsDefs) validateJobsDefs(jobsDefs)\n\n // Resolved here rather than at start: the manifest has to describe the same\n // router the runtime will serve, and `bunderstack blueprint` never starts.\n const customApiRouter =\n typeof config.api === 'function'\n ? (\n config.api as (\n builder: BunderstackApiBuilder<TSchema, ValidatedEnv<TEnv>>,\n ) => TCustomApiRouter\n )(createApiBuilder<TSchema, ValidatedEnv<TEnv>>())\n : config.api\n\n const manifest = buildManifest({\n schema: config.schema,\n dialect,\n migrationsDirectory: config.database.migrations ?? './migrations',\n storage: resolveBuckets(config.storage, {}),\n envConfig: config.env,\n emailProvider: emailProviderTag(config.email),\n realtime: Boolean(config.realtime),\n api: describeApiOperations(customApiRouter),\n jobs: config.storage\n ? {\n ...jobsDefs,\n [STORAGE_SWEEP_JOB_NAME]: {\n kind: 'cron',\n schedule: STORAGE_SWEEP_SCHEDULE,\n handler: () => {},\n },\n }\n : jobsDefs,\n })\n\n type App = BunderstackApp<\n TSchema,\n TAccess,\n BucketNamesOf<TStorage>,\n TEnv,\n TJobsDefs,\n TCustomApiRouter,\n TRealtime\n >\n\n const start = (\n source: Record<string, string | undefined>,\n overrides: RuntimeOverrides = {},\n ): Promise<App> =>\n materializeBunderstack(\n { ...config, jobs: jobsDefs, api: customApiRouter } as never,\n source,\n overrides,\n ) as Promise<App>\n\n let backend: BunderstackBackend<App>\n const test = (async (options) => {\n const testing = await import('./testing')\n return testing.createTestApp(backend, options)\n }) as TestMethod<App>\n test.configure = (options) =>\n (async (overrides: TestOptions = {}) => {\n const testing = await import('./testing')\n return testing.configureTestApp(backend, options)(overrides)\n }) as never\n backend = {\n manifest,\n start: ({ env } = {}) =>\n start(env ?? (process.env as Record<string, string | undefined>)),\n test,\n [BACKEND_INTERNALS]: { declaration: { config, jobsDefs }, start },\n }\n return backend\n}\n"]}
@@ -1,6 +1,17 @@
1
1
  import type { BunderstackManifest } from './manifest.js';
2
2
  export type MigrationMode = 'migrations' | 'push';
3
3
  export type ApplicationFramework = 'tanstack-start' | 'solid' | 'bun-ssr' | 'custom';
4
+ /**
5
+ * Blueprint form of an environment key. `sensitive` and `description` are
6
+ * optional because blueprints committed before 0.23.0 do not carry them.
7
+ */
8
+ export type BlueprintEnvVar = {
9
+ key: string;
10
+ required: boolean;
11
+ scope: 'server' | 'client';
12
+ sensitive?: boolean;
13
+ description?: string;
14
+ };
4
15
  export type BunderstackBlueprint = {
5
16
  version: 1;
6
17
  generator: {
@@ -28,7 +39,11 @@ export type BunderstackBlueprint = {
28
39
  required: true;
29
40
  };
30
41
  };
31
- environment: BunderstackManifest['environment'];
42
+ environment: BlueprintEnvVar[];
43
+ /** Application-declared procedures. Absent in blueprints written before 0.23.0. */
44
+ api?: {
45
+ operations: BunderstackManifest['api']['operations'];
46
+ };
32
47
  background: BunderstackManifest['background'] & {
33
48
  worker: {
34
49
  required: boolean;
@@ -36,6 +51,11 @@ export type BunderstackBlueprint = {
36
51
  };
37
52
  };
38
53
  export declare function parseBlueprint(value: unknown): BunderstackBlueprint;
54
+ /**
55
+ * Whether a host must treat this key as a secret. A blueprint generated before
56
+ * 0.23.0 has no flag, and server keys are secret by default.
57
+ */
58
+ export declare function isSensitiveEnvVar(entry: BlueprintEnvVar): boolean;
39
59
  export declare function blueprintFromManifest(args: {
40
60
  manifest: BunderstackManifest;
41
61
  generatorVersion: string;
@@ -1 +1 @@
1
- {"version":3,"file":"blueprint.d.ts","sourceRoot":"","sources":["../src/blueprint.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAA;AAKrD,MAAM,MAAM,aAAa,GAAG,YAAY,GAAG,MAAM,CAAA;AACjD,MAAM,MAAM,oBAAoB,GAC5B,gBAAgB,GAChB,OAAO,GACP,SAAS,GACT,QAAQ,CAAA;AAEZ,MAAM,MAAM,oBAAoB,GAAG;IACjC,OAAO,EAAE,CAAC,CAAA;IACV,SAAS,EAAE;QAAE,IAAI,EAAE,aAAa,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAA;IACnD,WAAW,EAAE;QACX,SAAS,EAAE,oBAAoB,CAAA;QAC/B,OAAO,EAAE;YAAE,KAAK,EAAE,OAAO,CAAC;YAAC,KAAK,EAAE,OAAO,CAAC;YAAC,MAAM,CAAC,EAAE,QAAQ,CAAA;SAAE,CAAA;KAC/D,CAAA;IACD,WAAW,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,eAAe,EAAE,CAAC,CAAA;KAAE,CAAA;IAClD,SAAS,EAAE;QACT,QAAQ,EAAE,mBAAmB,CAAC,UAAU,CAAC,GAAG;YAAE,aAAa,EAAE,aAAa,CAAA;SAAE,CAAA;QAC5E,OAAO,EAAE,mBAAmB,CAAC,SAAS,CAAC,CAAA;QACvC,QAAQ,CAAC,EAAE;YAAE,QAAQ,EAAE,IAAI,CAAA;SAAE,CAAA;KAC9B,CAAA;IACD,WAAW,EAAE,mBAAmB,CAAC,aAAa,CAAC,CAAA;IAC/C,UAAU,EAAE,mBAAmB,CAAC,YAAY,CAAC,GAAG;QAC9C,MAAM,EAAE;YAAE,QAAQ,EAAE,OAAO,CAAA;SAAE,CAAA;KAC9B,CAAA;CACF,CAAA;AA2GD,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,oBAAoB,CAuDnE;AAED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE;IAC1C,QAAQ,EAAE,mBAAmB,CAAA;IAC7B,gBAAgB,EAAE,MAAM,CAAA;IACxB,KAAK,EAAE,MAAM,CAAA;IACb,aAAa,EAAE,aAAa,CAAA;IAC5B,SAAS,CAAC,EAAE,oBAAoB,CAAA;CACjC,GAAG,oBAAoB,CA0CvB;AAED,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,oBAAoB,CAEvE;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,oBAAoB,GAAG,MAAM,CAmBtE"}
1
+ {"version":3,"file":"blueprint.d.ts","sourceRoot":"","sources":["../src/blueprint.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAA;AAKrD,MAAM,MAAM,aAAa,GAAG,YAAY,GAAG,MAAM,CAAA;AACjD,MAAM,MAAM,oBAAoB,GAC5B,gBAAgB,GAChB,OAAO,GACP,SAAS,GACT,QAAQ,CAAA;AAEZ;;;GAGG;AACH,MAAM,MAAM,eAAe,GAAG;IAC5B,GAAG,EAAE,MAAM,CAAA;IACX,QAAQ,EAAE,OAAO,CAAA;IACjB,KAAK,EAAE,QAAQ,GAAG,QAAQ,CAAA;IAC1B,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB,CAAA;AAED,MAAM,MAAM,oBAAoB,GAAG;IACjC,OAAO,EAAE,CAAC,CAAA;IACV,SAAS,EAAE;QAAE,IAAI,EAAE,aAAa,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAA;IACnD,WAAW,EAAE;QACX,SAAS,EAAE,oBAAoB,CAAA;QAC/B,OAAO,EAAE;YAAE,KAAK,EAAE,OAAO,CAAC;YAAC,KAAK,EAAE,OAAO,CAAC;YAAC,MAAM,CAAC,EAAE,QAAQ,CAAA;SAAE,CAAA;KAC/D,CAAA;IACD,WAAW,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,eAAe,EAAE,CAAC,CAAA;KAAE,CAAA;IAClD,SAAS,EAAE;QACT,QAAQ,EAAE,mBAAmB,CAAC,UAAU,CAAC,GAAG;YAAE,aAAa,EAAE,aAAa,CAAA;SAAE,CAAA;QAC5E,OAAO,EAAE,mBAAmB,CAAC,SAAS,CAAC,CAAA;QACvC,QAAQ,CAAC,EAAE;YAAE,QAAQ,EAAE,IAAI,CAAA;SAAE,CAAA;KAC9B,CAAA;IACD,WAAW,EAAE,eAAe,EAAE,CAAA;IAC9B,mFAAmF;IACnF,GAAG,CAAC,EAAE;QAAE,UAAU,EAAE,mBAAmB,CAAC,KAAK,CAAC,CAAC,YAAY,CAAC,CAAA;KAAE,CAAA;IAC9D,UAAU,EAAE,mBAAmB,CAAC,YAAY,CAAC,GAAG;QAC9C,MAAM,EAAE;YAAE,QAAQ,EAAE,OAAO,CAAA;SAAE,CAAA;KAC9B,CAAA;CACF,CAAA;AAkID,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,oBAAoB,CA4DnE;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CAEjE;AAED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE;IAC1C,QAAQ,EAAE,mBAAmB,CAAA;IAC7B,gBAAgB,EAAE,MAAM,CAAA;IACxB,KAAK,EAAE,MAAM,CAAA;IACb,aAAa,EAAE,aAAa,CAAA;IAC5B,SAAS,CAAC,EAAE,oBAAoB,CAAA;CACjC,GAAG,oBAAoB,CA2CvB;AAED,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,oBAAoB,CAEvE;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,oBAAoB,GAAG,MAAM,CAmBtE"}
package/dist/blueprint.js CHANGED
@@ -15,58 +15,76 @@ const cronSchedule = v.pipe(nonEmpty, v.check((value) => {
15
15
  return false;
16
16
  }
17
17
  }, 'invalid cron schedule'));
18
- const blueprintSchema = v.strictObject({
18
+ // Hosts parse blueprints committed by applications that upgrade bunderstack on
19
+ // their own schedule, so a section written by a newer generator has to survive
20
+ // an older parser instead of failing the whole file.
21
+ function open(entries) {
22
+ return v.objectWithRest(entries, v.unknown());
23
+ }
24
+ const blueprintSchema = open({
19
25
  version: v.literal(1),
20
- generator: v.strictObject({
26
+ generator: open({
21
27
  name: v.literal('bunderstack'),
22
28
  version: nonEmpty,
23
29
  }),
24
- application: v.strictObject({
30
+ application: open({
25
31
  framework: v.picklist(['tanstack-start', 'solid', 'bun-ssr', 'custom']),
26
- scripts: v.strictObject({
32
+ scripts: open({
27
33
  build: v.literal('build'),
28
34
  start: v.literal('start'),
29
35
  worker: v.optional(v.literal('worker')),
30
36
  }),
31
37
  }),
32
- bunderstack: v.strictObject({
38
+ bunderstack: open({
33
39
  entry: relativePath,
34
40
  manifestVersion: v.literal(3),
35
41
  }),
36
- resources: v.strictObject({
37
- database: v.strictObject({
42
+ resources: open({
43
+ database: open({
38
44
  dialect: v.picklist(['sqlite', 'pg']),
39
45
  migrationsDirectory: relativePath,
40
46
  migrationMode: v.picklist(['migrations', 'push']),
41
- tables: v.array(v.strictObject({
47
+ tables: v.array(open({
42
48
  exportName: nonEmpty,
43
49
  physicalName: nonEmpty,
44
50
  system: v.boolean(),
45
51
  })),
46
52
  }),
47
- storage: v.strictObject({
53
+ storage: open({
48
54
  defaultBucket: nonEmpty,
49
- buckets: v.array(v.strictObject({
55
+ buckets: v.array(open({
50
56
  name: nonEmpty,
51
57
  visibility: v.picklist(['public', 'private']),
52
58
  })),
53
59
  }),
54
- realtime: v.optional(v.strictObject({ required: v.literal(true) })),
60
+ realtime: v.optional(open({ required: v.literal(true) })),
55
61
  }),
56
- environment: v.array(v.strictObject({
62
+ environment: v.array(open({
57
63
  key: nonEmpty,
58
64
  required: v.boolean(),
59
65
  scope: v.picklist(['server', 'client']),
66
+ sensitive: v.optional(v.boolean()),
67
+ description: v.optional(v.pipe(nonEmpty, v.maxLength(200))),
68
+ })),
69
+ api: v.optional(open({
70
+ operations: v.array(open({
71
+ handle: nonEmpty,
72
+ operationId: nonEmpty,
73
+ effect: v.picklist(['read', 'mutation', 'unknown']),
74
+ method: v.optional(nonEmpty),
75
+ path: v.optional(nonEmpty),
76
+ summary: v.optional(v.pipe(nonEmpty, v.maxLength(200))),
77
+ })),
60
78
  })),
61
- background: v.strictObject({
62
- worker: v.strictObject({ required: v.boolean() }),
63
- jobs: v.array(v.strictObject({ name: nonEmpty })),
64
- cron: v.array(v.strictObject({
79
+ background: open({
80
+ worker: open({ required: v.boolean() }),
81
+ jobs: v.array(open({ name: nonEmpty })),
82
+ cron: v.array(open({
65
83
  name: nonEmpty,
66
84
  schedule: cronSchedule,
67
85
  timezone: v.literal('UTC'),
68
86
  })),
69
- maintenance: v.array(v.strictObject({
87
+ maintenance: v.array(open({
70
88
  name: v.literal('storage-sweep'),
71
89
  schedule: cronSchedule,
72
90
  timezone: v.literal('UTC'),
@@ -90,6 +108,8 @@ export function parseBlueprint(value) {
90
108
  rejectDuplicates('database export table', blueprint.resources.database.tables.map((entry) => entry.exportName));
91
109
  rejectDuplicates('storage bucket', blueprint.resources.storage.buckets.map((entry) => entry.name));
92
110
  rejectDuplicates('environment key', blueprint.environment.map((entry) => entry.key));
111
+ if (blueprint.api)
112
+ rejectDuplicates('api operation', blueprint.api.operations.map((entry) => entry.handle));
93
113
  rejectDuplicates('background job', blueprint.background.jobs.map((entry) => entry.name));
94
114
  rejectDuplicates('background cron', blueprint.background.cron.map((entry) => entry.name));
95
115
  rejectDuplicates('background maintenance', blueprint.background.maintenance.map((entry) => entry.name));
@@ -105,6 +125,13 @@ export function parseBlueprint(value) {
105
125
  }
106
126
  return blueprint;
107
127
  }
128
+ /**
129
+ * Whether a host must treat this key as a secret. A blueprint generated before
130
+ * 0.23.0 has no flag, and server keys are secret by default.
131
+ */
132
+ export function isSensitiveEnvVar(entry) {
133
+ return entry.sensitive ?? entry.scope === 'server';
134
+ }
108
135
  export function blueprintFromManifest(args) {
109
136
  const workerRequired = args.manifest.background.jobs.length > 0;
110
137
  return parseBlueprint({
@@ -134,6 +161,7 @@ export function blueprintFromManifest(args) {
134
161
  : {}),
135
162
  },
136
163
  environment: sortBy(args.manifest.environment, (entry) => entry.key),
164
+ api: { operations: args.manifest.api.operations },
137
165
  background: {
138
166
  worker: { required: workerRequired },
139
167
  jobs: sortBy(args.manifest.background.jobs, (entry) => entry.name),