@lunora/server 1.0.0-alpha.7 → 1.0.0-alpha.9

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
@@ -1,6 +1,6 @@
1
1
  import { Validator, Infer, ValidatorMap, InferValidatorMap, v } from '@lunora/values';
2
2
  export { type ColumnValidator, type Id, type Infer, ValidationError, type Validator, type ValidatorKind, v } from '@lunora/values';
3
- import { ArgsValidator, InferArgs, RegisteredAction, ActionCtx, MutationCtx, RegisteredMutation, QueryCtx, RegisteredQuery, RegisteredStream, FunctionKind, Secrets, LifecycleEvent, RegisteredLifecycleHook, TableDefinition, RegisteredFunction, VectorIndexDefinition, Schema, AggregateOp, DurableObjectJurisdiction, RelationDefinition, GlobalBackend, OnDeleteAction, TriggerBuilder, TriggerDefinition, VectorEmbedder, VectorMetric, AggregateIndexDefinition, RankIndexDefinition } from "./types.mjs";
3
+ import { ArgsValidator, InferArgs, RegisteredAction, ActionCtx, MutationCtx, RegisteredMutation, QueryCtx, RegisteredQuery, RegisteredStream, FunctionKind, Secrets, LifecycleEvent, RegisteredLifecycleHook, TableDefinition, RegisteredFunction, VectorIndexDefinition, Schema, AggregateOp, DurableObjectJurisdiction, RelationDefinition, GlobalBackend, OnDeleteAction, ExternalSourceDefinition, TriggerBuilder, TriggerDefinition, VectorEmbedder, VectorMetric, AggregateIndexDefinition, RankIndexDefinition } from "./types.mjs";
4
4
  export { type AnyApi, type AuthState, type DatabaseReader, type DatabaseWriter, type FunctionVisibility, type IndexDefinition, type IndexRangeBuilder, type LifecycleEventKind, type LunoraLogger, type PaginationOptions, type PaginationResult, type RankSortKey, type ReadOnlyStorage, type ScheduledFunctionDoc, type ScheduledJob, type Scheduler, type SearchFilterBuilder, type SearchIndexDefinition, type ShardMode, type Storage, type StorageMetadata, type SystemDatabaseReader, type SystemDoc, type SystemQuery, type SystemTableName, type TableReader, type TableVectorIndex, type TriggerAggregateOptions, type TriggerCtx, type TriggerDatabase, type TriggerDeleteEvent, type TriggerEvent, type TriggerGroupByEntry, type TriggerGroupByOptions, type TriggerHandler, type TriggerInsertEvent, type TriggerOp, type TriggerQueryArgs, type TriggerQueryPage, type TriggerRankOptions, type TriggerRankPageOptions, type TriggerRankResult, type TriggerRow, type TriggerTiming, type TriggerUpdateEvent, type VectorMatch, type VectorMatches, type VectorQueryInput, type VectorRecord, type VectorSearch, type VectorSearchReader, type VectorUpsertInput, type WorkflowCreateOptions, type WorkflowHandle, type WorkflowInstance, type WorkflowInstanceStatus, type WorkflowStatusResult, type Workflows, anyApi } from "./types.mjs";
5
5
  import { Context, Hono } from 'hono';
6
6
  import { b as Permission, R as Role, T as TypedDefinePolicyInput, a as Policy, D as DefinePolicyInput, W as WhereInput, c as RlsOptions } from "./packem_shared/types.d-DmvyEMD6.mjs";
@@ -283,7 +283,10 @@ declare const defineEnv: <S extends EnvShape>(shape: S) => EnvAccessor<S>;
283
283
  * The runtime's structural error mapper keys off `name === "LunoraError"` plus
284
284
  * the numeric `status`, so throwing one of these from a handler or middleware
285
285
  * yields the right RPC/HTTP status without any further wiring. `code` carries
286
- * the machine-readable reason for clients.
286
+ * the machine-readable reason for clients; the optional `data` carries a
287
+ * structured, JSON+wire-encodable payload propagated verbatim to the client
288
+ * (e.g. `{ retryAfterMs }`). Only an explicit `LunoraError`'s `data` crosses the
289
+ * wire — an unhandled throw is still redacted to a generic message server-side.
287
290
  */
288
291
  declare const CODE_STATUS: {
289
292
  readonly BAD_REQUEST: 400;
@@ -329,7 +332,9 @@ declare class LunoraError extends Error {
329
332
  override readonly name = "LunoraError";
330
333
  readonly code: LunoraErrorCode;
331
334
  readonly status: number;
332
- constructor(code: LunoraErrorCode, message?: string);
335
+ /** Structured, JSON+wire-encodable payload surfaced to the client alongside `code`. */
336
+ readonly data?: unknown;
337
+ constructor(code: LunoraErrorCode, message?: string, data?: unknown);
333
338
  }
334
339
  /**
335
340
  * Minimal structural writer the facade binds over. Declared with **method**
@@ -1271,6 +1276,19 @@ interface TableBuilder<Shape extends Record<string, Validator> = Record<string,
1271
1276
  softDelete: (options?: {
1272
1277
  field?: string;
1273
1278
  }) => TableBuilder<Shape>;
1279
+ /**
1280
+ * Materialize this table from an external Postgres/MySQL behind Cloudflare
1281
+ * Hyperdrive (plan 077). A system-driven poll loop reads the tenant slice
1282
+ * (`query`, with params bound from `tenantBy`) and lands it in the DO's SQLite,
1283
+ * after which `defineShape` carries it to clients unchanged. Implies
1284
+ * `.externallyManaged()` (rows come from the ingest loop, not user mutations).
1285
+ *
1286
+ * Orthogonal to `.shardBy()` — combine them for per-tenant DOs. **Under
1287
+ * `.shardBy()` `tenantBy` is mandatory** (the tenant-isolation boundary); the
1288
+ * `external_source_unscoped` advisor lint fails the build when it is absent, and
1289
+ * `external_source_on_global` rejects combining `.source()` with `.global()`.
1290
+ */
1291
+ source: (definition: ExternalSourceDefinition) => TableBuilder<Shape>;
1274
1292
  /** Declare named lifecycle triggers fired inline within the write path. */
1275
1293
  triggers: (build: (t: TriggerBuilder<Shape>) => Record<string, TriggerDefinition>) => TableBuilder<Shape>;
1276
1294
  /** Declare a vector index over a single text field on this table. */
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { Validator, Infer, ValidatorMap, InferValidatorMap, v } from '@lunora/values';
2
2
  export { type ColumnValidator, type Id, type Infer, ValidationError, type Validator, type ValidatorKind, v } from '@lunora/values';
3
- import { ArgsValidator, InferArgs, RegisteredAction, ActionCtx, MutationCtx, RegisteredMutation, QueryCtx, RegisteredQuery, RegisteredStream, FunctionKind, Secrets, LifecycleEvent, RegisteredLifecycleHook, TableDefinition, RegisteredFunction, VectorIndexDefinition, Schema, AggregateOp, DurableObjectJurisdiction, RelationDefinition, GlobalBackend, OnDeleteAction, TriggerBuilder, TriggerDefinition, VectorEmbedder, VectorMetric, AggregateIndexDefinition, RankIndexDefinition } from "./types.js";
3
+ import { ArgsValidator, InferArgs, RegisteredAction, ActionCtx, MutationCtx, RegisteredMutation, QueryCtx, RegisteredQuery, RegisteredStream, FunctionKind, Secrets, LifecycleEvent, RegisteredLifecycleHook, TableDefinition, RegisteredFunction, VectorIndexDefinition, Schema, AggregateOp, DurableObjectJurisdiction, RelationDefinition, GlobalBackend, OnDeleteAction, ExternalSourceDefinition, TriggerBuilder, TriggerDefinition, VectorEmbedder, VectorMetric, AggregateIndexDefinition, RankIndexDefinition } from "./types.js";
4
4
  export { type AnyApi, type AuthState, type DatabaseReader, type DatabaseWriter, type FunctionVisibility, type IndexDefinition, type IndexRangeBuilder, type LifecycleEventKind, type LunoraLogger, type PaginationOptions, type PaginationResult, type RankSortKey, type ReadOnlyStorage, type ScheduledFunctionDoc, type ScheduledJob, type Scheduler, type SearchFilterBuilder, type SearchIndexDefinition, type ShardMode, type Storage, type StorageMetadata, type SystemDatabaseReader, type SystemDoc, type SystemQuery, type SystemTableName, type TableReader, type TableVectorIndex, type TriggerAggregateOptions, type TriggerCtx, type TriggerDatabase, type TriggerDeleteEvent, type TriggerEvent, type TriggerGroupByEntry, type TriggerGroupByOptions, type TriggerHandler, type TriggerInsertEvent, type TriggerOp, type TriggerQueryArgs, type TriggerQueryPage, type TriggerRankOptions, type TriggerRankPageOptions, type TriggerRankResult, type TriggerRow, type TriggerTiming, type TriggerUpdateEvent, type VectorMatch, type VectorMatches, type VectorQueryInput, type VectorRecord, type VectorSearch, type VectorSearchReader, type VectorUpsertInput, type WorkflowCreateOptions, type WorkflowHandle, type WorkflowInstance, type WorkflowInstanceStatus, type WorkflowStatusResult, type Workflows, anyApi } from "./types.js";
5
5
  import { Context, Hono } from 'hono';
6
6
  import { b as Permission, R as Role, T as TypedDefinePolicyInput, a as Policy, D as DefinePolicyInput, W as WhereInput, c as RlsOptions } from "./packem_shared/types.d-BDY0FYHK.js";
@@ -283,7 +283,10 @@ declare const defineEnv: <S extends EnvShape>(shape: S) => EnvAccessor<S>;
283
283
  * The runtime's structural error mapper keys off `name === "LunoraError"` plus
284
284
  * the numeric `status`, so throwing one of these from a handler or middleware
285
285
  * yields the right RPC/HTTP status without any further wiring. `code` carries
286
- * the machine-readable reason for clients.
286
+ * the machine-readable reason for clients; the optional `data` carries a
287
+ * structured, JSON+wire-encodable payload propagated verbatim to the client
288
+ * (e.g. `{ retryAfterMs }`). Only an explicit `LunoraError`'s `data` crosses the
289
+ * wire — an unhandled throw is still redacted to a generic message server-side.
287
290
  */
288
291
  declare const CODE_STATUS: {
289
292
  readonly BAD_REQUEST: 400;
@@ -329,7 +332,9 @@ declare class LunoraError extends Error {
329
332
  override readonly name = "LunoraError";
330
333
  readonly code: LunoraErrorCode;
331
334
  readonly status: number;
332
- constructor(code: LunoraErrorCode, message?: string);
335
+ /** Structured, JSON+wire-encodable payload surfaced to the client alongside `code`. */
336
+ readonly data?: unknown;
337
+ constructor(code: LunoraErrorCode, message?: string, data?: unknown);
333
338
  }
334
339
  /**
335
340
  * Minimal structural writer the facade binds over. Declared with **method**
@@ -1271,6 +1276,19 @@ interface TableBuilder<Shape extends Record<string, Validator> = Record<string,
1271
1276
  softDelete: (options?: {
1272
1277
  field?: string;
1273
1278
  }) => TableBuilder<Shape>;
1279
+ /**
1280
+ * Materialize this table from an external Postgres/MySQL behind Cloudflare
1281
+ * Hyperdrive (plan 077). A system-driven poll loop reads the tenant slice
1282
+ * (`query`, with params bound from `tenantBy`) and lands it in the DO's SQLite,
1283
+ * after which `defineShape` carries it to clients unchanged. Implies
1284
+ * `.externallyManaged()` (rows come from the ingest loop, not user mutations).
1285
+ *
1286
+ * Orthogonal to `.shardBy()` — combine them for per-tenant DOs. **Under
1287
+ * `.shardBy()` `tenantBy` is mandatory** (the tenant-isolation boundary); the
1288
+ * `external_source_unscoped` advisor lint fails the build when it is absent, and
1289
+ * `external_source_on_global` rejects combining `.source()` with `.global()`.
1290
+ */
1291
+ source: (definition: ExternalSourceDefinition) => TableBuilder<Shape>;
1274
1292
  /** Declare named lifecycle triggers fired inline within the write path. */
1275
1293
  triggers: (build: (t: TriggerBuilder<Shape>) => Record<string, TriggerDefinition>) => TableBuilder<Shape>;
1276
1294
  /** Declare a vector index over a single text field on this table. */
package/dist/index.mjs CHANGED
@@ -2,26 +2,26 @@ export { default as asBucketStorage } from './packem_shared/asBucketStorage-Cnxd
2
2
  export { initLunora } from './packem_shared/initLunora-lxwHTEV3.mjs';
3
3
  export { createSecrets } from './packem_shared/createSecrets-TsIP9lOa.mjs';
4
4
  export { LunoraEnvError, defineEnv, redactSecrets } from './packem_shared/LunoraEnvError-DjFkpkSP.mjs';
5
- export { LunoraError } from './packem_shared/LunoraError-DhggBJZF.mjs';
5
+ export { LunoraError } from './packem_shared/LunoraError-DN7Zhhvu.mjs';
6
6
  export { bindOrm, bindTableFacade } from './packem_shared/bindOrm-Ce57S3N9.mjs';
7
- export { httpAction, httpRoute, httpRouter, serveStorageObject } from './packem_shared/httpAction-B7FYUEgr.mjs';
7
+ export { httpAction, httpRoute, httpRouter, serveStorageObject } from './packem_shared/httpAction-FLwfsePg.mjs';
8
8
  export { onConnect, onDisconnect } from './packem_shared/onConnect-CIPXKPyw.mjs';
9
9
  export { defineMigration } from './packem_shared/defineMigration-CAJLr6fx.mjs';
10
10
  export { defineMutator } from './packem_shared/defineMutator-EIXAWhs9.mjs';
11
11
  export { composePluginMiddleware, defineComponent, definePlugin, defineSchemaExtension, installPlugins, mergeSchemaExtension } from './packem_shared/composePluginMiddleware-Ck5_TUO8.mjs';
12
- export { PRESENCE_DEFAULT_TTL_MS, PRESENCE_TABLE, definePresence, presenceExtension } from './packem_shared/PRESENCE_DEFAULT_TTL_MS-DH9zLyXa.mjs';
12
+ export { PRESENCE_DEFAULT_TTL_MS, PRESENCE_TABLE, definePresence, presenceExtension } from './packem_shared/PRESENCE_DEFAULT_TTL_MS-D8viLY1S.mjs';
13
13
  export { protectPublic } from './packem_shared/protectPublic-BjFkQ_Or.mjs';
14
- export { defineAggregateIndex, defineRankIndex, defineSchema, defineTable, defineVectorIndex } from './packem_shared/defineAggregateIndex-C2gT1GzM.mjs';
14
+ export { defineAggregateIndex, defineRankIndex, defineSchema, defineTable, defineVectorIndex } from './packem_shared/defineAggregateIndex-ZdyU78gh.mjs';
15
15
  export { defineShape } from './packem_shared/defineShape-CJ27Wx7o.mjs';
16
16
  export { anyApi } from './types.mjs';
17
17
  export { cronJobs } from '@lunora/scheduler';
18
18
  export { ValidationError, v } from '@lunora/values';
19
- export { buildRlsReadRegistry, composeShapeReadWhere } from './packem_shared/buildRlsReadRegistry-CkxuS69B.mjs';
19
+ export { buildRlsReadRegistry, composeShapeReadWhere } from './packem_shared/buildRlsReadRegistry-1jexWrb3.mjs';
20
20
  export { createPolicyDsl, definePermission, definePolicies, definePolicy, defineRole } from './packem_shared/createPolicyDsl-De67zPDS.mjs';
21
21
  export { defineStorageRule, defineStorageRules } from './packem_shared/defineStorageRule-qu0mpilX.mjs';
22
- export { mask } from './packem_shared/mask-eCUYOwhd.mjs';
23
- export { rls } from './packem_shared/rls-ClmjkkxZ.mjs';
24
- export { storageRules } from './packem_shared/storageRules-4a30FSpI.mjs';
22
+ export { mask } from './packem_shared/mask-BV_jNzsN.mjs';
23
+ export { rls } from './packem_shared/rls-2Jhd0uev.mjs';
24
+ export { storageRules } from './packem_shared/storageRules-Cje6Woea.mjs';
25
25
 
26
26
  const VERSION = "0.0.0";
27
27
 
@@ -41,10 +41,13 @@ class LunoraError extends Error {
41
41
  name = "LunoraError";
42
42
  code;
43
43
  status;
44
- constructor(code, message) {
44
+ /** Structured, JSON+wire-encodable payload surfaced to the client alongside `code`. */
45
+ data;
46
+ constructor(code, message, data) {
45
47
  super(message ?? code);
46
48
  this.code = code;
47
49
  this.status = CODE_STATUS[code];
50
+ this.data = data;
48
51
  }
49
52
  }
50
53
 
@@ -1,9 +1,9 @@
1
1
  import { v } from '@lunora/values';
2
2
  import { initLunora } from './initLunora-lxwHTEV3.mjs';
3
- import { LunoraError } from './LunoraError-DhggBJZF.mjs';
3
+ import { LunoraError } from './LunoraError-DN7Zhhvu.mjs';
4
4
  import { onDisconnect } from './onConnect-CIPXKPyw.mjs';
5
5
  import { defineSchemaExtension, defineComponent } from './composePluginMiddleware-Ck5_TUO8.mjs';
6
- import { defineTable } from './defineAggregateIndex-C2gT1GzM.mjs';
6
+ import { defineTable } from './defineAggregateIndex-ZdyU78gh.mjs';
7
7
 
8
8
  const DEFAULT_TTL_MS = 3e4;
9
9
  const MAX_DATA_BYTES = 4096;
@@ -1,4 +1,4 @@
1
- import { indexRolePermissions, computeReadBaseWhere, permissionName } from './rls-ClmjkkxZ.mjs';
1
+ import { indexRolePermissions, computeReadBaseWhere, permissionName } from './rls-2Jhd0uev.mjs';
2
2
  import { r as readRlsTag } from './policy-tag-DvpVH2tv.mjs';
3
3
 
4
4
  const FALSE_PREDICATE = { OR: [] };
@@ -41,6 +41,7 @@ const defineTable = (inputShape) => {
41
41
  let isExternallyManaged = false;
42
42
  let isPublic = false;
43
43
  let softDelete;
44
+ let externalSource;
44
45
  const builder = {
45
46
  aggregateIndex(name, options) {
46
47
  const op = options?.op ?? "count";
@@ -63,6 +64,9 @@ const defineTable = (inputShape) => {
63
64
  get aggregateIndexes() {
64
65
  return aggregateIndexes;
65
66
  },
67
+ get externalSource() {
68
+ return externalSource;
69
+ },
66
70
  externallyManaged() {
67
71
  isExternallyManaged = true;
68
72
  return builder;
@@ -137,6 +141,17 @@ const defineTable = (inputShape) => {
137
141
  get softDeleteMode() {
138
142
  return softDelete;
139
143
  },
144
+ source(definition) {
145
+ if (!definition.binding) {
146
+ throw new Error("source: `binding` is required (the wrangler Hyperdrive binding name)");
147
+ }
148
+ if (!definition.query) {
149
+ throw new Error("source: `query` is required (the tenant-membership SQL)");
150
+ }
151
+ externalSource = definition;
152
+ isExternallyManaged = true;
153
+ return builder;
154
+ },
140
155
  softDelete(options) {
141
156
  const field = options?.field ?? "deletedAt";
142
157
  softDelete = { field };
@@ -243,9 +258,33 @@ const attachStandaloneIndexes = (tables, aggregateIndexes, rankIndexes) => {
243
258
  table.rankIndexes.push(index);
244
259
  }
245
260
  };
261
+ const validateExternalSources = (tables) => {
262
+ for (const [name, table] of Object.entries(tables)) {
263
+ const source = table.externalSource;
264
+ if (!source) {
265
+ continue;
266
+ }
267
+ if (table.shardMode.kind === "global") {
268
+ throw new Error(
269
+ `defineSchema: table "${name}" cannot be both .source() and .global() — a sourced table materializes into a shard DO's SQLite, a global table lives in the external tier`
270
+ );
271
+ }
272
+ if (table.shardMode.kind === "shardBy" && !source.tenantBy) {
273
+ throw new Error(
274
+ `defineSchema: sourced + .shardBy() table "${name}" needs a \`tenantBy\` mapper — without it every tenant's DO would run the same unscoped query and replicate the whole multitenant table (a cross-tenant leak). Add \`tenantBy: (shardKey) => [shardKey]\` binding the shard key into the query's parameters.`
275
+ );
276
+ }
277
+ if (source.mode === "incremental") {
278
+ throw new Error(
279
+ `defineSchema: table "${name}" uses \`mode: "incremental"\`, which is not yet implemented — only "full-pull" (the default) is supported. Remove \`mode\` or set it to "full-pull".`
280
+ );
281
+ }
282
+ }
283
+ };
246
284
  const defineSchema = (tables, vectorIndexes = {}, aggregateIndexes = {}, rankIndexes = {}) => {
247
285
  fillIndexTableNames(tables);
248
286
  attachStandaloneIndexes(tables, aggregateIndexes, rankIndexes);
287
+ validateExternalSources(tables);
249
288
  return withExtend({ tables, vectorIndexes });
250
289
  };
251
290
 
@@ -1,6 +1,6 @@
1
1
  import { parseValidatorMap, ValidationError } from '@lunora/values';
2
2
  import { Hono } from 'hono';
3
- import { LunoraError } from './LunoraError-DhggBJZF.mjs';
3
+ import { LunoraError } from './LunoraError-DN7Zhhvu.mjs';
4
4
 
5
5
  const httpAction = (handler) => async (c) => handler(c.get("lunora"), c.req.raw);
6
6
  const httpRouter = () => {
@@ -1,4 +1,4 @@
1
- import { LunoraError } from './LunoraError-DhggBJZF.mjs';
1
+ import { LunoraError } from './LunoraError-DN7Zhhvu.mjs';
2
2
  import { bindTableFacade, bindOrm } from './bindOrm-Ce57S3N9.mjs';
3
3
 
4
4
  const permissionName = (permission) => typeof permission === "string" ? permission : permission.name;
@@ -1,4 +1,4 @@
1
- import { LunoraError } from './LunoraError-DhggBJZF.mjs';
1
+ import { LunoraError } from './LunoraError-DN7Zhhvu.mjs';
2
2
  import { bindTableFacade, bindOrm } from './bindOrm-Ce57S3N9.mjs';
3
3
  import { t as tagRlsMiddleware } from './policy-tag-DvpVH2tv.mjs';
4
4
 
@@ -1,4 +1,4 @@
1
- import { LunoraError } from './LunoraError-DhggBJZF.mjs';
1
+ import { LunoraError } from './LunoraError-DN7Zhhvu.mjs';
2
2
 
3
3
  const permissionName = (permission) => typeof permission === "string" ? permission : permission.name;
4
4
  const indexRolePermissions = (roles = []) => {
@@ -1,4 +1,4 @@
1
- import { indexRolePermissions, computeReadBaseWhere, matchesWhere, evaluateWrite, permissionName } from '../packem_shared/rls-ClmjkkxZ.mjs';
1
+ import { indexRolePermissions, computeReadBaseWhere, matchesWhere, evaluateWrite, permissionName } from '../packem_shared/rls-2Jhd0uev.mjs';
2
2
 
3
3
  const expectPolicy = (policies, options = {}) => {
4
4
  const rolePermissions = indexRolePermissions(options.roles);
package/dist/types.d.mts CHANGED
@@ -23,6 +23,51 @@ type ShardMode = {
23
23
  } | {
24
24
  kind: "root";
25
25
  };
26
+ /** Poll cadence for a sourced table — `"manual"` (pull only on an explicit trigger) or a fixed interval. */
27
+ type ExternalSourceRefresh = "manual" | {
28
+ everyMs: number;
29
+ };
30
+ /**
31
+ * Delete-detection mode for external-source ingest. `"full-pull"` (default) reads
32
+ * the whole tenant membership each tick and diffs it, so it observes upstream
33
+ * deletes; `"incremental"` pulls only changed rows (cheap) and is blind to deletes
34
+ * unless paired with a soft-delete column or a `reconcileEveryMs` full-pull sweep.
35
+ */
36
+ type ExternalSourceMode = "full-pull" | "incremental";
37
+ /**
38
+ * Config for `.source(...)` (plan 077): declares a table as **materialized from an
39
+ * external Postgres/MySQL behind Cloudflare Hyperdrive**, not written by user
40
+ * mutations. A system-driven poll loop reads the tenant slice and lands it in the
41
+ * DO's SQLite (via the validated CDC writer), after which `defineShape` carries it
42
+ * to clients unchanged. Orthogonal to `shardMode` — a sourced table almost always
43
+ * also `.shardBy()`s, in which case `tenantBy` is the mandatory tenant-isolation
44
+ * boundary (enforced by the `external_source_unscoped` advisor lint).
45
+ */
46
+ interface ExternalSourceDefinition {
47
+ /** The wrangler Hyperdrive binding name the poll loop reads from. */
48
+ binding: string;
49
+ /** Project the materialized rows to these columns (passed to the membership diff). Omit ⇒ the full mapped document. */
50
+ columns?: ReadonlyArray<string>;
51
+ /** Column whose value becomes the Lunora `_id`. Defaults to `"id"`. */
52
+ idColumn?: string;
53
+ /** Transform an external row into the stored document body. Omit ⇒ every selected column except `idColumn` is copied. */
54
+ map?: (row: Record<string, unknown>) => Record<string, unknown>;
55
+ /** Delete-detection mode. Defaults to `"full-pull"`. */
56
+ mode?: ExternalSourceMode;
57
+ /** The full tenant-membership query, with driver-native placeholders (`$1` / `?`). `tenantBy` binds its params. */
58
+ query: string;
59
+ /** `"incremental"` only: run a full-pull reconcile this often to garbage-collect tombstones the incremental path can't see. */
60
+ reconcileEveryMs?: number;
61
+ /** Poll cadence, or `"manual"`. Omit ⇒ the runtime's size-scaled default. */
62
+ refresh?: ExternalSourceRefresh;
63
+ /**
64
+ * **Mandatory under `.shardBy()`**: map this DO's shard key → the query's bound
65
+ * params, so a tenant DO can only ever pull its own rows. An unscoped sourced +
66
+ * sharded table replicates the whole multitenant table into every shard — the
67
+ * `external_source_unscoped` advisor lint fails the build when this is absent.
68
+ */
69
+ tenantBy?: (shardKey: string) => ReadonlyArray<unknown>;
70
+ }
26
71
  interface IndexDefinition {
27
72
  fields: ReadonlyArray<string>;
28
73
  name: string;
@@ -138,6 +183,14 @@ interface TableDefinition<Shape extends Record<string, Validator> = Record<strin
138
183
  * without scanning the underlying table.
139
184
  */
140
185
  aggregateIndexes: ReadonlyArray<AggregateIndexDefinition>;
186
+ /**
187
+ * Set by `.source(...)` (named `externalSource`, not `source`, so the data
188
+ * field doesn't collide with the fluent `.source()` builder method — same
189
+ * convention as `shardBy()`/`shardMode`). When present, the table is
190
+ * materialized from an external Hyperdrive-backed database by a system poll
191
+ * loop rather than user mutations. Implies `isExternallyManaged`.
192
+ */
193
+ externalSource?: ExternalSourceDefinition;
141
194
  indexes: ReadonlyArray<IndexDefinition>;
142
195
  /**
143
196
  * `true` when `.externallyManaged()` was called — the table's rows are
@@ -1101,4 +1154,4 @@ interface ActionCtx {
1101
1154
  */
1102
1155
  type AnyApi = Record<string, Record<string, RegisteredFunction<ArgsValidator, unknown, FunctionKind>>>;
1103
1156
  declare const anyApi: AnyApi;
1104
- export { type ActionCtx, type AggregateIndexDefinition, type AggregateOp, type AnyApi, type ArgsValidator, type AuthState, type DatabaseReader, type DatabaseWriter, type DurableObjectJurisdiction, type FunctionKind, type FunctionVisibility, type GlobalBackend, type IndexDefinition, type IndexRangeBuilder, type InferArgs, type LifecycleEvent, type LifecycleEventKind, type LunoraLogger, type MutationCtx, type OnDeleteAction, type PaginationOptions, type PaginationResult, type QueryCtx, type RankIndexDefinition, type RankSortKey, type ReadOnlyStorage, type RegisteredAction, type RegisteredFunction, type RegisteredLifecycleHook, type RegisteredMutation, type RegisteredQuery, type RegisteredStream, type RelationDefinition, type ScheduledFunctionDoc, type ScheduledJob, type Scheduler, type Schema, type SearchFilterBuilder, type SearchIndexDefinition, type Secrets, type SecretsStoreSecretLike, type ShardMode, type Storage, type StorageMetadata, type SystemDatabaseReader, type SystemDoc, type SystemQuery, type SystemTableName, type TableDefinition, type TableReader, type TableVectorIndex, type TriggerAggregateOptions, type TriggerBuilder, type TriggerCtx, type TriggerDatabase, type TriggerDefinition, type TriggerDeleteEvent, type TriggerEvent, type TriggerGroupByEntry, type TriggerGroupByOptions, type TriggerHandler, type TriggerInsertEvent, type TriggerOp, type TriggerQueryArgs, type TriggerQueryPage, type TriggerRankOptions, type TriggerRankPageOptions, type TriggerRankResult, type TriggerRow, type TriggerTiming, type TriggerUpdateEvent, type VectorEmbedder, type VectorIndexDefinition, type VectorMatch, type VectorMatches, type VectorMetric, type VectorQueryInput, type VectorRecord, type VectorSearch, type VectorSearchReader, type VectorUpsertInput, type WorkflowCreateOptions, type WorkflowHandle, type WorkflowInstance, type WorkflowInstanceStatus, type WorkflowStatusResult, type Workflows, anyApi };
1157
+ export { type ActionCtx, type AggregateIndexDefinition, type AggregateOp, type AnyApi, type ArgsValidator, type AuthState, type DatabaseReader, type DatabaseWriter, type DurableObjectJurisdiction, type ExternalSourceDefinition, type ExternalSourceMode, type ExternalSourceRefresh, type FunctionKind, type FunctionVisibility, type GlobalBackend, type IndexDefinition, type IndexRangeBuilder, type InferArgs, type LifecycleEvent, type LifecycleEventKind, type LunoraLogger, type MutationCtx, type OnDeleteAction, type PaginationOptions, type PaginationResult, type QueryCtx, type RankIndexDefinition, type RankSortKey, type ReadOnlyStorage, type RegisteredAction, type RegisteredFunction, type RegisteredLifecycleHook, type RegisteredMutation, type RegisteredQuery, type RegisteredStream, type RelationDefinition, type ScheduledFunctionDoc, type ScheduledJob, type Scheduler, type Schema, type SearchFilterBuilder, type SearchIndexDefinition, type Secrets, type SecretsStoreSecretLike, type ShardMode, type Storage, type StorageMetadata, type SystemDatabaseReader, type SystemDoc, type SystemQuery, type SystemTableName, type TableDefinition, type TableReader, type TableVectorIndex, type TriggerAggregateOptions, type TriggerBuilder, type TriggerCtx, type TriggerDatabase, type TriggerDefinition, type TriggerDeleteEvent, type TriggerEvent, type TriggerGroupByEntry, type TriggerGroupByOptions, type TriggerHandler, type TriggerInsertEvent, type TriggerOp, type TriggerQueryArgs, type TriggerQueryPage, type TriggerRankOptions, type TriggerRankPageOptions, type TriggerRankResult, type TriggerRow, type TriggerTiming, type TriggerUpdateEvent, type VectorEmbedder, type VectorIndexDefinition, type VectorMatch, type VectorMatches, type VectorMetric, type VectorQueryInput, type VectorRecord, type VectorSearch, type VectorSearchReader, type VectorUpsertInput, type WorkflowCreateOptions, type WorkflowHandle, type WorkflowInstance, type WorkflowInstanceStatus, type WorkflowStatusResult, type Workflows, anyApi };
package/dist/types.d.ts CHANGED
@@ -23,6 +23,51 @@ type ShardMode = {
23
23
  } | {
24
24
  kind: "root";
25
25
  };
26
+ /** Poll cadence for a sourced table — `"manual"` (pull only on an explicit trigger) or a fixed interval. */
27
+ type ExternalSourceRefresh = "manual" | {
28
+ everyMs: number;
29
+ };
30
+ /**
31
+ * Delete-detection mode for external-source ingest. `"full-pull"` (default) reads
32
+ * the whole tenant membership each tick and diffs it, so it observes upstream
33
+ * deletes; `"incremental"` pulls only changed rows (cheap) and is blind to deletes
34
+ * unless paired with a soft-delete column or a `reconcileEveryMs` full-pull sweep.
35
+ */
36
+ type ExternalSourceMode = "full-pull" | "incremental";
37
+ /**
38
+ * Config for `.source(...)` (plan 077): declares a table as **materialized from an
39
+ * external Postgres/MySQL behind Cloudflare Hyperdrive**, not written by user
40
+ * mutations. A system-driven poll loop reads the tenant slice and lands it in the
41
+ * DO's SQLite (via the validated CDC writer), after which `defineShape` carries it
42
+ * to clients unchanged. Orthogonal to `shardMode` — a sourced table almost always
43
+ * also `.shardBy()`s, in which case `tenantBy` is the mandatory tenant-isolation
44
+ * boundary (enforced by the `external_source_unscoped` advisor lint).
45
+ */
46
+ interface ExternalSourceDefinition {
47
+ /** The wrangler Hyperdrive binding name the poll loop reads from. */
48
+ binding: string;
49
+ /** Project the materialized rows to these columns (passed to the membership diff). Omit ⇒ the full mapped document. */
50
+ columns?: ReadonlyArray<string>;
51
+ /** Column whose value becomes the Lunora `_id`. Defaults to `"id"`. */
52
+ idColumn?: string;
53
+ /** Transform an external row into the stored document body. Omit ⇒ every selected column except `idColumn` is copied. */
54
+ map?: (row: Record<string, unknown>) => Record<string, unknown>;
55
+ /** Delete-detection mode. Defaults to `"full-pull"`. */
56
+ mode?: ExternalSourceMode;
57
+ /** The full tenant-membership query, with driver-native placeholders (`$1` / `?`). `tenantBy` binds its params. */
58
+ query: string;
59
+ /** `"incremental"` only: run a full-pull reconcile this often to garbage-collect tombstones the incremental path can't see. */
60
+ reconcileEveryMs?: number;
61
+ /** Poll cadence, or `"manual"`. Omit ⇒ the runtime's size-scaled default. */
62
+ refresh?: ExternalSourceRefresh;
63
+ /**
64
+ * **Mandatory under `.shardBy()`**: map this DO's shard key → the query's bound
65
+ * params, so a tenant DO can only ever pull its own rows. An unscoped sourced +
66
+ * sharded table replicates the whole multitenant table into every shard — the
67
+ * `external_source_unscoped` advisor lint fails the build when this is absent.
68
+ */
69
+ tenantBy?: (shardKey: string) => ReadonlyArray<unknown>;
70
+ }
26
71
  interface IndexDefinition {
27
72
  fields: ReadonlyArray<string>;
28
73
  name: string;
@@ -138,6 +183,14 @@ interface TableDefinition<Shape extends Record<string, Validator> = Record<strin
138
183
  * without scanning the underlying table.
139
184
  */
140
185
  aggregateIndexes: ReadonlyArray<AggregateIndexDefinition>;
186
+ /**
187
+ * Set by `.source(...)` (named `externalSource`, not `source`, so the data
188
+ * field doesn't collide with the fluent `.source()` builder method — same
189
+ * convention as `shardBy()`/`shardMode`). When present, the table is
190
+ * materialized from an external Hyperdrive-backed database by a system poll
191
+ * loop rather than user mutations. Implies `isExternallyManaged`.
192
+ */
193
+ externalSource?: ExternalSourceDefinition;
141
194
  indexes: ReadonlyArray<IndexDefinition>;
142
195
  /**
143
196
  * `true` when `.externallyManaged()` was called — the table's rows are
@@ -1101,4 +1154,4 @@ interface ActionCtx {
1101
1154
  */
1102
1155
  type AnyApi = Record<string, Record<string, RegisteredFunction<ArgsValidator, unknown, FunctionKind>>>;
1103
1156
  declare const anyApi: AnyApi;
1104
- export { type ActionCtx, type AggregateIndexDefinition, type AggregateOp, type AnyApi, type ArgsValidator, type AuthState, type DatabaseReader, type DatabaseWriter, type DurableObjectJurisdiction, type FunctionKind, type FunctionVisibility, type GlobalBackend, type IndexDefinition, type IndexRangeBuilder, type InferArgs, type LifecycleEvent, type LifecycleEventKind, type LunoraLogger, type MutationCtx, type OnDeleteAction, type PaginationOptions, type PaginationResult, type QueryCtx, type RankIndexDefinition, type RankSortKey, type ReadOnlyStorage, type RegisteredAction, type RegisteredFunction, type RegisteredLifecycleHook, type RegisteredMutation, type RegisteredQuery, type RegisteredStream, type RelationDefinition, type ScheduledFunctionDoc, type ScheduledJob, type Scheduler, type Schema, type SearchFilterBuilder, type SearchIndexDefinition, type Secrets, type SecretsStoreSecretLike, type ShardMode, type Storage, type StorageMetadata, type SystemDatabaseReader, type SystemDoc, type SystemQuery, type SystemTableName, type TableDefinition, type TableReader, type TableVectorIndex, type TriggerAggregateOptions, type TriggerBuilder, type TriggerCtx, type TriggerDatabase, type TriggerDefinition, type TriggerDeleteEvent, type TriggerEvent, type TriggerGroupByEntry, type TriggerGroupByOptions, type TriggerHandler, type TriggerInsertEvent, type TriggerOp, type TriggerQueryArgs, type TriggerQueryPage, type TriggerRankOptions, type TriggerRankPageOptions, type TriggerRankResult, type TriggerRow, type TriggerTiming, type TriggerUpdateEvent, type VectorEmbedder, type VectorIndexDefinition, type VectorMatch, type VectorMatches, type VectorMetric, type VectorQueryInput, type VectorRecord, type VectorSearch, type VectorSearchReader, type VectorUpsertInput, type WorkflowCreateOptions, type WorkflowHandle, type WorkflowInstance, type WorkflowInstanceStatus, type WorkflowStatusResult, type Workflows, anyApi };
1157
+ export { type ActionCtx, type AggregateIndexDefinition, type AggregateOp, type AnyApi, type ArgsValidator, type AuthState, type DatabaseReader, type DatabaseWriter, type DurableObjectJurisdiction, type ExternalSourceDefinition, type ExternalSourceMode, type ExternalSourceRefresh, type FunctionKind, type FunctionVisibility, type GlobalBackend, type IndexDefinition, type IndexRangeBuilder, type InferArgs, type LifecycleEvent, type LifecycleEventKind, type LunoraLogger, type MutationCtx, type OnDeleteAction, type PaginationOptions, type PaginationResult, type QueryCtx, type RankIndexDefinition, type RankSortKey, type ReadOnlyStorage, type RegisteredAction, type RegisteredFunction, type RegisteredLifecycleHook, type RegisteredMutation, type RegisteredQuery, type RegisteredStream, type RelationDefinition, type ScheduledFunctionDoc, type ScheduledJob, type Scheduler, type Schema, type SearchFilterBuilder, type SearchIndexDefinition, type Secrets, type SecretsStoreSecretLike, type ShardMode, type Storage, type StorageMetadata, type SystemDatabaseReader, type SystemDoc, type SystemQuery, type SystemTableName, type TableDefinition, type TableReader, type TableVectorIndex, type TriggerAggregateOptions, type TriggerBuilder, type TriggerCtx, type TriggerDatabase, type TriggerDefinition, type TriggerDeleteEvent, type TriggerEvent, type TriggerGroupByEntry, type TriggerGroupByOptions, type TriggerHandler, type TriggerInsertEvent, type TriggerOp, type TriggerQueryArgs, type TriggerQueryPage, type TriggerRankOptions, type TriggerRankPageOptions, type TriggerRankResult, type TriggerRow, type TriggerTiming, type TriggerUpdateEvent, type VectorEmbedder, type VectorIndexDefinition, type VectorMatch, type VectorMatches, type VectorMetric, type VectorQueryInput, type VectorRecord, type VectorSearch, type VectorSearchReader, type VectorUpsertInput, type WorkflowCreateOptions, type WorkflowHandle, type WorkflowInstance, type WorkflowInstanceStatus, type WorkflowStatusResult, type Workflows, anyApi };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lunora/server",
3
- "version": "1.0.0-alpha.7",
3
+ "version": "1.0.0-alpha.9",
4
4
  "description": "Server primitives for Lunora: defineSchema, defineTable, query, mutation, and action",
5
5
  "keywords": [
6
6
  "backend",