@sonicjs-cms/core 3.0.0-beta.4 → 3.0.0-beta.6

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 (47) hide show
  1. package/dist/{chunk-NGAAVEW7.cjs → chunk-26X5DBNW.cjs} +2 -2
  2. package/dist/{chunk-NGAAVEW7.cjs.map → chunk-26X5DBNW.cjs.map} +1 -1
  3. package/dist/{chunk-RAU6STF6.js → chunk-3HYVPA3S.js} +3 -3
  4. package/dist/{chunk-RAU6STF6.js.map → chunk-3HYVPA3S.js.map} +1 -1
  5. package/dist/{chunk-G2NJ4HFL.js → chunk-7SDH5656.js} +2 -2
  6. package/dist/{chunk-G2NJ4HFL.js.map → chunk-7SDH5656.js.map} +1 -1
  7. package/dist/{chunk-X2NG2Y73.cjs → chunk-B52GZLUI.cjs} +3 -3
  8. package/dist/{chunk-X2NG2Y73.cjs.map → chunk-B52GZLUI.cjs.map} +1 -1
  9. package/dist/{chunk-USMR5FB2.js → chunk-CVQMZZNH.js} +4 -4
  10. package/dist/{chunk-USMR5FB2.js.map → chunk-CVQMZZNH.js.map} +1 -1
  11. package/dist/{chunk-TOSCCNK2.js → chunk-D4WJXC6V.js} +2 -2
  12. package/dist/{chunk-TOSCCNK2.js.map → chunk-D4WJXC6V.js.map} +1 -1
  13. package/dist/{chunk-FBLNBFL6.js → chunk-E3C333Y5.js} +4 -4
  14. package/dist/{chunk-FBLNBFL6.js.map → chunk-E3C333Y5.js.map} +1 -1
  15. package/dist/{chunk-UGCA666D.cjs → chunk-FPACD2QX.cjs} +10 -10
  16. package/dist/{chunk-UGCA666D.cjs.map → chunk-FPACD2QX.cjs.map} +1 -1
  17. package/dist/{chunk-IEEBXDSY.cjs → chunk-FXIVUBMC.cjs} +11 -11
  18. package/dist/{chunk-IEEBXDSY.cjs.map → chunk-FXIVUBMC.cjs.map} +1 -1
  19. package/dist/{chunk-UGKKJLSB.cjs → chunk-GE4626OK.cjs} +4 -4
  20. package/dist/{chunk-UGKKJLSB.cjs.map → chunk-GE4626OK.cjs.map} +1 -1
  21. package/dist/{chunk-M6WCSNF5.cjs → chunk-GP2K2K3G.cjs} +2 -2
  22. package/dist/{chunk-M6WCSNF5.cjs.map → chunk-GP2K2K3G.cjs.map} +1 -1
  23. package/dist/{chunk-M7UIZ5TX.cjs → chunk-QG7OILOU.cjs} +200 -200
  24. package/dist/{chunk-M7UIZ5TX.cjs.map → chunk-QG7OILOU.cjs.map} +1 -1
  25. package/dist/{chunk-OO3ETDZD.js → chunk-RPORS2HF.js} +3 -3
  26. package/dist/{chunk-OO3ETDZD.js.map → chunk-RPORS2HF.js.map} +1 -1
  27. package/dist/{chunk-UAKHXLDI.js → chunk-W7LUKLZW.js} +9 -9
  28. package/dist/{chunk-UAKHXLDI.js.map → chunk-W7LUKLZW.js.map} +1 -1
  29. package/dist/index.cjs +252 -252
  30. package/dist/index.js +13 -13
  31. package/dist/middleware.cjs +32 -32
  32. package/dist/middleware.js +3 -3
  33. package/dist/migrations-2VMJH7YB.cjs +13 -0
  34. package/dist/{migrations-6PRYJJ4C.cjs.map → migrations-2VMJH7YB.cjs.map} +1 -1
  35. package/dist/migrations-WPLV26J5.js +4 -0
  36. package/dist/{migrations-E7IFSX44.js.map → migrations-WPLV26J5.js.map} +1 -1
  37. package/dist/plugins.cjs +40 -40
  38. package/dist/plugins.js +3 -3
  39. package/dist/routes.cjs +26 -26
  40. package/dist/routes.js +6 -6
  41. package/dist/services.cjs +14 -14
  42. package/dist/services.js +2 -2
  43. package/dist/utils.cjs +3 -3
  44. package/dist/utils.js +1 -1
  45. package/package.json +1 -1
  46. package/dist/migrations-6PRYJJ4C.cjs +0 -13
  47. package/dist/migrations-E7IFSX44.js +0 -4
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  var chunkJ6JTWD2A_cjs = require('./chunk-J6JTWD2A.cjs');
4
- var chunkX2NG2Y73_cjs = require('./chunk-X2NG2Y73.cjs');
4
+ var chunkB52GZLUI_cjs = require('./chunk-B52GZLUI.cjs');
5
5
  var chunkMNWKYY5E_cjs = require('./chunk-MNWKYY5E.cjs');
6
6
 
7
7
  // src/plugins/singletons/service-singleton.ts
@@ -205,7 +205,7 @@ function definePlugin(input) {
205
205
  );
206
206
  }
207
207
  if (input.sonicjsVersionRange) {
208
- const coreVersion = chunkX2NG2Y73_cjs.getCoreVersion();
208
+ const coreVersion = chunkB52GZLUI_cjs.getCoreVersion();
209
209
  if (!semverSatisfies(coreVersion, input.sonicjsVersionRange)) {
210
210
  console.warn(
211
211
  `[plugins] Plugin "${input.id}" declares sonicjsVersionRange "${input.sonicjsVersionRange}" but running core version is "${coreVersion}". The plugin may not work correctly. Consider updating the plugin or the version range.`
@@ -404,5 +404,5 @@ exports.parseFormDataToSettings = parseFormDataToSettings;
404
404
  exports.renderSchemaFields = renderSchemaFields;
405
405
  exports.setPluginDefinitions = setPluginDefinitions;
406
406
  exports.validateCapabilities = validateCapabilities;
407
- //# sourceMappingURL=chunk-UGKKJLSB.cjs.map
408
- //# sourceMappingURL=chunk-UGKKJLSB.cjs.map
407
+ //# sourceMappingURL=chunk-GE4626OK.cjs.map
408
+ //# sourceMappingURL=chunk-GE4626OK.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/plugins/singletons/service-singleton.ts","../src/plugins/capabilities.ts","../src/plugins/sdk/define-plugin.ts","../src/plugins/sdk/config-schema.ts","../src/services/plugin-definition-registry.ts"],"names":["createTypedHooks","getCoreVersion","escapeHtml"],"mappings":"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","file":"chunk-UGKKJLSB.cjs","sourcesContent":["/**\n * Generic service-singleton factory\n *\n * Generalizes the hook-system-singleton pattern: a process-wide slot for a\n * service that code outside the HTTP request context — most importantly cron /\n * `scheduled()` handlers, which have no per-request `c.env` — can reach without\n * threading it through every call.\n *\n * Contract (same as the hook-system singleton):\n * - `get()` throws if read before `set()` (throw-before-get) so wiring-order\n * bugs surface loudly rather than silently no-oping.\n * - `set()` is idempotent (last write wins) so constructing multiple apps in one\n * process (e.g. across tests) never throws.\n * - `reset()` clears the slot for test isolation.\n */\n\nexport interface ServiceSingleton<T> {\n /** Set the process-wide instance. Last write wins. */\n set(instance: T): void\n /** Get the instance; throws if not yet set. */\n get(): T\n /** True if an instance has been set. */\n has(): boolean\n /** Clear the slot (test isolation). */\n reset(): void\n}\n\n/**\n * Create a service singleton.\n *\n * @param label Human-readable name used in the throw-before-get error message\n * (e.g. `'EmailService'`).\n */\nexport function createServiceSingleton<T>(label: string): ServiceSingleton<T> {\n let current: T | undefined\n return {\n set(instance: T) {\n current = instance\n },\n get(): T {\n if (current === undefined) {\n throw new Error(\n `${label} has not been initialized. Its setter must be called (the app factory does this at construction) before reading it.`\n )\n }\n return current\n },\n has(): boolean {\n return current !== undefined\n },\n reset() {\n current = undefined\n },\n }\n}\n","/**\n * Plugin capabilities\n *\n * A plugin declares the capabilities it needs (`capabilities: ['email:send']`).\n * The host then hands it a context whose powerful accessors are *gated* by those\n * declarations: reaching `ctx.email` without having declared `email:send` throws\n * `SonicCapabilityError` immediately, instead of failing deep inside a send.\n *\n * This is the isolation boundary that Strapi (namespacing only) and Payload (full\n * config access) don't have: capabilities make a plugin's blast radius explicit\n * and enforceable, and double as documentation of what a plugin can touch.\n *\n * Phase 1 vocabulary. Payment/queue/scheduled-fetch capabilities are intentionally\n * deferred to Phase 2 (see the overhaul plan §8.4 / open question 2).\n */\n\n// Type-only import (no runtime coupling) so a const-narrowed `ctx.cap.email`\n// resolves to the real EmailService type instead of `unknown`.\nimport type { EmailService } from '../services/email/email-service'\nimport type { HookEventName } from './hooks/catalog'\n\n/**\n * Fixed capability names. `db:<table>` is parameterized (a plugin owns specific\n * tables), so it is matched by pattern rather than listed here.\n */\nexport const FIXED_CAPABILITIES = [\n 'email:send',\n 'cache:read',\n 'cache:write',\n 'media:read',\n 'media:write',\n 'http:fetch',\n 'cron:register',\n 'admin:menu',\n 'hooks.auth:subscribe',\n 'hooks.content:subscribe',\n // Reserved: gates subscription to the email event family once those events are\n // dispatched. Declared now so the canonical vocabulary is stable and the\n // Infowall rename target resolves.\n 'hooks.email:subscribe',\n] as const\n\nexport type FixedCapability = (typeof FIXED_CAPABILITIES)[number]\n\n/** A scoped database capability, e.g. `db:email_log`. */\nexport type DbCapability = `db:${string}`\n\n/** Any declarable capability. */\nexport type Capability = FixedCapability | DbCapability\n\nconst DB_CAPABILITY_RE = /^db:[a-zA-Z_][a-zA-Z0-9_]*$/\n\n/** True if `name` is a recognized capability (fixed name or a valid `db:<table>`). */\nexport function isKnownCapability(name: string): name is Capability {\n return (FIXED_CAPABILITIES as readonly string[]).includes(name) || DB_CAPABILITY_RE.test(name)\n}\n\n/**\n * Thrown when a plugin uses a capability it did not declare.\n */\nexport class SonicCapabilityError extends Error {\n readonly capability: string\n readonly plugin?: string\n /** The API surface the plugin tried to access (e.g. 'ctx.cap.email'). Optional. */\n readonly accessedApi?: string\n constructor(capability: string, plugin?: string, accessedApi?: string) {\n super(\n `${plugin ? `Plugin \"${plugin}\"` : 'Plugin'} used capability \"${capability}\" without declaring it. ` +\n `Add \"${capability}\" to the plugin's capabilities.`\n )\n this.name = 'SonicCapabilityError'\n this.capability = capability\n this.plugin = plugin\n this.accessedApi = accessedApi\n }\n}\n\n/** True if `capability` is in the granted set. */\nexport function hasCapability(granted: readonly string[], capability: string): boolean {\n return granted.includes(capability)\n}\n\n/**\n * Assert a capability is granted, throwing {@link SonicCapabilityError} otherwise.\n */\nexport function assertCapability(granted: readonly string[], capability: string, plugin?: string): void {\n if (!hasCapability(granted, capability)) {\n throw new SonicCapabilityError(capability, plugin)\n }\n}\n\n/**\n * Validate a plugin's declared capability list. Returns the unknown entries (empty\n * array = all valid). Callers decide whether to warn or hard-fail.\n *\n * Apply {@link normalizeCapabilities} first if the input may contain deprecated\n * spellings (e.g. from an older SDK or a sibling fork's manifest).\n */\nexport function validateCapabilities(declared: readonly string[]): string[] {\n return declared.filter((c) => !isKnownCapability(c))\n}\n\n// ── Capability rename / normalization (cross-version & cross-fork portability) ─\n//\n// Lets a plugin authored against a different/older SDK spelling load against the\n// canonical vocabulary without code changes. The map is deprecated→canonical; it\n// is applied before the known-capability check. Seeded with the sibling fork's\n// (Infowall) spellings so plugins built there are portable here.\n\nexport const CAPABILITY_RENAMES = {\n // storage:* → media:* (this fork scopes the media library as `media`)\n 'storage:read': 'media:read',\n 'storage:write': 'media:write',\n // cron is a direct registration, not a hook subscription, here\n 'hooks.cron:register': 'cron:register',\n // `:register` → `:subscribe` verb; content read/write granularity lives in the\n // event name (before/after), so both collapse to one subscription capability\n 'hooks.auth:register': 'hooks.auth:subscribe',\n 'hooks.content-read:register': 'hooks.content:subscribe',\n 'hooks.content-write:register': 'hooks.content:subscribe',\n 'hooks.email-events:register': 'hooks.email:subscribe',\n // NOTE: Infowall's `request:intercept` has no canonical target — there is no\n // middleware-insertion surface to gate yet, so it is intentionally absent\n // (a plugin declaring it will surface as unknown rather than silently no-op).\n} as const satisfies Record<string, Capability>\n\n/**\n * Resolve a (possibly deprecated) capability string to its canonical form, or\n * `null` if it is unknown after rename resolution. Renames apply first, then the\n * result is checked against the known vocabulary.\n */\nexport function normalizeCapability(input: string): Capability | null {\n const renamed: string = (CAPABILITY_RENAMES as Record<string, Capability>)[input] ?? input\n return isKnownCapability(renamed) ? (renamed as Capability) : null\n}\n\n/**\n * Normalize a list of capability strings. Returns the canonical capabilities plus\n * the inputs that remained unknown after rename resolution, so the caller can warn\n * (production) or reject (strict).\n */\nexport function normalizeCapabilities(declared: readonly string[]): {\n capabilities: Capability[]\n unknown: string[]\n} {\n const capabilities: Capability[] = []\n const unknown: string[] = []\n for (const raw of declared) {\n const canonical = normalizeCapability(raw)\n if (canonical) {\n if (!capabilities.includes(canonical)) capabilities.push(canonical)\n } else {\n unknown.push(raw)\n }\n }\n return { capabilities, unknown }\n}\n\n// ── Capability-gated context ─────────────────────────────────────────────────\n\n/** Provider factories for capability-backed accessors. Each is called lazily. */\nexport interface CapabilityProviders {\n email?: () => EmailService\n cache?: () => unknown\n http?: () => typeof fetch\n}\n\n// ── Type-level capability narrowing ──────────────────────────────────────────\ndeclare const CAP_NOT_DECLARED: unique symbol\n/**\n * The type of a capability accessor the plugin did NOT declare. Deliberately a\n * branded type (not `never`, which is assignable to everything): using it where a\n * service is expected is a compile error, so `ctx.cap.email` without `email:send`\n * fails to type-check instead of silently passing.\n */\nexport type CapabilityNotDeclared<C extends string = string> = {\n readonly [CAP_NOT_DECLARED]: C\n}\n\n// `T` if the declared tuple `Caps` contains `C`, else the branded not-declared type.\ntype WhenGranted<Caps extends readonly Capability[], C extends string, T> =\n C extends Caps[number] ? T : CapabilityNotDeclared<C>\n// `T` if `Caps` contains ANY of the union `C`, else the branded not-declared type.\ntype WhenGrantedAny<Caps extends readonly Capability[], C extends string, T> =\n Extract<Caps[number], C> extends never ? CapabilityNotDeclared<C> : T\n\n/**\n * The gated context handed to a plugin. Accessors throw at runtime unless the\n * backing capability was declared; with a const-narrowed `Caps` tuple they are\n * also *typed* to the service (or `never`) at compile time, so `ctx.cap.email`\n * is `EmailService` only when `'email:send'` was declared.\n */\nexport interface CapabilityContext<Caps extends readonly Capability[] = readonly Capability[]> {\n /** The capabilities granted to this plugin. */\n readonly capabilities: readonly string[]\n /** True if the plugin declared `capability`. */\n has(capability: string): boolean\n /** Throw {@link SonicCapabilityError} unless `capability` was declared. */\n require(capability: string): void\n /** Email service. `EmailService` when `email:send` is declared, else `never`. */\n readonly email: WhenGranted<Caps, 'email:send', EmailService>\n /** Cache service. Present when `cache:read` or `cache:write` is declared. */\n readonly cache: WhenGrantedAny<Caps, 'cache:read' | 'cache:write', unknown>\n /** Outbound fetch. Present when `http:fetch` is declared. */\n readonly http: WhenGranted<Caps, 'http:fetch', typeof fetch>\n}\n\n/** The un-narrowed gated context (every accessor typed as its service). */\nexport type PluginCapabilityContext = CapabilityContext</** all */ readonly Capability[]>\n\n\n/**\n * Build a capability-gated context.\n *\n * Accessors are lazy getters: they check the grant (and that a provider was\n * supplied) at access time, so merely constructing the context is cheap and\n * holding a reference to a capability you never use costs nothing.\n *\n * @param granted Capabilities the plugin declared.\n * @param providers Backing service factories (only the granted ones get called).\n * @param plugin Plugin name, for clearer errors.\n */\nexport function createCapabilityContext<const Caps extends readonly Capability[]>(\n granted: Caps,\n providers: CapabilityProviders = {},\n plugin?: string\n): CapabilityContext<Caps> {\n // `any` so the lazy getters satisfy the narrowed accessor types (the provider\n // returns the real service at runtime; gating is enforced dynamically below).\n const gate = (capability: string, anyOf: string[], provider: (() => unknown) | undefined): any => {\n const ok = anyOf.some((c) => hasCapability(granted, c))\n if (!ok) throw new SonicCapabilityError(capability, plugin)\n if (!provider) {\n throw new Error(\n `Capability \"${capability}\" is declared by ${plugin ?? 'the plugin'} but no provider was supplied by the host.`\n )\n }\n return provider()\n }\n\n const ctx: CapabilityContext<Caps> = {\n capabilities: [...granted],\n has: (capability) => hasCapability(granted, capability),\n require: (capability) => assertCapability(granted, capability, plugin),\n get email() {\n return gate('email:send', ['email:send'], providers.email)\n },\n get cache() {\n return gate('cache:read', ['cache:read', 'cache:write'], providers.cache)\n },\n get http() {\n return gate('http:fetch', ['http:fetch'], providers.http)\n },\n }\n return ctx\n}\n\n// ── Hook-subscription capability map ────────────────────────────────────────\n//\n// Maps each catalog event to the capability a plugin must declare in order to\n// subscribe to it. Used by the wire phase (Phase A) to gate declarative hook\n// registrations. Plugins that don't declare the required capability have the\n// offending hook skipped (warn in prod, error in strict).\n\nexport const HOOK_CAPABILITY_MAP: Record<HookEventName, Capability> = {\n 'content:read': 'hooks.content:subscribe',\n 'content:before:create': 'hooks.content:subscribe',\n 'content:before:update': 'hooks.content:subscribe',\n 'content:before:delete': 'hooks.content:subscribe',\n 'content:after:create': 'hooks.content:subscribe',\n 'content:after:update': 'hooks.content:subscribe',\n 'content:after:delete': 'hooks.content:subscribe',\n 'content:after:publish': 'hooks.content:subscribe',\n 'auth:registration:completed': 'hooks.auth:subscribe',\n 'auth:password-reset:requested': 'hooks.auth:subscribe',\n 'auth:password-reset:completed': 'hooks.auth:subscribe',\n 'auth:magic-link:consumed': 'hooks.auth:subscribe',\n 'auth:otp:verified': 'hooks.auth:subscribe',\n}\n","/**\n * definePlugin() — the v3 plugin authoring entry point\n *\n * A plugin is authored as a single typed declaration and consumed, unchanged, by\n * every part of the runtime:\n *\n * export const emailPlugin = definePlugin({\n * id: 'email',\n * version: '3.0.0',\n * capabilities: ['email:send', 'hooks.auth:subscribe', 'cron:register'],\n * register(app) { app.route('/admin/plugins/email', emailRoutes) }, // SYNC\n * async onBoot(ctx) { // ASYNC\n * ctx.hooks.on('auth:registration:completed', (p) => { ... }) // typed\n * ctx.cap.email // gated\n * },\n * crons: [{ schedule: '*\\/15 * * * *', hookFamily: 'email-reconciliation' }],\n * async onCronTick(event, ctx) { ... },\n * })\n *\n * The object it returns satisfies the structural contracts the runtime already\n * uses — `MountablePlugin` (mount.ts), `WirablePlugin` (wire.ts), `CronablePlugin`\n * (cron.ts) — plus the legacy `Plugin` metadata fields the admin/registry read.\n * No adapters: a defined plugin drops straight into `plugins.register` or the core\n * plugin list.\n *\n * The value definePlugin adds over a hand-written object is the enriched context:\n * inside `onBoot`/`onCronTick` the author gets a *typed* hook facade (`ctx.hooks`)\n * and the *capability-gated* service context (`ctx.cap`), instead of the raw\n * string-keyed hook system. See the two-phase boot contract: `register` is\n * synchronous (routes only); everything env-dependent lives in `onBoot`.\n */\n\nimport type { Hono } from 'hono'\nimport type { Capability } from '../capabilities'\nimport {\n createCapabilityContext,\n normalizeCapabilities,\n type CapabilityProviders,\n type CapabilityContext,\n} from '../capabilities'\nimport { createTypedHooks, type TypedHooks, type TypedHookHandler } from '../hooks/typed-hooks'\nimport type { HookEventName } from '../hooks/catalog'\nimport type { PluginBootContext, WirableHook } from '../wire'\nimport type { CronContext, CronDeclaration, CronTickEvent } from '../cron'\nimport type { MountableRoute } from '../mount'\nimport { getCoreVersion } from '../../utils/version'\nimport type { ConfigSchema } from './config-schema'\nimport type { PluginMenuEntry } from '../../services/plugin-menu-singleton'\n\n// ── Minimal semver helpers (no external dep — Workers are bundle-size constrained) ─\n\n/** True if `v` is a valid semver string (X.Y.Z with optional pre-release). */\nfunction isSemver(v: string): boolean {\n return /^\\d+\\.\\d+\\.\\d+(-[\\w.]+)?(\\+[\\w.]+)?$/.test(v.trim())\n}\n\n/**\n * Very lightweight semver range satisfier. Handles the most common range forms:\n * exact (`1.2.3`), caret (`^1.2.3` = compatible major), tilde (`~1.2.3` = compatible\n * minor), comparators (`>=1.0.0`, `>1`, `<2.0.0`), and space-separated AND chains.\n * Not a full semver implementation — use the `semver` npm package if you need\n * full range syntax in a non-Workers environment.\n */\nfunction semverSatisfies(version: string, range: string): boolean {\n try {\n const [major, minor, patch] = version.trim().split('-')[0]!.split('.').map(Number)\n const v = major! * 1_000_000 + (minor ?? 0) * 1_000 + (patch ?? 0)\n\n const toInt = (s: string) => {\n const [a, b, c] = s.split('.').map(Number)\n return a! * 1_000_000 + (b ?? 0) * 1_000 + (c ?? 0)\n }\n\n // AND chain: all clauses must pass.\n return range\n .trim()\n .split(/\\s+(?=[><=^~])|\\s+(?=\\d)/)\n .filter(Boolean)\n .every((clause) => {\n const c = clause.trim()\n if (c.startsWith('^')) {\n const base = toInt(c.slice(1))\n const nextMajor = Math.floor(base / 1_000_000 + 1) * 1_000_000\n return v >= base && v < nextMajor\n }\n if (c.startsWith('~')) {\n const base = toInt(c.slice(1))\n const nextMinor = Math.floor(base / 1_000 + 1) * 1_000\n return v >= base && v < nextMinor\n }\n if (c.startsWith('>=')) return v >= toInt(c.slice(2))\n if (c.startsWith('<=')) return v <= toInt(c.slice(2))\n if (c.startsWith('>')) return v > toInt(c.slice(1))\n if (c.startsWith('<')) return v < toInt(c.slice(1))\n return v === toInt(c) // exact\n })\n } catch {\n return true // fail open — don't block a plugin on a parse error\n }\n}\n\n/**\n * Declarative typed hook subscriptions: a map of canonical event name → handler,\n * each narrowed to that event's payload. Flattened into the plugin's `hooks[]`\n * and subscribed during the wire phase. Use `onBoot`'s `ctx.hooks.on()` instead\n * for dynamic/conditional subscriptions.\n */\nexport type DeclarativeHooks = {\n [E in HookEventName]?: TypedHookHandler<E>\n}\n\n/**\n * Context handed to a defined plugin's `onBoot` / `onCronTick`.\n *\n * Enriches the raw boot/cron context with a typed hook facade and the gated\n * capability context, while keeping `raw` and `env` available as an escape hatch.\n */\nexport interface DefinedPluginContext<Caps extends readonly Capability[] = readonly Capability[]> {\n /** Typed hook facade — `ctx.hooks.on('auth:registration:completed', …)`. */\n hooks: TypedHooks\n /**\n * Capability-gated services. With a const-narrowed `Caps`, `ctx.cap.email` is\n * typed `EmailService` only when `'email:send'` was declared (else `never`),\n * and throws `SonicCapabilityError` at runtime if accessed undeclared.\n */\n cap: CapabilityContext<Caps>\n /** Runtime bindings, when available (absent during construction). */\n env?: Record<string, unknown>\n /** The unwrapped context the runtime passed. */\n raw: PluginBootContext | CronContext\n}\n\n/** Input to {@link definePlugin}. */\nexport interface DefinePluginInput<Caps extends readonly Capability[] = readonly []> {\n /** Unique, stable plugin id (kebab-case). Becomes the plugin `name`. */\n id: string\n /** Human-readable display name. Defaults to `id`. */\n name?: string\n /**\n * Semantic version of this plugin (e.g. `'1.2.3'`). Must be a valid semver\n * string — invalid values emit a console.warn at definition time.\n */\n version: string\n /**\n * A semver range expressing which SonicJS core versions this plugin supports\n * (e.g. `'^3.0.0'` or `'>=3.1.0 <4.0.0'`). Checked against the running\n * core version at registration; a mismatch logs a compatibility warning but\n * does not block activation (plugins remain resilient by default).\n */\n sonicjsVersionRange?: string\n description?: string\n author?: { name: string; email?: string; url?: string }\n /** Other plugin ids this one needs (load-order / activation). */\n dependencies?: string[]\n /**\n * Capabilities this plugin declares. The gated `ctx.cap` accessors throw for\n * anything not listed here. Pass as a literal (`['email:send'] as const` is not\n * needed — the `const` type param infers the tuple) to get `ctx.cap` narrowed.\n * Unknown/deprecated names are normalized then warned about at definition.\n */\n capabilities?: Caps\n\n // ── Synchronous registration (routes only) ──\n /** Declarative routes (mounted before the /admin catch-all). */\n routes?: MountableRoute[]\n /**\n * Imperative route registration. MUST be synchronous (Hono's router locks after\n * the first request). Async work belongs in `onBoot`.\n */\n register?: (app: Hono) => void\n\n // ── Asynchronous wiring ──\n /**\n * Declarative typed hook subscriptions (`{ 'content:after:create': (p) => … }`).\n * Subscribed during the wire phase; each handler is narrowed to its event payload.\n */\n hooks?: DeclarativeHooks\n /**\n * Run once on first request, after every plugin has registered. The place for\n * dynamic hook subscriptions (`ctx.hooks.on(...)`) and env-dependent setup.\n */\n onBoot?: (context: DefinedPluginContext<Caps>) => void | Promise<void>\n\n // ── Cron ──\n /** Scheduled-work declarations (also list the expressions in wrangler.toml). */\n crons?: CronDeclaration[]\n /** Handler for a fired cron; branch on `event.hookFamily`. */\n onCronTick?: (event: CronTickEvent, context: DefinedPluginContext<Caps>) => void | Promise<void>\n\n // ── Lifecycle (DB/schema only — never touches routes) ──\n install?: (context: unknown) => void | Promise<void>\n uninstall?: (context: unknown) => void | Promise<void>\n activate?: (context: unknown) => void | Promise<void>\n deactivate?: (context: unknown) => void | Promise<void>\n\n // ── Declarative admin surface ──\n /**\n * Declarative admin-sidebar entries collected by registerPlugins. The catalyst\n * sidebar renders them automatically; no per-plugin `addMenuItem(...)` call\n * required.\n */\n menu?: PluginMenuEntry[]\n /**\n * Schema-driven settings. Declaring this auto-renders the admin form at\n * `/admin/settings/plugins/:id`, parses FormData back into typed values, and\n * persists them via the plugin-service. Authors no longer hand-roll settings\n * routes/templates.\n */\n configSchema?: ConfigSchema\n}\n\n/**\n * The runtime shape produced by {@link definePlugin}. Carries the legacy metadata\n * fields plus the v3 surfaces; structurally satisfies `MountablePlugin`,\n * `WirablePlugin`, and `CronablePlugin`.\n */\nexport interface DefinedPlugin {\n id: string\n name: string\n version: string\n /** The semver range for SonicJS core compatibility declared by the author. */\n sonicjsVersionRange?: string\n description?: string\n author?: { name: string; email?: string; url?: string }\n dependencies?: string[]\n capabilities: Capability[]\n routes?: MountableRoute[]\n register?: (app: Hono) => void\n /** Declarative hook subscriptions, flattened for the wire phase. */\n hooks?: WirableHook[]\n /** Wrapped onBoot accepting the runtime's raw boot context. */\n onBoot?: (context: PluginBootContext) => void | Promise<void>\n crons?: CronDeclaration[]\n /** Wrapped onCronTick accepting the runtime's raw cron context. */\n onCronTick?: (event: CronTickEvent, context: CronContext) => void | Promise<void>\n install?: (context: unknown) => void | Promise<void>\n uninstall?: (context: unknown) => void | Promise<void>\n activate?: (context: unknown) => void | Promise<void>\n deactivate?: (context: unknown) => void | Promise<void>\n /** Declarative admin sidebar entries. registerPlugins collects + sets the menu singleton. */\n menu?: PluginMenuEntry[]\n /** Schema-driven settings. Renders the admin settings form for this plugin. */\n configSchema?: ConfigSchema\n /** Marker so tooling/tests can detect a v3-defined plugin. */\n // eslint-disable-next-line @typescript-eslint/naming-convention -- intentional internal marker\n readonly __sonicV3: true\n}\n\n/**\n * Build the enriched context from whatever raw context the runtime passed.\n *\n * Capability providers ride on the raw context (`raw.providers`) — the host\n * supplies real `email`/`cache`/`http` factories there. When none are supplied, a\n * declared-but-used capability throws \"no provider supplied by host\", which is the\n * correct signal during early bring-up.\n */\nfunction enrich<Caps extends readonly Capability[]>(\n raw: PluginBootContext | CronContext,\n runtimeCaps: readonly Capability[],\n pluginName: string\n): DefinedPluginContext<Caps> {\n const providers = (raw as { providers?: CapabilityProviders }).providers ?? {}\n return {\n hooks: createTypedHooks(raw.hooks),\n // Runtime gating uses the normalized capability set; the context TYPE reflects\n // the declared `Caps` tuple (the narrowing the author sees).\n cap: createCapabilityContext(runtimeCaps as unknown as Caps, providers, pluginName),\n env: raw.env,\n raw,\n }\n}\n\n/**\n * Define a v3 plugin. Validates declared capabilities (warns on unknown), then\n * returns a runtime-ready plugin whose `onBoot`/`onCronTick` receive the enriched,\n * typed, capability-gated context. The `const Caps` type parameter captures the\n * declared capability tuple so `ctx.cap` is narrowed at the author's call site.\n */\nexport function definePlugin<const Caps extends readonly Capability[] = readonly []>(\n input: DefinePluginInput<Caps>\n): DefinedPlugin {\n if (!input.id) throw new Error('definePlugin: `id` is required')\n if (!input.version) throw new Error(`definePlugin: \\`version\\` is required (plugin \"${input.id}\")`)\n\n // Semver validation: warn if the plugin's own version is not a valid semver string.\n if (!isSemver(input.version)) {\n // eslint-disable-next-line no-console\n console.warn(\n `[plugins] Plugin \"${input.id}\" has an invalid version: \"${input.version}\". ` +\n `Use a valid semver string (e.g. \"1.0.0\") to participate in version-range checks.`\n )\n }\n\n // SonicJS core compatibility range check.\n if (input.sonicjsVersionRange) {\n const coreVersion = getCoreVersion()\n if (!semverSatisfies(coreVersion, input.sonicjsVersionRange)) {\n // eslint-disable-next-line no-console\n console.warn(\n `[plugins] Plugin \"${input.id}\" declares sonicjsVersionRange \"${input.sonicjsVersionRange}\" ` +\n `but running core version is \"${coreVersion}\". ` +\n `The plugin may not work correctly. Consider updating the plugin or the version range.`\n )\n }\n }\n\n // Normalize declared capabilities to canonical names first (resolves deprecated\n // / cross-fork spellings like `storage:write` → `media:write`), then warn on any\n // that remain unknown. Strict-reject at registration is layered in Phase 5d.\n const { capabilities, unknown } = normalizeCapabilities(input.capabilities ?? [])\n if (unknown.length > 0) {\n // eslint-disable-next-line no-console\n console.warn(\n `[plugins] Plugin \"${input.id}\" declares unknown capabilities: ${unknown.join(', ')}. ` +\n `These will gate nothing. Check for typos or update the capability vocabulary.`\n )\n }\n\n const name = input.id\n\n const onBoot = input.onBoot\n ? (raw: PluginBootContext) => input.onBoot!(enrich(raw, capabilities, name))\n : undefined\n\n const onCronTick = input.onCronTick\n ? (event: CronTickEvent, raw: CronContext) => input.onCronTick!(event, enrich(raw, capabilities, name))\n : undefined\n\n // Flatten the declarative typed `hooks` map into the wirable `hooks[]` array.\n // Each handler is wrapped to the raw (data, context) shape, coalescing a void\n // return back to the incoming payload (matching createTypedHooks().on()).\n const hooks: WirableHook[] | undefined = input.hooks\n ? Object.entries(input.hooks)\n .filter(([, h]) => typeof h === 'function')\n .map(([eventName, h]) => ({\n name: eventName,\n handler: async (data: any, context: any) => {\n const result = await (h as TypedHookHandler<HookEventName>)(data, context ?? {})\n return result === undefined ? data : result\n },\n }))\n : undefined\n\n return {\n id: input.id,\n name,\n version: input.version,\n sonicjsVersionRange: input.sonicjsVersionRange,\n description: input.description,\n author: input.author,\n dependencies: input.dependencies,\n capabilities,\n routes: input.routes,\n register: input.register,\n hooks,\n onBoot,\n crons: input.crons,\n onCronTick,\n install: input.install,\n uninstall: input.uninstall,\n activate: input.activate,\n deactivate: input.deactivate,\n menu: input.menu,\n configSchema: input.configSchema,\n // eslint-disable-next-line @typescript-eslint/naming-convention -- intentional internal marker\n __sonicV3: true,\n }\n}\n\n/** True if `plugin` was produced by {@link definePlugin}. */\nexport function isDefinedPlugin(plugin: unknown): plugin is DefinedPlugin {\n // eslint-disable-next-line @typescript-eslint/naming-convention -- intentional internal marker\n return !!plugin && typeof plugin === 'object' && (plugin as { __sonicV3?: unknown }).__sonicV3 === true\n}\n","/**\n * Schema-driven plugin settings.\n *\n * Plugins declare `configSchema: { key: ConfigSchemaField }` on definePlugin.\n * The host renders the admin settings UI from the schema, parses FormData back\n * into typed values, and exposes the resulting record via `ctx.settings.load()`.\n *\n * Field kinds: 'string' | 'number' | 'boolean' | 'select'. New kinds get added\n * here once and every consumer picks them up.\n *\n * Phase 1 — settings UI only. The renderer emits plain HTML strings designed to\n * compose into the existing admin layout (no client-side JS). FormData parsing\n * coalesces unchecked-checkbox omission into `false` (the only browser quirk\n * the renderer must account for).\n */\n\nimport { escapeHtml } from '../../utils/sanitize'\n\n// ── Field model ──────────────────────────────────────────────────────────────\n\ninterface BaseField {\n label: string\n description?: string\n required?: boolean\n}\n\nexport interface StringField extends BaseField {\n type: 'string'\n default?: string\n format?: 'email' | 'url' | 'password'\n /** Render as `<input type=\"password\">`. Implied when `format === 'password'`. */\n sensitive?: boolean\n placeholder?: string\n minLength?: number\n maxLength?: number\n}\n\nexport interface NumberField extends BaseField {\n type: 'number'\n default?: number\n min?: number\n max?: number\n step?: number\n}\n\nexport interface BooleanField extends BaseField {\n type: 'boolean'\n default?: boolean\n}\n\nexport interface SelectField extends BaseField {\n type: 'select'\n default?: string\n /** Either `['us','eu']` shorthand or `[{ value, label }]` for distinct display strings. */\n options: readonly string[] | readonly { value: string; label: string }[]\n}\n\nexport type ConfigSchemaField = StringField | NumberField | BooleanField | SelectField\n\nexport type ConfigSchema = Record<string, ConfigSchemaField>\n\n/** Parsed shape — typed record inferred from the schema. */\nexport type SettingsFor<S extends ConfigSchema> = {\n [K in keyof S]: S[K] extends StringField\n ? string\n : S[K] extends NumberField\n ? number\n : S[K] extends BooleanField\n ? boolean\n : S[K] extends SelectField\n ? string\n : never\n}\n\n// ── Parse ────────────────────────────────────────────────────────────────────\n\nexport interface ParsedField {\n key: string\n field: ConfigSchemaField\n}\n\n/** Stable, ordered field list (preserves declaration order). */\nexport function parseConfigSchema(schema: ConfigSchema): ParsedField[] {\n return Object.entries(schema).map(([key, field]) => ({ key, field }))\n}\n\n// ── Render ───────────────────────────────────────────────────────────────────\n\n/**\n * Render all fields as HTML controls. Output is a sequence of `<div class=\"field\">…</div>`\n * blocks designed to drop into the admin form template (no surrounding `<form>` or\n * submit button — those belong to the page that calls this).\n */\nexport function renderSchemaFields(\n schema: ConfigSchema,\n currentValues: Record<string, unknown> = {}\n): string {\n return parseConfigSchema(schema)\n .map(({ key, field }) => {\n const value = key in currentValues ? currentValues[key] : (field as { default?: unknown }).default\n return renderField(key, field, value)\n })\n .join('\\n')\n}\n\nfunction renderField(key: string, field: ConfigSchemaField, value: unknown): string {\n const id = `field-${key}`\n const requiredAttr = field.required ? ' required' : ''\n const labelHtml = `${escapeHtml(field.label)}${field.required ? ' *' : ''}`\n const helpHtml = field.description ? `<p class=\"help text-sm text-gray-400 mt-1\">${escapeHtml(field.description)}</p>` : ''\n\n switch (field.type) {\n case 'string': {\n const isPassword = field.sensitive === true || field.format === 'password'\n const inputType = isPassword ? 'password' : field.format === 'email' ? 'email' : field.format === 'url' ? 'url' : 'text'\n const placeholder = field.placeholder ? ` placeholder=\"${escapeHtml(field.placeholder)}\"` : ''\n const min = field.minLength != null ? ` minlength=\"${field.minLength}\"` : ''\n const max = field.maxLength != null ? ` maxlength=\"${field.maxLength}\"` : ''\n return `\n<div class=\"field mb-4\">\n <label for=\"${id}\" class=\"block text-sm font-medium mb-1\">${labelHtml}</label>\n <input id=\"${id}\" name=\"${escapeHtml(key)}\" type=\"${inputType}\"\n class=\"w-full rounded border border-gray-700 bg-gray-800 px-3 py-2 text-sm\"\n value=\"${escapeHtml(String(value ?? ''))}\"${placeholder}${min}${max}${requiredAttr} />\n ${helpHtml}\n</div>`.trim()\n }\n case 'number': {\n const min = field.min != null ? ` min=\"${field.min}\"` : ''\n const max = field.max != null ? ` max=\"${field.max}\"` : ''\n const step = field.step != null ? ` step=\"${field.step}\"` : ''\n return `\n<div class=\"field mb-4\">\n <label for=\"${id}\" class=\"block text-sm font-medium mb-1\">${labelHtml}</label>\n <input id=\"${id}\" name=\"${escapeHtml(key)}\" type=\"number\"\n class=\"w-full rounded border border-gray-700 bg-gray-800 px-3 py-2 text-sm\"\n value=\"${value == null ? '' : escapeHtml(String(value))}\"${min}${max}${step}${requiredAttr} />\n ${helpHtml}\n</div>`.trim()\n }\n case 'boolean': {\n const checked = value === true ? ' checked' : ''\n return `\n<div class=\"field mb-4\">\n <label class=\"flex items-center gap-2 text-sm font-medium\">\n <input name=\"${escapeHtml(key)}\" type=\"checkbox\" class=\"rounded\"${checked} />\n ${labelHtml}\n </label>\n ${helpHtml}\n</div>`.trim()\n }\n case 'select': {\n const optionsHtml = field.options\n .map((o) => {\n const opt = typeof o === 'string' ? { value: o, label: o } : o\n const selected = opt.value === value ? ' selected' : ''\n return `<option value=\"${escapeHtml(opt.value)}\"${selected}>${escapeHtml(opt.label)}</option>`\n })\n .join('')\n return `\n<div class=\"field mb-4\">\n <label for=\"${id}\" class=\"block text-sm font-medium mb-1\">${labelHtml}</label>\n <select id=\"${id}\" name=\"${escapeHtml(key)}\"\n class=\"w-full rounded border border-gray-700 bg-gray-800 px-3 py-2 text-sm\"${requiredAttr}>\n ${optionsHtml}\n </select>\n ${helpHtml}\n</div>`.trim()\n }\n }\n}\n\n// ── FormData → settings ──────────────────────────────────────────────────────\n\n/**\n * Coerce FormData entries into typed settings per the schema. Notably:\n * - Unchecked checkboxes are omitted by the browser; we coalesce to `false`.\n * - Numbers parse via `Number()` and fall back to the field's default if invalid.\n * - Strings preserve empty string (NOT default) so an admin can intentionally clear.\n */\nexport function parseFormDataToSettings(schema: ConfigSchema, form: FormData): Record<string, unknown> {\n const result: Record<string, unknown> = {}\n for (const { key, field } of parseConfigSchema(schema)) {\n switch (field.type) {\n case 'string': {\n const raw = form.get(key)\n result[key] = raw == null ? field.default ?? '' : String(raw)\n break\n }\n case 'number': {\n const raw = form.get(key)\n if (raw == null || raw === '') {\n result[key] = field.default\n } else {\n const n = Number(raw)\n result[key] = Number.isFinite(n) ? n : field.default\n }\n break\n }\n case 'boolean': {\n // Unchecked checkbox → not present in FormData → false.\n result[key] = form.get(key) != null\n break\n }\n case 'select': {\n const raw = form.get(key)\n result[key] = raw == null ? field.default ?? '' : String(raw)\n break\n }\n }\n }\n return result\n}\n\n// ── Defaults ─────────────────────────────────────────────────────────────────\n\n/** Fill missing keys with field defaults. Existing keys (incl. `false`/`0`/'') win. */\nexport function applySchemaDefaults<S extends ConfigSchema>(\n schema: S,\n stored: Record<string, unknown>\n): Record<string, unknown> {\n const result: Record<string, unknown> = { ...stored }\n for (const { key, field } of parseConfigSchema(schema)) {\n if (result[key] === undefined) {\n const def = (field as { default?: unknown }).default\n if (def !== undefined) result[key] = def\n }\n }\n return result\n}\n","/**\n * In-memory registry of registered plugin definitions, indexed by id.\n *\n * Populated by `registerPlugins()` at app construction. The admin settings\n * route uses it to look up a plugin's `configSchema` so the schema-driven\n * settings form can be auto-rendered without per-plugin glue code.\n *\n * Read by the admin layer; written ONLY by registerPlugins.\n */\n\nimport type { RegisterablePlugin } from '../plugins/sdk/register-plugins'\n\nlet registry: ReadonlyMap<string, RegisterablePlugin> = new Map()\n\nexport function setPluginDefinitions(plugins: ReadonlyArray<RegisterablePlugin>): void {\n const next = new Map<string, RegisterablePlugin>()\n for (const p of plugins) next.set(p.id, p)\n registry = next\n}\n\nexport function getPluginDefinition(id: string): RegisterablePlugin | undefined {\n return registry.get(id)\n}\n\nexport function getAllPluginDefinitions(): ReadonlyArray<RegisterablePlugin> {\n return Array.from(registry.values())\n}\n\nexport function resetPluginDefinitions(): void {\n registry = new Map()\n}\n"]}
1
+ {"version":3,"sources":["../src/plugins/singletons/service-singleton.ts","../src/plugins/capabilities.ts","../src/plugins/sdk/define-plugin.ts","../src/plugins/sdk/config-schema.ts","../src/services/plugin-definition-registry.ts"],"names":["createTypedHooks","getCoreVersion","escapeHtml"],"mappings":"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","file":"chunk-GE4626OK.cjs","sourcesContent":["/**\n * Generic service-singleton factory\n *\n * Generalizes the hook-system-singleton pattern: a process-wide slot for a\n * service that code outside the HTTP request context — most importantly cron /\n * `scheduled()` handlers, which have no per-request `c.env` — can reach without\n * threading it through every call.\n *\n * Contract (same as the hook-system singleton):\n * - `get()` throws if read before `set()` (throw-before-get) so wiring-order\n * bugs surface loudly rather than silently no-oping.\n * - `set()` is idempotent (last write wins) so constructing multiple apps in one\n * process (e.g. across tests) never throws.\n * - `reset()` clears the slot for test isolation.\n */\n\nexport interface ServiceSingleton<T> {\n /** Set the process-wide instance. Last write wins. */\n set(instance: T): void\n /** Get the instance; throws if not yet set. */\n get(): T\n /** True if an instance has been set. */\n has(): boolean\n /** Clear the slot (test isolation). */\n reset(): void\n}\n\n/**\n * Create a service singleton.\n *\n * @param label Human-readable name used in the throw-before-get error message\n * (e.g. `'EmailService'`).\n */\nexport function createServiceSingleton<T>(label: string): ServiceSingleton<T> {\n let current: T | undefined\n return {\n set(instance: T) {\n current = instance\n },\n get(): T {\n if (current === undefined) {\n throw new Error(\n `${label} has not been initialized. Its setter must be called (the app factory does this at construction) before reading it.`\n )\n }\n return current\n },\n has(): boolean {\n return current !== undefined\n },\n reset() {\n current = undefined\n },\n }\n}\n","/**\n * Plugin capabilities\n *\n * A plugin declares the capabilities it needs (`capabilities: ['email:send']`).\n * The host then hands it a context whose powerful accessors are *gated* by those\n * declarations: reaching `ctx.email` without having declared `email:send` throws\n * `SonicCapabilityError` immediately, instead of failing deep inside a send.\n *\n * This is the isolation boundary that Strapi (namespacing only) and Payload (full\n * config access) don't have: capabilities make a plugin's blast radius explicit\n * and enforceable, and double as documentation of what a plugin can touch.\n *\n * Phase 1 vocabulary. Payment/queue/scheduled-fetch capabilities are intentionally\n * deferred to Phase 2 (see the overhaul plan §8.4 / open question 2).\n */\n\n// Type-only import (no runtime coupling) so a const-narrowed `ctx.cap.email`\n// resolves to the real EmailService type instead of `unknown`.\nimport type { EmailService } from '../services/email/email-service'\nimport type { HookEventName } from './hooks/catalog'\n\n/**\n * Fixed capability names. `db:<table>` is parameterized (a plugin owns specific\n * tables), so it is matched by pattern rather than listed here.\n */\nexport const FIXED_CAPABILITIES = [\n 'email:send',\n 'cache:read',\n 'cache:write',\n 'media:read',\n 'media:write',\n 'http:fetch',\n 'cron:register',\n 'admin:menu',\n 'hooks.auth:subscribe',\n 'hooks.content:subscribe',\n // Reserved: gates subscription to the email event family once those events are\n // dispatched. Declared now so the canonical vocabulary is stable and the\n // Infowall rename target resolves.\n 'hooks.email:subscribe',\n] as const\n\nexport type FixedCapability = (typeof FIXED_CAPABILITIES)[number]\n\n/** A scoped database capability, e.g. `db:email_log`. */\nexport type DbCapability = `db:${string}`\n\n/** Any declarable capability. */\nexport type Capability = FixedCapability | DbCapability\n\nconst DB_CAPABILITY_RE = /^db:[a-zA-Z_][a-zA-Z0-9_]*$/\n\n/** True if `name` is a recognized capability (fixed name or a valid `db:<table>`). */\nexport function isKnownCapability(name: string): name is Capability {\n return (FIXED_CAPABILITIES as readonly string[]).includes(name) || DB_CAPABILITY_RE.test(name)\n}\n\n/**\n * Thrown when a plugin uses a capability it did not declare.\n */\nexport class SonicCapabilityError extends Error {\n readonly capability: string\n readonly plugin?: string\n /** The API surface the plugin tried to access (e.g. 'ctx.cap.email'). Optional. */\n readonly accessedApi?: string\n constructor(capability: string, plugin?: string, accessedApi?: string) {\n super(\n `${plugin ? `Plugin \"${plugin}\"` : 'Plugin'} used capability \"${capability}\" without declaring it. ` +\n `Add \"${capability}\" to the plugin's capabilities.`\n )\n this.name = 'SonicCapabilityError'\n this.capability = capability\n this.plugin = plugin\n this.accessedApi = accessedApi\n }\n}\n\n/** True if `capability` is in the granted set. */\nexport function hasCapability(granted: readonly string[], capability: string): boolean {\n return granted.includes(capability)\n}\n\n/**\n * Assert a capability is granted, throwing {@link SonicCapabilityError} otherwise.\n */\nexport function assertCapability(granted: readonly string[], capability: string, plugin?: string): void {\n if (!hasCapability(granted, capability)) {\n throw new SonicCapabilityError(capability, plugin)\n }\n}\n\n/**\n * Validate a plugin's declared capability list. Returns the unknown entries (empty\n * array = all valid). Callers decide whether to warn or hard-fail.\n *\n * Apply {@link normalizeCapabilities} first if the input may contain deprecated\n * spellings (e.g. from an older SDK or a sibling fork's manifest).\n */\nexport function validateCapabilities(declared: readonly string[]): string[] {\n return declared.filter((c) => !isKnownCapability(c))\n}\n\n// ── Capability rename / normalization (cross-version & cross-fork portability) ─\n//\n// Lets a plugin authored against a different/older SDK spelling load against the\n// canonical vocabulary without code changes. The map is deprecated→canonical; it\n// is applied before the known-capability check. Seeded with the sibling fork's\n// (Infowall) spellings so plugins built there are portable here.\n\nexport const CAPABILITY_RENAMES = {\n // storage:* → media:* (this fork scopes the media library as `media`)\n 'storage:read': 'media:read',\n 'storage:write': 'media:write',\n // cron is a direct registration, not a hook subscription, here\n 'hooks.cron:register': 'cron:register',\n // `:register` → `:subscribe` verb; content read/write granularity lives in the\n // event name (before/after), so both collapse to one subscription capability\n 'hooks.auth:register': 'hooks.auth:subscribe',\n 'hooks.content-read:register': 'hooks.content:subscribe',\n 'hooks.content-write:register': 'hooks.content:subscribe',\n 'hooks.email-events:register': 'hooks.email:subscribe',\n // NOTE: Infowall's `request:intercept` has no canonical target — there is no\n // middleware-insertion surface to gate yet, so it is intentionally absent\n // (a plugin declaring it will surface as unknown rather than silently no-op).\n} as const satisfies Record<string, Capability>\n\n/**\n * Resolve a (possibly deprecated) capability string to its canonical form, or\n * `null` if it is unknown after rename resolution. Renames apply first, then the\n * result is checked against the known vocabulary.\n */\nexport function normalizeCapability(input: string): Capability | null {\n const renamed: string = (CAPABILITY_RENAMES as Record<string, Capability>)[input] ?? input\n return isKnownCapability(renamed) ? (renamed as Capability) : null\n}\n\n/**\n * Normalize a list of capability strings. Returns the canonical capabilities plus\n * the inputs that remained unknown after rename resolution, so the caller can warn\n * (production) or reject (strict).\n */\nexport function normalizeCapabilities(declared: readonly string[]): {\n capabilities: Capability[]\n unknown: string[]\n} {\n const capabilities: Capability[] = []\n const unknown: string[] = []\n for (const raw of declared) {\n const canonical = normalizeCapability(raw)\n if (canonical) {\n if (!capabilities.includes(canonical)) capabilities.push(canonical)\n } else {\n unknown.push(raw)\n }\n }\n return { capabilities, unknown }\n}\n\n// ── Capability-gated context ─────────────────────────────────────────────────\n\n/** Provider factories for capability-backed accessors. Each is called lazily. */\nexport interface CapabilityProviders {\n email?: () => EmailService\n cache?: () => unknown\n http?: () => typeof fetch\n}\n\n// ── Type-level capability narrowing ──────────────────────────────────────────\ndeclare const CAP_NOT_DECLARED: unique symbol\n/**\n * The type of a capability accessor the plugin did NOT declare. Deliberately a\n * branded type (not `never`, which is assignable to everything): using it where a\n * service is expected is a compile error, so `ctx.cap.email` without `email:send`\n * fails to type-check instead of silently passing.\n */\nexport type CapabilityNotDeclared<C extends string = string> = {\n readonly [CAP_NOT_DECLARED]: C\n}\n\n// `T` if the declared tuple `Caps` contains `C`, else the branded not-declared type.\ntype WhenGranted<Caps extends readonly Capability[], C extends string, T> =\n C extends Caps[number] ? T : CapabilityNotDeclared<C>\n// `T` if `Caps` contains ANY of the union `C`, else the branded not-declared type.\ntype WhenGrantedAny<Caps extends readonly Capability[], C extends string, T> =\n Extract<Caps[number], C> extends never ? CapabilityNotDeclared<C> : T\n\n/**\n * The gated context handed to a plugin. Accessors throw at runtime unless the\n * backing capability was declared; with a const-narrowed `Caps` tuple they are\n * also *typed* to the service (or `never`) at compile time, so `ctx.cap.email`\n * is `EmailService` only when `'email:send'` was declared.\n */\nexport interface CapabilityContext<Caps extends readonly Capability[] = readonly Capability[]> {\n /** The capabilities granted to this plugin. */\n readonly capabilities: readonly string[]\n /** True if the plugin declared `capability`. */\n has(capability: string): boolean\n /** Throw {@link SonicCapabilityError} unless `capability` was declared. */\n require(capability: string): void\n /** Email service. `EmailService` when `email:send` is declared, else `never`. */\n readonly email: WhenGranted<Caps, 'email:send', EmailService>\n /** Cache service. Present when `cache:read` or `cache:write` is declared. */\n readonly cache: WhenGrantedAny<Caps, 'cache:read' | 'cache:write', unknown>\n /** Outbound fetch. Present when `http:fetch` is declared. */\n readonly http: WhenGranted<Caps, 'http:fetch', typeof fetch>\n}\n\n/** The un-narrowed gated context (every accessor typed as its service). */\nexport type PluginCapabilityContext = CapabilityContext</** all */ readonly Capability[]>\n\n\n/**\n * Build a capability-gated context.\n *\n * Accessors are lazy getters: they check the grant (and that a provider was\n * supplied) at access time, so merely constructing the context is cheap and\n * holding a reference to a capability you never use costs nothing.\n *\n * @param granted Capabilities the plugin declared.\n * @param providers Backing service factories (only the granted ones get called).\n * @param plugin Plugin name, for clearer errors.\n */\nexport function createCapabilityContext<const Caps extends readonly Capability[]>(\n granted: Caps,\n providers: CapabilityProviders = {},\n plugin?: string\n): CapabilityContext<Caps> {\n // `any` so the lazy getters satisfy the narrowed accessor types (the provider\n // returns the real service at runtime; gating is enforced dynamically below).\n const gate = (capability: string, anyOf: string[], provider: (() => unknown) | undefined): any => {\n const ok = anyOf.some((c) => hasCapability(granted, c))\n if (!ok) throw new SonicCapabilityError(capability, plugin)\n if (!provider) {\n throw new Error(\n `Capability \"${capability}\" is declared by ${plugin ?? 'the plugin'} but no provider was supplied by the host.`\n )\n }\n return provider()\n }\n\n const ctx: CapabilityContext<Caps> = {\n capabilities: [...granted],\n has: (capability) => hasCapability(granted, capability),\n require: (capability) => assertCapability(granted, capability, plugin),\n get email() {\n return gate('email:send', ['email:send'], providers.email)\n },\n get cache() {\n return gate('cache:read', ['cache:read', 'cache:write'], providers.cache)\n },\n get http() {\n return gate('http:fetch', ['http:fetch'], providers.http)\n },\n }\n return ctx\n}\n\n// ── Hook-subscription capability map ────────────────────────────────────────\n//\n// Maps each catalog event to the capability a plugin must declare in order to\n// subscribe to it. Used by the wire phase (Phase A) to gate declarative hook\n// registrations. Plugins that don't declare the required capability have the\n// offending hook skipped (warn in prod, error in strict).\n\nexport const HOOK_CAPABILITY_MAP: Record<HookEventName, Capability> = {\n 'content:read': 'hooks.content:subscribe',\n 'content:before:create': 'hooks.content:subscribe',\n 'content:before:update': 'hooks.content:subscribe',\n 'content:before:delete': 'hooks.content:subscribe',\n 'content:after:create': 'hooks.content:subscribe',\n 'content:after:update': 'hooks.content:subscribe',\n 'content:after:delete': 'hooks.content:subscribe',\n 'content:after:publish': 'hooks.content:subscribe',\n 'auth:registration:completed': 'hooks.auth:subscribe',\n 'auth:password-reset:requested': 'hooks.auth:subscribe',\n 'auth:password-reset:completed': 'hooks.auth:subscribe',\n 'auth:magic-link:consumed': 'hooks.auth:subscribe',\n 'auth:otp:verified': 'hooks.auth:subscribe',\n}\n","/**\n * definePlugin() — the v3 plugin authoring entry point\n *\n * A plugin is authored as a single typed declaration and consumed, unchanged, by\n * every part of the runtime:\n *\n * export const emailPlugin = definePlugin({\n * id: 'email',\n * version: '3.0.0',\n * capabilities: ['email:send', 'hooks.auth:subscribe', 'cron:register'],\n * register(app) { app.route('/admin/plugins/email', emailRoutes) }, // SYNC\n * async onBoot(ctx) { // ASYNC\n * ctx.hooks.on('auth:registration:completed', (p) => { ... }) // typed\n * ctx.cap.email // gated\n * },\n * crons: [{ schedule: '*\\/15 * * * *', hookFamily: 'email-reconciliation' }],\n * async onCronTick(event, ctx) { ... },\n * })\n *\n * The object it returns satisfies the structural contracts the runtime already\n * uses — `MountablePlugin` (mount.ts), `WirablePlugin` (wire.ts), `CronablePlugin`\n * (cron.ts) — plus the legacy `Plugin` metadata fields the admin/registry read.\n * No adapters: a defined plugin drops straight into `plugins.register` or the core\n * plugin list.\n *\n * The value definePlugin adds over a hand-written object is the enriched context:\n * inside `onBoot`/`onCronTick` the author gets a *typed* hook facade (`ctx.hooks`)\n * and the *capability-gated* service context (`ctx.cap`), instead of the raw\n * string-keyed hook system. See the two-phase boot contract: `register` is\n * synchronous (routes only); everything env-dependent lives in `onBoot`.\n */\n\nimport type { Hono } from 'hono'\nimport type { Capability } from '../capabilities'\nimport {\n createCapabilityContext,\n normalizeCapabilities,\n type CapabilityProviders,\n type CapabilityContext,\n} from '../capabilities'\nimport { createTypedHooks, type TypedHooks, type TypedHookHandler } from '../hooks/typed-hooks'\nimport type { HookEventName } from '../hooks/catalog'\nimport type { PluginBootContext, WirableHook } from '../wire'\nimport type { CronContext, CronDeclaration, CronTickEvent } from '../cron'\nimport type { MountableRoute } from '../mount'\nimport { getCoreVersion } from '../../utils/version'\nimport type { ConfigSchema } from './config-schema'\nimport type { PluginMenuEntry } from '../../services/plugin-menu-singleton'\n\n// ── Minimal semver helpers (no external dep — Workers are bundle-size constrained) ─\n\n/** True if `v` is a valid semver string (X.Y.Z with optional pre-release). */\nfunction isSemver(v: string): boolean {\n return /^\\d+\\.\\d+\\.\\d+(-[\\w.]+)?(\\+[\\w.]+)?$/.test(v.trim())\n}\n\n/**\n * Very lightweight semver range satisfier. Handles the most common range forms:\n * exact (`1.2.3`), caret (`^1.2.3` = compatible major), tilde (`~1.2.3` = compatible\n * minor), comparators (`>=1.0.0`, `>1`, `<2.0.0`), and space-separated AND chains.\n * Not a full semver implementation — use the `semver` npm package if you need\n * full range syntax in a non-Workers environment.\n */\nfunction semverSatisfies(version: string, range: string): boolean {\n try {\n const [major, minor, patch] = version.trim().split('-')[0]!.split('.').map(Number)\n const v = major! * 1_000_000 + (minor ?? 0) * 1_000 + (patch ?? 0)\n\n const toInt = (s: string) => {\n const [a, b, c] = s.split('.').map(Number)\n return a! * 1_000_000 + (b ?? 0) * 1_000 + (c ?? 0)\n }\n\n // AND chain: all clauses must pass.\n return range\n .trim()\n .split(/\\s+(?=[><=^~])|\\s+(?=\\d)/)\n .filter(Boolean)\n .every((clause) => {\n const c = clause.trim()\n if (c.startsWith('^')) {\n const base = toInt(c.slice(1))\n const nextMajor = Math.floor(base / 1_000_000 + 1) * 1_000_000\n return v >= base && v < nextMajor\n }\n if (c.startsWith('~')) {\n const base = toInt(c.slice(1))\n const nextMinor = Math.floor(base / 1_000 + 1) * 1_000\n return v >= base && v < nextMinor\n }\n if (c.startsWith('>=')) return v >= toInt(c.slice(2))\n if (c.startsWith('<=')) return v <= toInt(c.slice(2))\n if (c.startsWith('>')) return v > toInt(c.slice(1))\n if (c.startsWith('<')) return v < toInt(c.slice(1))\n return v === toInt(c) // exact\n })\n } catch {\n return true // fail open — don't block a plugin on a parse error\n }\n}\n\n/**\n * Declarative typed hook subscriptions: a map of canonical event name → handler,\n * each narrowed to that event's payload. Flattened into the plugin's `hooks[]`\n * and subscribed during the wire phase. Use `onBoot`'s `ctx.hooks.on()` instead\n * for dynamic/conditional subscriptions.\n */\nexport type DeclarativeHooks = {\n [E in HookEventName]?: TypedHookHandler<E>\n}\n\n/**\n * Context handed to a defined plugin's `onBoot` / `onCronTick`.\n *\n * Enriches the raw boot/cron context with a typed hook facade and the gated\n * capability context, while keeping `raw` and `env` available as an escape hatch.\n */\nexport interface DefinedPluginContext<Caps extends readonly Capability[] = readonly Capability[]> {\n /** Typed hook facade — `ctx.hooks.on('auth:registration:completed', …)`. */\n hooks: TypedHooks\n /**\n * Capability-gated services. With a const-narrowed `Caps`, `ctx.cap.email` is\n * typed `EmailService` only when `'email:send'` was declared (else `never`),\n * and throws `SonicCapabilityError` at runtime if accessed undeclared.\n */\n cap: CapabilityContext<Caps>\n /** Runtime bindings, when available (absent during construction). */\n env?: Record<string, unknown>\n /** The unwrapped context the runtime passed. */\n raw: PluginBootContext | CronContext\n}\n\n/** Input to {@link definePlugin}. */\nexport interface DefinePluginInput<Caps extends readonly Capability[] = readonly []> {\n /** Unique, stable plugin id (kebab-case). Becomes the plugin `name`. */\n id: string\n /** Human-readable display name. Defaults to `id`. */\n name?: string\n /**\n * Semantic version of this plugin (e.g. `'1.2.3'`). Must be a valid semver\n * string — invalid values emit a console.warn at definition time.\n */\n version: string\n /**\n * A semver range expressing which SonicJS core versions this plugin supports\n * (e.g. `'^3.0.0'` or `'>=3.1.0 <4.0.0'`). Checked against the running\n * core version at registration; a mismatch logs a compatibility warning but\n * does not block activation (plugins remain resilient by default).\n */\n sonicjsVersionRange?: string\n description?: string\n author?: { name: string; email?: string; url?: string }\n /** Other plugin ids this one needs (load-order / activation). */\n dependencies?: string[]\n /**\n * Capabilities this plugin declares. The gated `ctx.cap` accessors throw for\n * anything not listed here. Pass as a literal (`['email:send'] as const` is not\n * needed — the `const` type param infers the tuple) to get `ctx.cap` narrowed.\n * Unknown/deprecated names are normalized then warned about at definition.\n */\n capabilities?: Caps\n\n // ── Synchronous registration (routes only) ──\n /** Declarative routes (mounted before the /admin catch-all). */\n routes?: MountableRoute[]\n /**\n * Imperative route registration. MUST be synchronous (Hono's router locks after\n * the first request). Async work belongs in `onBoot`.\n */\n register?: (app: Hono) => void\n\n // ── Asynchronous wiring ──\n /**\n * Declarative typed hook subscriptions (`{ 'content:after:create': (p) => … }`).\n * Subscribed during the wire phase; each handler is narrowed to its event payload.\n */\n hooks?: DeclarativeHooks\n /**\n * Run once on first request, after every plugin has registered. The place for\n * dynamic hook subscriptions (`ctx.hooks.on(...)`) and env-dependent setup.\n */\n onBoot?: (context: DefinedPluginContext<Caps>) => void | Promise<void>\n\n // ── Cron ──\n /** Scheduled-work declarations (also list the expressions in wrangler.toml). */\n crons?: CronDeclaration[]\n /** Handler for a fired cron; branch on `event.hookFamily`. */\n onCronTick?: (event: CronTickEvent, context: DefinedPluginContext<Caps>) => void | Promise<void>\n\n // ── Lifecycle (DB/schema only — never touches routes) ──\n install?: (context: unknown) => void | Promise<void>\n uninstall?: (context: unknown) => void | Promise<void>\n activate?: (context: unknown) => void | Promise<void>\n deactivate?: (context: unknown) => void | Promise<void>\n\n // ── Declarative admin surface ──\n /**\n * Declarative admin-sidebar entries collected by registerPlugins. The catalyst\n * sidebar renders them automatically; no per-plugin `addMenuItem(...)` call\n * required.\n */\n menu?: PluginMenuEntry[]\n /**\n * Schema-driven settings. Declaring this auto-renders the admin form at\n * `/admin/settings/plugins/:id`, parses FormData back into typed values, and\n * persists them via the plugin-service. Authors no longer hand-roll settings\n * routes/templates.\n */\n configSchema?: ConfigSchema\n}\n\n/**\n * The runtime shape produced by {@link definePlugin}. Carries the legacy metadata\n * fields plus the v3 surfaces; structurally satisfies `MountablePlugin`,\n * `WirablePlugin`, and `CronablePlugin`.\n */\nexport interface DefinedPlugin {\n id: string\n name: string\n version: string\n /** The semver range for SonicJS core compatibility declared by the author. */\n sonicjsVersionRange?: string\n description?: string\n author?: { name: string; email?: string; url?: string }\n dependencies?: string[]\n capabilities: Capability[]\n routes?: MountableRoute[]\n register?: (app: Hono) => void\n /** Declarative hook subscriptions, flattened for the wire phase. */\n hooks?: WirableHook[]\n /** Wrapped onBoot accepting the runtime's raw boot context. */\n onBoot?: (context: PluginBootContext) => void | Promise<void>\n crons?: CronDeclaration[]\n /** Wrapped onCronTick accepting the runtime's raw cron context. */\n onCronTick?: (event: CronTickEvent, context: CronContext) => void | Promise<void>\n install?: (context: unknown) => void | Promise<void>\n uninstall?: (context: unknown) => void | Promise<void>\n activate?: (context: unknown) => void | Promise<void>\n deactivate?: (context: unknown) => void | Promise<void>\n /** Declarative admin sidebar entries. registerPlugins collects + sets the menu singleton. */\n menu?: PluginMenuEntry[]\n /** Schema-driven settings. Renders the admin settings form for this plugin. */\n configSchema?: ConfigSchema\n /** Marker so tooling/tests can detect a v3-defined plugin. */\n // eslint-disable-next-line @typescript-eslint/naming-convention -- intentional internal marker\n readonly __sonicV3: true\n}\n\n/**\n * Build the enriched context from whatever raw context the runtime passed.\n *\n * Capability providers ride on the raw context (`raw.providers`) — the host\n * supplies real `email`/`cache`/`http` factories there. When none are supplied, a\n * declared-but-used capability throws \"no provider supplied by host\", which is the\n * correct signal during early bring-up.\n */\nfunction enrich<Caps extends readonly Capability[]>(\n raw: PluginBootContext | CronContext,\n runtimeCaps: readonly Capability[],\n pluginName: string\n): DefinedPluginContext<Caps> {\n const providers = (raw as { providers?: CapabilityProviders }).providers ?? {}\n return {\n hooks: createTypedHooks(raw.hooks),\n // Runtime gating uses the normalized capability set; the context TYPE reflects\n // the declared `Caps` tuple (the narrowing the author sees).\n cap: createCapabilityContext(runtimeCaps as unknown as Caps, providers, pluginName),\n env: raw.env,\n raw,\n }\n}\n\n/**\n * Define a v3 plugin. Validates declared capabilities (warns on unknown), then\n * returns a runtime-ready plugin whose `onBoot`/`onCronTick` receive the enriched,\n * typed, capability-gated context. The `const Caps` type parameter captures the\n * declared capability tuple so `ctx.cap` is narrowed at the author's call site.\n */\nexport function definePlugin<const Caps extends readonly Capability[] = readonly []>(\n input: DefinePluginInput<Caps>\n): DefinedPlugin {\n if (!input.id) throw new Error('definePlugin: `id` is required')\n if (!input.version) throw new Error(`definePlugin: \\`version\\` is required (plugin \"${input.id}\")`)\n\n // Semver validation: warn if the plugin's own version is not a valid semver string.\n if (!isSemver(input.version)) {\n // eslint-disable-next-line no-console\n console.warn(\n `[plugins] Plugin \"${input.id}\" has an invalid version: \"${input.version}\". ` +\n `Use a valid semver string (e.g. \"1.0.0\") to participate in version-range checks.`\n )\n }\n\n // SonicJS core compatibility range check.\n if (input.sonicjsVersionRange) {\n const coreVersion = getCoreVersion()\n if (!semverSatisfies(coreVersion, input.sonicjsVersionRange)) {\n // eslint-disable-next-line no-console\n console.warn(\n `[plugins] Plugin \"${input.id}\" declares sonicjsVersionRange \"${input.sonicjsVersionRange}\" ` +\n `but running core version is \"${coreVersion}\". ` +\n `The plugin may not work correctly. Consider updating the plugin or the version range.`\n )\n }\n }\n\n // Normalize declared capabilities to canonical names first (resolves deprecated\n // / cross-fork spellings like `storage:write` → `media:write`), then warn on any\n // that remain unknown. Strict-reject at registration is layered in Phase 5d.\n const { capabilities, unknown } = normalizeCapabilities(input.capabilities ?? [])\n if (unknown.length > 0) {\n // eslint-disable-next-line no-console\n console.warn(\n `[plugins] Plugin \"${input.id}\" declares unknown capabilities: ${unknown.join(', ')}. ` +\n `These will gate nothing. Check for typos or update the capability vocabulary.`\n )\n }\n\n const name = input.id\n\n const onBoot = input.onBoot\n ? (raw: PluginBootContext) => input.onBoot!(enrich(raw, capabilities, name))\n : undefined\n\n const onCronTick = input.onCronTick\n ? (event: CronTickEvent, raw: CronContext) => input.onCronTick!(event, enrich(raw, capabilities, name))\n : undefined\n\n // Flatten the declarative typed `hooks` map into the wirable `hooks[]` array.\n // Each handler is wrapped to the raw (data, context) shape, coalescing a void\n // return back to the incoming payload (matching createTypedHooks().on()).\n const hooks: WirableHook[] | undefined = input.hooks\n ? Object.entries(input.hooks)\n .filter(([, h]) => typeof h === 'function')\n .map(([eventName, h]) => ({\n name: eventName,\n handler: async (data: any, context: any) => {\n const result = await (h as TypedHookHandler<HookEventName>)(data, context ?? {})\n return result === undefined ? data : result\n },\n }))\n : undefined\n\n return {\n id: input.id,\n name,\n version: input.version,\n sonicjsVersionRange: input.sonicjsVersionRange,\n description: input.description,\n author: input.author,\n dependencies: input.dependencies,\n capabilities,\n routes: input.routes,\n register: input.register,\n hooks,\n onBoot,\n crons: input.crons,\n onCronTick,\n install: input.install,\n uninstall: input.uninstall,\n activate: input.activate,\n deactivate: input.deactivate,\n menu: input.menu,\n configSchema: input.configSchema,\n // eslint-disable-next-line @typescript-eslint/naming-convention -- intentional internal marker\n __sonicV3: true,\n }\n}\n\n/** True if `plugin` was produced by {@link definePlugin}. */\nexport function isDefinedPlugin(plugin: unknown): plugin is DefinedPlugin {\n // eslint-disable-next-line @typescript-eslint/naming-convention -- intentional internal marker\n return !!plugin && typeof plugin === 'object' && (plugin as { __sonicV3?: unknown }).__sonicV3 === true\n}\n","/**\n * Schema-driven plugin settings.\n *\n * Plugins declare `configSchema: { key: ConfigSchemaField }` on definePlugin.\n * The host renders the admin settings UI from the schema, parses FormData back\n * into typed values, and exposes the resulting record via `ctx.settings.load()`.\n *\n * Field kinds: 'string' | 'number' | 'boolean' | 'select'. New kinds get added\n * here once and every consumer picks them up.\n *\n * Phase 1 — settings UI only. The renderer emits plain HTML strings designed to\n * compose into the existing admin layout (no client-side JS). FormData parsing\n * coalesces unchecked-checkbox omission into `false` (the only browser quirk\n * the renderer must account for).\n */\n\nimport { escapeHtml } from '../../utils/sanitize'\n\n// ── Field model ──────────────────────────────────────────────────────────────\n\ninterface BaseField {\n label: string\n description?: string\n required?: boolean\n}\n\nexport interface StringField extends BaseField {\n type: 'string'\n default?: string\n format?: 'email' | 'url' | 'password'\n /** Render as `<input type=\"password\">`. Implied when `format === 'password'`. */\n sensitive?: boolean\n placeholder?: string\n minLength?: number\n maxLength?: number\n}\n\nexport interface NumberField extends BaseField {\n type: 'number'\n default?: number\n min?: number\n max?: number\n step?: number\n}\n\nexport interface BooleanField extends BaseField {\n type: 'boolean'\n default?: boolean\n}\n\nexport interface SelectField extends BaseField {\n type: 'select'\n default?: string\n /** Either `['us','eu']` shorthand or `[{ value, label }]` for distinct display strings. */\n options: readonly string[] | readonly { value: string; label: string }[]\n}\n\nexport type ConfigSchemaField = StringField | NumberField | BooleanField | SelectField\n\nexport type ConfigSchema = Record<string, ConfigSchemaField>\n\n/** Parsed shape — typed record inferred from the schema. */\nexport type SettingsFor<S extends ConfigSchema> = {\n [K in keyof S]: S[K] extends StringField\n ? string\n : S[K] extends NumberField\n ? number\n : S[K] extends BooleanField\n ? boolean\n : S[K] extends SelectField\n ? string\n : never\n}\n\n// ── Parse ────────────────────────────────────────────────────────────────────\n\nexport interface ParsedField {\n key: string\n field: ConfigSchemaField\n}\n\n/** Stable, ordered field list (preserves declaration order). */\nexport function parseConfigSchema(schema: ConfigSchema): ParsedField[] {\n return Object.entries(schema).map(([key, field]) => ({ key, field }))\n}\n\n// ── Render ───────────────────────────────────────────────────────────────────\n\n/**\n * Render all fields as HTML controls. Output is a sequence of `<div class=\"field\">…</div>`\n * blocks designed to drop into the admin form template (no surrounding `<form>` or\n * submit button — those belong to the page that calls this).\n */\nexport function renderSchemaFields(\n schema: ConfigSchema,\n currentValues: Record<string, unknown> = {}\n): string {\n return parseConfigSchema(schema)\n .map(({ key, field }) => {\n const value = key in currentValues ? currentValues[key] : (field as { default?: unknown }).default\n return renderField(key, field, value)\n })\n .join('\\n')\n}\n\nfunction renderField(key: string, field: ConfigSchemaField, value: unknown): string {\n const id = `field-${key}`\n const requiredAttr = field.required ? ' required' : ''\n const labelHtml = `${escapeHtml(field.label)}${field.required ? ' *' : ''}`\n const helpHtml = field.description ? `<p class=\"help text-sm text-gray-400 mt-1\">${escapeHtml(field.description)}</p>` : ''\n\n switch (field.type) {\n case 'string': {\n const isPassword = field.sensitive === true || field.format === 'password'\n const inputType = isPassword ? 'password' : field.format === 'email' ? 'email' : field.format === 'url' ? 'url' : 'text'\n const placeholder = field.placeholder ? ` placeholder=\"${escapeHtml(field.placeholder)}\"` : ''\n const min = field.minLength != null ? ` minlength=\"${field.minLength}\"` : ''\n const max = field.maxLength != null ? ` maxlength=\"${field.maxLength}\"` : ''\n return `\n<div class=\"field mb-4\">\n <label for=\"${id}\" class=\"block text-sm font-medium mb-1\">${labelHtml}</label>\n <input id=\"${id}\" name=\"${escapeHtml(key)}\" type=\"${inputType}\"\n class=\"w-full rounded border border-gray-700 bg-gray-800 px-3 py-2 text-sm\"\n value=\"${escapeHtml(String(value ?? ''))}\"${placeholder}${min}${max}${requiredAttr} />\n ${helpHtml}\n</div>`.trim()\n }\n case 'number': {\n const min = field.min != null ? ` min=\"${field.min}\"` : ''\n const max = field.max != null ? ` max=\"${field.max}\"` : ''\n const step = field.step != null ? ` step=\"${field.step}\"` : ''\n return `\n<div class=\"field mb-4\">\n <label for=\"${id}\" class=\"block text-sm font-medium mb-1\">${labelHtml}</label>\n <input id=\"${id}\" name=\"${escapeHtml(key)}\" type=\"number\"\n class=\"w-full rounded border border-gray-700 bg-gray-800 px-3 py-2 text-sm\"\n value=\"${value == null ? '' : escapeHtml(String(value))}\"${min}${max}${step}${requiredAttr} />\n ${helpHtml}\n</div>`.trim()\n }\n case 'boolean': {\n const checked = value === true ? ' checked' : ''\n return `\n<div class=\"field mb-4\">\n <label class=\"flex items-center gap-2 text-sm font-medium\">\n <input name=\"${escapeHtml(key)}\" type=\"checkbox\" class=\"rounded\"${checked} />\n ${labelHtml}\n </label>\n ${helpHtml}\n</div>`.trim()\n }\n case 'select': {\n const optionsHtml = field.options\n .map((o) => {\n const opt = typeof o === 'string' ? { value: o, label: o } : o\n const selected = opt.value === value ? ' selected' : ''\n return `<option value=\"${escapeHtml(opt.value)}\"${selected}>${escapeHtml(opt.label)}</option>`\n })\n .join('')\n return `\n<div class=\"field mb-4\">\n <label for=\"${id}\" class=\"block text-sm font-medium mb-1\">${labelHtml}</label>\n <select id=\"${id}\" name=\"${escapeHtml(key)}\"\n class=\"w-full rounded border border-gray-700 bg-gray-800 px-3 py-2 text-sm\"${requiredAttr}>\n ${optionsHtml}\n </select>\n ${helpHtml}\n</div>`.trim()\n }\n }\n}\n\n// ── FormData → settings ──────────────────────────────────────────────────────\n\n/**\n * Coerce FormData entries into typed settings per the schema. Notably:\n * - Unchecked checkboxes are omitted by the browser; we coalesce to `false`.\n * - Numbers parse via `Number()` and fall back to the field's default if invalid.\n * - Strings preserve empty string (NOT default) so an admin can intentionally clear.\n */\nexport function parseFormDataToSettings(schema: ConfigSchema, form: FormData): Record<string, unknown> {\n const result: Record<string, unknown> = {}\n for (const { key, field } of parseConfigSchema(schema)) {\n switch (field.type) {\n case 'string': {\n const raw = form.get(key)\n result[key] = raw == null ? field.default ?? '' : String(raw)\n break\n }\n case 'number': {\n const raw = form.get(key)\n if (raw == null || raw === '') {\n result[key] = field.default\n } else {\n const n = Number(raw)\n result[key] = Number.isFinite(n) ? n : field.default\n }\n break\n }\n case 'boolean': {\n // Unchecked checkbox → not present in FormData → false.\n result[key] = form.get(key) != null\n break\n }\n case 'select': {\n const raw = form.get(key)\n result[key] = raw == null ? field.default ?? '' : String(raw)\n break\n }\n }\n }\n return result\n}\n\n// ── Defaults ─────────────────────────────────────────────────────────────────\n\n/** Fill missing keys with field defaults. Existing keys (incl. `false`/`0`/'') win. */\nexport function applySchemaDefaults<S extends ConfigSchema>(\n schema: S,\n stored: Record<string, unknown>\n): Record<string, unknown> {\n const result: Record<string, unknown> = { ...stored }\n for (const { key, field } of parseConfigSchema(schema)) {\n if (result[key] === undefined) {\n const def = (field as { default?: unknown }).default\n if (def !== undefined) result[key] = def\n }\n }\n return result\n}\n","/**\n * In-memory registry of registered plugin definitions, indexed by id.\n *\n * Populated by `registerPlugins()` at app construction. The admin settings\n * route uses it to look up a plugin's `configSchema` so the schema-driven\n * settings form can be auto-rendered without per-plugin glue code.\n *\n * Read by the admin layer; written ONLY by registerPlugins.\n */\n\nimport type { RegisterablePlugin } from '../plugins/sdk/register-plugins'\n\nlet registry: ReadonlyMap<string, RegisterablePlugin> = new Map()\n\nexport function setPluginDefinitions(plugins: ReadonlyArray<RegisterablePlugin>): void {\n const next = new Map<string, RegisterablePlugin>()\n for (const p of plugins) next.set(p.id, p)\n registry = next\n}\n\nexport function getPluginDefinition(id: string): RegisterablePlugin | undefined {\n return registry.get(id)\n}\n\nexport function getAllPluginDefinitions(): ReadonlyArray<RegisterablePlugin> {\n return Array.from(registry.values())\n}\n\nexport function resetPluginDefinitions(): void {\n registry = new Map()\n}\n"]}
@@ -272,5 +272,5 @@ var MigrationService = class {
272
272
 
273
273
  exports.MigrationService = MigrationService;
274
274
  exports.ensureScalarSchema = ensureScalarSchema;
275
- //# sourceMappingURL=chunk-M6WCSNF5.cjs.map
276
- //# sourceMappingURL=chunk-M6WCSNF5.cjs.map
275
+ //# sourceMappingURL=chunk-GP2K2K3G.cjs.map
276
+ //# sourceMappingURL=chunk-GP2K2K3G.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/db/migrations-bundle.ts","../src/services/document-scalar-schema.ts","../src/services/migrations.ts"],"names":[],"mappings":"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","file":"chunk-M6WCSNF5.cjs","sourcesContent":["/**\n * AUTO-GENERATED FILE - DO NOT EDIT\n * Generated by: scripts/generate-migrations.ts\n * Generated at: 2026-06-18T23:29:38.385Z\n *\n * This file contains all migration SQL bundled for use in Cloudflare Workers\n * where filesystem access is not available at runtime.\n */\n\nexport interface BundledMigration {\n id: string\n name: string\n filename: string\n description: string\n sql: string\n}\n\nexport const bundledMigrations: BundledMigration[] = [\n {\n id: '0001',\n name: 'Core',\n filename: '0001_core.sql',\n description: 'Migration 0001: Core',\n sql: \"-- Migration 0001: Auth tables\\n-- auth_user, auth_session, auth_account, auth_verification + BA plugin tables + RBAC + auth support.\\n-- Only auth_* prefixed tables live here. All content lives in document_* tables (0002_documents.sql).\\n\\n-- ── auth_user ────────────────────────────────────────────────────────────────\\n-- BA user model + SonicJS domain columns as BA additionalFields.\\nCREATE TABLE IF NOT EXISTS auth_user (\\n id TEXT PRIMARY KEY,\\n name TEXT,\\n email TEXT NOT NULL UNIQUE,\\n email_verified INTEGER NOT NULL DEFAULT 0,\\n image TEXT,\\n created_at INTEGER NOT NULL,\\n updated_at INTEGER NOT NULL,\\n -- SonicJS additionalFields\\n first_name TEXT NOT NULL,\\n last_name TEXT NOT NULL,\\n role TEXT NOT NULL DEFAULT 'viewer',\\n -- Platform super-admin: bypasses the multi-tenant membership gate, uses global roles in every\\n -- tenant. Opt-in (default 0); intentionally NOT derived from the 'admin' role.\\n is_super_admin INTEGER NOT NULL DEFAULT 0,\\n avatar TEXT,\\n password_hash TEXT,\\n is_active INTEGER NOT NULL DEFAULT 1,\\n last_login_at INTEGER,\\n phone TEXT,\\n bio TEXT,\\n timezone TEXT DEFAULT 'UTC',\\n language TEXT DEFAULT 'en',\\n email_notifications INTEGER DEFAULT 1,\\n theme TEXT DEFAULT 'dark',\\n invitation_token TEXT,\\n invited_by TEXT,\\n invited_at INTEGER,\\n accepted_invitation_at INTEGER,\\n failed_login_count INTEGER NOT NULL DEFAULT 0,\\n locked_until INTEGER\\n);\\n\\nCREATE INDEX IF NOT EXISTS idx_auth_user_email ON auth_user(email);\\nCREATE INDEX IF NOT EXISTS idx_auth_user_role ON auth_user(role);\\nCREATE INDEX IF NOT EXISTS idx_auth_user_invitation_token ON auth_user(invitation_token);\\nCREATE INDEX IF NOT EXISTS idx_auth_user_locked_until ON auth_user(locked_until) WHERE locked_until IS NOT NULL;\\n\\n-- ── auth_session ─────────────────────────────────────────────────────────────\\nCREATE TABLE IF NOT EXISTS auth_session (\\n id TEXT PRIMARY KEY,\\n user_id TEXT NOT NULL REFERENCES auth_user(id) ON DELETE CASCADE,\\n token TEXT NOT NULL UNIQUE,\\n expires_at INTEGER NOT NULL,\\n ip_address TEXT,\\n user_agent TEXT,\\n created_at INTEGER NOT NULL,\\n updated_at INTEGER NOT NULL\\n);\\nCREATE INDEX IF NOT EXISTS idx_auth_session_user_id ON auth_session(user_id);\\nCREATE INDEX IF NOT EXISTS idx_auth_session_token ON auth_session(token);\\nCREATE INDEX IF NOT EXISTS idx_auth_session_expires_at ON auth_session(expires_at);\\n\\n-- ── auth_account ─────────────────────────────────────────────────────────────\\nCREATE TABLE IF NOT EXISTS auth_account (\\n id TEXT PRIMARY KEY,\\n user_id TEXT NOT NULL REFERENCES auth_user(id) ON DELETE CASCADE,\\n account_id TEXT NOT NULL,\\n provider_id TEXT NOT NULL,\\n access_token TEXT,\\n refresh_token TEXT,\\n access_token_expires_at INTEGER,\\n refresh_token_expires_at INTEGER,\\n scope TEXT,\\n id_token TEXT,\\n password TEXT,\\n created_at INTEGER NOT NULL,\\n updated_at INTEGER NOT NULL\\n);\\nCREATE INDEX IF NOT EXISTS idx_auth_account_user_id ON auth_account(user_id);\\nCREATE INDEX IF NOT EXISTS idx_auth_account_provider ON auth_account(provider_id, account_id);\\n\\n-- ── auth_verification ────────────────────────────────────────────────────────\\n-- Covers email verification, password reset, magic-link tokens, OTP codes.\\nCREATE TABLE IF NOT EXISTS auth_verification (\\n id TEXT PRIMARY KEY,\\n identifier TEXT NOT NULL,\\n value TEXT NOT NULL,\\n expires_at INTEGER NOT NULL,\\n created_at INTEGER NOT NULL,\\n updated_at INTEGER NOT NULL\\n);\\nCREATE INDEX IF NOT EXISTS idx_auth_verification_identifier ON auth_verification(identifier);\\n\\n-- ── BA plugin tables ──────────────────────────────────────────────────────────\\n\\nCREATE TABLE IF NOT EXISTS auth_two_factor (\\n id TEXT PRIMARY KEY,\\n secret TEXT NOT NULL,\\n backup_codes TEXT NOT NULL,\\n user_id TEXT NOT NULL REFERENCES auth_user(id) ON DELETE CASCADE,\\n verified INTEGER NOT NULL DEFAULT 1,\\n created_at INTEGER NOT NULL,\\n updated_at INTEGER NOT NULL\\n);\\nCREATE INDEX IF NOT EXISTS idx_auth_two_factor_user_id ON auth_two_factor(user_id);\\n\\nCREATE TABLE IF NOT EXISTS auth_tenant (\\n id TEXT PRIMARY KEY,\\n name TEXT NOT NULL,\\n slug TEXT NOT NULL UNIQUE,\\n logo TEXT,\\n metadata TEXT,\\n -- SonicJS tenant-resolution fields (BA organization additionalFields):\\n status TEXT NOT NULL DEFAULT 'active',\\n domain TEXT,\\n notes TEXT NOT NULL DEFAULT '',\\n created_at INTEGER NOT NULL,\\n updated_at INTEGER NOT NULL\\n);\\nCREATE INDEX IF NOT EXISTS idx_auth_tenant_domain ON auth_tenant(domain);\\n\\nCREATE TABLE IF NOT EXISTS auth_tenant_member (\\n id TEXT PRIMARY KEY,\\n tenant_id TEXT NOT NULL REFERENCES auth_tenant(id) ON DELETE CASCADE,\\n user_id TEXT NOT NULL REFERENCES auth_user(id) ON DELETE CASCADE,\\n role TEXT NOT NULL DEFAULT 'member',\\n email TEXT,\\n created_at INTEGER NOT NULL,\\n updated_at INTEGER NOT NULL,\\n UNIQUE(tenant_id, user_id)\\n);\\nCREATE INDEX IF NOT EXISTS idx_auth_tenant_member_tenant ON auth_tenant_member(tenant_id);\\nCREATE INDEX IF NOT EXISTS idx_auth_tenant_member_user ON auth_tenant_member(user_id);\\n\\nCREATE TABLE IF NOT EXISTS auth_tenant_invitation (\\n id TEXT PRIMARY KEY,\\n tenant_id TEXT NOT NULL REFERENCES auth_tenant(id) ON DELETE CASCADE,\\n email TEXT NOT NULL,\\n role TEXT NOT NULL DEFAULT 'member',\\n status TEXT NOT NULL DEFAULT 'pending',\\n expires_at INTEGER NOT NULL,\\n inviter_id TEXT REFERENCES auth_user(id) ON DELETE SET NULL,\\n created_at INTEGER NOT NULL,\\n updated_at INTEGER NOT NULL\\n);\\nCREATE INDEX IF NOT EXISTS idx_auth_tenant_invitation_tenant ON auth_tenant_invitation(tenant_id);\\nCREATE INDEX IF NOT EXISTS idx_auth_tenant_invitation_email ON auth_tenant_invitation(email);\\n\\nCREATE TABLE IF NOT EXISTS auth_tenant_team (\\n id TEXT PRIMARY KEY,\\n name TEXT NOT NULL,\\n tenant_id TEXT NOT NULL REFERENCES auth_tenant(id) ON DELETE CASCADE,\\n created_at INTEGER NOT NULL,\\n updated_at INTEGER NOT NULL\\n);\\n\\n-- ── RBAC ─────────────────────────────────────────────────────────────────────\\n-- RBAC roles, verbs, and user-role assignments are document-backed (is_auth doc\\n-- types rbac_role / rbac_verb / rbac_user_roles — see services/rbac.ts). The\\n-- system roles/verbs/grants are seeded at bootstrap by RbacService.ensureSystemRbacSeed().\\n-- No auth_rbac_* tables.\\n\\n-- ── Auth support tables ───────────────────────────────────────────────────────\\nCREATE TABLE IF NOT EXISTS auth_password_history (\\n id TEXT PRIMARY KEY,\\n user_id TEXT NOT NULL REFERENCES auth_user(id) ON DELETE CASCADE,\\n password_hash TEXT NOT NULL,\\n created_at INTEGER NOT NULL\\n);\\nCREATE INDEX IF NOT EXISTS idx_auth_password_history_user_id ON auth_password_history(user_id);\\n\\nCREATE TABLE IF NOT EXISTS auth_api_tokens (\\n id TEXT PRIMARY KEY,\\n name TEXT NOT NULL,\\n token TEXT NOT NULL UNIQUE,\\n user_id TEXT NOT NULL REFERENCES auth_user(id),\\n permissions TEXT NOT NULL,\\n expires_at INTEGER,\\n last_used_at INTEGER,\\n created_at INTEGER NOT NULL\\n);\\nCREATE INDEX IF NOT EXISTS idx_auth_api_tokens_user ON auth_api_tokens(user_id);\\nCREATE INDEX IF NOT EXISTS idx_auth_api_tokens_token ON auth_api_tokens(token);\\n\\n-- User profiles moved to the document model: a `user_profile` document (is_auth type),\\n-- one per user, addressed by slug = userId. See services/document-types-seed.ts and\\n-- plugins/core-plugins/user-profiles/user-profile-document.ts. No auth_user_profiles table.\\n\"\n },\n {\n id: '0002',\n name: 'Documents',\n filename: '0002_documents.sql',\n description: 'Migration 0002: Documents',\n sql: \"-- Migration 0002: Document Schema (v3 greenfield)\\n-- Contains only the new document data model tables, generated columns, and indexes.\\n\\n-- Document type registry\\nCREATE TABLE IF NOT EXISTS document_types (\\n id TEXT PRIMARY KEY,\\n name TEXT NOT NULL UNIQUE,\\n display_name TEXT NOT NULL,\\n description TEXT,\\n schema TEXT NOT NULL DEFAULT '{}',\\n queryable_fields TEXT NOT NULL DEFAULT '[]',\\n settings TEXT NOT NULL DEFAULT '{}',\\n plugin_id TEXT,\\n source TEXT NOT NULL DEFAULT 'code' CHECK (source IN ('code', 'plugin', 'system')),\\n schema_version INTEGER NOT NULL DEFAULT 1,\\n is_system INTEGER NOT NULL DEFAULT 0,\\n is_active INTEGER NOT NULL DEFAULT 1,\\n is_auth INTEGER NOT NULL DEFAULT 0,\\n created_at INTEGER NOT NULL DEFAULT (unixepoch()),\\n updated_at INTEGER NOT NULL DEFAULT (unixepoch())\\n);\\n\\nCREATE INDEX IF NOT EXISTS idx_document_types_plugin ON document_types(plugin_id);\\nCREATE INDEX IF NOT EXISTS idx_document_types_active ON document_types(is_active);\\n\\n-- Documents: canonical document rows and historical versions.\\nCREATE TABLE IF NOT EXISTS documents (\\n id TEXT PRIMARY KEY,\\n root_id TEXT NOT NULL,\\n type_id TEXT NOT NULL REFERENCES document_types(id),\\n type_version INTEGER NOT NULL DEFAULT 1,\\n\\n version_of_id TEXT REFERENCES documents(id),\\n version_number INTEGER NOT NULL DEFAULT 1,\\n\\n is_current_draft INTEGER NOT NULL DEFAULT 1,\\n is_published INTEGER NOT NULL DEFAULT 0,\\n status TEXT NOT NULL DEFAULT 'draft' CHECK (status IN ('draft', 'published', 'archived')),\\n\\n parent_root_id TEXT NOT NULL DEFAULT '',\\n slug TEXT,\\n path TEXT,\\n title TEXT,\\n zone TEXT,\\n sort_order INTEGER NOT NULL DEFAULT 0,\\n visible INTEGER NOT NULL DEFAULT 1,\\n\\n published_at INTEGER,\\n scheduled_at INTEGER,\\n expires_at INTEGER,\\n deleted_at INTEGER,\\n\\n tenant_id TEXT NOT NULL DEFAULT 'default',\\n locale TEXT NOT NULL DEFAULT 'default',\\n translation_group_id TEXT NOT NULL DEFAULT '',\\n\\n data TEXT NOT NULL DEFAULT '{}',\\n metadata TEXT NOT NULL DEFAULT '{}',\\n\\n owner_id TEXT,\\n created_by TEXT,\\n updated_by TEXT,\\n created_at INTEGER NOT NULL DEFAULT (unixepoch()),\\n updated_at INTEGER NOT NULL DEFAULT (unixepoch())\\n);\\n\\n-- Queryable scalar fields (VIRTUAL generated columns) and their q_* filter indexes\\n-- are AUTO-GENERATED at runtime from each document type's queryableFields config —\\n-- see DocumentTypeRegistry.register() -> ensureScalarSchema() (document-scalar-schema.ts).\\n-- Do not hand-add q_* columns/indexes here; declare the field in the type instead.\\n\\n-- Revision chain\\nCREATE INDEX IF NOT EXISTS idx_documents_root ON documents(root_id, version_number DESC);\\n\\n-- List / lifecycle\\nCREATE INDEX IF NOT EXISTS idx_documents_published ON documents(tenant_id, type_id, locale, is_published)\\n WHERE is_published = 1 AND deleted_at IS NULL;\\nCREATE INDEX IF NOT EXISTS idx_documents_drafts ON documents(tenant_id, type_id, status, is_current_draft)\\n WHERE is_current_draft = 1;\\nCREATE INDEX IF NOT EXISTS idx_documents_parent ON documents(tenant_id, parent_root_id, sort_order, is_published);\\nCREATE INDEX IF NOT EXISTS idx_documents_path ON documents(tenant_id, path);\\nCREATE INDEX IF NOT EXISTS idx_documents_translation ON documents(translation_group_id, locale);\\nCREATE INDEX IF NOT EXISTS idx_documents_deleted ON documents(deleted_at);\\nCREATE INDEX IF NOT EXISTS idx_documents_scheduled ON documents(scheduled_at) WHERE scheduled_at IS NOT NULL;\\nCREATE INDEX IF NOT EXISTS idx_documents_expires ON documents(expires_at) WHERE expires_at IS NOT NULL;\\n\\n-- Stable keyset/cursor pagination for published lists\\nCREATE INDEX IF NOT EXISTS idx_documents_published_cursor\\n ON documents(tenant_id, type_id, updated_at DESC, id DESC)\\n WHERE is_published = 1 AND deleted_at IS NULL;\\n\\n-- (q_* generated-column filter indexes are auto-created at runtime — see note above.)\\n\\n-- Partial unique indexes: the hard concurrency guarantees for draft/publish invariants.\\nCREATE UNIQUE INDEX IF NOT EXISTS idx_documents_one_current_draft\\n ON documents(root_id) WHERE is_current_draft = 1;\\nCREATE UNIQUE INDEX IF NOT EXISTS idx_documents_one_published\\n ON documents(root_id) WHERE is_published = 1;\\nCREATE UNIQUE INDEX IF NOT EXISTS idx_documents_unique_version\\n ON documents(root_id, version_number);\\nCREATE UNIQUE INDEX IF NOT EXISTS idx_documents_unique_slug\\n ON documents(tenant_id, locale, type_id, parent_root_id, slug)\\n WHERE is_current_draft = 1 AND deleted_at IS NULL AND slug IS NOT NULL;\\nCREATE UNIQUE INDEX IF NOT EXISTS idx_documents_one_translation_per_locale\\n ON documents(tenant_id, translation_group_id, locale)\\n WHERE is_current_draft = 1 AND translation_group_id <> '';\\n\\n-- Document references: typed document-to-document edges.\\nCREATE TABLE IF NOT EXISTS document_references (\\n id TEXT PRIMARY KEY,\\n tenant_id TEXT NOT NULL,\\n from_root_id TEXT NOT NULL,\\n from_document_id TEXT NOT NULL REFERENCES documents(id) ON DELETE CASCADE,\\n field_name TEXT NOT NULL,\\n ordinal INTEGER NOT NULL DEFAULT 0,\\n to_root_id TEXT NOT NULL,\\n ref_strength TEXT NOT NULL DEFAULT 'weak' CHECK (ref_strength IN ('strong', 'weak')),\\n created_at INTEGER NOT NULL DEFAULT (unixepoch())\\n);\\n\\nCREATE INDEX IF NOT EXISTS idx_docref_to ON document_references(tenant_id, to_root_id);\\nCREATE INDEX IF NOT EXISTS idx_docref_from ON document_references(from_document_id);\\nCREATE UNIQUE INDEX IF NOT EXISTS idx_docref_unique\\n ON document_references(from_document_id, field_name, ordinal);\\n\\n-- Document facets: indexed rows for multi-valued scalar fields (e.g. tags arrays).\\nCREATE TABLE IF NOT EXISTS document_facets (\\n id TEXT PRIMARY KEY,\\n tenant_id TEXT NOT NULL,\\n document_id TEXT NOT NULL REFERENCES documents(id) ON DELETE CASCADE,\\n root_id TEXT NOT NULL,\\n type_id TEXT NOT NULL,\\n field_name TEXT NOT NULL,\\n ordinal INTEGER NOT NULL DEFAULT 0,\\n value_text TEXT,\\n value_number REAL,\\n created_at INTEGER NOT NULL DEFAULT (unixepoch())\\n);\\n\\nCREATE INDEX IF NOT EXISTS idx_facets_lookup ON document_facets(tenant_id, type_id, field_name, value_text);\\nCREATE INDEX IF NOT EXISTS idx_facets_doc ON document_facets(document_id);\\nCREATE UNIQUE INDEX IF NOT EXISTS idx_facets_unique\\n ON document_facets(document_id, field_name, ordinal);\\n\\n-- Document permissions: per-document ACL overrides.\\nCREATE TABLE IF NOT EXISTS document_permissions (\\n id TEXT PRIMARY KEY,\\n tenant_id TEXT NOT NULL,\\n root_id TEXT NOT NULL,\\n principal_type TEXT NOT NULL CHECK (principal_type IN ('user', 'role', 'group', 'public', 'token')),\\n principal_id TEXT NOT NULL,\\n permission TEXT NOT NULL CHECK (permission IN ('read', 'create', 'update', 'delete', 'publish', 'manage')),\\n effect TEXT NOT NULL DEFAULT 'allow' CHECK (effect IN ('allow', 'deny')),\\n inherited INTEGER NOT NULL DEFAULT 0,\\n created_at INTEGER NOT NULL DEFAULT (unixepoch()),\\n created_by TEXT\\n);\\n\\nCREATE INDEX IF NOT EXISTS idx_document_permissions_root ON document_permissions(tenant_id, root_id);\\nCREATE INDEX IF NOT EXISTS idx_document_permissions_principal\\n ON document_permissions(tenant_id, principal_type, principal_id, permission);\\nCREATE UNIQUE INDEX IF NOT EXISTS idx_document_permissions_unique\\n ON document_permissions(root_id, principal_type, principal_id, permission);\\n\"\n }\n]\n\n// Map for quick lookup by ID\nexport const migrationsByIdMap = new Map<string, BundledMigration>(\n bundledMigrations.map(m => [m.id, m])\n)\n\n// Get migration SQL by ID\nexport function getMigrationSQLById(id: string): string | null {\n return migrationsByIdMap.get(id)?.sql ?? null\n}\n\n// Get all migration info (without SQL for lighter payloads)\nexport function getMigrationList(): Array<Omit<BundledMigration, 'sql'>> {\n return bundledMigrations.map(({ sql, ...rest }) => rest)\n}\n","import { D1Database } from '@cloudflare/workers-types'\nimport type { QueryableField } from '../schemas/document'\n\n// Identifiers and JSON paths are interpolated into DDL (they cannot be bound), so\n// they are format-guarded here. Source is trusted code config, not user input —\n// this is defense-in-depth, mirroring document-repository.ts.\nconst SAFE_IDENTIFIER = /^[a-z_][a-z0-9_]*$/\n\n/** Map a queryable field's logical type to a SQLite column affinity. */\nfunction affinity(type?: QueryableField['type']): 'TEXT' | 'INTEGER' | 'REAL' {\n if (type === 'number') return 'REAL'\n if (type === 'integer' || type === 'boolean' || type === 'date') return 'INTEGER'\n return 'TEXT'\n}\n\nconst slug = (s: string) => s.toLowerCase().replace(/[^a-z0-9]+/g, '_').replace(/^_+|_+$/g, '')\n\n/**\n * Authoritative generated-column name for a scalar field. An explicit `column`\n * always wins (back-compat with every existing type definition); otherwise it is\n * derived deterministically from the type id + field name. The repository reads\n * the same `column`/derivation when building filter SQL, so the two never drift.\n */\nexport function resolveColumn(typeId: string, f: QueryableField): string {\n if (f.column) return f.column\n const name = `q_${slug(typeId)}_${slug(f.name)}`\n return name.length <= 60 ? name : `q_${slug(typeId).slice(0, 20)}_${slug(f.name).slice(0, 20)}`\n}\n\n/**\n * Idempotently ensure the `documents` table has a VIRTUAL generated column and a\n * filter/sort index for each of a type's scalar queryable fields. Safe to call on\n * every registration and every bootstrap: existing columns/indexes are skipped,\n * and a concurrent add surfaces as a swallowed \"duplicate column name\".\n *\n * Facet and reference fields need no DDL (generic document_facets /\n * document_references tables), so they are ignored here.\n *\n * Returns the columns it actually created (empty when all already existed).\n */\nexport async function ensureScalarSchema(\n db: D1Database,\n typeId: string,\n fields: QueryableField[],\n): Promise<string[]> {\n const scalars = fields.filter((f) => f.kind === 'scalar')\n if (scalars.length === 0) return []\n\n // pragma_table_info does NOT list VIRTUAL generated columns — use table_xinfo, which does.\n let existing = new Set<string>()\n try {\n const info = await db.prepare(\"SELECT name FROM pragma_table_xinfo('documents')\").all()\n existing = new Set((info?.results ?? []).map((r: any) => r.name))\n } catch {\n // table_xinfo unavailable — fall back to attempting every ALTER (duplicate errors swallowed).\n }\n\n const added: string[] = []\n for (const f of scalars) {\n const col = resolveColumn(typeId, f)\n if (!SAFE_IDENTIFIER.test(col)) {\n console.error(`[scalar-schema] unsafe column name '${col}' for ${typeId}.${f.name} — skipped`)\n continue\n }\n const path = f.path ?? `$.${f.name}`\n if (path.includes(\"'\")) {\n console.error(`[scalar-schema] unsafe json path for ${col} (${typeId}.${f.name}) — skipped`)\n continue\n }\n\n if (!existing.has(col)) {\n try {\n await db\n .prepare(`ALTER TABLE documents ADD COLUMN ${col} ${affinity(f.type)} AS (json_extract(data, '${path}')) VIRTUAL`)\n .run()\n added.push(col)\n console.log(`[scalar-schema] added documents.${col} for type '${typeId}'`)\n } catch (error) {\n const msg = error instanceof Error ? error.message : String(error)\n if (!msg.includes('duplicate column name')) {\n console.error(`[scalar-schema] failed to add documents.${col}:`, msg)\n continue\n }\n }\n }\n\n // One general index per column: the leading (tenant_id, type_id, col) prefix\n // serves every equality filter the repository builds; the trailing\n // (updated_at DESC, id DESC) matches the default keyset sort/cursor. Non-partial\n // so a single index covers both draft and published lists.\n try {\n await db\n .prepare(`CREATE INDEX IF NOT EXISTS idx_${col} ON documents(tenant_id, type_id, ${col}, updated_at DESC, id DESC)`)\n .run()\n } catch (error) {\n console.error(`[scalar-schema] failed to create idx_${col}:`, error instanceof Error ? error.message : String(error))\n }\n }\n return added\n}\n","import { D1Database } from '@cloudflare/workers-types'\nimport { bundledMigrations } from '../db/migrations-bundle'\nimport { ensureScalarSchema } from './document-scalar-schema'\nimport type { QueryableField } from '../schemas/document'\n\nexport interface Migration {\n id: string\n name: string\n filename: string\n description?: string\n applied: boolean\n appliedAt?: string\n size?: number\n}\n\nexport interface MigrationStatus {\n totalMigrations: number\n appliedMigrations: number\n pendingMigrations: number\n lastApplied?: string\n migrations: Migration[]\n}\n\nexport class MigrationService {\n constructor(private db: D1Database) {}\n\n /**\n * Cloudflare D1 owns migration bookkeeping through `d1_migrations`.\n * SonicJS intentionally does not create its own tracking table.\n */\n async initializeMigrationsTable(): Promise<void> {\n // Kept as a no-op for compatibility with older callers.\n }\n\n /**\n * Get all available migrations from the bundled migrations\n */\n async getAvailableMigrations(): Promise<Migration[]> {\n const migrations: Migration[] = []\n const appliedMigrations = await this.getD1AppliedMigrations()\n await this.ensureSchemaCompatibility()\n\n // Use bundled migrations as the source of truth\n for (const bundled of bundledMigrations) {\n const applied = appliedMigrations.has(bundled.id)\n const appliedData = appliedMigrations.get(bundled.id)\n\n migrations.push({\n id: bundled.id,\n name: bundled.name,\n filename: bundled.filename,\n description: bundled.description,\n applied,\n appliedAt: applied ? appliedData?.applied_at : undefined,\n size: bundled.sql.length\n })\n }\n\n return migrations\n }\n\n /**\n * Read Wrangler/D1's canonical migration table. If the table is absent, no\n * migrations have been applied by the supported migration runner yet.\n */\n private async getD1AppliedMigrations(): Promise<Map<string, any>> {\n try {\n const appliedResult = await this.db.prepare(\n 'SELECT name, applied_at FROM d1_migrations ORDER BY applied_at ASC'\n ).all()\n\n return new Map(\n (appliedResult.results ?? [])\n .map((row: any) => {\n const filename = String(row.name ?? '')\n const id = filename.match(/^(\\d+)/)?.[1]\n if (!id) return null\n return [id, {\n id,\n name: filename,\n filename,\n applied_at: row.applied_at\n }]\n })\n .filter((entry): entry is [string, any] => entry !== null)\n )\n } catch (error) {\n return new Map()\n }\n }\n\n /**\n * Run idempotent compatibility repairs that are safe outside migration state.\n */\n async ensureSchemaCompatibility(): Promise<void> {\n if (await this.checkTablesExist(['documents'])) {\n await this.ensureDocumentGeneratedColumns()\n }\n }\n\n /**\n * Ensure the `documents` table exposes every queryable VIRTUAL generated column + index (D45).\n * Data-driven repair: reconciles from each active type's `queryable_fields` rather than a hardcoded\n * list, so it stays in sync with whatever types are registered. Generation of these columns is owned\n * by DocumentTypeRegistry.register() (via ensureScalarSchema); this pass is a bootstrap safety net for\n * a DB that has document_types rows but lost columns (e.g. table rebuilt). Idempotent.\n */\n private async ensureDocumentGeneratedColumns(): Promise<void> {\n if (!(await this.checkTablesExist(['document_types']))) return\n const rows = await this.db\n .prepare('SELECT id, queryable_fields FROM document_types WHERE is_active = 1')\n .all<{ id: string; queryable_fields: string }>()\n for (const row of rows.results ?? []) {\n let fields: QueryableField[]\n try {\n fields = JSON.parse(row.queryable_fields)\n } catch {\n continue\n }\n await ensureScalarSchema(this.db, row.id, fields)\n }\n }\n\n /**\n * Check if specific tables exist in the database\n */\n private async checkTablesExist(tableNames: string[]): Promise<boolean> {\n try {\n for (const tableName of tableNames) {\n const result = await this.db.prepare(\n `SELECT name FROM sqlite_master WHERE type='table' AND name=?`\n ).bind(tableName).first()\n\n if (!result) {\n return false\n }\n }\n return true\n } catch (error) {\n return false\n }\n }\n\n /**\n * Check if a specific column exists in a table\n */\n private async checkColumnExists(tableName: string, columnName: string): Promise<boolean> {\n try {\n const result = await this.db.prepare(\n `SELECT * FROM pragma_table_info(?) WHERE name = ?`\n ).bind(tableName, columnName).first()\n\n return !!result\n } catch (error) {\n return false\n }\n }\n\n /**\n * Get migration status summary\n */\n async getMigrationStatus(): Promise<MigrationStatus> {\n const migrations = await this.getAvailableMigrations()\n const appliedMigrations = migrations.filter(m => m.applied)\n const pendingMigrations = migrations.filter(m => !m.applied)\n\n const lastApplied = appliedMigrations.length > 0\n ? appliedMigrations[appliedMigrations.length - 1]?.appliedAt\n : undefined\n\n return {\n totalMigrations: migrations.length,\n appliedMigrations: appliedMigrations.length,\n pendingMigrations: pendingMigrations.length,\n lastApplied,\n migrations\n }\n }\n\n /**\n * D1 migration state is managed by Wrangler.\n */\n async markMigrationApplied(migrationId: string, name: string, filename: string): Promise<void> {\n void migrationId\n void name\n void filename\n }\n\n /**\n * D1 migration state is managed by Wrangler.\n */\n async removeMigrationApplied(migrationId: string): Promise<void> {\n void migrationId\n }\n\n /**\n * Check if a specific migration has been applied\n */\n async isMigrationApplied(migrationId: string): Promise<boolean> {\n const appliedMigrations = await this.getD1AppliedMigrations()\n return appliedMigrations.has(migrationId)\n }\n\n /**\n * Get the last applied migration\n */\n async getLastAppliedMigration(): Promise<Migration | null> {\n const migrations = await this.getAvailableMigrations()\n return migrations.filter(m => m.applied).at(-1) ?? null\n }\n\n /**\n * Run pending migrations\n */\n async runPendingMigrations(): Promise<{ success: boolean; message: string; applied: string[]; errors: string[] }> {\n return {\n success: false,\n message: 'Migrations are managed by Cloudflare D1. Run `wrangler d1 migrations apply DB --local` or `wrangler d1 migrations apply DB --remote`.',\n applied: [],\n errors: []\n }\n }\n\n /**\n * Validate database schema\n */\n async validateSchema(): Promise<{ valid: boolean; issues: string[] }> {\n const issues: string[] = []\n\n // Basic table existence checks\n const requiredTables = [\n 'users', 'documents', 'document_types'\n ]\n\n for (const table of requiredTables) {\n try {\n await this.db.prepare(`SELECT COUNT(*) FROM ${table} LIMIT 1`).first()\n } catch (error) {\n issues.push(`Missing table: ${table}`)\n }\n }\n\n return {\n valid: issues.length === 0,\n issues\n }\n }\n}\n"]}
1
+ {"version":3,"sources":["../src/db/migrations-bundle.ts","../src/services/document-scalar-schema.ts","../src/services/migrations.ts"],"names":[],"mappings":"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","file":"chunk-GP2K2K3G.cjs","sourcesContent":["/**\n * AUTO-GENERATED FILE - DO NOT EDIT\n * Generated by: scripts/generate-migrations.ts\n * Generated at: 2026-06-19T00:32:58.570Z\n *\n * This file contains all migration SQL bundled for use in Cloudflare Workers\n * where filesystem access is not available at runtime.\n */\n\nexport interface BundledMigration {\n id: string\n name: string\n filename: string\n description: string\n sql: string\n}\n\nexport const bundledMigrations: BundledMigration[] = [\n {\n id: '0001',\n name: 'Core',\n filename: '0001_core.sql',\n description: 'Migration 0001: Core',\n sql: \"-- Migration 0001: Auth tables\\n-- auth_user, auth_session, auth_account, auth_verification + BA plugin tables + RBAC + auth support.\\n-- Only auth_* prefixed tables live here. All content lives in document_* tables (0002_documents.sql).\\n\\n-- ── auth_user ────────────────────────────────────────────────────────────────\\n-- BA user model + SonicJS domain columns as BA additionalFields.\\nCREATE TABLE IF NOT EXISTS auth_user (\\n id TEXT PRIMARY KEY,\\n name TEXT,\\n email TEXT NOT NULL UNIQUE,\\n email_verified INTEGER NOT NULL DEFAULT 0,\\n image TEXT,\\n created_at INTEGER NOT NULL,\\n updated_at INTEGER NOT NULL,\\n -- SonicJS additionalFields\\n first_name TEXT NOT NULL,\\n last_name TEXT NOT NULL,\\n role TEXT NOT NULL DEFAULT 'viewer',\\n -- Platform super-admin: bypasses the multi-tenant membership gate, uses global roles in every\\n -- tenant. Opt-in (default 0); intentionally NOT derived from the 'admin' role.\\n is_super_admin INTEGER NOT NULL DEFAULT 0,\\n avatar TEXT,\\n password_hash TEXT,\\n is_active INTEGER NOT NULL DEFAULT 1,\\n last_login_at INTEGER,\\n phone TEXT,\\n bio TEXT,\\n timezone TEXT DEFAULT 'UTC',\\n language TEXT DEFAULT 'en',\\n email_notifications INTEGER DEFAULT 1,\\n theme TEXT DEFAULT 'dark',\\n invitation_token TEXT,\\n invited_by TEXT,\\n invited_at INTEGER,\\n accepted_invitation_at INTEGER,\\n failed_login_count INTEGER NOT NULL DEFAULT 0,\\n locked_until INTEGER\\n);\\n\\nCREATE INDEX IF NOT EXISTS idx_auth_user_email ON auth_user(email);\\nCREATE INDEX IF NOT EXISTS idx_auth_user_role ON auth_user(role);\\nCREATE INDEX IF NOT EXISTS idx_auth_user_invitation_token ON auth_user(invitation_token);\\nCREATE INDEX IF NOT EXISTS idx_auth_user_locked_until ON auth_user(locked_until) WHERE locked_until IS NOT NULL;\\n\\n-- ── auth_session ─────────────────────────────────────────────────────────────\\nCREATE TABLE IF NOT EXISTS auth_session (\\n id TEXT PRIMARY KEY,\\n user_id TEXT NOT NULL REFERENCES auth_user(id) ON DELETE CASCADE,\\n token TEXT NOT NULL UNIQUE,\\n expires_at INTEGER NOT NULL,\\n ip_address TEXT,\\n user_agent TEXT,\\n created_at INTEGER NOT NULL,\\n updated_at INTEGER NOT NULL\\n);\\nCREATE INDEX IF NOT EXISTS idx_auth_session_user_id ON auth_session(user_id);\\nCREATE INDEX IF NOT EXISTS idx_auth_session_token ON auth_session(token);\\nCREATE INDEX IF NOT EXISTS idx_auth_session_expires_at ON auth_session(expires_at);\\n\\n-- ── auth_account ─────────────────────────────────────────────────────────────\\nCREATE TABLE IF NOT EXISTS auth_account (\\n id TEXT PRIMARY KEY,\\n user_id TEXT NOT NULL REFERENCES auth_user(id) ON DELETE CASCADE,\\n account_id TEXT NOT NULL,\\n provider_id TEXT NOT NULL,\\n access_token TEXT,\\n refresh_token TEXT,\\n access_token_expires_at INTEGER,\\n refresh_token_expires_at INTEGER,\\n scope TEXT,\\n id_token TEXT,\\n password TEXT,\\n created_at INTEGER NOT NULL,\\n updated_at INTEGER NOT NULL\\n);\\nCREATE INDEX IF NOT EXISTS idx_auth_account_user_id ON auth_account(user_id);\\nCREATE INDEX IF NOT EXISTS idx_auth_account_provider ON auth_account(provider_id, account_id);\\n\\n-- ── auth_verification ────────────────────────────────────────────────────────\\n-- Covers email verification, password reset, magic-link tokens, OTP codes.\\nCREATE TABLE IF NOT EXISTS auth_verification (\\n id TEXT PRIMARY KEY,\\n identifier TEXT NOT NULL,\\n value TEXT NOT NULL,\\n expires_at INTEGER NOT NULL,\\n created_at INTEGER NOT NULL,\\n updated_at INTEGER NOT NULL\\n);\\nCREATE INDEX IF NOT EXISTS idx_auth_verification_identifier ON auth_verification(identifier);\\n\\n-- ── BA plugin tables ──────────────────────────────────────────────────────────\\n\\nCREATE TABLE IF NOT EXISTS auth_two_factor (\\n id TEXT PRIMARY KEY,\\n secret TEXT NOT NULL,\\n backup_codes TEXT NOT NULL,\\n user_id TEXT NOT NULL REFERENCES auth_user(id) ON DELETE CASCADE,\\n verified INTEGER NOT NULL DEFAULT 1,\\n created_at INTEGER NOT NULL,\\n updated_at INTEGER NOT NULL\\n);\\nCREATE INDEX IF NOT EXISTS idx_auth_two_factor_user_id ON auth_two_factor(user_id);\\n\\nCREATE TABLE IF NOT EXISTS auth_tenant (\\n id TEXT PRIMARY KEY,\\n name TEXT NOT NULL,\\n slug TEXT NOT NULL UNIQUE,\\n logo TEXT,\\n metadata TEXT,\\n -- SonicJS tenant-resolution fields (BA organization additionalFields):\\n status TEXT NOT NULL DEFAULT 'active',\\n domain TEXT,\\n notes TEXT NOT NULL DEFAULT '',\\n created_at INTEGER NOT NULL,\\n updated_at INTEGER NOT NULL\\n);\\nCREATE INDEX IF NOT EXISTS idx_auth_tenant_domain ON auth_tenant(domain);\\n\\nCREATE TABLE IF NOT EXISTS auth_tenant_member (\\n id TEXT PRIMARY KEY,\\n tenant_id TEXT NOT NULL REFERENCES auth_tenant(id) ON DELETE CASCADE,\\n user_id TEXT NOT NULL REFERENCES auth_user(id) ON DELETE CASCADE,\\n role TEXT NOT NULL DEFAULT 'member',\\n email TEXT,\\n created_at INTEGER NOT NULL,\\n updated_at INTEGER NOT NULL,\\n UNIQUE(tenant_id, user_id)\\n);\\nCREATE INDEX IF NOT EXISTS idx_auth_tenant_member_tenant ON auth_tenant_member(tenant_id);\\nCREATE INDEX IF NOT EXISTS idx_auth_tenant_member_user ON auth_tenant_member(user_id);\\n\\nCREATE TABLE IF NOT EXISTS auth_tenant_invitation (\\n id TEXT PRIMARY KEY,\\n tenant_id TEXT NOT NULL REFERENCES auth_tenant(id) ON DELETE CASCADE,\\n email TEXT NOT NULL,\\n role TEXT NOT NULL DEFAULT 'member',\\n status TEXT NOT NULL DEFAULT 'pending',\\n expires_at INTEGER NOT NULL,\\n inviter_id TEXT REFERENCES auth_user(id) ON DELETE SET NULL,\\n created_at INTEGER NOT NULL,\\n updated_at INTEGER NOT NULL\\n);\\nCREATE INDEX IF NOT EXISTS idx_auth_tenant_invitation_tenant ON auth_tenant_invitation(tenant_id);\\nCREATE INDEX IF NOT EXISTS idx_auth_tenant_invitation_email ON auth_tenant_invitation(email);\\n\\nCREATE TABLE IF NOT EXISTS auth_tenant_team (\\n id TEXT PRIMARY KEY,\\n name TEXT NOT NULL,\\n tenant_id TEXT NOT NULL REFERENCES auth_tenant(id) ON DELETE CASCADE,\\n created_at INTEGER NOT NULL,\\n updated_at INTEGER NOT NULL\\n);\\n\\n-- ── RBAC ─────────────────────────────────────────────────────────────────────\\n-- RBAC roles, verbs, and user-role assignments are document-backed (is_auth doc\\n-- types rbac_role / rbac_verb / rbac_user_roles — see services/rbac.ts). The\\n-- system roles/verbs/grants are seeded at bootstrap by RbacService.ensureSystemRbacSeed().\\n-- No auth_rbac_* tables.\\n\\n-- ── Auth support tables ───────────────────────────────────────────────────────\\nCREATE TABLE IF NOT EXISTS auth_password_history (\\n id TEXT PRIMARY KEY,\\n user_id TEXT NOT NULL REFERENCES auth_user(id) ON DELETE CASCADE,\\n password_hash TEXT NOT NULL,\\n created_at INTEGER NOT NULL\\n);\\nCREATE INDEX IF NOT EXISTS idx_auth_password_history_user_id ON auth_password_history(user_id);\\n\\nCREATE TABLE IF NOT EXISTS auth_api_tokens (\\n id TEXT PRIMARY KEY,\\n name TEXT NOT NULL,\\n token TEXT NOT NULL UNIQUE,\\n user_id TEXT NOT NULL REFERENCES auth_user(id),\\n permissions TEXT NOT NULL,\\n expires_at INTEGER,\\n last_used_at INTEGER,\\n created_at INTEGER NOT NULL\\n);\\nCREATE INDEX IF NOT EXISTS idx_auth_api_tokens_user ON auth_api_tokens(user_id);\\nCREATE INDEX IF NOT EXISTS idx_auth_api_tokens_token ON auth_api_tokens(token);\\n\\n-- User profiles moved to the document model: a `user_profile` document (is_auth type),\\n-- one per user, addressed by slug = userId. See services/document-types-seed.ts and\\n-- plugins/core-plugins/user-profiles/user-profile-document.ts. No auth_user_profiles table.\\n\"\n },\n {\n id: '0002',\n name: 'Documents',\n filename: '0002_documents.sql',\n description: 'Migration 0002: Documents',\n sql: \"-- Migration 0002: Document Schema (v3 greenfield)\\n-- Contains only the new document data model tables, generated columns, and indexes.\\n\\n-- Document type registry\\nCREATE TABLE IF NOT EXISTS document_types (\\n id TEXT PRIMARY KEY,\\n name TEXT NOT NULL UNIQUE,\\n display_name TEXT NOT NULL,\\n description TEXT,\\n schema TEXT NOT NULL DEFAULT '{}',\\n queryable_fields TEXT NOT NULL DEFAULT '[]',\\n settings TEXT NOT NULL DEFAULT '{}',\\n plugin_id TEXT,\\n source TEXT NOT NULL DEFAULT 'code' CHECK (source IN ('code', 'plugin', 'system')),\\n schema_version INTEGER NOT NULL DEFAULT 1,\\n is_system INTEGER NOT NULL DEFAULT 0,\\n is_active INTEGER NOT NULL DEFAULT 1,\\n is_auth INTEGER NOT NULL DEFAULT 0,\\n created_at INTEGER NOT NULL DEFAULT (unixepoch()),\\n updated_at INTEGER NOT NULL DEFAULT (unixepoch())\\n);\\n\\nCREATE INDEX IF NOT EXISTS idx_document_types_plugin ON document_types(plugin_id);\\nCREATE INDEX IF NOT EXISTS idx_document_types_active ON document_types(is_active);\\n\\n-- Documents: canonical document rows and historical versions.\\nCREATE TABLE IF NOT EXISTS documents (\\n id TEXT PRIMARY KEY,\\n root_id TEXT NOT NULL,\\n type_id TEXT NOT NULL REFERENCES document_types(id),\\n type_version INTEGER NOT NULL DEFAULT 1,\\n\\n version_of_id TEXT REFERENCES documents(id),\\n version_number INTEGER NOT NULL DEFAULT 1,\\n\\n is_current_draft INTEGER NOT NULL DEFAULT 1,\\n is_published INTEGER NOT NULL DEFAULT 0,\\n status TEXT NOT NULL DEFAULT 'draft' CHECK (status IN ('draft', 'published', 'archived')),\\n\\n parent_root_id TEXT NOT NULL DEFAULT '',\\n slug TEXT,\\n path TEXT,\\n title TEXT,\\n zone TEXT,\\n sort_order INTEGER NOT NULL DEFAULT 0,\\n visible INTEGER NOT NULL DEFAULT 1,\\n\\n published_at INTEGER,\\n scheduled_at INTEGER,\\n expires_at INTEGER,\\n deleted_at INTEGER,\\n\\n tenant_id TEXT NOT NULL DEFAULT 'default',\\n locale TEXT NOT NULL DEFAULT 'default',\\n translation_group_id TEXT NOT NULL DEFAULT '',\\n\\n data TEXT NOT NULL DEFAULT '{}',\\n metadata TEXT NOT NULL DEFAULT '{}',\\n\\n owner_id TEXT,\\n created_by TEXT,\\n updated_by TEXT,\\n created_at INTEGER NOT NULL DEFAULT (unixepoch()),\\n updated_at INTEGER NOT NULL DEFAULT (unixepoch())\\n);\\n\\n-- Queryable scalar fields (VIRTUAL generated columns) and their q_* filter indexes\\n-- are AUTO-GENERATED at runtime from each document type's queryableFields config —\\n-- see DocumentTypeRegistry.register() -> ensureScalarSchema() (document-scalar-schema.ts).\\n-- Do not hand-add q_* columns/indexes here; declare the field in the type instead.\\n\\n-- Revision chain\\nCREATE INDEX IF NOT EXISTS idx_documents_root ON documents(root_id, version_number DESC);\\n\\n-- List / lifecycle\\nCREATE INDEX IF NOT EXISTS idx_documents_published ON documents(tenant_id, type_id, locale, is_published)\\n WHERE is_published = 1 AND deleted_at IS NULL;\\nCREATE INDEX IF NOT EXISTS idx_documents_drafts ON documents(tenant_id, type_id, status, is_current_draft)\\n WHERE is_current_draft = 1;\\nCREATE INDEX IF NOT EXISTS idx_documents_parent ON documents(tenant_id, parent_root_id, sort_order, is_published);\\nCREATE INDEX IF NOT EXISTS idx_documents_path ON documents(tenant_id, path);\\nCREATE INDEX IF NOT EXISTS idx_documents_translation ON documents(translation_group_id, locale);\\nCREATE INDEX IF NOT EXISTS idx_documents_deleted ON documents(deleted_at);\\nCREATE INDEX IF NOT EXISTS idx_documents_scheduled ON documents(scheduled_at) WHERE scheduled_at IS NOT NULL;\\nCREATE INDEX IF NOT EXISTS idx_documents_expires ON documents(expires_at) WHERE expires_at IS NOT NULL;\\n\\n-- Stable keyset/cursor pagination for published lists\\nCREATE INDEX IF NOT EXISTS idx_documents_published_cursor\\n ON documents(tenant_id, type_id, updated_at DESC, id DESC)\\n WHERE is_published = 1 AND deleted_at IS NULL;\\n\\n-- (q_* generated-column filter indexes are auto-created at runtime — see note above.)\\n\\n-- Partial unique indexes: the hard concurrency guarantees for draft/publish invariants.\\nCREATE UNIQUE INDEX IF NOT EXISTS idx_documents_one_current_draft\\n ON documents(root_id) WHERE is_current_draft = 1;\\nCREATE UNIQUE INDEX IF NOT EXISTS idx_documents_one_published\\n ON documents(root_id) WHERE is_published = 1;\\nCREATE UNIQUE INDEX IF NOT EXISTS idx_documents_unique_version\\n ON documents(root_id, version_number);\\nCREATE UNIQUE INDEX IF NOT EXISTS idx_documents_unique_slug\\n ON documents(tenant_id, locale, type_id, parent_root_id, slug)\\n WHERE is_current_draft = 1 AND deleted_at IS NULL AND slug IS NOT NULL;\\nCREATE UNIQUE INDEX IF NOT EXISTS idx_documents_one_translation_per_locale\\n ON documents(tenant_id, translation_group_id, locale)\\n WHERE is_current_draft = 1 AND translation_group_id <> '';\\n\\n-- Document references: typed document-to-document edges.\\nCREATE TABLE IF NOT EXISTS document_references (\\n id TEXT PRIMARY KEY,\\n tenant_id TEXT NOT NULL,\\n from_root_id TEXT NOT NULL,\\n from_document_id TEXT NOT NULL REFERENCES documents(id) ON DELETE CASCADE,\\n field_name TEXT NOT NULL,\\n ordinal INTEGER NOT NULL DEFAULT 0,\\n to_root_id TEXT NOT NULL,\\n ref_strength TEXT NOT NULL DEFAULT 'weak' CHECK (ref_strength IN ('strong', 'weak')),\\n created_at INTEGER NOT NULL DEFAULT (unixepoch())\\n);\\n\\nCREATE INDEX IF NOT EXISTS idx_docref_to ON document_references(tenant_id, to_root_id);\\nCREATE INDEX IF NOT EXISTS idx_docref_from ON document_references(from_document_id);\\nCREATE UNIQUE INDEX IF NOT EXISTS idx_docref_unique\\n ON document_references(from_document_id, field_name, ordinal);\\n\\n-- Document facets: indexed rows for multi-valued scalar fields (e.g. tags arrays).\\nCREATE TABLE IF NOT EXISTS document_facets (\\n id TEXT PRIMARY KEY,\\n tenant_id TEXT NOT NULL,\\n document_id TEXT NOT NULL REFERENCES documents(id) ON DELETE CASCADE,\\n root_id TEXT NOT NULL,\\n type_id TEXT NOT NULL,\\n field_name TEXT NOT NULL,\\n ordinal INTEGER NOT NULL DEFAULT 0,\\n value_text TEXT,\\n value_number REAL,\\n created_at INTEGER NOT NULL DEFAULT (unixepoch())\\n);\\n\\nCREATE INDEX IF NOT EXISTS idx_facets_lookup ON document_facets(tenant_id, type_id, field_name, value_text);\\nCREATE INDEX IF NOT EXISTS idx_facets_doc ON document_facets(document_id);\\nCREATE UNIQUE INDEX IF NOT EXISTS idx_facets_unique\\n ON document_facets(document_id, field_name, ordinal);\\n\\n-- Document permissions: per-document ACL overrides.\\nCREATE TABLE IF NOT EXISTS document_permissions (\\n id TEXT PRIMARY KEY,\\n tenant_id TEXT NOT NULL,\\n root_id TEXT NOT NULL,\\n principal_type TEXT NOT NULL CHECK (principal_type IN ('user', 'role', 'group', 'public', 'token')),\\n principal_id TEXT NOT NULL,\\n permission TEXT NOT NULL CHECK (permission IN ('read', 'create', 'update', 'delete', 'publish', 'manage')),\\n effect TEXT NOT NULL DEFAULT 'allow' CHECK (effect IN ('allow', 'deny')),\\n inherited INTEGER NOT NULL DEFAULT 0,\\n created_at INTEGER NOT NULL DEFAULT (unixepoch()),\\n created_by TEXT\\n);\\n\\nCREATE INDEX IF NOT EXISTS idx_document_permissions_root ON document_permissions(tenant_id, root_id);\\nCREATE INDEX IF NOT EXISTS idx_document_permissions_principal\\n ON document_permissions(tenant_id, principal_type, principal_id, permission);\\nCREATE UNIQUE INDEX IF NOT EXISTS idx_document_permissions_unique\\n ON document_permissions(root_id, principal_type, principal_id, permission);\\n\"\n }\n]\n\n// Map for quick lookup by ID\nexport const migrationsByIdMap = new Map<string, BundledMigration>(\n bundledMigrations.map(m => [m.id, m])\n)\n\n// Get migration SQL by ID\nexport function getMigrationSQLById(id: string): string | null {\n return migrationsByIdMap.get(id)?.sql ?? null\n}\n\n// Get all migration info (without SQL for lighter payloads)\nexport function getMigrationList(): Array<Omit<BundledMigration, 'sql'>> {\n return bundledMigrations.map(({ sql, ...rest }) => rest)\n}\n","import { D1Database } from '@cloudflare/workers-types'\nimport type { QueryableField } from '../schemas/document'\n\n// Identifiers and JSON paths are interpolated into DDL (they cannot be bound), so\n// they are format-guarded here. Source is trusted code config, not user input —\n// this is defense-in-depth, mirroring document-repository.ts.\nconst SAFE_IDENTIFIER = /^[a-z_][a-z0-9_]*$/\n\n/** Map a queryable field's logical type to a SQLite column affinity. */\nfunction affinity(type?: QueryableField['type']): 'TEXT' | 'INTEGER' | 'REAL' {\n if (type === 'number') return 'REAL'\n if (type === 'integer' || type === 'boolean' || type === 'date') return 'INTEGER'\n return 'TEXT'\n}\n\nconst slug = (s: string) => s.toLowerCase().replace(/[^a-z0-9]+/g, '_').replace(/^_+|_+$/g, '')\n\n/**\n * Authoritative generated-column name for a scalar field. An explicit `column`\n * always wins (back-compat with every existing type definition); otherwise it is\n * derived deterministically from the type id + field name. The repository reads\n * the same `column`/derivation when building filter SQL, so the two never drift.\n */\nexport function resolveColumn(typeId: string, f: QueryableField): string {\n if (f.column) return f.column\n const name = `q_${slug(typeId)}_${slug(f.name)}`\n return name.length <= 60 ? name : `q_${slug(typeId).slice(0, 20)}_${slug(f.name).slice(0, 20)}`\n}\n\n/**\n * Idempotently ensure the `documents` table has a VIRTUAL generated column and a\n * filter/sort index for each of a type's scalar queryable fields. Safe to call on\n * every registration and every bootstrap: existing columns/indexes are skipped,\n * and a concurrent add surfaces as a swallowed \"duplicate column name\".\n *\n * Facet and reference fields need no DDL (generic document_facets /\n * document_references tables), so they are ignored here.\n *\n * Returns the columns it actually created (empty when all already existed).\n */\nexport async function ensureScalarSchema(\n db: D1Database,\n typeId: string,\n fields: QueryableField[],\n): Promise<string[]> {\n const scalars = fields.filter((f) => f.kind === 'scalar')\n if (scalars.length === 0) return []\n\n // pragma_table_info does NOT list VIRTUAL generated columns — use table_xinfo, which does.\n let existing = new Set<string>()\n try {\n const info = await db.prepare(\"SELECT name FROM pragma_table_xinfo('documents')\").all()\n existing = new Set((info?.results ?? []).map((r: any) => r.name))\n } catch {\n // table_xinfo unavailable — fall back to attempting every ALTER (duplicate errors swallowed).\n }\n\n const added: string[] = []\n for (const f of scalars) {\n const col = resolveColumn(typeId, f)\n if (!SAFE_IDENTIFIER.test(col)) {\n console.error(`[scalar-schema] unsafe column name '${col}' for ${typeId}.${f.name} — skipped`)\n continue\n }\n const path = f.path ?? `$.${f.name}`\n if (path.includes(\"'\")) {\n console.error(`[scalar-schema] unsafe json path for ${col} (${typeId}.${f.name}) — skipped`)\n continue\n }\n\n if (!existing.has(col)) {\n try {\n await db\n .prepare(`ALTER TABLE documents ADD COLUMN ${col} ${affinity(f.type)} AS (json_extract(data, '${path}')) VIRTUAL`)\n .run()\n added.push(col)\n console.log(`[scalar-schema] added documents.${col} for type '${typeId}'`)\n } catch (error) {\n const msg = error instanceof Error ? error.message : String(error)\n if (!msg.includes('duplicate column name')) {\n console.error(`[scalar-schema] failed to add documents.${col}:`, msg)\n continue\n }\n }\n }\n\n // One general index per column: the leading (tenant_id, type_id, col) prefix\n // serves every equality filter the repository builds; the trailing\n // (updated_at DESC, id DESC) matches the default keyset sort/cursor. Non-partial\n // so a single index covers both draft and published lists.\n try {\n await db\n .prepare(`CREATE INDEX IF NOT EXISTS idx_${col} ON documents(tenant_id, type_id, ${col}, updated_at DESC, id DESC)`)\n .run()\n } catch (error) {\n console.error(`[scalar-schema] failed to create idx_${col}:`, error instanceof Error ? error.message : String(error))\n }\n }\n return added\n}\n","import { D1Database } from '@cloudflare/workers-types'\nimport { bundledMigrations } from '../db/migrations-bundle'\nimport { ensureScalarSchema } from './document-scalar-schema'\nimport type { QueryableField } from '../schemas/document'\n\nexport interface Migration {\n id: string\n name: string\n filename: string\n description?: string\n applied: boolean\n appliedAt?: string\n size?: number\n}\n\nexport interface MigrationStatus {\n totalMigrations: number\n appliedMigrations: number\n pendingMigrations: number\n lastApplied?: string\n migrations: Migration[]\n}\n\nexport class MigrationService {\n constructor(private db: D1Database) {}\n\n /**\n * Cloudflare D1 owns migration bookkeeping through `d1_migrations`.\n * SonicJS intentionally does not create its own tracking table.\n */\n async initializeMigrationsTable(): Promise<void> {\n // Kept as a no-op for compatibility with older callers.\n }\n\n /**\n * Get all available migrations from the bundled migrations\n */\n async getAvailableMigrations(): Promise<Migration[]> {\n const migrations: Migration[] = []\n const appliedMigrations = await this.getD1AppliedMigrations()\n await this.ensureSchemaCompatibility()\n\n // Use bundled migrations as the source of truth\n for (const bundled of bundledMigrations) {\n const applied = appliedMigrations.has(bundled.id)\n const appliedData = appliedMigrations.get(bundled.id)\n\n migrations.push({\n id: bundled.id,\n name: bundled.name,\n filename: bundled.filename,\n description: bundled.description,\n applied,\n appliedAt: applied ? appliedData?.applied_at : undefined,\n size: bundled.sql.length\n })\n }\n\n return migrations\n }\n\n /**\n * Read Wrangler/D1's canonical migration table. If the table is absent, no\n * migrations have been applied by the supported migration runner yet.\n */\n private async getD1AppliedMigrations(): Promise<Map<string, any>> {\n try {\n const appliedResult = await this.db.prepare(\n 'SELECT name, applied_at FROM d1_migrations ORDER BY applied_at ASC'\n ).all()\n\n return new Map(\n (appliedResult.results ?? [])\n .map((row: any) => {\n const filename = String(row.name ?? '')\n const id = filename.match(/^(\\d+)/)?.[1]\n if (!id) return null\n return [id, {\n id,\n name: filename,\n filename,\n applied_at: row.applied_at\n }]\n })\n .filter((entry): entry is [string, any] => entry !== null)\n )\n } catch (error) {\n return new Map()\n }\n }\n\n /**\n * Run idempotent compatibility repairs that are safe outside migration state.\n */\n async ensureSchemaCompatibility(): Promise<void> {\n if (await this.checkTablesExist(['documents'])) {\n await this.ensureDocumentGeneratedColumns()\n }\n }\n\n /**\n * Ensure the `documents` table exposes every queryable VIRTUAL generated column + index (D45).\n * Data-driven repair: reconciles from each active type's `queryable_fields` rather than a hardcoded\n * list, so it stays in sync with whatever types are registered. Generation of these columns is owned\n * by DocumentTypeRegistry.register() (via ensureScalarSchema); this pass is a bootstrap safety net for\n * a DB that has document_types rows but lost columns (e.g. table rebuilt). Idempotent.\n */\n private async ensureDocumentGeneratedColumns(): Promise<void> {\n if (!(await this.checkTablesExist(['document_types']))) return\n const rows = await this.db\n .prepare('SELECT id, queryable_fields FROM document_types WHERE is_active = 1')\n .all<{ id: string; queryable_fields: string }>()\n for (const row of rows.results ?? []) {\n let fields: QueryableField[]\n try {\n fields = JSON.parse(row.queryable_fields)\n } catch {\n continue\n }\n await ensureScalarSchema(this.db, row.id, fields)\n }\n }\n\n /**\n * Check if specific tables exist in the database\n */\n private async checkTablesExist(tableNames: string[]): Promise<boolean> {\n try {\n for (const tableName of tableNames) {\n const result = await this.db.prepare(\n `SELECT name FROM sqlite_master WHERE type='table' AND name=?`\n ).bind(tableName).first()\n\n if (!result) {\n return false\n }\n }\n return true\n } catch (error) {\n return false\n }\n }\n\n /**\n * Check if a specific column exists in a table\n */\n private async checkColumnExists(tableName: string, columnName: string): Promise<boolean> {\n try {\n const result = await this.db.prepare(\n `SELECT * FROM pragma_table_info(?) WHERE name = ?`\n ).bind(tableName, columnName).first()\n\n return !!result\n } catch (error) {\n return false\n }\n }\n\n /**\n * Get migration status summary\n */\n async getMigrationStatus(): Promise<MigrationStatus> {\n const migrations = await this.getAvailableMigrations()\n const appliedMigrations = migrations.filter(m => m.applied)\n const pendingMigrations = migrations.filter(m => !m.applied)\n\n const lastApplied = appliedMigrations.length > 0\n ? appliedMigrations[appliedMigrations.length - 1]?.appliedAt\n : undefined\n\n return {\n totalMigrations: migrations.length,\n appliedMigrations: appliedMigrations.length,\n pendingMigrations: pendingMigrations.length,\n lastApplied,\n migrations\n }\n }\n\n /**\n * D1 migration state is managed by Wrangler.\n */\n async markMigrationApplied(migrationId: string, name: string, filename: string): Promise<void> {\n void migrationId\n void name\n void filename\n }\n\n /**\n * D1 migration state is managed by Wrangler.\n */\n async removeMigrationApplied(migrationId: string): Promise<void> {\n void migrationId\n }\n\n /**\n * Check if a specific migration has been applied\n */\n async isMigrationApplied(migrationId: string): Promise<boolean> {\n const appliedMigrations = await this.getD1AppliedMigrations()\n return appliedMigrations.has(migrationId)\n }\n\n /**\n * Get the last applied migration\n */\n async getLastAppliedMigration(): Promise<Migration | null> {\n const migrations = await this.getAvailableMigrations()\n return migrations.filter(m => m.applied).at(-1) ?? null\n }\n\n /**\n * Run pending migrations\n */\n async runPendingMigrations(): Promise<{ success: boolean; message: string; applied: string[]; errors: string[] }> {\n return {\n success: false,\n message: 'Migrations are managed by Cloudflare D1. Run `wrangler d1 migrations apply DB --local` or `wrangler d1 migrations apply DB --remote`.',\n applied: [],\n errors: []\n }\n }\n\n /**\n * Validate database schema\n */\n async validateSchema(): Promise<{ valid: boolean; issues: string[] }> {\n const issues: string[] = []\n\n // Basic table existence checks\n const requiredTables = [\n 'users', 'documents', 'document_types'\n ]\n\n for (const table of requiredTables) {\n try {\n await this.db.prepare(`SELECT COUNT(*) FROM ${table} LIMIT 1`).first()\n } catch (error) {\n issues.push(`Missing table: ${table}`)\n }\n }\n\n return {\n valid: issues.length === 0,\n issues\n }\n }\n}\n"]}