@lunora/codegen 1.0.0-alpha.24 → 1.0.0-alpha.26

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.mjs CHANGED
@@ -19,11 +19,11 @@ export { default as discoverSchema } from './packem_shared/discoverSchema-KYq55q
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-CCisG0Vy.mjs';
22
- export { GENERATED_HEADER, emitApi, emitCollections, emitContainers, emitCrons, emitDataModel, emitDrizzleSchema, emitFunctions, emitServer, emitShard, emitVectors, emitWorkflows, emitWranglerCronTriggers } from './packem_shared/GENERATED_HEADER-DUkXYD5R.mjs';
23
- export { emitApp } from './packem_shared/emitApp-DD0i1ks0.mjs';
24
- export { buildOpenApiDocument, emitOpenApi, emitOpenApiModule } from './packem_shared/buildOpenApiDocument-WxYTLh11.mjs';
25
- export { OPENRPC_VERSION, buildOpenRpcDocument, emitOpenRpc, emitOpenRpcModule } from './packem_shared/OPENRPC_VERSION-CjBUaolx.mjs';
26
- export { SCHEMA_SNAPSHOT_FILENAME, createCodegenProject, refreshCodegenProject, runCodegen } from './packem_shared/SCHEMA_SNAPSHOT_FILENAME-BUcGCXi3.mjs';
22
+ export { GENERATED_HEADER, emitApi, emitCollections, emitContainers, emitCrons, emitDataModel, emitDrizzleSchema, emitFunctions, emitServer, emitShard, emitVectors, emitWorkflows, emitWranglerCronTriggers } from './packem_shared/GENERATED_HEADER-CYaSI_qY.mjs';
23
+ export { emitApp } from './packem_shared/emitApp-C38fHt_1.mjs';
24
+ export { buildOpenApiDocument, emitOpenApi, emitOpenApiModule } from './packem_shared/buildOpenApiDocument-BlpXQe2K.mjs';
25
+ export { OPENRPC_VERSION, buildOpenRpcDocument, emitOpenRpc, emitOpenRpcModule } from './packem_shared/OPENRPC_VERSION-CRe5l79E.mjs';
26
+ export { SCHEMA_SNAPSHOT_FILENAME, createCodegenProject, refreshCodegenProject, runCodegen } from './packem_shared/SCHEMA_SNAPSHOT_FILENAME-cgOjZTVY.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';
@@ -173,6 +173,7 @@ const GENERATED_HEADER = "// GENERATED by @lunora/codegen — do not edit.\n// R
173
173
  const baseSpecifiers = (useUmbrella = false) => useUmbrella ? {
174
174
  client: "lunorash/client",
175
175
  do: "lunorash/do",
176
+ flags: "lunorash/flags",
176
177
  server: "lunorash/server",
177
178
  serverDataModel: "lunorash/server/data-model",
178
179
  serverDrizzle: "lunorash/server/drizzle",
@@ -181,6 +182,7 @@ const baseSpecifiers = (useUmbrella = false) => useUmbrella ? {
181
182
  } : {
182
183
  client: "@lunora/client",
183
184
  do: "@lunora/do",
185
+ flags: "@lunora/flags",
184
186
  server: "@lunora/server",
185
187
  serverDataModel: "@lunora/server/data-model",
186
188
  serverDrizzle: "@lunora/server/drizzle",
@@ -828,7 +830,7 @@ export type Env = CloudflareBindings;`;
828
830
  readonly access: import("@lunora/cloudflare-access/context").AccessFacade;` : "";
829
831
  const flagsContextField = hasFlags ? `
830
832
  /** Feature-flag evaluation (OpenFeature). Reads are memoized per request; evaluations never throw — a provider error resolves to the supplied default. */
831
- readonly flags: import("@lunora/flags").LunoraFlags;` : "";
833
+ readonly flags: import("${base.flags}").LunoraFlags;` : "";
832
834
  const hyperdriveActionField = hasHyperdrive ? `
833
835
  /**
834
836
  * External database access via Hyperdrive. Non-deterministic — available only in actions. Writes here are NOT tracked by Lunora live queries; subscriptions will not re-run on external DB changes.
@@ -1359,13 +1361,13 @@ const kvStub: Kv = {
1359
1361
  `
1360
1362
  };
1361
1363
  };
1362
- const emitFlagsFragments = (hasFlags) => {
1364
+ const emitFlagsFragments = (hasFlags, flagsSpecifier) => {
1363
1365
  if (!hasFlags) {
1364
1366
  return EMPTY_HELPER_FRAGMENTS;
1365
1367
  }
1366
1368
  return {
1367
1369
  build: `
1368
- const flags: import("@lunora/flags").LunoraFlags = createFlags({
1370
+ const flags: import("${flagsSpecifier}").LunoraFlags = createFlags({
1369
1371
  hooks: flagsConfig.hooks,
1370
1372
  logger: flagsConfig.logger,
1371
1373
  provider: () => config.flags?.(env) ?? flagsConfig.provider(env),
@@ -1373,10 +1375,10 @@ const emitFlagsFragments = (hasFlags) => {
1373
1375
  });
1374
1376
  `,
1375
1377
  configField: `
1376
- flags?: (env: Record<string, unknown>) => import("@lunora/flags").Provider;`,
1378
+ flags?: (env: Record<string, unknown>) => import("${flagsSpecifier}").Provider;`,
1377
1379
  contextField: `
1378
1380
  flags,`,
1379
- importLines: [`import { createFlags } from "@lunora/flags";`, `import flagsConfig from "../flags.js";`],
1381
+ importLines: [`import { createFlags } from "${flagsSpecifier}";`, `import flagsConfig from "../flags.js";`],
1380
1382
  stub: ""
1381
1383
  };
1382
1384
  };
@@ -1395,13 +1397,13 @@ const emitAccessFragments = (hasAccessFacade) => {
1395
1397
  stub: ""
1396
1398
  };
1397
1399
  };
1398
- const emitFlagsOverrides = (flagKeys, hasFlags) => {
1400
+ const emitFlagsOverrides = (flagKeys, hasFlags, flagsSpecifier) => {
1399
1401
  if (!hasFlags) {
1400
1402
  return { constant: "", evaluateOverride: "", subscriptionOverride: "" };
1401
1403
  }
1402
1404
  const clientBuild = (targetingKey) => `
1403
1405
  const env = (this.env ?? {}) as Record<string, unknown>;
1404
- const flags: import("@lunora/flags").LunoraFlags = createFlags({
1406
+ const flags: import("${flagsSpecifier}").LunoraFlags = createFlags({
1405
1407
  hooks: flagsConfig.hooks,
1406
1408
  logger: flagsConfig.logger,
1407
1409
  provider: () => config.flags?.(env) ?? flagsConfig.provider(env),
@@ -1413,7 +1415,7 @@ const LUNORA_FLAG_KEYS: ReadonlyArray<{ key: string; type: "boolean" | "number"
1413
1415
  `;
1414
1416
  const evaluateOverride = `
1415
1417
  protected override async evaluateFlags(context?: Record<string, unknown>): Promise<FlagsResult> {${clientBuild("undefined")}
1416
- const evalContext = context as import("@lunora/flags").EvaluationContext | undefined;
1418
+ const evalContext = context as import("${flagsSpecifier}").EvaluationContext | undefined;
1417
1419
  const evaluations: FlagsResult["flags"] = [];
1418
1420
 
1419
1421
  for (const entry of LUNORA_FLAG_KEYS) {
@@ -1436,19 +1438,22 @@ const LUNORA_FLAG_KEYS: ReadonlyArray<{ key: string; type: "boolean" | "number"
1436
1438
  const subscriptionOverride = `
1437
1439
  protected override runFlagSubscriptionRead(_functionPath: string, args: Record<string, unknown>, identity?: { identity?: Record<string, unknown>; userId?: string }): Promise<unknown> {${clientBuild("() => flagsConfig.identify?.({ identity: identity?.identity ?? null, userId: identity?.userId ?? null })")}
1438
1440
  const key = typeof args.key === "string" ? args.key : "";
1439
- const rawContext = typeof args.context === "object" && args.context !== null ? (args.context as Record<string, unknown>) : undefined;
1440
- // The reactive channel is public (any socket, no auth required): never
1441
- // let a subscriber-supplied targetingKey override the verified identity
1442
- // resolved above, or a client could read another user's flags. Other
1443
- // (non-identity) targeting attributes pass through unchanged.
1444
- const { targetingKey: _clientTargetingKey, ...safeContext } = rawContext ?? {};
1445
- const context = (rawContext === undefined ? undefined : safeContext) as import("@lunora/flags").EvaluationContext | undefined;
1446
1441
 
1442
+ // SECURITY: the reactive channel is public (any socket, no auth). Serve
1443
+ // ONLY statically-discovered flag keys — an arbitrary client-supplied key
1444
+ // would let a subscriber probe the value of internal/unreleased flags the
1445
+ // app never exposes. Unknown key ⇒ the "nothing to deliver" sentinel.
1447
1446
  // eslint-disable-next-line unicorn/no-null -- the base hook's "nothing to deliver" sentinel
1448
- if (key.length === 0) {
1447
+ if (key.length === 0 || !LUNORA_FLAG_KEYS.some((entry) => entry.key === key)) {
1449
1448
  return Promise.resolve(null);
1450
1449
  }
1451
1450
 
1451
+ // SECURITY: evaluate under the socket's server-verified identity ONLY
1452
+ // (the targetingKey resolved above). Client-supplied targeting context is
1453
+ // NOT honored on this public channel — otherwise a subscriber could spoof
1454
+ // targeting attributes (e.g. plan/role) to unlock a flag gated on them.
1455
+ const context = undefined;
1456
+
1452
1457
  if (args.type === "number") {
1453
1458
  return flags.number(key, typeof args.default === "number" ? args.default : 0, context);
1454
1459
  }
@@ -1458,7 +1463,7 @@ const LUNORA_FLAG_KEYS: ReadonlyArray<{ key: string; type: "boolean" | "number"
1458
1463
  }
1459
1464
 
1460
1465
  if (args.type === "object") {
1461
- return flags.object(key, (args.default ?? {}) as import("@lunora/flags").JsonValue, context);
1466
+ return flags.object(key, (args.default ?? {}) as import("${flagsSpecifier}").JsonValue, context);
1462
1467
  }
1463
1468
 
1464
1469
  return flags.boolean(key, typeof args.default === "boolean" ? args.default : false, context);
@@ -2016,8 +2021,8 @@ const emitShard = ({
2016
2021
  const { build: aiBuild, configField: aiConfigField, contextField: aiContextField, stub: aiStub } = emitAiFragments(hasAi);
2017
2022
  const accessFragments = emitAccessFragments(hasAccessFacade);
2018
2023
  const kvFragments = emitKvFragments(hasKv);
2019
- const flagsFragments = emitFlagsFragments(hasFlags);
2020
- const flagsOverrides = emitFlagsOverrides(flagKeys, hasFlags);
2024
+ const flagsFragments = emitFlagsFragments(hasFlags, base.flags);
2025
+ const flagsOverrides = emitFlagsOverrides(flagKeys, hasFlags, base.flags);
2021
2026
  const analyticsFragments = emitAnalyticsFragments(hasAnalytics);
2022
2027
  const imagesFragments = emitImagesFragments(hasImages);
2023
2028
  const hyperdriveFragments = emitHyperdriveFragments(hasHyperdrive);
@@ -2049,7 +2054,10 @@ const emitShard = ({
2049
2054
  const maskData = maskMetadata ?? { columns: [] };
2050
2055
  const storageRulesData = storageRules ?? { rules: [] };
2051
2056
  const studioFeaturesData = studioFeatures ?? {
2057
+ analytics: false,
2058
+ auth: false,
2052
2059
  flags: false,
2060
+ kv: false,
2053
2061
  mail: false,
2054
2062
  payments: false,
2055
2063
  queues: false,
@@ -1,4 +1,4 @@
1
- import { GENERATED_HEADER } from './GENERATED_HEADER-DUkXYD5R.mjs';
1
+ import { GENERATED_HEADER } from './GENERATED_HEADER-CYaSI_qY.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
 
@@ -24,10 +24,10 @@ import { discoverShapes } from './SHAPES_FILENAME-ChV7MqgE.mjs';
24
24
  import discoverStorageRulesMetadata from './discoverStorageRulesMetadata-Da8BKXcI.mjs';
25
25
  import { e as enclosingExportName$1 } from './discover-ast-CT6BgBr4.mjs';
26
26
  import { discoverWorkflows } from './WORKFLOWS_FILENAME-CCisG0Vy.mjs';
27
- import { buildStorageColumns, emitDataModel, emitApi, emitServer, emitFunctions, emitShard, emitCollections, emitContainers, emitWorkflows, emitQueues, emitCrons, emitVectors, emitDrizzleSchema, emitSeed, emitWranglerCronTriggers } from './GENERATED_HEADER-DUkXYD5R.mjs';
28
- import { emitApp } from './emitApp-DD0i1ks0.mjs';
29
- import { buildOpenApiDocument, emitOpenApiModule } from './buildOpenApiDocument-WxYTLh11.mjs';
30
- import { buildOpenRpcDocument, emitOpenRpcModule } from './OPENRPC_VERSION-CjBUaolx.mjs';
27
+ import { buildStorageColumns, emitDataModel, emitApi, emitServer, emitFunctions, emitShard, emitCollections, emitContainers, emitWorkflows, emitQueues, emitCrons, emitVectors, emitDrizzleSchema, emitSeed, emitWranglerCronTriggers } from './GENERATED_HEADER-CYaSI_qY.mjs';
28
+ import { emitApp } from './emitApp-C38fHt_1.mjs';
29
+ import { buildOpenApiDocument, emitOpenApiModule } from './buildOpenApiDocument-BlpXQe2K.mjs';
30
+ import { buildOpenRpcDocument, emitOpenRpcModule } from './OPENRPC_VERSION-CRe5l79E.mjs';
31
31
  import { buildSchemaSnapshot, serializeSchemaSnapshot } from './SCHEMA_SNAPSHOT_VERSION-wWGP2_5E.mjs';
32
32
 
33
33
  const HTTP_VERBS = /* @__PURE__ */ new Set(["delete", "get", "head", "options", "patch", "post", "put"]);
@@ -327,7 +327,10 @@ const discoverFeatureUsage = (project, lunoraDirectory) => {
327
327
  };
328
328
  const buildStudioFeatures = (usage, signals) => {
329
329
  return {
330
+ analytics: usage.analytics || signals.dependencies.has("@lunora/bindings/analytics"),
331
+ auth: signals.dependencies.has("@lunora/auth"),
330
332
  flags: usage.flags || signals.dependencies.has("@lunora/flags"),
333
+ kv: usage.kv || signals.dependencies.has("@lunora/bindings/kv"),
331
334
  mail: usage.mail || signals.dependencies.has("@lunora/mail"),
332
335
  payments: usage.payments || signals.dependencies.has("@lunora/payment"),
333
336
  queues: signals.queueCount > 0 || signals.dependencies.has("@lunora/queue"),
@@ -1021,7 +1024,11 @@ const runCodegen = (options) => {
1021
1024
  hasHyperdrive,
1022
1025
  hasHyperdriveGlobal: schema.tables.some((table) => table.shardMode === "global" && table.globalBackend === "hyperdrive"),
1023
1026
  hasImages,
1024
- hasKv,
1027
+ // Auto-wire the studio's KV introspector on the SAME condition the nav
1028
+ // gates its tab on (`studioFeatures.kv` = ctx.kv usage OR a declared
1029
+ // `@lunora/bindings/kv` dep), so a visible KV tab always has a working
1030
+ // backend — never the reverse. The `ctx.kv` type-seam stays usage-only.
1031
+ hasKv: studioFeatures.kv,
1025
1032
  hasPayments,
1026
1033
  hasR2sql,
1027
1034
  hasQueue: queues.some((queue) => queue.mode === "push"),
@@ -1,4 +1,4 @@
1
- import { GENERATED_HEADER } from './GENERATED_HEADER-DUkXYD5R.mjs';
1
+ import { GENERATED_HEADER } from './GENERATED_HEADER-CYaSI_qY.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-DUkXYD5R.mjs';
1
+ import { GENERATED_HEADER } from './GENERATED_HEADER-CYaSI_qY.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`)."],
@@ -27,6 +27,7 @@ const buildAccessImports = (hasAccess, hasAuth) => hasAccess ? [
27
27
  `import type { CreateAccessResolverOptions } from "@lunora/cloudflare-access";`,
28
28
  `import { createAccessResolver${hasAuth ? ", composeResolvers" : ""} } from "@lunora/cloudflare-access";`
29
29
  ] : [];
30
+ const buildKvImports = (hasKv) => hasKv ? [`import { createKvIntrospectorFromEnv } from "@lunora/bindings/kv";`] : [];
30
31
  const buildImportLines = (options) => {
31
32
  const {
32
33
  hasAccess,
@@ -34,6 +35,7 @@ const buildImportLines = (options) => {
34
35
  hasFramework,
35
36
  hasGlobal,
36
37
  hasHyperdriveGlobal,
38
+ hasKv,
37
39
  hasQueue,
38
40
  hasScheduler,
39
41
  hasStorage,
@@ -70,6 +72,7 @@ const buildImportLines = (options) => {
70
72
  `import { createHyperdriveGlobalCtxDb } from "@lunora/hyperdrive/global";`,
71
73
  `import type { SqlCtxDbOptions, SqlExec } from "@lunora/sql-store";`
72
74
  ] : [],
75
+ ...buildKvImports(hasKv),
73
76
  ...hasScheduler ? [`import type { DurableObjectNamespaceLike } from "@lunora/scheduler";`, `import { createScheduler } from "@lunora/scheduler";`] : [],
74
77
  ...hasStorage ? [`import type { R2BucketLike, Storage } from "@lunora/storage";`, `import { createBucketStorage, createStorage } from "@lunora/storage";`] : [],
75
78
  ...hasWorkflow ? [`import { createWorkflowsRestClient } from "@lunora/workflow";`] : [],
@@ -351,6 +354,12 @@ const buildWorkerOptionLines = (options) => [
351
354
  Object.assign(options, this.buildStorageAdmin(env));
352
355
  }`
353
356
  ] : [],
357
+ // The studio's KV browser is wired zero-config: `createKvIntrospectorFromEnv`
358
+ // scans `env` for every bound Workers KV namespace, so each `kv_namespaces`
359
+ // entry in wrangler.jsonc appears under its binding name (any name, any count)
360
+ // with no manual `createKvIntrospector` call. A deployment with no KV binding
361
+ // yields an empty namespace list rather than crashing.
362
+ ...options.hasKv ? [` options.kvIntrospector = createKvIntrospectorFromEnv(env);`] : [],
354
363
  ...options.hasAuth ? [
355
364
  ` if (this.authDeclaration) {
356
365
  options.authHandler = (request) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lunora/codegen",
3
- "version": "1.0.0-alpha.24",
3
+ "version": "1.0.0-alpha.26",
4
4
  "description": "Code generator for Lunora: emits _generated/{api,server,dataModel}.ts from your schema",
5
5
  "keywords": [
6
6
  "cloudflare",
@@ -46,9 +46,9 @@
46
46
  "access": "public"
47
47
  },
48
48
  "dependencies": {
49
- "@lunora/advisor": "1.0.0-alpha.13",
49
+ "@lunora/advisor": "1.0.0-alpha.14",
50
50
  "@lunora/container": "1.0.0-alpha.5",
51
- "@lunora/queue": "1.0.0-alpha.1",
51
+ "@lunora/queue": "1.0.0-alpha.2",
52
52
  "@lunora/scheduler": "1.0.0-alpha.4",
53
53
  "@lunora/values": "1.0.0-alpha.3",
54
54
  "@lunora/workflow": "1.0.0-alpha.5",