@clovnet/plugin-sdk 0.1.4
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/LICENSE +21 -0
- package/README.md +114 -0
- package/dist/chunk-XRSKWL3A.js +1872 -0
- package/dist/chunk-XRSKWL3A.js.map +1 -0
- package/dist/index.d.ts +3658 -0
- package/dist/index.js +561 -0
- package/dist/index.js.map +1 -0
- package/dist/testing.d.ts +275 -0
- package/dist/testing.js +1010 -0
- package/dist/testing.js.map +1 -0
- package/package.json +75 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/testing/context.ts","../src/testing/errors.ts","../src/testing/catalog.ts","../src/testing/validate.ts"],"sourcesContent":["import { createHmac } from \"node:crypto\";\nimport type { PluginContext, PluginEmittedEvent } from \"../context.js\";\nimport type { PluginDefinition, PluginHandlers } from \"../define.js\";\nimport type { PluginManifest } from \"../manifest.js\";\nimport type { PluginRuntimeHooks } from \"../hooks.js\";\nimport type {\n PluginRequest,\n PluginResponse,\n PluginRouteDecl,\n SignatureVerifyOptions,\n} from \"../routes.js\";\nimport type { PluginDatasetCollection, PluginDatasetQuery } from \"../datasets.js\";\nimport { PLUGIN_DATASET_LIMITS } from \"../datasets.js\";\nimport type { PluginFetchInit, PluginFetchResponse } from \"../http.js\";\nimport type { PluginTaskProgress } from \"../tasks.js\";\nimport type { PluginMigrationHelper } from \"../migrations.js\";\nimport { PLUGIN_MIGRATION_LIMITS } from \"../migrations.js\";\nimport { ReadModelRequiredScopes, type ReadModelName } from \"../data.js\";\nimport type { Actor } from \"../host-types.js\";\nimport { CATALOG_IMPORT_TASK_TYPE, type CatalogImportSummary, type PluginCatalogSourceAdapter } from \"../catalog.js\";\nimport {\n applyCatalogSnapshot,\n catalogGrantedFor,\n createCatalogMock,\n type MockCatalogStores,\n} from \"./catalog.js\";\nimport {\n egressDenied,\n forbidden,\n notFound,\n quotaExceeded,\n unauthorized,\n validation,\n} from \"./errors.js\";\n\n/**\n * `createTestContext` — a fully working in-memory PluginContext plus a\n * harness that records and controls everything. Behavioral parity with the\n * real host is a test target: anything the host rejects (permission miss,\n * quota, batch cap, non-allowlisted host, event namespace), this context\n * rejects with the same error `name`/`code`. The platform repo runs a shared\n * conformance suite against both so the mock cannot silently diverge.\n */\nexport interface TestHttpRequest {\n url: string;\n method: string;\n headers: Record<string, string>;\n body?: string;\n}\n\nexport interface TestHttpResponse {\n status: number;\n json?: unknown;\n text?: string;\n headers?: Record<string, string>;\n}\n\n/** Response shape returned by a host-keyed `options.http` mock (§10). */\nexport interface TestHttpHostResponse {\n status: number;\n /** A string passes through verbatim as the body text; anything else is JSON-serialized. */\n body?: unknown;\n headers?: Record<string, string>;\n}\n\n/** A host-keyed outbound-HTTP mock: receives the (secret-resolved) request. */\nexport type TestHttpHostMock = (\n req: TestHttpRequest,\n) => TestHttpHostResponse | Promise<TestHttpHostResponse>;\n\nexport interface CreateTestContextOptions {\n /** Full definition (preferred) — enables runHook/invokeRoute/runJob/… */\n definition?: PluginDefinition;\n /** Or a bare manifest (permissions are ENFORCED in tests too). */\n manifest?: PluginManifest;\n handlers?: PluginHandlers;\n hooks?: PluginRuntimeHooks;\n tenantId?: string;\n region?: string;\n settings?: Record<string, unknown>;\n secrets?: Record<string, string>;\n /** Stubbed command results by command name (value, factory, or Error). */\n commandResults?: Record<\n string,\n unknown | Error | ((input: unknown) => unknown | Promise<unknown>)\n >;\n /** `ctx.http` interception (after the allowlist check). */\n httpMock?: (req: TestHttpRequest) => TestHttpResponse | Promise<TestHttpResponse>;\n /** Read-model fixtures by model name (value or factory by params). */\n readModels?: Partial<Record<ReadModelName, unknown | ((params: unknown) => unknown)>>;\n /** Per-dataset record quota override (simulate quota exhaustion). */\n datasetQuota?: number;\n /** Foreign shared datasets available to this plugin: \"owner.dataset\" → rows. */\n sharedDatasets?: Record<string, Array<{ key: string; value: unknown }>>;\n /**\n * Seed own-dataset fixtures at construction — the same arrange-only path\n * as `harness.seedDataset` (fixtures are trusted; the schema is not run).\n */\n datasets?: Record<string, Array<{ key: string; value: unknown }>>;\n /**\n * Host-keyed outbound-HTTP mocks, e.g. `{ \"api.sloterv.com\": (req) => … }`.\n * A matching host wins over `httpMock` (which stays the catch-all beneath\n * it). The HTTPS + allowlist checks still run FIRST — a non-allowlisted\n * host throws the same egress error even when a mock exists for it.\n */\n http?: Record<string, TestHttpHostMock>;\n /** Per-dataset record quotas; a dataset named here wins over `datasetQuota`. */\n quotas?: Record<string, number>;\n}\n\nexport interface RecordedCommand {\n name: string;\n input: unknown;\n idempotencyKey?: string;\n}\n\nexport interface RecordedHttpExchange {\n url: string;\n method: string;\n /** Secret headers appear as `<redacted:name>` — never the value. */\n headers: Record<string, string>;\n status: number;\n}\n\nexport interface RecordedLog {\n level: string;\n message: string;\n extra: Record<string, unknown>;\n}\n\nexport interface InvokeRouteRequest {\n params?: Record<string, string>;\n query?: unknown;\n body?: unknown;\n player?: { id: string };\n actor?: Actor;\n headers?: Record<string, string>;\n idempotencyKey?: string;\n /**\n * Callback surface: either provide `rawBody` + a real signature header, or\n * set `trustSignature: true` to make verifySignature succeed in the test.\n */\n rawBody?: string;\n trustSignature?: boolean;\n}\n\nconst REDACT_KEY_RE = /secret|password|token|apikey|api_key|credential/i;\n\nfunction hostMatches(host: string, allowlist: readonly string[]): boolean {\n const target = host.toLowerCase();\n for (const raw of allowlist) {\n const entry = raw.toLowerCase();\n if (entry.startsWith(\"*.\")) {\n const suffix = entry.slice(1);\n if (target.endsWith(suffix) && !target.slice(0, -suffix.length).includes(\".\")) return true;\n } else if (target === entry) return true;\n }\n return false;\n}\n\ninterface StoredRecord {\n key: string;\n value: Record<string, unknown>;\n}\n\n/** Options accepted alongside a definition in the two-argument call shape. */\nexport type TestContextOptions = Omit<\n CreateTestContextOptions,\n \"definition\" | \"manifest\" | \"handlers\" | \"hooks\"\n>;\n\n/**\n * Keys that only ever appear on the OPTIONS object — used to tell the two\n * `createTestContext` call shapes apart, because BOTH a `PluginDefinition`\n * and a bare-manifest options object carry a `manifest` property. An object\n * with `manifest` and none of these keys behaves identically under either\n * interpretation (manifest/hooks/handlers are read the same way).\n */\nconst OPTION_ONLY_KEYS = [\n \"definition\",\n \"tenantId\",\n \"region\",\n \"settings\",\n \"secrets\",\n \"commandResults\",\n \"httpMock\",\n \"readModels\",\n \"datasetQuota\",\n \"sharedDatasets\",\n \"datasets\",\n \"http\",\n \"quotas\",\n] as const satisfies ReadonlyArray<keyof CreateTestContextOptions>;\n\nfunction isDefinitionForm(\n first: PluginDefinition | CreateTestContextOptions,\n second: TestContextOptions | undefined,\n): first is PluginDefinition {\n if (second !== undefined) return true;\n if (typeof first !== \"object\" || first === null || !(\"manifest\" in first)) return false;\n return !OPTION_ONLY_KEYS.some((key) => key in first);\n}\n\n/**\n * Two call shapes, one return type:\n *\n * createTestContext(plugin, { settings, … }) // §10 definition-first\n * createTestContext({ definition, settings, … }) // original options object\n *\n * Both return the same enriched object: `{ ctx, harness }` exactly as before,\n * plus top-level aliases of the harness runners and a `recorder` view.\n *\n * The options overload is deliberately LAST: `Parameters<typeof\n * createTestContext>[0]` resolves against the final overload, and existing\n * consumers (the scaffold template among them) rely on it being the options\n * object.\n */\nexport function createTestContext(\n definition: PluginDefinition,\n options?: TestContextOptions,\n): TestContext;\nexport function createTestContext(options: CreateTestContextOptions): TestContext;\nexport function createTestContext(\n first: PluginDefinition | CreateTestContextOptions,\n second?: TestContextOptions,\n): TestContext {\n const options: CreateTestContextOptions = isDefinitionForm(first, second)\n ? { ...(second ?? {}), definition: first }\n : first;\n return buildTestContext(options);\n}\n\nfunction buildTestContext(options: CreateTestContextOptions) {\n const resolved = options.definition?.manifest ?? options.manifest;\n if (!resolved) throw new Error(\"createTestContext requires a manifest (or definition)\");\n const manifest: PluginManifest = resolved;\n const handlers = options.definition?.handlers ?? options.handlers ?? {};\n const hooks = options.definition?.hooks ?? options.hooks;\n\n const tenantId = options.tenantId ?? \"test-tenant\";\n const region = options.region ?? \"eu\";\n const settings = { ...(options.settings ?? {}) };\n const secrets = new Map(Object.entries(options.secrets ?? {}));\n\n const allowedCommands = new Set(manifest.permissions.commands);\n const allowedSubscribe = new Set<string>(manifest.permissions.events.subscribe);\n const allowedEmit = new Set(manifest.permissions.events.emit);\n const grantedScopes = new Set<string>((manifest.permissions.dataScopes ?? []) as string[]);\n const allowedHosts = [\n ...(manifest.network?.allowedHosts ?? []),\n ...(manifest.permissions.networkAllow ?? []),\n ];\n const emitPrefix = `plugin.${manifest.key}.`;\n // §10 host-keyed HTTP mocks — matched by exact hostname, case-insensitive.\n // Layered ABOVE `httpMock` and BELOW the egress checks (never consulted for\n // a denied host).\n const httpHostMocks = new Map<string, TestHttpHostMock>(\n Object.entries(options.http ?? {}).map(([host, mock]) => [host.toLowerCase(), mock]),\n );\n\n // ── harness state ───────────────────────────────────────────────────────────\n const commands: RecordedCommand[] = [];\n const events: PluginEmittedEvent[] = [];\n const httpExchanges: RecordedHttpExchange[] = [];\n const logs: RecordedLog[] = [];\n const tasksStarted: Array<{ type: string; input?: Record<string, unknown> }> = [];\n const eventHandlers = new Map<string, Array<(e: unknown) => Promise<void>>>();\n const datasetStores = new Map<string, Map<string, StoredRecord>>();\n\n const storeOf = (dataset: string): Map<string, StoredRecord> => {\n let store = datasetStores.get(dataset);\n if (!store) {\n store = new Map();\n datasetStores.set(dataset, store);\n }\n return store;\n };\n\n // ── datasets (own): honors keyField, schema, indexes, batch cap, quota ─────\n function ownCollection<T>(dataset: string): PluginDatasetCollection<T> {\n const decl = manifest.datasets?.[dataset];\n if (!decl) {\n throw notFound(`Plugin '${manifest.key}' does not declare a dataset named '${dataset}'`);\n }\n const indexes = (decl.indexes ?? []).slice(0, PLUGIN_DATASET_LIMITS.maxIndexes);\n // Per-dataset `quotas` beats the single `datasetQuota`, which beats the\n // declaration's own cap — same \"host cap wins\" shape as production.\n const quota =\n options.quotas?.[dataset] ??\n options.datasetQuota ??\n decl.maxRecords ??\n PLUGIN_DATASET_LIMITS.defaultMaxRecords;\n const store = storeOf(dataset);\n\n const validate = (key: string, value: unknown): Record<string, unknown> => {\n const parsed = decl.schema.safeParse(value);\n if (!parsed.success) {\n throw validation(`Record '${key}' does not match the '${dataset}' dataset schema`, {\n issues: parsed.error.issues.slice(0, 5),\n });\n }\n const data = parsed.data as Record<string, unknown>;\n const keyFieldValue = data[decl.keyField];\n if (keyFieldValue !== undefined && String(keyFieldValue) !== key) {\n throw validation(`Record key '${key}' does not match its '${decl.keyField}' field`);\n }\n const bytes = Buffer.byteLength(JSON.stringify(data), \"utf8\");\n if (bytes > PLUGIN_DATASET_LIMITS.maxRecordBytes) {\n throw validation(\n `Record '${key}' is ${bytes} bytes; the cap is ${PLUGIN_DATASET_LIMITS.maxRecordBytes}`,\n );\n }\n return data;\n };\n\n return {\n async get(key) {\n return (store.get(key)?.value as T | undefined) ?? null;\n },\n async put(key, value) {\n await this.putMany([{ key, value }]);\n },\n async putMany(records) {\n if (records.length === 0) return;\n if (records.length > PLUGIN_DATASET_LIMITS.maxBatch) {\n throw validation(\n `putMany accepts at most ${PLUGIN_DATASET_LIMITS.maxBatch} records per call`,\n );\n }\n // Validate the WHOLE batch, then check quota, then write — a breach\n // must never leave a partial batch (host parity).\n const validated = records.map(({ key, value }) => ({ key, data: validate(key, value) }));\n const newCount = validated.filter(({ key }) => !store.has(key)).length;\n if (store.size + newCount > quota) {\n throw quotaExceeded(\n `Dataset '${manifest.key}.${dataset}' quota exceeded (${store.size}+${newCount} > ${quota})`,\n { current: store.size, incoming: newCount, quota },\n );\n }\n for (const { key, data } of validated) store.set(key, { key, value: data });\n },\n async delete(key) {\n store.delete(key);\n },\n async query(q: PluginDatasetQuery) {\n for (const field of Object.keys(q.where ?? {})) {\n if (!indexes.includes(field)) {\n throw validation(`Field '${field}' is not an indexed field of dataset '${dataset}'`);\n }\n }\n const limit = Math.min(Math.max(1, q.limit ?? 50), PLUGIN_DATASET_LIMITS.maxQueryLimit);\n let rows = [...store.values()].sort((a, b) => (a.key < b.key ? -1 : 1));\n for (const [field, value] of Object.entries(q.where ?? {})) {\n rows = rows.filter((r) => String(r.value[field]) === String(value));\n }\n if (q.cursor !== undefined) rows = rows.filter((r) => r.key > q.cursor!);\n const page = rows.slice(0, limit);\n return {\n records: page.map((r) => ({ key: r.key, value: r.value as T })),\n ...(page.length === limit ? { nextCursor: page[page.length - 1]!.key } : {}),\n };\n },\n async count() {\n return store.size;\n },\n async truncate() {\n store.clear();\n },\n };\n }\n\n function foreignCollection<T>(name: string): PluginDatasetCollection<T> {\n const declared = manifest.permissions.datasets?.read?.includes(name) === true;\n const fixture = options.sharedDatasets?.[name];\n if (!declared || !fixture) {\n throw forbidden(`Plugin '${manifest.key}' may not read dataset '${name}'`);\n }\n const rows = new Map(fixture.map((r) => [r.key, r]));\n const readOnly = () => {\n throw forbidden(`Plugin '${manifest.key}' has read-only access to '${name}'`);\n };\n return {\n get: async (key) => (rows.get(key)?.value as T | undefined) ?? null,\n query: async (q) => {\n const limit = Math.min(Math.max(1, q.limit ?? 50), PLUGIN_DATASET_LIMITS.maxQueryLimit);\n let sorted = [...rows.values()].sort((a, b) => (a.key < b.key ? -1 : 1));\n if (q.cursor !== undefined) sorted = sorted.filter((r) => r.key > q.cursor!);\n const page = sorted.slice(0, limit);\n return {\n records: page.map((r) => ({ key: r.key, value: r.value as T })),\n ...(page.length === limit ? { nextCursor: page[page.length - 1]!.key } : {}),\n };\n },\n count: async () => rows.size,\n put: readOnly,\n putMany: readOnly,\n delete: readOnly,\n truncate: readOnly,\n };\n }\n\n // ── the context ─────────────────────────────────────────────────────────────\n const redact = (extra: Record<string, unknown> | undefined): Record<string, unknown> => {\n const out: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(extra ?? {})) {\n out[k] = REDACT_KEY_RE.test(k) ? \"[redacted]\" : v;\n }\n return out;\n };\n const log = (level: string) => (message: string, extra?: Record<string, unknown>) => {\n logs.push({ level, message, extra: redact(extra) });\n };\n\n const ctx: PluginContext = {\n tenantId,\n region,\n pluginKey: manifest.key,\n version: manifest.version,\n\n commands: {\n async execute<TResult>(\n command: { name: string; input: unknown },\n opts?: { idempotencyKey?: string },\n ): Promise<TResult> {\n if (!allowedCommands.has(command.name)) {\n throw forbidden(\n `Plugin '${manifest.key}' is not permitted to execute command '${command.name}'`,\n );\n }\n commands.push({ name: command.name, input: command.input, ...(opts ?? {}) });\n const stub = options.commandResults?.[command.name];\n if (stub instanceof Error) throw stub;\n if (typeof stub === \"function\") {\n return (await (stub as (input: unknown) => unknown)(command.input)) as TResult;\n }\n return (stub ?? {}) as TResult;\n },\n },\n\n settings: {\n get<T = Record<string, unknown>>(): T {\n return settings as T;\n },\n },\n\n secrets: {\n get: async (key) => secrets.get(key),\n },\n\n events: {\n on(event, handler) {\n if (!allowedSubscribe.has(event)) {\n throw forbidden(`Plugin '${manifest.key}' is not permitted to subscribe to '${event}'`);\n }\n const list = eventHandlers.get(event) ?? [];\n list.push(handler as (e: unknown) => Promise<void>);\n eventHandlers.set(event, list);\n },\n async emit(event) {\n if (!event.name.startsWith(emitPrefix) || !allowedEmit.has(event.name)) {\n throw forbidden(`Plugin '${manifest.key}' is not permitted to emit '${event.name}'`);\n }\n events.push(event);\n },\n },\n\n data: {\n async query(model, params) {\n const required = ReadModelRequiredScopes[model as ReadModelName];\n if (!required) throw notFound(`Unknown read model '${String(model)}'`);\n if (!grantedScopes.has(required)) {\n throw forbidden(\n `Plugin '${manifest.key}' lacks scope '${required}' for read model '${String(model)}'`,\n );\n }\n const fixture = options.readModels?.[model as ReadModelName];\n if (fixture === undefined) {\n throw new Error(\n `No fixture for read model '${String(model)}' — pass options.readModels['${String(model)}']`,\n );\n }\n const value = typeof fixture === \"function\" ? await fixture(params) : fixture;\n return value as never;\n },\n },\n\n datasets: {\n collection<T = Record<string, unknown>>(name: string): PluginDatasetCollection<T> {\n const dot = name.indexOf(\".\");\n if (dot === -1 || name.slice(0, dot) === manifest.key) {\n return ownCollection<T>(dot === -1 ? name : name.slice(dot + 1));\n }\n return foreignCollection<T>(name);\n },\n },\n\n http: {\n async fetch(url: string, init?: PluginFetchInit): Promise<PluginFetchResponse> {\n let parsed: URL;\n try {\n parsed = new URL(url);\n } catch {\n throw validation(`'${url}' is not a valid URL`);\n }\n if (parsed.protocol !== \"https:\") {\n throw egressDenied(\n `Plugin '${manifest.key}' may only call HTTPS endpoints (got ${parsed.protocol}//)`,\n );\n }\n if (!hostMatches(parsed.hostname, allowedHosts)) {\n throw egressDenied(\n `Host '${parsed.hostname}' is not in plugin '${manifest.key}' allowedHosts`,\n );\n }\n\n const real: Record<string, string> = { ...(init?.headers ?? {}) };\n const redacted: Record<string, string> = { ...(init?.headers ?? {}) };\n for (const [header, secretName] of Object.entries(init?.secretHeaders ?? {})) {\n const secret = secrets.get(secretName);\n if (secret === undefined) {\n throw validation(`secretHeaders['${header}'] references unknown secret '${secretName}'`);\n }\n real[header] = secret;\n redacted[header] = `<redacted:${secretName}>`;\n }\n\n const bodyText =\n init?.body === undefined\n ? undefined\n : typeof init.body === \"string\"\n ? init.body\n : JSON.stringify(init.body);\n const request: TestHttpRequest = {\n url,\n method: init?.method ?? \"GET\",\n headers: real,\n ...(bodyText !== undefined ? { body: bodyText } : {}),\n };\n\n // Mock resolution (permission checks already passed above): a\n // host-keyed `options.http` entry wins; `options.httpMock` is the\n // catch-all for allowlisted hosts without a dedicated mock.\n const hostMock = httpHostMocks.get(parsed.hostname.toLowerCase());\n let status: number;\n let text: string;\n let responseHeaders: Record<string, string>;\n if (hostMock) {\n const response = await hostMock(request);\n status = response.status;\n text =\n typeof response.body === \"string\"\n ? response.body\n : JSON.stringify(response.body ?? null);\n responseHeaders = response.headers ?? {};\n } else {\n if (!options.httpMock) {\n throw new Error(\"ctx.http.fetch called without options.httpMock — provide one\");\n }\n const response = await options.httpMock(request);\n status = response.status;\n text = response.text ?? JSON.stringify(response.json ?? null);\n responseHeaders = response.headers ?? {};\n }\n httpExchanges.push({\n url: `${parsed.origin}${parsed.pathname}`,\n method: init?.method ?? \"GET\",\n headers: redacted,\n status,\n });\n return {\n status,\n ok: status >= 200 && status < 300,\n headers: responseHeaders,\n bodyText: text,\n json<T = unknown>(): T {\n return JSON.parse(text) as T;\n },\n };\n },\n async hmacSha256(secretName: string, payload: string): Promise<string> {\n const secret = secrets.get(secretName);\n if (secret === undefined) throw validation(`Unknown secret '${secretName}'`);\n return createHmac(\"sha256\", secret).update(payload).digest(\"hex\");\n },\n },\n\n tasks: {\n async start(type, input) {\n tasksStarted.push({ type, ...(input !== undefined ? { input } : {}) });\n return { taskId: `test-task-${tasksStarted.length}` };\n },\n },\n\n logger: {\n debug: log(\"debug\"),\n info: log(\"info\"),\n warn: log(\"warn\"),\n error: log(\"error\"),\n },\n };\n\n if (manifest.kind === \"provider\") {\n ctx.registerProviderAdapter = (adapter) => {\n harness.providerAdapter = adapter;\n };\n }\n\n // ── catalog content surface (§7b) — present only when granted, like the host.\n let catalogStores: MockCatalogStores | undefined;\n if (catalogGrantedFor(manifest)) {\n const mock = createCatalogMock(manifest, async () => {\n tasksStarted.push({ type: CATALOG_IMPORT_TASK_TYPE });\n return { taskId: `test-task-${tasksStarted.length}` };\n });\n catalogStores = mock.stores;\n ctx.catalog = mock.capability;\n ctx.registerCatalogSource = (adapter: PluginCatalogSourceAdapter) => {\n harness.catalogSourceAdapter = adapter;\n };\n }\n\n // ── harness helpers ─────────────────────────────────────────────────────────\n // Accepts a decl, a path (\"/favorites\" — first match), or \"METHOD /path\"\n // (\"POST /favorites\") when one path serves several methods.\n const findRoute = (ref: string | PluginRouteDecl): PluginRouteDecl => {\n if (typeof ref !== \"string\") return ref;\n const withMethod = /^([A-Z]+)\\s+(.+)$/.exec(ref);\n const method = withMethod?.[1];\n const path = withMethod?.[2] ?? ref;\n const decl = (manifest.routes ?? []).find(\n (r) => r.path === path && (method === undefined || r.method === method),\n );\n if (!decl) throw notFound(`No declared route matching '${ref}'`);\n return decl;\n };\n\n const harness = {\n ctx,\n commands,\n events,\n httpExchanges,\n logs,\n tasksStarted,\n datasetStores,\n /** In-memory catalog stores (undefined unless `manifest.catalog` granted). */\n catalogStores,\n providerAdapter: undefined as unknown,\n /** Captured by `ctx.registerCatalogSource` during `runSetup()`. */\n catalogSourceAdapter: undefined as PluginCatalogSourceAdapter | undefined,\n\n /**\n * Run the registered catalog source adapter the way the host's reserved\n * `catalog:import` task does: `ingest(ctx)` → apply as a full snapshot\n * (upsert every family, RETIRE games absent from the snapshot) → summary.\n */\n async runImport(): Promise<CatalogImportSummary> {\n if (!ctx.catalog || !catalogStores) {\n throw forbidden(`Plugin '${manifest.key}' has no catalog grant (manifest.catalog missing)`);\n }\n const adapter = harness.catalogSourceAdapter;\n if (!adapter) {\n throw notFound(\n `Plugin '${manifest.key}' registered no catalog source adapter — call harness.runSetup() first`,\n );\n }\n const normalized = await adapter.ingest(ctx);\n return applyCatalogSnapshot(catalogStores, ctx.catalog, normalized);\n },\n\n /** Deliver a domain event to handlers registered via `ctx.events.on`. */\n async dispatch(event: { name: string; payload?: unknown } & Record<string, unknown>) {\n for (const handler of eventHandlers.get(event.name) ?? []) {\n await handler(event);\n }\n },\n\n async runHook<K extends keyof PluginRuntimeHooks>(\n name: K,\n ...args: unknown[]\n ): Promise<unknown> {\n const hook = hooks?.[name] as ((...a: unknown[]) => unknown) | undefined;\n if (!hook) return undefined;\n return hook(ctx, ...args);\n },\n\n async runSetup(): Promise<void> {\n await options.definition?.setup?.(ctx);\n },\n\n /** Zod-validate inputs per the declaration, enforce surface auth, invoke. */\n async invokeRoute(\n ref: string | PluginRouteDecl,\n request: InvokeRouteRequest = {},\n ): Promise<PluginResponse> {\n const decl = findRoute(ref);\n const handler = handlers.routes?.[decl.handler];\n if (!handler) throw notFound(`Handler '${decl.handler}' not found in handlers.routes`);\n\n if (decl.surface === \"player\" && !request.player) {\n throw unauthorized(\"Player session required\");\n }\n if (decl.surface === \"admin\" && !request.actor) {\n throw unauthorized(\"Staff actor required\");\n }\n if (decl.idempotent && !request.idempotencyKey) {\n throw validation(\"Idempotency-Key header is required for this route\");\n }\n\n const parse = (schema: { parse(v: unknown): unknown } | undefined, value: unknown) =>\n schema ? schema.parse(value) : value;\n\n const rawBody = request.rawBody ?? JSON.stringify(request.body ?? {});\n const headers = Object.fromEntries(\n Object.entries(request.headers ?? {}).map(([k, v]) => [k.toLowerCase(), v]),\n );\n const verifySignature = async (\n secretName: string,\n opts?: SignatureVerifyOptions,\n ): Promise<void> => {\n if (request.trustSignature) return;\n const secret = secrets.get(secretName);\n if (!secret) throw unauthorized(\"Signature secret is not configured\");\n const headerName = (opts?.header ?? \"x-signature\").toLowerCase();\n const provided = headers[headerName];\n if (!provided) throw unauthorized(\"Missing signature\");\n const expected = createHmac(opts?.algorithm ?? \"sha256\", secret)\n .update(rawBody)\n .digest(opts?.encoding ?? \"hex\");\n if (provided !== expected) throw unauthorized(\"Invalid signature\");\n };\n\n const pluginRequest: PluginRequest = {\n params: parse(decl.input?.params, request.params ?? {}),\n query: parse(decl.input?.query, request.query ?? {}),\n body: parse(decl.input?.body, request.body),\n ...(request.player ? { player: request.player } : {}),\n ...(request.actor ? { actor: request.actor } : {}),\n headers: Object.freeze(headers),\n ...(request.idempotencyKey ? { idempotencyKey: request.idempotencyKey } : {}),\n verifySignature,\n };\n return handler(pluginRequest, ctx);\n },\n\n async runJob(name: string): Promise<void> {\n const decl = manifest.jobs?.[name];\n if (!decl) throw notFound(`Plugin '${manifest.key}' has no job '${name}'`);\n const handler = handlers.jobs?.[decl.handler];\n if (!handler) throw notFound(`Handler '${decl.handler}' not found in handlers.jobs`);\n await handler(ctx);\n },\n\n async runTask(\n type: string,\n input: Record<string, unknown> = {},\n checkpoint: Record<string, unknown> | null = null,\n ): Promise<{ progress: number; message?: string; checkpoint: Record<string, unknown> | null }> {\n const handler = handlers.tasks?.[type];\n if (!handler) throw notFound(`Handler '${type}' not found in handlers.tasks`);\n let current = checkpoint;\n let percent = 0;\n let message: string | undefined;\n const progress: PluginTaskProgress = {\n report: async (p, m) => {\n percent = p;\n message = m;\n },\n get checkpoint() {\n return current;\n },\n saveCheckpoint: async (cp) => {\n current = cp;\n },\n };\n await handler(ctx, input, progress);\n return { progress: percent, ...(message !== undefined ? { message } : {}), checkpoint: current };\n },\n\n /** Run declared migrations with semver `toVersion` (in-memory datasets). */\n async runMigration(toVersion: string): Promise<{ processed: number }> {\n const decl = (manifest.migrations ?? []).find((m) => m.toVersion === toVersion);\n if (!decl) throw notFound(`No migration with toVersion '${toVersion}'`);\n const handler = handlers.migrations?.[decl.handler];\n if (!handler) throw notFound(`Handler '${decl.handler}' not found in handlers.migrations`);\n\n let totalProcessed = 0;\n const helper: PluginMigrationHelper = {\n async transformDataset(dataset, transform) {\n const store = storeOf(dataset);\n const collection = ownCollection(dataset);\n const keys = [...store.keys()].sort();\n let processed = 0;\n for (let i = 0; i < keys.length; i += PLUGIN_MIGRATION_LIMITS.batchSize) {\n const batch = keys.slice(i, i + PLUGIN_MIGRATION_LIMITS.batchSize);\n const out: Array<{ key: string; value: never }> = [];\n for (const key of batch) {\n const result = await transform({\n key,\n value: store.get(key)!.value as never,\n });\n if (result) out.push(result as never);\n }\n if (out.length > 0) await collection.putMany(out);\n processed += batch.length;\n }\n totalProcessed += processed;\n return { processed };\n },\n };\n await handler(ctx, helper);\n return { processed: totalProcessed };\n },\n\n /** Seed a dataset store directly (bypasses schema — arrange step only). */\n seedDataset(dataset: string, records: Array<{ key: string; value: Record<string, unknown> }>) {\n const store = storeOf(dataset);\n for (const r of records) store.set(r.key, { key: r.key, value: r.value });\n },\n\n setSecret(key: string, value: string | undefined) {\n if (value === undefined) secrets.delete(key);\n else secrets.set(key, value);\n },\n\n setSettings(next: Record<string, unknown>) {\n Object.assign(settings, next);\n },\n };\n\n // §10 `datasets` option: seed fixtures through the same arrange-only path\n // as `harness.seedDataset` (bypasses the schema — fixtures are trusted).\n for (const [dataset, records] of Object.entries(options.datasets ?? {})) {\n harness.seedDataset(\n dataset,\n records.map((r) => ({ key: r.key, value: r.value as Record<string, unknown> })),\n );\n }\n\n /**\n * Live recorder views — the SAME arrays the harness mutates (never copies),\n * so reads after any run see everything recorded so far. `http` is an alias\n * of `harness.httpExchanges`.\n */\n const recorder = {\n commands,\n events,\n http: httpExchanges,\n logs,\n tasksStarted,\n };\n\n return {\n ctx,\n harness,\n recorder,\n // Top-level aliases of the harness runners (§10 ergonomics). These are\n // direct references — the harness methods close over module state and\n // must stay `this`-free (runImport, which uses `harness.`, is not aliased).\n runHook: harness.runHook,\n runJob: harness.runJob,\n runTask: harness.runTask,\n runMigration: harness.runMigration,\n invokeRoute: harness.invokeRoute,\n dispatch: harness.dispatch,\n runSetup: harness.runSetup,\n seedDataset: harness.seedDataset,\n setSettings: harness.setSettings,\n setSecret: harness.setSecret,\n };\n}\n\n/** The enriched object BOTH `createTestContext` call shapes return. */\nexport type TestContext = ReturnType<typeof buildTestContext>;\nexport type TestHarness = TestContext[\"harness\"];\nexport type TestRecorder = TestContext[\"recorder\"];\n","/**\n * Test-context errors. The SDK is contract-only — it cannot import the\n * platform's runtime error classes — so the mock throws errors whose `name`,\n * `code` and `statusCode` match the host's exactly. The conformance suite in\n * the platform repo asserts this parity (same assertions run against the mock\n * and the real capability layer).\n */\nexport class TestContextError extends Error {\n readonly statusCode: number;\n readonly code: string;\n readonly details?: unknown;\n\n constructor(\n name: string,\n message: string,\n opts: { statusCode: number; code: string; details?: unknown },\n ) {\n super(message);\n this.name = name;\n this.statusCode = opts.statusCode;\n this.code = opts.code;\n this.details = opts.details;\n }\n}\n\nexport const forbidden = (message: string): TestContextError =>\n new TestContextError(\"ForbiddenError\", message, { statusCode: 403, code: \"FORBIDDEN\" });\n\nexport const validation = (message: string, details?: unknown): TestContextError =>\n new TestContextError(\"ValidationError\", message, {\n statusCode: 400,\n code: \"VALIDATION_ERROR\",\n details,\n });\n\nexport const notFound = (message: string): TestContextError =>\n new TestContextError(\"NotFoundError\", message, { statusCode: 404, code: \"NOT_FOUND\" });\n\nexport const unauthorized = (message: string): TestContextError =>\n new TestContextError(\"UnauthorizedError\", message, { statusCode: 401, code: \"UNAUTHORIZED\" });\n\nexport const quotaExceeded = (message: string, details?: unknown): TestContextError =>\n new TestContextError(\"PluginDatasetQuotaExceededError\", message, {\n statusCode: 409,\n code: \"PLUGIN_DATASET_QUOTA_EXCEEDED\",\n details,\n });\n\nexport const egressDenied = (message: string): TestContextError =>\n new TestContextError(\"PluginEgressDeniedError\", message, {\n statusCode: 403,\n code: \"PLUGIN_EGRESS_DENIED\",\n });\n","import type { PluginManifest } from \"../manifest.js\";\nimport type {\n CatalogImportSummary,\n ImportSummaryLite,\n NormalizedCatalog,\n NormalizedCatalogDelta,\n NormalizedCategory,\n NormalizedCurrencyGroup,\n NormalizedGame,\n NormalizedProvider,\n NormalizedRestrictionGroup,\n OwnGameSummary,\n PluginCatalogCapability,\n} from \"../catalog.js\";\nimport { CATALOG_ALLOWED_KINDS, PLUGIN_CATALOG_LIMITS } from \"../catalog.js\";\nimport { notFound, validation } from \"./errors.js\";\n\n/**\n * In-memory `ctx.catalog` for `createTestContext` — behavioral parity with\n * the host CatalogBridge is the target (asserted by the shared conformance\n * suite): own-source store only, batch cap, unknown-provider auto-stub,\n * unknown group/category reference → ValidationError naming the externalId,\n * deactivate-only (`setGameStatus(false)` retires, never deletes).\n */\n\nexport interface MockCatalogGameRow {\n game: NormalizedGame;\n status: \"active\" | \"retired\";\n}\n\nexport interface MockCatalogStores {\n providers: Map<string, NormalizedProvider>;\n categories: Map<string, NormalizedCategory>;\n restrictionGroups: Map<string, NormalizedRestrictionGroup>;\n currencyGroups: Map<string, NormalizedCurrencyGroup>;\n games: Map<string, MockCatalogGameRow>;\n}\n\nexport function catalogGrantedFor(manifest: PluginManifest): boolean {\n return (\n manifest.catalog !== undefined &&\n (CATALOG_ALLOWED_KINDS as readonly string[]).includes(manifest.kind)\n );\n}\n\nconst emptyLite = (): ImportSummaryLite => ({\n providers: { inserted: 0, updated: 0 },\n categories: { inserted: 0, updated: 0 },\n restrictionGroups: { inserted: 0, updated: 0 },\n currencyGroups: { inserted: 0, updated: 0 },\n games: { inserted: 0, updated: 0, unchanged: 0 },\n gameCategoryLinks: 0,\n});\n\nconst same = (a: unknown, b: unknown): boolean => JSON.stringify(a) === JSON.stringify(b);\n\nexport function createCatalogMock(\n manifest: PluginManifest,\n requestImport: () => Promise<{ taskId: string }>,\n): { capability: PluginCatalogCapability; stores: MockCatalogStores } {\n const stores: MockCatalogStores = {\n providers: new Map(),\n categories: new Map(),\n restrictionGroups: new Map(),\n currencyGroups: new Map(),\n games: new Map(),\n };\n\n const capFamily = (family: string, rows: unknown[] | undefined): void => {\n if (rows && rows.length > PLUGIN_CATALOG_LIMITS.maxRowsPerFamily) {\n throw validation(\n `delta family '${family}' has ${rows.length} rows; the cap is ${PLUGIN_CATALOG_LIMITS.maxRowsPerFamily} per call`,\n );\n }\n };\n\n const upsertInto = <T>(\n store: Map<string, T>,\n key: string,\n value: T,\n counts: { inserted: number; updated: number; unchanged?: number },\n ): void => {\n const existing = store.get(key);\n if (existing === undefined) {\n store.set(key, value);\n counts.inserted++;\n } else if (!same(existing, value)) {\n store.set(key, value);\n counts.updated++;\n } else if (counts.unchanged !== undefined) {\n counts.unchanged++;\n }\n };\n\n const stubProvider = (externalId: string, counts: { inserted: number; updated: number }): void => {\n if (stores.providers.has(externalId)) return;\n stores.providers.set(externalId, {\n externalId,\n name: `Provider ${externalId}`,\n stub: true,\n });\n counts.inserted++;\n };\n\n const applyDelta = (delta: NormalizedCatalogDelta): ImportSummaryLite => {\n for (const family of [\n \"providers\",\n \"categories\",\n \"restrictionGroups\",\n \"currencyGroups\",\n \"games\",\n \"gameCategories\",\n ] as const) {\n capFamily(family, delta[family]);\n }\n const summary = emptyLite();\n\n for (const p of delta.providers ?? []) {\n upsertInto(stores.providers, p.externalId, p, summary.providers);\n }\n for (const r of delta.restrictionGroups ?? []) {\n upsertInto(stores.restrictionGroups, r.externalId, r, summary.restrictionGroups);\n }\n for (const c of delta.currencyGroups ?? []) {\n upsertInto(stores.currencyGroups, c.externalId, c, summary.currencyGroups);\n }\n for (const c of delta.categories ?? []) {\n upsertInto(stores.categories, c.externalId, c, summary.categories);\n }\n for (const g of delta.games ?? []) {\n // Importer-parity resolution: unknown provider → auto-stub; unknown\n // restriction/currency group or category → ValidationError naming it.\n stubProvider(g.providerExternalId, summary.providers);\n if (g.subProviderExternalId) stubProvider(g.subProviderExternalId, summary.providers);\n if (g.restrictionGroupExternalId && !stores.restrictionGroups.has(g.restrictionGroupExternalId)) {\n throw validation(\n `game '${g.externalGameId}' references unknown restriction group externalId '${g.restrictionGroupExternalId}'`,\n );\n }\n if (g.currencyGroupExternalId && !stores.currencyGroups.has(g.currencyGroupExternalId)) {\n throw validation(\n `game '${g.externalGameId}' references unknown currency group externalId '${g.currencyGroupExternalId}'`,\n );\n }\n for (const ext of g.categoryExternalIds) {\n if (!stores.categories.has(ext)) {\n throw validation(\n `game '${g.externalGameId}' references unknown category externalId '${ext}'`,\n );\n }\n }\n const existing = stores.games.get(g.externalGameId);\n if (!existing) {\n stores.games.set(g.externalGameId, { game: g, status: \"active\" });\n summary.games.inserted++;\n } else if (!same(existing.game, g) || existing.status !== \"active\") {\n stores.games.set(g.externalGameId, { game: g, status: \"active\" });\n summary.games.updated++;\n } else {\n summary.games.unchanged++;\n }\n }\n const linkedGames = new Set<string>();\n for (const link of delta.gameCategories ?? []) {\n const row = stores.games.get(link.gameExternalId);\n if (!row) {\n throw validation(\n `game category link references unknown game externalId '${link.gameExternalId}'`,\n );\n }\n if (!stores.categories.has(link.categoryExternalId)) {\n throw validation(\n `game category link references unknown category externalId '${link.categoryExternalId}'`,\n );\n }\n if (!row.game.categoryExternalIds.includes(link.categoryExternalId)) {\n stores.games.set(link.gameExternalId, {\n ...row,\n game: {\n ...row.game,\n categoryExternalIds: [...row.game.categoryExternalIds, link.categoryExternalId],\n },\n });\n linkedGames.add(link.gameExternalId);\n }\n }\n summary.gameCategoryLinks = linkedGames.size;\n return summary;\n };\n\n const upserted = (s: ImportSummaryLite, family: keyof MockCatalogStores): { upserted: number } => {\n const c = s[family as keyof ImportSummaryLite] as { inserted: number; updated: number };\n return { upserted: c.inserted + c.updated };\n };\n\n const capability: PluginCatalogCapability = {\n async upsertBatch(delta) {\n return applyDelta(delta);\n },\n async upsertProviders(providers) {\n return upserted(applyDelta({ providers }), \"providers\");\n },\n async upsertRestrictionGroups(groups) {\n return upserted(applyDelta({ restrictionGroups: groups }), \"restrictionGroups\");\n },\n async upsertCurrencyGroups(groups) {\n return upserted(applyDelta({ currencyGroups: groups }), \"currencyGroups\");\n },\n async upsertCategories(categories) {\n return upserted(applyDelta({ categories }), \"categories\");\n },\n async upsertGames(games) {\n return upserted(applyDelta({ games }), \"games\");\n },\n async attachGameCategories(links) {\n applyDelta({ gameCategories: links });\n },\n async setGameStatus(externalGameId, enabled) {\n const row = stores.games.get(externalGameId);\n if (!row) {\n throw notFound(`game '${externalGameId}' not found under source 'plugin:${manifest.key}'`);\n }\n // Deactivate-only: a disable retires the row; nothing is ever deleted.\n stores.games.set(externalGameId, { ...row, status: enabled ? \"active\" : \"retired\" });\n },\n async listOwnGames(q) {\n const limit = Math.min(Math.max(1, q?.limit ?? 50), 200);\n let rows = [...stores.games.entries()].sort(([a], [b]) => (a < b ? -1 : 1));\n if (q?.cursor !== undefined) rows = rows.filter(([key]) => key > q.cursor!);\n const page = rows.slice(0, limit);\n const games: OwnGameSummary[] = page.map(([externalGameId, row]) => ({\n gameId: `mock-${externalGameId}`,\n externalGameId,\n slug: null,\n name: row.game.name ?? null,\n launchCode: row.game.launchCode ?? null,\n providerId: `mock-provider-${row.game.providerExternalId}`,\n status: row.status,\n }));\n return {\n games,\n ...(page.length === limit ? { nextCursor: page[page.length - 1]![0] } : {}),\n };\n },\n async requestImport() {\n return requestImport();\n },\n };\n\n return { capability, stores };\n}\n\n/** Apply a full snapshot the way the host `catalog:import` task does:\n * upsert every family, then RETIRE games absent from the snapshot. */\nexport function applyCatalogSnapshot(\n stores: MockCatalogStores,\n capability: PluginCatalogCapability,\n normalized: NormalizedCatalog,\n): Promise<CatalogImportSummary> {\n return (async () => {\n const startedAt = Date.now();\n const chunks = <T>(rows: T[]): T[][] => {\n const out: T[][] = [];\n for (let i = 0; i < rows.length; i += PLUGIN_CATALOG_LIMITS.maxRowsPerFamily) {\n out.push(rows.slice(i, i + PLUGIN_CATALOG_LIMITS.maxRowsPerFamily));\n }\n return out.length ? out : [[]];\n };\n const total: CatalogImportSummary = {\n providers: { inserted: 0, updated: 0 },\n categories: { inserted: 0, updated: 0 },\n restrictionGroups: { inserted: 0, updated: 0 },\n currencyGroups: { inserted: 0, updated: 0 },\n games: { inserted: 0, updated: 0, retired: 0, unchanged: 0 },\n skippedUnknownCategoryLinks: 0,\n durationMs: 0,\n };\n const add = (a: { inserted: number; updated: number }, b: { inserted: number; updated: number }) => {\n a.inserted += b.inserted;\n a.updated += b.updated;\n };\n for (const providers of chunks(normalized.providers)) {\n add(total.providers, (await capability.upsertBatch({ providers })).providers);\n }\n for (const restrictionGroups of chunks(normalized.restrictionGroups)) {\n add(\n total.restrictionGroups,\n (await capability.upsertBatch({ restrictionGroups })).restrictionGroups,\n );\n }\n for (const currencyGroups of chunks(normalized.currencyGroups)) {\n add(total.currencyGroups, (await capability.upsertBatch({ currencyGroups })).currencyGroups);\n }\n for (const categories of chunks(normalized.categories)) {\n add(total.categories, (await capability.upsertBatch({ categories })).categories);\n }\n for (const games of chunks(normalized.games)) {\n const s = (await capability.upsertBatch({ games })).games;\n total.games.inserted += s.inserted;\n total.games.updated += s.updated;\n total.games.unchanged += s.unchanged;\n }\n // Retire games absent from the snapshot (never delete).\n const incoming = new Set(normalized.games.map((g) => g.externalGameId));\n for (const [externalGameId, row] of stores.games) {\n if (row.status === \"active\" && !incoming.has(externalGameId)) {\n stores.games.set(externalGameId, { ...row, status: \"retired\" });\n total.games.retired++;\n }\n }\n total.durationMs = Date.now() - startedAt;\n return total;\n })();\n}\n","import type { PluginDefinition } from \"../define.js\";\nimport { DataScopes } from \"../data.js\";\nimport { PLUGIN_DATASET_LIMITS } from \"../datasets.js\";\nimport { PLUGIN_JOB_LIMITS } from \"../jobs.js\";\nimport { validateActionDecls } from \"../actions-validation.js\";\n\n/**\n * \"Plugin doctor lite\" — the static checks a plugin author can run without a\n * runtime: handler references, callback signature usage, dataset keyField /\n * index declarations, cron/semver syntax, surface routes. The full doctor\n * (`POST /dev/plugins/:key/validate` on a dev runtime) additionally compares\n * against the published catalog; run it via `cwe-plugin validate`.\n */\nexport interface ManifestValidationResult {\n ok: boolean;\n errors: string[];\n warnings: string[];\n}\n\nconst ROUTE_PATH_RE = /^\\/[A-Za-z0-9_\\-./:%]*$/;\nconst SURFACES = new Set([\"public\", \"player\", \"admin\", \"callback\"]);\nconst HOST_RE = /^(\\*\\.)?[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)+$/i;\nconst SEMVER_RE = /^\\d+\\.\\d+\\.\\d+(?:[-+].*)?$/;\nconst KEY_RE = /^[a-z0-9][a-z0-9-]*$/;\n\n/** 5-field cron, minute resolution — syntax-level check (host parses fully). */\nexport function isValidCronSyntax(expression: string): boolean {\n const fields = expression.trim().split(/\\s+/);\n if (fields.length !== 5) return false;\n return fields.every((f) => /^[0-9*,/-]+$/.test(f));\n}\n\nexport function validateManifest(definition: PluginDefinition): ManifestValidationResult {\n const errors: string[] = [];\n const warnings: string[] = [];\n const manifest = definition.manifest;\n const handlers = definition.handlers ?? {};\n\n if (!KEY_RE.test(manifest.key)) errors.push(`key '${manifest.key}': must be a lowercase slug`);\n if (!SEMVER_RE.test(manifest.version)) {\n errors.push(`version '${manifest.version}': not SemVer x.y.z`);\n }\n\n const emitPrefix = `plugin.${manifest.key}.`;\n for (const emitted of manifest.permissions.events.emit) {\n if (!emitted.startsWith(emitPrefix)) {\n errors.push(`emitted event '${emitted}' must be namespaced '${emitPrefix}*'`);\n }\n }\n\n const seenRoutes = new Set<string>();\n for (const route of manifest.routes ?? []) {\n const label = `${route.method} ${route.surface}${route.path}`;\n if (!SURFACES.has(route.surface)) errors.push(`route ${label}: unknown surface`);\n if (!ROUTE_PATH_RE.test(route.path) || route.path.includes(\"..\")) {\n errors.push(`route ${label}: path must look like '/lobby' or '/games/:gameKey'`);\n }\n const dup = `${route.surface} ${route.method} ${route.path}`;\n if (seenRoutes.has(dup)) errors.push(`route ${label}: duplicate declaration`);\n seenRoutes.add(dup);\n if (!handlers.routes?.[route.handler]) {\n errors.push(`route ${label}: handler '${route.handler}' not found in handlers.routes`);\n } else if (route.surface === \"callback\") {\n const src = handlers.routes[route.handler]!.toString();\n if (!src.includes(\"verifySignature\")) {\n errors.push(\n `route ${label}: callback handlers must call req.verifySignature(...) before trusting the body`,\n );\n }\n }\n }\n\n for (const [name, decl] of Object.entries(manifest.datasets ?? {})) {\n const fields = Object.keys(decl.schema.shape);\n if (!fields.includes(decl.keyField)) {\n errors.push(`dataset '${name}': keyField '${decl.keyField}' is not a schema field`);\n }\n const indexes = decl.indexes ?? [];\n if (indexes.length > PLUGIN_DATASET_LIMITS.maxIndexes) {\n errors.push(`dataset '${name}': at most ${PLUGIN_DATASET_LIMITS.maxIndexes} indexes`);\n }\n for (const idx of indexes) {\n if (!fields.includes(idx)) {\n errors.push(`dataset '${name}': index '${idx}' is not a top-level schema field`);\n }\n }\n }\n\n for (const [name, decl] of Object.entries(manifest.jobs ?? {})) {\n if (!isValidCronSyntax(decl.schedule)) {\n errors.push(`job '${name}': '${decl.schedule}' is not a 5-field cron expression`);\n }\n if (!handlers.jobs?.[decl.handler]) {\n errors.push(`job '${name}': handler '${decl.handler}' not found in handlers.jobs`);\n }\n if (\n decl.timeoutSec !== undefined &&\n (decl.timeoutSec < 1 || decl.timeoutSec > PLUGIN_JOB_LIMITS.maxTimeoutSec)\n ) {\n errors.push(`job '${name}': timeoutSec must be 1..${PLUGIN_JOB_LIMITS.maxTimeoutSec}`);\n }\n }\n\n if (manifest.install?.seedTask && !handlers.tasks?.[manifest.install.seedTask]) {\n errors.push(`install.seedTask '${manifest.install.seedTask}' not found in handlers.tasks`);\n }\n\n for (const migration of manifest.migrations ?? []) {\n if (!SEMVER_RE.test(migration.toVersion)) {\n errors.push(`migration '${migration.toVersion}': not a valid SemVer version`);\n }\n if (!handlers.migrations?.[migration.handler]) {\n errors.push(\n `migration '${migration.toVersion}': handler '${migration.handler}' not found in handlers.migrations`,\n );\n }\n }\n\n for (const host of manifest.network?.allowedHosts ?? []) {\n if (!HOST_RE.test(host)) {\n errors.push(`network.allowedHosts '${host}': exact host or '*.example.com' only`);\n }\n }\n\n const knownScopes = new Set<string>(DataScopes);\n for (const scope of manifest.permissions.dataScopes ?? []) {\n if (!knownScopes.has(scope)) {\n warnings.push(`dataScope '${scope}' is not a v2 scope — it grants no read models`);\n }\n }\n for (const dep of manifest.permissions.datasets?.read ?? []) {\n if (!dep.includes(\".\")) {\n errors.push(`permissions.datasets.read '${dep}': must be '<ownerPluginKey>.<dataset>'`);\n }\n }\n\n const adminRoutes = new Set(\n (manifest.routes ?? []).filter((r) => r.surface === \"admin\").map((r) => r.path),\n );\n const pageKeys = new Set<string>();\n for (const page of manifest.surfaces?.backoffice?.pages ?? []) {\n if (pageKeys.has(page.key)) errors.push(`surface page '${page.key}': duplicate key`);\n pageKeys.add(page.key);\n for (const block of page.blocks) {\n const ref = \"dataRoute\" in block ? block.dataRoute : block.submitRoute;\n if (!adminRoutes.has(ref)) {\n errors.push(`surface page '${page.key}': '${ref}' is not a declared admin route`);\n }\n }\n }\n for (const nav of manifest.surfaces?.backoffice?.nav ?? []) {\n if (!pageKeys.has(nav.pageKey)) {\n errors.push(`surface nav '${nav.label}': pageKey '${nav.pageKey}' has no page`);\n }\n }\n\n // v3 actions / frontend / frontendEvents — same checks the publish gate runs.\n const actionIssues = validateActionDecls(definition);\n errors.push(...actionIssues.errors);\n warnings.push(...actionIssues.warnings);\n\n return { ok: errors.length === 0, errors, warnings };\n}\n"],"mappings":"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","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@clovnet/plugin-sdk",
|
|
3
|
+
"version": "0.1.4",
|
|
4
|
+
"description": "Clovnet plugin SDK \u2014 the complete, contract-only surface for building Clovnet plugins (manifest, PluginContext, routes, datasets, jobs, migrations) plus an in-memory test harness under ./testing.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"private": false,
|
|
7
|
+
"type": "module",
|
|
8
|
+
"sideEffects": false,
|
|
9
|
+
"engines": {
|
|
10
|
+
"node": ">=20"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"dist",
|
|
14
|
+
"README.md",
|
|
15
|
+
"LICENSE"
|
|
16
|
+
],
|
|
17
|
+
"exports": {
|
|
18
|
+
".": "./src/index.ts",
|
|
19
|
+
"./testing": "./src/testing/index.ts"
|
|
20
|
+
},
|
|
21
|
+
"publishConfig": {
|
|
22
|
+
"access": "public",
|
|
23
|
+
"exports": {
|
|
24
|
+
".": {
|
|
25
|
+
"types": "./dist/index.d.ts",
|
|
26
|
+
"import": "./dist/index.js"
|
|
27
|
+
},
|
|
28
|
+
"./testing": {
|
|
29
|
+
"types": "./dist/testing.d.ts",
|
|
30
|
+
"import": "./dist/testing.js"
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"scripts": {
|
|
35
|
+
"build": "tsup",
|
|
36
|
+
"check:dist": "node scripts/check-dist.mjs",
|
|
37
|
+
"check:lockstep": "node scripts/check-lockstep.mjs",
|
|
38
|
+
"test:actions": "tsx test/actions.test.ts",
|
|
39
|
+
"test:testkit": "tsx test/testkit.test.ts",
|
|
40
|
+
"verify": "pnpm build && pnpm check:dist && pnpm check:lockstep && pnpm test:actions && pnpm test:testkit",
|
|
41
|
+
"prepack": "pnpm build && pnpm check:dist && pnpm check:lockstep"
|
|
42
|
+
},
|
|
43
|
+
"peerDependencies": {
|
|
44
|
+
"zod": "^3.23.0"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"@cwe/catalog": "workspace:*",
|
|
48
|
+
"@cwe/domain-events": "workspace:*",
|
|
49
|
+
"@cwe/providers": "workspace:*",
|
|
50
|
+
"@cwe/shared": "workspace:*",
|
|
51
|
+
"@types/node": "^20.16.5",
|
|
52
|
+
"tsup": "^8.3.0",
|
|
53
|
+
"tsx": "^4.19.1",
|
|
54
|
+
"typescript": "^5.6.2",
|
|
55
|
+
"zod": "^3.23.8",
|
|
56
|
+
"zod-to-json-schema": "^3.23.5"
|
|
57
|
+
},
|
|
58
|
+
"keywords": [
|
|
59
|
+
"casinowebengine",
|
|
60
|
+
"cwe",
|
|
61
|
+
"plugin",
|
|
62
|
+
"sdk",
|
|
63
|
+
"igaming"
|
|
64
|
+
],
|
|
65
|
+
"repository": {
|
|
66
|
+
"type": "git",
|
|
67
|
+
"url": "git+https://github.com/Clovnet/casino-runtime-core.git",
|
|
68
|
+
"directory": "packages/plugin-sdk"
|
|
69
|
+
},
|
|
70
|
+
"homepage": "https://clovnet.com",
|
|
71
|
+
"bugs": {
|
|
72
|
+
"url": "https://github.com/Clovnet/casino-runtime-core/issues"
|
|
73
|
+
},
|
|
74
|
+
"author": "Clovnet"
|
|
75
|
+
}
|