@sonicjs-cms/core 3.0.0-beta.17 → 3.0.0-beta.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-C3KSTUEA.cjs → chunk-2DSRS3VA.cjs} +4 -2
- package/dist/chunk-2DSRS3VA.cjs.map +1 -0
- package/dist/{chunk-RQ6N3FTV.js → chunk-2XAQTPAE.js} +3 -2
- package/dist/chunk-2XAQTPAE.js.map +1 -0
- package/dist/{chunk-4BTBSXMR.cjs → chunk-36Z3AKX3.cjs} +3 -2
- package/dist/chunk-36Z3AKX3.cjs.map +1 -0
- package/dist/{chunk-PDC7DLOV.js → chunk-3TMTYJAJ.js} +7 -3
- package/dist/chunk-3TMTYJAJ.js.map +1 -0
- package/dist/{chunk-73KSHGOT.js → chunk-52DAVKDM.js} +3 -2
- package/dist/chunk-52DAVKDM.js.map +1 -0
- package/dist/{chunk-KXTGLSFY.js → chunk-64MTTVX2.js} +133 -67
- package/dist/chunk-64MTTVX2.js.map +1 -0
- package/dist/{chunk-3RNDJAZ5.cjs → chunk-EHTBXFTA.cjs} +4 -4
- package/dist/{chunk-3RNDJAZ5.cjs.map → chunk-EHTBXFTA.cjs.map} +1 -1
- package/dist/{chunk-KMCLNO4K.cjs → chunk-GMMCCIQO.cjs} +3 -3
- package/dist/{chunk-KMCLNO4K.cjs.map → chunk-GMMCCIQO.cjs.map} +1 -1
- package/dist/{chunk-OSMPF3PU.js → chunk-HUDG4GS6.js} +8 -7
- package/dist/chunk-HUDG4GS6.js.map +1 -0
- package/dist/{chunk-7YMMCIWY.cjs → chunk-IDMHRCZS.cjs} +3 -2
- package/dist/chunk-IDMHRCZS.cjs.map +1 -0
- package/dist/{chunk-NOBU34HS.cjs → chunk-JJ6HE4J4.cjs} +279 -273
- package/dist/chunk-JJ6HE4J4.cjs.map +1 -0
- package/dist/{chunk-JEQ7FLOD.cjs → chunk-KIGVVO6N.cjs} +16 -10
- package/dist/chunk-KIGVVO6N.cjs.map +1 -0
- package/dist/{chunk-YA4V4Y5U.js → chunk-L6IPQIWU.js} +4 -2
- package/dist/chunk-L6IPQIWU.js.map +1 -0
- package/dist/{chunk-CYDEM6SY.js → chunk-LGZHS6WW.js} +23 -17
- package/dist/chunk-LGZHS6WW.js.map +1 -0
- package/dist/{chunk-W36OZTJ5.cjs → chunk-LTZ5WYOK.cjs} +10 -6
- package/dist/chunk-LTZ5WYOK.cjs.map +1 -0
- package/dist/{chunk-KDRHGEUF.cjs → chunk-NZF3THNY.cjs} +3 -3
- package/dist/{chunk-KDRHGEUF.cjs.map → chunk-NZF3THNY.cjs.map} +1 -1
- package/dist/{chunk-RIFCRZYC.js → chunk-OJHJ4I34.js} +3 -3
- package/dist/{chunk-RIFCRZYC.js.map → chunk-OJHJ4I34.js.map} +1 -1
- package/dist/{chunk-Y34TG4TF.js → chunk-PMEV7CWP.js} +3 -3
- package/dist/{chunk-Y34TG4TF.js.map → chunk-PMEV7CWP.js.map} +1 -1
- package/dist/{chunk-IQZA2OY5.js → chunk-SOZUG52O.js} +3 -3
- package/dist/{chunk-IQZA2OY5.js.map → chunk-SOZUG52O.js.map} +1 -1
- package/dist/{chunk-ZHQP75SR.cjs → chunk-SYAUCKSD.cjs} +19 -18
- package/dist/chunk-SYAUCKSD.cjs.map +1 -0
- package/dist/{chunk-GCDZZNIN.js → chunk-UWUP2VMT.js} +16 -10
- package/dist/chunk-UWUP2VMT.js.map +1 -0
- package/dist/{chunk-MJZI3NHR.cjs → chunk-YS5S47VV.cjs} +139 -73
- package/dist/chunk-YS5S47VV.cjs.map +1 -0
- package/dist/config-6FB2BXMP.js +6 -0
- package/dist/{config-SM2UWSDN.js.map → config-6FB2BXMP.js.map} +1 -1
- package/dist/{config-YCRSU7ZP.cjs → config-OCH7S4WK.cjs} +5 -5
- package/dist/{config-YCRSU7ZP.cjs.map → config-OCH7S4WK.cjs.map} +1 -1
- package/dist/document-projection-4HG75S5S.js +4 -0
- package/dist/{document-projection-YYMC6I4U.js.map → document-projection-4HG75S5S.js.map} +1 -1
- package/dist/document-projection-JQHSTSFD.cjs +13 -0
- package/dist/{document-projection-TDWRJX3Z.cjs.map → document-projection-JQHSTSFD.cjs.map} +1 -1
- package/dist/index.cjs +327 -291
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +56 -20
- package/dist/index.js.map +1 -1
- package/dist/middleware.cjs +35 -35
- package/dist/middleware.js +6 -6
- package/dist/migrations-HUR2YY4Q.js +4 -0
- package/dist/{migrations-7G6QBDSM.js.map → migrations-HUR2YY4Q.js.map} +1 -1
- package/dist/migrations-RYH2MGG4.cjs +13 -0
- package/dist/{migrations-QIRBSVJJ.cjs.map → migrations-RYH2MGG4.cjs.map} +1 -1
- package/dist/plugins.cjs +40 -40
- package/dist/plugins.js +3 -3
- package/dist/rbac-7QZ4JZ2E.cjs +14 -0
- package/dist/{rbac-ESTOCVT2.cjs.map → rbac-7QZ4JZ2E.cjs.map} +1 -1
- package/dist/rbac-Y43IEQOP.js +5 -0
- package/dist/{rbac-4QLDXIDF.js.map → rbac-Y43IEQOP.js.map} +1 -1
- package/dist/routes.cjs +29 -29
- package/dist/routes.js +9 -9
- package/dist/services.cjs +30 -30
- package/dist/services.js +3 -3
- package/dist/utils.cjs +3 -3
- package/dist/utils.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-4BTBSXMR.cjs.map +0 -1
- package/dist/chunk-73KSHGOT.js.map +0 -1
- package/dist/chunk-7YMMCIWY.cjs.map +0 -1
- package/dist/chunk-C3KSTUEA.cjs.map +0 -1
- package/dist/chunk-CYDEM6SY.js.map +0 -1
- package/dist/chunk-GCDZZNIN.js.map +0 -1
- package/dist/chunk-JEQ7FLOD.cjs.map +0 -1
- package/dist/chunk-KXTGLSFY.js.map +0 -1
- package/dist/chunk-MJZI3NHR.cjs.map +0 -1
- package/dist/chunk-NOBU34HS.cjs.map +0 -1
- package/dist/chunk-OSMPF3PU.js.map +0 -1
- package/dist/chunk-PDC7DLOV.js.map +0 -1
- package/dist/chunk-RQ6N3FTV.js.map +0 -1
- package/dist/chunk-W36OZTJ5.cjs.map +0 -1
- package/dist/chunk-YA4V4Y5U.js.map +0 -1
- package/dist/chunk-ZHQP75SR.cjs.map +0 -1
- package/dist/config-SM2UWSDN.js +0 -6
- package/dist/document-projection-TDWRJX3Z.cjs +0 -13
- package/dist/document-projection-YYMC6I4U.js +0 -4
- package/dist/migrations-7G6QBDSM.js +0 -4
- package/dist/migrations-QIRBSVJJ.cjs +0 -13
- package/dist/rbac-4QLDXIDF.js +0 -5
- package/dist/rbac-ESTOCVT2.cjs +0 -14
|
@@ -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":[],"mappings":"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","file":"chunk-Y34TG4TF.js","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":[],"mappings":"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","file":"chunk-PMEV7CWP.js","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"]}
|
|
@@ -139,7 +139,7 @@ function getDefaultAuthOptions(env, requestBaseURL) {
|
|
|
139
139
|
},
|
|
140
140
|
after: async (user) => {
|
|
141
141
|
try {
|
|
142
|
-
const { RbacService } = await import('./rbac-
|
|
142
|
+
const { RbacService } = await import('./rbac-Y43IEQOP.js');
|
|
143
143
|
const rbac = new RbacService(env.DB);
|
|
144
144
|
const roleName = await rbac.countPortalAdmins(user.id) === 0 ? "admin" : "viewer";
|
|
145
145
|
await rbac.addUserRoleByName(user.id, roleName);
|
|
@@ -238,5 +238,5 @@ function createAuth(env, extendBetterAuth, requestBaseURL) {
|
|
|
238
238
|
}
|
|
239
239
|
|
|
240
240
|
export { createAuth, getDefaultAuthOptions };
|
|
241
|
-
//# sourceMappingURL=chunk-
|
|
242
|
-
//# sourceMappingURL=chunk-
|
|
241
|
+
//# sourceMappingURL=chunk-SOZUG52O.js.map
|
|
242
|
+
//# sourceMappingURL=chunk-SOZUG52O.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/auth/config.ts"],"names":["baHashPassword","baVerifyPassword"],"mappings":";;;;;;;;;;;AAeA,eAAe,kBAAA,CACb,EAAA,EACA,EAAA,EACA,OAAA,EACA,IAAA,EACe;AACf,EAAA,IAAI;AACF,IAAA,MAAM,MAAO,MAAM,EAAA,CAChB,OAAA,CAAQ,iDAAiD,EACzD,KAAA,EAAM;AACT,IAAA,IAAI,KAAK,QAAA,EAAU;AACjB,MAAA,MAAM,EAAE,QAAQ,SAAA,EAAW,QAAA,KAAa,IAAA,CAAK,KAAA,CAAM,IAAI,QAAQ,CAAA;AAG/D,MAAA,IAAI,UAAU,SAAA,EAAW;AACvB,QAAA,MAAM,MAAM,+BAAA,EAAiC;AAAA,UAC3C,MAAA,EAAQ,MAAA;AAAA,UACR,SAAS,EAAE,aAAA,EAAe,UAAU,MAAM,CAAA,CAAA,EAAI,gBAAgB,kBAAA,EAAmB;AAAA,UACjF,IAAA,EAAM,KAAK,SAAA,CAAU;AAAA,YACnB,IAAA,EAAM,CAAA,EAAG,QAAA,IAAY,SAAS,KAAK,SAAS,CAAA,CAAA,CAAA;AAAA,YAC5C,EAAA,EAAI,CAAC,EAAE,CAAA;AAAA,YACP,OAAA;AAAA,YACA;AAAA,WACD;AAAA,SACF,CAAA;AACD,QAAA;AAAA,MACF;AAAA,IACF;AAAA,EACF,CAAA,CAAA,MAAQ;AAAA,EAAgC;AACxC,EAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,eAAA,EAAkB,EAAE,CAAA,WAAA,EAAc,OAAO,CAAA,CAAE,CAAA;AACzD;AAmBA,eAAe,kBAAA,CAAmB,UAAkB,MAAA,EAAkC;AACpF,EAAA,MAAM,KAAA,GAAQ,MAAA,CAAO,KAAA,CAAM,GAAG,CAAA;AAC9B,EAAA,IAAI,KAAA,CAAM,MAAA,KAAW,CAAA,EAAG,OAAO,KAAA;AAC/B,EAAA,MAAM,UAAA,GAAa,QAAA,CAAS,KAAA,CAAM,CAAC,GAAI,EAAE,CAAA;AACzC,EAAA,MAAM,SAAA,GAAY,KAAA,CAAM,CAAC,CAAA,CAAG,MAAM,OAAO,CAAA;AACzC,EAAA,IAAI,CAAC,SAAA,IAAa,CAAC,OAAO,QAAA,CAAS,UAAU,GAAG,OAAO,KAAA;AACvD,EAAA,MAAM,IAAA,GAAO,IAAI,UAAA,CAAW,SAAA,CAAU,GAAA,CAAI,CAAC,CAAA,KAAM,QAAA,CAAS,CAAA,EAAG,EAAE,CAAC,CAAC,CAAA;AACjE,EAAA,MAAM,KAAK,MAAM,MAAA,CAAO,MAAA,CAAO,SAAA,CAAU,OAAO,IAAI,WAAA,EAAY,CAAE,MAAA,CAAO,QAAQ,CAAA,EAAG,QAAA,EAAU,KAAA,EAAO,CAAC,YAAY,CAAC,CAAA;AACnH,EAAA,MAAM,IAAA,GAAO,MAAM,MAAA,CAAO,MAAA,CAAO,WAAW,EAAE,IAAA,EAAM,QAAA,EAAU,IAAA,EAAM,UAAA,EAAY,IAAA,EAAM,SAAA,EAAU,EAAG,IAAI,GAAG,CAAA;AAC1G,EAAA,MAAM,MAAA,GAAS,MAAM,IAAA,CAAK,IAAI,WAAW,IAAI,CAAC,EAAE,GAAA,CAAI,CAAC,MAAM,CAAA,CAAE,QAAA,CAAS,EAAE,CAAA,CAAE,QAAA,CAAS,GAAG,GAAG,CAAC,CAAA,CAAE,IAAA,CAAK,EAAE,CAAA;AACnG,EAAA,MAAM,QAAA,GAAW,MAAM,CAAC,CAAA;AACxB,EAAA,IAAI,MAAA,CAAO,MAAA,KAAW,QAAA,CAAS,MAAA,EAAQ,OAAO,KAAA;AAC9C,EAAA,IAAI,IAAA,GAAO,CAAA;AACX,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,MAAA,CAAO,MAAA,EAAQ,CAAA,EAAA,EAAK,IAAA,IAAQ,MAAA,CAAO,UAAA,CAAW,CAAC,CAAA,GAAI,QAAA,CAAS,WAAW,CAAC,CAAA;AAC5F,EAAA,OAAO,IAAA,KAAS,CAAA;AAClB;AAMO,SAAS,qBAAA,CAAsB,KAAe,cAAA,EAAyB;AAC5E,EAAA,MAAM,EAAA,GAAK,OAAA,CAAQ,GAAA,CAAI,EAAE,CAAA;AAEzB,EAAA,OAAO;AAAA,IACL,QAAQ,GAAA,CAAI,kBAAA;AAAA,IACZ,OAAA,EAAS,IAAI,eAAA,IAAmB,cAAA;AAAA,IAChC,OAAA,EAAS,SAAA;AAAA,IACT,GAAG,cAAA;AAAA,MACD;AAAA,QACE,mBAAA,EAAqB,IAAA;AAAA,QACrB,mBAAA,EAAqB,KAAA;AAAA,QACrB,IAAI,EAAC;AAAA,QACL,EAAA,EAAI;AAAA,UACF,EAAA;AAAA,UACA,OAAA,EAAS;AAAA;AAAA,YAEP,QAAQ,EAAE,SAAA,EAAW,QAAA,EAAU,YAAA,EAAc,aAAa,YAAA,EAAc,WAAA,EAAa,iBAAA,EAAmB,gBAAA,EAAkB,aAAa,UAAA,EAAY,kBAAA,EAAoB,kBAAkB,sBAAA,EAAwB,oBAAA,EAAsB,kBAAkB,cAAA;AAAe;AAC1Q,SACF;AAAA,QACA,IAAI,GAAA,CAAI;AAAA;AAAA,OACV;AAAA,MACA;AAAA,QACE,QAAA,EAAU,OAAA;AAAA,QACV,gBAAA,EAAkB;AAAA,UAChB,OAAA,EAAS,IAAA;AAAA,UACT,UAAA,EAAY,IAAA;AAAA;AAAA;AAAA;AAAA,UAIZ,QAAA,EAAU;AAAA,YACR,MAAA,EAAQ,OAAO,EAAE,IAAA,EAAM,UAAS,KAA0C;AACxE,cAAA,IAAI,IAAA,CAAK,UAAA,CAAW,SAAS,CAAA,EAAG;AAC9B,gBAAA,MAAM,EAAA,GAAK,MAAM,kBAAA,CAAmB,QAAA,EAAU,IAAI,CAAA;AAClD,gBAAA,IAAI,EAAA,EAAI;AACN,kBAAA,MAAM,QAAA,GAAW,MAAMA,YAAA,CAAe,QAAQ,CAAA;AAC9C,kBAAA,MAAM,IAAI,EAAA,CAAG,OAAA;AAAA,oBACX;AAAA,mBACF,CACG,IAAA,CAAK,QAAA,EAAU,IAAA,CAAK,KAAA,CAAM,IAAA,CAAK,GAAA,EAAI,GAAI,GAAI,CAAA,EAAG,IAAI,CAAA,CAClD,GAAA,EAAI;AAAA,gBACT;AACA,gBAAA,OAAO,EAAA;AAAA,cACT;AACA,cAAA,OAAOC,cAAA,CAAiB,EAAE,IAAA,EAAM,QAAA,EAAU,CAAA;AAAA,YAC5C;AAAA;AACF,SACF;AAAA,QACA,IAAA,EAAM;AAAA,UACJ,SAAA,EAAW,WAAA;AAAA;AAAA;AAAA;AAAA,UAIX,MAAA,EAAQ;AAAA,YACN,KAAA,EAAO;AAAA,WACT;AAAA,UACA,gBAAA,EAAkB;AAAA,YAChB,IAAA,EAAM,EAAE,IAAA,EAAM,QAAA,EAAU,UAAU,KAAA,EAAO,YAAA,EAAc,QAAA,EAAU,KAAA,EAAO,KAAA,EAAM;AAAA,YAC9E,SAAA,EAAW,EAAE,IAAA,EAAM,QAAA,EAAU,UAAU,KAAA,EAAO,YAAA,EAAc,EAAA,EAAI,KAAA,EAAO,IAAA,EAAK;AAAA,YAC5E,QAAA,EAAU,EAAE,IAAA,EAAM,QAAA,EAAU,UAAU,KAAA,EAAO,YAAA,EAAc,EAAA,EAAI,KAAA,EAAO,IAAA,EAAK;AAAA,YAC3E,YAAA,EAAc,EAAE,IAAA,EAAM,SAAA,EAAW,UAAU,KAAA,EAAO,YAAA,EAAc,KAAA,EAAO,KAAA,EAAO,KAAA;AAAM;AACtF,SACF;AAAA,QACA,OAAA,EAAS;AAAA,UACP,SAAA,EAAW,cAAA;AAAA;AAAA;AAAA,UAGX,SAAA,EAAW,EAAA,GAAK,EAAA,GAAK,EAAA,GAAK,CAAA;AAAA;AAAA,UAC1B,SAAA,EAAW,KAAK,EAAA,GAAK;AAAA;AAAA,SACvB;AAAA,QACA,OAAA,EAAS,EAAE,SAAA,EAAW,cAAA,EAAe;AAAA,QACrC,YAAA,EAAc,EAAE,SAAA,EAAW,mBAAA,EAAoB;AAAA,QAC/C,aAAA,EAAe;AAAA,UACb,IAAA,EAAM;AAAA,YACJ,MAAA,EAAQ;AAAA,cACN,MAAA,EAAQ,OAAO,QAAA,KAAsC;AACnD,gBAAA,MAAM,OAAA,GAAU,MAAM,uBAAA,CAAwB,GAAA,CAAI,EAAE,CAAA;AACpD,gBAAA,IAAI,CAAC,OAAA,EAAS;AACZ,kBAAA,MAAM,OAAA,GAAU,MAAM,qBAAA,CAAsB,GAAA,CAAI,EAAE,CAAA;AAClD,kBAAA,IAAI,CAAC,OAAA,EAAS;AACZ,oBAAA,MAAM,IAAI,QAAA,CAAS,aAAA,EAAe,EAAE,OAAA,EAAS,uCAAuC,CAAA;AAAA,kBACtF;AAAA,gBACF;AACA,gBAAA,MAAM,CAAA,GAAI,QAAA;AAGV,gBAAA,MAAM,IAAA,GAAA,CAAQ,CAAA,CAAE,IAAA,IAAQ,MAAA,EAAQ,QAAA,EAAS;AACzC,gBAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,IAAA,EAAK,CAAE,MAAM,KAAK,CAAA;AAGrC,gBAAA,MAAM,SAAA,GAAY,CAAA,CAAE,SAAA,IAAa,KAAA,CAAM,CAAC,CAAA,IAAK,MAAA;AAC7C,gBAAA,MAAM,QAAA,GAAW,EAAE,QAAA,IAAY,KAAA,CAAM,MAAM,CAAC,CAAA,CAAE,IAAA,CAAK,GAAG,CAAA,IAAK,SAAA;AAC3D,gBAAA,OAAO,EAAE,IAAA,EAAM,EAAE,GAAG,QAAA,EAAU,MAAM,SAAA,EAAW,QAAA,EAAU,IAAA,EAAM,QAAA,EAAS,EAAE;AAAA,cAC5E,CAAA;AAAA,cACA,KAAA,EAAO,OAAO,IAAA,KAAyB;AAIrC,gBAAA,IAAI;AAIF,kBAAA,MAAM,EAAE,WAAA,EAAY,GAAI,MAAM,OAAO,oBAAkB,CAAA;AACvD,kBAAA,MAAM,IAAA,GAAO,IAAI,WAAA,CAAY,GAAA,CAAI,EAAE,CAAA;AACnC,kBAAA,MAAM,QAAA,GAAY,MAAM,IAAA,CAAK,iBAAA,CAAkB,KAAK,EAAE,CAAA,KAAO,IAAI,OAAA,GAAU,QAAA;AAC3E,kBAAA,MAAM,IAAA,CAAK,iBAAA,CAAkB,IAAA,CAAK,EAAA,EAAI,QAAQ,CAAA;AAAA,gBAChD,CAAA,CAAA,MAAQ;AAAA,gBAER;AAAA,cACF;AAAA;AACF;AACF;AACF;AACF,KACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAQA,OAAA,EAAS;AAAA;AAAA;AAAA,MAGP,SAAA,CAAU;AAAA,QACR,eAAe,OAAO,EAAE,KAAA,EAAO,GAAA,IAAuC,QAAA,KAAkB;AACtF,UAAA,MAAM,kBAAA;AAAA,YACJ,GAAA,CAAI,EAAA;AAAA,YAAI,KAAA;AAAA,YACR,mBAAA;AAAA,YACA,CAAA;AAAA;AAAA;AAAA,0BAAA,EAGgB,GAAG,CAAA;AAAA,4DAAA,EAC+B,GAAG,CAAA;AAAA,kBAAA;AAAA,WAEvD;AAAA,QACF,CAAA;AAAA,QACA,WAAW,EAAA,GAAK;AAAA,OACjB,CAAA;AAAA;AAAA,MAGD,QAAA,CAAS;AAAA,QACP,mBAAA,EAAqB,OAAO,MAAA,EAAsD,QAAA,KAAkB;AAClG,UAAA,MAAM,kBAAA;AAAA,YACJ,GAAA,CAAI,EAAA;AAAA,YAAI,MAAA,CAAO,KAAA;AAAA,YACf,mBAAA;AAAA,YACA,CAAA;AAAA;AAAA,0FAAA,EAEgF,OAAO,GAAG,CAAA;AAAA;AAAA,kBAAA;AAAA,WAG5F;AAAA,QACF,CAAA;AAAA,QACA,SAAA,EAAW,CAAA;AAAA,QACX,WAAW,EAAA,GAAK;AAAA,OACjB,CAAA;AAAA,MAED,YAAA,CAAa;AAAA,QACX,MAAA,EAAQ;AAAA,UACN,YAAA,EAAc;AAAA,YACZ,SAAA,EAAW,aAAA;AAAA,YACX,gBAAA,EAAkB;AAAA,cAChB,MAAA,EAAQ,EAAE,IAAA,EAAM,QAAA,EAAU,UAAU,KAAA,EAAO,YAAA,EAAc,QAAA,EAAU,KAAA,EAAO,IAAA,EAAK;AAAA,cAC/E,QAAQ,EAAE,IAAA,EAAM,UAAU,QAAA,EAAU,KAAA,EAAO,OAAO,IAAA,EAAK;AAAA,cACvD,KAAA,EAAO,EAAE,IAAA,EAAM,QAAA,EAAU,UAAU,KAAA,EAAO,YAAA,EAAc,EAAA,EAAI,KAAA,EAAO,IAAA;AAAK;AAC1E,WACF;AAAA,UACA,MAAA,EAAQ;AAAA,YACN,SAAA,EAAW,oBAAA;AAAA,YACX,MAAA,EAAQ,EAAE,cAAA,EAAgB,WAAA;AAAY,WACxC;AAAA,UACA,UAAA,EAAY;AAAA,YACV,SAAA,EAAW,wBAAA;AAAA,YACX,MAAA,EAAQ,EAAE,cAAA,EAAgB,WAAA;AAAY,WACxC;AAAA,UACA,IAAA,EAAM;AAAA,YACJ,SAAA,EAAW,kBAAA;AAAA,YACX,MAAA,EAAQ,EAAE,cAAA,EAAgB,WAAA;AAAY;AACxC;AACF,OACD;AAAA,KACH;AAAA;AAAA;AAAA;AAAA,IAKA,eAAA,EAAiB;AAAA,MACf,GAAI,GAAA,CAAI,gBAAA,IAAoB,GAAA,CAAI,oBAAA,GAC5B,EAAE,MAAA,EAAQ,EAAE,QAAA,EAAU,GAAA,CAAI,kBAAkB,YAAA,EAAc,GAAA,CAAI,oBAAA,EAAqB,KACnF,EAAC;AAAA,MACL,GAAI,GAAA,CAAI,gBAAA,IAAoB,GAAA,CAAI,oBAAA,GAC5B,EAAE,MAAA,EAAQ,EAAE,QAAA,EAAU,GAAA,CAAI,kBAAkB,YAAA,EAAc,GAAA,CAAI,oBAAA,EAAqB,KACnF;AAAC;AACP,GACF;AACF;AAMO,SAAS,UAAA,CAAW,GAAA,EAAe,gBAAA,EAAqC,cAAA,EAAyB;AAItG,EAAA,IAAI,CAAC,GAAA,CAAI,kBAAA,IAAsB,GAAA,CAAI,kBAAA,CAAmB,SAAS,EAAA,EAAI;AACjE,IAAA,MAAM,IAAI,KAAA;AAAA,MACR;AAAA,KAGF;AAAA,EACF;AACA,EAAA,MAAM,QAAA,GAAW,qBAAA,CAAsB,GAAA,EAAK,cAAc,CAAA;AAC1D,EAAA,MAAM,OAAA,GAAU,gBAAA,GAAmB,gBAAA,CAAiB,QAAQ,CAAA,GAAI,QAAA;AAChE,EAAA,OAAO,WAAW,OAA2C,CAAA;AAC/D","file":"chunk-IQZA2OY5.js","sourcesContent":["/**\n * Better Auth configuration for SonicJS — via the better-auth-cloudflare shim.\n *\n * A fresh auth instance is built per request (Workers lifecycle). The existing\n * `auth_user` table is used as Better Auth's user model. Legacy SonicJS PBKDF2\n * hashes are verified and transparently upgraded to scrypt on first login. KV\n * (CACHE_KV) is used as session secondary storage so getSession does not hit D1\n * on every request.\n *\n * Extend via config.auth.extendBetterAuth in createSonicJSApp() to add social\n * providers, magic link, 2FA, etc.\n */\n/** Send an email via the SonicJS email plugin (Resend).\n * Loads apiKey/fromEmail/fromName from the `plugins` table at runtime.\n * Falls back to console.log when the plugin is unconfigured (local dev). */\nasync function sendViaEmailPlugin(\n db: D1Database,\n to: string,\n subject: string,\n html: string\n): Promise<void> {\n try {\n const row = (await db\n .prepare(\"SELECT settings FROM plugins WHERE id = 'email'\")\n .first()) as { settings: string } | null\n if (row?.settings) {\n const { apiKey, fromEmail, fromName } = JSON.parse(row.settings) as {\n apiKey?: string; fromEmail?: string; fromName?: string\n }\n if (apiKey && fromEmail) {\n await fetch('https://api.resend.com/emails', {\n method: 'POST',\n headers: { Authorization: `Bearer ${apiKey}`, 'Content-Type': 'application/json' },\n body: JSON.stringify({\n from: `${fromName ?? 'SonicJS'} <${fromEmail}>`,\n to: [to],\n subject,\n html,\n }),\n })\n return\n }\n }\n } catch { /* fall through to dev log */ }\n console.log(`[email-dev] To:${to} | Subject:${subject}`)\n}\n\nimport { betterAuth } from 'better-auth'\nimport { withCloudflare } from 'better-auth-cloudflare'\nimport { hashPassword as baHashPassword, verifyPassword as baVerifyPassword } from 'better-auth/crypto'\nimport { APIError } from 'better-auth/api'\nimport { magicLink } from 'better-auth/plugins/magic-link'\nimport { emailOTP } from 'better-auth/plugins/email-otp'\nimport { organization } from 'better-auth/plugins/organization'\nimport { drizzle } from 'drizzle-orm/d1'\nimport { authUser, authSession, authAccount, authVerification, authTenant, authTenantMember, authTenantInvitation, authTenantTeam } from '../db/schema'\nimport { isRegistrationEnabled, isFirstUserRegistration } from '../services/auth-validation'\nimport type { Bindings } from '../app'\n\n/**\n * Verify a password against a SonicJS legacy PBKDF2 hash:\n * pbkdf2:<iterations>:<saltHex>:<hashHex> (PBKDF2-SHA256, 256-bit)\n * Mirrors AuthManager.verifyPassword in middleware/auth.ts.\n */\nasync function verifyLegacyPbkdf2(password: string, stored: string): Promise<boolean> {\n const parts = stored.split(':')\n if (parts.length !== 4) return false\n const iterations = parseInt(parts[1]!, 10)\n const saltBytes = parts[2]!.match(/.{2}/g)\n if (!saltBytes || !Number.isFinite(iterations)) return false\n const salt = new Uint8Array(saltBytes.map((b) => parseInt(b, 16)))\n const km = await crypto.subtle.importKey('raw', new TextEncoder().encode(password), 'PBKDF2', false, ['deriveBits'])\n const bits = await crypto.subtle.deriveBits({ name: 'PBKDF2', salt, iterations, hash: 'SHA-256' }, km, 256)\n const actual = Array.from(new Uint8Array(bits)).map((b) => b.toString(16).padStart(2, '0')).join('')\n const expected = parts[3]!\n if (actual.length !== expected.length) return false\n let diff = 0\n for (let i = 0; i < actual.length; i++) diff |= actual.charCodeAt(i) ^ expected.charCodeAt(i)\n return diff === 0\n}\n\n/**\n * Build the default Better Auth options used by SonicJS (through the CF shim).\n * Exported so apps can extend via config.auth.extendBetterAuth.\n */\nexport function getDefaultAuthOptions(env: Bindings, requestBaseURL?: string) {\n const db = drizzle(env.DB)\n\n return {\n secret: env.BETTER_AUTH_SECRET,\n baseURL: env.BETTER_AUTH_URL || requestBaseURL,\n appName: 'SonicJS',\n ...withCloudflare(\n {\n autoDetectIpAddress: true,\n geolocationTracking: false,\n cf: {},\n d1: {\n db,\n options: {\n // Keys MUST match modelName values — BA resolves by modelName, not by JS variable name.\n schema: { auth_user: authUser, auth_session: authSession, auth_account: authAccount, auth_verification: authVerification, auth_tenant: authTenant, auth_tenant_member: authTenantMember, auth_tenant_invitation: authTenantInvitation, auth_tenant_team: authTenantTeam },\n },\n },\n kv: env.CACHE_KV, // session secondary storage → getSession skips D1\n },\n {\n basePath: '/auth',\n emailAndPassword: {\n enabled: true,\n autoSignIn: true,\n // Transparent migration of SonicJS legacy PBKDF2 hashes: verify against\n // the old format on login, then re-hash to scrypt and persist. No\n // mass-rehash, no forced password resets.\n password: {\n verify: async ({ hash, password }: { hash: string; password: string }) => {\n if (hash.startsWith('pbkdf2:')) {\n const ok = await verifyLegacyPbkdf2(password, hash)\n if (ok) {\n const upgraded = await baHashPassword(password)\n await env.DB.prepare(\n \"UPDATE auth_account SET password = ?, updated_at = ? WHERE password = ? AND provider_id = 'credential'\"\n )\n .bind(upgraded, Math.floor(Date.now() / 1000), hash)\n .run()\n }\n return ok\n }\n return baVerifyPassword({ hash, password })\n },\n },\n },\n user: {\n modelName: 'auth_user',\n // Field-mapping values are Drizzle *property keys* (camelCase), which\n // already match Better Auth's defaults for emailVerified/createdAt/\n // updatedAt. Only `image` differs (SonicJS uses `avatar`).\n fields: {\n image: 'avatar',\n },\n additionalFields: {\n role: { type: 'string', required: false, defaultValue: 'viewer', input: false },\n firstName: { type: 'string', required: false, defaultValue: '', input: true },\n lastName: { type: 'string', required: false, defaultValue: '', input: true },\n isSuperAdmin: { type: 'boolean', required: false, defaultValue: false, input: false },\n },\n },\n session: {\n modelName: 'auth_session',\n // Drizzle property keys already match Better Auth defaults (userId,\n // expiresAt, ipAddress, …) — no field overrides needed.\n expiresIn: 60 * 60 * 24 * 7, // 7 days\n updateAge: 60 * 60 * 24, // refresh once per day\n },\n account: { modelName: 'auth_account' },\n verification: { modelName: 'auth_verification' },\n databaseHooks: {\n user: {\n create: {\n before: async (userData: Record<string, unknown>) => {\n const isFirst = await isFirstUserRegistration(env.DB)\n if (!isFirst) {\n const enabled = await isRegistrationEnabled(env.DB)\n if (!enabled) {\n throw new APIError('BAD_REQUEST', { message: 'Registration is currently disabled.' })\n }\n }\n const d = userData as {\n name?: string; email?: string; firstName?: string; lastName?: string\n }\n const name = (d.name ?? 'User').toString()\n const parts = name.trim().split(/\\s+/)\n // Prefer explicitly-provided fields (registration form); fall back\n // to values derived from name/email.\n const firstName = d.firstName || parts[0] || 'User'\n const lastName = d.lastName || parts.slice(1).join(' ') || firstName\n return { data: { ...userData, name, firstName, lastName, role: 'viewer' } }\n },\n after: async (user: { id: string }) => {\n // Assign dynamic RBAC membership. The first real user receives\n // Administrator so fresh installs can enter the portal; later\n // self-registered users receive Viewer.\n try {\n // RBAC roles/assignments are document-backed (services/rbac.ts).\n // First real portal admin → Administrator; later users → Viewer.\n // setUserRoles (via addUserRoleByName) also projects auth_user.role.\n const { RbacService } = await import('../services/rbac')\n const rbac = new RbacService(env.DB)\n const roleName = (await rbac.countPortalAdmins(user.id)) === 0 ? 'admin' : 'viewer'\n await rbac.addUserRoleByName(user.id, roleName)\n } catch {\n /* rbac docs may not be seeded yet on older schemas — non-fatal */\n }\n },\n },\n },\n },\n }\n ),\n\n // ── Phase 4: BA-native login methods ─────────────────────────────────────\n // Magic-link and Email-OTP replace the standalone SonicJS plugins that\n // minted JWT cookies. Social providers replace the bespoke oauth-providers\n // plugin. All are gated on the relevant env vars / email service config\n // so they activate only when configured.\n\n plugins: [\n // Magic-link passwordless auth. Sends a one-time link to the user's inbox;\n // the link resolves to a BA session. Requires a working email service.\n magicLink({\n sendMagicLink: async ({ email, url }: { email: string; url: string }, _request: any) => {\n await sendViaEmailPlugin(\n env.DB, email,\n 'Your sign-in link',\n `<div style=\"font-family:sans-serif;max-width:600px\">\n <h2>Sign in to SonicJS</h2>\n <p>Click the link below to sign in. Expires in 15 minutes.</p>\n <p><a href=\"${url}\" style=\"background:#465FFF;color:#fff;padding:12px 24px;border-radius:6px;text-decoration:none\">Sign in</a></p>\n <p style=\"color:#666;font-size:12px\">Or copy: ${url}</p>\n </div>`\n )\n },\n expiresIn: 15 * 60,\n }),\n\n // Email OTP — 6-digit code sent to inbox. Replaces the otp-login-plugin.\n emailOTP({\n sendVerificationOTP: async (params: { email: string; otp: string; type: string }, _request: any) => {\n await sendViaEmailPlugin(\n env.DB, params.email,\n 'Your sign-in code',\n `<div style=\"font-family:sans-serif;max-width:600px\">\n <h2>Your one-time code</h2>\n <p style=\"font-size:36px;font-weight:bold;letter-spacing:8px;color:#465FFF\">${params.otp}</p>\n <p style=\"color:#666\">Expires in 10 minutes. Do not share this code.</p>\n </div>`\n )\n },\n otpLength: 6,\n expiresIn: 10 * 60,\n }),\n\n organization({\n schema: {\n organization: {\n modelName: 'auth_tenant',\n additionalFields: {\n status: { type: 'string', required: false, defaultValue: 'active', input: true },\n domain: { type: 'string', required: false, input: true },\n notes: { type: 'string', required: false, defaultValue: '', input: true },\n },\n },\n member: {\n modelName: 'auth_tenant_member',\n fields: { organizationId: 'tenant_id' },\n },\n invitation: {\n modelName: 'auth_tenant_invitation',\n fields: { organizationId: 'tenant_id' },\n },\n team: {\n modelName: 'auth_tenant_team',\n fields: { organizationId: 'tenant_id' },\n },\n },\n }),\n ],\n\n // ── Phase 4: Social providers ─────────────────────────────────────────\n // Activated when the relevant env vars are set. Replaces the bespoke\n // oauth-providers SonicJS plugin. Set via wrangler secret put / .dev.vars.\n socialProviders: {\n ...(env.GITHUB_CLIENT_ID && env.GITHUB_CLIENT_SECRET\n ? { github: { clientId: env.GITHUB_CLIENT_ID, clientSecret: env.GITHUB_CLIENT_SECRET } }\n : {}),\n ...(env.GOOGLE_CLIENT_ID && env.GOOGLE_CLIENT_SECRET\n ? { google: { clientId: env.GOOGLE_CLIENT_ID, clientSecret: env.GOOGLE_CLIENT_SECRET } }\n : {}),\n },\n }\n}\n\nexport type BetterAuthDefaultOptions = ReturnType<typeof getDefaultAuthOptions>\nexport type ExtendBetterAuth = (opts: BetterAuthDefaultOptions) => BetterAuthDefaultOptions\n\n/** Create a Better Auth instance for this request. */\nexport function createAuth(env: Bindings, extendBetterAuth?: ExtendBetterAuth, requestBaseURL?: string) {\n // Hard-fail rather than sign sessions with an undefined/blank secret. The\n // secret must be provided via `wrangler secret put BETTER_AUTH_SECRET`\n // (prod/preview) or a gitignored `.dev.vars` (local) — never committed.\n if (!env.BETTER_AUTH_SECRET || env.BETTER_AUTH_SECRET.length < 16) {\n throw new Error(\n 'BETTER_AUTH_SECRET is missing or too short. Set it as a Wrangler secret ' +\n '(wrangler secret put BETTER_AUTH_SECRET) or in a gitignored .dev.vars for local dev. ' +\n 'Refusing to initialize auth without a strong signing secret.'\n )\n }\n const defaults = getDefaultAuthOptions(env, requestBaseURL)\n const options = extendBetterAuth ? extendBetterAuth(defaults) : defaults\n return betterAuth(options as Parameters<typeof betterAuth>[0])\n}\n\nexport type SonicJSAuth = ReturnType<typeof createAuth>\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/auth/config.ts"],"names":["baHashPassword","baVerifyPassword"],"mappings":"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","file":"chunk-SOZUG52O.js","sourcesContent":["/**\n * Better Auth configuration for SonicJS — via the better-auth-cloudflare shim.\n *\n * A fresh auth instance is built per request (Workers lifecycle). The existing\n * `auth_user` table is used as Better Auth's user model. Legacy SonicJS PBKDF2\n * hashes are verified and transparently upgraded to scrypt on first login. KV\n * (CACHE_KV) is used as session secondary storage so getSession does not hit D1\n * on every request.\n *\n * Extend via config.auth.extendBetterAuth in createSonicJSApp() to add social\n * providers, magic link, 2FA, etc.\n */\n/** Send an email via the SonicJS email plugin (Resend).\n * Loads apiKey/fromEmail/fromName from the `plugins` table at runtime.\n * Falls back to console.log when the plugin is unconfigured (local dev). */\nasync function sendViaEmailPlugin(\n db: D1Database,\n to: string,\n subject: string,\n html: string\n): Promise<void> {\n try {\n const row = (await db\n .prepare(\"SELECT settings FROM plugins WHERE id = 'email'\")\n .first()) as { settings: string } | null\n if (row?.settings) {\n const { apiKey, fromEmail, fromName } = JSON.parse(row.settings) as {\n apiKey?: string; fromEmail?: string; fromName?: string\n }\n if (apiKey && fromEmail) {\n await fetch('https://api.resend.com/emails', {\n method: 'POST',\n headers: { Authorization: `Bearer ${apiKey}`, 'Content-Type': 'application/json' },\n body: JSON.stringify({\n from: `${fromName ?? 'SonicJS'} <${fromEmail}>`,\n to: [to],\n subject,\n html,\n }),\n })\n return\n }\n }\n } catch { /* fall through to dev log */ }\n console.log(`[email-dev] To:${to} | Subject:${subject}`)\n}\n\nimport { betterAuth } from 'better-auth'\nimport { withCloudflare } from 'better-auth-cloudflare'\nimport { hashPassword as baHashPassword, verifyPassword as baVerifyPassword } from 'better-auth/crypto'\nimport { APIError } from 'better-auth/api'\nimport { magicLink } from 'better-auth/plugins/magic-link'\nimport { emailOTP } from 'better-auth/plugins/email-otp'\nimport { organization } from 'better-auth/plugins/organization'\nimport { drizzle } from 'drizzle-orm/d1'\nimport { authUser, authSession, authAccount, authVerification, authTenant, authTenantMember, authTenantInvitation, authTenantTeam } from '../db/schema'\nimport { isRegistrationEnabled, isFirstUserRegistration } from '../services/auth-validation'\nimport type { Bindings } from '../app'\n\n/**\n * Verify a password against a SonicJS legacy PBKDF2 hash:\n * pbkdf2:<iterations>:<saltHex>:<hashHex> (PBKDF2-SHA256, 256-bit)\n * Mirrors AuthManager.verifyPassword in middleware/auth.ts.\n */\nasync function verifyLegacyPbkdf2(password: string, stored: string): Promise<boolean> {\n const parts = stored.split(':')\n if (parts.length !== 4) return false\n const iterations = parseInt(parts[1]!, 10)\n const saltBytes = parts[2]!.match(/.{2}/g)\n if (!saltBytes || !Number.isFinite(iterations)) return false\n const salt = new Uint8Array(saltBytes.map((b) => parseInt(b, 16)))\n const km = await crypto.subtle.importKey('raw', new TextEncoder().encode(password), 'PBKDF2', false, ['deriveBits'])\n const bits = await crypto.subtle.deriveBits({ name: 'PBKDF2', salt, iterations, hash: 'SHA-256' }, km, 256)\n const actual = Array.from(new Uint8Array(bits)).map((b) => b.toString(16).padStart(2, '0')).join('')\n const expected = parts[3]!\n if (actual.length !== expected.length) return false\n let diff = 0\n for (let i = 0; i < actual.length; i++) diff |= actual.charCodeAt(i) ^ expected.charCodeAt(i)\n return diff === 0\n}\n\n/**\n * Build the default Better Auth options used by SonicJS (through the CF shim).\n * Exported so apps can extend via config.auth.extendBetterAuth.\n */\nexport function getDefaultAuthOptions(env: Bindings, requestBaseURL?: string) {\n const db = drizzle(env.DB)\n\n return {\n secret: env.BETTER_AUTH_SECRET,\n baseURL: env.BETTER_AUTH_URL || requestBaseURL,\n appName: 'SonicJS',\n ...withCloudflare(\n {\n autoDetectIpAddress: true,\n geolocationTracking: false,\n cf: {},\n d1: {\n db,\n options: {\n // Keys MUST match modelName values — BA resolves by modelName, not by JS variable name.\n schema: { auth_user: authUser, auth_session: authSession, auth_account: authAccount, auth_verification: authVerification, auth_tenant: authTenant, auth_tenant_member: authTenantMember, auth_tenant_invitation: authTenantInvitation, auth_tenant_team: authTenantTeam },\n },\n },\n kv: env.CACHE_KV, // session secondary storage → getSession skips D1\n },\n {\n basePath: '/auth',\n emailAndPassword: {\n enabled: true,\n autoSignIn: true,\n // Transparent migration of SonicJS legacy PBKDF2 hashes: verify against\n // the old format on login, then re-hash to scrypt and persist. No\n // mass-rehash, no forced password resets.\n password: {\n verify: async ({ hash, password }: { hash: string; password: string }) => {\n if (hash.startsWith('pbkdf2:')) {\n const ok = await verifyLegacyPbkdf2(password, hash)\n if (ok) {\n const upgraded = await baHashPassword(password)\n await env.DB.prepare(\n \"UPDATE auth_account SET password = ?, updated_at = ? WHERE password = ? AND provider_id = 'credential'\"\n )\n .bind(upgraded, Math.floor(Date.now() / 1000), hash)\n .run()\n }\n return ok\n }\n return baVerifyPassword({ hash, password })\n },\n },\n },\n user: {\n modelName: 'auth_user',\n // Field-mapping values are Drizzle *property keys* (camelCase), which\n // already match Better Auth's defaults for emailVerified/createdAt/\n // updatedAt. Only `image` differs (SonicJS uses `avatar`).\n fields: {\n image: 'avatar',\n },\n additionalFields: {\n role: { type: 'string', required: false, defaultValue: 'viewer', input: false },\n firstName: { type: 'string', required: false, defaultValue: '', input: true },\n lastName: { type: 'string', required: false, defaultValue: '', input: true },\n isSuperAdmin: { type: 'boolean', required: false, defaultValue: false, input: false },\n },\n },\n session: {\n modelName: 'auth_session',\n // Drizzle property keys already match Better Auth defaults (userId,\n // expiresAt, ipAddress, …) — no field overrides needed.\n expiresIn: 60 * 60 * 24 * 7, // 7 days\n updateAge: 60 * 60 * 24, // refresh once per day\n },\n account: { modelName: 'auth_account' },\n verification: { modelName: 'auth_verification' },\n databaseHooks: {\n user: {\n create: {\n before: async (userData: Record<string, unknown>) => {\n const isFirst = await isFirstUserRegistration(env.DB)\n if (!isFirst) {\n const enabled = await isRegistrationEnabled(env.DB)\n if (!enabled) {\n throw new APIError('BAD_REQUEST', { message: 'Registration is currently disabled.' })\n }\n }\n const d = userData as {\n name?: string; email?: string; firstName?: string; lastName?: string\n }\n const name = (d.name ?? 'User').toString()\n const parts = name.trim().split(/\\s+/)\n // Prefer explicitly-provided fields (registration form); fall back\n // to values derived from name/email.\n const firstName = d.firstName || parts[0] || 'User'\n const lastName = d.lastName || parts.slice(1).join(' ') || firstName\n return { data: { ...userData, name, firstName, lastName, role: 'viewer' } }\n },\n after: async (user: { id: string }) => {\n // Assign dynamic RBAC membership. The first real user receives\n // Administrator so fresh installs can enter the portal; later\n // self-registered users receive Viewer.\n try {\n // RBAC roles/assignments are document-backed (services/rbac.ts).\n // First real portal admin → Administrator; later users → Viewer.\n // setUserRoles (via addUserRoleByName) also projects auth_user.role.\n const { RbacService } = await import('../services/rbac')\n const rbac = new RbacService(env.DB)\n const roleName = (await rbac.countPortalAdmins(user.id)) === 0 ? 'admin' : 'viewer'\n await rbac.addUserRoleByName(user.id, roleName)\n } catch {\n /* rbac docs may not be seeded yet on older schemas — non-fatal */\n }\n },\n },\n },\n },\n }\n ),\n\n // ── Phase 4: BA-native login methods ─────────────────────────────────────\n // Magic-link and Email-OTP replace the standalone SonicJS plugins that\n // minted JWT cookies. Social providers replace the bespoke oauth-providers\n // plugin. All are gated on the relevant env vars / email service config\n // so they activate only when configured.\n\n plugins: [\n // Magic-link passwordless auth. Sends a one-time link to the user's inbox;\n // the link resolves to a BA session. Requires a working email service.\n magicLink({\n sendMagicLink: async ({ email, url }: { email: string; url: string }, _request: any) => {\n await sendViaEmailPlugin(\n env.DB, email,\n 'Your sign-in link',\n `<div style=\"font-family:sans-serif;max-width:600px\">\n <h2>Sign in to SonicJS</h2>\n <p>Click the link below to sign in. Expires in 15 minutes.</p>\n <p><a href=\"${url}\" style=\"background:#465FFF;color:#fff;padding:12px 24px;border-radius:6px;text-decoration:none\">Sign in</a></p>\n <p style=\"color:#666;font-size:12px\">Or copy: ${url}</p>\n </div>`\n )\n },\n expiresIn: 15 * 60,\n }),\n\n // Email OTP — 6-digit code sent to inbox. Replaces the otp-login-plugin.\n emailOTP({\n sendVerificationOTP: async (params: { email: string; otp: string; type: string }, _request: any) => {\n await sendViaEmailPlugin(\n env.DB, params.email,\n 'Your sign-in code',\n `<div style=\"font-family:sans-serif;max-width:600px\">\n <h2>Your one-time code</h2>\n <p style=\"font-size:36px;font-weight:bold;letter-spacing:8px;color:#465FFF\">${params.otp}</p>\n <p style=\"color:#666\">Expires in 10 minutes. Do not share this code.</p>\n </div>`\n )\n },\n otpLength: 6,\n expiresIn: 10 * 60,\n }),\n\n organization({\n schema: {\n organization: {\n modelName: 'auth_tenant',\n additionalFields: {\n status: { type: 'string', required: false, defaultValue: 'active', input: true },\n domain: { type: 'string', required: false, input: true },\n notes: { type: 'string', required: false, defaultValue: '', input: true },\n },\n },\n member: {\n modelName: 'auth_tenant_member',\n fields: { organizationId: 'tenant_id' },\n },\n invitation: {\n modelName: 'auth_tenant_invitation',\n fields: { organizationId: 'tenant_id' },\n },\n team: {\n modelName: 'auth_tenant_team',\n fields: { organizationId: 'tenant_id' },\n },\n },\n }),\n ],\n\n // ── Phase 4: Social providers ─────────────────────────────────────────\n // Activated when the relevant env vars are set. Replaces the bespoke\n // oauth-providers SonicJS plugin. Set via wrangler secret put / .dev.vars.\n socialProviders: {\n ...(env.GITHUB_CLIENT_ID && env.GITHUB_CLIENT_SECRET\n ? { github: { clientId: env.GITHUB_CLIENT_ID, clientSecret: env.GITHUB_CLIENT_SECRET } }\n : {}),\n ...(env.GOOGLE_CLIENT_ID && env.GOOGLE_CLIENT_SECRET\n ? { google: { clientId: env.GOOGLE_CLIENT_ID, clientSecret: env.GOOGLE_CLIENT_SECRET } }\n : {}),\n },\n }\n}\n\nexport type BetterAuthDefaultOptions = ReturnType<typeof getDefaultAuthOptions>\nexport type ExtendBetterAuth = (opts: BetterAuthDefaultOptions) => BetterAuthDefaultOptions\n\n/** Create a Better Auth instance for this request. */\nexport function createAuth(env: Bindings, extendBetterAuth?: ExtendBetterAuth, requestBaseURL?: string) {\n // Hard-fail rather than sign sessions with an undefined/blank secret. The\n // secret must be provided via `wrangler secret put BETTER_AUTH_SECRET`\n // (prod/preview) or a gitignored `.dev.vars` (local) — never committed.\n if (!env.BETTER_AUTH_SECRET || env.BETTER_AUTH_SECRET.length < 16) {\n throw new Error(\n 'BETTER_AUTH_SECRET is missing or too short. Set it as a Wrangler secret ' +\n '(wrangler secret put BETTER_AUTH_SECRET) or in a gitignored .dev.vars for local dev. ' +\n 'Refusing to initialize auth without a strong signing secret.'\n )\n }\n const defaults = getDefaultAuthOptions(env, requestBaseURL)\n const options = extendBetterAuth ? extendBetterAuth(defaults) : defaults\n return betterAuth(options as Parameters<typeof betterAuth>[0])\n}\n\nexport type SonicJSAuth = ReturnType<typeof createAuth>\n"]}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
4
|
-
var
|
|
5
|
-
var
|
|
3
|
+
var chunkLTZ5WYOK_cjs = require('./chunk-LTZ5WYOK.cjs');
|
|
4
|
+
var chunk2DSRS3VA_cjs = require('./chunk-2DSRS3VA.cjs');
|
|
5
|
+
var chunkIDMHRCZS_cjs = require('./chunk-IDMHRCZS.cjs');
|
|
6
6
|
var chunkK623Q6WD_cjs = require('./chunk-K623Q6WD.cjs');
|
|
7
7
|
var chunkJ6JTWD2A_cjs = require('./chunk-J6JTWD2A.cjs');
|
|
8
8
|
var chunkRCQ2HIQD_cjs = require('./chunk-RCQ2HIQD.cjs');
|
|
9
|
-
var
|
|
9
|
+
var chunkNZF3THNY_cjs = require('./chunk-NZF3THNY.cjs');
|
|
10
10
|
var zod = require('zod');
|
|
11
11
|
var jwt = require('hono/jwt');
|
|
12
12
|
var cookie = require('hono/cookie');
|
|
@@ -35,6 +35,7 @@ var DocumentTypeRegistry = class {
|
|
|
35
35
|
constructor(db) {
|
|
36
36
|
this.db = db;
|
|
37
37
|
}
|
|
38
|
+
db;
|
|
38
39
|
cache = /* @__PURE__ */ new Map();
|
|
39
40
|
// Register or update a document type. Idempotent: bumps schema_version only when schema changes.
|
|
40
41
|
async register(def) {
|
|
@@ -71,7 +72,7 @@ var DocumentTypeRegistry = class {
|
|
|
71
72
|
now,
|
|
72
73
|
def.id
|
|
73
74
|
).run();
|
|
74
|
-
await
|
|
75
|
+
await chunkIDMHRCZS_cjs.ensureScalarSchema(this.db, def.id, def.queryableFields ?? []);
|
|
75
76
|
const updated = await this.findById(def.id);
|
|
76
77
|
this.cache.set(def.id, updated);
|
|
77
78
|
return updated;
|
|
@@ -93,7 +94,7 @@ var DocumentTypeRegistry = class {
|
|
|
93
94
|
now,
|
|
94
95
|
now
|
|
95
96
|
).run();
|
|
96
|
-
await
|
|
97
|
+
await chunkIDMHRCZS_cjs.ensureScalarSchema(this.db, def.id, def.queryableFields ?? []);
|
|
97
98
|
const created = await this.findById(def.id);
|
|
98
99
|
this.cache.set(def.id, created);
|
|
99
100
|
return created;
|
|
@@ -351,7 +352,7 @@ async function bootstrapDocumentTypes(db) {
|
|
|
351
352
|
}
|
|
352
353
|
async function autoRegisterCollectionDocumentTypes(db) {
|
|
353
354
|
const registry = new DocumentTypeRegistry(db);
|
|
354
|
-
const collections =
|
|
355
|
+
const collections = chunk2DSRS3VA_cjs.getCollectionRegistry().listActive();
|
|
355
356
|
const registered = [];
|
|
356
357
|
for (const collection of collections) {
|
|
357
358
|
if (collection.internal) continue;
|
|
@@ -444,7 +445,7 @@ function bootstrapMiddleware(config = {}) {
|
|
|
444
445
|
try {
|
|
445
446
|
console.log("[Bootstrap] Starting system initialization...");
|
|
446
447
|
console.log("[Bootstrap] Checking schema compatibility...");
|
|
447
|
-
const migrationService = new
|
|
448
|
+
const migrationService = new chunkIDMHRCZS_cjs.MigrationService(c.env.DB);
|
|
448
449
|
await migrationService.ensureSchemaCompatibility();
|
|
449
450
|
try {
|
|
450
451
|
const kv = c.env.CACHE_KV;
|
|
@@ -457,8 +458,8 @@ function bootstrapMiddleware(config = {}) {
|
|
|
457
458
|
}
|
|
458
459
|
console.log("[Bootstrap] Populating collection registry...");
|
|
459
460
|
try {
|
|
460
|
-
const configs = await
|
|
461
|
-
|
|
461
|
+
const configs = await chunk2DSRS3VA_cjs.loadCollectionConfigs();
|
|
462
|
+
chunk2DSRS3VA_cjs.getCollectionRegistry().register(configs);
|
|
462
463
|
console.log(`[Bootstrap] Registry populated with ${configs.length} collection(s)`);
|
|
463
464
|
} catch (error) {
|
|
464
465
|
console.error("[Bootstrap] Error populating collection registry:", error);
|
|
@@ -475,7 +476,7 @@ function bootstrapMiddleware(config = {}) {
|
|
|
475
476
|
console.error("[Bootstrap] Error repairing credential accounts:", error);
|
|
476
477
|
}
|
|
477
478
|
try {
|
|
478
|
-
const { RbacService: RbacService2 } = await import('./rbac-
|
|
479
|
+
const { RbacService: RbacService2 } = await import('./rbac-7QZ4JZ2E.cjs');
|
|
479
480
|
await new RbacService2(c.env.DB, c.env.CACHE_KV).ensureSystemRbacSeed();
|
|
480
481
|
} catch (error) {
|
|
481
482
|
console.error("[Bootstrap] Error seeding RBAC documents:", error);
|
|
@@ -488,7 +489,7 @@ function bootstrapMiddleware(config = {}) {
|
|
|
488
489
|
}
|
|
489
490
|
if (!config.plugins?.disableAll) {
|
|
490
491
|
console.log("[Bootstrap] Bootstrapping core plugins...");
|
|
491
|
-
const bootstrapService = new
|
|
492
|
+
const bootstrapService = new chunk2DSRS3VA_cjs.PluginBootstrapService(c.env.DB);
|
|
492
493
|
const needsBootstrap = await bootstrapService.isBootstrapNeeded();
|
|
493
494
|
if (needsBootstrap) {
|
|
494
495
|
await bootstrapService.bootstrapCorePlugins();
|
|
@@ -499,7 +500,7 @@ function bootstrapMiddleware(config = {}) {
|
|
|
499
500
|
bootstrapComplete = true;
|
|
500
501
|
console.log("[Bootstrap] System initialization completed");
|
|
501
502
|
try {
|
|
502
|
-
const registry =
|
|
503
|
+
const registry = chunk2DSRS3VA_cjs.getCollectionRegistry();
|
|
503
504
|
const collections = registry.listActive();
|
|
504
505
|
const countResult = await c.env.DB.prepare(
|
|
505
506
|
`SELECT type_id, COUNT(*) AS cnt FROM documents
|
|
@@ -532,7 +533,7 @@ function bootstrapMiddleware(config = {}) {
|
|
|
532
533
|
}
|
|
533
534
|
} catch {
|
|
534
535
|
}
|
|
535
|
-
const telemetry =
|
|
536
|
+
const telemetry = chunk2DSRS3VA_cjs.getTelemetryService();
|
|
536
537
|
await telemetry.trackProjectSnapshot({
|
|
537
538
|
installation_id: installationId,
|
|
538
539
|
collection_names: collections.map((c2) => c2.name),
|
|
@@ -540,7 +541,7 @@ function bootstrapMiddleware(config = {}) {
|
|
|
540
541
|
active_plugins: activePlugins,
|
|
541
542
|
field_type_histogram: fieldTypeHistogram,
|
|
542
543
|
doc_total: docTotal,
|
|
543
|
-
sonicjs_version:
|
|
544
|
+
sonicjs_version: chunkNZF3THNY_cjs.SONICJS_VERSION
|
|
544
545
|
});
|
|
545
546
|
} catch {
|
|
546
547
|
}
|
|
@@ -897,7 +898,7 @@ var requireRbac = (resource, verb) => {
|
|
|
897
898
|
if (cachedPerms !== void 0) {
|
|
898
899
|
allowed = cachedPerms.includes(`${resource}:${verb}`);
|
|
899
900
|
} else {
|
|
900
|
-
allowed = await new
|
|
901
|
+
allowed = await new chunkLTZ5WYOK_cjs.RbacService(c.env.DB).can(user.userId, resource, verb);
|
|
901
902
|
}
|
|
902
903
|
if (!allowed) {
|
|
903
904
|
const acceptHeader = c.req.header("Accept") || "";
|
|
@@ -1237,5 +1238,5 @@ exports.securityHeadersMiddleware = securityHeadersMiddleware;
|
|
|
1237
1238
|
exports.securityLoggingMiddleware = securityLoggingMiddleware;
|
|
1238
1239
|
exports.validateCsrfToken = validateCsrfToken;
|
|
1239
1240
|
exports.verifySecurityConfig = verifySecurityConfig;
|
|
1240
|
-
//# sourceMappingURL=chunk-
|
|
1241
|
-
//# sourceMappingURL=chunk-
|
|
1241
|
+
//# sourceMappingURL=chunk-SYAUCKSD.cjs.map
|
|
1242
|
+
//# sourceMappingURL=chunk-SYAUCKSD.cjs.map
|