@lunora/codegen 1.0.0-alpha.44 → 1.0.0-alpha.46

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
@@ -122,8 +122,10 @@ interface ExternalSourceIR {
122
122
  binding: string;
123
123
  /** Whether a `columns` projection allow-list was given. */
124
124
  columns?: ReadonlyArray<string>;
125
- /** `true` when a `reconcileEveryMs` was given — reserved for a future incremental mode's delete-visibility lint; not on the typed `.source()` surface today. */
125
+ /** `true` when a `reconcileEveryMs` was given — one of the two incremental delete-visibility paths the `external_source_incremental_no_delete_path` lint checks. */
126
126
  hasReconcile?: boolean;
127
+ /** `true` when a `softDeleteColumn` was given — the other incremental delete-visibility path. */
128
+ hasSoftDelete?: boolean;
127
129
  /** `true` when a `tenantBy` mapper was given — the tenant-isolation boundary the `external_source_unscoped` lint checks. */
128
130
  hasTenantBy: boolean;
129
131
  /** The `idColumn` literal, when given (defaults to `"id"` at runtime). */
@@ -1933,6 +1935,20 @@ declare const discoverRlsProcedures: (project: Project, lunoraDirectory: string)
1933
1935
  */
1934
1936
  declare const discoverRlsMetadata: (project: Project, lunoraDirectory: string) => RlsMetadataIR;
1935
1937
  /**
1938
+ * Which sandbox tools a project imports from `@lunora/agent` (main entry or the
1939
+ * `/sandbox` subpath), detected by NAMED value import. Drives two things:
1940
+ * registering the `sandbox:invoke` dispatcher (either tool) and provisioning the
1941
+ * `BROWSER` wrangler binding (`browserTool` — the browser op runs on
1942
+ * `ctx.browser` inside the dispatcher).
1943
+ */
1944
+ interface SandboxUsage {
1945
+ /** `import { browserTool } from "@lunora/agent"` (or `/sandbox`) appears in `lunora/`. */
1946
+ usesSandboxBrowser: boolean;
1947
+ /** `import { containerTool } from "@lunora/agent"` (or `/sandbox`) appears in `lunora/`. */
1948
+ usesSandboxContainer: boolean;
1949
+ }
1950
+ declare const discoverSandboxUsage: (project: Project, lunoraDirectory: string) => SandboxUsage;
1951
+ /**
1936
1952
  * Load `&lt;projectRoot>/lunora/schema.ts`, find `defineSchema({...})`, and
1937
1953
  * return a structural IR. Throws if the file or call cannot be found.
1938
1954
  */
@@ -2805,4 +2821,4 @@ declare const secretKindOf: (value: string) => string | undefined;
2805
2821
  /** A redacted preview of a secret value — first 4 chars plus its length, never the full value. */
2806
2822
  declare const redact: (value: string) => string;
2807
2823
  declare const VERSION = "0.0.0";
2808
- export { AGENTS_FILENAME, type AgentIR, type AuthApiCallIR, CONTAINERS_FILENAME, CodegenDiagnosticError, type CodegenOptions, type CodegenResult, type ContainerIR, type CronJobIR, type DriftChange, type EmitAppOptions, FLAGS_FILENAME, type FieldSnapshot, type FlagsIR, type FunctionIR, GENERATED_HEADER, type HttpRouteIR, type IndexIR, type IndexSnapshot, type InsertWriteIR, LUNORA_ERROR_CODES, type LintSchemaOptions, MUTATORS_FILENAME, type MaskProcedureIR, type MigrationIR, type MutatorIR, OPENRPC_VERSION, type OpenApiEmitInput, type OpenRpcEmitInput, type ProjectIR, QUEUES_FILENAME, type QueryReadIR, type QueueIR, type R2sqlCallIR, type RelationSnapshot, type RlsMetadataIR, type RlsPolicyIR, type RlsProcedureIR, type RlsRoleIR, SCHEMA_SNAPSHOT_FILENAME, SCHEMA_SNAPSHOT_VERSION, SHAPES_FILENAME, type SchemaDrift, type SchemaDriftDecision, type SchemaIR, type SchemaSnapshot, SchemaSnapshotParseError, type ShapeIR, type StorageRuleIR, type StorageRulesMetadataIR, type TableIR, type TableSnapshot, VERSION, type ValidatorIR, type VectorIndexIR, WORKFLOWS_FILENAME, type WorkflowIR, type WranglerVariableIR, buildOpenApiDocument, buildOpenRpcDocument, buildSchemaSnapshot, createCodegenProject, diagnosticAt, diffSchemaSnapshots, discoverAgents, discoverAuthApiCalls, discoverContainers, discoverCrons, discoverFlags, discoverFunctions, discoverHttpRoutes, discoverInserts, discoverMaskProcedures, discoverMigrations, discoverMutators, discoverNondeterministicCalls, discoverQueries, discoverQueues, discoverR2sqlCalls, discoverRlsMetadata, discoverRlsProcedures, discoverSchema, discoverShapes, discoverStorageRulesMetadata, discoverWorkflows, emitAgents, emitApi, emitApp, emitCollections, emitContainers, emitCrons, emitDataModel, emitDrizzleSchema, emitFunctions, emitOpenApi, emitOpenApiModule, emitOpenRpc, emitOpenRpcModule, emitServer, emitShard, emitVectors, emitWorkflows, emitWranglerCronTriggers, evaluateSchemaDrift, formatAdvisories, lintSchema, parseSchemaSnapshot, redact, refreshCodegenProject, runCodegen, schemaFromIr, secretKindOf, serializeSchemaSnapshot, validatorIrToJsonSchema };
2824
+ export { AGENTS_FILENAME, type AgentIR, type AuthApiCallIR, CONTAINERS_FILENAME, CodegenDiagnosticError, type CodegenOptions, type CodegenResult, type ContainerIR, type CronJobIR, type DriftChange, type EmitAppOptions, FLAGS_FILENAME, type FieldSnapshot, type FlagsIR, type FunctionIR, GENERATED_HEADER, type HttpRouteIR, type IndexIR, type IndexSnapshot, type InsertWriteIR, LUNORA_ERROR_CODES, type LintSchemaOptions, MUTATORS_FILENAME, type MaskProcedureIR, type MigrationIR, type MutatorIR, OPENRPC_VERSION, type OpenApiEmitInput, type OpenRpcEmitInput, type ProjectIR, QUEUES_FILENAME, type QueryReadIR, type QueueIR, type R2sqlCallIR, type RelationSnapshot, type RlsMetadataIR, type RlsPolicyIR, type RlsProcedureIR, type RlsRoleIR, SCHEMA_SNAPSHOT_FILENAME, SCHEMA_SNAPSHOT_VERSION, SHAPES_FILENAME, type SandboxUsage, type SchemaDrift, type SchemaDriftDecision, type SchemaIR, type SchemaSnapshot, SchemaSnapshotParseError, type ShapeIR, type StorageRuleIR, type StorageRulesMetadataIR, type TableIR, type TableSnapshot, VERSION, type ValidatorIR, type VectorIndexIR, WORKFLOWS_FILENAME, type WorkflowIR, type WranglerVariableIR, buildOpenApiDocument, buildOpenRpcDocument, buildSchemaSnapshot, createCodegenProject, diagnosticAt, diffSchemaSnapshots, discoverAgents, discoverAuthApiCalls, discoverContainers, discoverCrons, discoverFlags, discoverFunctions, discoverHttpRoutes, discoverInserts, discoverMaskProcedures, discoverMigrations, discoverMutators, discoverNondeterministicCalls, discoverQueries, discoverQueues, discoverR2sqlCalls, discoverRlsMetadata, discoverRlsProcedures, discoverSandboxUsage, discoverSchema, discoverShapes, discoverStorageRulesMetadata, discoverWorkflows, emitAgents, emitApi, emitApp, emitCollections, emitContainers, emitCrons, emitDataModel, emitDrizzleSchema, emitFunctions, emitOpenApi, emitOpenApiModule, emitOpenRpc, emitOpenRpcModule, emitServer, emitShard, emitVectors, emitWorkflows, emitWranglerCronTriggers, evaluateSchemaDrift, formatAdvisories, lintSchema, parseSchemaSnapshot, redact, refreshCodegenProject, runCodegen, schemaFromIr, secretKindOf, serializeSchemaSnapshot, validatorIrToJsonSchema };
package/dist/index.d.ts CHANGED
@@ -122,8 +122,10 @@ interface ExternalSourceIR {
122
122
  binding: string;
123
123
  /** Whether a `columns` projection allow-list was given. */
124
124
  columns?: ReadonlyArray<string>;
125
- /** `true` when a `reconcileEveryMs` was given — reserved for a future incremental mode's delete-visibility lint; not on the typed `.source()` surface today. */
125
+ /** `true` when a `reconcileEveryMs` was given — one of the two incremental delete-visibility paths the `external_source_incremental_no_delete_path` lint checks. */
126
126
  hasReconcile?: boolean;
127
+ /** `true` when a `softDeleteColumn` was given — the other incremental delete-visibility path. */
128
+ hasSoftDelete?: boolean;
127
129
  /** `true` when a `tenantBy` mapper was given — the tenant-isolation boundary the `external_source_unscoped` lint checks. */
128
130
  hasTenantBy: boolean;
129
131
  /** The `idColumn` literal, when given (defaults to `"id"` at runtime). */
@@ -1933,6 +1935,20 @@ declare const discoverRlsProcedures: (project: Project, lunoraDirectory: string)
1933
1935
  */
1934
1936
  declare const discoverRlsMetadata: (project: Project, lunoraDirectory: string) => RlsMetadataIR;
1935
1937
  /**
1938
+ * Which sandbox tools a project imports from `@lunora/agent` (main entry or the
1939
+ * `/sandbox` subpath), detected by NAMED value import. Drives two things:
1940
+ * registering the `sandbox:invoke` dispatcher (either tool) and provisioning the
1941
+ * `BROWSER` wrangler binding (`browserTool` — the browser op runs on
1942
+ * `ctx.browser` inside the dispatcher).
1943
+ */
1944
+ interface SandboxUsage {
1945
+ /** `import { browserTool } from "@lunora/agent"` (or `/sandbox`) appears in `lunora/`. */
1946
+ usesSandboxBrowser: boolean;
1947
+ /** `import { containerTool } from "@lunora/agent"` (or `/sandbox`) appears in `lunora/`. */
1948
+ usesSandboxContainer: boolean;
1949
+ }
1950
+ declare const discoverSandboxUsage: (project: Project, lunoraDirectory: string) => SandboxUsage;
1951
+ /**
1936
1952
  * Load `&lt;projectRoot>/lunora/schema.ts`, find `defineSchema({...})`, and
1937
1953
  * return a structural IR. Throws if the file or call cannot be found.
1938
1954
  */
@@ -2805,4 +2821,4 @@ declare const secretKindOf: (value: string) => string | undefined;
2805
2821
  /** A redacted preview of a secret value — first 4 chars plus its length, never the full value. */
2806
2822
  declare const redact: (value: string) => string;
2807
2823
  declare const VERSION = "0.0.0";
2808
- export { AGENTS_FILENAME, type AgentIR, type AuthApiCallIR, CONTAINERS_FILENAME, CodegenDiagnosticError, type CodegenOptions, type CodegenResult, type ContainerIR, type CronJobIR, type DriftChange, type EmitAppOptions, FLAGS_FILENAME, type FieldSnapshot, type FlagsIR, type FunctionIR, GENERATED_HEADER, type HttpRouteIR, type IndexIR, type IndexSnapshot, type InsertWriteIR, LUNORA_ERROR_CODES, type LintSchemaOptions, MUTATORS_FILENAME, type MaskProcedureIR, type MigrationIR, type MutatorIR, OPENRPC_VERSION, type OpenApiEmitInput, type OpenRpcEmitInput, type ProjectIR, QUEUES_FILENAME, type QueryReadIR, type QueueIR, type R2sqlCallIR, type RelationSnapshot, type RlsMetadataIR, type RlsPolicyIR, type RlsProcedureIR, type RlsRoleIR, SCHEMA_SNAPSHOT_FILENAME, SCHEMA_SNAPSHOT_VERSION, SHAPES_FILENAME, type SchemaDrift, type SchemaDriftDecision, type SchemaIR, type SchemaSnapshot, SchemaSnapshotParseError, type ShapeIR, type StorageRuleIR, type StorageRulesMetadataIR, type TableIR, type TableSnapshot, VERSION, type ValidatorIR, type VectorIndexIR, WORKFLOWS_FILENAME, type WorkflowIR, type WranglerVariableIR, buildOpenApiDocument, buildOpenRpcDocument, buildSchemaSnapshot, createCodegenProject, diagnosticAt, diffSchemaSnapshots, discoverAgents, discoverAuthApiCalls, discoverContainers, discoverCrons, discoverFlags, discoverFunctions, discoverHttpRoutes, discoverInserts, discoverMaskProcedures, discoverMigrations, discoverMutators, discoverNondeterministicCalls, discoverQueries, discoverQueues, discoverR2sqlCalls, discoverRlsMetadata, discoverRlsProcedures, discoverSchema, discoverShapes, discoverStorageRulesMetadata, discoverWorkflows, emitAgents, emitApi, emitApp, emitCollections, emitContainers, emitCrons, emitDataModel, emitDrizzleSchema, emitFunctions, emitOpenApi, emitOpenApiModule, emitOpenRpc, emitOpenRpcModule, emitServer, emitShard, emitVectors, emitWorkflows, emitWranglerCronTriggers, evaluateSchemaDrift, formatAdvisories, lintSchema, parseSchemaSnapshot, redact, refreshCodegenProject, runCodegen, schemaFromIr, secretKindOf, serializeSchemaSnapshot, validatorIrToJsonSchema };
2824
+ export { AGENTS_FILENAME, type AgentIR, type AuthApiCallIR, CONTAINERS_FILENAME, CodegenDiagnosticError, type CodegenOptions, type CodegenResult, type ContainerIR, type CronJobIR, type DriftChange, type EmitAppOptions, FLAGS_FILENAME, type FieldSnapshot, type FlagsIR, type FunctionIR, GENERATED_HEADER, type HttpRouteIR, type IndexIR, type IndexSnapshot, type InsertWriteIR, LUNORA_ERROR_CODES, type LintSchemaOptions, MUTATORS_FILENAME, type MaskProcedureIR, type MigrationIR, type MutatorIR, OPENRPC_VERSION, type OpenApiEmitInput, type OpenRpcEmitInput, type ProjectIR, QUEUES_FILENAME, type QueryReadIR, type QueueIR, type R2sqlCallIR, type RelationSnapshot, type RlsMetadataIR, type RlsPolicyIR, type RlsProcedureIR, type RlsRoleIR, SCHEMA_SNAPSHOT_FILENAME, SCHEMA_SNAPSHOT_VERSION, SHAPES_FILENAME, type SandboxUsage, type SchemaDrift, type SchemaDriftDecision, type SchemaIR, type SchemaSnapshot, SchemaSnapshotParseError, type ShapeIR, type StorageRuleIR, type StorageRulesMetadataIR, type TableIR, type TableSnapshot, VERSION, type ValidatorIR, type VectorIndexIR, WORKFLOWS_FILENAME, type WorkflowIR, type WranglerVariableIR, buildOpenApiDocument, buildOpenRpcDocument, buildSchemaSnapshot, createCodegenProject, diagnosticAt, diffSchemaSnapshots, discoverAgents, discoverAuthApiCalls, discoverContainers, discoverCrons, discoverFlags, discoverFunctions, discoverHttpRoutes, discoverInserts, discoverMaskProcedures, discoverMigrations, discoverMutators, discoverNondeterministicCalls, discoverQueries, discoverQueues, discoverR2sqlCalls, discoverRlsMetadata, discoverRlsProcedures, discoverSandboxUsage, discoverSchema, discoverShapes, discoverStorageRulesMetadata, discoverWorkflows, emitAgents, emitApi, emitApp, emitCollections, emitContainers, emitCrons, emitDataModel, emitDrizzleSchema, emitFunctions, emitOpenApi, emitOpenApiModule, emitOpenRpc, emitOpenRpcModule, emitServer, emitShard, emitVectors, emitWorkflows, emitWranglerCronTriggers, evaluateSchemaDrift, formatAdvisories, lintSchema, parseSchemaSnapshot, redact, refreshCodegenProject, runCodegen, schemaFromIr, secretKindOf, serializeSchemaSnapshot, validatorIrToJsonSchema };
package/dist/index.mjs CHANGED
@@ -1,9 +1,9 @@
1
- export { formatAdvisories, lintSchema } from './packem_shared/formatAdvisories-DRhEiJUz.mjs';
1
+ export { formatAdvisories, lintSchema } from './packem_shared/formatAdvisories-BmF7Mplc.mjs';
2
2
  export { CodegenDiagnosticError, diagnosticAt } from './packem_shared/CodegenDiagnosticError-DyQ5FwkM.mjs';
3
- export { AGENTS_FILENAME, discoverAgents } from './packem_shared/AGENTS_FILENAME-BksnAJgx.mjs';
3
+ export { AGENTS_FILENAME, discoverAgents } from './packem_shared/AGENTS_FILENAME-Cxx7wiSs.mjs';
4
4
  export { default as discoverAuthApiCalls } from './packem_shared/discoverAuthApiCalls-Dx3K42rk.mjs';
5
5
  export { CONTAINERS_FILENAME, discoverContainers } from './packem_shared/CONTAINERS_FILENAME-DjpXMqhp.mjs';
6
- export { default as discoverCrons } from './packem_shared/discoverCrons-_aosTRLr.mjs';
6
+ export { default as discoverCrons } from './packem_shared/discoverCrons-DvqkEWdx.mjs';
7
7
  export { FLAGS_FILENAME, discoverFlags } from './packem_shared/FLAGS_FILENAME-Dg4mKUuz.mjs';
8
8
  export { discoverFunctions } from './packem_shared/discoverFunctions-BJ-qR7Rg.mjs';
9
9
  export { default as discoverHttpRoutes } from './packem_shared/discoverHttpRoutes-daCzuTe8.mjs';
@@ -16,15 +16,16 @@ export { default as discoverQueries } from './packem_shared/discoverQueries-CJnn
16
16
  export { QUEUES_FILENAME, discoverQueues } from './packem_shared/QUEUES_FILENAME-Xv6Vy-Ku.mjs';
17
17
  export { default as discoverR2sqlCalls } from './packem_shared/discoverR2sqlCalls-pnpicWfz.mjs';
18
18
  export { discoverRlsMetadata, default as discoverRlsProcedures } from './packem_shared/discoverRlsMetadata-DppniPUH.mjs';
19
- export { default as discoverSchema } from './packem_shared/discoverSchema-DFWVgU5o.mjs';
19
+ export { discoverSandboxUsage } from './packem_shared/discoverSandboxUsage-BUM2r90k.mjs';
20
+ export { default as discoverSchema } from './packem_shared/discoverSchema-BaSAvooG.mjs';
20
21
  export { SHAPES_FILENAME, discoverShapes } from './packem_shared/SHAPES_FILENAME-DOhPGi-6.mjs';
21
22
  export { default as discoverStorageRulesMetadata } from './packem_shared/discoverStorageRulesMetadata-CnHl2rXD.mjs';
22
- export { WORKFLOWS_FILENAME, discoverWorkflows } from './packem_shared/WORKFLOWS_FILENAME-B2By8S4s.mjs';
23
- export { G as GENERATED_HEADER, e as emitAgents, a as emitApi, b as emitCollections, c as emitContainers, d as emitCrons, f as emitDataModel, g as emitDrizzleSchema, h as emitFunctions, i as emitServer, j as emitShard, k as emitVectors, l as emitWorkflows, m as emitWranglerCronTriggers } from './packem_shared/emit-C_29WVlW.mjs';
24
- export { emitApp } from './packem_shared/emitApp-C9oljiZd.mjs';
25
- export { buildOpenApiDocument, emitOpenApi, emitOpenApiModule } from './packem_shared/buildOpenApiDocument-saZ_-kl4.mjs';
26
- export { OPENRPC_VERSION, buildOpenRpcDocument, emitOpenRpc, emitOpenRpcModule } from './packem_shared/OPENRPC_VERSION-Cyttekkt.mjs';
27
- export { SCHEMA_SNAPSHOT_FILENAME, createCodegenProject, refreshCodegenProject, runCodegen } from './packem_shared/SCHEMA_SNAPSHOT_FILENAME-CMjGZSa1.mjs';
23
+ export { WORKFLOWS_FILENAME, discoverWorkflows } from './packem_shared/WORKFLOWS_FILENAME-Dtuzvluc.mjs';
24
+ export { G as GENERATED_HEADER, e as emitAgents, a as emitApi, b as emitCollections, c as emitContainers, d as emitCrons, f as emitDataModel, g as emitDrizzleSchema, h as emitFunctions, i as emitServer, j as emitShard, k as emitVectors, l as emitWorkflows, m as emitWranglerCronTriggers } from './packem_shared/emit-DzOk6uuP.mjs';
25
+ export { emitApp } from './packem_shared/emitApp-DDXi_E8N.mjs';
26
+ export { buildOpenApiDocument, emitOpenApi, emitOpenApiModule } from './packem_shared/buildOpenApiDocument-5t2Tpo16.mjs';
27
+ export { OPENRPC_VERSION, buildOpenRpcDocument, emitOpenRpc, emitOpenRpcModule } from './packem_shared/OPENRPC_VERSION-DYr4zeJA.mjs';
28
+ export { SCHEMA_SNAPSHOT_FILENAME, createCodegenProject, refreshCodegenProject, runCodegen } from './packem_shared/SCHEMA_SNAPSHOT_FILENAME-JnnVthcj.mjs';
28
29
  export { SCHEMA_SNAPSHOT_VERSION, SchemaSnapshotParseError, buildSchemaSnapshot, diffSchemaSnapshots, evaluateSchemaDrift, parseSchemaSnapshot, serializeSchemaSnapshot } from './packem_shared/SCHEMA_SNAPSHOT_VERSION-D0ARY6rL.mjs';
29
30
  export { schemaFromIr } from './packem_shared/schemaFromIr-DTYsLBaA.mjs';
30
31
  export { LUNORA_ERROR_CODES, validatorIrToJsonSchema } from './packem_shared/LUNORA_ERROR_CODES-DvTLozCu.mjs';
@@ -1,7 +1,8 @@
1
1
  import { existsSync } from 'node:fs';
2
2
  import { join } from 'node:path';
3
3
  import { agentDefaultName, agentClassName, agentBindingName, voiceBindingName, voiceClassName } from '@lunora/agent/naming';
4
- import { SyntaxKind, Node } from 'ts-morph';
4
+ import { LunoraError } from '@lunora/errors';
5
+ import { Node, SyntaxKind } from 'ts-morph';
5
6
  import { diagnosticAt } from './CodegenDiagnosticError-DyQ5FwkM.mjs';
6
7
 
7
8
  const AGENTS_FILENAME = "agents.ts";
@@ -79,17 +80,23 @@ const agentFromCall = (call, exportName) => {
79
80
  }
80
81
  return ir;
81
82
  };
83
+ const unwrapToCallExpression = (node) => {
84
+ let current = node;
85
+ while (current && (Node.isAsExpression(current) || Node.isSatisfiesExpression(current) || Node.isParenthesizedExpression(current))) {
86
+ current = current.getExpression();
87
+ }
88
+ return current && Node.isCallExpression(current) ? current : void 0;
89
+ };
82
90
  const agentsFromSource = (source) => {
83
91
  const agents = [];
84
92
  for (const declaration of source.getVariableDeclarations()) {
85
93
  if (!declaration.isExported()) {
86
94
  continue;
87
95
  }
88
- const initializer = declaration.getInitializer();
89
- if (initializer?.getKind() !== SyntaxKind.CallExpression) {
96
+ const call = unwrapToCallExpression(declaration.getInitializer());
97
+ if (!call) {
90
98
  continue;
91
99
  }
92
- const call = initializer;
93
100
  const callee = call.getExpression();
94
101
  if (!Node.isIdentifier(callee) || !isDefineAgent(callee)) {
95
102
  continue;
@@ -102,6 +109,40 @@ const agentsFromSource = (source) => {
102
109
  }
103
110
  return agents;
104
111
  };
112
+ const assertUniqueNames = (agents) => {
113
+ const seenNames = /* @__PURE__ */ new Map();
114
+ const seenBindings = /* @__PURE__ */ new Map();
115
+ const seenClasses = /* @__PURE__ */ new Map();
116
+ for (const agent of agents) {
117
+ const priorName = seenNames.get(agent.name);
118
+ if (priorName !== void 0) {
119
+ throw new LunoraError(
120
+ "DUPLICATE_AGENT_NAME",
121
+ `Duplicate agent name "${agent.name}": produced by both "${priorName}" and "${agent.exportName}". Deployed agent names must be unique across the project.`,
122
+ { status: 500 }
123
+ );
124
+ }
125
+ seenNames.set(agent.name, agent.exportName);
126
+ const priorBinding = seenBindings.get(agent.bindingName);
127
+ if (priorBinding !== void 0) {
128
+ throw new LunoraError(
129
+ "DUPLICATE_AGENT_BINDING",
130
+ `Duplicate agent binding "${agent.bindingName}": produced by both "${priorBinding}" and "${agent.exportName}". Agent export names must yield unique binding names.`,
131
+ { status: 500 }
132
+ );
133
+ }
134
+ seenBindings.set(agent.bindingName, agent.exportName);
135
+ const priorClass = seenClasses.get(agent.className);
136
+ if (priorClass !== void 0) {
137
+ throw new LunoraError(
138
+ "DUPLICATE_AGENT_CLASS",
139
+ `Duplicate agent class "${agent.className}": produced by both "${priorClass}" and "${agent.exportName}". Agent export names must yield unique generated class names.`,
140
+ { status: 500 }
141
+ );
142
+ }
143
+ seenClasses.set(agent.className, agent.exportName);
144
+ }
145
+ };
105
146
  const discoverAgents = (project, lunoraDirectory) => {
106
147
  const agentsPath = join(lunoraDirectory, AGENTS_FILENAME);
107
148
  if (!existsSync(agentsPath)) {
@@ -110,6 +151,7 @@ const discoverAgents = (project, lunoraDirectory) => {
110
151
  const source = project.getSourceFile(agentsPath) ?? project.addSourceFileAtPath(agentsPath);
111
152
  const agents = agentsFromSource(source);
112
153
  agents.sort((a, b) => a.exportName.localeCompare(b.exportName));
154
+ assertUniqueNames(agents);
113
155
  return agents;
114
156
  };
115
157
 
@@ -1,4 +1,4 @@
1
1
  import '@lunora/agent/component';
2
2
  import '@lunora/errors';
3
- export { G as GENERATED_HEADER, n as buildStorageColumns, e as emitAgents, a as emitApi, b as emitCollections, c as emitContainers, d as emitCrons, f as emitDataModel, g as emitDrizzleSchema, h as emitFunctions, o as emitQueues, p as emitSeed, i as emitServer, j as emitShard, k as emitVectors, l as emitWorkflows, m as emitWranglerCronTriggers } from './emit-C_29WVlW.mjs';
3
+ export { G as GENERATED_HEADER, n as buildStorageColumns, e as emitAgents, a as emitApi, b as emitCollections, c as emitContainers, d as emitCrons, f as emitDataModel, g as emitDrizzleSchema, h as emitFunctions, o as emitQueues, p as emitSeed, i as emitServer, j as emitShard, k as emitVectors, l as emitWorkflows, m as emitWranglerCronTriggers } from './emit-DzOk6uuP.mjs';
4
4
  import './paths-BRd6JHuF.mjs';
@@ -1,4 +1,4 @@
1
- import { G as GENERATED_HEADER } from './emit-C_29WVlW.mjs';
1
+ import { G as GENERATED_HEADER } from './emit-DzOk6uuP.mjs';
2
2
  import { s as sanitizeNamespace } from './paths-BRd6JHuF.mjs';
3
3
  import { argsObjectSchema, LUNORA_ERROR_CODES } from './LUNORA_ERROR_CODES-DvTLozCu.mjs';
4
4
 
@@ -3,14 +3,14 @@ import { join, dirname } from 'node:path';
3
3
  import { performance } from 'node:perf_hooks';
4
4
  import { LunoraError } from '@lunora/errors';
5
5
  import { Node, SyntaxKind, Project } from 'ts-morph';
6
- import { lintSchema } from './formatAdvisories-DRhEiJUz.mjs';
6
+ import { lintSchema } from './formatAdvisories-BmF7Mplc.mjs';
7
7
  import { listLunoraSourceFiles, lunoraRelativePath, classifyProcedureCall, inlineHandler as inlineHandler$1, procedureHandler, chainUsesWrappedCall, isDatabaseAccessor, chainHasStep, discoverFunctions } from './discoverFunctions-BJ-qR7Rg.mjs';
8
- import { discoverAgents } from './AGENTS_FILENAME-BksnAJgx.mjs';
8
+ import { discoverAgents } from './AGENTS_FILENAME-Cxx7wiSs.mjs';
9
9
  import discoverAuthApiCalls from './discoverAuthApiCalls-Dx3K42rk.mjs';
10
10
  import { discoverContainers } from './CONTAINERS_FILENAME-DjpXMqhp.mjs';
11
- import discoverCrons from './discoverCrons-_aosTRLr.mjs';
11
+ import discoverCrons from './discoverCrons-DvqkEWdx.mjs';
12
12
  import { diagnosticAt } from './CodegenDiagnosticError-DyQ5FwkM.mjs';
13
- import { C as CAPABILITIES, n as buildStorageColumns, f as emitDataModel, a as emitApi, i as emitServer, h as emitFunctions, j as emitShard, b as emitCollections, c as emitContainers, l as emitWorkflows, e as emitAgents, o as emitQueues, d as emitCrons, k as emitVectors, g as emitDrizzleSchema, p as emitSeed, m as emitWranglerCronTriggers } from './emit-C_29WVlW.mjs';
13
+ import { C as CAPABILITIES, n as buildStorageColumns, f as emitDataModel, a as emitApi, i as emitServer, h as emitFunctions, j as emitShard, b as emitCollections, c as emitContainers, l as emitWorkflows, e as emitAgents, o as emitQueues, d as emitCrons, k as emitVectors, g as emitDrizzleSchema, p as emitSeed, m as emitWranglerCronTriggers } from './emit-DzOk6uuP.mjs';
14
14
  import { discoverFlagKeys } from './FLAGS_FILENAME-Dg4mKUuz.mjs';
15
15
  import discoverHttpRoutes from './discoverHttpRoutes-daCzuTe8.mjs';
16
16
  import discoverInserts from './discoverInserts-DI4q5NxE.mjs';
@@ -22,15 +22,16 @@ import discoverQueries from './discoverQueries-CJnnnLpd.mjs';
22
22
  import { discoverQueues } from './QUEUES_FILENAME-Xv6Vy-Ku.mjs';
23
23
  import discoverR2sqlCalls from './discoverR2sqlCalls-pnpicWfz.mjs';
24
24
  import discoverRlsProcedures, { discoverRlsMetadata } from './discoverRlsMetadata-DppniPUH.mjs';
25
- import discoverSchema from './discoverSchema-DFWVgU5o.mjs';
25
+ import { discoverSandboxUsage } from './discoverSandboxUsage-BUM2r90k.mjs';
26
+ import discoverSchema from './discoverSchema-BaSAvooG.mjs';
26
27
  import { secretKindOf, redact } from './redact-oTmsol5A.mjs';
27
28
  import { discoverShapes } from './SHAPES_FILENAME-DOhPGi-6.mjs';
28
29
  import discoverStorageRulesMetadata from './discoverStorageRulesMetadata-CnHl2rXD.mjs';
29
30
  import { e as enclosingExportName$2 } from './discover-ast-CT6BgBr4.mjs';
30
- import { discoverWorkflows } from './WORKFLOWS_FILENAME-B2By8S4s.mjs';
31
- import { emitApp } from './emitApp-C9oljiZd.mjs';
32
- import { buildOpenApiDocument, emitOpenApiModule } from './buildOpenApiDocument-saZ_-kl4.mjs';
33
- import { buildOpenRpcDocument, emitOpenRpcModule } from './OPENRPC_VERSION-Cyttekkt.mjs';
31
+ import { discoverWorkflows } from './WORKFLOWS_FILENAME-Dtuzvluc.mjs';
32
+ import { emitApp } from './emitApp-DDXi_E8N.mjs';
33
+ import { buildOpenApiDocument, emitOpenApiModule } from './buildOpenApiDocument-5t2Tpo16.mjs';
34
+ import { buildOpenRpcDocument, emitOpenRpcModule } from './OPENRPC_VERSION-DYr4zeJA.mjs';
34
35
  import { buildSchemaSnapshot, serializeSchemaSnapshot } from './SCHEMA_SNAPSHOT_VERSION-D0ARY6rL.mjs';
35
36
 
36
37
  const HTTP_VERBS$1 = /* @__PURE__ */ new Set(["delete", "get", "head", "options", "patch", "post", "put"]);
@@ -2699,41 +2700,6 @@ const discoverRelationLoads = (project, lunoraDirectory) => {
2699
2700
  return rows;
2700
2701
  };
2701
2702
 
2702
- const SANDBOX_MODULE_SPECIFIERS = /* @__PURE__ */ new Set(["@lunora/agent", "@lunora/agent/sandbox"]);
2703
- const scanImportDeclaration = (declaration) => {
2704
- const found = { usesSandboxBrowser: false, usesSandboxContainer: false };
2705
- if (!SANDBOX_MODULE_SPECIFIERS.has(declaration.getModuleSpecifierValue()) || declaration.isTypeOnly()) {
2706
- return found;
2707
- }
2708
- for (const named of declaration.getNamedImports()) {
2709
- if (named.isTypeOnly()) {
2710
- continue;
2711
- }
2712
- const name = named.getNameNode().getText();
2713
- if (name === "browserTool") {
2714
- found.usesSandboxBrowser = true;
2715
- } else if (name === "containerTool") {
2716
- found.usesSandboxContainer = true;
2717
- }
2718
- }
2719
- return found;
2720
- };
2721
- const discoverSandboxUsage = (project, lunoraDirectory) => {
2722
- const usage = { usesSandboxBrowser: false, usesSandboxContainer: false };
2723
- for (const filePath of listLunoraSourceFiles(lunoraDirectory)) {
2724
- const sourceFile = project.getSourceFile(filePath) ?? project.addSourceFileAtPath(filePath);
2725
- for (const declaration of sourceFile.getImportDeclarations()) {
2726
- const found = scanImportDeclaration(declaration);
2727
- usage.usesSandboxBrowser ||= found.usesSandboxBrowser;
2728
- usage.usesSandboxContainer ||= found.usesSandboxContainer;
2729
- }
2730
- if (usage.usesSandboxBrowser && usage.usesSandboxContainer) {
2731
- break;
2732
- }
2733
- }
2734
- return usage;
2735
- };
2736
-
2737
2703
  const literalValueOf = (node) => {
2738
2704
  if (Node.isStringLiteral(node) || Node.isNoSubstitutionTemplateLiteral(node)) {
2739
2705
  return node.getLiteralText();
@@ -3155,6 +3121,44 @@ const findTsconfig = (startPath) => {
3155
3121
  return void 0;
3156
3122
  };
3157
3123
  const toPosixPath = (path) => path.replaceAll("\\", "/");
3124
+ const assertNoWorkflowAgentCollision = (workflows, agents) => {
3125
+ const namesByLabel = /* @__PURE__ */ new Map();
3126
+ const bindingsByLabel = /* @__PURE__ */ new Map();
3127
+ const classesByLabel = /* @__PURE__ */ new Map();
3128
+ for (const workflow of workflows) {
3129
+ namesByLabel.set(workflow.name, `workflow "${workflow.exportName}"`);
3130
+ bindingsByLabel.set(workflow.bindingName, `workflow "${workflow.exportName}"`);
3131
+ classesByLabel.set(workflow.className, `workflow "${workflow.exportName}"`);
3132
+ }
3133
+ for (const agent of agents) {
3134
+ const priorName = namesByLabel.get(agent.name);
3135
+ if (priorName !== void 0) {
3136
+ throw new LunoraError(
3137
+ // eslint-disable-next-line no-secrets/no-secrets -- an error code, not a secret
3138
+ "DUPLICATE_WORKFLOW_NAME",
3139
+ `Duplicate deployed name "${agent.name}": produced by both ${priorName} and agent "${agent.exportName}". Workflow and agent names share the same wrangler workflows[] array and must be unique together.`,
3140
+ { status: 500 }
3141
+ );
3142
+ }
3143
+ const priorBinding = bindingsByLabel.get(agent.bindingName);
3144
+ if (priorBinding !== void 0) {
3145
+ throw new LunoraError(
3146
+ // eslint-disable-next-line no-secrets/no-secrets -- an error code, not a secret
3147
+ "DUPLICATE_WORKFLOW_BINDING",
3148
+ `Duplicate binding "${agent.bindingName}": produced by both ${priorBinding} and agent "${agent.exportName}". Workflow and agent bindings share the same wrangler workflows[] array and must be unique together.`,
3149
+ { status: 500 }
3150
+ );
3151
+ }
3152
+ const priorClass = classesByLabel.get(agent.className);
3153
+ if (priorClass !== void 0) {
3154
+ throw new LunoraError(
3155
+ "DUPLICATE_WORKFLOW_CLASS",
3156
+ `Duplicate generated class "${agent.className}": produced by both ${priorClass} and agent "${agent.exportName}". Workflow and agent export names must yield unique generated class names.`,
3157
+ { status: 500 }
3158
+ );
3159
+ }
3160
+ }
3161
+ };
3158
3162
  const createCodegenProject = (lunoraDirectory) => {
3159
3163
  const tsconfigPath = findTsconfig(lunoraDirectory);
3160
3164
  return tsconfigPath ? new Project({ skipAddingFilesFromTsConfig: false, tsConfigFilePath: tsconfigPath, useInMemoryFileSystem: false }) : new Project({ skipAddingFilesFromTsConfig: true, useInMemoryFileSystem: false });
@@ -3203,6 +3207,7 @@ const runCodegen = (options) => {
3203
3207
  const workflows = discoverWorkflows(project, lunoraDirectory);
3204
3208
  const queues = discoverQueues(project, lunoraDirectory);
3205
3209
  const agents = discoverAgents(project, lunoraDirectory);
3210
+ assertNoWorkflowAgentCollision(workflows, agents);
3206
3211
  const crons = discoverCrons(project, lunoraDirectory, workflows, agents);
3207
3212
  const containers = discoverContainers(project, lunoraDirectory);
3208
3213
  const advisories = options.lint === false ? [] : lintSchema({
@@ -2,7 +2,7 @@ import { existsSync } from 'node:fs';
2
2
  import { join } from 'node:path';
3
3
  import { LunoraError } from '@lunora/errors';
4
4
  import { workflowDefaultName, workflowClassName, workflowBindingName } from '@lunora/workflow';
5
- import { SyntaxKind, Node } from 'ts-morph';
5
+ import { Node, SyntaxKind } from 'ts-morph';
6
6
  import { diagnosticAt } from './CodegenDiagnosticError-DyQ5FwkM.mjs';
7
7
 
8
8
  const WORKFLOWS_FILENAME = "workflows.ts";
@@ -88,17 +88,23 @@ const workflowFromCall = (call, exportName) => {
88
88
  }
89
89
  return ir;
90
90
  };
91
+ const unwrapToCallExpression = (node) => {
92
+ let current = node;
93
+ while (current && (Node.isAsExpression(current) || Node.isSatisfiesExpression(current) || Node.isParenthesizedExpression(current))) {
94
+ current = current.getExpression();
95
+ }
96
+ return current && Node.isCallExpression(current) ? current : void 0;
97
+ };
91
98
  const workflowsFromSource = (source) => {
92
99
  const workflows = [];
93
100
  for (const declaration of source.getVariableDeclarations()) {
94
101
  if (!declaration.isExported()) {
95
102
  continue;
96
103
  }
97
- const initializer = declaration.getInitializer();
98
- if (initializer?.getKind() !== SyntaxKind.CallExpression) {
104
+ const call = unwrapToCallExpression(declaration.getInitializer());
105
+ if (!call) {
99
106
  continue;
100
107
  }
101
- const call = initializer;
102
108
  const callee = call.getExpression();
103
109
  if (!Node.isIdentifier(callee) || !isDefineWorkflow(callee)) {
104
110
  continue;
@@ -1,4 +1,4 @@
1
- import { G as GENERATED_HEADER } from './emit-C_29WVlW.mjs';
1
+ import { G as GENERATED_HEADER } from './emit-DzOk6uuP.mjs';
2
2
  import { s as sanitizeNamespace } from './paths-BRd6JHuF.mjs';
3
3
  import { LUNORA_ERROR_CODES, objectSchema, argsObjectSchema, validatorIrToJsonSchema } from './LUNORA_ERROR_CODES-DvTLozCu.mjs';
4
4
 
@@ -46,7 +46,7 @@ const rootIdentifierOf = (node) => {
46
46
  };
47
47
  const stringArgument = (call, index) => {
48
48
  const argument = call.getArguments()[index];
49
- return argument && Node.isStringLiteral(argument) ? argument.getLiteralValue() : void 0;
49
+ return argument && (Node.isStringLiteral(argument) || Node.isNoSubstitutionTemplateLiteral(argument)) ? argument.getLiteralValue() : void 0;
50
50
  };
51
51
  const literalValue = (node, jobName) => {
52
52
  if (Node.isStringLiteral(node) || Node.isNoSubstitutionTemplateLiteral(node)) {
@@ -165,12 +165,27 @@ const resolveTarget = (call, index, jobName, workflowsByName, agentsByName) => {
165
165
  }
166
166
  if (argument && Node.isIdentifier(argument)) {
167
167
  const workflow = workflowsByName.get(argument.getText());
168
+ const agent = agentsByName.get(argument.getText());
169
+ if (workflow && agent) {
170
+ throw diagnosticAt(
171
+ argument,
172
+ `Cron job "${jobName}" references "${argument.getText()}", which is ambiguous: a workflow and an agent are both declared under that name.`,
173
+ {
174
+ code: "CRON_NON_STATIC_FN",
175
+ name: "LunoraError",
176
+ status: 500
177
+ }
178
+ );
179
+ }
168
180
  if (workflow) {
169
181
  return workflowTarget(workflow);
170
182
  }
183
+ if (agent) {
184
+ return agentTarget(agent);
185
+ }
171
186
  throw diagnosticAt(
172
187
  argument,
173
- `Cron job "${jobName}" references "${argument.getText()}", which is neither a function (internal.file.fn / api.file.fn) nor a declared workflow in lunora/workflows.ts.`,
188
+ `Cron job "${jobName}" references "${argument.getText()}", which is neither a function (internal.file.fn / api.file.fn) nor a declared workflow in lunora/workflows.ts nor a declared agent in lunora/agents.ts.`,
174
189
  {
175
190
  code: "CRON_NON_STATIC_FN",
176
191
  name: "LunoraError",
@@ -0,0 +1,38 @@
1
+ import { listLunoraSourceFiles } from './discoverFunctions-BJ-qR7Rg.mjs';
2
+
3
+ const SANDBOX_MODULE_SPECIFIERS = /* @__PURE__ */ new Set(["@lunora/agent", "@lunora/agent/sandbox"]);
4
+ const scanImportDeclaration = (declaration) => {
5
+ const found = { usesSandboxBrowser: false, usesSandboxContainer: false };
6
+ if (!SANDBOX_MODULE_SPECIFIERS.has(declaration.getModuleSpecifierValue()) || declaration.isTypeOnly()) {
7
+ return found;
8
+ }
9
+ for (const named of declaration.getNamedImports()) {
10
+ if (named.isTypeOnly()) {
11
+ continue;
12
+ }
13
+ const name = named.getNameNode().getText();
14
+ if (name === "browserTool") {
15
+ found.usesSandboxBrowser = true;
16
+ } else if (name === "containerTool") {
17
+ found.usesSandboxContainer = true;
18
+ }
19
+ }
20
+ return found;
21
+ };
22
+ const discoverSandboxUsage = (project, lunoraDirectory) => {
23
+ const usage = { usesSandboxBrowser: false, usesSandboxContainer: false };
24
+ for (const filePath of listLunoraSourceFiles(lunoraDirectory)) {
25
+ const sourceFile = project.getSourceFile(filePath) ?? project.addSourceFileAtPath(filePath);
26
+ for (const declaration of sourceFile.getImportDeclarations()) {
27
+ const found = scanImportDeclaration(declaration);
28
+ usage.usesSandboxBrowser ||= found.usesSandboxBrowser;
29
+ usage.usesSandboxContainer ||= found.usesSandboxContainer;
30
+ }
31
+ if (usage.usesSandboxBrowser && usage.usesSandboxContainer) {
32
+ break;
33
+ }
34
+ }
35
+ return usage;
36
+ };
37
+
38
+ export { discoverSandboxUsage };
@@ -428,6 +428,7 @@ const parseSourceCall = (args) => {
428
428
  binding: stringPropertyOf(first, "binding") ?? "",
429
429
  columns: stringArrayPropertyOf(first, "columns"),
430
430
  hasReconcile: first.getProperty("reconcileEveryMs") !== void 0,
431
+ hasSoftDelete: first.getProperty("softDeleteColumn") !== void 0,
431
432
  hasTenantBy: first.getProperty("tenantBy") !== void 0,
432
433
  idColumn: stringPropertyOf(first, "idColumn"),
433
434
  mode: stringPropertyOf(first, "mode"),
@@ -760,12 +760,23 @@ ${objectMembers}
760
760
  };
761
761
  const agentRuntimeFunctionNames = () => Object.keys(agentComponent().functions).toSorted((a, b) => a.localeCompare(b));
762
762
  const takenAgentFunctionNames = (functions) => new Set(functions.filter((definition) => sanitizeNamespace(definition.filePath) === "agents").map((definition) => definition.exportName));
763
+ const internalAgentRuntimeFunctionNames = () => new Set(
764
+ Object.entries(agentComponent().functions).filter(([, definition]) => definition.visibility === "internal").map(([name]) => name)
765
+ );
763
766
  const renderAgentFunctionRegistry = (agents, functions) => {
764
767
  const empty = { importLine: "", lines: "", prelude: "" };
765
768
  if (agents.length === 0) {
766
769
  return empty;
767
770
  }
768
771
  const taken = takenAgentFunctionNames(functions);
772
+ const internal = internalAgentRuntimeFunctionNames();
773
+ const shadowedInternal = functions.find((definition) => sanitizeNamespace(definition.filePath) === "agents" && internal.has(definition.exportName));
774
+ if (shadowedInternal) {
775
+ throw new LunoraError(
776
+ "INTERNAL",
777
+ `@lunora/codegen: "agents:${shadowedInternal.exportName}" is reserved for the durable agent loop's internal dispatch — rename the "${shadowedInternal.exportName}" export in ${shadowedInternal.filePath}`
778
+ );
779
+ }
769
780
  const names = agentRuntimeFunctionNames().filter((name) => !taken.has(name));
770
781
  if (names.length === 0) {
771
782
  return empty;
@@ -2668,7 +2679,7 @@ const LUNORA_RLS_READ_REGISTRY = buildRlsReadRegistry(Object.values(LUNORA_FUNCT
2668
2679
  const shapeGuardImport = hasShapes ? "assertShapeShardable, " : "";
2669
2680
  const importLines = [
2670
2681
  `import type { ${doTypeImports.join(", ")} } from "${base.do}";`,
2671
- `import { applyCdcChanges, ${shapeGuardImport}createShardCtxDb, ${hasSourcedTables ? "isSourceDue, pullExternalSourceTick, " : ""}runDataMigration, runShardMigrations, ${relationFanout.importFragment}ShardDO as ShardDOBase } from "${base.do}";`,
2682
+ `import { applyCdcChanges, ${shapeGuardImport}createShardCtxDb, ${hasSourcedTables ? "isSourceDue, pullExternalSourceIncrementalTick, pullExternalSourceTick, " : ""}runDataMigration, runShardMigrations, ${relationFanout.importFragment}ShardDO as ShardDOBase } from "${base.do}";`,
2672
2683
  ...hasSourcedTables ? [`import type { ExternalSourceLike, SourceClientLike } from "${base.do}";`] : [],
2673
2684
  // `asBucketStorage` (the bucket-aware `ctx.storage` wrapper) and
2674
2685
  // `createSecrets` (the `ctx.secrets` core built-in) live in
@@ -2875,14 +2886,14 @@ const sourceClientCache = new WeakMap<object, Map<string, SourceClientLike>>();
2875
2886
  const sourcePollAtCache = new WeakMap<object, Map<string, number>>();
2876
2887
  ` : "";
2877
2888
  const externalSourceOverride = hasSourcedTables ? `
2878
- protected override async pollExternalSources(): Promise<number> {
2889
+ protected override async pollExternalSources(): Promise<number | undefined> {
2879
2890
  const env = (this.env ?? {}) as Record<string, unknown>;
2880
2891
  const sourced = Object.entries((schema as unknown as SchemaLike).tables)
2881
2892
  .map(([table, definition]) => [table, (definition as { externalSource?: ExternalSourceLike }).externalSource] as const)
2882
2893
  .filter((entry): entry is [string, ExternalSourceLike] => entry[1] !== undefined);
2883
2894
 
2884
2895
  if (sourced.length === 0) {
2885
- return 0;
2896
+ return undefined;
2886
2897
  }
2887
2898
 
2888
2899
  const shardKey = this.currentShardKey();
@@ -2912,54 +2923,68 @@ const sourcePollAtCache = new WeakMap<object, Map<string, number>>();
2912
2923
  }
2913
2924
 
2914
2925
  const now = Date.now();
2915
- // \`active\` counts non-manual sources: while > 0 the alarm re-arms; a
2916
- // manual-only schema returns 0 so the shared alarm goes idle.
2917
- let active = 0;
2926
+ // \`nextDueAt\` tracks the EARLIEST next-due timestamp across every
2927
+ // non-manual source; the shared alarm re-arms there instead of the
2928
+ // fixed 2 s global-shape floor, so a large \`refresh.everyMs\` actually
2929
+ // sleeps until it's due. Stays \`undefined\` when every source is
2930
+ // \`refresh: "manual"\`, so the shared alarm goes idle for this tier.
2931
+ let nextDueAt: number | undefined;
2918
2932
 
2919
2933
  for (const [table, source] of sourced) {
2920
2934
  if (source.refresh === "manual") {
2921
2935
  continue;
2922
2936
  }
2923
2937
 
2924
- active += 1;
2925
-
2926
- if (!isSourceDue(source.refresh, polledAt.get(table), now)) {
2927
- continue;
2928
- }
2938
+ if (isSourceDue(source.refresh, polledAt.get(table), now)) {
2939
+ try {
2940
+ let client = clients.get(source.binding);
2929
2941
 
2930
- try {
2931
- let client = clients.get(source.binding);
2942
+ if (client === undefined) {
2943
+ client = config.sourceClient?.(env, source.binding);
2932
2944
 
2933
- if (client === undefined) {
2934
- client = config.sourceClient?.(env, source.binding);
2945
+ if (client !== undefined) {
2946
+ clients.set(source.binding, client);
2947
+ }
2948
+ }
2935
2949
 
2936
- if (client !== undefined) {
2937
- clients.set(source.binding, client);
2950
+ if (client === undefined) {
2951
+ // No SqlClient resolved for this binding (host never wired
2952
+ // \`config.sourceClient\`, or wired it wrong). Surface it in the
2953
+ // Logs panel and stamp \`polledAt\` so a persistent misconfig backs
2954
+ // off to \`refresh.everyMs\` instead of retrying every alarm tick.
2955
+ this.recordExternalSourceError(table, new Error(\`external-source: no sourceClient resolved for binding "\${source.binding}"\`));
2956
+ } else if (source.mode === "incremental") {
2957
+ // Incremental (plan 136): pull only rows past the durable
2958
+ // watermark (or a full-pull seed/reconcile), upsert-only.
2959
+ // eslint-disable-next-line no-await-in-loop -- one sourced table at a time; sequential keeps the writer transaction simple
2960
+ await pullExternalSourceIncrementalTick(this.sql as SqlExec, writer, client, table, source, shardKey, now);
2961
+ } else {
2962
+ // eslint-disable-next-line no-await-in-loop -- one sourced table at a time; slices are independent but small and sequential keeps the writer transaction simple
2963
+ await pullExternalSourceTick(this.sql as SqlExec, writer, client, table, source, shardKey);
2938
2964
  }
2939
- }
2940
2965
 
2941
- if (client === undefined) {
2942
- // No SqlClient resolved for this binding (host never wired
2943
- // \`config.sourceClient\`, or wired it wrong). Surface it in the
2944
- // Logs panel and stamp \`polledAt\` so a persistent misconfig backs
2945
- // off to \`refresh.everyMs\` instead of retrying every alarm tick.
2946
- this.recordExternalSourceError(table, new Error(\`external-source: no sourceClient resolved for binding "\${source.binding}"\`));
2947
- polledAt.set(table, now);
2948
- continue;
2966
+ // Timestamp AFTER the poll finishes, not the batch-start \`now\` — a
2967
+ // poll that outruns \`everyMs\` must not make \`nextDueAt\` (below)
2968
+ // stale-immediate and re-arm the alarm in a hammering loop.
2969
+ polledAt.set(table, Date.now());
2970
+ } catch (error) {
2971
+ this.recordExternalSourceError(table, error);
2972
+ // Stamp on failure too, so a persistently failing source throttles
2973
+ // to \`refresh.everyMs\` rather than being hammered every tick.
2974
+ polledAt.set(table, Date.now());
2949
2975
  }
2950
-
2951
- // eslint-disable-next-line no-await-in-loop -- one sourced table at a time; slices are independent but small and sequential keeps the writer transaction simple
2952
- await pullExternalSourceTick(this.sql as SqlExec, writer, client, table, source, shardKey);
2953
- polledAt.set(table, now);
2954
- } catch (error) {
2955
- this.recordExternalSourceError(table, error);
2956
- // Stamp on failure too, so a persistently failing source throttles
2957
- // to \`refresh.everyMs\` rather than being hammered every tick.
2958
- polledAt.set(table, now);
2959
2976
  }
2977
+
2978
+ // This source's own next-due time, read AFTER the poll-or-skip above
2979
+ // so a just-polled source reports \`now + everyMs\` (its FRESH due
2980
+ // time), not the stale pre-poll one. An omitted \`refresh\` (poll
2981
+ // every tick) is due again immediately.
2982
+ const sourceNextDueAt = source.refresh === undefined ? now : (polledAt.get(table) ?? now) + source.refresh.everyMs;
2983
+
2984
+ nextDueAt = nextDueAt === undefined ? sourceNextDueAt : Math.min(nextDueAt, sourceNextDueAt);
2960
2985
  }
2961
2986
 
2962
- return active;
2987
+ return nextDueAt;
2963
2988
  }
2964
2989
  ` : "";
2965
2990
  const sourceConstructorOverride = hasSourcedTables ? `
@@ -1,4 +1,4 @@
1
- import { A as APP_METHOD_CAPABILITIES, G as GENERATED_HEADER } from './emit-C_29WVlW.mjs';
1
+ import { A as APP_METHOD_CAPABILITIES, G as GENERATED_HEADER } from './emit-DzOk6uuP.mjs';
2
2
 
3
3
  const hasFlagKey = (key) => `has${key.charAt(0).toUpperCase()}${key.slice(1)}`;
4
4
  const LONG_TAIL = APP_METHOD_CAPABILITIES.map(
@@ -22,6 +22,7 @@ const toAdvisorSchema = (schema) => {
22
22
  externallyManaged: table.externallyManaged ?? false,
23
23
  externalSource: table.externalSource ? {
24
24
  hasReconcile: table.externalSource.hasReconcile ?? false,
25
+ hasSoftDelete: table.externalSource.hasSoftDelete ?? false,
25
26
  hasTenantBy: table.externalSource.hasTenantBy,
26
27
  mode: table.externalSource.mode,
27
28
  unanalyzable: table.externalSource.unanalyzable
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lunora/codegen",
3
- "version": "1.0.0-alpha.44",
3
+ "version": "1.0.0-alpha.46",
4
4
  "description": "Code generator for Lunora: emits _generated/{api,server,dataModel}.ts from your schema",
5
5
  "keywords": [
6
6
  "cloudflare",
@@ -46,8 +46,8 @@
46
46
  "access": "public"
47
47
  },
48
48
  "dependencies": {
49
- "@lunora/advisor": "1.0.0-alpha.28",
50
- "@lunora/agent": "1.0.0-alpha.2",
49
+ "@lunora/advisor": "1.0.0-alpha.30",
50
+ "@lunora/agent": "1.0.0-alpha.4",
51
51
  "@lunora/container": "1.0.0-alpha.12",
52
52
  "@lunora/errors": "1.0.0-alpha.5",
53
53
  "@lunora/queue": "1.0.0-alpha.8",