@lunora/codegen 1.0.0-alpha.17 → 1.0.0-alpha.18

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
@@ -1099,6 +1099,13 @@ declare const emitApi: (functions: ReadonlyArray<FunctionIR>, workflows?: Readon
1099
1099
  declare const emitCollections: (shapes: ReadonlyArray<ShapeIR>, hasDatabase: boolean, useUmbrella?: boolean) => string;
1100
1100
  interface EmitServerOptions {
1101
1101
  containers?: ReadonlyArray<ContainerIR>;
1102
+ /**
1103
+ * A `lunora/` source reads `ctx.access` — wires the verified Cloudflare Access
1104
+ * facade (`@lunora/cloudflare-access/context`) onto every ctx. Distinct from
1105
+ * `emitApp`'s `hasAccess` (which gates the worker's `.access()` resolveIdentity
1106
+ * method); this one gates the per-request `ctx.access` read surface.
1107
+ */
1108
+ hasAccessFacade?: boolean;
1102
1109
  hasAi?: boolean;
1103
1110
  /** A `lunora/` source uses `@lunora/bindings/analytics` / `ctx.analytics` — wires the write helper onto every ctx. */
1104
1111
  hasAnalytics?: boolean;
@@ -1127,6 +1134,7 @@ interface EmitServerOptions {
1127
1134
  }
1128
1135
  declare const emitServer: ({
1129
1136
  containers,
1137
+ hasAccessFacade,
1130
1138
  hasAi,
1131
1139
  hasAnalytics,
1132
1140
  hasBrowser,
@@ -1187,6 +1195,8 @@ interface EmitShardOptions {
1187
1195
  key: string;
1188
1196
  type: "boolean" | "number" | "object" | "string";
1189
1197
  }>;
1198
+ /** A `lunora/` source reads `ctx.access` — wires the verified Cloudflare Access facade onto every ctx. */
1199
+ hasAccessFacade?: boolean;
1190
1200
  hasAi?: boolean;
1191
1201
  /** A `lunora/` source reads `ctx.analytics` — wires the Analytics Engine write helper onto every ctx. */
1192
1202
  hasAnalytics?: boolean;
@@ -1224,6 +1234,7 @@ declare const emitShard: ({
1224
1234
  advisories,
1225
1235
  containers,
1226
1236
  flagKeys,
1237
+ hasAccessFacade,
1227
1238
  hasAi,
1228
1239
  hasAnalytics,
1229
1240
  hasBrowser,
@@ -1304,6 +1315,8 @@ declare const emitVectors: (vectorIndexes: ReadonlyArray<VectorIndexIR>) => stri
1304
1315
  declare const emitWranglerCronTriggers: (crons: ReadonlyArray<CronJobIR>) => string[];
1305
1316
  /** Which capability methods the generated `defineApp` builder exposes — one flag per package-backed feature the app actually uses. */
1306
1317
  interface EmitAppOptions {
1318
+ /** App depends on `@lunora/cloudflare-access` → emit `.access()` (wire the Cloudflare Access `resolveIdentity`, composed ahead of `@lunora/auth` when both are present). */
1319
+ hasAccess: boolean;
1307
1320
  /** App uses `@lunora/ai` / `ctx.ai` → emit `.ai()` (override the Workers AI binding backing `ctx.ai`). */
1308
1321
  hasAi: boolean;
1309
1322
  /** App uses `@lunora/bindings/analytics` / `ctx.analytics` → emit `.analytics()` (override the dataset backing `ctx.analytics`). */
package/dist/index.d.ts CHANGED
@@ -1099,6 +1099,13 @@ declare const emitApi: (functions: ReadonlyArray<FunctionIR>, workflows?: Readon
1099
1099
  declare const emitCollections: (shapes: ReadonlyArray<ShapeIR>, hasDatabase: boolean, useUmbrella?: boolean) => string;
1100
1100
  interface EmitServerOptions {
1101
1101
  containers?: ReadonlyArray<ContainerIR>;
1102
+ /**
1103
+ * A `lunora/` source reads `ctx.access` — wires the verified Cloudflare Access
1104
+ * facade (`@lunora/cloudflare-access/context`) onto every ctx. Distinct from
1105
+ * `emitApp`'s `hasAccess` (which gates the worker's `.access()` resolveIdentity
1106
+ * method); this one gates the per-request `ctx.access` read surface.
1107
+ */
1108
+ hasAccessFacade?: boolean;
1102
1109
  hasAi?: boolean;
1103
1110
  /** A `lunora/` source uses `@lunora/bindings/analytics` / `ctx.analytics` — wires the write helper onto every ctx. */
1104
1111
  hasAnalytics?: boolean;
@@ -1127,6 +1134,7 @@ interface EmitServerOptions {
1127
1134
  }
1128
1135
  declare const emitServer: ({
1129
1136
  containers,
1137
+ hasAccessFacade,
1130
1138
  hasAi,
1131
1139
  hasAnalytics,
1132
1140
  hasBrowser,
@@ -1187,6 +1195,8 @@ interface EmitShardOptions {
1187
1195
  key: string;
1188
1196
  type: "boolean" | "number" | "object" | "string";
1189
1197
  }>;
1198
+ /** A `lunora/` source reads `ctx.access` — wires the verified Cloudflare Access facade onto every ctx. */
1199
+ hasAccessFacade?: boolean;
1190
1200
  hasAi?: boolean;
1191
1201
  /** A `lunora/` source reads `ctx.analytics` — wires the Analytics Engine write helper onto every ctx. */
1192
1202
  hasAnalytics?: boolean;
@@ -1224,6 +1234,7 @@ declare const emitShard: ({
1224
1234
  advisories,
1225
1235
  containers,
1226
1236
  flagKeys,
1237
+ hasAccessFacade,
1227
1238
  hasAi,
1228
1239
  hasAnalytics,
1229
1240
  hasBrowser,
@@ -1304,6 +1315,8 @@ declare const emitVectors: (vectorIndexes: ReadonlyArray<VectorIndexIR>) => stri
1304
1315
  declare const emitWranglerCronTriggers: (crons: ReadonlyArray<CronJobIR>) => string[];
1305
1316
  /** Which capability methods the generated `defineApp` builder exposes — one flag per package-backed feature the app actually uses. */
1306
1317
  interface EmitAppOptions {
1318
+ /** App depends on `@lunora/cloudflare-access` → emit `.access()` (wire the Cloudflare Access `resolveIdentity`, composed ahead of `@lunora/auth` when both are present). */
1319
+ hasAccess: boolean;
1307
1320
  /** App uses `@lunora/ai` / `ctx.ai` → emit `.ai()` (override the Workers AI binding backing `ctx.ai`). */
1308
1321
  hasAi: boolean;
1309
1322
  /** App uses `@lunora/bindings/analytics` / `ctx.analytics` → emit `.analytics()` (override the dataset backing `ctx.analytics`). */
package/dist/index.mjs CHANGED
@@ -19,11 +19,11 @@ export { default as discoverSchema } from './packem_shared/discoverSchema-BnWHHJ
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
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';
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-ExbveiC6.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
 
@@ -23,10 +23,10 @@ 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
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';
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"),
@@ -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.18",
4
4
  "description": "Code generator for Lunora: emits _generated/{api,server,dataModel}.ts from your schema",
5
5
  "keywords": [
6
6
  "cloudflare",