@lunora/codegen 1.0.0-alpha.4 → 1.0.0-alpha.40

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/LICENSE.md +6 -0
  2. package/__assets__/package-og.svg +1 -1
  3. package/dist/index.d.mts +1175 -25
  4. package/dist/index.d.ts +1175 -25
  5. package/dist/index.mjs +30 -24
  6. package/dist/packem_shared/{CONTAINERS_FILENAME-0K-pjNb8.mjs → CONTAINERS_FILENAME-DjpXMqhp.mjs} +1 -1
  7. package/dist/packem_shared/{CodegenDiagnosticError-54jWDxA9.mjs → CodegenDiagnosticError-DyQ5FwkM.mjs} +7 -5
  8. package/dist/packem_shared/FLAGS_FILENAME-fEZtzWXi.mjs +139 -0
  9. package/dist/packem_shared/GENERATED_HEADER-cUyu5VOU.mjs +3 -0
  10. package/dist/packem_shared/{LUNORA_ERROR_CODES-CySpQPD3.mjs → LUNORA_ERROR_CODES-DvTLozCu.mjs} +8 -1
  11. package/dist/packem_shared/MUTATORS_FILENAME-BZOfUhlY.mjs +81 -0
  12. package/dist/packem_shared/{OPENRPC_VERSION-C7zKVeKi.mjs → OPENRPC_VERSION-DxbqafcJ.mjs} +2 -2
  13. package/dist/packem_shared/QUEUES_FILENAME-Xv6Vy-Ku.mjs +145 -0
  14. package/dist/packem_shared/SCHEMA_SNAPSHOT_FILENAME-C1yHg38Z.mjs +3438 -0
  15. package/dist/packem_shared/{SCHEMA_SNAPSHOT_VERSION-DzLDbWk3.mjs → SCHEMA_SNAPSHOT_VERSION-D0ARY6rL.mjs} +18 -2
  16. package/dist/packem_shared/SHAPES_FILENAME-DOhPGi-6.mjs +94 -0
  17. package/dist/packem_shared/{WORKFLOWS_FILENAME-DRDQdhfq.mjs → WORKFLOWS_FILENAME-B2By8S4s.mjs} +70 -2
  18. package/dist/packem_shared/{buildOpenApiDocument-Cns1EkCQ.mjs → buildOpenApiDocument-_mwWOc2N.mjs} +2 -2
  19. package/dist/packem_shared/{discoverAuthApiCalls-C35R6z0T.mjs → discoverAuthApiCalls-WMx8L2FG.mjs} +1 -1
  20. package/dist/packem_shared/{discoverCrons-BL6iGuJ3.mjs → discoverCrons-D-jrpm97.mjs} +22 -20
  21. package/dist/packem_shared/{discoverFunctions-DEgAcRuD.mjs → discoverFunctions-CZ91aenA.mjs} +72 -14
  22. package/dist/packem_shared/{discoverHttpRoutes-C978pBiG.mjs → discoverHttpRoutes-DNZLDCmm.mjs} +2 -2
  23. package/dist/packem_shared/{discoverInserts-CRQdXvHO.mjs → discoverInserts-DFRJhmCv.mjs} +24 -2
  24. package/dist/packem_shared/{discoverMaskProcedures-B64zA740.mjs → discoverMaskProcedures-B5iFnZ-X.mjs} +58 -2
  25. package/dist/packem_shared/{discoverMigrations-Doj_-BAA.mjs → discoverMigrations-CdKtMlBc.mjs} +10 -4
  26. package/dist/packem_shared/{discoverNondeterministicCalls-4KiPQxQU.mjs → discoverNondeterministicCalls-C_PrQPkA.mjs} +1 -1
  27. package/dist/packem_shared/{discoverQueries-BkIi0dBD.mjs → discoverQueries-B8W9UPrt.mjs} +1 -1
  28. package/dist/packem_shared/{discoverR2sqlCalls-BpDqvcUn.mjs → discoverR2sqlCalls-Bm7Rfnf4.mjs} +1 -1
  29. package/dist/packem_shared/{discoverRlsMetadata-DpRB1HMe.mjs → discoverRlsMetadata-B4GL4ZKm.mjs} +1 -1
  30. package/dist/packem_shared/{discoverSchema-BBulgGbH.mjs → discoverSchema-DFWVgU5o.mjs} +349 -17
  31. package/dist/packem_shared/{discoverStorageRulesMetadata-DAqJUxUv.mjs → discoverStorageRulesMetadata-nosjYcvN.mjs} +1 -1
  32. package/dist/packem_shared/{GENERATED_HEADER-DF1hQcix.mjs → emit-CFz-9dcD.mjs} +1100 -122
  33. package/dist/packem_shared/{emitApp-Ci_hcJNO.mjs → emitApp-D8rVuM__.mjs} +99 -30
  34. package/dist/packem_shared/formatAdvisories-DRhEiJUz.mjs +114 -0
  35. package/dist/packem_shared/{parse-validator-tuQtHrsr.mjs → parse-validator-BSJo1HGP.mjs} +18 -6
  36. package/dist/packem_shared/redact-oTmsol5A.mjs +33 -0
  37. package/package.json +9 -7
  38. package/dist/packem_shared/SCHEMA_SNAPSHOT_FILENAME-C5UlpWKF.mjs +0 -922
  39. package/dist/packem_shared/formatAdvisories-8NIv1k0I.mjs +0 -69
@@ -1,29 +1,32 @@
1
- import { GENERATED_HEADER } from './GENERATED_HEADER-DF1hQcix.mjs';
2
-
3
- const LONG_TAIL = [
4
- ["hasAi", "ai", "ai", "Override the Workers AI binding backing `ctx.ai` (defaults to `env.AI`)."],
5
- ["hasAnalytics", "analytics", "analytics", "Override the Analytics Engine dataset backing `ctx.analytics` (defaults to `env.ANALYTICS`)."],
6
- ["hasBrowser", "browser", "browser", "Override the Browser Rendering binding backing `ctx.browser` (defaults to `env.BROWSER`)."],
7
- [
8
- "hasHyperdrive",
9
- "hyperdrive",
10
- "sql",
11
- "Wire the Hyperdrive SQL client backing `ctx.sql` — build it with `createHyperdrive` + `fromPostgresJs`/`fromNodePg`/`fromMysql2`."
12
- ],
13
- ["hasImages", "images", "images", "Override the Images binding backing `ctx.images` (defaults to `env.IMAGES`)."],
14
- ["hasKv", "kv", "kv", "Override the Workers KV binding backing `ctx.kv` (defaults to `env.KV`)."],
15
- ["hasPayments", "payment", "payment", "Wire the payment options backing `ctx.payments`."],
16
- [
17
- "hasR2sql",
18
- "r2sql",
19
- "r2sql",
20
- "Wire the R2 SQL client backing `ctx.r2sql` — build it with `createR2Sql({ accountId, apiToken, bucket })` (defaults to env `R2_SQL_TOKEN` / `R2_SQL_ACCOUNT_ID` / `R2_SQL_BUCKET`)."
21
- ],
22
- ["hasVectors", "vectors", "vectors", "Wire the Vectorize index map backing `ctx.vectors`."]
23
- ];
1
+ import { A as APP_METHOD_CAPABILITIES, G as GENERATED_HEADER } from './emit-CFz-9dcD.mjs';
2
+
3
+ const hasFlagKey = (key) => `has${key.charAt(0).toUpperCase()}${key.slice(1)}`;
4
+ const LONG_TAIL = APP_METHOD_CAPABILITIES.map(
5
+ ({ appMethod, key }) => [hasFlagKey(key), appMethod.method, appMethod.configKey, appMethod.doc]
6
+ );
24
7
  const hasAnyLongTail = (options) => LONG_TAIL.some(([flag]) => options[flag]);
8
+ const buildIdentityImports = (identity) => identity ? [`import * as lunoraIdentityContract from "../identity.js";`] : [];
9
+ const buildAccessImports = (hasAccess, hasAuth) => hasAccess ? [
10
+ `import type { CreateAccessResolverOptions } from "@lunora/cloudflare-access";`,
11
+ `import { createAccessResolver${hasAuth ? ", composeResolvers" : ""} } from "@lunora/cloudflare-access";`
12
+ ] : [];
13
+ const buildKvImports = (hasKv) => hasKv ? [`import { createKvIntrospectorFromEnv } from "@lunora/bindings/kv";`] : [];
25
14
  const buildImportLines = (options) => {
26
- const { hasAuth, hasFramework, hasGlobal, hasHyperdriveGlobal, hasScheduler, hasStorage, hasWorkflow, useUmbrella, wantsOpenApi, wantsOpenRpc } = options;
15
+ const {
16
+ hasAccess,
17
+ hasAuth,
18
+ hasFramework,
19
+ hasGlobal,
20
+ hasHyperdriveGlobal,
21
+ hasKv,
22
+ hasQueue,
23
+ hasScheduler,
24
+ hasStorage,
25
+ hasWorkflow,
26
+ useUmbrella,
27
+ wantsOpenApi,
28
+ wantsOpenRpc
29
+ } = options;
27
30
  const runtimeModule = useUmbrella ? "lunorash/runtime" : "@lunora/runtime";
28
31
  const runtimeTypeImports = ["ExecutionContextLike", "LunoraWorker", "Route", "ScheduledControllerLike", "ShardNamespaceLike", "WorkerOptions"];
29
32
  if (hasGlobal) {
@@ -42,6 +45,7 @@ const buildImportLines = (options) => {
42
45
  `import type { LunoraAuth, LunoraAuthOptions } from "@lunora/auth";`,
43
46
  `import { createAuth, createAuthAdmin, ensureMigrated, handleAuthRequest, lunoraD1Adapter } from "@lunora/auth";`
44
47
  ] : [],
48
+ ...buildAccessImports(hasAccess, hasAuth),
45
49
  ...hasGlobal ? [
46
50
  `import type { D1CtxDbOptions, D1DatabaseLike, D1Exec } from "@lunora/d1";`,
47
51
  `import { createD1CtxDb, facetGlobalColumn, listGlobalTables, readGlobalTablePage } from "@lunora/d1";`
@@ -51,15 +55,21 @@ const buildImportLines = (options) => {
51
55
  `import { createHyperdriveGlobalCtxDb } from "@lunora/hyperdrive/global";`,
52
56
  `import type { SqlCtxDbOptions, SqlExec } from "@lunora/sql-store";`
53
57
  ] : [],
58
+ ...buildKvImports(hasKv),
54
59
  ...hasScheduler ? [`import type { DurableObjectNamespaceLike } from "@lunora/scheduler";`, `import { createScheduler } from "@lunora/scheduler";`] : [],
55
60
  ...hasStorage ? [`import type { R2BucketLike, Storage } from "@lunora/storage";`, `import { createBucketStorage, createStorage } from "@lunora/storage";`] : [],
56
61
  ...hasWorkflow ? [`import { createWorkflowsRestClient } from "@lunora/workflow";`] : [],
57
62
  `import type { ${[...runtimeTypeImports].toSorted((a, b) => a.localeCompare(b)).join(", ")} } from "${runtimeModule}";`,
58
63
  `import { ${runtimeValueImports} } from "${runtimeModule}";`,
59
64
  ``,
65
+ ...buildIdentityImports(options.identity),
60
66
  ...hasGlobal || hasHyperdriveGlobal ? [`import schema from "../schema.js";`] : [],
61
67
  `import { LUNORA_CRONS } from "./crons.js";`,
62
68
  `import { LUNORA_FUNCTIONS } from "./functions.js";`,
69
+ ...hasQueue ? [
70
+ `import { createQueueCaptureSink, dispatchQueueBatch, shouldCaptureQueue } from "@lunora/queue";`,
71
+ `import { LUNORA_QUEUE_REGISTRY } from "./queues.js";`
72
+ ] : [],
63
73
  ...wantsOpenApi ? [`import { openApiSpec } from "./openapi.js";`] : [],
64
74
  ...wantsOpenRpc ? [`import { openRpcSpec } from "./openrpc.js";`] : [],
65
75
  `import { createShardDO } from "./shard.js";`
@@ -119,9 +129,10 @@ interface AuthDeclaration<Env> {
119
129
  ] : []
120
130
  ];
121
131
  const buildFieldLines = (options) => [
132
+ ...options.hasAccess ? [` private accessSelector?: Selector<Env, CreateAccessResolverOptions>;`] : [],
122
133
  ` private adminToken?: Selector<Env, string>;`,
123
134
  ...options.hasAuth ? [` private authDeclaration?: AuthDeclaration<Env>;`] : [],
124
- ` private readonly extendFns: ((env: Env) => Partial<WorkerOptions>)[] = [];`,
135
+ ` private readonly extendFns: ((env: Env, derived: Readonly<WorkerOptions>) => Partial<WorkerOptions>)[] = [];`,
125
136
  ...options.hasGlobal ? [` private globalDeclaration?: GlobalDeclaration<Env>;`] : [],
126
137
  ...options.hasHyperdriveGlobal ? [` private hyperdriveGlobalDeclaration?: HyperdriveGlobalDeclaration<Env>;`] : [],
127
138
  ` private readonly routeMap: Record<string, Route> = {};`,
@@ -139,6 +150,14 @@ const buildLongTailMethods = (options) => LONG_TAIL.filter(([flag]) => options[f
139
150
  }`
140
151
  );
141
152
  const buildMethodBlocks = (options) => [
153
+ ...options.hasAccess ? [
154
+ ` /** Wire Cloudflare Access (Zero Trust) — verifies the \`Cf-Access-Jwt-Assertion\` JWT and feeds the identity into \`ctx.auth\` / RLS via \`resolveIdentity\`. When \`.auth(...)\` is also configured, Access is composed ahead of it (Access wins when its JWT is present; everyone else falls through to the app session). */
155
+ public access(selector: Selector<Env, CreateAccessResolverOptions>): this {
156
+ this.accessSelector = selector;
157
+
158
+ return this;
159
+ }`
160
+ ] : [],
142
161
  ` /** Bearer token gating the \`/_lunora/admin/*\` endpoints the studio calls. */
143
162
  public admin(selector: Selector<Env, string>): this {
144
163
  this.adminToken = selector;
@@ -153,8 +172,8 @@ const buildMethodBlocks = (options) => [
153
172
  return this;
154
173
  }`
155
174
  ] : [],
156
- ` /** Escape hatch — merge raw \`WorkerOptions\` (anything not yet sugared) over the derived options at build time. */
157
- public extend(fn: (env: Env) => Partial<WorkerOptions>): this {
175
+ ` /** Escape hatch — merge raw \`WorkerOptions\` (anything not yet sugared) over the derived options at build time. The second \`derived\` argument is a snapshot of the options assembled so far (after \`.auth(...)\` etc.), so you can compose rather than clobber — e.g. wrap \`derived.resolveIdentity\` instead of replacing it. */
176
+ public extend(fn: (env: Env, derived: Readonly<WorkerOptions>) => Partial<WorkerOptions>): this {
158
177
  this.extendFns.push(fn);
159
178
 
160
179
  return this;
@@ -274,7 +293,7 @@ const buildShardFactoryBody = (options) => {
274
293
  const namespace = this.schedulerDeclaration?.namespace(env);
275
294
  const origin = this.schedulerDeclaration?.origin?.(env);
276
295
 
277
- return namespace && origin ? createScheduler({ namespace, originUrl: origin }) : undefined;
296
+ return namespace && origin ? createScheduler({${options.jurisdiction ? ` jurisdiction: ${JSON.stringify(options.jurisdiction)},` : ""} namespace, originUrl: origin }) : undefined;
278
297
  },
279
298
  }
280
299
  : {}),`
@@ -321,6 +340,12 @@ const buildWorkerOptionLines = (options) => [
321
340
  Object.assign(options, this.buildStorageAdmin(env));
322
341
  }`
323
342
  ] : [],
343
+ // The studio's KV browser is wired zero-config: `createKvIntrospectorFromEnv`
344
+ // scans `env` for every bound Workers KV namespace, so each `kv_namespaces`
345
+ // entry in wrangler.jsonc appears under its binding name (any name, any count)
346
+ // with no manual `createKvIntrospector` call. A deployment with no KV binding
347
+ // yields an empty namespace list rather than crashing.
348
+ ...options.hasKv ? [` options.kvIntrospector = createKvIntrospectorFromEnv(env);`] : [],
324
349
  ...options.hasAuth ? [
325
350
  ` if (this.authDeclaration) {
326
351
  options.authHandler = (request) => {
@@ -343,13 +368,52 @@ const buildWorkerOptionLines = (options) => [
343
368
 
344
369
  options.authAdmin = authInstance ? createAuthAdmin(authInstance) : undefined;
345
370
  }`
371
+ ] : [],
372
+ // Cloudflare Access — runs AFTER the auth block so it can compose ahead of
373
+ // the better-auth resolver rather than clobber it. With `.auth()` present,
374
+ // a request carrying a verified Access JWT is authenticated by Access and
375
+ // everyone else falls through to the app session; without it, Access is the
376
+ // sole resolver.
377
+ ...options.hasAccess ? [
378
+ options.hasAuth ? ` if (this.accessSelector) {
379
+ const accessResolver = createAccessResolver(this.accessSelector(env));
380
+ const fallback = options.resolveIdentity;
381
+
382
+ options.resolveIdentity = fallback ? composeResolvers(accessResolver, fallback) : accessResolver;
383
+ }` : ` if (this.accessSelector) {
384
+ options.resolveIdentity = createAccessResolver(this.accessSelector(env));
385
+ }`
346
386
  ] : []
347
387
  ];
348
388
  const buildBaseWorkerOptions = (options) => [
349
389
  ` cronJobs: LUNORA_CRONS,`,
350
390
  ` functions: LUNORA_FUNCTIONS,`,
391
+ // The declared `defineIdentity(...)` contract — wires the runtime trust
392
+ // boundary so `wrapResolverWithContract` validates every resolved identity
393
+ // against it before it becomes `ctx.auth`. Emitted only when the app declares
394
+ // a contract, so apps without one keep unchanged output.
395
+ ...options.identity ? [` identity: lunoraIdentityContract.${options.identity.exportName},`] : [],
396
+ // Schema `.jurisdiction("…")` pins every DO the worker reaches to the
397
+ // Cloudflare data-residency region. Emitted only when declared, so apps
398
+ // without it keep the un-pinned global namespace (and unchanged output).
399
+ ...options.jurisdiction ? [` jurisdiction: ${JSON.stringify(options.jurisdiction)},`] : [],
351
400
  ...options.wantsOpenApi ? [` openApiSpec,`] : [],
352
401
  ...options.wantsOpenRpc ? [` openRpcSpec,`] : [],
402
+ // The push-consumer handler backing the worker's `queue(batch, …)` entry:
403
+ // routes each delivered batch to its `defineQueue` handler. Built from
404
+ // `@lunora/queue` here (keeping the runtime decoupled) and wired only when the
405
+ // app declares push queues in `lunora/queues.ts`. In a dev environment (or with
406
+ // `LUNORA_QUEUE_CAPTURE`), every consumed message is recorded into the studio's
407
+ // Queues log via the root shard's `recordQueueMessage` admin RPC.
408
+ ...options.hasQueue ? [
409
+ ` queue: (batch: unknown, queueEnv: unknown, _context: ExecutionContextLike): Promise<void> =>`,
410
+ ` dispatchQueueBatch(batch as Parameters<typeof dispatchQueueBatch>[0], LUNORA_QUEUE_REGISTRY, {`,
411
+ ` capture: shouldCaptureQueue(queueEnv as Record<string, unknown>)`,
412
+ ` ? createQueueCaptureSink(queueEnv as Record<string, unknown>${options.jurisdiction ? `, { jurisdiction: ${JSON.stringify(options.jurisdiction)} }` : ""})`,
413
+ ` : undefined,`,
414
+ ` env: queueEnv as Record<string, unknown>,`,
415
+ ` }),`
416
+ ] : [],
353
417
  ` routes: this.routeMap,`,
354
418
  ` shardDO: this.shardSelector?.(env) ?? (undefined as unknown as ShardNamespaceLike),`
355
419
  ];
@@ -558,7 +622,12 @@ ${buildWorkerLine}
558
622
  worker ??= buildWorker(rawEnv as Env);
559
623
 
560
624
  return worker.serverQuery(request, rawEnv, reference, args, options);
561
- },
625
+ },${options.hasQueue ? `
626
+ queue: async (batch: unknown, rawEnv: unknown, context: ExecutionContextLike): Promise<void> => {
627
+ worker ??= buildWorker(rawEnv as Env);
628
+
629
+ return worker.queue?.(batch, rawEnv, context);
630
+ },` : ""}
562
631
  };
563
632
 
564
633
  if (this.emailHandler) {
@@ -581,7 +650,7 @@ ${buildBaseWorkerOptions(options).join("\n")}
581
650
  }
582
651
 
583
652
  ${workerOptionLines.join("\n\n")}${workerOptionLines.length > 0 ? "\n\n" : ""} for (const fn of this.extendFns) {
584
- Object.assign(options, fn(env));
653
+ Object.assign(options, fn(env, { ...options }));
585
654
  }
586
655
 
587
656
  return options;
@@ -0,0 +1,114 @@
1
+ import { runAdvisor } from '@lunora/advisor';
2
+
3
+ const flattenIndexes = (table) => [
4
+ ...table.indexes.map((index) => {
5
+ return { fields: index.fields, kind: "index", name: index.name, unique: index.unique };
6
+ }),
7
+ ...table.searchIndexes.map((index) => {
8
+ return { fields: [index.field, ...index.filterFields ?? []], kind: "search", name: index.name };
9
+ }),
10
+ ...table.rankIndexes.map((index) => {
11
+ return { fields: [...index.sortBy.map((key) => key.field), ...index.partitionBy ?? []], kind: "rank", name: index.name };
12
+ }),
13
+ ...table.vectorIndexes.filter((index) => index.field !== void 0).map((index) => {
14
+ return { fields: [index.field], kind: "vector", name: index.name };
15
+ })
16
+ ];
17
+ const toAdvisorSchema = (schema) => {
18
+ return {
19
+ rlsMode: schema.rlsMode,
20
+ tables: schema.tables.map((table) => {
21
+ return {
22
+ externallyManaged: table.externallyManaged ?? false,
23
+ externalSource: table.externalSource ? {
24
+ hasReconcile: table.externalSource.hasReconcile ?? false,
25
+ hasTenantBy: table.externalSource.hasTenantBy,
26
+ mode: table.externalSource.mode,
27
+ unanalyzable: table.externalSource.unanalyzable
28
+ } : void 0,
29
+ fields: Object.keys(table.shape),
30
+ indexes: flattenIndexes(table),
31
+ isPublic: table.isPublic ?? false,
32
+ name: table.name,
33
+ relations: table.relations.map((relation) => {
34
+ return {
35
+ field: relation.field,
36
+ kind: relation.kind,
37
+ name: relation.name,
38
+ onDelete: relation.onDelete,
39
+ references: relation.references,
40
+ table: relation.table
41
+ };
42
+ }),
43
+ shardKind: typeof table.shardMode === "string" ? table.shardMode : "shardBy",
44
+ softDelete: table.softDelete
45
+ };
46
+ })
47
+ };
48
+ };
49
+ const toAdvisorShapes = (shapes) => shapes.map((shape) => {
50
+ return { exportName: shape.exportName, file: `lunora/${shape.filePath}.ts`, table: shape.table };
51
+ });
52
+ const lintSchema = (options) => runAdvisor(
53
+ {
54
+ adminRoutes: options.adminRoutes,
55
+ aiRawRuns: options.aiRawRuns,
56
+ aiToolSideEffects: options.aiToolSideEffects,
57
+ argumentDerivedFetches: options.argumentDerivedFetches,
58
+ argValidators: options.argumentValidators,
59
+ authApiCalls: options.authApiCalls,
60
+ authConfigs: options.authConfigs,
61
+ browserUrlAccesses: options.browserUrlAccesses,
62
+ configCalls: options.configCalls,
63
+ containerKeyAccesses: options.containerKeyAccesses,
64
+ containerOverrides: options.containerOverrides,
65
+ containers: options.containers,
66
+ failOpenGuards: options.failOpenGuards,
67
+ flagSecurityDefaults: options.flagSecurityDefaults,
68
+ httpActionGuards: options.httpActionGuards,
69
+ httpHeaderWrites: options.httpHeaderWrites,
70
+ identityClaimReads: options.identityClaimReads,
71
+ imageDeliveryUrlAccesses: options.imageDeliveryUrlAccesses,
72
+ inserts: options.inserts,
73
+ kvKeyAccesses: options.kvKeyAccesses,
74
+ mailRecipientAccesses: options.mailRecipientAccesses,
75
+ maskProcedures: options.maskProcedures,
76
+ maskStrategies: options.maskStrategies,
77
+ mutatorWrites: options.mutatorWrites,
78
+ nondeterministicCalls: options.nondeterministicCalls,
79
+ normalizeIdAuthorizations: options.normalizeIdAuthorizations,
80
+ ownerFieldWrites: options.ownerFieldWrites,
81
+ paymentWebhooks: options.paymentWebhooks,
82
+ privilegedDispatches: options.privilegedDispatches,
83
+ procedureProtections: options.procedureProtections,
84
+ queries: options.queries ?? [],
85
+ queues: options.queues,
86
+ r2sqlCalls: options.r2sqlCalls,
87
+ ratelimitKeySelectors: options.ratelimitKeySelectors,
88
+ rawRowReturns: options.rawRowReturns,
89
+ relationLoads: options.relationLoads,
90
+ rlsProcedures: options.rlsProcedures,
91
+ schema: toAdvisorSchema(options.schema),
92
+ secretLiterals: options.secretLiterals,
93
+ shapes: options.shapes === void 0 ? void 0 : toAdvisorShapes(options.shapes),
94
+ softDeleteReads: options.softDeleteReads,
95
+ sqlInterpolations: options.sqlInterpolations,
96
+ storageKeyAccesses: options.storageKeyAccesses,
97
+ storageUploads: options.storageUploads,
98
+ vectorNamespaceAccesses: options.vectorNamespaceAccesses,
99
+ workflowCalls: options.workflowCalls,
100
+ workflows: options.workflows,
101
+ wranglerVariables: options.wranglerVariables
102
+ },
103
+ { source: "static" }
104
+ );
105
+ const formatAdvisories = (findings) => {
106
+ if (findings.length === 0) {
107
+ return "";
108
+ }
109
+ const header = `@lunora/codegen: ${String(findings.length)} schema advisor finding${findings.length === 1 ? "" : "s"}`;
110
+ const lines = findings.map((finding) => ` [${finding.level}] ${finding.name}: ${finding.detail}`);
111
+ return [header, ...lines].join("\n");
112
+ };
113
+
114
+ export { formatAdvisories, lintSchema };
@@ -1,3 +1,4 @@
1
+ import { LunoraError } from '@lunora/errors';
1
2
  import { Node } from 'ts-morph';
2
3
 
3
4
  const FIELD_NAME_RE = /^[A-Za-z_$][\w$]*$/u;
@@ -53,13 +54,22 @@ const parseObjectShape = (object) => {
53
54
  }
54
55
  const fieldName = property.getName();
55
56
  if (!FIELD_NAME_RE.test(fieldName)) {
56
- throw new Error(`@lunora/codegen: field name is not a valid JS identifier: ${JSON.stringify(fieldName)}`);
57
+ throw new LunoraError("INTERNAL", `@lunora/codegen: field name is not a valid JS identifier: ${JSON.stringify(fieldName)}`);
57
58
  }
58
59
  out[fieldName] = parseValidator(initializer);
59
60
  }
60
61
  return out;
61
62
  };
62
63
  const parseArgument = (argument, fallback) => argument && Node.isExpression(argument) ? parseValidator(argument) : fallback;
64
+ const renderLiteralSource = (node) => {
65
+ if (node === void 0) {
66
+ return "undefined";
67
+ }
68
+ if (Node.isStringLiteral(node) || Node.isNoSubstitutionTemplateLiteral(node)) {
69
+ return JSON.stringify(node.getLiteralValue());
70
+ }
71
+ return node.getText();
72
+ };
63
73
  const parseBuilderMember = (member, args) => {
64
74
  if (SCALAR_KINDS.has(member)) {
65
75
  return { kind: member };
@@ -78,9 +88,10 @@ const parseBuilderMember = (member, args) => {
78
88
  case "literal": {
79
89
  return {
80
90
  kind: "literal",
81
- // Captures the source text — for string/number/boolean/null literals
82
- // this matches the TS type representation directly.
83
- literalValue: first ? first.getText() : "undefined"
91
+ // Canonical source text — strings/templates are re-encoded via
92
+ // JSON.stringify (see renderLiteralSource) so escapes/backticks
93
+ // survive; numbers/booleans/null keep their verbatim text.
94
+ literalValue: renderLiteralSource(first)
84
95
  };
85
96
  }
86
97
  case "object": {
@@ -106,7 +117,7 @@ const parseBuilderMember = (member, args) => {
106
117
  };
107
118
  }
108
119
  default: {
109
- throw new Error(`Unsupported validator kind: ${member}`);
120
+ throw new LunoraError("INTERNAL", `Unsupported validator kind: ${member}`);
110
121
  }
111
122
  }
112
123
  };
@@ -124,7 +135,8 @@ const parseValidatorCall = (call) => {
124
135
  }
125
136
  if (TRANSPARENT_MODIFIERS.has(member)) {
126
137
  const receiver = callee.getExpression();
127
- return Node.isExpression(receiver) ? parseValidator(receiver) : { kind: "any" };
138
+ const base = Node.isExpression(receiver) ? parseValidator(receiver) : { kind: "any" };
139
+ return member === "check" ? { ...base, hasRefinement: true } : base;
128
140
  }
129
141
  return parseBuilderMember(member, args);
130
142
  };
@@ -0,0 +1,33 @@
1
+ const HIGH_ENTROPY_TOKEN_RE = /[\w+/=-]{40,}/u;
2
+ const LOWER_RE = /[a-z]/u;
3
+ const UPPER_RE = /[A-Z]/u;
4
+ const DIGIT_RE = /\d/u;
5
+ const HEX_SECRET_RE = /\b(?:[\da-f]{32,}|[\dA-F]{32,})\b/u;
6
+ const isHighEntropy = (value) => {
7
+ const token = HIGH_ENTROPY_TOKEN_RE.exec(value)?.[0];
8
+ if (token === void 0) {
9
+ return false;
10
+ }
11
+ return LOWER_RE.test(token) && UPPER_RE.test(token) && DIGIT_RE.test(token);
12
+ };
13
+ const isHexSecret = (value) => HEX_SECRET_RE.test(value);
14
+ const STRIPE_LIVE_KEY_RE = /\b(?:sk|rk)_live_[\dA-Za-z]{20,}/u;
15
+ const AWS_ACCESS_KEY_RE = /\bAKIA[\dA-Z]{16}\b/u;
16
+ const GITHUB_TOKEN_RE = /\bgh[posru]_[\dA-Za-z]{36,}/u;
17
+ const OPENAI_KEY_RE = /\bsk-[\dA-Za-z]{32,}/u;
18
+ const SLACK_TOKEN_RE = /\bxox[abprs]-[\dA-Za-z-]{10,}/u;
19
+ const PRIVATE_KEY_RE = /-----BEGIN(?: [A-Z]+)? PRIVATE KEY-----/u;
20
+ const SECRET_RULES = [
21
+ { kind: "stripe_live_key", test: (value) => STRIPE_LIVE_KEY_RE.test(value) },
22
+ { kind: "aws_access_key", test: (value) => AWS_ACCESS_KEY_RE.test(value) },
23
+ { kind: "github_token", test: (value) => GITHUB_TOKEN_RE.test(value) },
24
+ { kind: "openai_key", test: (value) => OPENAI_KEY_RE.test(value) },
25
+ { kind: "slack_token", test: (value) => SLACK_TOKEN_RE.test(value) },
26
+ { kind: "private_key", test: (value) => PRIVATE_KEY_RE.test(value) },
27
+ { kind: "high_entropy", test: isHighEntropy },
28
+ { kind: "hex_secret", test: isHexSecret }
29
+ ];
30
+ const secretKindOf = (value) => SECRET_RULES.find((rule) => rule.test(value))?.kind;
31
+ const redact = (value) => `${value.slice(0, 4)}…(${String(value.length)} chars)`;
32
+
33
+ export { SECRET_RULES, redact, secretKindOf };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lunora/codegen",
3
- "version": "1.0.0-alpha.4",
3
+ "version": "1.0.0-alpha.40",
4
4
  "description": "Code generator for Lunora: emits _generated/{api,server,dataModel}.ts from your schema",
5
5
  "keywords": [
6
6
  "cloudflare",
@@ -25,7 +25,7 @@
25
25
  "directory": "packages/codegen"
26
26
  },
27
27
  "files": [
28
- "dist",
28
+ "./dist",
29
29
  "__assets__",
30
30
  "README.md",
31
31
  "LICENSE.md"
@@ -46,11 +46,13 @@
46
46
  "access": "public"
47
47
  },
48
48
  "dependencies": {
49
- "@lunora/advisor": "1.0.0-alpha.3",
50
- "@lunora/container": "1.0.0-alpha.1",
51
- "@lunora/scheduler": "1.0.0-alpha.1",
52
- "@lunora/values": "1.0.0-alpha.1",
53
- "@lunora/workflow": "1.0.0-alpha.1",
49
+ "@lunora/advisor": "1.0.0-alpha.26",
50
+ "@lunora/container": "1.0.0-alpha.9",
51
+ "@lunora/errors": "1.0.0-alpha.4",
52
+ "@lunora/queue": "1.0.0-alpha.7",
53
+ "@lunora/scheduler": "1.0.0-alpha.8",
54
+ "@lunora/values": "1.0.0-alpha.7",
55
+ "@lunora/workflow": "1.0.0-alpha.9",
54
56
  "ts-morph": "^28.0.0"
55
57
  },
56
58
  "engines": {