@lunora/server 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.d.mts +19 -1
- package/dist/index.d.ts +19 -1
- package/dist/index.mjs +3 -3
- package/dist/packem_shared/{PRESENCE_DEFAULT_TTL_MS-BAD0QPAg.mjs → PRESENCE_DEFAULT_TTL_MS-Cv1UPaJj.mjs} +2 -2
- package/dist/packem_shared/{composePluginMiddleware-CcTj1_v2.mjs → composePluginMiddleware-z62dttBo.mjs} +2 -2
- package/dist/packem_shared/{defineAggregateIndex-znq4ygKQ.mjs → defineAggregateIndex-DTULzh09.mjs} +45 -7
- package/dist/types.d.mts +46 -7
- package/dist/types.d.ts +46 -7
- package/package.json +4 -4
package/dist/index.d.mts
CHANGED
|
@@ -558,6 +558,7 @@ interface HttpRouteHandlerOptions<SearchParams extends ArgsValidator, Body exten
|
|
|
558
558
|
* handler receives. There is no parsed `body` — streams are typically GET, and
|
|
559
559
|
* the raw `request` is exposed if a handler needs to read the body itself.
|
|
560
560
|
* `signal` is tripped when the client disconnects.
|
|
561
|
+
* @experimental Part of the HTTP-SSE stream surface; reconnect/POST-body design questions are still open.
|
|
561
562
|
*/
|
|
562
563
|
interface HttpStreamHandlerOptions<SearchParams extends ArgsValidator, Params extends ArgsValidator> {
|
|
563
564
|
ctx: HttpActionCtx;
|
|
@@ -604,6 +605,7 @@ interface HttpRouteBuilder<SearchParams extends ArgsValidator, Body extends Args
|
|
|
604
605
|
* throw, an `event: error` frame is written with `{code, message}` before
|
|
605
606
|
* the stream closes. The chunks are JSON-encoded; `R` is inferred from the
|
|
606
607
|
* handler's yielded type.
|
|
608
|
+
* @experimental Reconnect/POST-body/wire-fidelity design questions are still open, so the shape may change.
|
|
607
609
|
*/
|
|
608
610
|
stream: <R>(handler: (options: HttpStreamHandlerOptions<SearchParams, Params>) => AsyncGenerator<R, void, void> | AsyncIterable<R>) => LunoraRouteHandler;
|
|
609
611
|
/**
|
|
@@ -1141,10 +1143,26 @@ interface DefinePluginOptions<TExtension extends Record<string, TableDefinition>
|
|
|
1141
1143
|
middleware?: Middleware<TContextIn, TContextOut>;
|
|
1142
1144
|
}
|
|
1143
1145
|
/**
|
|
1146
|
+
* Call signatures for {@link definePlugin}. When `extension` is supplied the
|
|
1147
|
+
* returned plugin's `extension` is typed as PRESENT (not `?`), so the
|
|
1148
|
+
* canonical install pattern `defineSchema(...).extend(plugin.extension)`
|
|
1149
|
+
* typechecks without a non-null assertion — the shape every scaffold template
|
|
1150
|
+
* ships. The bare-options signature keeps `extension` optional for plugins
|
|
1151
|
+
* that carry only middleware.
|
|
1152
|
+
*/
|
|
1153
|
+
interface DefinePluginFunction {
|
|
1154
|
+
<TExtension extends Record<string, TableDefinition>, TContextIn = unknown, TContextOut = TContextIn>(key: string, options: DefinePluginOptions<TExtension, TContextIn, TContextOut> & {
|
|
1155
|
+
extension: SchemaExtension<TExtension>;
|
|
1156
|
+
}): Plugin<TExtension, TContextIn, TContextOut> & {
|
|
1157
|
+
readonly extension: SchemaExtension<TExtension>;
|
|
1158
|
+
};
|
|
1159
|
+
<TExtension extends Record<string, TableDefinition>, TContextIn = unknown, TContextOut = TContextIn>(key: string, options: DefinePluginOptions<TExtension, TContextIn, TContextOut>): Plugin<TExtension, TContextIn, TContextOut>;
|
|
1160
|
+
}
|
|
1161
|
+
/**
|
|
1144
1162
|
* Package a schema extension + middleware as a reusable plugin. Either
|
|
1145
1163
|
* field is optional — `definePlugin("foo", {})` is valid but degenerate.
|
|
1146
1164
|
*/
|
|
1147
|
-
declare const definePlugin:
|
|
1165
|
+
declare const definePlugin: DefinePluginFunction;
|
|
1148
1166
|
/**
|
|
1149
1167
|
* Bundle of registered functions a {@link Component} ships. Keys are the
|
|
1150
1168
|
* function's local name (e.g. `check`, `reset`); the registered function
|
package/dist/index.d.ts
CHANGED
|
@@ -558,6 +558,7 @@ interface HttpRouteHandlerOptions<SearchParams extends ArgsValidator, Body exten
|
|
|
558
558
|
* handler receives. There is no parsed `body` — streams are typically GET, and
|
|
559
559
|
* the raw `request` is exposed if a handler needs to read the body itself.
|
|
560
560
|
* `signal` is tripped when the client disconnects.
|
|
561
|
+
* @experimental Part of the HTTP-SSE stream surface; reconnect/POST-body design questions are still open.
|
|
561
562
|
*/
|
|
562
563
|
interface HttpStreamHandlerOptions<SearchParams extends ArgsValidator, Params extends ArgsValidator> {
|
|
563
564
|
ctx: HttpActionCtx;
|
|
@@ -604,6 +605,7 @@ interface HttpRouteBuilder<SearchParams extends ArgsValidator, Body extends Args
|
|
|
604
605
|
* throw, an `event: error` frame is written with `{code, message}` before
|
|
605
606
|
* the stream closes. The chunks are JSON-encoded; `R` is inferred from the
|
|
606
607
|
* handler's yielded type.
|
|
608
|
+
* @experimental Reconnect/POST-body/wire-fidelity design questions are still open, so the shape may change.
|
|
607
609
|
*/
|
|
608
610
|
stream: <R>(handler: (options: HttpStreamHandlerOptions<SearchParams, Params>) => AsyncGenerator<R, void, void> | AsyncIterable<R>) => LunoraRouteHandler;
|
|
609
611
|
/**
|
|
@@ -1141,10 +1143,26 @@ interface DefinePluginOptions<TExtension extends Record<string, TableDefinition>
|
|
|
1141
1143
|
middleware?: Middleware<TContextIn, TContextOut>;
|
|
1142
1144
|
}
|
|
1143
1145
|
/**
|
|
1146
|
+
* Call signatures for {@link definePlugin}. When `extension` is supplied the
|
|
1147
|
+
* returned plugin's `extension` is typed as PRESENT (not `?`), so the
|
|
1148
|
+
* canonical install pattern `defineSchema(...).extend(plugin.extension)`
|
|
1149
|
+
* typechecks without a non-null assertion — the shape every scaffold template
|
|
1150
|
+
* ships. The bare-options signature keeps `extension` optional for plugins
|
|
1151
|
+
* that carry only middleware.
|
|
1152
|
+
*/
|
|
1153
|
+
interface DefinePluginFunction {
|
|
1154
|
+
<TExtension extends Record<string, TableDefinition>, TContextIn = unknown, TContextOut = TContextIn>(key: string, options: DefinePluginOptions<TExtension, TContextIn, TContextOut> & {
|
|
1155
|
+
extension: SchemaExtension<TExtension>;
|
|
1156
|
+
}): Plugin<TExtension, TContextIn, TContextOut> & {
|
|
1157
|
+
readonly extension: SchemaExtension<TExtension>;
|
|
1158
|
+
};
|
|
1159
|
+
<TExtension extends Record<string, TableDefinition>, TContextIn = unknown, TContextOut = TContextIn>(key: string, options: DefinePluginOptions<TExtension, TContextIn, TContextOut>): Plugin<TExtension, TContextIn, TContextOut>;
|
|
1160
|
+
}
|
|
1161
|
+
/**
|
|
1144
1162
|
* Package a schema extension + middleware as a reusable plugin. Either
|
|
1145
1163
|
* field is optional — `definePlugin("foo", {})` is valid but degenerate.
|
|
1146
1164
|
*/
|
|
1147
|
-
declare const definePlugin:
|
|
1165
|
+
declare const definePlugin: DefinePluginFunction;
|
|
1148
1166
|
/**
|
|
1149
1167
|
* Bundle of registered functions a {@link Component} ships. Keys are the
|
|
1150
1168
|
* function's local name (e.g. `check`, `reset`); the registered function
|
package/dist/index.mjs
CHANGED
|
@@ -9,10 +9,10 @@ export { defineIdentity } from './packem_shared/defineIdentity-DiX4zM9x.mjs';
|
|
|
9
9
|
export { onConnect, onDisconnect } from './packem_shared/onConnect-CIPXKPyw.mjs';
|
|
10
10
|
export { defineMigration } from './packem_shared/defineMigration-Hx01yIht.mjs';
|
|
11
11
|
export { defineMutator } from './packem_shared/defineMutator-EIXAWhs9.mjs';
|
|
12
|
-
export { composePluginMiddleware, defineComponent, definePlugin, defineSchemaExtension, installPlugins, mergeSchemaExtension } from './packem_shared/composePluginMiddleware-
|
|
13
|
-
export { PRESENCE_DEFAULT_TTL_MS, PRESENCE_TABLE, definePresence, presenceExtension } from './packem_shared/PRESENCE_DEFAULT_TTL_MS-
|
|
12
|
+
export { composePluginMiddleware, defineComponent, definePlugin, defineSchemaExtension, installPlugins, mergeSchemaExtension } from './packem_shared/composePluginMiddleware-z62dttBo.mjs';
|
|
13
|
+
export { PRESENCE_DEFAULT_TTL_MS, PRESENCE_TABLE, definePresence, presenceExtension } from './packem_shared/PRESENCE_DEFAULT_TTL_MS-Cv1UPaJj.mjs';
|
|
14
14
|
export { protectPublic } from './packem_shared/protectPublic-BlcGpiRc.mjs';
|
|
15
|
-
export { defineAggregateIndex, defineRankIndex, defineSchema, defineTable, defineVectorIndex } from './packem_shared/defineAggregateIndex-
|
|
15
|
+
export { defineAggregateIndex, defineRankIndex, defineSchema, defineTable, defineVectorIndex } from './packem_shared/defineAggregateIndex-DTULzh09.mjs';
|
|
16
16
|
export { defineShape } from './packem_shared/defineShape-C5scNOrf.mjs';
|
|
17
17
|
export { anyApi } from './types.mjs';
|
|
18
18
|
export { cronJobs } from '@lunora/scheduler';
|
|
@@ -2,8 +2,8 @@ import { v } from '@lunora/values';
|
|
|
2
2
|
import { initLunora } from './initLunora-CmMVk4i7.mjs';
|
|
3
3
|
import { LunoraError } from './LunoraError-WbxmrpxR.mjs';
|
|
4
4
|
import { onDisconnect } from './onConnect-CIPXKPyw.mjs';
|
|
5
|
-
import { defineSchemaExtension, defineComponent } from './composePluginMiddleware-
|
|
6
|
-
import { defineTable } from './defineAggregateIndex-
|
|
5
|
+
import { defineSchemaExtension, defineComponent } from './composePluginMiddleware-z62dttBo.mjs';
|
|
6
|
+
import { defineTable } from './defineAggregateIndex-DTULzh09.mjs';
|
|
7
7
|
|
|
8
8
|
const DEFAULT_TTL_MS = 3e4;
|
|
9
9
|
const MAX_DATA_BYTES = 4096;
|
|
@@ -28,7 +28,7 @@ const defineSchemaExtension = (key, options) => {
|
|
|
28
28
|
...options.vectorIndexes ? { vectorIndexes: options.vectorIndexes } : {}
|
|
29
29
|
};
|
|
30
30
|
};
|
|
31
|
-
const definePlugin = (key, options) => {
|
|
31
|
+
const definePlugin = ((key, options) => {
|
|
32
32
|
if (!key) {
|
|
33
33
|
throw new LunoraError$1("INTERNAL", "definePlugin: `key` is required and must be a non-empty string");
|
|
34
34
|
}
|
|
@@ -40,7 +40,7 @@ const definePlugin = (key, options) => {
|
|
|
40
40
|
...options.extension ? { extension: options.extension } : {},
|
|
41
41
|
...options.middleware ? { middleware: options.middleware } : {}
|
|
42
42
|
};
|
|
43
|
-
};
|
|
43
|
+
});
|
|
44
44
|
const defineComponent = (key, options) => {
|
|
45
45
|
const plugin = definePlugin(key, {
|
|
46
46
|
...options.extension ? { extension: options.extension } : {},
|
package/dist/packem_shared/{defineAggregateIndex-znq4ygKQ.mjs → defineAggregateIndex-DTULzh09.mjs}
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { LunoraError as LunoraError$1 } from '@lunora/errors';
|
|
2
2
|
import { isOrWrapsFromValidator, v } from '@lunora/values';
|
|
3
|
-
import { mergeSchemaExtension } from './composePluginMiddleware-
|
|
3
|
+
import { mergeSchemaExtension } from './composePluginMiddleware-z62dttBo.mjs';
|
|
4
4
|
|
|
5
5
|
const relationBuilder = {
|
|
6
6
|
many: (table, options) => {
|
|
@@ -259,6 +259,49 @@ const attachStandaloneIndexes = (tables, aggregateIndexes, rankIndexes) => {
|
|
|
259
259
|
table.rankIndexes.push(index);
|
|
260
260
|
}
|
|
261
261
|
};
|
|
262
|
+
const strayIncrementalKnob = (source) => {
|
|
263
|
+
if (source.cursor) {
|
|
264
|
+
return "cursor";
|
|
265
|
+
}
|
|
266
|
+
if (source.reconcileEveryMs !== void 0) {
|
|
267
|
+
return "reconcileEveryMs";
|
|
268
|
+
}
|
|
269
|
+
if (source.softDeleteColumn !== void 0) {
|
|
270
|
+
return "softDeleteColumn";
|
|
271
|
+
}
|
|
272
|
+
return void 0;
|
|
273
|
+
};
|
|
274
|
+
const validateExternalSourceMode = (name, source) => {
|
|
275
|
+
const isIncremental = source.mode === "incremental";
|
|
276
|
+
if (source.mode !== void 0 && source.mode !== "full-pull" && !isIncremental) {
|
|
277
|
+
throw new LunoraError$1(
|
|
278
|
+
"INTERNAL",
|
|
279
|
+
`defineSchema: table "${name}" uses \`mode: ${JSON.stringify(source.mode)}\` — supported modes are "full-pull" (default) and "incremental".`
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
if (!isIncremental) {
|
|
283
|
+
const strayKnob = strayIncrementalKnob(source);
|
|
284
|
+
if (strayKnob) {
|
|
285
|
+
throw new LunoraError$1(
|
|
286
|
+
"INTERNAL",
|
|
287
|
+
`defineSchema: table "${name}" sets \`${strayKnob}\` but is not \`mode: "incremental"\` — that knob only applies to incremental ingest. Set \`mode: "incremental"\` or remove \`${strayKnob}\`.`
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
if (!source.cursor?.column || !source.cursor.query) {
|
|
293
|
+
throw new LunoraError$1(
|
|
294
|
+
"INTERNAL",
|
|
295
|
+
`defineSchema: table "${name}" is \`mode: "incremental"\` but has no \`cursor\` — add \`cursor: { column: "updated_at", query: "… WHERE … > $N" }\` binding the watermark as the trailing parameter.`
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
if (source.reconcileEveryMs === void 0 && source.softDeleteColumn === void 0) {
|
|
299
|
+
throw new LunoraError$1(
|
|
300
|
+
"INTERNAL",
|
|
301
|
+
`defineSchema: table "${name}" is \`mode: "incremental"\` with no delete-visibility path — an incremental pull never sees upstream deletes, so it would accumulate phantom rows. Add \`reconcileEveryMs\` (a periodic full-pull sweep) or \`softDeleteColumn\` (an upstream tombstone column).`
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
};
|
|
262
305
|
const validateExternalSources = (tables) => {
|
|
263
306
|
for (const [name, table] of Object.entries(tables)) {
|
|
264
307
|
const source = table.externalSource;
|
|
@@ -277,12 +320,7 @@ const validateExternalSources = (tables) => {
|
|
|
277
320
|
`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.`
|
|
278
321
|
);
|
|
279
322
|
}
|
|
280
|
-
|
|
281
|
-
throw new LunoraError$1(
|
|
282
|
-
"INTERNAL",
|
|
283
|
-
`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".`
|
|
284
|
-
);
|
|
285
|
-
}
|
|
323
|
+
validateExternalSourceMode(name, source);
|
|
286
324
|
}
|
|
287
325
|
};
|
|
288
326
|
const defineSchema = (tables, vectorIndexes = {}, aggregateIndexes = {}, rankIndexes = {}) => {
|
package/dist/types.d.mts
CHANGED
|
@@ -28,13 +28,38 @@ type ExternalSourceRefresh = "manual" | {
|
|
|
28
28
|
everyMs: number;
|
|
29
29
|
};
|
|
30
30
|
/**
|
|
31
|
-
* Delete-detection mode for external-source ingest
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
31
|
+
* Delete-detection mode for external-source ingest (plan 077 / 136).
|
|
32
|
+
*
|
|
33
|
+
* `"full-pull"` (the default) reads the **whole** tenant membership each tick and
|
|
34
|
+
* diffs it, so it observes upstream deletes for free — but costs a full read per tick
|
|
35
|
+
* (the Phase-0 bench put the ceiling at ~10k rows).
|
|
36
|
+
*
|
|
37
|
+
* `"incremental"` pulls **only rows past a durable watermark** (`cursor`), cheap for
|
|
38
|
+
* large low-churn tables above the full-pull cap. Because an absent row then means
|
|
39
|
+
* "unchanged", not "deleted", incremental requires a delete-visibility path: either a
|
|
40
|
+
* `reconcileEveryMs` periodic full-pull sweep, or a `softDeleteColumn` whose
|
|
41
|
+
* tombstones the pull returns. `defineSchema` throws (and the
|
|
42
|
+
* `external_source_incremental_no_delete_path` advisor lint fails the build) when an
|
|
43
|
+
* incremental source declares neither.
|
|
35
44
|
*/
|
|
36
45
|
type ExternalSourceMode = "full-pull" | "incremental";
|
|
37
46
|
/**
|
|
47
|
+
* Incremental-ingest cursor (plan 136): the monotonic watermark column plus the
|
|
48
|
+
* watermark-parameterized pull query. `column` names the field in the pulled rows
|
|
49
|
+
* whose max becomes the next watermark (e.g. `"updated_at"`). `query` is a second
|
|
50
|
+
* SQL that returns only rows changed since the watermark — the watermark binds as
|
|
51
|
+
* the parameter AFTER `tenantBy`'s params (e.g. Postgres
|
|
52
|
+
* `... WHERE tenant_id = $1 AND updated_at >= $2 ORDER BY updated_at`). Prefer `>=`
|
|
53
|
+
* with the idempotent upsert apply so rows sharing the boundary timestamp are never
|
|
54
|
+
* skipped (re-pulling them is a no-op).
|
|
55
|
+
*/
|
|
56
|
+
interface ExternalSourceCursor {
|
|
57
|
+
/** The monotonic watermark column in the pulled rows; its max advances the stored watermark. */
|
|
58
|
+
column: string;
|
|
59
|
+
/** The incremental pull SQL. `tenantBy`'s params bind first, then the watermark as the trailing param. */
|
|
60
|
+
query: string;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
38
63
|
* Config for `.source(...)` (plan 077): declares a table as **materialized from an
|
|
39
64
|
* external Postgres/MySQL behind Cloudflare Hyperdrive**, not written by user
|
|
40
65
|
* mutations. A system-driven poll loop reads the tenant slice and lands it in the
|
|
@@ -48,19 +73,33 @@ interface ExternalSourceDefinition {
|
|
|
48
73
|
binding: string;
|
|
49
74
|
/** Project the materialized rows to these columns (passed to the membership diff). Omit ⇒ the full mapped document. */
|
|
50
75
|
columns?: ReadonlyArray<string>;
|
|
76
|
+
/** **Required for `mode: "incremental"`**: the watermark column + watermark-parameterized pull query (plan 136). Rejected on a `"full-pull"` source. */
|
|
77
|
+
cursor?: ExternalSourceCursor;
|
|
51
78
|
/** Column whose value becomes the Lunora `_id`. Defaults to `"id"`. */
|
|
52
79
|
idColumn?: string;
|
|
53
80
|
/** Transform an external row into the stored document body. Omit ⇒ every selected column except `idColumn` is copied. */
|
|
54
81
|
map?: (row: Record<string, unknown>) => Record<string, unknown>;
|
|
55
|
-
/** Delete-detection mode.
|
|
82
|
+
/** Delete-detection mode. `"full-pull"` (the default) diffs the whole membership; `"incremental"` pulls past a `cursor` watermark. */
|
|
56
83
|
mode?: ExternalSourceMode;
|
|
57
84
|
/** The full tenant-membership query, with driver-native placeholders (`$1` / `?`). `tenantBy` binds its params. */
|
|
58
85
|
query: string;
|
|
59
|
-
/**
|
|
86
|
+
/**
|
|
87
|
+
* **Incremental delete-visibility (plan 136)**: run a full-pull sweep at most
|
|
88
|
+
* this often (millis) to GC upstream deletes an incremental slice can't see.
|
|
89
|
+
* One of `reconcileEveryMs` / `softDeleteColumn` is required for incremental;
|
|
90
|
+
* rejected on a `"full-pull"` source.
|
|
91
|
+
*/
|
|
60
92
|
reconcileEveryMs?: number;
|
|
61
93
|
/** Poll cadence, or `"manual"`. Omit ⇒ the runtime's size-scaled default. */
|
|
62
94
|
refresh?: ExternalSourceRefresh;
|
|
63
95
|
/**
|
|
96
|
+
* **Incremental delete-visibility (plan 136)**: the upstream soft-delete
|
|
97
|
+
* tombstone column (e.g. `"deleted_at"`). When set, the incremental pull must
|
|
98
|
+
* return tombstoned rows and the ingest turns each into a local delete — an
|
|
99
|
+
* alternative to `reconcileEveryMs`. Rejected on a `"full-pull"` source.
|
|
100
|
+
*/
|
|
101
|
+
softDeleteColumn?: string;
|
|
102
|
+
/**
|
|
64
103
|
* **Mandatory under `.shardBy()`**: map this DO's shard key → the query's bound
|
|
65
104
|
* params, so a tenant DO can only ever pull its own rows. An unscoped sourced +
|
|
66
105
|
* sharded table replicates the whole multitenant table into every shard — the
|
|
@@ -1293,4 +1332,4 @@ interface ActionCtx {
|
|
|
1293
1332
|
*/
|
|
1294
1333
|
type AnyApi = Record<string, Record<string, RegisteredFunction<ArgsValidator, unknown, FunctionKind>>>;
|
|
1295
1334
|
declare const anyApi: AnyApi;
|
|
1296
|
-
export { type ActionCtx, type AggregateIndexDefinition, type AggregateOp, type AnyApi, type ArgsValidator, type AuthState, type CachePurge, 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, type X402ProcedureConfig, anyApi };
|
|
1335
|
+
export { type ActionCtx, type AggregateIndexDefinition, type AggregateOp, type AnyApi, type ArgsValidator, type AuthState, type CachePurge, type DatabaseReader, type DatabaseWriter, type DurableObjectJurisdiction, type ExternalSourceCursor, 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, type X402ProcedureConfig, anyApi };
|
package/dist/types.d.ts
CHANGED
|
@@ -28,13 +28,38 @@ type ExternalSourceRefresh = "manual" | {
|
|
|
28
28
|
everyMs: number;
|
|
29
29
|
};
|
|
30
30
|
/**
|
|
31
|
-
* Delete-detection mode for external-source ingest
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
31
|
+
* Delete-detection mode for external-source ingest (plan 077 / 136).
|
|
32
|
+
*
|
|
33
|
+
* `"full-pull"` (the default) reads the **whole** tenant membership each tick and
|
|
34
|
+
* diffs it, so it observes upstream deletes for free — but costs a full read per tick
|
|
35
|
+
* (the Phase-0 bench put the ceiling at ~10k rows).
|
|
36
|
+
*
|
|
37
|
+
* `"incremental"` pulls **only rows past a durable watermark** (`cursor`), cheap for
|
|
38
|
+
* large low-churn tables above the full-pull cap. Because an absent row then means
|
|
39
|
+
* "unchanged", not "deleted", incremental requires a delete-visibility path: either a
|
|
40
|
+
* `reconcileEveryMs` periodic full-pull sweep, or a `softDeleteColumn` whose
|
|
41
|
+
* tombstones the pull returns. `defineSchema` throws (and the
|
|
42
|
+
* `external_source_incremental_no_delete_path` advisor lint fails the build) when an
|
|
43
|
+
* incremental source declares neither.
|
|
35
44
|
*/
|
|
36
45
|
type ExternalSourceMode = "full-pull" | "incremental";
|
|
37
46
|
/**
|
|
47
|
+
* Incremental-ingest cursor (plan 136): the monotonic watermark column plus the
|
|
48
|
+
* watermark-parameterized pull query. `column` names the field in the pulled rows
|
|
49
|
+
* whose max becomes the next watermark (e.g. `"updated_at"`). `query` is a second
|
|
50
|
+
* SQL that returns only rows changed since the watermark — the watermark binds as
|
|
51
|
+
* the parameter AFTER `tenantBy`'s params (e.g. Postgres
|
|
52
|
+
* `... WHERE tenant_id = $1 AND updated_at >= $2 ORDER BY updated_at`). Prefer `>=`
|
|
53
|
+
* with the idempotent upsert apply so rows sharing the boundary timestamp are never
|
|
54
|
+
* skipped (re-pulling them is a no-op).
|
|
55
|
+
*/
|
|
56
|
+
interface ExternalSourceCursor {
|
|
57
|
+
/** The monotonic watermark column in the pulled rows; its max advances the stored watermark. */
|
|
58
|
+
column: string;
|
|
59
|
+
/** The incremental pull SQL. `tenantBy`'s params bind first, then the watermark as the trailing param. */
|
|
60
|
+
query: string;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
38
63
|
* Config for `.source(...)` (plan 077): declares a table as **materialized from an
|
|
39
64
|
* external Postgres/MySQL behind Cloudflare Hyperdrive**, not written by user
|
|
40
65
|
* mutations. A system-driven poll loop reads the tenant slice and lands it in the
|
|
@@ -48,19 +73,33 @@ interface ExternalSourceDefinition {
|
|
|
48
73
|
binding: string;
|
|
49
74
|
/** Project the materialized rows to these columns (passed to the membership diff). Omit ⇒ the full mapped document. */
|
|
50
75
|
columns?: ReadonlyArray<string>;
|
|
76
|
+
/** **Required for `mode: "incremental"`**: the watermark column + watermark-parameterized pull query (plan 136). Rejected on a `"full-pull"` source. */
|
|
77
|
+
cursor?: ExternalSourceCursor;
|
|
51
78
|
/** Column whose value becomes the Lunora `_id`. Defaults to `"id"`. */
|
|
52
79
|
idColumn?: string;
|
|
53
80
|
/** Transform an external row into the stored document body. Omit ⇒ every selected column except `idColumn` is copied. */
|
|
54
81
|
map?: (row: Record<string, unknown>) => Record<string, unknown>;
|
|
55
|
-
/** Delete-detection mode.
|
|
82
|
+
/** Delete-detection mode. `"full-pull"` (the default) diffs the whole membership; `"incremental"` pulls past a `cursor` watermark. */
|
|
56
83
|
mode?: ExternalSourceMode;
|
|
57
84
|
/** The full tenant-membership query, with driver-native placeholders (`$1` / `?`). `tenantBy` binds its params. */
|
|
58
85
|
query: string;
|
|
59
|
-
/**
|
|
86
|
+
/**
|
|
87
|
+
* **Incremental delete-visibility (plan 136)**: run a full-pull sweep at most
|
|
88
|
+
* this often (millis) to GC upstream deletes an incremental slice can't see.
|
|
89
|
+
* One of `reconcileEveryMs` / `softDeleteColumn` is required for incremental;
|
|
90
|
+
* rejected on a `"full-pull"` source.
|
|
91
|
+
*/
|
|
60
92
|
reconcileEveryMs?: number;
|
|
61
93
|
/** Poll cadence, or `"manual"`. Omit ⇒ the runtime's size-scaled default. */
|
|
62
94
|
refresh?: ExternalSourceRefresh;
|
|
63
95
|
/**
|
|
96
|
+
* **Incremental delete-visibility (plan 136)**: the upstream soft-delete
|
|
97
|
+
* tombstone column (e.g. `"deleted_at"`). When set, the incremental pull must
|
|
98
|
+
* return tombstoned rows and the ingest turns each into a local delete — an
|
|
99
|
+
* alternative to `reconcileEveryMs`. Rejected on a `"full-pull"` source.
|
|
100
|
+
*/
|
|
101
|
+
softDeleteColumn?: string;
|
|
102
|
+
/**
|
|
64
103
|
* **Mandatory under `.shardBy()`**: map this DO's shard key → the query's bound
|
|
65
104
|
* params, so a tenant DO can only ever pull its own rows. An unscoped sourced +
|
|
66
105
|
* sharded table replicates the whole multitenant table into every shard — the
|
|
@@ -1293,4 +1332,4 @@ interface ActionCtx {
|
|
|
1293
1332
|
*/
|
|
1294
1333
|
type AnyApi = Record<string, Record<string, RegisteredFunction<ArgsValidator, unknown, FunctionKind>>>;
|
|
1295
1334
|
declare const anyApi: AnyApi;
|
|
1296
|
-
export { type ActionCtx, type AggregateIndexDefinition, type AggregateOp, type AnyApi, type ArgsValidator, type AuthState, type CachePurge, 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, type X402ProcedureConfig, anyApi };
|
|
1335
|
+
export { type ActionCtx, type AggregateIndexDefinition, type AggregateOp, type AnyApi, type ArgsValidator, type AuthState, type CachePurge, type DatabaseReader, type DatabaseWriter, type DurableObjectJurisdiction, type ExternalSourceCursor, 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, type X402ProcedureConfig, anyApi };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lunora/server",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.26",
|
|
4
4
|
"description": "Server primitives for Lunora: defineSchema, defineTable, query, mutation, and action",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"backend",
|
|
@@ -62,9 +62,9 @@
|
|
|
62
62
|
"access": "public"
|
|
63
63
|
},
|
|
64
64
|
"dependencies": {
|
|
65
|
-
"@lunora/errors": "1.0.0-alpha.
|
|
66
|
-
"@lunora/scheduler": "1.0.0-alpha.
|
|
67
|
-
"@lunora/values": "1.0.0-alpha.
|
|
65
|
+
"@lunora/errors": "1.0.0-alpha.5",
|
|
66
|
+
"@lunora/scheduler": "1.0.0-alpha.10",
|
|
67
|
+
"@lunora/values": "1.0.0-alpha.8",
|
|
68
68
|
"drizzle-orm": "^0.45.2",
|
|
69
69
|
"hono": "^4.12.28"
|
|
70
70
|
},
|