@absolutejs/sync 0.0.1 → 0.1.0
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/README.md +264 -24
- package/dist/adapters/drizzle/index.d.ts +17 -0
- package/dist/adapters/drizzle/index.js +128 -0
- package/dist/adapters/drizzle/index.js.map +12 -0
- package/dist/adapters/drizzle/read.d.ts +31 -0
- package/dist/adapters/drizzle/topics.d.ts +41 -0
- package/dist/adapters/drizzle/write.d.ts +69 -0
- package/dist/adapters/mysql/index.d.ts +75 -0
- package/dist/adapters/mysql/index.js +171 -0
- package/dist/adapters/mysql/index.js.map +11 -0
- package/dist/adapters/postgres/index.d.ts +53 -0
- package/dist/adapters/postgres/index.js +86 -0
- package/dist/adapters/postgres/index.js.map +10 -0
- package/dist/adapters/prisma/collection.d.ts +39 -0
- package/dist/adapters/prisma/index.d.ts +23 -0
- package/dist/adapters/prisma/index.js +231 -0
- package/dist/adapters/prisma/index.js.map +14 -0
- package/dist/adapters/prisma/predicate.d.ts +20 -0
- package/dist/adapters/prisma/read.d.ts +28 -0
- package/dist/adapters/prisma/topics.d.ts +29 -0
- package/dist/adapters/prisma/write.d.ts +65 -0
- package/dist/adapters/sqlite/index.d.ts +32 -0
- package/dist/adapters/sqlite/index.js +128 -0
- package/dist/adapters/sqlite/index.js.map +11 -0
- package/dist/angular/index.d.ts +1 -0
- package/dist/angular/index.js +347 -0
- package/dist/angular/index.js.map +11 -0
- package/dist/angular/sync-collection.service.d.ts +20 -0
- package/dist/client/index.d.ts +8 -30
- package/dist/client/index.js +744 -3
- package/dist/client/index.js.map +8 -4
- package/dist/client/liveQuery.d.ts +75 -0
- package/dist/client/subscriber.d.ts +30 -0
- package/dist/client/syncCollection.d.ts +102 -0
- package/dist/client/syncStore.d.ts +81 -0
- package/dist/engine/aggregate.d.ts +45 -0
- package/dist/engine/collection.d.ts +87 -0
- package/dist/engine/connection.d.ts +71 -0
- package/dist/engine/dataflow.d.ts +109 -0
- package/dist/engine/equiJoin.d.ts +51 -0
- package/dist/engine/graph.d.ts +85 -0
- package/dist/engine/index.d.ts +34 -0
- package/dist/engine/index.js +1269 -0
- package/dist/engine/index.js.map +20 -0
- package/dist/engine/materializedView.d.ts +53 -0
- package/dist/engine/mutation.d.ts +30 -0
- package/dist/engine/pollingSource.d.ts +42 -0
- package/dist/engine/routes.d.ts +40 -0
- package/dist/engine/socket.d.ts +64 -0
- package/dist/engine/syncEngine.d.ts +100 -0
- package/dist/engine/types.d.ts +45 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +160 -2
- package/dist/index.js.map +7 -5
- package/dist/react/index.d.ts +1 -0
- package/dist/react/index.js +332 -0
- package/dist/react/index.js.map +11 -0
- package/dist/react/useSyncCollection.d.ts +16 -0
- package/dist/reactiveHub.d.ts +6 -0
- package/dist/svelte/createSyncCollectionStore.d.ts +15 -0
- package/dist/svelte/index.d.ts +1 -0
- package/dist/svelte/index.js +338 -0
- package/dist/svelte/index.js.map +11 -0
- package/dist/vue/index.d.ts +1 -0
- package/dist/vue/index.js +331 -0
- package/dist/vue/index.js.map +11 -0
- package/dist/vue/useSyncCollection.d.ts +17 -0
- package/package.json +102 -6
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/engine/materializedView.ts", "../src/engine/aggregate.ts", "../src/engine/equiJoin.ts", "../src/engine/dataflow.ts", "../src/engine/pollingSource.ts", "../src/engine/collection.ts", "../src/engine/graph.ts", "../src/engine/mutation.ts", "../src/engine/syncEngine.ts", "../src/engine/routes.ts", "../src/engine/connection.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"import type { RowChange, RowKey, ViewDiff } from './types';\n\nexport type MaterializedViewOptions<T> = {\n\t/** Row identity within the result set. */\n\tkey: (row: T) => RowKey;\n\t/**\n\t * The query's WHERE as a JS predicate: does this row belong in the result\n\t * set? Evaluated on every changed row to decide enter/leave/update. (The\n\t * Drizzle/Prisma adapters can derive this from a query filter.)\n\t */\n\tmatch: (row: T) => boolean;\n\t/**\n\t * Equality used by {@link MaterializedView.reset} to detect changed rows.\n\t * Defaults to a shallow compare of own enumerable properties.\n\t */\n\tequals?: (a: T, b: T) => boolean;\n};\n\nexport type MaterializedView<T> = {\n\t/**\n\t * Replace the result set with `rows` (the initial DB query result). Rows are\n\t * trusted to already satisfy the predicate — the database applied the filter.\n\t */\n\thydrate: (rows: Iterable<T>) => void;\n\t/**\n\t * Apply one row change and return the resulting diff. Empty `added`/`removed`/\n\t * `changed` arrays mean the change did not affect this view.\n\t */\n\tapply: (change: RowChange<T>) => ViewDiff<T>;\n\t/**\n\t * Replace the result set with a fresh query result and return the diff versus\n\t * what the view previously held. Powers the refetch fallback for queries that\n\t * can't be matched incrementally.\n\t */\n\treset: (rows: Iterable<T>) => ViewDiff<T>;\n\t/** Current result set, as an array. */\n\trows: () => T[];\n\t/** Current result-set size. */\n\tsize: () => number;\n};\n\nconst emptyDiff = <T>(): ViewDiff<T> => ({\n\tadded: [],\n\tremoved: [],\n\tchanged: []\n});\n\nconst shallowEqual = (a: unknown, b: unknown): boolean => {\n\tif (a === b) {\n\t\treturn true;\n\t}\n\tif (\n\t\ttypeof a !== 'object' ||\n\t\ttypeof b !== 'object' ||\n\t\ta === null ||\n\t\tb === null\n\t) {\n\t\treturn false;\n\t}\n\tconst aKeys = Object.keys(a);\n\tconst bKeys = Object.keys(b);\n\tif (aKeys.length !== bKeys.length) {\n\t\treturn false;\n\t}\n\treturn aKeys.every(\n\t\t(key) =>\n\t\t\t(a as Record<string, unknown>)[key] ===\n\t\t\t(b as Record<string, unknown>)[key]\n\t);\n};\n\n/** True when a diff carries no changes. */\nexport const isEmptyViewDiff = <T>(diff: ViewDiff<T>): boolean =>\n\tdiff.added.length === 0 &&\n\tdiff.removed.length === 0 &&\n\tdiff.changed.length === 0;\n\n/**\n * A single query's materialized result set, maintained incrementally by\n * predicate matching (the Tier 3 IVM core). Hydrate once from the database, then\n * feed each changed row through {@link MaterializedView.apply}: the view decides\n * whether the row entered, left, stayed-and-changed, or is irrelevant, and\n * returns just that delta — so the server pushes diffs instead of refetching.\n *\n * Scope: single-table filtered queries (no ORDER BY / LIMIT windows — a top-N\n * query should hydrate-and-refetch rather than rely on this view, since a row\n * entering can silently evict another). Joins/aggregations are a later,\n * differential-dataflow engine.\n */\nexport const createMaterializedView = <T>(\n\toptions: MaterializedViewOptions<T>\n): MaterializedView<T> => {\n\tconst { key, match } = options;\n\tconst equals = options.equals ?? shallowEqual;\n\tconst set = new Map<RowKey, T>();\n\n\treturn {\n\t\thydrate: (rows) => {\n\t\t\tset.clear();\n\t\t\tfor (const row of rows) {\n\t\t\t\tset.set(key(row), row);\n\t\t\t}\n\t\t},\n\t\treset: (rows) => {\n\t\t\tconst next = new Map<RowKey, T>();\n\t\t\tconst added: T[] = [];\n\t\t\tconst changed: T[] = [];\n\t\t\tfor (const row of rows) {\n\t\t\t\tconst rowKey = key(row);\n\t\t\t\tnext.set(rowKey, row);\n\t\t\t\tconst previous = set.get(rowKey);\n\t\t\t\tif (previous === undefined) {\n\t\t\t\t\tadded.push(row);\n\t\t\t\t} else if (!equals(previous, row)) {\n\t\t\t\t\tchanged.push(row);\n\t\t\t\t}\n\t\t\t}\n\t\t\tconst removed: T[] = [];\n\t\t\tfor (const [rowKey, previous] of set) {\n\t\t\t\tif (!next.has(rowKey)) {\n\t\t\t\t\tremoved.push(previous);\n\t\t\t\t}\n\t\t\t}\n\t\t\tset.clear();\n\t\t\tfor (const [rowKey, row] of next) {\n\t\t\t\tset.set(rowKey, row);\n\t\t\t}\n\t\t\treturn { added, removed, changed };\n\t\t},\n\t\tapply: ({ op, row }) => {\n\t\t\tconst rowKey = key(row);\n\t\t\tconst existing = set.get(rowKey);\n\n\t\t\tif (op === 'delete') {\n\t\t\t\tif (existing === undefined) {\n\t\t\t\t\treturn emptyDiff();\n\t\t\t\t}\n\t\t\t\tset.delete(rowKey);\n\t\t\t\treturn { added: [], removed: [existing], changed: [] };\n\t\t\t}\n\n\t\t\t// insert | update — let the predicate decide membership.\n\t\t\tif (match(row)) {\n\t\t\t\tset.set(rowKey, row);\n\t\t\t\treturn existing === undefined\n\t\t\t\t\t? { added: [row], removed: [], changed: [] }\n\t\t\t\t\t: { added: [], removed: [], changed: [row] };\n\t\t\t}\n\n\t\t\t// No longer matches: it leaves the set if it was in it.\n\t\t\tif (existing !== undefined) {\n\t\t\t\tset.delete(rowKey);\n\t\t\t\treturn { added: [], removed: [existing], changed: [] };\n\t\t\t}\n\t\t\treturn emptyDiff();\n\t\t},\n\t\trows: () => [...set.values()],\n\t\tsize: () => set.size\n\t};\n};\n",
|
|
6
|
+
"import type { RowChange, RowKey } from './types';\n\nexport type AggregateOptions<T> = {\n\t/** Row identity — used to track each row's contribution across updates. */\n\tkey: (row: T) => RowKey;\n\t/** Group rows by this key. Omit to aggregate everything into one group (`''`). */\n\tgroupBy?: (row: T) => RowKey;\n\t/**\n\t * Numeric value to aggregate for `sum`/`avg`/`min`/`max`. Omit for a\n\t * count-only aggregate (sum stays 0, min/max stay undefined).\n\t */\n\tvalue?: (row: T) => number;\n};\n\n/** Maintained summary for one group. */\nexport type AggregateGroup = {\n\tgroup: RowKey;\n\tcount: number;\n\tsum: number;\n\t/** `sum / count`, or 0 when the group is empty. */\n\tavg: number;\n\tmin: number | undefined;\n\tmax: number | undefined;\n};\n\nexport type Aggregate<T> = {\n\t/** Bulk-load the initial rows (replaces current state). */\n\thydrate: (rows: Iterable<T>) => void;\n\t/** Fold one row change into the running aggregates. */\n\tapply: (change: RowChange<T>) => void;\n\t/** Current summary for every non-empty group. */\n\tgroups: () => AggregateGroup[];\n\t/** Current summary for one group, or `undefined` if empty. */\n\tgroup: (group: RowKey) => AggregateGroup | undefined;\n};\n\ntype GroupState = {\n\tcount: number;\n\tsum: number;\n\t/** Multiset of values, so removing the current min/max stays correct. */\n\tvalueCounts: Map<number, number>;\n\tmin: number | undefined;\n\tmax: number | undefined;\n};\n\nconst newGroupState = (): GroupState => ({\n\tcount: 0,\n\tsum: 0,\n\tvalueCounts: new Map(),\n\tmin: undefined,\n\tmax: undefined\n});\n\nconst recomputeExtremes = (state: GroupState) => {\n\tif (state.valueCounts.size === 0) {\n\t\tstate.min = undefined;\n\t\tstate.max = undefined;\n\t\treturn;\n\t}\n\tlet min = Infinity;\n\tlet max = -Infinity;\n\tfor (const value of state.valueCounts.keys()) {\n\t\tif (value < min) {\n\t\t\tmin = value;\n\t\t}\n\t\tif (value > max) {\n\t\t\tmax = value;\n\t\t}\n\t}\n\tstate.min = min;\n\tstate.max = max;\n};\n\nconst summarize = (group: RowKey, state: GroupState): AggregateGroup => ({\n\tgroup,\n\tcount: state.count,\n\tsum: state.sum,\n\tavg: state.count > 0 ? state.sum / state.count : 0,\n\tmin: state.min,\n\tmax: state.max\n});\n\n/**\n * An incrementally-maintained aggregation — the DD-lite for `count`/`sum`/`avg`/\n * `min`/`max`, optionally grouped. Feed it the change feed (insert/update/delete)\n * and it updates each group's summary in place: count/sum/avg are O(1); min/max\n * use a value multiset so removing the current extremum recomputes correctly\n * (O(distinct values) only when the extremum leaves).\n *\n * Per-row contributions are tracked by `key`, so updates (including a row moving\n * between groups) and deletes adjust the right group without re-scanning. Use it\n * server-side over the engine's change feed, or client-side over collection\n * diffs.\n */\nexport const createAggregate = <T>(\n\toptions: AggregateOptions<T>\n): Aggregate<T> => {\n\tconst { key, groupBy, value } = options;\n\tconst groups = new Map<RowKey, GroupState>();\n\t// Each row's last (group, value), so updates/deletes adjust the right group.\n\t// `value` is undefined for a count-only aggregate (no value extractor).\n\tconst contributions = new Map<\n\t\tRowKey,\n\t\t{ group: RowKey; value: number | undefined }\n\t>();\n\n\tconst add = (group: RowKey, contribution: number | undefined) => {\n\t\tlet state = groups.get(group);\n\t\tif (state === undefined) {\n\t\t\tstate = newGroupState();\n\t\t\tgroups.set(group, state);\n\t\t}\n\t\tstate.count += 1;\n\t\tif (contribution === undefined) {\n\t\t\treturn;\n\t\t}\n\t\tstate.sum += contribution;\n\t\tstate.valueCounts.set(\n\t\t\tcontribution,\n\t\t\t(state.valueCounts.get(contribution) ?? 0) + 1\n\t\t);\n\t\tstate.min =\n\t\t\tstate.min === undefined\n\t\t\t\t? contribution\n\t\t\t\t: Math.min(state.min, contribution);\n\t\tstate.max =\n\t\t\tstate.max === undefined\n\t\t\t\t? contribution\n\t\t\t\t: Math.max(state.max, contribution);\n\t};\n\n\tconst remove = (group: RowKey, contribution: number | undefined) => {\n\t\tconst state = groups.get(group);\n\t\tif (state === undefined) {\n\t\t\treturn;\n\t\t}\n\t\tstate.count -= 1;\n\t\tif (contribution !== undefined) {\n\t\t\tstate.sum -= contribution;\n\t\t\tconst remaining = (state.valueCounts.get(contribution) ?? 0) - 1;\n\t\t\tif (remaining <= 0) {\n\t\t\t\tstate.valueCounts.delete(contribution);\n\t\t\t\tif (contribution === state.min || contribution === state.max) {\n\t\t\t\t\trecomputeExtremes(state);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tstate.valueCounts.set(contribution, remaining);\n\t\t\t}\n\t\t}\n\t\tif (state.count <= 0) {\n\t\t\tgroups.delete(group);\n\t\t}\n\t};\n\n\tconst apply = (change: RowChange<T>) => {\n\t\tconst rowKey = key(change.row);\n\t\tconst previous = contributions.get(rowKey);\n\n\t\tif (change.op === 'delete') {\n\t\t\tif (previous !== undefined) {\n\t\t\t\tremove(previous.group, previous.value);\n\t\t\t\tcontributions.delete(rowKey);\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tconst group = groupBy ? groupBy(change.row) : '';\n\t\tconst contribution = value ? value(change.row) : undefined;\n\t\tif (previous !== undefined) {\n\t\t\tremove(previous.group, previous.value);\n\t\t}\n\t\tadd(group, contribution);\n\t\tcontributions.set(rowKey, { group, value: contribution });\n\t};\n\n\treturn {\n\t\thydrate: (rows) => {\n\t\t\tgroups.clear();\n\t\t\tcontributions.clear();\n\t\t\tfor (const row of rows) {\n\t\t\t\tapply({ op: 'insert', row });\n\t\t\t}\n\t\t},\n\t\tapply,\n\t\tgroups: () =>\n\t\t\t[...groups.entries()].map(([group, state]) =>\n\t\t\t\tsummarize(group, state)\n\t\t\t),\n\t\tgroup: (group) => {\n\t\t\tconst state = groups.get(group);\n\t\t\treturn state === undefined ? undefined : summarize(group, state);\n\t\t}\n\t};\n};\n",
|
|
7
|
+
"import type { RowChange, RowKey, ViewDiff } from './types';\n\nexport type EquiJoinOptions<L, R, Out> = {\n\t/** Left row identity. */\n\tleftKey: (left: L) => RowKey;\n\t/** Right row identity. */\n\trightKey: (right: R) => RowKey;\n\t/** Join value on the left (matched against `rightOn`). */\n\tleftOn: (left: L) => RowKey;\n\t/** Join value on the right (matched against `leftOn`). */\n\trightOn: (right: R) => RowKey;\n\t/** Combine a matched pair into an output row. */\n\tselect: (left: L, right: R) => Out;\n\t/**\n\t * Provide to make this a LEFT join: a left row with no matching right emits\n\t * `selectUnmatched(left)` instead of nothing, and is replaced by matched rows\n\t * once a right appears (and reverts when the last right leaves). Omit for an\n\t * inner join.\n\t */\n\tselectUnmatched?: (left: L) => Out;\n\t/**\n\t * Equality used to detect when a matched pair's output value changed.\n\t * Defaults to a shallow compare of own enumerable properties.\n\t */\n\tequals?: (a: Out, b: Out) => boolean;\n};\n\nexport type EquiJoin<L, R, Out> = {\n\t/** Bulk-load both inputs (replaces current state). */\n\thydrate: (left: Iterable<L>, right: Iterable<R>) => void;\n\t/** Apply a change to the left input; returns the output diff. */\n\tapplyLeft: (change: RowChange<L>) => ViewDiff<Out>;\n\t/** Apply a change to the right input; returns the output diff. */\n\tapplyRight: (change: RowChange<R>) => ViewDiff<Out>;\n\t/** Current joined rows. */\n\trows: () => Out[];\n\t/** Number of joined rows. */\n\tsize: () => number;\n};\n\nconst shallowEqual = (a: unknown, b: unknown): boolean => {\n\tif (a === b) {\n\t\treturn true;\n\t}\n\tif (\n\t\ttypeof a !== 'object' ||\n\t\ttypeof b !== 'object' ||\n\t\ta === null ||\n\t\tb === null\n\t) {\n\t\treturn false;\n\t}\n\tconst aKeys = Object.keys(a);\n\tconst bKeys = Object.keys(b);\n\treturn (\n\t\taKeys.length === bKeys.length &&\n\t\taKeys.every(\n\t\t\t(key) =>\n\t\t\t\t(a as Record<string, unknown>)[key] ===\n\t\t\t\t(b as Record<string, unknown>)[key]\n\t\t)\n\t);\n};\n\nconst addToIndex = (\n\tindex: Map<RowKey, Set<RowKey>>,\n\tjoinValue: RowKey,\n\tkey: RowKey\n) => {\n\tlet bucket = index.get(joinValue);\n\tif (bucket === undefined) {\n\t\tbucket = new Set();\n\t\tindex.set(joinValue, bucket);\n\t}\n\tbucket.add(key);\n};\n\nconst removeFromIndex = (\n\tindex: Map<RowKey, Set<RowKey>>,\n\tjoinValue: RowKey,\n\tkey: RowKey\n) => {\n\tconst bucket = index.get(joinValue);\n\tif (bucket === undefined) {\n\t\treturn;\n\t}\n\tbucket.delete(key);\n\tif (bucket.size === 0) {\n\t\tindex.delete(joinValue);\n\t}\n};\n\n/**\n * An incrementally-maintained equi-join (the differential-dataflow core, bounded\n * to a single two-input equality join). Hydrate both sides, then feed each side's\n * row changes through {@link EquiJoin.applyLeft} / `applyRight`: it indexes both\n * inputs by the join value and emits only the `{ added, removed, changed }`\n * joined rows the change affects — touching the matching pairs, not the whole\n * result. Inner by default; pass `selectUnmatched` for a LEFT join.\n *\n * Output rows are keyed internally by the `(leftKey, rightKey)` pair, so it\n * handles many-to-many. The consumer keys the emitted rows by its own key; for a\n * many-to-many join, include both ids in the output so that key is unique\n * (a many-to-one join can key by the left id alone).\n */\nexport const createEquiJoin = <L, R, Out>(\n\toptions: EquiJoinOptions<L, R, Out>\n): EquiJoin<L, R, Out> => {\n\tconst { leftKey, rightKey, leftOn, rightOn, select, selectUnmatched } =\n\t\toptions;\n\tconst equals = options.equals ?? shallowEqual;\n\n\tconst lefts = new Map<RowKey, L>();\n\tconst rights = new Map<RowKey, R>();\n\tconst leftByJoin = new Map<RowKey, Set<RowKey>>();\n\tconst rightByJoin = new Map<RowKey, Set<RowKey>>();\n\tconst output = new Map<string, Out>();\n\t// Output keys each left currently contributes (matched pairs, or its single\n\t// unmatched row in a left join) — so a change recomputes just that left.\n\tconst outByLeft = new Map<RowKey, Set<string>>();\n\n\tconst outKey = (lk: RowKey, rk: RowKey): string => `${lk} ${rk}`;\n\tconst unmatchedKey = (lk: RowKey): string => `${lk} ~`;\n\n\t/** The output rows a single left row currently contributes. */\n\tconst leftOutputs = (lk: RowKey, left: L): Map<string, Out> => {\n\t\tconst result = new Map<string, Out>();\n\t\tconst rks = rightByJoin.get(leftOn(left));\n\t\tif (rks !== undefined && rks.size > 0) {\n\t\t\tfor (const rk of rks) {\n\t\t\t\tconst right = rights.get(rk);\n\t\t\t\tif (right !== undefined) {\n\t\t\t\t\tresult.set(outKey(lk, rk), select(left, right));\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (selectUnmatched !== undefined) {\n\t\t\tresult.set(unmatchedKey(lk), selectUnmatched(left));\n\t\t}\n\t\treturn result;\n\t};\n\n\t/** Diff one left's previous output keys against its freshly-computed set. */\n\tconst reconcileLeft = (\n\t\tlk: RowKey,\n\t\tafter: Map<string, Out>\n\t): ViewDiff<Out> => {\n\t\tconst before = outByLeft.get(lk) ?? new Set<string>();\n\t\tconst added: Out[] = [];\n\t\tconst removed: Out[] = [];\n\t\tconst changed: Out[] = [];\n\t\tfor (const [ok, value] of after) {\n\t\t\tconst previous = output.get(ok);\n\t\t\tif (previous === undefined) {\n\t\t\t\tadded.push(value);\n\t\t\t} else if (!equals(previous, value)) {\n\t\t\t\tchanged.push(value);\n\t\t\t}\n\t\t\toutput.set(ok, value);\n\t\t}\n\t\tfor (const ok of before) {\n\t\t\tif (!after.has(ok)) {\n\t\t\t\tconst previous = output.get(ok);\n\t\t\t\tif (previous !== undefined) {\n\t\t\t\t\tremoved.push(previous);\n\t\t\t\t\toutput.delete(ok);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif (after.size === 0) {\n\t\t\toutByLeft.delete(lk);\n\t\t} else {\n\t\t\toutByLeft.set(lk, new Set(after.keys()));\n\t\t}\n\t\treturn { added, removed, changed };\n\t};\n\n\tconst mergeInto = (target: ViewDiff<Out>, diff: ViewDiff<Out>) => {\n\t\ttarget.added.push(...diff.added);\n\t\ttarget.removed.push(...diff.removed);\n\t\ttarget.changed.push(...diff.changed);\n\t};\n\n\treturn {\n\t\thydrate: (left, right) => {\n\t\t\tlefts.clear();\n\t\t\trights.clear();\n\t\t\tleftByJoin.clear();\n\t\t\trightByJoin.clear();\n\t\t\toutput.clear();\n\t\t\toutByLeft.clear();\n\t\t\tfor (const right_ of right) {\n\t\t\t\tconst rk = rightKey(right_);\n\t\t\t\trights.set(rk, right_);\n\t\t\t\taddToIndex(rightByJoin, rightOn(right_), rk);\n\t\t\t}\n\t\t\tfor (const left_ of left) {\n\t\t\t\tconst lk = leftKey(left_);\n\t\t\t\tlefts.set(lk, left_);\n\t\t\t\taddToIndex(leftByJoin, leftOn(left_), lk);\n\t\t\t\tconst outs = leftOutputs(lk, left_);\n\t\t\t\tfor (const [ok, value] of outs) {\n\t\t\t\t\toutput.set(ok, value);\n\t\t\t\t}\n\t\t\t\tif (outs.size > 0) {\n\t\t\t\t\toutByLeft.set(lk, new Set(outs.keys()));\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\n\t\tapplyLeft: ({ op, row }) => {\n\t\t\tconst lk = leftKey(row);\n\t\t\tconst existing = lefts.get(lk);\n\t\t\tif (existing !== undefined) {\n\t\t\t\tremoveFromIndex(leftByJoin, leftOn(existing), lk);\n\t\t\t}\n\t\t\tif (op === 'delete') {\n\t\t\t\tlefts.delete(lk);\n\t\t\t} else {\n\t\t\t\tlefts.set(lk, row);\n\t\t\t\taddToIndex(leftByJoin, leftOn(row), lk);\n\t\t\t}\n\t\t\tconst after =\n\t\t\t\top === 'delete' ? new Map<string, Out>() : leftOutputs(lk, row);\n\t\t\treturn reconcileLeft(lk, after);\n\t\t},\n\n\t\tapplyRight: ({ op, row }) => {\n\t\t\tconst rk = rightKey(row);\n\t\t\tconst existing = rights.get(rk);\n\t\t\tconst affected = new Set<RowKey>();\n\t\t\tconst addAffected = (joinValue: RowKey) => {\n\t\t\t\tfor (const lk of leftByJoin.get(joinValue) ?? []) {\n\t\t\t\t\taffected.add(lk);\n\t\t\t\t}\n\t\t\t};\n\t\t\tif (existing !== undefined) {\n\t\t\t\taddAffected(rightOn(existing));\n\t\t\t\tremoveFromIndex(rightByJoin, rightOn(existing), rk);\n\t\t\t}\n\t\t\tif (op === 'delete') {\n\t\t\t\trights.delete(rk);\n\t\t\t} else {\n\t\t\t\trights.set(rk, row);\n\t\t\t\taddToIndex(rightByJoin, rightOn(row), rk);\n\t\t\t\taddAffected(rightOn(row));\n\t\t\t}\n\n\t\t\tconst diff: ViewDiff<Out> = { added: [], removed: [], changed: [] };\n\t\t\tfor (const lk of affected) {\n\t\t\t\tconst left = lefts.get(lk);\n\t\t\t\tif (left !== undefined) {\n\t\t\t\t\tmergeInto(diff, reconcileLeft(lk, leftOutputs(lk, left)));\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn diff;\n\t\t},\n\n\t\trows: () => [...output.values()],\n\t\tsize: () => output.size\n\t};\n};\n",
|
|
8
|
+
"import { createAggregate } from './aggregate';\nimport type { AggregateGroup } from './aggregate';\nimport { createEquiJoin } from './equiJoin';\nimport type { RowChange, RowKey, ViewDiff } from './types';\n\n/**\n * Composable incremental dataflow (the general operator graph, in progress).\n *\n * Every edge in the graph is a stream of keyed **changes** — an `upsert` (insert\n * or update, last-write-wins per key) or a `delete`. Collapsing added/changed\n * into one `upsert` is what makes operators compose: `filter`/`map` become\n * stateless stream transforms, `join` reuses the equi-join operator, and a\n * `materialize` sink turns the final stream back into a `{ added, removed,\n * changed }` diff for the transport.\n */\nexport type Change<T> = { op: 'upsert' | 'delete'; key: RowKey; row: T };\n\n/** A single-input incremental operator: a batch of input changes → output changes. */\nexport type Operator<In, Out> = {\n\tpush: (changes: Change<In>[]) => Change<Out>[];\n};\n\nconst shallowEqual = (a: unknown, b: unknown): boolean => {\n\tif (a === b) {\n\t\treturn true;\n\t}\n\tif (\n\t\ttypeof a !== 'object' ||\n\t\ttypeof b !== 'object' ||\n\t\ta === null ||\n\t\tb === null\n\t) {\n\t\treturn false;\n\t}\n\tconst aKeys = Object.keys(a);\n\tconst bKeys = Object.keys(b);\n\treturn (\n\t\taKeys.length === bKeys.length &&\n\t\taKeys.every(\n\t\t\t(key) =>\n\t\t\t\t(a as Record<string, unknown>)[key] ===\n\t\t\t\t(b as Record<string, unknown>)[key]\n\t\t)\n\t);\n};\n\n/** Lift a table row change into a dataflow change (a graph source). */\nexport const fromRowChange = <T>(\n\tchange: RowChange<T>,\n\tkey: (row: T) => RowKey\n): Change<T> => ({\n\top: change.op === 'delete' ? 'delete' : 'upsert',\n\tkey: key(change.row),\n\trow: change.row\n});\n\n/**\n * Keep only rows matching `predicate`. Stateless: a row that fails the predicate\n * is emitted as a `delete` (downstream removes it if present, else no-op), so a\n * row that stops matching leaves correctly.\n */\nexport const filterOp = <T>(\n\tpredicate: (row: T) => boolean\n): Operator<T, T> => ({\n\tpush: (changes) =>\n\t\tchanges.map((change) =>\n\t\t\tchange.op === 'upsert' && !predicate(change.row)\n\t\t\t\t? { op: 'delete', key: change.key, row: change.row }\n\t\t\t\t: change\n\t\t)\n});\n\n/**\n * Transform each row. Stateless; preserves identity unless `rekey` is given (to\n * derive the output key from the mapped row).\n */\nexport const mapOp = <In, Out>(\n\ttransform: (row: In) => Out,\n\trekey?: (row: Out) => RowKey\n): Operator<In, Out> => ({\n\tpush: (changes) =>\n\t\tchanges.map((change) => {\n\t\t\tconst row = transform(change.row);\n\t\t\treturn {\n\t\t\t\top: change.op,\n\t\t\t\tkey: rekey ? rekey(row) : change.key,\n\t\t\t\trow\n\t\t\t};\n\t\t})\n});\n\n/** Compose two operators into one (`a` then `b`). Nest for longer chains. */\nexport const chain = <A, B, C>(\n\ta: Operator<A, B>,\n\tb: Operator<B, C>\n): Operator<A, C> => ({\n\tpush: (changes) => b.push(a.push(changes))\n});\n\nexport type AggregateOpOptions<In> = {\n\t/** Input row identity (to track each row's contribution across updates). */\n\tkey: (row: In) => RowKey;\n\t/** Group rows by this key (omit for one `''` group). */\n\tgroupBy?: (row: In) => RowKey;\n\t/** Numeric value for sum/avg/min/max (omit for a count-only aggregate). */\n\tvalue?: (row: In) => number;\n};\n\n/**\n * Aggregate a change stream into a stream of group summaries — `count`/`sum`/\n * `avg`/`min`/`max` per group, maintained incrementally (wraps\n * {@link createAggregate}). Emits an `upsert` of the group summary for each group\n * a batch touched, or a `delete` when a group empties. Output is keyed by group,\n * so it composes downstream like any other operator (e.g. after a join).\n */\nexport const aggregateOp = <In>(\n\toptions: AggregateOpOptions<In>\n): Operator<In, AggregateGroup> => {\n\tconst aggregate = createAggregate<In>(options);\n\tconst groupOf = new Map<RowKey, RowKey>();\n\n\treturn {\n\t\tpush: (changes) => {\n\t\t\tconst affected = new Set<RowKey>();\n\t\t\tfor (const change of changes) {\n\t\t\t\tconst inputKey = options.key(change.row);\n\t\t\t\tconst previousGroup = groupOf.get(inputKey);\n\t\t\t\tif (previousGroup !== undefined) {\n\t\t\t\t\taffected.add(previousGroup);\n\t\t\t\t}\n\t\t\t\tif (change.op === 'delete') {\n\t\t\t\t\taggregate.apply({ op: 'delete', row: change.row });\n\t\t\t\t\tgroupOf.delete(inputKey);\n\t\t\t\t} else {\n\t\t\t\t\tconst group = options.groupBy\n\t\t\t\t\t\t? options.groupBy(change.row)\n\t\t\t\t\t\t: '';\n\t\t\t\t\taffected.add(group);\n\t\t\t\t\taggregate.apply({ op: 'update', row: change.row });\n\t\t\t\t\tgroupOf.set(inputKey, group);\n\t\t\t\t}\n\t\t\t}\n\t\t\tconst out: Change<AggregateGroup>[] = [];\n\t\t\tfor (const group of affected) {\n\t\t\t\tconst summary = aggregate.group(group);\n\t\t\t\tif (summary === undefined) {\n\t\t\t\t\tout.push({\n\t\t\t\t\t\top: 'delete',\n\t\t\t\t\t\tkey: group,\n\t\t\t\t\t\trow: {\n\t\t\t\t\t\t\tgroup,\n\t\t\t\t\t\t\tcount: 0,\n\t\t\t\t\t\t\tsum: 0,\n\t\t\t\t\t\t\tavg: 0,\n\t\t\t\t\t\t\tmin: undefined,\n\t\t\t\t\t\t\tmax: undefined\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t} else {\n\t\t\t\t\tout.push({ op: 'upsert', key: group, row: summary });\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn out;\n\t\t}\n\t};\n};\n\nexport type OrderByOptions<T> = {\n\t/** Row identity. */\n\tkey: (row: T) => RowKey;\n\t/** Sort comparator (ascending: negative = a before b). */\n\tcompare: (a: T, b: T) => number;\n\t/** Keep at most this many rows (the top-N window). */\n\tlimit?: number;\n\t/** Skip this many rows from the front (pagination). Defaults to 0. */\n\toffset?: number;\n};\n\n/**\n * Maintain a sorted top-N window: keep only the `[offset, offset + limit)` slice\n * by `compare`, emitting which rows entered/left the window as input changes.\n * A single insert can both add a row and evict the one it displaced — both are\n * emitted. Bounded output (≤ limit upserts per batch); it re-sorts its input, so\n * place it where the input is already narrowed (e.g. after a filter/join).\n *\n * The window is the right *set* of rows; sort them by the same comparator on the\n * client for display order (cheap for N rows — the diff protocol is unordered).\n */\nexport const orderByOp = <T>(options: OrderByOptions<T>): Operator<T, T> => {\n\tconst { key, compare } = options;\n\tconst offset = options.offset ?? 0;\n\tconst limit = options.limit ?? Number.POSITIVE_INFINITY;\n\tconst all = new Map<RowKey, T>();\n\tlet window = new Map<RowKey, T>();\n\n\treturn {\n\t\tpush: (changes) => {\n\t\t\tfor (const change of changes) {\n\t\t\t\tif (change.op === 'delete') {\n\t\t\t\t\tall.delete(change.key);\n\t\t\t\t} else {\n\t\t\t\t\tall.set(change.key, change.row);\n\t\t\t\t}\n\t\t\t}\n\t\t\tconst windowed = [...all.values()]\n\t\t\t\t.sort(compare)\n\t\t\t\t.slice(offset, offset + limit);\n\t\t\tconst next = new Map<RowKey, T>();\n\t\t\tfor (const row of windowed) {\n\t\t\t\tnext.set(key(row), row);\n\t\t\t}\n\t\t\tconst out: Change<T>[] = [];\n\t\t\tfor (const [rowKey, row] of window) {\n\t\t\t\tif (!next.has(rowKey)) {\n\t\t\t\t\tout.push({ op: 'delete', key: rowKey, row });\n\t\t\t\t}\n\t\t\t}\n\t\t\tfor (const [rowKey, row] of next) {\n\t\t\t\tout.push({ op: 'upsert', key: rowKey, row });\n\t\t\t}\n\t\t\twindow = next;\n\t\t\treturn out;\n\t\t}\n\t};\n};\n\n/** A two-input incremental equi-join node. */\nexport type JoinNode<L, R, Out> = {\n\thydrate: (left: Iterable<L>, right: Iterable<R>) => void;\n\tpushLeft: (changes: Change<L>[]) => Change<Out>[];\n\tpushRight: (changes: Change<R>[]) => Change<Out>[];\n\trows: () => Out[];\n};\n\nexport type JoinNodeOptions<L, R, Out> = {\n\tleftKey: (left: L) => RowKey;\n\trightKey: (right: R) => RowKey;\n\tleftOn: (left: L) => RowKey;\n\trightOn: (right: R) => RowKey;\n\tselect: (left: L, right: R) => Out;\n\t/** Provide for a LEFT join: output for a left row with no match. */\n\tselectUnmatched?: (left: L) => Out;\n\t/** Output row identity (unique per emitted pair). */\n\tkey: (out: Out) => RowKey;\n};\n\n/**\n * A join as a dataflow node — reuses {@link createEquiJoin} and converts its\n * `{ added, removed, changed }` deltas into the upsert/delete change stream.\n */\nexport const joinNode = <L, R, Out>(\n\toptions: JoinNodeOptions<L, R, Out>\n): JoinNode<L, R, Out> => {\n\tconst join = createEquiJoin<L, R, Out>(options);\n\tconst key = options.key;\n\n\tconst toChanges = (diff: ViewDiff<Out>): Change<Out>[] => {\n\t\tconst changes: Change<Out>[] = [];\n\t\tfor (const row of diff.removed) {\n\t\t\tchanges.push({ op: 'delete', key: key(row), row });\n\t\t}\n\t\tfor (const row of diff.added) {\n\t\t\tchanges.push({ op: 'upsert', key: key(row), row });\n\t\t}\n\t\tfor (const row of diff.changed) {\n\t\t\tchanges.push({ op: 'upsert', key: key(row), row });\n\t\t}\n\t\treturn changes;\n\t};\n\tconst asRowChange = <T>(change: Change<T>): RowChange<T> => ({\n\t\top: change.op === 'delete' ? 'delete' : 'update',\n\t\trow: change.row\n\t});\n\n\treturn {\n\t\thydrate: (left, right) => join.hydrate(left, right),\n\t\tpushLeft: (changes) =>\n\t\t\tchanges.flatMap((change) =>\n\t\t\t\ttoChanges(join.applyLeft(asRowChange(change)))\n\t\t\t),\n\t\tpushRight: (changes) =>\n\t\t\tchanges.flatMap((change) =>\n\t\t\t\ttoChanges(join.applyRight(asRowChange(change)))\n\t\t\t),\n\t\trows: () => join.rows()\n\t};\n};\n\nexport type Materializer<T> = {\n\t/** Replace the set with initial rows (no diff emitted). */\n\thydrate: (rows: Iterable<T>) => void;\n\t/** Apply a change stream and return the resulting result-set diff. */\n\tapply: (changes: Change<T>[]) => ViewDiff<T>;\n\trows: () => T[];\n};\n\n/**\n * The graph sink: maintain a keyed result set from a change stream and emit the\n * `{ added, removed, changed }` diff each batch produces — the boundary back to\n * the transport / client.\n */\nexport const materialize = <T>(\n\tkey: (row: T) => RowKey,\n\tequals: (a: T, b: T) => boolean = shallowEqual\n): Materializer<T> => {\n\tconst set = new Map<RowKey, T>();\n\treturn {\n\t\thydrate: (rows) => {\n\t\t\tset.clear();\n\t\t\tfor (const row of rows) {\n\t\t\t\tset.set(key(row), row);\n\t\t\t}\n\t\t},\n\t\tapply: (changes) => {\n\t\t\tconst added: T[] = [];\n\t\t\tconst removed: T[] = [];\n\t\t\tconst changed: T[] = [];\n\t\t\tfor (const change of changes) {\n\t\t\t\tif (change.op === 'delete') {\n\t\t\t\t\tconst previous = set.get(change.key);\n\t\t\t\t\tif (previous !== undefined) {\n\t\t\t\t\t\tremoved.push(previous);\n\t\t\t\t\t\tset.delete(change.key);\n\t\t\t\t\t}\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tconst previous = set.get(change.key);\n\t\t\t\tset.set(change.key, change.row);\n\t\t\t\tif (previous === undefined) {\n\t\t\t\t\tadded.push(change.row);\n\t\t\t\t} else if (!equals(previous, change.row)) {\n\t\t\t\t\tchanged.push(change.row);\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn { added, removed, changed };\n\t\t},\n\t\trows: () => [...set.values()]\n\t};\n};\n",
|
|
9
|
+
"import type { ChangeSource, EmitChange, ParsedChange, RowOp } from './types';\n\n/**\n * A database-agnostic CDC {@link ChangeSource} that tails an append-only\n * changelog (outbox) table and emits its rows into the engine.\n *\n * This is the portable way to catch out-of-band writes on databases without a\n * native push channel — MySQL (no `LISTEN/NOTIFY`) and SQLite (no update hook\n * reachable from the JS runtime). Install per-table triggers that append to the\n * changelog (see `@absolutejs/sync/mysql` and `/sqlite`), then poll it here. It\n * is also a fine fallback for Postgres when you'd rather not run `LISTEN`.\n *\n * Driver-agnostic: you supply `poll(sinceSeq)` — a single\n * `SELECT seq, tbl, op, payload FROM <changelog> WHERE seq > ? ORDER BY seq` run\n * with your client — and the adapter tracks the cursor, parses each row, emits,\n * and advances. Delivery is at-least-once across crashes (a row may re-emit if\n * the process dies mid-batch); the engine's per-key last-write-wins makes that\n * safe. Use `onProcessed` to prune the changelog once a watermark is durable.\n */\n\nconst OP_BY_NAME: Record<string, RowOp> = {\n\tinsert: 'insert',\n\tINSERT: 'insert',\n\tupdate: 'update',\n\tUPDATE: 'update',\n\tdelete: 'delete',\n\tDELETE: 'delete'\n};\n\n/** One changelog row, as returned by your `poll` query. */\nexport type OutboxRow = {\n\t/** Monotonic sequence (the cursor advances to the max seen). */\n\tseq: number;\n\t/** Source table the change happened on. */\n\ttbl: string;\n\t/** `insert` | `update` | `delete` (upper- or lower-case). */\n\top: string;\n\t/** The row's captured values — a JSON string or an already-parsed object. */\n\tpayload: unknown;\n};\n\n/**\n * Default changelog-row parser: normalizes `op`, JSON-parses a string `payload`,\n * and returns `{ table, change }`. Returns `undefined` for a malformed row so it\n * is skipped (and its `seq` still advances the cursor) rather than wedging the\n * feed.\n */\nexport const parseOutboxRow = (row: OutboxRow): ParsedChange | undefined => {\n\tif (typeof row.tbl !== 'string') {\n\t\treturn undefined;\n\t}\n\tconst op = OP_BY_NAME[row.op];\n\tif (op === undefined) {\n\t\treturn undefined;\n\t}\n\tlet payload: unknown = row.payload;\n\tif (typeof payload === 'string') {\n\t\ttry {\n\t\t\tpayload = JSON.parse(payload);\n\t\t} catch {\n\t\t\treturn undefined;\n\t\t}\n\t}\n\tif (typeof payload !== 'object' || payload === null) {\n\t\treturn undefined;\n\t}\n\treturn { table: row.tbl, change: { op, row: payload } };\n};\n\nexport type PollingChangeSourceOptions = {\n\t/**\n\t * Fetch changelog rows with `seq > sinceSeq`, ordered by `seq` ascending.\n\t * e.g. `(since) => sql\\`SELECT seq, tbl, op, payload FROM absolute_sync_changelog WHERE seq > ${since} ORDER BY seq\\``.\n\t */\n\tpoll: (sinceSeq: number) => Promise<OutboxRow[]> | OutboxRow[];\n\t/** Poll interval in ms. Defaults to 1000. */\n\tintervalMs?: number;\n\t/** Resume cursor (highest `seq` already processed). Defaults to 0. */\n\tstartSeq?: number;\n\t/** Override the row parser (defaults to {@link parseOutboxRow}). */\n\tparse?: (row: OutboxRow) => ParsedChange | undefined;\n\t/** Called after a non-empty batch with the new watermark — prune here. */\n\tonProcessed?: (uptoSeq: number) => void | Promise<void>;\n\t/** Called if a poll throws (the loop keeps running). Defaults to a warning. */\n\tonError?: (error: unknown) => void;\n};\n\n/**\n * Create a polling {@link ChangeSource} over a changelog table. Connect it with\n * `engine.connectSource(...)`; `start` runs an immediate first poll (draining any\n * backlog) and then polls every `intervalMs` until `stop`.\n */\nexport const createPollingChangeSource = (\n\toptions: PollingChangeSourceOptions\n): ChangeSource => {\n\tconst intervalMs = options.intervalMs ?? 1000;\n\tconst parse = options.parse ?? parseOutboxRow;\n\tconst onError =\n\t\toptions.onError ??\n\t\t((error: unknown) => {\n\t\t\tconsole.warn('[sync] polling change source error:', error);\n\t\t});\n\tlet cursor = options.startSeq ?? 0;\n\tlet running = false;\n\tlet timer: ReturnType<typeof setTimeout> | undefined;\n\n\tconst tick = async (emit: EmitChange): Promise<void> => {\n\t\tif (!running) {\n\t\t\treturn;\n\t\t}\n\t\ttry {\n\t\t\tconst rows = await options.poll(cursor);\n\t\t\tfor (const row of rows) {\n\t\t\t\tconst parsed = parse(row);\n\t\t\t\tif (parsed !== undefined) {\n\t\t\t\t\tawait emit(parsed.table, parsed.change);\n\t\t\t\t}\n\t\t\t\tif (typeof row.seq === 'number' && row.seq > cursor) {\n\t\t\t\t\tcursor = row.seq;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (rows.length > 0) {\n\t\t\t\tawait options.onProcessed?.(cursor);\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tonError(error);\n\t\t}\n\t\tif (running) {\n\t\t\ttimer = setTimeout(() => {\n\t\t\t\tvoid tick(emit);\n\t\t\t}, intervalMs);\n\t\t}\n\t};\n\n\treturn {\n\t\tstart: async (emit) => {\n\t\t\tif (running) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\trunning = true;\n\t\t\tawait tick(emit);\n\t\t},\n\t\tstop: () => {\n\t\t\trunning = false;\n\t\t\tif (timer !== undefined) {\n\t\t\t\tclearTimeout(timer);\n\t\t\t\ttimer = undefined;\n\t\t\t}\n\t\t}\n\t};\n};\n",
|
|
10
|
+
"import type { RowKey } from './types';\n\n/**\n * App-provided context for a subscription — typically the authenticated session\n * (user id, roles). Passed to `authorize`, `hydrate`, and `match` so a\n * collection can scope its rows to the caller.\n */\nexport type CollectionContext = Record<string, unknown>;\n\nexport type CollectionDefinition<T, P = void, Ctx = CollectionContext> = {\n\t/** Collection name — its identity for subscribe (e.g. `orders`). */\n\tname: string;\n\t/**\n\t * Source tables this collection reads. A committed change to any of them\n\t * updates the collection. Defaults to `[name]`. List several for a join /\n\t * aggregate collection — which uses the refetch fallback, since a single\n\t * table's row can't be matched into a multi-table result.\n\t */\n\ttables?: string[];\n\t/**\n\t * Fetch the initial result set from your database (any ORM). Receives the\n\t * subscription's params and context so it can filter to the caller.\n\t */\n\thydrate: (params: P, ctx: Ctx) => Promise<Iterable<T>> | Iterable<T>;\n\t/** Row identity. Defaults to `row.id`. */\n\tkey?: (row: T) => RowKey;\n\t/**\n\t * The query's filter as a JS predicate, for incremental matching. Omit to use\n\t * the refetch fallback (re-hydrate on every change to the collection).\n\t *\n\t * It MUST encode the same row filter as `hydrate`/`authorize`: a change that\n\t * the predicate accepts is pushed to the subscriber, so a too-loose predicate\n\t * leaks rows. (Deriving `match` from the same filter as `hydrate` keeps the\n\t * two in lockstep — the planned adapter convenience.)\n\t */\n\tmatch?: (row: T, params: P, ctx: Ctx) => boolean;\n\t/**\n\t * Access control: return `false` (or throw) to deny the subscription. Runs\n\t * before `hydrate`. Without it a collection is world-readable, so treat it as\n\t * mandatory for any non-public data.\n\t */\n\tauthorize?: (params: P, ctx: Ctx) => boolean | Promise<boolean>;\n};\n\n/**\n * Define a syncable collection. Identity at runtime — it exists for type\n * inference, so `params`/`ctx`/row types flow through `hydrate`/`match`/\n * `authorize` without restating them. Register it with a {@link SyncEngine}.\n */\nexport const defineCollection = <T, P = void, Ctx = CollectionContext>(\n\tdefinition: CollectionDefinition<T, P, Ctx>\n): CollectionDefinition<T, P, Ctx> => definition;\n\n/** One input of a join collection. */\nexport type JoinSide<Row, P, Ctx> = {\n\t/** Source table name (the change feed routes its changes to this side). */\n\ttable: string;\n\t/** Fetch this side's rows for the subscription (scoped to the caller). */\n\thydrate: (params: P, ctx: Ctx) => Promise<Iterable<Row>> | Iterable<Row>;\n\t/** Row identity within this side. */\n\tkey: (row: Row) => RowKey;\n\t/** Join value — matched for equality against the other side's `on`. */\n\ton: (row: Row) => RowKey;\n\t/**\n\t * Access/predicate filter for incremental changes on this side. A changed row\n\t * that fails it is treated as a leave (removed from the join), so a row that\n\t * becomes invisible drops out. Omit only for an unscoped side.\n\t */\n\tmatch?: (row: Row, params: P, ctx: Ctx) => boolean;\n};\n\n/**\n * A collection that is the incremental inner equi-join of two tables. The engine\n * maintains it with an {@link createEquiJoin} operator — a change to either side\n * moves only the affected pairs, instead of re-hydrating the whole join.\n */\nexport type JoinCollectionDefinition<\n\tL,\n\tR,\n\tOut,\n\tP = void,\n\tCtx = CollectionContext\n> = {\n\tname: string;\n\tkind: 'join';\n\tleft: JoinSide<L, P, Ctx>;\n\tright: JoinSide<R, P, Ctx>;\n\t/** Combine a matched pair into an output row. */\n\tselect: (left: L, right: R) => Out;\n\t/** Output row identity (must be unique per emitted row). */\n\tkey: (out: Out) => RowKey;\n\t/** Access control; return false (or throw) to deny the subscription. */\n\tauthorize?: (params: P, ctx: Ctx) => boolean | Promise<boolean>;\n};\n\n/**\n * Define an incremental equi-join collection (see {@link JoinCollectionDefinition}).\n * For a many-to-one join the output can key by the left id; for many-to-many,\n * include both ids in the output and key on the pair.\n */\nexport const defineJoinCollection = <\n\tL,\n\tR,\n\tOut,\n\tP = void,\n\tCtx = CollectionContext\n>(\n\tdefinition: Omit<JoinCollectionDefinition<L, R, Out, P, Ctx>, 'kind'>\n): JoinCollectionDefinition<L, R, Out, P, Ctx> => ({\n\t...definition,\n\tkind: 'join'\n});\n",
|
|
11
|
+
"import type { AggregateGroup } from './aggregate';\nimport type { CollectionContext } from './collection';\nimport {\n\taggregateOp,\n\tfilterOp,\n\tjoinNode,\n\tmapOp,\n\tmaterialize,\n\torderByOp\n} from './dataflow';\nimport type { Change, JoinNode, Operator } from './dataflow';\nimport type { RowChange, RowKey, ViewDiff } from './types';\n\n/**\n * Declarative incremental queries — the front door to the operator graph. Build a\n * pipeline with {@link query} (`source → filter → map → join → groupBy`); the\n * engine instantiates it per subscription, hydrates each source, and routes each\n * table's changes through the wired operators, emitting result diffs.\n */\n\n/** A table this query reads. */\nexport type GraphSource<Row, P = void, Ctx = CollectionContext> = {\n\ttable: string;\n\thydrate: (params: P, ctx: Ctx) => Promise<Iterable<Row>> | Iterable<Row>;\n\tkey: (row: Row) => RowKey;\n\t/** Scope incremental changes (a row that fails it leaves). */\n\tmatch?: (row: Row, params: P, ctx: Ctx) => boolean;\n};\n\nexport type JoinOptions<Left, Right, Out> = {\n\t/** Join value on the left (current) stream. */\n\ton: (left: Left) => RowKey;\n\t/** Join value on the right source. */\n\trightOn: (right: Right) => RowKey;\n\t/** Combine a matched pair. */\n\tselect: (left: Left, right: Right) => Out;\n\t/**\n\t * Provide to make this a LEFT join: output for a left row with no matching\n\t * right (e.g. a user with zero orders). Omit for an inner join.\n\t */\n\tselectUnmatched?: (left: Left) => Out;\n\t/** Output row identity (unique per pair). */\n\tkey: (out: Out) => RowKey;\n};\n\nexport type GroupByOptions<Row> = {\n\t/** Input row identity (to track contributions). */\n\tkey: (row: Row) => RowKey;\n\tgroupBy?: (row: Row) => RowKey;\n\tvalue?: (row: Row) => number;\n};\n\nexport type OrderByQueryOptions<Row> = {\n\t/** Row identity. */\n\tkey: (row: Row) => RowKey;\n\t/** Sort comparator (ascending). */\n\tcompare: (a: Row, b: Row) => number;\n\t/** Keep at most this many rows (top-N). */\n\tlimit?: number;\n\t/** Skip this many from the front (pagination). */\n\toffset?: number;\n};\n\n/** A live, instantiated graph for one subscription. */\nexport type GraphInstance<Out> = {\n\ttables: string[];\n\thydrate: () => Promise<Out[]>;\n\tapplyChange: (table: string, change: RowChange<unknown>) => ViewDiff<Out>;\n};\n\n// `any` throughout the internals: a graph chains heterogeneously-typed stages;\n// the public Query/GraphSource surface stays fully typed.\ntype AnySource = GraphSource<any, any, any>;\n\ntype Plan = { source: AnySource; steps: AnyStep[] };\n\ntype AnyStep =\n\t| { kind: 'filter'; predicate: (row: any, p: any, ctx: any) => boolean }\n\t| {\n\t\t\tkind: 'map';\n\t\t\ttransform: (row: any) => any;\n\t\t\trekey?: (row: any) => RowKey;\n\t }\n\t// A join's right input is itself a (sub)query plan — a base table is just a\n\t// plan with no steps, so a join can combine two derived streams.\n\t| ({ kind: 'join'; rightPlan: Plan } & JoinOptions<any, any, any>)\n\t| ({ kind: 'aggregate' } & GroupByOptions<any>)\n\t| ({ kind: 'orderBy' } & OrderByQueryOptions<any>);\n\n/** Plan behind each Query, so a Query can be passed as a join's right input. */\nconst PLANS = new WeakMap<object, Plan>();\n\nconst planTables = (plan: Plan): string[] => {\n\tconst tables = [plan.source.table];\n\tfor (const step of plan.steps) {\n\t\tif (step.kind === 'join') {\n\t\t\ttables.push(...planTables(step.rightPlan));\n\t\t}\n\t}\n\treturn [...new Set(tables)];\n};\n\nexport type Query<Row, P = void, Ctx = CollectionContext> = {\n\tfilter: (\n\t\tpredicate: (row: Row, params: P, ctx: Ctx) => boolean\n\t) => Query<Row, P, Ctx>;\n\tmap: <Out>(\n\t\ttransform: (row: Row) => Out,\n\t\trekey?: (row: Out) => RowKey\n\t) => Query<Out, P, Ctx>;\n\tjoin: <Right, Out>(\n\t\tright: GraphSource<Right, P, Ctx> | Query<Right, P, Ctx>,\n\t\toptions: JoinOptions<Row, Right, Out>\n\t) => Query<Out, P, Ctx>;\n\t/**\n\t * LEFT join: like {@link join} but keeps left rows with no match, emitting\n\t * `selectUnmatched(left)` for them (required, so the intent is explicit).\n\t */\n\tleftJoin: <Right, Out>(\n\t\tright: GraphSource<Right, P, Ctx> | Query<Right, P, Ctx>,\n\t\toptions: JoinOptions<Row, Right, Out> & {\n\t\t\tselectUnmatched: (left: Row) => Out;\n\t\t}\n\t) => Query<Out, P, Ctx>;\n\tgroupBy: (options: GroupByOptions<Row>) => Query<AggregateGroup, P, Ctx>;\n\torderBy: (options: OrderByQueryOptions<Row>) => Query<Row, P, Ctx>;\n\t/** Source tables this query reads. */\n\ttables: () => string[];\n\t/** Instantiate the graph for one subscription's params/ctx. */\n\tinstantiate: (params: P, ctx: Ctx) => GraphInstance<Row>;\n};\n\n/** A graph that emits a change stream (no materialization) — recursive: a join's\n * right is itself a StreamGraph, so subqueries nest. */\ntype StreamGraph = {\n\ttables: string[];\n\toutKey: (row: any) => RowKey;\n\thydrateStream: () => Promise<Change<any>[]>;\n\tapplyStream: (table: string, change: RowChange<unknown>) => Change<any>[];\n};\n\ntype Stage =\n\t| { kind: 'op'; op: Operator<any, any> }\n\t| { kind: 'join'; node: JoinNode<any, any, any>; right: StreamGraph };\n\n/** How a table's change enters the graph (root's left, or a join's right input). */\ntype Entry = {\n\tstageIndex: number;\n\tside: 'left' | 'right';\n\tproduce: (change: RowChange<unknown>) => Change<any>[];\n};\n\nconst instantiateStream = (\n\tsource: AnySource,\n\tsteps: AnyStep[],\n\tparams: any,\n\tctx: any\n): StreamGraph => {\n\tconst stages: Stage[] = [];\n\tlet currentKey: (row: any) => RowKey = source.key;\n\n\tfor (const step of steps) {\n\t\tif (step.kind === 'filter') {\n\t\t\tconst predicate = step.predicate;\n\t\t\tstages.push({\n\t\t\t\tkind: 'op',\n\t\t\t\top: filterOp((row) => predicate(row, params, ctx))\n\t\t\t});\n\t\t} else if (step.kind === 'map') {\n\t\t\tstages.push({ kind: 'op', op: mapOp(step.transform, step.rekey) });\n\t\t\tif (step.rekey) {\n\t\t\t\tcurrentKey = step.rekey;\n\t\t\t}\n\t\t} else if (step.kind === 'join') {\n\t\t\tconst right = instantiateStream(\n\t\t\t\tstep.rightPlan.source,\n\t\t\t\tstep.rightPlan.steps,\n\t\t\t\tparams,\n\t\t\t\tctx\n\t\t\t);\n\t\t\tstages.push({\n\t\t\t\tkind: 'join',\n\t\t\t\tnode: joinNode({\n\t\t\t\t\tleftKey: currentKey,\n\t\t\t\t\trightKey: right.outKey,\n\t\t\t\t\tleftOn: step.on,\n\t\t\t\t\trightOn: step.rightOn,\n\t\t\t\t\tselect: step.select,\n\t\t\t\t\tselectUnmatched: step.selectUnmatched,\n\t\t\t\t\tkey: step.key\n\t\t\t\t}),\n\t\t\t\tright\n\t\t\t});\n\t\t\tcurrentKey = step.key;\n\t\t} else if (step.kind === 'aggregate') {\n\t\t\tstages.push({\n\t\t\t\tkind: 'op',\n\t\t\t\top: aggregateOp({\n\t\t\t\t\tkey: step.key,\n\t\t\t\t\tgroupBy: step.groupBy,\n\t\t\t\t\tvalue: step.value\n\t\t\t\t})\n\t\t\t});\n\t\t\tcurrentKey = (group: AggregateGroup) => group.group;\n\t\t} else {\n\t\t\tstages.push({\n\t\t\t\tkind: 'op',\n\t\t\t\top: orderByOp({\n\t\t\t\t\tkey: step.key,\n\t\t\t\t\tcompare: step.compare,\n\t\t\t\t\tlimit: step.limit,\n\t\t\t\t\toffset: step.offset\n\t\t\t\t})\n\t\t\t});\n\t\t\t// orderBy preserves rows (and their identity), just windows them.\n\t\t\tcurrentKey = step.key;\n\t\t}\n\t}\n\n\tconst propagate = (\n\t\tchanges: Change<any>[],\n\t\tfromStage: number,\n\t\tside: 'left' | 'right'\n\t): Change<any>[] => {\n\t\tlet cs = changes;\n\t\tfor (let i = fromStage; i < stages.length; i += 1) {\n\t\t\tconst stage = stages[i]!;\n\t\t\tif (stage.kind === 'join') {\n\t\t\t\tcs =\n\t\t\t\t\ti === fromStage && side === 'right'\n\t\t\t\t\t\t? stage.node.pushRight(cs)\n\t\t\t\t\t\t: stage.node.pushLeft(cs);\n\t\t\t} else {\n\t\t\t\tcs = stage.op.push(cs);\n\t\t\t}\n\t\t}\n\t\treturn cs;\n\t};\n\n\tconst sourceChange = (change: RowChange<unknown>): Change<any> => {\n\t\tconst key = source.key(change.row);\n\t\tif (\n\t\t\tchange.op === 'delete' ||\n\t\t\t(source.match !== undefined &&\n\t\t\t\t!source.match(change.row, params, ctx))\n\t\t) {\n\t\t\treturn { op: 'delete', key, row: change.row };\n\t\t}\n\t\treturn { op: 'upsert', key, row: change.row };\n\t};\n\n\tconst entries = new Map<string, Entry[]>();\n\tconst addEntry = (table: string, entry: Entry) => {\n\t\tconst list = entries.get(table);\n\t\tif (list === undefined) {\n\t\t\tentries.set(table, [entry]);\n\t\t} else {\n\t\t\tlist.push(entry);\n\t\t}\n\t};\n\t// The root source feeds the left of stage 0.\n\taddEntry(source.table, {\n\t\tstageIndex: 0,\n\t\tside: 'left',\n\t\tproduce: (change) => [sourceChange(change)]\n\t});\n\t// Each join's right subgraph feeds that join's right; route every sub-table.\n\tstages.forEach((stage, index) => {\n\t\tif (stage.kind === 'join') {\n\t\t\tfor (const table of stage.right.tables) {\n\t\t\t\taddEntry(table, {\n\t\t\t\t\tstageIndex: index,\n\t\t\t\t\tside: 'right',\n\t\t\t\t\tproduce: (change) =>\n\t\t\t\t\t\t(stage as { right: StreamGraph }).right.applyStream(\n\t\t\t\t\t\t\ttable,\n\t\t\t\t\t\t\tchange\n\t\t\t\t\t\t)\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t});\n\n\treturn {\n\t\ttables: planTables({ source, steps }),\n\t\toutKey: currentKey,\n\t\thydrateStream: async () => {\n\t\t\t// Prime each join's right (recursively hydrating its subgraph) first.\n\t\t\tfor (let i = 0; i < stages.length; i += 1) {\n\t\t\t\tconst stage = stages[i]!;\n\t\t\t\tif (stage.kind === 'join') {\n\t\t\t\t\tpropagate(await stage.right.hydrateStream(), i, 'right');\n\t\t\t\t}\n\t\t\t}\n\t\t\tconst rootRows = [...(await source.hydrate(params, ctx))];\n\t\t\treturn propagate(\n\t\t\t\trootRows.map((row) => ({\n\t\t\t\t\top: 'upsert' as const,\n\t\t\t\t\tkey: source.key(row),\n\t\t\t\t\trow\n\t\t\t\t})),\n\t\t\t\t0,\n\t\t\t\t'left'\n\t\t\t);\n\t\t},\n\t\tapplyStream: (table, change) => {\n\t\t\tconst list = entries.get(table);\n\t\t\tif (list === undefined) {\n\t\t\t\treturn [];\n\t\t\t}\n\t\t\tconst out: Change<any>[] = [];\n\t\t\tfor (const entry of list) {\n\t\t\t\tout.push(\n\t\t\t\t\t...propagate(\n\t\t\t\t\t\tentry.produce(change),\n\t\t\t\t\t\tentry.stageIndex,\n\t\t\t\t\t\tentry.side\n\t\t\t\t\t)\n\t\t\t\t);\n\t\t\t}\n\t\t\treturn out;\n\t\t}\n\t};\n};\n\nconst instantiate = (\n\tsource: AnySource,\n\tsteps: AnyStep[],\n\tparams: any,\n\tctx: any\n): GraphInstance<any> => {\n\tconst graph = instantiateStream(source, steps, params, ctx);\n\tconst sink = materialize<any>(graph.outKey);\n\treturn {\n\t\ttables: graph.tables,\n\t\thydrate: async () => {\n\t\t\tsink.apply(await graph.hydrateStream());\n\t\t\treturn sink.rows();\n\t\t},\n\t\tapplyChange: (table, change) =>\n\t\t\tsink.apply(graph.applyStream(table, change))\n\t};\n};\n\nconst makeQuery = <Row, P, Ctx>(\n\tsource: AnySource,\n\tsteps: AnyStep[]\n): Query<Row, P, Ctx> => {\n\t// `right` is a base source or a sub-Query; normalize to a plan, then append a\n\t// join step (inner or left — `selectUnmatched` in `options` decides).\n\tconst addJoin = <Right, Out>(\n\t\tright: GraphSource<Right, P, Ctx> | Query<Right, P, Ctx>,\n\t\toptions: JoinOptions<Row, Right, Out>\n\t): Query<Out, P, Ctx> => {\n\t\tconst rightPlan = PLANS.get(right as object) ?? {\n\t\t\tsource: right as AnySource,\n\t\t\tsteps: []\n\t\t};\n\t\treturn makeQuery(source, [\n\t\t\t...steps,\n\t\t\t{ kind: 'join', rightPlan, ...options }\n\t\t]);\n\t};\n\tconst queryInstance: Query<Row, P, Ctx> = {\n\t\tfilter: (predicate) =>\n\t\t\tmakeQuery(source, [...steps, { kind: 'filter', predicate }]),\n\t\tmap: (transform, rekey) =>\n\t\t\tmakeQuery(source, [...steps, { kind: 'map', transform, rekey }]),\n\t\tjoin: (right, options) => addJoin(right, options),\n\t\tleftJoin: (right, options) => addJoin(right, options),\n\t\tgroupBy: (options) =>\n\t\t\tmakeQuery(source, [...steps, { kind: 'aggregate', ...options }]),\n\t\torderBy: (options) =>\n\t\t\tmakeQuery(source, [...steps, { kind: 'orderBy', ...options }]),\n\t\ttables: () => planTables({ source, steps }),\n\t\tinstantiate: (params, ctx) =>\n\t\t\tinstantiate(source, steps, params, ctx) as GraphInstance<Row>\n\t};\n\tPLANS.set(queryInstance, { source, steps });\n\treturn queryInstance;\n};\n\n/** Start a query from a source table. */\nexport const query = <Row, P = void, Ctx = CollectionContext>(\n\tsource: GraphSource<Row, P, Ctx>\n): Query<Row, P, Ctx> => makeQuery(source, []);\n\n/** A collection backed by an incremental operator graph (see {@link query}). */\nexport type GraphCollectionDefinition<\n\tOut,\n\tP = void,\n\tCtx = CollectionContext\n> = {\n\tname: string;\n\tkind: 'graph';\n\tquery: Query<Out, P, Ctx>;\n\tauthorize?: (params: P, ctx: Ctx) => boolean | Promise<boolean>;\n\t/** Output row identity (used by the engine/transport to key result rows). */\n\tkey: (out: Out) => RowKey;\n};\n\n/** Define a collection whose result is maintained by an operator graph. */\nexport const defineGraphCollection = <Out, P = void, Ctx = CollectionContext>(\n\tdefinition: Omit<GraphCollectionDefinition<Out, P, Ctx>, 'kind'>\n): GraphCollectionDefinition<Out, P, Ctx> => ({ ...definition, kind: 'graph' });\n",
|
|
12
|
+
"import type { CollectionContext } from './collection';\nimport type { RowChange } from './types';\n\n/**\n * Actions a mutation handler uses to publish what it changed. Call `change`\n * after each durable write so live views update and subscribers (including the\n * caller) receive the authoritative diff.\n */\nexport type MutationActions = {\n\tchange: <T>(collection: string, change: RowChange<T>) => Promise<void>;\n};\n\nexport type MutationHandler<Args, Ctx, Result> = (\n\targs: Args,\n\tctx: Ctx,\n\tactions: MutationActions\n) => Promise<Result> | Result;\n\nexport type MutationDefinition<\n\tArgs = unknown,\n\tCtx = CollectionContext,\n\tResult = unknown\n> = {\n\t/** Mutation name the client invokes. */\n\tname: string;\n\t/** Access control: return false (or throw) to reject the mutation. */\n\tauthorize?: (args: Args, ctx: Ctx) => boolean | Promise<boolean>;\n\t/**\n\t * Apply the mutation: write to your durable store, then call\n\t * `actions.change(...)` for each affected row. Return value (e.g. the created\n\t * record) is sent back to the caller in the ack.\n\t */\n\thandler: MutationHandler<Args, Ctx, Result>;\n};\n\n/**\n * Define a server mutation. Identity at runtime — it exists for type inference\n * (args/ctx/result flow through). Register it with {@link SyncEngine.registerMutation}\n * and invoke it from the client; the engine authorizes, runs the handler, fans\n * the resulting diffs to subscribers, and acks the caller.\n */\nexport const defineMutation = <\n\tArgs = unknown,\n\tCtx = CollectionContext,\n\tResult = unknown\n>(\n\tdefinition: MutationDefinition<Args, Ctx, Result>\n): MutationDefinition<Args, Ctx, Result> => definition;\n",
|
|
13
|
+
"import type {\n\tCollectionContext,\n\tCollectionDefinition,\n\tJoinCollectionDefinition\n} from './collection';\nimport { createEquiJoin } from './equiJoin';\nimport type { EquiJoin } from './equiJoin';\nimport type { GraphCollectionDefinition, GraphInstance } from './graph';\nimport { createMaterializedView, isEmptyViewDiff } from './materializedView';\nimport type { MaterializedView } from './materializedView';\nimport type { MutationDefinition } from './mutation';\nimport type { ChangeSource, RowChange, RowKey, ViewDiff } from './types';\n\n/**\n * Thrown when `authorize` denies a subscribe or a mutation. The message names\n * the denied action; the message always starts with \"Not authorized\".\n */\nexport class UnauthorizedError extends Error {\n\tconstructor(subject: string) {\n\t\tsuper(`Not authorized: ${subject}`);\n\t\tthis.name = 'UnauthorizedError';\n\t}\n}\n\nexport type SubscribeArgs<T, P, Ctx> = {\n\t/** Registered collection name. */\n\tcollection: string;\n\t/** Query params (e.g. a filter value); passed to hydrate/match/authorize. */\n\tparams: P;\n\t/** Caller context (e.g. session); passed to hydrate/match/authorize. */\n\tctx: Ctx;\n\t/** Receives every non-empty diff (with its version) after the initial reply. */\n\tonDiff: (diff: ViewDiff<T>, version: number) => void;\n\t/**\n\t * Resume from a version the client already applied. When the change log still\n\t * covers `(since, now]` for a single-table collection, the engine replies with\n\t * a catch-up diff instead of a full snapshot; otherwise it falls back to a\n\t * snapshot.\n\t */\n\tsince?: number;\n};\n\nexport type Subscription<T> = {\n\t/** The result set at subscribe time — a snapshot (empty when resuming). */\n\tinitial: T[];\n\t/** Catch-up diff when resuming via `since` (instead of `initial`). */\n\tcatchup?: ViewDiff<T>;\n\t/** The engine version this reply brings the client up to. */\n\tversion: number;\n\t/** Stop receiving diffs and release the view. */\n\tunsubscribe: () => void;\n};\n\nexport type SyncEngine = {\n\t/** Register a collection definition (see {@link defineCollection}). */\n\tregister: <T, P = void, Ctx = CollectionContext>(\n\t\tcollection: CollectionDefinition<T, P, Ctx>\n\t) => void;\n\t/** Register an incremental join collection (see {@link defineJoinCollection}). */\n\tregisterJoin: <L, R, Out, P = void, Ctx = CollectionContext>(\n\t\tcollection: JoinCollectionDefinition<L, R, Out, P, Ctx>\n\t) => void;\n\t/** Register an operator-graph collection (see {@link defineGraphCollection}). */\n\tregisterGraph: <Out, P = void, Ctx = CollectionContext>(\n\t\tcollection: GraphCollectionDefinition<Out, P, Ctx>\n\t) => void;\n\t/**\n\t * Open a live subscription: authorize, hydrate the initial set, and stream\n\t * diffs as changes arrive. Rejects with {@link UnauthorizedError} on deny.\n\t */\n\tsubscribe: <T, P = void, Ctx = CollectionContext>(\n\t\targs: SubscribeArgs<T, P, Ctx>\n\t) => Promise<Subscription<T>>;\n\t/**\n\t * One-shot read: authorize and return a collection's current rows without\n\t * subscribing. Powers an Eden-typed HTTP hydrate route (and SSR). Rejects\n\t * with {@link UnauthorizedError} on deny.\n\t */\n\thydrate: (\n\t\tcollection: string,\n\t\tparams: unknown,\n\t\tctx: unknown\n\t) => Promise<unknown[]>;\n\t/**\n\t * Feed a committed change to `table` into the engine, fanning the resulting\n\t * diff to every live subscription of every collection that reads that table.\n\t * Call after a mutation, or wire a {@link ChangeSource} via `connectSource`.\n\t * Single-table subscriptions diff the row; multi-table / refetch ones\n\t * re-hydrate.\n\t */\n\tapplyChange: <T>(table: string, change: RowChange<T>) => Promise<void>;\n\t/**\n\t * Connect a change source (e.g. a CDC adapter): its emitted changes flow into\n\t * `applyChange`. Resolves to a disconnect function that stops the source.\n\t */\n\tconnectSource: (source: ChangeSource) => Promise<() => Promise<void>>;\n\t/** Active subscription count, optionally for one collection. */\n\tsubscriptionCount: (collection?: string) => number;\n\t/** Register a mutation definition (see {@link defineMutation}). */\n\tregisterMutation: <Args, Ctx = CollectionContext, Result = unknown>(\n\t\tmutation: MutationDefinition<Args, Ctx, Result>\n\t) => void;\n\t/**\n\t * Run a registered mutation: authorize, invoke its handler (which writes and\n\t * emits changes via `applyChange`), and resolve with the handler's result.\n\t * Rejects with {@link UnauthorizedError} on deny, or an error for an unknown\n\t * mutation / a handler throw. Drive this from the transport's mutate frame.\n\t */\n\trunMutation: (\n\t\tname: string,\n\t\targs: unknown,\n\t\tctx: unknown\n\t) => Promise<unknown>;\n};\n\ntype OnDiff = (diff: ViewDiff<unknown>, version: number) => void;\n\ntype JoinState = {\n\top: EquiJoin<unknown, unknown, unknown>;\n\tleftTable: string;\n\trightTable: string;\n\t/** Per-side filters (bound to params/ctx) — a failing change leaves the join. */\n\tleftMatch?: (row: unknown) => boolean;\n\trightMatch?: (row: unknown) => boolean;\n};\n\ntype ActiveSubscription =\n\t| {\n\t\t\tkind: 'view';\n\t\t\tcollection: string;\n\t\t\tview: MaterializedView<unknown>;\n\t\t\t/** Incremental (has a predicate) vs refetch fallback. */\n\t\t\tincremental: boolean;\n\t\t\t/** Re-run the bound hydrate for the refetch fallback. */\n\t\t\trehydrate: () => Promise<Iterable<unknown>>;\n\t\t\tonDiff: OnDiff;\n\t }\n\t| {\n\t\t\tkind: 'join';\n\t\t\tcollection: string;\n\t\t\tjoin: JoinState;\n\t\t\tonDiff: OnDiff;\n\t }\n\t| {\n\t\t\tkind: 'graph';\n\t\t\tcollection: string;\n\t\t\tinstance: GraphInstance<unknown>;\n\t\t\tonDiff: OnDiff;\n\t };\n\ntype LoggedChange = {\n\tversion: number;\n\ttable: string;\n\tchange: RowChange<unknown>;\n};\n\nexport type SyncEngineOptions = {\n\t/**\n\t * How many recent changes to retain for resumable reconnects. A client that\n\t * reconnects within this window gets a catch-up diff; beyond it, a fresh\n\t * snapshot. Defaults to 1024.\n\t */\n\tchangeLogSize?: number;\n};\n\nconst defaultKey = (row: unknown): RowKey => (row as { id: RowKey }).id;\n\n/**\n * The Tier 3 sync engine: a registry of collections plus the view syncer. It is\n * transport-agnostic — `subscribe` returns the initial snapshot and an\n * `onDiff` stream, which an Elysia/SSE layer wires to a connection, and\n * `applyChange` is the change feed you drive from your mutations.\n *\n * Access control is first-class: every subscribe runs the collection's\n * `authorize`, and a collection's `match`/`hydrate` scope rows to the caller, so\n * a change to a row the caller can't see never reaches them.\n */\nexport const createSyncEngine = (\n\toptions: SyncEngineOptions = {}\n): SyncEngine => {\n\t// Heterogeneous registry: `any` here is what lets collections of different\n\t// row/param/context types share one map (the public `register`/`subscribe`\n\t// surface stays fully typed).\n\tconst registry = new Map<\n\t\tstring,\n\t\t| CollectionDefinition<any, any, any>\n\t\t| JoinCollectionDefinition<any, any, any, any, any>\n\t\t| GraphCollectionDefinition<any, any, any>\n\t>();\n\tconst mutations = new Map<string, MutationDefinition<any, any, any>>();\n\tconst active = new Map<string, Set<ActiveSubscription>>();\n\t// Which collections read each table — so a table change fans to all of them.\n\tconst tableIndex = new Map<string, Set<string>>();\n\n\t// Monotonic change feed: every applyChange bumps `version` and appends to a\n\t// bounded log, so a client can resume from the version it last applied.\n\tconst changeLogSize = options.changeLogSize ?? 1024;\n\tconst changeLog: LoggedChange[] = [];\n\tlet version = 0;\n\n\tconst subsFor = (collection: string) => {\n\t\tlet set = active.get(collection);\n\t\tif (set === undefined) {\n\t\t\tset = new Set();\n\t\t\tactive.set(collection, set);\n\t\t}\n\t\treturn set;\n\t};\n\n\tconst addTableIndex = (table: string, name: string) => {\n\t\tlet set = tableIndex.get(table);\n\t\tif (set === undefined) {\n\t\t\tset = new Set();\n\t\t\ttableIndex.set(table, set);\n\t\t}\n\t\tset.add(name);\n\t};\n\n\t/** A side change that fails its filter becomes a leave (delete from the join). */\n\tconst sideChange = (\n\t\tchange: RowChange<unknown>,\n\t\tmatch?: (row: unknown) => boolean\n\t): RowChange<unknown> =>\n\t\tchange.op !== 'delete' && match !== undefined && !match(change.row)\n\t\t\t? { op: 'delete', row: change.row }\n\t\t\t: change;\n\n\tconst applyToSubscription = async (\n\t\tsubscription: ActiveSubscription,\n\t\ttable: string,\n\t\tchange: RowChange<unknown>,\n\t\tchangeVersion: number\n\t) => {\n\t\tlet diff: ViewDiff<unknown>;\n\t\tif (subscription.kind === 'graph') {\n\t\t\tdiff = subscription.instance.applyChange(table, change);\n\t\t} else if (subscription.kind === 'join') {\n\t\t\tconst js = subscription.join;\n\t\t\tif (table === js.leftTable) {\n\t\t\t\tdiff = js.op.applyLeft(sideChange(change, js.leftMatch));\n\t\t\t} else if (table === js.rightTable) {\n\t\t\t\tdiff = js.op.applyRight(sideChange(change, js.rightMatch));\n\t\t\t} else {\n\t\t\t\treturn;\n\t\t\t}\n\t\t} else if (subscription.incremental) {\n\t\t\ttry {\n\t\t\t\tdiff = subscription.view.apply(change);\n\t\t\t} catch {\n\t\t\t\t// The predicate couldn't decide this change (e.g. an operator the\n\t\t\t\t// inferred matcher doesn't support) — degrade to a correct refetch\n\t\t\t\t// rather than a wrong diff.\n\t\t\t\tdiff = subscription.view.reset(await subscription.rehydrate());\n\t\t\t}\n\t\t} else {\n\t\t\tdiff = subscription.view.reset(await subscription.rehydrate());\n\t\t}\n\t\tif (!isEmptyViewDiff(diff)) {\n\t\t\tsubscription.onDiff(diff, changeVersion);\n\t\t}\n\t};\n\n\tconst applyChange = async (table: string, change: RowChange<unknown>) => {\n\t\tversion += 1;\n\t\tchangeLog.push({ version, table, change });\n\t\tif (changeLog.length > changeLogSize) {\n\t\t\tchangeLog.shift();\n\t\t}\n\t\tconst changeVersion = version;\n\t\tconst collectionNames = tableIndex.get(table);\n\t\tif (collectionNames === undefined) {\n\t\t\treturn;\n\t\t}\n\t\tfor (const name of collectionNames) {\n\t\t\tconst set = active.get(name);\n\t\t\tif (set === undefined || set.size === 0) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tfor (const subscription of set) {\n\t\t\t\tawait applyToSubscription(\n\t\t\t\t\tsubscription,\n\t\t\t\t\ttable,\n\t\t\t\t\tchange,\n\t\t\t\t\tchangeVersion\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t};\n\n\t/**\n\t * Can we replay `(since, now]` from the log for `tables`? Only when the log\n\t * hasn't been trimmed past `since` (no gap).\n\t */\n\tconst canResume = (since: number, incremental: boolean): boolean => {\n\t\tif (!incremental) {\n\t\t\treturn false; // refetch/join subs can't be replayed precisely\n\t\t}\n\t\tif (since >= version) {\n\t\t\treturn true; // nothing newer to replay\n\t\t}\n\t\tconst oldest = changeLog[0];\n\t\treturn oldest !== undefined && oldest.version <= since + 1;\n\t};\n\n\t/** Build a catch-up diff from the log for one subscription (last op per key wins). */\n\tconst buildCatchup = (\n\t\tsince: number,\n\t\ttables: string[],\n\t\tkey: (row: unknown) => RowKey,\n\t\tmatch: (row: unknown) => boolean\n\t): ViewDiff<unknown> => {\n\t\tconst latest = new Map<\n\t\t\tRowKey,\n\t\t\t{ op: 'upsert' | 'remove'; row: unknown }\n\t\t>();\n\t\tfor (const entry of changeLog) {\n\t\t\tif (entry.version <= since || !tables.includes(entry.table)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst row = entry.change.row;\n\t\t\tconst present =\n\t\t\t\tentry.change.op !== 'delete' && match(row)\n\t\t\t\t\t? 'upsert'\n\t\t\t\t\t: 'remove';\n\t\t\tlatest.set(key(row), { op: present, row });\n\t\t}\n\t\tconst changed: unknown[] = [];\n\t\tconst removed: unknown[] = [];\n\t\tfor (const { op, row } of latest.values()) {\n\t\t\t(op === 'upsert' ? changed : removed).push(row);\n\t\t}\n\t\treturn { added: [], removed, changed };\n\t};\n\n\tconst subscribeJoin = async (\n\t\tcollection: string,\n\t\tdefinition: JoinCollectionDefinition<\n\t\t\tunknown,\n\t\t\tunknown,\n\t\t\tunknown,\n\t\t\tunknown,\n\t\t\tunknown\n\t\t>,\n\t\tparams: unknown,\n\t\tctx: unknown,\n\t\tonDiff: OnDiff,\n\t\tset: Set<ActiveSubscription>\n\t): Promise<Subscription<unknown>> => {\n\t\tif (definition.authorize !== undefined) {\n\t\t\tconst allowed = await definition.authorize(params, ctx);\n\t\t\tif (!allowed) {\n\t\t\t\tthrow new UnauthorizedError(\n\t\t\t\t\t`subscribe to collection \"${collection}\"`\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t\tconst { left, right } = definition;\n\t\tconst op = createEquiJoin<unknown, unknown, unknown>({\n\t\t\tleftKey: left.key,\n\t\t\trightKey: right.key,\n\t\t\tleftOn: left.on,\n\t\t\trightOn: right.on,\n\t\t\tselect: definition.select\n\t\t});\n\t\top.hydrate(\n\t\t\t[...(await left.hydrate(params, ctx))],\n\t\t\t[...(await right.hydrate(params, ctx))]\n\t\t);\n\t\tconst atVersion = version;\n\n\t\tconst subscription: ActiveSubscription = {\n\t\t\tkind: 'join',\n\t\t\tcollection,\n\t\t\tjoin: {\n\t\t\t\top,\n\t\t\t\tleftTable: left.table,\n\t\t\t\trightTable: right.table,\n\t\t\t\tleftMatch: left.match\n\t\t\t\t\t? (row) => left.match!(row, params, ctx)\n\t\t\t\t\t: undefined,\n\t\t\t\trightMatch: right.match\n\t\t\t\t\t? (row) => right.match!(row, params, ctx)\n\t\t\t\t\t: undefined\n\t\t\t},\n\t\t\tonDiff\n\t\t};\n\t\tset.add(subscription);\n\n\t\treturn {\n\t\t\tinitial: op.rows(),\n\t\t\tversion: atVersion,\n\t\t\tunsubscribe: () => {\n\t\t\t\tset.delete(subscription);\n\t\t\t}\n\t\t};\n\t};\n\n\tconst subscribeGraph = async (\n\t\tcollection: string,\n\t\tdefinition: GraphCollectionDefinition<unknown, unknown, unknown>,\n\t\tparams: unknown,\n\t\tctx: unknown,\n\t\tonDiff: OnDiff,\n\t\tset: Set<ActiveSubscription>\n\t): Promise<Subscription<unknown>> => {\n\t\tif (definition.authorize !== undefined) {\n\t\t\tconst allowed = await definition.authorize(params, ctx);\n\t\t\tif (!allowed) {\n\t\t\t\tthrow new UnauthorizedError(\n\t\t\t\t\t`subscribe to collection \"${collection}\"`\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t\tconst instance = definition.query.instantiate(params, ctx);\n\t\tconst initial = await instance.hydrate();\n\t\tconst atVersion = version;\n\t\tconst subscription: ActiveSubscription = {\n\t\t\tkind: 'graph',\n\t\t\tcollection,\n\t\t\tinstance,\n\t\t\tonDiff\n\t\t};\n\t\tset.add(subscription);\n\t\treturn {\n\t\t\tinitial,\n\t\t\tversion: atVersion,\n\t\t\tunsubscribe: () => {\n\t\t\t\tset.delete(subscription);\n\t\t\t}\n\t\t};\n\t};\n\n\treturn {\n\t\tregister: (collection) => {\n\t\t\tregistry.set(collection.name, collection);\n\t\t\tfor (const table of collection.tables ?? [collection.name]) {\n\t\t\t\taddTableIndex(table, collection.name);\n\t\t\t}\n\t\t},\n\n\t\tregisterJoin: (collection) => {\n\t\t\tregistry.set(collection.name, collection);\n\t\t\taddTableIndex(collection.left.table, collection.name);\n\t\t\taddTableIndex(collection.right.table, collection.name);\n\t\t},\n\n\t\tregisterGraph: (collection) => {\n\t\t\tregistry.set(collection.name, collection);\n\t\t\tfor (const table of collection.query.tables()) {\n\t\t\t\taddTableIndex(table, collection.name);\n\t\t\t}\n\t\t},\n\n\t\tsubscribe: async ({ collection, params, ctx, onDiff, since }) => {\n\t\t\tconst registered = registry.get(collection);\n\t\t\tif (registered === undefined) {\n\t\t\t\tthrow new Error(`Unknown collection \"${collection}\"`);\n\t\t\t}\n\n\t\t\tconst typedOnDiff = onDiff as OnDiff;\n\t\t\tconst subscribeSet = subsFor(collection);\n\n\t\t\tconst registeredKind = (registered as { kind?: string }).kind;\n\t\t\tif (registeredKind === 'join') {\n\t\t\t\tconst joined = await subscribeJoin(\n\t\t\t\t\tcollection,\n\t\t\t\t\tregistered as JoinCollectionDefinition<\n\t\t\t\t\t\tunknown,\n\t\t\t\t\t\tunknown,\n\t\t\t\t\t\tunknown,\n\t\t\t\t\t\tunknown,\n\t\t\t\t\t\tunknown\n\t\t\t\t\t>,\n\t\t\t\t\tparams,\n\t\t\t\t\tctx,\n\t\t\t\t\ttypedOnDiff,\n\t\t\t\t\tsubscribeSet\n\t\t\t\t);\n\t\t\t\treturn joined as Subscription<never>;\n\t\t\t}\n\t\t\tif (registeredKind === 'graph') {\n\t\t\t\tconst graphed = await subscribeGraph(\n\t\t\t\t\tcollection,\n\t\t\t\t\tregistered as GraphCollectionDefinition<\n\t\t\t\t\t\tunknown,\n\t\t\t\t\t\tunknown,\n\t\t\t\t\t\tunknown\n\t\t\t\t\t>,\n\t\t\t\t\tparams,\n\t\t\t\t\tctx,\n\t\t\t\t\ttypedOnDiff,\n\t\t\t\t\tsubscribeSet\n\t\t\t\t);\n\t\t\t\treturn graphed as Subscription<never>;\n\t\t\t}\n\t\t\tconst definition = registered as CollectionDefinition<\n\t\t\t\tunknown,\n\t\t\t\tunknown,\n\t\t\t\tunknown\n\t\t\t>;\n\n\t\t\tif (definition.authorize !== undefined) {\n\t\t\t\tconst allowed = await definition.authorize(params, ctx);\n\t\t\t\tif (!allowed) {\n\t\t\t\t\tthrow new UnauthorizedError(\n\t\t\t\t\t\t`subscribe to collection \"${collection}\"`\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst key = definition.key ?? defaultKey;\n\t\t\tconst rehydrate = async () => definition.hydrate(params, ctx);\n\t\t\tconst match = definition.match;\n\t\t\tconst tables = definition.tables ?? [collection];\n\t\t\t// Incremental matching only applies to single-table collections; a\n\t\t\t// join/aggregate spanning tables can't match a single row, so it uses\n\t\t\t// the refetch fallback.\n\t\t\tconst incremental = match !== undefined && tables.length === 1;\n\t\t\tconst boundMatch = incremental\n\t\t\t\t? (row: unknown) => match(row, params, ctx)\n\t\t\t\t: () => true;\n\t\t\tconst view = createMaterializedView<unknown>({\n\t\t\t\tkey,\n\t\t\t\tmatch: boundMatch\n\t\t\t});\n\n\t\t\t// Resume from the log when possible (catch-up diff); else send a\n\t\t\t// snapshot. The view is hydrated either way so future changes match.\n\t\t\tconst resuming =\n\t\t\t\tsince !== undefined && canResume(since, incremental);\n\t\t\tview.hydrate([...(await rehydrate())]);\n\t\t\tconst atVersion = version;\n\n\t\t\tconst subscription: ActiveSubscription = {\n\t\t\t\tkind: 'view',\n\t\t\t\tcollection,\n\t\t\t\tview,\n\t\t\t\tincremental,\n\t\t\t\trehydrate,\n\t\t\t\tonDiff: typedOnDiff\n\t\t\t};\n\t\t\tsubscribeSet.add(subscription);\n\n\t\t\tconst unsubscribe = () => {\n\t\t\t\tsubscribeSet.delete(subscription);\n\t\t\t};\n\n\t\t\tif (resuming) {\n\t\t\t\treturn {\n\t\t\t\t\tinitial: [],\n\t\t\t\t\tcatchup: buildCatchup(\n\t\t\t\t\t\tsince,\n\t\t\t\t\t\ttables,\n\t\t\t\t\t\tkey,\n\t\t\t\t\t\tboundMatch\n\t\t\t\t\t) as ViewDiff<never>,\n\t\t\t\t\tversion: atVersion,\n\t\t\t\t\tunsubscribe\n\t\t\t\t};\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tinitial: view.rows() as never[],\n\t\t\t\tversion: atVersion,\n\t\t\t\tunsubscribe\n\t\t\t};\n\t\t},\n\n\t\thydrate: async (collection, params, ctx) => {\n\t\t\tconst definition = registry.get(collection) as\n\t\t\t\t| CollectionDefinition<unknown, unknown, unknown>\n\t\t\t\t| undefined;\n\t\t\tif (definition === undefined) {\n\t\t\t\tthrow new Error(`Unknown collection \"${collection}\"`);\n\t\t\t}\n\t\t\tif (definition.authorize !== undefined) {\n\t\t\t\tconst allowed = await definition.authorize(params, ctx);\n\t\t\t\tif (!allowed) {\n\t\t\t\t\tthrow new UnauthorizedError(\n\t\t\t\t\t\t`hydrate collection \"${collection}\"`\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn [...(await definition.hydrate(params, ctx))];\n\t\t},\n\n\t\tapplyChange: (table, change) =>\n\t\t\tapplyChange(table, change as RowChange<unknown>),\n\n\t\tconnectSource: async (source) => {\n\t\t\tawait source.start((table, change) => applyChange(table, change));\n\t\t\treturn async () => {\n\t\t\t\tawait source.stop();\n\t\t\t};\n\t\t},\n\n\t\tsubscriptionCount: (collection) => {\n\t\t\tif (collection !== undefined) {\n\t\t\t\treturn active.get(collection)?.size ?? 0;\n\t\t\t}\n\t\t\tlet total = 0;\n\t\t\tfor (const set of active.values()) {\n\t\t\t\ttotal += set.size;\n\t\t\t}\n\t\t\treturn total;\n\t\t},\n\n\t\tregisterMutation: (mutation) => {\n\t\t\tmutations.set(mutation.name, mutation);\n\t\t},\n\n\t\trunMutation: async (name, args, ctx) => {\n\t\t\tconst mutation = mutations.get(name);\n\t\t\tif (mutation === undefined) {\n\t\t\t\tthrow new Error(`Unknown mutation \"${name}\"`);\n\t\t\t}\n\t\t\tif (mutation.authorize !== undefined) {\n\t\t\t\tconst allowed = await mutation.authorize(args, ctx);\n\t\t\t\tif (!allowed) {\n\t\t\t\t\tthrow new UnauthorizedError(`run mutation \"${name}\"`);\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn mutation.handler(args, ctx, {\n\t\t\t\tchange: (collection, change) =>\n\t\t\t\t\tapplyChange(collection, change as RowChange<unknown>)\n\t\t\t});\n\t\t}\n\t};\n};\n",
|
|
14
|
+
"import type { CollectionDefinition } from './collection';\nimport type { MutationDefinition } from './mutation';\nimport type { SyncEngine } from './syncEngine';\n\n/**\n * Eden-native HTTP route helpers (Tier 3). These turn a typed collection /\n * mutation definition into a plain Elysia route handler — so hydrate and mutate\n * are ordinary Elysia routes that Eden types end to end, with TypeBox validating\n * the params/body. The live diff stream stays on the WebSocket (`syncSocket`).\n *\n * They import no Elysia: each returns `(context) => Promise<...>`, which you pass\n * to `.get` / `.post` with a TypeBox `query` / `body` schema. The handler's\n * return type carries the row / result type, so `treaty<typeof app>()` infers it.\n */\n\n/** The slice of an Elysia route context these helpers read. */\nexport type SyncRouteContext = {\n\tquery: unknown;\n\tbody: unknown;\n\t[key: string]: unknown;\n};\n\nconst emptyContext = () => ({});\n\n/**\n * Build a GET handler that hydrates `collection` — authorize, then return its\n * current rows. The handler returns `Promise<Row[]>`, so Eden infers the row\n * type from the collection definition; the route's TypeBox `query` schema\n * validates and types the params.\n *\n * @example\n * .get('/sync/orders', hydrateRoute(engine, ordersCollection, (c) => ({ userId: c.userId })),\n * { query: t.Object({ userId: t.Numeric() }) })\n */\nexport const hydrateRoute = <Row, Params, Ctx>(\n\tengine: SyncEngine,\n\tcollection: CollectionDefinition<Row, Params, Ctx>,\n\tresolveContext: (\n\t\tcontext: SyncRouteContext\n\t) => Ctx = emptyContext as () => Ctx\n) => {\n\treturn async (context: SyncRouteContext): Promise<Row[]> => {\n\t\tconst rows = await engine.hydrate(\n\t\t\tcollection.name,\n\t\t\tcontext.query as Params,\n\t\t\tresolveContext(context)\n\t\t);\n\t\treturn rows as Row[];\n\t};\n};\n\n/**\n * Build a POST handler that runs `mutation`. The handler returns\n * `Promise<Result>`, so Eden infers the result type; the route's TypeBox `body`\n * schema validates and types the args.\n *\n * @example\n * .post('/sync/createOrder', mutateRoute(engine, createOrder, (c) => ({ userId: c.userId })),\n * { body: t.Object({ total: t.Number() }) })\n */\nexport const mutateRoute = <Args, Ctx, Result>(\n\tengine: SyncEngine,\n\tmutation: MutationDefinition<Args, Ctx, Result>,\n\tresolveContext: (\n\t\tcontext: SyncRouteContext\n\t) => Ctx = emptyContext as () => Ctx\n) => {\n\treturn async (context: SyncRouteContext): Promise<Result> => {\n\t\tconst result = await engine.runMutation(\n\t\t\tmutation.name,\n\t\t\tcontext.body as Args,\n\t\t\tresolveContext(context)\n\t\t);\n\t\treturn result as Result;\n\t};\n};\n",
|
|
15
|
+
"import type { Subscription, SyncEngine } from './syncEngine';\n\n/**\n * Wire protocol for the sync-engine WebSocket. One connection multiplexes many\n * collection subscriptions, each tagged with a client-chosen `id`.\n */\n\n/** Client → server. */\nexport type ClientFrame =\n\t| {\n\t\t\ttype: 'subscribe';\n\t\t\tid: string;\n\t\t\tcollection: string;\n\t\t\tparams?: unknown;\n\t\t\t/** Resume from a version already applied (catch-up instead of snapshot). */\n\t\t\tsince?: number;\n\t }\n\t| { type: 'unsubscribe'; id: string }\n\t| { type: 'mutate'; mutationId: number; name: string; args?: unknown };\n\n/** Server → client. `version` is the change-feed watermark this frame brings. */\nexport type ServerFrame<T = unknown> =\n\t| { type: 'snapshot'; id: string; rows: T[]; version?: number }\n\t| {\n\t\t\ttype: 'diff';\n\t\t\tid: string;\n\t\t\tadded: T[];\n\t\t\tremoved: T[];\n\t\t\tchanged: T[];\n\t\t\tversion?: number;\n\t }\n\t| { type: 'error'; id?: string; message: string }\n\t| { type: 'ack'; mutationId: number; result?: unknown }\n\t| { type: 'reject'; mutationId: number; message: string };\n\nexport type SyncConnectionOptions = {\n\tengine: SyncEngine;\n\t/** Resolved auth context for this connection; passed to every subscribe. */\n\tctx: unknown;\n\t/** Send a frame to the client (the transport serializes it). */\n\tsend: (frame: ServerFrame) => void;\n};\n\nexport type SyncConnection = {\n\t/** Handle one client frame (a parsed object or a raw JSON string). */\n\thandle: (raw: unknown) => Promise<void>;\n\t/** Tear down every subscription on this connection (call on socket close). */\n\tclose: () => void;\n};\n\nconst parseFrame = (raw: unknown): ClientFrame | undefined => {\n\tlet value: unknown = raw;\n\tif (typeof value === 'string') {\n\t\ttry {\n\t\t\tvalue = JSON.parse(value);\n\t\t} catch {\n\t\t\treturn undefined;\n\t\t}\n\t}\n\tif (typeof value !== 'object' || value === null) {\n\t\treturn undefined;\n\t}\n\tconst frame = value as {\n\t\ttype?: unknown;\n\t\tid?: unknown;\n\t\tcollection?: unknown;\n\t\tparams?: unknown;\n\t\tsince?: unknown;\n\t\tmutationId?: unknown;\n\t\tname?: unknown;\n\t\targs?: unknown;\n\t};\n\tif (frame.type === 'subscribe') {\n\t\treturn typeof frame.id === 'string' &&\n\t\t\ttypeof frame.collection === 'string'\n\t\t\t? {\n\t\t\t\t\ttype: 'subscribe',\n\t\t\t\t\tid: frame.id,\n\t\t\t\t\tcollection: frame.collection,\n\t\t\t\t\tparams: frame.params,\n\t\t\t\t\tsince:\n\t\t\t\t\t\ttypeof frame.since === 'number'\n\t\t\t\t\t\t\t? frame.since\n\t\t\t\t\t\t\t: undefined\n\t\t\t\t}\n\t\t\t: undefined;\n\t}\n\tif (frame.type === 'unsubscribe') {\n\t\treturn typeof frame.id === 'string'\n\t\t\t? { type: 'unsubscribe', id: frame.id }\n\t\t\t: undefined;\n\t}\n\tif (frame.type === 'mutate') {\n\t\treturn typeof frame.mutationId === 'number' &&\n\t\t\ttypeof frame.name === 'string'\n\t\t\t? {\n\t\t\t\t\ttype: 'mutate',\n\t\t\t\t\tmutationId: frame.mutationId,\n\t\t\t\t\tname: frame.name,\n\t\t\t\t\targs: frame.args\n\t\t\t\t}\n\t\t\t: undefined;\n\t}\n\treturn undefined;\n};\n\n/**\n * The per-connection protocol handler — transport-agnostic glue between a single\n * client socket and the {@link SyncEngine}. It owns that connection's\n * subscriptions: a `subscribe` frame authorizes + hydrates and replies with a\n * `snapshot`, then streams `diff` frames; `unsubscribe`/`close` release views.\n *\n * Pure (no WebSocket import) so it can be unit-tested with a fake `send`; the\n * Elysia `syncSocket` plugin is the thin adapter that feeds it socket events.\n */\nexport const createSyncConnection = ({\n\tengine,\n\tctx,\n\tsend\n}: SyncConnectionOptions): SyncConnection => {\n\tconst subscriptions = new Map<string, Subscription<unknown>>();\n\n\tconst handle = async (raw: unknown) => {\n\t\tconst frame = parseFrame(raw);\n\t\tif (frame === undefined) {\n\t\t\tsend({ type: 'error', message: 'Malformed sync frame' });\n\t\t\treturn;\n\t\t}\n\n\t\tif (frame.type === 'mutate') {\n\t\t\ttry {\n\t\t\t\tconst result = await engine.runMutation(\n\t\t\t\t\tframe.name,\n\t\t\t\t\tframe.args,\n\t\t\t\t\tctx\n\t\t\t\t);\n\t\t\t\t// The mutation's diffs were sent during runMutation (over the same\n\t\t\t\t// ordered socket), so the ack arrives after them.\n\t\t\t\tsend({ type: 'ack', mutationId: frame.mutationId, result });\n\t\t\t} catch (error) {\n\t\t\t\tsend({\n\t\t\t\t\ttype: 'reject',\n\t\t\t\t\tmutationId: frame.mutationId,\n\t\t\t\t\tmessage:\n\t\t\t\t\t\terror instanceof Error ? error.message : String(error)\n\t\t\t\t});\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tif (frame.type === 'unsubscribe') {\n\t\t\tsubscriptions.get(frame.id)?.unsubscribe();\n\t\t\tsubscriptions.delete(frame.id);\n\t\t\treturn;\n\t\t}\n\n\t\tif (subscriptions.has(frame.id)) {\n\t\t\tsend({\n\t\t\t\ttype: 'error',\n\t\t\t\tid: frame.id,\n\t\t\t\tmessage: `Subscription id \"${frame.id}\" already in use`\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\n\t\ttry {\n\t\t\tconst subscription = await engine.subscribe({\n\t\t\t\tcollection: frame.collection,\n\t\t\t\tparams: frame.params,\n\t\t\t\tctx,\n\t\t\t\tsince: frame.since,\n\t\t\t\tonDiff: (diff, diffVersion) => {\n\t\t\t\t\tsend({\n\t\t\t\t\t\ttype: 'diff',\n\t\t\t\t\t\tid: frame.id,\n\t\t\t\t\t\tadded: diff.added,\n\t\t\t\t\t\tremoved: diff.removed,\n\t\t\t\t\t\tchanged: diff.changed,\n\t\t\t\t\t\tversion: diffVersion\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t});\n\t\t\tsubscriptions.set(frame.id, subscription);\n\t\t\t// No await between subscribe resolving and this send, so the initial\n\t\t\t// reply always precedes any diff for this subscription.\n\t\t\tif (subscription.catchup !== undefined) {\n\t\t\t\t// Resumed: a catch-up diff applied on top of the client's set.\n\t\t\t\tsend({\n\t\t\t\t\ttype: 'diff',\n\t\t\t\t\tid: frame.id,\n\t\t\t\t\tadded: subscription.catchup.added,\n\t\t\t\t\tremoved: subscription.catchup.removed,\n\t\t\t\t\tchanged: subscription.catchup.changed,\n\t\t\t\t\tversion: subscription.version\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\tsend({\n\t\t\t\t\ttype: 'snapshot',\n\t\t\t\t\tid: frame.id,\n\t\t\t\t\trows: subscription.initial,\n\t\t\t\t\tversion: subscription.version\n\t\t\t\t});\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tsend({\n\t\t\t\ttype: 'error',\n\t\t\t\tid: frame.id,\n\t\t\t\tmessage: error instanceof Error ? error.message : String(error)\n\t\t\t});\n\t\t}\n\t};\n\n\tconst close = () => {\n\t\tfor (const subscription of subscriptions.values()) {\n\t\t\tsubscription.unsubscribe();\n\t\t}\n\t\tsubscriptions.clear();\n\t};\n\n\treturn { handle, close };\n};\n"
|
|
16
|
+
],
|
|
17
|
+
"mappings": "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",
|
|
18
|
+
"debugId": "CDF0007CE81F93F364756E2164756E21",
|
|
19
|
+
"names": []
|
|
20
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { RowChange, RowKey, ViewDiff } from './types';
|
|
2
|
+
export type MaterializedViewOptions<T> = {
|
|
3
|
+
/** Row identity within the result set. */
|
|
4
|
+
key: (row: T) => RowKey;
|
|
5
|
+
/**
|
|
6
|
+
* The query's WHERE as a JS predicate: does this row belong in the result
|
|
7
|
+
* set? Evaluated on every changed row to decide enter/leave/update. (The
|
|
8
|
+
* Drizzle/Prisma adapters can derive this from a query filter.)
|
|
9
|
+
*/
|
|
10
|
+
match: (row: T) => boolean;
|
|
11
|
+
/**
|
|
12
|
+
* Equality used by {@link MaterializedView.reset} to detect changed rows.
|
|
13
|
+
* Defaults to a shallow compare of own enumerable properties.
|
|
14
|
+
*/
|
|
15
|
+
equals?: (a: T, b: T) => boolean;
|
|
16
|
+
};
|
|
17
|
+
export type MaterializedView<T> = {
|
|
18
|
+
/**
|
|
19
|
+
* Replace the result set with `rows` (the initial DB query result). Rows are
|
|
20
|
+
* trusted to already satisfy the predicate — the database applied the filter.
|
|
21
|
+
*/
|
|
22
|
+
hydrate: (rows: Iterable<T>) => void;
|
|
23
|
+
/**
|
|
24
|
+
* Apply one row change and return the resulting diff. Empty `added`/`removed`/
|
|
25
|
+
* `changed` arrays mean the change did not affect this view.
|
|
26
|
+
*/
|
|
27
|
+
apply: (change: RowChange<T>) => ViewDiff<T>;
|
|
28
|
+
/**
|
|
29
|
+
* Replace the result set with a fresh query result and return the diff versus
|
|
30
|
+
* what the view previously held. Powers the refetch fallback for queries that
|
|
31
|
+
* can't be matched incrementally.
|
|
32
|
+
*/
|
|
33
|
+
reset: (rows: Iterable<T>) => ViewDiff<T>;
|
|
34
|
+
/** Current result set, as an array. */
|
|
35
|
+
rows: () => T[];
|
|
36
|
+
/** Current result-set size. */
|
|
37
|
+
size: () => number;
|
|
38
|
+
};
|
|
39
|
+
/** True when a diff carries no changes. */
|
|
40
|
+
export declare const isEmptyViewDiff: <T>(diff: ViewDiff<T>) => boolean;
|
|
41
|
+
/**
|
|
42
|
+
* A single query's materialized result set, maintained incrementally by
|
|
43
|
+
* predicate matching (the Tier 3 IVM core). Hydrate once from the database, then
|
|
44
|
+
* feed each changed row through {@link MaterializedView.apply}: the view decides
|
|
45
|
+
* whether the row entered, left, stayed-and-changed, or is irrelevant, and
|
|
46
|
+
* returns just that delta — so the server pushes diffs instead of refetching.
|
|
47
|
+
*
|
|
48
|
+
* Scope: single-table filtered queries (no ORDER BY / LIMIT windows — a top-N
|
|
49
|
+
* query should hydrate-and-refetch rather than rely on this view, since a row
|
|
50
|
+
* entering can silently evict another). Joins/aggregations are a later,
|
|
51
|
+
* differential-dataflow engine.
|
|
52
|
+
*/
|
|
53
|
+
export declare const createMaterializedView: <T>(options: MaterializedViewOptions<T>) => MaterializedView<T>;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { CollectionContext } from './collection';
|
|
2
|
+
import type { RowChange } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* Actions a mutation handler uses to publish what it changed. Call `change`
|
|
5
|
+
* after each durable write so live views update and subscribers (including the
|
|
6
|
+
* caller) receive the authoritative diff.
|
|
7
|
+
*/
|
|
8
|
+
export type MutationActions = {
|
|
9
|
+
change: <T>(collection: string, change: RowChange<T>) => Promise<void>;
|
|
10
|
+
};
|
|
11
|
+
export type MutationHandler<Args, Ctx, Result> = (args: Args, ctx: Ctx, actions: MutationActions) => Promise<Result> | Result;
|
|
12
|
+
export type MutationDefinition<Args = unknown, Ctx = CollectionContext, Result = unknown> = {
|
|
13
|
+
/** Mutation name the client invokes. */
|
|
14
|
+
name: string;
|
|
15
|
+
/** Access control: return false (or throw) to reject the mutation. */
|
|
16
|
+
authorize?: (args: Args, ctx: Ctx) => boolean | Promise<boolean>;
|
|
17
|
+
/**
|
|
18
|
+
* Apply the mutation: write to your durable store, then call
|
|
19
|
+
* `actions.change(...)` for each affected row. Return value (e.g. the created
|
|
20
|
+
* record) is sent back to the caller in the ack.
|
|
21
|
+
*/
|
|
22
|
+
handler: MutationHandler<Args, Ctx, Result>;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Define a server mutation. Identity at runtime — it exists for type inference
|
|
26
|
+
* (args/ctx/result flow through). Register it with {@link SyncEngine.registerMutation}
|
|
27
|
+
* and invoke it from the client; the engine authorizes, runs the handler, fans
|
|
28
|
+
* the resulting diffs to subscribers, and acks the caller.
|
|
29
|
+
*/
|
|
30
|
+
export declare const defineMutation: <Args = unknown, Ctx = CollectionContext, Result = unknown>(definition: MutationDefinition<Args, Ctx, Result>) => MutationDefinition<Args, Ctx, Result>;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { ChangeSource, ParsedChange } from './types';
|
|
2
|
+
/** One changelog row, as returned by your `poll` query. */
|
|
3
|
+
export type OutboxRow = {
|
|
4
|
+
/** Monotonic sequence (the cursor advances to the max seen). */
|
|
5
|
+
seq: number;
|
|
6
|
+
/** Source table the change happened on. */
|
|
7
|
+
tbl: string;
|
|
8
|
+
/** `insert` | `update` | `delete` (upper- or lower-case). */
|
|
9
|
+
op: string;
|
|
10
|
+
/** The row's captured values — a JSON string or an already-parsed object. */
|
|
11
|
+
payload: unknown;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Default changelog-row parser: normalizes `op`, JSON-parses a string `payload`,
|
|
15
|
+
* and returns `{ table, change }`. Returns `undefined` for a malformed row so it
|
|
16
|
+
* is skipped (and its `seq` still advances the cursor) rather than wedging the
|
|
17
|
+
* feed.
|
|
18
|
+
*/
|
|
19
|
+
export declare const parseOutboxRow: (row: OutboxRow) => ParsedChange | undefined;
|
|
20
|
+
export type PollingChangeSourceOptions = {
|
|
21
|
+
/**
|
|
22
|
+
* Fetch changelog rows with `seq > sinceSeq`, ordered by `seq` ascending.
|
|
23
|
+
* e.g. `(since) => sql\`SELECT seq, tbl, op, payload FROM absolute_sync_changelog WHERE seq > ${since} ORDER BY seq\``.
|
|
24
|
+
*/
|
|
25
|
+
poll: (sinceSeq: number) => Promise<OutboxRow[]> | OutboxRow[];
|
|
26
|
+
/** Poll interval in ms. Defaults to 1000. */
|
|
27
|
+
intervalMs?: number;
|
|
28
|
+
/** Resume cursor (highest `seq` already processed). Defaults to 0. */
|
|
29
|
+
startSeq?: number;
|
|
30
|
+
/** Override the row parser (defaults to {@link parseOutboxRow}). */
|
|
31
|
+
parse?: (row: OutboxRow) => ParsedChange | undefined;
|
|
32
|
+
/** Called after a non-empty batch with the new watermark — prune here. */
|
|
33
|
+
onProcessed?: (uptoSeq: number) => void | Promise<void>;
|
|
34
|
+
/** Called if a poll throws (the loop keeps running). Defaults to a warning. */
|
|
35
|
+
onError?: (error: unknown) => void;
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Create a polling {@link ChangeSource} over a changelog table. Connect it with
|
|
39
|
+
* `engine.connectSource(...)`; `start` runs an immediate first poll (draining any
|
|
40
|
+
* backlog) and then polls every `intervalMs` until `stop`.
|
|
41
|
+
*/
|
|
42
|
+
export declare const createPollingChangeSource: (options: PollingChangeSourceOptions) => ChangeSource;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { CollectionDefinition } from './collection';
|
|
2
|
+
import type { MutationDefinition } from './mutation';
|
|
3
|
+
import type { SyncEngine } from './syncEngine';
|
|
4
|
+
/**
|
|
5
|
+
* Eden-native HTTP route helpers (Tier 3). These turn a typed collection /
|
|
6
|
+
* mutation definition into a plain Elysia route handler — so hydrate and mutate
|
|
7
|
+
* are ordinary Elysia routes that Eden types end to end, with TypeBox validating
|
|
8
|
+
* the params/body. The live diff stream stays on the WebSocket (`syncSocket`).
|
|
9
|
+
*
|
|
10
|
+
* They import no Elysia: each returns `(context) => Promise<...>`, which you pass
|
|
11
|
+
* to `.get` / `.post` with a TypeBox `query` / `body` schema. The handler's
|
|
12
|
+
* return type carries the row / result type, so `treaty<typeof app>()` infers it.
|
|
13
|
+
*/
|
|
14
|
+
/** The slice of an Elysia route context these helpers read. */
|
|
15
|
+
export type SyncRouteContext = {
|
|
16
|
+
query: unknown;
|
|
17
|
+
body: unknown;
|
|
18
|
+
[key: string]: unknown;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Build a GET handler that hydrates `collection` — authorize, then return its
|
|
22
|
+
* current rows. The handler returns `Promise<Row[]>`, so Eden infers the row
|
|
23
|
+
* type from the collection definition; the route's TypeBox `query` schema
|
|
24
|
+
* validates and types the params.
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* .get('/sync/orders', hydrateRoute(engine, ordersCollection, (c) => ({ userId: c.userId })),
|
|
28
|
+
* { query: t.Object({ userId: t.Numeric() }) })
|
|
29
|
+
*/
|
|
30
|
+
export declare const hydrateRoute: <Row, Params, Ctx>(engine: SyncEngine, collection: CollectionDefinition<Row, Params, Ctx>, resolveContext?: (context: SyncRouteContext) => Ctx) => (context: SyncRouteContext) => Promise<Row[]>;
|
|
31
|
+
/**
|
|
32
|
+
* Build a POST handler that runs `mutation`. The handler returns
|
|
33
|
+
* `Promise<Result>`, so Eden infers the result type; the route's TypeBox `body`
|
|
34
|
+
* schema validates and types the args.
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* .post('/sync/createOrder', mutateRoute(engine, createOrder, (c) => ({ userId: c.userId })),
|
|
38
|
+
* { body: t.Object({ total: t.Number() }) })
|
|
39
|
+
*/
|
|
40
|
+
export declare const mutateRoute: <Args, Ctx, Result>(engine: SyncEngine, mutation: MutationDefinition<Args, Ctx, Result>, resolveContext?: (context: SyncRouteContext) => Ctx) => (context: SyncRouteContext) => Promise<Result>;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { Elysia } from 'elysia';
|
|
2
|
+
import type { SyncEngine } from './syncEngine';
|
|
3
|
+
export type SyncSocketOptions = {
|
|
4
|
+
/** The sync engine whose collections this socket serves. */
|
|
5
|
+
engine: SyncEngine;
|
|
6
|
+
/** WebSocket route. Defaults to `/sync/ws`. */
|
|
7
|
+
path?: string;
|
|
8
|
+
/**
|
|
9
|
+
* Build the per-connection auth context from the upgrade request data
|
|
10
|
+
* (`ws.data`: query, headers, cookies, and anything you `derive`d/`resolve`d
|
|
11
|
+
* earlier in the chain). Whatever you return is the `ctx` passed to every
|
|
12
|
+
* collection's `authorize`/`hydrate`/`match`. Defaults to an empty object.
|
|
13
|
+
*/
|
|
14
|
+
resolveContext?: (data: Record<string, unknown>) => unknown | Promise<unknown>;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Elysia WebSocket plugin for the Tier 3 sync engine. One socket multiplexes any
|
|
18
|
+
* number of collection subscriptions: the client sends `subscribe`/`unsubscribe`
|
|
19
|
+
* frames and receives `snapshot`/`diff`/`error` frames (see
|
|
20
|
+
* {@link createSyncConnection}). Mount it once and drive `engine.applyChange`
|
|
21
|
+
* from your mutations.
|
|
22
|
+
*
|
|
23
|
+
* Uses Elysia's first-class `.ws()` rather than a hand-rolled stream — the
|
|
24
|
+
* bidirectional channel carries both subscriptions and (later) mutations, and
|
|
25
|
+
* `ws.send` serializes frames for us.
|
|
26
|
+
*/
|
|
27
|
+
export declare const syncSocket: ({ engine, path, resolveContext }: SyncSocketOptions) => Elysia<"", {
|
|
28
|
+
decorator: {};
|
|
29
|
+
store: {};
|
|
30
|
+
derive: {};
|
|
31
|
+
resolve: {};
|
|
32
|
+
}, {
|
|
33
|
+
typebox: {};
|
|
34
|
+
error: {};
|
|
35
|
+
}, {
|
|
36
|
+
schema: {};
|
|
37
|
+
standaloneSchema: {};
|
|
38
|
+
macro: {};
|
|
39
|
+
macroFn: {};
|
|
40
|
+
parser: {};
|
|
41
|
+
response: {};
|
|
42
|
+
}, {
|
|
43
|
+
[x: string]: {
|
|
44
|
+
subscribe: {
|
|
45
|
+
body: {};
|
|
46
|
+
params: {};
|
|
47
|
+
query: {};
|
|
48
|
+
headers: {};
|
|
49
|
+
response: {};
|
|
50
|
+
};
|
|
51
|
+
};
|
|
52
|
+
}, {
|
|
53
|
+
derive: {};
|
|
54
|
+
resolve: {};
|
|
55
|
+
schema: {};
|
|
56
|
+
standaloneSchema: {};
|
|
57
|
+
response: {};
|
|
58
|
+
}, {
|
|
59
|
+
derive: {};
|
|
60
|
+
resolve: {};
|
|
61
|
+
schema: {};
|
|
62
|
+
standaloneSchema: {};
|
|
63
|
+
response: {};
|
|
64
|
+
}>;
|