@lunora/codegen 1.0.0-alpha.17 → 1.0.0-alpha.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/index.d.mts CHANGED
@@ -363,6 +363,24 @@ interface WorkflowIR {
363
363
  * definition overrides it.
364
364
  */
365
365
  name: string;
366
+ /**
367
+ * Durable step labels lifted from the handler body — the first string-literal
368
+ * argument of every `ctx.step.do` / `.sleep` / `.sleepUntil` / `.waitForEvent`
369
+ * call. Feeds the duplicate-step-name lint, which flags a name used twice
370
+ * (Cloudflare memoizes by name, so the second call silently returns the
371
+ * first's cached result). Calls with a non-literal name are omitted (not
372
+ * statically comparable).
373
+ */
374
+ steps: ReadonlyArray<WorkflowStepIR>;
375
+ }
376
+ /** One durable step call lifted from a workflow handler body (the use side of {@link WorkflowIR.steps}). */
377
+ interface WorkflowStepIR {
378
+ /** 1-based line of the durable step call. */
379
+ line: number;
380
+ /** The native step method invoked: `do` / `sleep` / `sleepUntil` / `waitForEvent`. */
381
+ method: string;
382
+ /** The step's static label (the first string-literal argument). */
383
+ name: string;
366
384
  }
367
385
  /**
368
386
  * A queue lifted from a `defineQueue()` export in `lunora/queues.ts`. Carries
@@ -828,8 +846,9 @@ interface ProjectIR {
828
846
  * feed `table_without_insert`, authApi calls feed `auth_api_call_without_headers`,
829
847
  * rls procedure snapshots feed `rls_uncovered_table`, and mask procedure
830
848
  * snapshots feed `mask_uncovered_pii_column`; declared containers
831
- * feed the `container_*` lints; declared workflows + `ctx.workflows.get(...)` call
832
- * sites feed the `workflow_unused` / `workflow_unknown_target` lints; non-deterministic
849
+ * feed the `container_*` lints; declared workflows (with their durable step labels)
850
+ * + `ctx.workflows.get(...)` call sites feed the `workflow_unused` /
851
+ * `workflow_unknown_target` / duplicate-step-name lints; non-deterministic
833
852
  * calls inside query/mutation handlers feed the `nondeterministic_query_mutation` lint
834
853
  * (all default empty for callers that don't analyze functions/containers/workflows).
835
854
  * The IR types are structurally identical to the advisor's evidence types so they
@@ -1099,6 +1118,13 @@ declare const emitApi: (functions: ReadonlyArray<FunctionIR>, workflows?: Readon
1099
1118
  declare const emitCollections: (shapes: ReadonlyArray<ShapeIR>, hasDatabase: boolean, useUmbrella?: boolean) => string;
1100
1119
  interface EmitServerOptions {
1101
1120
  containers?: ReadonlyArray<ContainerIR>;
1121
+ /**
1122
+ * A `lunora/` source reads `ctx.access` — wires the verified Cloudflare Access
1123
+ * facade (`@lunora/cloudflare-access/context`) onto every ctx. Distinct from
1124
+ * `emitApp`'s `hasAccess` (which gates the worker's `.access()` resolveIdentity
1125
+ * method); this one gates the per-request `ctx.access` read surface.
1126
+ */
1127
+ hasAccessFacade?: boolean;
1102
1128
  hasAi?: boolean;
1103
1129
  /** A `lunora/` source uses `@lunora/bindings/analytics` / `ctx.analytics` — wires the write helper onto every ctx. */
1104
1130
  hasAnalytics?: boolean;
@@ -1127,6 +1153,7 @@ interface EmitServerOptions {
1127
1153
  }
1128
1154
  declare const emitServer: ({
1129
1155
  containers,
1156
+ hasAccessFacade,
1130
1157
  hasAi,
1131
1158
  hasAnalytics,
1132
1159
  hasBrowser,
@@ -1187,6 +1214,8 @@ interface EmitShardOptions {
1187
1214
  key: string;
1188
1215
  type: "boolean" | "number" | "object" | "string";
1189
1216
  }>;
1217
+ /** A `lunora/` source reads `ctx.access` — wires the verified Cloudflare Access facade onto every ctx. */
1218
+ hasAccessFacade?: boolean;
1190
1219
  hasAi?: boolean;
1191
1220
  /** A `lunora/` source reads `ctx.analytics` — wires the Analytics Engine write helper onto every ctx. */
1192
1221
  hasAnalytics?: boolean;
@@ -1224,6 +1253,7 @@ declare const emitShard: ({
1224
1253
  advisories,
1225
1254
  containers,
1226
1255
  flagKeys,
1256
+ hasAccessFacade,
1227
1257
  hasAi,
1228
1258
  hasAnalytics,
1229
1259
  hasBrowser,
@@ -1304,6 +1334,8 @@ declare const emitVectors: (vectorIndexes: ReadonlyArray<VectorIndexIR>) => stri
1304
1334
  declare const emitWranglerCronTriggers: (crons: ReadonlyArray<CronJobIR>) => string[];
1305
1335
  /** Which capability methods the generated `defineApp` builder exposes — one flag per package-backed feature the app actually uses. */
1306
1336
  interface EmitAppOptions {
1337
+ /** App depends on `@lunora/cloudflare-access` → emit `.access()` (wire the Cloudflare Access `resolveIdentity`, composed ahead of `@lunora/auth` when both are present). */
1338
+ hasAccess: boolean;
1307
1339
  /** App uses `@lunora/ai` / `ctx.ai` → emit `.ai()` (override the Workers AI binding backing `ctx.ai`). */
1308
1340
  hasAi: boolean;
1309
1341
  /** App uses `@lunora/bindings/analytics` / `ctx.analytics` → emit `.analytics()` (override the dataset backing `ctx.analytics`). */
package/dist/index.d.ts CHANGED
@@ -363,6 +363,24 @@ interface WorkflowIR {
363
363
  * definition overrides it.
364
364
  */
365
365
  name: string;
366
+ /**
367
+ * Durable step labels lifted from the handler body — the first string-literal
368
+ * argument of every `ctx.step.do` / `.sleep` / `.sleepUntil` / `.waitForEvent`
369
+ * call. Feeds the duplicate-step-name lint, which flags a name used twice
370
+ * (Cloudflare memoizes by name, so the second call silently returns the
371
+ * first's cached result). Calls with a non-literal name are omitted (not
372
+ * statically comparable).
373
+ */
374
+ steps: ReadonlyArray<WorkflowStepIR>;
375
+ }
376
+ /** One durable step call lifted from a workflow handler body (the use side of {@link WorkflowIR.steps}). */
377
+ interface WorkflowStepIR {
378
+ /** 1-based line of the durable step call. */
379
+ line: number;
380
+ /** The native step method invoked: `do` / `sleep` / `sleepUntil` / `waitForEvent`. */
381
+ method: string;
382
+ /** The step's static label (the first string-literal argument). */
383
+ name: string;
366
384
  }
367
385
  /**
368
386
  * A queue lifted from a `defineQueue()` export in `lunora/queues.ts`. Carries
@@ -828,8 +846,9 @@ interface ProjectIR {
828
846
  * feed `table_without_insert`, authApi calls feed `auth_api_call_without_headers`,
829
847
  * rls procedure snapshots feed `rls_uncovered_table`, and mask procedure
830
848
  * snapshots feed `mask_uncovered_pii_column`; declared containers
831
- * feed the `container_*` lints; declared workflows + `ctx.workflows.get(...)` call
832
- * sites feed the `workflow_unused` / `workflow_unknown_target` lints; non-deterministic
849
+ * feed the `container_*` lints; declared workflows (with their durable step labels)
850
+ * + `ctx.workflows.get(...)` call sites feed the `workflow_unused` /
851
+ * `workflow_unknown_target` / duplicate-step-name lints; non-deterministic
833
852
  * calls inside query/mutation handlers feed the `nondeterministic_query_mutation` lint
834
853
  * (all default empty for callers that don't analyze functions/containers/workflows).
835
854
  * The IR types are structurally identical to the advisor's evidence types so they
@@ -1099,6 +1118,13 @@ declare const emitApi: (functions: ReadonlyArray<FunctionIR>, workflows?: Readon
1099
1118
  declare const emitCollections: (shapes: ReadonlyArray<ShapeIR>, hasDatabase: boolean, useUmbrella?: boolean) => string;
1100
1119
  interface EmitServerOptions {
1101
1120
  containers?: ReadonlyArray<ContainerIR>;
1121
+ /**
1122
+ * A `lunora/` source reads `ctx.access` — wires the verified Cloudflare Access
1123
+ * facade (`@lunora/cloudflare-access/context`) onto every ctx. Distinct from
1124
+ * `emitApp`'s `hasAccess` (which gates the worker's `.access()` resolveIdentity
1125
+ * method); this one gates the per-request `ctx.access` read surface.
1126
+ */
1127
+ hasAccessFacade?: boolean;
1102
1128
  hasAi?: boolean;
1103
1129
  /** A `lunora/` source uses `@lunora/bindings/analytics` / `ctx.analytics` — wires the write helper onto every ctx. */
1104
1130
  hasAnalytics?: boolean;
@@ -1127,6 +1153,7 @@ interface EmitServerOptions {
1127
1153
  }
1128
1154
  declare const emitServer: ({
1129
1155
  containers,
1156
+ hasAccessFacade,
1130
1157
  hasAi,
1131
1158
  hasAnalytics,
1132
1159
  hasBrowser,
@@ -1187,6 +1214,8 @@ interface EmitShardOptions {
1187
1214
  key: string;
1188
1215
  type: "boolean" | "number" | "object" | "string";
1189
1216
  }>;
1217
+ /** A `lunora/` source reads `ctx.access` — wires the verified Cloudflare Access facade onto every ctx. */
1218
+ hasAccessFacade?: boolean;
1190
1219
  hasAi?: boolean;
1191
1220
  /** A `lunora/` source reads `ctx.analytics` — wires the Analytics Engine write helper onto every ctx. */
1192
1221
  hasAnalytics?: boolean;
@@ -1224,6 +1253,7 @@ declare const emitShard: ({
1224
1253
  advisories,
1225
1254
  containers,
1226
1255
  flagKeys,
1256
+ hasAccessFacade,
1227
1257
  hasAi,
1228
1258
  hasAnalytics,
1229
1259
  hasBrowser,
@@ -1304,6 +1334,8 @@ declare const emitVectors: (vectorIndexes: ReadonlyArray<VectorIndexIR>) => stri
1304
1334
  declare const emitWranglerCronTriggers: (crons: ReadonlyArray<CronJobIR>) => string[];
1305
1335
  /** Which capability methods the generated `defineApp` builder exposes — one flag per package-backed feature the app actually uses. */
1306
1336
  interface EmitAppOptions {
1337
+ /** App depends on `@lunora/cloudflare-access` → emit `.access()` (wire the Cloudflare Access `resolveIdentity`, composed ahead of `@lunora/auth` when both are present). */
1338
+ hasAccess: boolean;
1307
1339
  /** App uses `@lunora/ai` / `ctx.ai` → emit `.ai()` (override the Workers AI binding backing `ctx.ai`). */
1308
1340
  hasAi: boolean;
1309
1341
  /** App uses `@lunora/bindings/analytics` / `ctx.analytics` → emit `.analytics()` (override the dataset backing `ctx.analytics`). */
package/dist/index.mjs CHANGED
@@ -18,12 +18,12 @@ export { discoverRlsMetadata, default as discoverRlsProcedures } from './packem_
18
18
  export { default as discoverSchema } from './packem_shared/discoverSchema-BnWHHJ4T.mjs';
19
19
  export { SHAPES_FILENAME, discoverShapes } from './packem_shared/SHAPES_FILENAME-ChV7MqgE.mjs';
20
20
  export { default as discoverStorageRulesMetadata } from './packem_shared/discoverStorageRulesMetadata-Da8BKXcI.mjs';
21
- export { WORKFLOWS_FILENAME, discoverWorkflows } from './packem_shared/WORKFLOWS_FILENAME-D62dcBGg.mjs';
22
- export { GENERATED_HEADER, emitApi, emitCollections, emitContainers, emitCrons, emitDataModel, emitDrizzleSchema, emitFunctions, emitServer, emitShard, emitVectors, emitWorkflows, emitWranglerCronTriggers } from './packem_shared/GENERATED_HEADER-BoVKLcnE.mjs';
23
- export { emitApp } from './packem_shared/emitApp-Bh0HsQVK.mjs';
24
- export { buildOpenApiDocument, emitOpenApi, emitOpenApiModule } from './packem_shared/buildOpenApiDocument-CB2brlbc.mjs';
25
- export { OPENRPC_VERSION, buildOpenRpcDocument, emitOpenRpc, emitOpenRpcModule } from './packem_shared/OPENRPC_VERSION-BMCOe3B6.mjs';
26
- export { SCHEMA_SNAPSHOT_FILENAME, createCodegenProject, refreshCodegenProject, runCodegen } from './packem_shared/SCHEMA_SNAPSHOT_FILENAME-BA9tjjOl.mjs';
21
+ export { WORKFLOWS_FILENAME, discoverWorkflows } from './packem_shared/WORKFLOWS_FILENAME-CCisG0Vy.mjs';
22
+ export { GENERATED_HEADER, emitApi, emitCollections, emitContainers, emitCrons, emitDataModel, emitDrizzleSchema, emitFunctions, emitServer, emitShard, emitVectors, emitWorkflows, emitWranglerCronTriggers } from './packem_shared/GENERATED_HEADER-D-AbhmRh.mjs';
23
+ export { emitApp } from './packem_shared/emitApp-CV81L74c.mjs';
24
+ export { buildOpenApiDocument, emitOpenApi, emitOpenApiModule } from './packem_shared/buildOpenApiDocument-DoK7_x1g.mjs';
25
+ export { OPENRPC_VERSION, buildOpenRpcDocument, emitOpenRpc, emitOpenRpcModule } from './packem_shared/OPENRPC_VERSION-DDT4mIzD.mjs';
26
+ export { SCHEMA_SNAPSHOT_FILENAME, createCodegenProject, refreshCodegenProject, runCodegen } from './packem_shared/SCHEMA_SNAPSHOT_FILENAME-BZH-O4uh.mjs';
27
27
  export { SCHEMA_SNAPSHOT_VERSION, SchemaSnapshotParseError, buildSchemaSnapshot, diffSchemaSnapshots, evaluateSchemaDrift, parseSchemaSnapshot, serializeSchemaSnapshot } from './packem_shared/SCHEMA_SNAPSHOT_VERSION-wWGP2_5E.mjs';
28
28
  export { schemaFromIr } from './packem_shared/schemaFromIr-DTYsLBaA.mjs';
29
29
  export { LUNORA_ERROR_CODES, validatorIrToJsonSchema } from './packem_shared/LUNORA_ERROR_CODES-CySpQPD3.mjs';
@@ -749,6 +749,7 @@ const buildStorageBucketNames = (schema, ruleBuckets = []) => {
749
749
  };
750
750
  const emitServer = ({
751
751
  containers = [],
752
+ hasAccessFacade = false,
752
753
  hasAi = false,
753
754
  hasAnalytics = false,
754
755
  hasBrowser = false,
@@ -821,6 +822,9 @@ ${envBindingFields}` : ""}
821
822
  export type Env = CloudflareBindings;`;
822
823
  const kvContextField = hasKv ? `
823
824
  readonly kv: import("@lunora/bindings/kv").Kv;` : "";
825
+ const accessContextField = hasAccessFacade ? `
826
+ /** Verified Cloudflare Access identity — a synchronous facade over the resolved claims (email / groups / hasGroup / claims). Anonymous when no Access token is present. */
827
+ readonly access: import("@lunora/cloudflare-access/context").AccessFacade;` : "";
824
828
  const flagsContextField = hasFlags ? `
825
829
  /** Feature-flag evaluation (OpenFeature). Reads are memoized per request; evaluations never throw — a provider error resolves to the supplied default. */
826
830
  readonly flags: import("@lunora/flags").LunoraFlags;` : "";
@@ -938,19 +942,19 @@ type TypedTableGet = <T extends TableName>(id: IdOfTable<T>) => Promise<Doc<T> |
938
942
  export interface QueryCtx extends Omit<QueryCtxBase, "db" | "storage"> {
939
943
  readonly db: Omit<DatabaseReader, "query" | "get"> & DatabaseReaderFacade & { query: TypedTableQuery; get: TypedTableGet };
940
944
  readonly orm: OrmReader;
941
- readonly storage: ReadOnlyStorage<StorageBucketName>;${kvContextField}${flagsContextField}${analyticsContextField}
945
+ readonly storage: ReadOnlyStorage<StorageBucketName>;${accessContextField}${kvContextField}${flagsContextField}${analyticsContextField}
942
946
  }
943
947
 
944
948
  export interface MutationCtx extends Omit<MutationCtxBase, "db" | "storage"${workflowsOmit}> {
945
949
  readonly db: Omit<DatabaseWriter, "query" | "get"> & DatabaseWriterFacade & { query: TypedTableQuery; get: TypedTableGet };
946
950
  readonly orm: OrmWriter;
947
- readonly storage: ReadOnlyStorage<StorageBucketName>;${kvContextField}${flagsContextField}${analyticsContextField}${workflowsContextField}${queuesContextField}
951
+ readonly storage: ReadOnlyStorage<StorageBucketName>;${accessContextField}${kvContextField}${flagsContextField}${analyticsContextField}${workflowsContextField}${queuesContextField}
948
952
  }
949
953
 
950
954
  export interface ActionCtx extends Omit<ActionCtxBase, "db" | "storage"${workflowsOmit}> {
951
955
  readonly db: Omit<DatabaseWriter, "query" | "get"> & DatabaseWriterFacade & { query: TypedTableQuery; get: TypedTableGet };
952
956
  readonly orm: OrmWriter;
953
- readonly storage: StorageBase<StorageBucketName>;${aiActionField}${paymentsActionField}${containersActionField}${kvContextField}${flagsContextField}${hyperdriveActionField}${browserActionField}${imagesActionField}${analyticsContextField}${pipelinesActionField}${r2sqlActionField}${workflowsContextField}${queuesContextField}
957
+ readonly storage: StorageBase<StorageBucketName>;${accessContextField}${aiActionField}${paymentsActionField}${containersActionField}${kvContextField}${flagsContextField}${hyperdriveActionField}${browserActionField}${imagesActionField}${analyticsContextField}${pipelinesActionField}${r2sqlActionField}${workflowsContextField}${queuesContextField}
954
958
  }
955
959
 
956
960
  /**
@@ -1361,6 +1365,21 @@ const emitFlagsFragments = (hasFlags) => {
1361
1365
  stub: ""
1362
1366
  };
1363
1367
  };
1368
+ const emitAccessFragments = (hasAccessFacade) => {
1369
+ if (!hasAccessFacade) {
1370
+ return EMPTY_HELPER_FRAGMENTS;
1371
+ }
1372
+ return {
1373
+ build: `
1374
+ const access = accessFacade(identity, userId);
1375
+ `,
1376
+ configField: "",
1377
+ contextField: `
1378
+ access,`,
1379
+ importLines: [`import { accessFacade } from "@lunora/cloudflare-access/context";`],
1380
+ stub: ""
1381
+ };
1382
+ };
1364
1383
  const emitFlagsOverrides = (flagKeys, hasFlags) => {
1365
1384
  if (!hasFlags) {
1366
1385
  return { constant: "", evaluateOverride: "", subscriptionOverride: "" };
@@ -1954,6 +1973,7 @@ const emitShard = ({
1954
1973
  advisories = [],
1955
1974
  containers = [],
1956
1975
  flagKeys = [],
1976
+ hasAccessFacade = false,
1957
1977
  hasAi = false,
1958
1978
  hasAnalytics = false,
1959
1979
  hasBrowser = false,
@@ -1979,6 +1999,7 @@ const emitShard = ({
1979
1999
  const hasMutators = mutators.length > 0;
1980
2000
  const hasShapes = shapes.length > 0;
1981
2001
  const { build: aiBuild, configField: aiConfigField, contextField: aiContextField, stub: aiStub } = emitAiFragments(hasAi);
2002
+ const accessFragments = emitAccessFragments(hasAccessFacade);
1982
2003
  const kvFragments = emitKvFragments(hasKv);
1983
2004
  const flagsFragments = emitFlagsFragments(hasFlags);
1984
2005
  const flagsOverrides = emitFlagsOverrides(flagKeys, hasFlags);
@@ -2126,6 +2147,7 @@ const LUNORA_RLS_READ_REGISTRY = buildRlsReadRegistry(Object.values(LUNORA_FUNCT
2126
2147
  importLines.push(`import type { AiBindingLike, LunoraAi } from "@lunora/ai";`, `import { createAi } from "@lunora/ai";`);
2127
2148
  }
2128
2149
  importLines.push(
2150
+ ...accessFragments.importLines,
2129
2151
  ...kvFragments.importLines,
2130
2152
  ...flagsFragments.importLines,
2131
2153
  ...analyticsFragments.importLines,
@@ -2314,8 +2336,8 @@ ${schema.tables.map(
2314
2336
  const secretsBuild = `
2315
2337
  const secrets = createSecrets(env);
2316
2338
  `;
2317
- const everyContextBuild = `${kvFragments.build}${flagsFragments.build}${analyticsFragments.build}${secretsBuild}`;
2318
- const everyContextField = `${kvFragments.contextField}${flagsFragments.contextField}${analyticsFragments.contextField}
2339
+ const everyContextBuild = `${accessFragments.build}${kvFragments.build}${flagsFragments.build}${analyticsFragments.build}${secretsBuild}`;
2340
+ const everyContextField = `${accessFragments.contextField}${kvFragments.contextField}${flagsFragments.contextField}${analyticsFragments.contextField}
2319
2341
  secrets,`;
2320
2342
  const actionOnlyHasAny = hasImages || hasHyperdrive || hasBrowser || hasR2sql || hasPipelines;
2321
2343
  const actionOnlyBlock = actionOnlyHasAny ? `
@@ -1,4 +1,4 @@
1
- import { GENERATED_HEADER } from './GENERATED_HEADER-BoVKLcnE.mjs';
1
+ import { GENERATED_HEADER } from './GENERATED_HEADER-D-AbhmRh.mjs';
2
2
  import { s as sanitizeNamespace } from './paths-BRd6JHuF.mjs';
3
3
  import { argsObjectSchema, LUNORA_ERROR_CODES } from './LUNORA_ERROR_CODES-CySpQPD3.mjs';
4
4
 
@@ -22,11 +22,11 @@ import discoverSchema from './discoverSchema-BnWHHJ4T.mjs';
22
22
  import { discoverShapes } from './SHAPES_FILENAME-ChV7MqgE.mjs';
23
23
  import discoverStorageRulesMetadata from './discoverStorageRulesMetadata-Da8BKXcI.mjs';
24
24
  import { e as enclosingExportName$1 } from './discover-ast-CT6BgBr4.mjs';
25
- import { discoverWorkflows } from './WORKFLOWS_FILENAME-D62dcBGg.mjs';
26
- import { buildStorageColumns, emitDataModel, emitApi, emitServer, emitFunctions, emitShard, emitCollections, emitContainers, emitWorkflows, emitQueues, emitCrons, emitVectors, emitDrizzleSchema, emitSeed, emitWranglerCronTriggers } from './GENERATED_HEADER-BoVKLcnE.mjs';
27
- import { emitApp } from './emitApp-Bh0HsQVK.mjs';
28
- import { buildOpenApiDocument, emitOpenApiModule } from './buildOpenApiDocument-CB2brlbc.mjs';
29
- import { buildOpenRpcDocument, emitOpenRpcModule } from './OPENRPC_VERSION-BMCOe3B6.mjs';
25
+ import { discoverWorkflows } from './WORKFLOWS_FILENAME-CCisG0Vy.mjs';
26
+ import { buildStorageColumns, emitDataModel, emitApi, emitServer, emitFunctions, emitShard, emitCollections, emitContainers, emitWorkflows, emitQueues, emitCrons, emitVectors, emitDrizzleSchema, emitSeed, emitWranglerCronTriggers } from './GENERATED_HEADER-D-AbhmRh.mjs';
27
+ import { emitApp } from './emitApp-CV81L74c.mjs';
28
+ import { buildOpenApiDocument, emitOpenApiModule } from './buildOpenApiDocument-DoK7_x1g.mjs';
29
+ import { buildOpenRpcDocument, emitOpenRpcModule } from './OPENRPC_VERSION-DDT4mIzD.mjs';
30
30
  import { buildSchemaSnapshot, serializeSchemaSnapshot } from './SCHEMA_SNAPSHOT_VERSION-wWGP2_5E.mjs';
31
31
 
32
32
  const HTTP_VERBS = /* @__PURE__ */ new Set(["delete", "get", "head", "options", "patch", "post", "put"]);
@@ -243,6 +243,11 @@ const discoverArgumentValidators = (project, lunoraDirectory) => {
243
243
  };
244
244
 
245
245
  const PROBES = {
246
+ // The middleware (`accessContext()` / `accessRoles()`) imports the `/context`
247
+ // and `/roles` subpaths, NOT the bare `@lunora/cloudflare-access` specifier —
248
+ // so the per-procedure middleware never trips the global `ctx.access` wiring.
249
+ // A handler reading `ctx.access` is the signal that wires it onto every ctx.
250
+ access: { contextProperty: "access", moduleSpecifier: "@lunora/cloudflare-access" },
246
251
  ai: { contextProperty: "ai", moduleSpecifier: "@lunora/ai" },
247
252
  analytics: { contextProperty: "analytics", moduleSpecifier: "@lunora/bindings/analytics" },
248
253
  browser: { contextProperty: "browser", moduleSpecifier: "@lunora/browser" },
@@ -279,6 +284,7 @@ const readsContextProperty = (sourceFile, property) => {
279
284
  };
280
285
  const discoverFeatureUsage = (project, lunoraDirectory) => {
281
286
  const usage = {
287
+ access: false,
282
288
  ai: false,
283
289
  analytics: false,
284
290
  browser: false,
@@ -833,6 +839,7 @@ const runCodegen = (options) => {
833
839
  const hasAi = featureUsage.ai;
834
840
  const hasPayments = featureUsage.payments;
835
841
  const hasKv = featureUsage.kv;
842
+ const hasAccessFacade = featureUsage.access;
836
843
  const hasFlags = existsSync(join(lunoraDirectory, "flags.ts"));
837
844
  const flagKeys = hasFlags ? discoverFlagKeys(project, lunoraDirectory) : [];
838
845
  const hasHyperdrive = featureUsage.hyperdrive;
@@ -857,6 +864,7 @@ const runCodegen = (options) => {
857
864
  const apiContent = emitApi(functions, workflows, useUmbrella);
858
865
  const serverContent = emitServer({
859
866
  containers,
867
+ hasAccessFacade,
860
868
  hasAi,
861
869
  hasAnalytics,
862
870
  hasBrowser,
@@ -878,6 +886,7 @@ const runCodegen = (options) => {
878
886
  advisories,
879
887
  containers,
880
888
  flagKeys,
889
+ hasAccessFacade,
881
890
  hasAi,
882
891
  hasAnalytics,
883
892
  hasBrowser,
@@ -911,6 +920,7 @@ const runCodegen = (options) => {
911
920
  const wantsOpenApi = apiSpec === "openapi" || apiSpec === "both";
912
921
  const wantsOpenRpc = apiSpec === "openrpc" || apiSpec === "both";
913
922
  const appContent = emitApp({
923
+ hasAccess: dependencies.has("@lunora/cloudflare-access"),
914
924
  hasAi,
915
925
  hasAnalytics,
916
926
  hasAuth: dependencies.has("@lunora/auth"),
@@ -5,6 +5,7 @@ import { SyntaxKind, Node } from 'ts-morph';
5
5
  import { diagnosticAt } from './CodegenDiagnosticError-DeblMkzO.mjs';
6
6
 
7
7
  const WORKFLOWS_FILENAME = "workflows.ts";
8
+ const STEP_METHODS = /* @__PURE__ */ new Set(["do", "sleep", "sleepUntil", "waitForEvent"]);
8
9
  const isDefineWorkflow = (identifier) => {
9
10
  const symbol = identifier.getSymbol();
10
11
  if (!symbol) {
@@ -30,6 +31,44 @@ const stringProperty = (expression, exportName, property) => {
30
31
  `workflow "${exportName}": \`${property}\` must be a static string literal — it is deploy configuration codegen writes into wrangler.jsonc`
31
32
  );
32
33
  };
34
+ const isStepCall = (call) => {
35
+ const callee = call.getExpression();
36
+ if (!Node.isPropertyAccessExpression(callee) || !STEP_METHODS.has(callee.getName())) {
37
+ return false;
38
+ }
39
+ const receiver = callee.getExpression();
40
+ if (Node.isPropertyAccessExpression(receiver) && receiver.getName() === "step") {
41
+ return true;
42
+ }
43
+ return Node.isIdentifier(receiver) && receiver.getText() === "step";
44
+ };
45
+ const stepsFromHandler = (argument) => {
46
+ const handlerProperty = argument.getProperty("handler");
47
+ if (!handlerProperty) {
48
+ return [];
49
+ }
50
+ const body = Node.isPropertyAssignment(handlerProperty) ? handlerProperty.getInitializer() : handlerProperty;
51
+ if (!body) {
52
+ return [];
53
+ }
54
+ const steps = [];
55
+ for (const call of body.getDescendantsOfKind(SyntaxKind.CallExpression)) {
56
+ if (!isStepCall(call)) {
57
+ continue;
58
+ }
59
+ const nameArgument = call.getArguments()[0];
60
+ if (!nameArgument || !(Node.isStringLiteral(nameArgument) || Node.isNoSubstitutionTemplateLiteral(nameArgument))) {
61
+ continue;
62
+ }
63
+ steps.push({
64
+ line: call.getStartLineNumber(),
65
+ // `isStepCall` already proved the callee is a `.<method>` property access.
66
+ method: call.getExpression().getName(),
67
+ name: nameArgument.getLiteralValue()
68
+ });
69
+ }
70
+ return steps;
71
+ };
33
72
  const workflowFromCall = (call, exportName) => {
34
73
  const argument = call.getArguments()[0];
35
74
  if (!argument || !Node.isObjectLiteralExpression(argument)) {
@@ -39,7 +78,8 @@ const workflowFromCall = (call, exportName) => {
39
78
  bindingName: workflowBindingName(exportName),
40
79
  className: workflowClassName(exportName),
41
80
  exportName,
42
- name: workflowDefaultName(exportName)
81
+ name: workflowDefaultName(exportName),
82
+ steps: stepsFromHandler(argument)
43
83
  };
44
84
  const nameProperty = argument.getProperty("name");
45
85
  if (nameProperty && Node.isPropertyAssignment(nameProperty)) {
@@ -1,4 +1,4 @@
1
- import { GENERATED_HEADER } from './GENERATED_HEADER-BoVKLcnE.mjs';
1
+ import { GENERATED_HEADER } from './GENERATED_HEADER-D-AbhmRh.mjs';
2
2
  import { s as sanitizeNamespace } from './paths-BRd6JHuF.mjs';
3
3
  import { LUNORA_ERROR_CODES, objectSchema, argsObjectSchema, validatorIrToJsonSchema } from './LUNORA_ERROR_CODES-CySpQPD3.mjs';
4
4
 
@@ -1,4 +1,4 @@
1
- import { GENERATED_HEADER } from './GENERATED_HEADER-BoVKLcnE.mjs';
1
+ import { GENERATED_HEADER } from './GENERATED_HEADER-D-AbhmRh.mjs';
2
2
 
3
3
  const LONG_TAIL = [
4
4
  ["hasAi", "ai", "ai", "Override the Workers AI binding backing `ctx.ai` (defaults to `env.AI`)."],
@@ -22,8 +22,25 @@ const LONG_TAIL = [
22
22
  ["hasVectors", "vectors", "vectors", "Wire the Vectorize index map backing `ctx.vectors`."]
23
23
  ];
24
24
  const hasAnyLongTail = (options) => LONG_TAIL.some(([flag]) => options[flag]);
25
+ const buildAccessImports = (hasAccess, hasAuth) => hasAccess ? [
26
+ `import type { CreateAccessResolverOptions } from "@lunora/cloudflare-access";`,
27
+ `import { createAccessResolver${hasAuth ? ", composeResolvers" : ""} } from "@lunora/cloudflare-access";`
28
+ ] : [];
25
29
  const buildImportLines = (options) => {
26
- const { hasAuth, hasFramework, hasGlobal, hasHyperdriveGlobal, hasQueue, hasScheduler, hasStorage, hasWorkflow, useUmbrella, wantsOpenApi, wantsOpenRpc } = options;
30
+ const {
31
+ hasAccess,
32
+ hasAuth,
33
+ hasFramework,
34
+ hasGlobal,
35
+ hasHyperdriveGlobal,
36
+ hasQueue,
37
+ hasScheduler,
38
+ hasStorage,
39
+ hasWorkflow,
40
+ useUmbrella,
41
+ wantsOpenApi,
42
+ wantsOpenRpc
43
+ } = options;
27
44
  const runtimeModule = useUmbrella ? "lunorash/runtime" : "@lunora/runtime";
28
45
  const runtimeTypeImports = ["ExecutionContextLike", "LunoraWorker", "Route", "ScheduledControllerLike", "ShardNamespaceLike", "WorkerOptions"];
29
46
  if (hasGlobal) {
@@ -42,6 +59,7 @@ const buildImportLines = (options) => {
42
59
  `import type { LunoraAuth, LunoraAuthOptions } from "@lunora/auth";`,
43
60
  `import { createAuth, createAuthAdmin, ensureMigrated, handleAuthRequest, lunoraD1Adapter } from "@lunora/auth";`
44
61
  ] : [],
62
+ ...buildAccessImports(hasAccess, hasAuth),
45
63
  ...hasGlobal ? [
46
64
  `import type { D1CtxDbOptions, D1DatabaseLike, D1Exec } from "@lunora/d1";`,
47
65
  `import { createD1CtxDb, facetGlobalColumn, listGlobalTables, readGlobalTablePage } from "@lunora/d1";`
@@ -120,9 +138,10 @@ interface AuthDeclaration<Env> {
120
138
  ] : []
121
139
  ];
122
140
  const buildFieldLines = (options) => [
141
+ ...options.hasAccess ? [` private accessSelector?: Selector<Env, CreateAccessResolverOptions>;`] : [],
123
142
  ` private adminToken?: Selector<Env, string>;`,
124
143
  ...options.hasAuth ? [` private authDeclaration?: AuthDeclaration<Env>;`] : [],
125
- ` private readonly extendFns: ((env: Env) => Partial<WorkerOptions>)[] = [];`,
144
+ ` private readonly extendFns: ((env: Env, derived: Readonly<WorkerOptions>) => Partial<WorkerOptions>)[] = [];`,
126
145
  ...options.hasGlobal ? [` private globalDeclaration?: GlobalDeclaration<Env>;`] : [],
127
146
  ...options.hasHyperdriveGlobal ? [` private hyperdriveGlobalDeclaration?: HyperdriveGlobalDeclaration<Env>;`] : [],
128
147
  ` private readonly routeMap: Record<string, Route> = {};`,
@@ -140,6 +159,14 @@ const buildLongTailMethods = (options) => LONG_TAIL.filter(([flag]) => options[f
140
159
  }`
141
160
  );
142
161
  const buildMethodBlocks = (options) => [
162
+ ...options.hasAccess ? [
163
+ ` /** 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). */
164
+ public access(selector: Selector<Env, CreateAccessResolverOptions>): this {
165
+ this.accessSelector = selector;
166
+
167
+ return this;
168
+ }`
169
+ ] : [],
143
170
  ` /** Bearer token gating the \`/_lunora/admin/*\` endpoints the studio calls. */
144
171
  public admin(selector: Selector<Env, string>): this {
145
172
  this.adminToken = selector;
@@ -154,8 +181,8 @@ const buildMethodBlocks = (options) => [
154
181
  return this;
155
182
  }`
156
183
  ] : [],
157
- ` /** Escape hatch — merge raw \`WorkerOptions\` (anything not yet sugared) over the derived options at build time. */
158
- public extend(fn: (env: Env) => Partial<WorkerOptions>): this {
184
+ ` /** 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. */
185
+ public extend(fn: (env: Env, derived: Readonly<WorkerOptions>) => Partial<WorkerOptions>): this {
159
186
  this.extendFns.push(fn);
160
187
 
161
188
  return this;
@@ -344,6 +371,21 @@ const buildWorkerOptionLines = (options) => [
344
371
 
345
372
  options.authAdmin = authInstance ? createAuthAdmin(authInstance) : undefined;
346
373
  }`
374
+ ] : [],
375
+ // Cloudflare Access — runs AFTER the auth block so it can compose ahead of
376
+ // the better-auth resolver rather than clobber it. With `.auth()` present,
377
+ // a request carrying a verified Access JWT is authenticated by Access and
378
+ // everyone else falls through to the app session; without it, Access is the
379
+ // sole resolver.
380
+ ...options.hasAccess ? [
381
+ options.hasAuth ? ` if (this.accessSelector) {
382
+ const accessResolver = createAccessResolver(this.accessSelector(env));
383
+ const fallback = options.resolveIdentity;
384
+
385
+ options.resolveIdentity = fallback ? composeResolvers(accessResolver, fallback) : accessResolver;
386
+ }` : ` if (this.accessSelector) {
387
+ options.resolveIdentity = createAccessResolver(this.accessSelector(env));
388
+ }`
347
389
  ] : []
348
390
  ];
349
391
  const buildBaseWorkerOptions = (options) => [
@@ -599,7 +641,7 @@ ${buildBaseWorkerOptions(options).join("\n")}
599
641
  }
600
642
 
601
643
  ${workerOptionLines.join("\n\n")}${workerOptionLines.length > 0 ? "\n\n" : ""} for (const fn of this.extendFns) {
602
- Object.assign(options, fn(env));
644
+ Object.assign(options, fn(env, { ...options }));
603
645
  }
604
646
 
605
647
  return options;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lunora/codegen",
3
- "version": "1.0.0-alpha.17",
3
+ "version": "1.0.0-alpha.19",
4
4
  "description": "Code generator for Lunora: emits _generated/{api,server,dataModel}.ts from your schema",
5
5
  "keywords": [
6
6
  "cloudflare",
@@ -46,12 +46,12 @@
46
46
  "access": "public"
47
47
  },
48
48
  "dependencies": {
49
- "@lunora/advisor": "1.0.0-alpha.8",
49
+ "@lunora/advisor": "1.0.0-alpha.9",
50
50
  "@lunora/container": "1.0.0-alpha.5",
51
51
  "@lunora/queue": "1.0.0-alpha.1",
52
52
  "@lunora/scheduler": "1.0.0-alpha.3",
53
53
  "@lunora/values": "1.0.0-alpha.3",
54
- "@lunora/workflow": "1.0.0-alpha.3",
54
+ "@lunora/workflow": "1.0.0-alpha.4",
55
55
  "ts-morph": "^28.0.0"
56
56
  },
57
57
  "engines": {