@absolutejs/sync 1.2.0 → 1.4.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/dist/adapters/drizzle/index.js +3 -1
- package/dist/adapters/drizzle/index.js.map +2 -2
- package/dist/adapters/mysql/index.js +3 -1
- package/dist/adapters/mysql/index.js.map +2 -2
- package/dist/adapters/postgres/index.js +3 -1
- package/dist/adapters/postgres/index.js.map +2 -2
- package/dist/adapters/prisma/index.js +3 -1
- package/dist/adapters/prisma/index.js.map +2 -2
- package/dist/adapters/sqlite/index.js +3 -1
- package/dist/adapters/sqlite/index.js.map +2 -2
- package/dist/engine/index.d.ts +1 -0
- package/dist/engine/index.js +162 -5
- package/dist/engine/index.js.map +6 -5
- package/dist/engine/mutation.d.ts +34 -1
- package/dist/engine/retry.d.ts +67 -0
- package/dist/engine/sandbox.d.ts +40 -0
- package/dist/engine/syncEngine.d.ts +17 -0
- package/dist/index.js +3 -1
- package/dist/index.js.map +2 -2
- package/dist/scheduled.js +3 -1
- package/dist/scheduled.js.map +2 -2
- package/package.json +6 -1
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
// @bun
|
|
2
|
+
var __require = import.meta.require;
|
|
3
|
+
|
|
2
4
|
// src/adapters/drizzle/topics.ts
|
|
3
5
|
import {
|
|
4
6
|
Column,
|
|
@@ -261,5 +263,5 @@ export {
|
|
|
261
263
|
UnsupportedDrizzleFilterError
|
|
262
264
|
};
|
|
263
265
|
|
|
264
|
-
//# debugId=
|
|
266
|
+
//# debugId=8F10A80D3C4C330164756E2164756E21
|
|
265
267
|
//# sourceMappingURL=index.js.map
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"import type { SQL, Table } from 'drizzle-orm';\nimport { extractKeyFromWhere, keyTopic, tableTopic } from './topics';\n\nexport type DeriveReadTopicsOptions = {\n\t/**\n\t * Column (its JS property name on the table) to treat as the row key when\n\t * narrowing to a `table:key` topic. Defaults to the table's single\n\t * primary-key column; composite or absent primary keys disable row-level\n\t * narrowing.\n\t */\n\tkeyColumn?: string;\n};\n\nexport type DerivedReadTopics = {\n\t/** Topics this read depends on — subscribe to all of them. */\n\ttopics: string[];\n\t/**\n\t * `true` when derivation narrowed to a specific row (`table:key`); `false`\n\t * when it fell back to the whole-table topic.\n\t */\n\trowLevel: boolean;\n};\n\n/**\n * Derive the reactive topics a read of `table` (optionally filtered by `where`)\n * depends on. A recognised primary-key equality narrows to a single `table:key`\n * topic; everything else subscribes to the whole-table topic, over-invalidating\n * a little rather than missing an update.\n *\n * @example\n * deriveReadTopics(users); // { topics: ['users'], rowLevel: false }\n * deriveReadTopics(users, eq(users.id, 5)); // { topics: ['users:5'], rowLevel: true }\n * deriveReadTopics(users, gt(users.id, 5)); // { topics: ['users'], rowLevel: false }\n */\nexport const deriveReadTopics = (\n\ttable: Table,\n\twhere?: SQL,\n\toptions: DeriveReadTopicsOptions = {}\n): DerivedReadTopics => {\n\tconst key =\n\t\twhere === undefined\n\t\t\t? undefined\n\t\t\t: extractKeyFromWhere(table, where, options.keyColumn);\n\n\tif (key === undefined) {\n\t\treturn { topics: [tableTopic(table)], rowLevel: false };\n\t}\n\treturn { topics: [keyTopic(table, key)], rowLevel: true };\n};\n",
|
|
9
9
|
"import type { SQL, Table } from 'drizzle-orm';\nimport type { ReactiveHub } from '../../reactiveHub';\nimport {\n\textractKeyFromWhere,\n\textractRowKeys,\n\tkeyTopic,\n\ttableTopic\n} from './topics';\n\n/**\n * Drizzle write-side topic publishing (Tier 2).\n *\n * The mirror of {@link deriveReadTopics}: after a mutation commits, publish the\n * topics it invalidates so subscribed reads refetch. Every change publishes the\n * **table** topic (so list/table queries refresh) plus a **row** topic per\n * affected key (so row-level queries refresh) — the exact topics the read side\n * subscribes to.\n *\n * These are the \"route mutations through us\" change source from the roadmap:\n * call them right after your durable write. They work on any DB Drizzle supports\n * and never touch DB-specific machinery; out-of-band writes (caught later by CDC\n * adapters) are the only thing they miss.\n */\n\n/** The kind of mutation, forwarded in the change-event payload. */\nexport type ChangeOp = 'insert' | 'update' | 'delete';\n\n/** Payload carried by every change event the write side publishes. */\nexport type ChangePayload = {\n\t/** Name of the table that changed. */\n\ttable: string;\n\t/** Mutation kind, when the caller provided it. */\n\top?: ChangeOp;\n\t/** Affected row keys (empty for a table-wide change). */\n\tkeys: (string | number)[];\n};\n\nexport type PublishChangeOptions = {\n\t/** Row keys that changed; each emits a `table:key` topic. */\n\tkeys?: ReadonlyArray<string | number>;\n\top?: ChangeOp;\n};\n\n/**\n * Publish the reactive topics a change to `table` invalidates: the whole-table\n * topic (always) plus a `table:key` topic per affected row. Call after the\n * durable write commits. Returns the (de-duplicated) topics published.\n */\nexport const publishChange = (\n\thub: Pick<ReactiveHub, 'publish'>,\n\ttable: Table,\n\toptions: PublishChangeOptions = {}\n): string[] => {\n\tconst name = tableTopic(table);\n\tconst keys = options.keys === undefined ? [] : [...new Set(options.keys)];\n\tconst payload: ChangePayload = { table: name, op: options.op, keys };\n\tconst topics = [\n\t\t...new Set([name, ...keys.map((key) => keyTopic(table, key))])\n\t];\n\tfor (const topic of topics) {\n\t\thub.publish(topic, payload);\n\t}\n\treturn topics;\n};\n\nexport type PublishRowsOptions = {\n\t/** Key column (JS property name); defaults to the table's primary key. */\n\tkeyColumn?: string;\n\top?: ChangeOp;\n};\n\n/**\n * Publish change topics for a set of rows — typically the output of a mutation's\n * `.returning()`, which yields real keys including auto-generated ones. Reads\n * each row's primary-key column (or `keyColumn`) to emit `table:key` topics.\n *\n * @example\n * const rows = await db.insert(users).values(input).returning();\n * publishRows(hub, users, rows, { op: 'insert' });\n */\nexport const publishRows = (\n\thub: Pick<ReactiveHub, 'publish'>,\n\ttable: Table,\n\trows: ReadonlyArray<Record<string, unknown>>,\n\toptions: PublishRowsOptions = {}\n): string[] =>\n\tpublishChange(hub, table, {\n\t\tkeys: extractRowKeys(table, rows, options.keyColumn),\n\t\top: options.op\n\t});\n\nexport type PublishWhereOptions = {\n\t/** Key column (JS property name); defaults to the table's primary key. */\n\tkeyColumn?: string;\n\top?: ChangeOp;\n};\n\n/**\n * Publish change topics for an `update`/`delete` identified by a `where` filter.\n * A simple primary-key equality narrows to that row's topic; any other filter\n * publishes just the table topic, so every affected subscriber refetches and\n * re-evaluates.\n *\n * @example\n * await db.update(users).set(patch).where(eq(users.id, id));\n * publishWhere(hub, users, eq(users.id, id), { op: 'update' });\n */\nexport const publishWhere = (\n\thub: Pick<ReactiveHub, 'publish'>,\n\ttable: Table,\n\twhere: SQL,\n\toptions: PublishWhereOptions = {}\n): string[] => {\n\tconst key = extractKeyFromWhere(table, where, options.keyColumn);\n\treturn publishChange(hub, table, {\n\t\tkeys: key === undefined ? [] : [key],\n\t\top: options.op\n\t});\n};\n"
|
|
10
10
|
],
|
|
11
|
-
"mappings": "
|
|
12
|
-
"debugId": "
|
|
11
|
+
"mappings": "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",
|
|
12
|
+
"debugId": "8F10A80D3C4C330164756E2164756E21",
|
|
13
13
|
"names": []
|
|
14
14
|
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
// @bun
|
|
2
|
+
var __require = import.meta.require;
|
|
3
|
+
|
|
2
4
|
// src/engine/pollingSource.ts
|
|
3
5
|
var OP_BY_NAME = {
|
|
4
6
|
insert: "insert",
|
|
@@ -167,5 +169,5 @@ export {
|
|
|
167
169
|
createPollingChangeSource
|
|
168
170
|
};
|
|
169
171
|
|
|
170
|
-
//# debugId=
|
|
172
|
+
//# debugId=5FBCD902F44C556164756E2164756E21
|
|
171
173
|
//# sourceMappingURL=index.js.map
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"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",
|
|
6
6
|
"import type { ChangeSource, ParsedChange, RowOp } from '../../engine/types';\n\n/**\n * MySQL CDC adapter for @absolutejs/sync (Tier 3, M5).\n *\n * MySQL has no `LISTEN/NOTIFY`, so two pluggable strategies catch out-of-band\n * writes — both behind the engine's {@link ChangeSource} seam:\n *\n * - **Changelog + poll (portable).** Install triggers with\n * {@link mysqlChangelogSchema} and tail the changelog with\n * {@link createPollingChangeSource}. Works anywhere, no extra privileges.\n * - **Binlog (higher throughput).** Wire a binlog reader (e.g. zongji) into\n * {@link mysqlBinlogChangeSource}; it normalizes row events into the change\n * feed. Catches writes without the per-write changelog overhead.\n *\n * Dependency-free — you bring the MySQL client / binlog reader.\n */\n\nexport {\n\tcreatePollingChangeSource,\n\tparseOutboxRow\n} from '../../engine/pollingSource';\nexport type {\n\tOutboxRow,\n\tPollingChangeSourceOptions\n} from '../../engine/pollingSource';\n\nconst DEFAULT_CHANGELOG = 'absolute_sync_changelog';\nconst DEFAULT_PREFIX = 'absolute_sync';\nconst OPS = ['insert', 'update', 'delete'] as const;\n\nexport type MysqlChangelogOptions = {\n\t/** Table name → the column names to capture in the change payload. */\n\ttables: Record<string, string[]>;\n\t/** Changelog table name. Defaults to `absolute_sync_changelog`. */\n\tchangelogTable?: string;\n\t/** Trigger name prefix. Defaults to `absolute_sync`. */\n\tprefix?: string;\n};\n\n/**\n * Generate the SQL that installs the changelog table and per-table\n * insert/update/delete triggers — run it once (e.g. in a migration). Each\n * trigger appends `{ tbl, op, payload }` (payload built with `JSON_OBJECT` from\n * the listed columns) to the changelog for {@link createPollingChangeSource}.\n *\n * Each `CREATE TRIGGER` body is a single statement (no `DELIMITER` needed). The\n * statements are `;`-separated; run them as a script, or split on `;` if your\n * driver executes one statement per call.\n */\nexport const mysqlChangelogSchema = (\n\toptions: MysqlChangelogOptions\n): string => {\n\tconst changelog = options.changelogTable ?? DEFAULT_CHANGELOG;\n\tconst prefix = options.prefix ?? DEFAULT_PREFIX;\n\n\tconst createTable = [\n\t\t`CREATE TABLE IF NOT EXISTS \\`${changelog}\\` (`,\n\t\t'\\tseq BIGINT AUTO_INCREMENT PRIMARY KEY,',\n\t\t'\\ttbl VARCHAR(255) NOT NULL,',\n\t\t'\\top VARCHAR(16) NOT NULL,',\n\t\t'\\tpayload JSON NOT NULL,',\n\t\t'\\tcreated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP',\n\t\t');'\n\t].join('\\n');\n\n\tconst jsonObject = (columns: string[], ref: 'NEW' | 'OLD'): string =>\n\t\t`JSON_OBJECT(${columns\n\t\t\t.map((column) => `'${column}', ${ref}.\\`${column}\\``)\n\t\t\t.join(', ')})`;\n\n\tconst triggers = Object.entries(options.tables).flatMap(\n\t\t([table, columns]) =>\n\t\t\tOPS.map((op) => {\n\t\t\t\tconst ref = op === 'delete' ? 'OLD' : 'NEW';\n\t\t\t\tconst name = `${prefix}_${table}_${op}`;\n\t\t\t\treturn [\n\t\t\t\t\t`DROP TRIGGER IF EXISTS \\`${name}\\`;`,\n\t\t\t\t\t`CREATE TRIGGER \\`${name}\\` AFTER ${op.toUpperCase()} ON \\`${table}\\` FOR EACH ROW`,\n\t\t\t\t\t`INSERT INTO \\`${changelog}\\` (tbl, op, payload)`,\n\t\t\t\t\t`VALUES ('${table}', '${op}', ${jsonObject(columns, ref)});`\n\t\t\t\t].join('\\n');\n\t\t\t})\n\t);\n\n\treturn [createTable, ...triggers].join('\\n\\n');\n};\n\nconst BINLOG_OP: Record<string, RowOp> = {\n\twriterows: 'insert',\n\tupdaterows: 'update',\n\tdeleterows: 'delete'\n};\n\n/** A row-level binlog event (the subset {@link normalizeBinlogEvent} needs). */\nexport type BinlogRowEvent = {\n\t/**\n\t * Event kind — zongji's `writerows` / `updaterows` / `deleterows`\n\t * (case-insensitive).\n\t */\n\ttype: string;\n\t/** The affected table. */\n\ttable: string;\n\t/**\n\t * Affected rows. Insert/delete: each entry is the row object. Update: each\n\t * entry is `{ before, after }` (zongji's shape).\n\t */\n\trows: unknown[];\n};\n\n/**\n * Normalize a binlog row event into engine changes — one per affected row. For\n * updates it takes the `after` image; for deletes the row (or its `before`\n * image). Rows that aren't objects are skipped. Pure, so it's easy to test and\n * to swap for a different reader's shape.\n */\nexport const normalizeBinlogEvent = (event: BinlogRowEvent): ParsedChange[] => {\n\tconst op = BINLOG_OP[event.type.toLowerCase()];\n\tif (op === undefined || typeof event.table !== 'string') {\n\t\treturn [];\n\t}\n\tconst changes: ParsedChange[] = [];\n\tfor (const entry of event.rows) {\n\t\tlet row: unknown = entry;\n\t\tif (entry !== null && typeof entry === 'object') {\n\t\t\tif (op === 'update' && 'after' in entry) {\n\t\t\t\trow = (entry as { after: unknown }).after;\n\t\t\t} else if (op === 'delete' && 'before' in entry) {\n\t\t\t\trow = (entry as { before: unknown }).before;\n\t\t\t}\n\t\t}\n\t\tif (typeof row === 'object' && row !== null) {\n\t\t\tchanges.push({ table: event.table, change: { op, row } });\n\t\t}\n\t}\n\treturn changes;\n};\n\nexport type MysqlBinlogChangeSourceOptions = {\n\t/**\n\t * Subscribe to row events from a binlog reader; return a function that stops\n\t * it. e.g. with zongji:\n\t * `(onEvent) => { zongji.on('binlog', e => onEvent({ type: e.getEventName(), table: e.tableMap[e.tableId].tableName, rows: e.rows })); zongji.start(...); return () => zongji.stop(); }`.\n\t */\n\tsubscribe: (\n\t\tonEvent: (event: BinlogRowEvent) => void\n\t) => Promise<() => void | Promise<void>> | (() => void | Promise<void>);\n\t/** Override the event normalizer (defaults to {@link normalizeBinlogEvent}). */\n\tnormalize?: (event: BinlogRowEvent) => ParsedChange[];\n};\n\n/**\n * A {@link ChangeSource} backed by the MySQL binlog. Each row event is\n * normalized and emitted into the engine. Connect with\n * `engine.connectSource(...)`.\n */\nexport const mysqlBinlogChangeSource = (\n\toptions: MysqlBinlogChangeSourceOptions\n): ChangeSource => {\n\tconst normalize = options.normalize ?? normalizeBinlogEvent;\n\tlet unsubscribe: (() => void | Promise<void>) | undefined;\n\n\treturn {\n\t\tstart: async (emit) => {\n\t\t\tunsubscribe = await options.subscribe((event) => {\n\t\t\t\tfor (const parsed of normalize(event)) {\n\t\t\t\t\tvoid emit(parsed.table, parsed.change);\n\t\t\t\t}\n\t\t\t});\n\t\t},\n\t\tstop: async () => {\n\t\t\tawait unsubscribe?.();\n\t\t\tunsubscribe = undefined;\n\t\t}\n\t};\n};\n"
|
|
7
7
|
],
|
|
8
|
-
"mappings": "
|
|
9
|
-
"debugId": "
|
|
8
|
+
"mappings": "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",
|
|
9
|
+
"debugId": "5FBCD902F44C556164756E2164756E21",
|
|
10
10
|
"names": []
|
|
11
11
|
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
// @bun
|
|
2
|
+
var __require = import.meta.require;
|
|
3
|
+
|
|
2
4
|
// src/adapters/postgres/index.ts
|
|
3
5
|
var DEFAULT_CHANNEL = "absolute_sync";
|
|
4
6
|
var DEFAULT_FUNCTION = "absolute_sync_notify";
|
|
@@ -82,5 +84,5 @@ export {
|
|
|
82
84
|
parseNotification
|
|
83
85
|
};
|
|
84
86
|
|
|
85
|
-
//# debugId=
|
|
87
|
+
//# debugId=7D22AF8D04FDCF6D64756E2164756E21
|
|
86
88
|
//# sourceMappingURL=index.js.map
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"sourcesContent": [
|
|
5
5
|
"import type { ChangeSource, ParsedChange, RowOp } from '../../engine/types';\n\n/**\n * Postgres CDC adapter for @absolutejs/sync (Tier 3, M5).\n *\n * Catches writes that didn't go through the mutation API by turning Postgres\n * `LISTEN/NOTIFY` into the engine's change feed. Install the triggers once with\n * {@link postgresNotifyTrigger}, then connect {@link postgresChangeSource} via\n * `engine.connectSource(...)`.\n *\n * Client-agnostic: you supply how to `listen` on a channel, so it works with\n * porsager/postgres (`sql.listen`), node-postgres, or `Bun.sql` — and the\n * adapter itself has no database dependency.\n */\n\nconst DEFAULT_CHANNEL = 'absolute_sync';\nconst DEFAULT_FUNCTION = 'absolute_sync_notify';\n\nconst OP_BY_TG: Record<string, RowOp> = {\n\tINSERT: 'insert',\n\tUPDATE: 'update',\n\tDELETE: 'delete'\n};\n\n/** A parsed change ready to feed the engine. */\nexport type ParsedNotification = ParsedChange;\n\n/**\n * Default NOTIFY-payload parser: expects the JSON the trigger from\n * {@link postgresNotifyTrigger} sends — `{ table, op, row }` where `op` is\n * `INSERT`/`UPDATE`/`DELETE`. Returns `undefined` for anything malformed so a\n * bad payload is skipped rather than throwing.\n */\nexport const parseNotification = (\n\tpayload: string\n): ParsedNotification | undefined => {\n\tlet data: unknown;\n\ttry {\n\t\tdata = JSON.parse(payload);\n\t} catch {\n\t\treturn undefined;\n\t}\n\tif (typeof data !== 'object' || data === null) {\n\t\treturn undefined;\n\t}\n\tconst { table, op, row } = data as {\n\t\ttable?: unknown;\n\t\top?: unknown;\n\t\trow?: unknown;\n\t};\n\tif (typeof table !== 'string') {\n\t\treturn undefined;\n\t}\n\tconst rowOp = typeof op === 'string' ? OP_BY_TG[op] : undefined;\n\tif (rowOp === undefined) {\n\t\treturn undefined;\n\t}\n\tif (typeof row !== 'object' || row === null) {\n\t\treturn undefined;\n\t}\n\treturn { table, change: { op: rowOp, row } };\n};\n\nexport type PostgresChangeSourceOptions = {\n\t/**\n\t * Subscribe to a Postgres NOTIFY channel; return a function that stops\n\t * listening. The wiring is yours, e.g. with porsager/postgres:\n\t * `(channel, onNotify) => { const s = await sql.listen(channel, onNotify); return s.unlisten; }`.\n\t */\n\tlisten: (\n\t\tchannel: string,\n\t\tonNotify: (payload: string) => void\n\t) => Promise<() => void | Promise<void>> | (() => void | Promise<void>);\n\t/** NOTIFY channel; must match the trigger's. Defaults to `absolute_sync`. */\n\tchannel?: string;\n\t/** Override the payload parser (defaults to {@link parseNotification}). */\n\tparse?: (payload: string) => ParsedNotification | undefined;\n};\n\n/**\n * A {@link ChangeSource} backed by Postgres `LISTEN/NOTIFY`. Each notification\n * is parsed to `(table, change)` and emitted into the engine.\n *\n * @example\n * const disconnect = await engine.connectSource(\n * postgresChangeSource({\n * listen: async (channel, onNotify) =>\n * (await sql.listen(channel, onNotify)).unlisten\n * })\n * );\n */\nexport const postgresChangeSource = (\n\toptions: PostgresChangeSourceOptions\n): ChangeSource => {\n\tconst channel = options.channel ?? DEFAULT_CHANNEL;\n\tconst parse = options.parse ?? parseNotification;\n\tlet unlisten: (() => void | Promise<void>) | undefined;\n\n\treturn {\n\t\tstart: async (emit) => {\n\t\t\tunlisten = await options.listen(channel, (payload) => {\n\t\t\t\tconst parsed = parse(payload);\n\t\t\t\tif (parsed !== undefined) {\n\t\t\t\t\tvoid emit(parsed.table, parsed.change);\n\t\t\t\t}\n\t\t\t});\n\t\t},\n\t\tstop: async () => {\n\t\t\tawait unlisten?.();\n\t\t\tunlisten = undefined;\n\t\t}\n\t};\n};\n\nexport type PostgresNotifyTriggerOptions = {\n\t/** Tables to emit changes for. */\n\ttables: string[];\n\t/** NOTIFY channel; must match the change source's. Defaults to `absolute_sync`. */\n\tchannel?: string;\n\t/** Trigger function name. Defaults to `absolute_sync_notify`. */\n\tfunctionName?: string;\n};\n\n/**\n * Generate the SQL that installs a NOTIFY trigger on each table — run it once\n * (e.g. in a migration). On every insert/update/delete it sends\n * `{ table, op, row }` JSON on the channel for {@link postgresChangeSource}.\n *\n * Note: `pg_notify` payloads are capped at 8000 bytes, so very wide rows can be\n * truncated (the parser then skips them). For large rows or high throughput,\n * prefer a logical-replication source behind the same {@link ChangeSource} seam.\n */\nexport const postgresNotifyTrigger = (\n\toptions: PostgresNotifyTriggerOptions\n): string => {\n\tconst channel = options.channel ?? DEFAULT_CHANNEL;\n\tconst fn = options.functionName ?? DEFAULT_FUNCTION;\n\n\tconst functionSql = [\n\t\t`CREATE OR REPLACE FUNCTION ${fn}() RETURNS trigger AS $$`,\n\t\t'BEGIN',\n\t\t` PERFORM pg_notify('${channel}', json_build_object(`,\n\t\t` 'table', TG_TABLE_NAME,`,\n\t\t` 'op', TG_OP,`,\n\t\t` 'row', row_to_json(COALESCE(NEW, OLD))`,\n\t\t' )::text);',\n\t\t' RETURN COALESCE(NEW, OLD);',\n\t\t'END;',\n\t\t'$$ LANGUAGE plpgsql;'\n\t].join('\\n');\n\n\tconst triggerSql = options.tables.map((table) =>\n\t\t[\n\t\t\t`DROP TRIGGER IF EXISTS ${fn}_${table} ON ${table};`,\n\t\t\t`CREATE TRIGGER ${fn}_${table}`,\n\t\t\t`AFTER INSERT OR UPDATE OR DELETE ON ${table}`,\n\t\t\t`FOR EACH ROW EXECUTE FUNCTION ${fn}();`\n\t\t].join('\\n')\n\t);\n\n\treturn [functionSql, ...triggerSql].join('\\n\\n');\n};\n"
|
|
6
6
|
],
|
|
7
|
-
"mappings": "
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": ";;;;AAeA,IAAM,kBAAkB;AACxB,IAAM,mBAAmB;AAEzB,IAAM,WAAkC;AAAA,EACvC,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,QAAQ;AACT;AAWO,IAAM,oBAAoB,CAChC,YACoC;AAAA,EACpC,IAAI;AAAA,EACJ,IAAI;AAAA,IACH,OAAO,KAAK,MAAM,OAAO;AAAA,IACxB,MAAM;AAAA,IACP;AAAA;AAAA,EAED,IAAI,OAAO,SAAS,YAAY,SAAS,MAAM;AAAA,IAC9C;AAAA,EACD;AAAA,EACA,QAAQ,OAAO,IAAI,QAAQ;AAAA,EAK3B,IAAI,OAAO,UAAU,UAAU;AAAA,IAC9B;AAAA,EACD;AAAA,EACA,MAAM,QAAQ,OAAO,OAAO,WAAW,SAAS,MAAM;AAAA,EACtD,IAAI,UAAU,WAAW;AAAA,IACxB;AAAA,EACD;AAAA,EACA,IAAI,OAAO,QAAQ,YAAY,QAAQ,MAAM;AAAA,IAC5C;AAAA,EACD;AAAA,EACA,OAAO,EAAE,OAAO,QAAQ,EAAE,IAAI,OAAO,IAAI,EAAE;AAAA;AA+BrC,IAAM,uBAAuB,CACnC,YACkB;AAAA,EAClB,MAAM,UAAU,QAAQ,WAAW;AAAA,EACnC,MAAM,QAAQ,QAAQ,SAAS;AAAA,EAC/B,IAAI;AAAA,EAEJ,OAAO;AAAA,IACN,OAAO,OAAO,SAAS;AAAA,MACtB,WAAW,MAAM,QAAQ,OAAO,SAAS,CAAC,YAAY;AAAA,QACrD,MAAM,SAAS,MAAM,OAAO;AAAA,QAC5B,IAAI,WAAW,WAAW;AAAA,UACpB,KAAK,OAAO,OAAO,OAAO,MAAM;AAAA,QACtC;AAAA,OACA;AAAA;AAAA,IAEF,MAAM,YAAY;AAAA,MACjB,MAAM,WAAW;AAAA,MACjB,WAAW;AAAA;AAAA,EAEb;AAAA;AAqBM,IAAM,wBAAwB,CACpC,YACY;AAAA,EACZ,MAAM,UAAU,QAAQ,WAAW;AAAA,EACnC,MAAM,KAAK,QAAQ,gBAAgB;AAAA,EAEnC,MAAM,cAAc;AAAA,IACnB,8BAA8B;AAAA,IAC9B;AAAA,IACA,wBAAwB;AAAA,IACxB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACD,EAAE,KAAK;AAAA,CAAI;AAAA,EAEX,MAAM,aAAa,QAAQ,OAAO,IAAI,CAAC,UACtC;AAAA,IACC,0BAA0B,MAAM,YAAY;AAAA,IAC5C,kBAAkB,MAAM;AAAA,IACxB,uCAAuC;AAAA,IACvC,iCAAiC;AAAA,EAClC,EAAE,KAAK;AAAA,CAAI,CACZ;AAAA,EAEA,OAAO,CAAC,aAAa,GAAG,UAAU,EAAE,KAAK;AAAA;AAAA,CAAM;AAAA;",
|
|
8
|
+
"debugId": "7D22AF8D04FDCF6D64756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
// @bun
|
|
2
|
+
var __require = import.meta.require;
|
|
3
|
+
|
|
2
4
|
// src/adapters/prisma/topics.ts
|
|
3
5
|
var tableTopic = (model) => model;
|
|
4
6
|
var keyTopic = (model, key) => `${model}:${key}`;
|
|
@@ -227,5 +229,5 @@ export {
|
|
|
227
229
|
UnsupportedFilterError
|
|
228
230
|
};
|
|
229
231
|
|
|
230
|
-
//# debugId=
|
|
232
|
+
//# debugId=AFF3207442A931ED64756E2164756E21
|
|
231
233
|
//# sourceMappingURL=index.js.map
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"import type { PrismaWhere } from './topics';\n\n/**\n * Thrown when a Prisma `where` uses an operator the incremental matcher can't\n * evaluate in JS. The sync engine catches it and degrades that subscription to\n * a refetch, so it never produces a wrong result — only a less efficient one.\n */\nexport class UnsupportedFilterError extends Error {\n\tconstructor(operator: string) {\n\t\tsuper(\n\t\t\t`Cannot evaluate Prisma filter operator \"${operator}\" incrementally`\n\t\t);\n\t\tthis.name = 'UnsupportedFilterError';\n\t}\n}\n\nconst isPlainObject = (value: unknown): value is Record<string, unknown> =>\n\ttypeof value === 'object' &&\n\tvalue !== null &&\n\t!Array.isArray(value) &&\n\t!(value instanceof Date);\n\n/** Prisma equality semantics (a `null` operand means IS NULL). */\nconst equals = (value: unknown, operand: unknown): boolean => {\n\tif (operand === null) {\n\t\treturn value === null || value === undefined;\n\t}\n\tif (value instanceof Date && operand instanceof Date) {\n\t\treturn value.getTime() === operand.getTime();\n\t}\n\treturn value === operand;\n};\n\nconst order = (value: unknown): number | string =>\n\tvalue instanceof Date ? value.getTime() : (value as number | string);\n\nconst compare = (value: unknown, operand: unknown): number => {\n\tconst a = order(value);\n\tconst b = order(operand);\n\tif (a < b) {\n\t\treturn -1;\n\t}\n\tif (a > b) {\n\t\treturn 1;\n\t}\n\treturn 0;\n};\n\nconst comparable = (value: unknown): boolean =>\n\tvalue !== null && value !== undefined;\n\nconst FIELD_OPERATORS = new Set([\n\t'equals',\n\t'not',\n\t'in',\n\t'notIn',\n\t'lt',\n\t'lte',\n\t'gt',\n\t'gte',\n\t'contains',\n\t'startsWith',\n\t'endsWith'\n]);\n\n/** Evaluate a single field's condition (scalar equality or an operator object). */\nconst matchesField = (value: unknown, condition: unknown): boolean => {\n\tif (!isPlainObject(condition)) {\n\t\treturn equals(value, condition);\n\t}\n\t// Validate support up front: an unsupported operator must force a refetch\n\t// even when an earlier operator would short-circuit to false.\n\tfor (const operator of Object.keys(condition)) {\n\t\tif (!FIELD_OPERATORS.has(operator)) {\n\t\t\tthrow new UnsupportedFilterError(operator);\n\t\t}\n\t}\n\tfor (const [operator, operand] of Object.entries(condition)) {\n\t\tswitch (operator) {\n\t\t\tcase 'equals':\n\t\t\t\tif (!equals(value, operand)) return false;\n\t\t\t\tbreak;\n\t\t\tcase 'not':\n\t\t\t\tif (isPlainObject(operand)) {\n\t\t\t\t\tif (matchesField(value, operand)) return false;\n\t\t\t\t} else if (equals(value, operand)) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase 'in':\n\t\t\t\tif (\n\t\t\t\t\t!Array.isArray(operand) ||\n\t\t\t\t\t!operand.some((item) => equals(value, item))\n\t\t\t\t)\n\t\t\t\t\treturn false;\n\t\t\t\tbreak;\n\t\t\tcase 'notIn':\n\t\t\t\tif (\n\t\t\t\t\tArray.isArray(operand) &&\n\t\t\t\t\toperand.some((item) => equals(value, item))\n\t\t\t\t)\n\t\t\t\t\treturn false;\n\t\t\t\tbreak;\n\t\t\tcase 'lt':\n\t\t\t\tif (!comparable(value) || compare(value, operand) >= 0)\n\t\t\t\t\treturn false;\n\t\t\t\tbreak;\n\t\t\tcase 'lte':\n\t\t\t\tif (!comparable(value) || compare(value, operand) > 0)\n\t\t\t\t\treturn false;\n\t\t\t\tbreak;\n\t\t\tcase 'gt':\n\t\t\t\tif (!comparable(value) || compare(value, operand) <= 0)\n\t\t\t\t\treturn false;\n\t\t\t\tbreak;\n\t\t\tcase 'gte':\n\t\t\t\tif (!comparable(value) || compare(value, operand) < 0)\n\t\t\t\t\treturn false;\n\t\t\t\tbreak;\n\t\t\tcase 'contains':\n\t\t\t\tif (\n\t\t\t\t\ttypeof value !== 'string' ||\n\t\t\t\t\t!value.includes(String(operand))\n\t\t\t\t)\n\t\t\t\t\treturn false;\n\t\t\t\tbreak;\n\t\t\tcase 'startsWith':\n\t\t\t\tif (\n\t\t\t\t\ttypeof value !== 'string' ||\n\t\t\t\t\t!value.startsWith(String(operand))\n\t\t\t\t)\n\t\t\t\t\treturn false;\n\t\t\t\tbreak;\n\t\t\tcase 'endsWith':\n\t\t\t\tif (\n\t\t\t\t\ttypeof value !== 'string' ||\n\t\t\t\t\t!value.endsWith(String(operand))\n\t\t\t\t)\n\t\t\t\t\treturn false;\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\t// `mode`, relation filters, etc. — bail so the engine refetches.\n\t\t\t\tthrow new UnsupportedFilterError(operator);\n\t\t}\n\t}\n\treturn true;\n};\n\nconst toConditions = (value: unknown): PrismaWhere[] => {\n\tif (Array.isArray(value)) {\n\t\treturn value.filter(isPlainObject);\n\t}\n\treturn isPlainObject(value) ? [value] : [];\n};\n\n/**\n * Evaluate a Prisma `where` object against an in-memory row — the JS mirror of\n * the SQL filter, used for incremental matching. Supports field equality and the\n * `equals/not/in/notIn/lt/lte/gt/gte/contains/startsWith/endsWith` operators\n * plus `AND`/`OR`/`NOT`. Anything else throws {@link UnsupportedFilterError}, so\n * the engine falls back to a refetch.\n *\n * @example\n * matchesWhere({ userId: 5, status: { not: 'archived' } }, row)\n */\nexport const matchesWhere = (\n\twhere: PrismaWhere,\n\trow: Record<string, unknown>\n): boolean => {\n\tfor (const [field, condition] of Object.entries(where)) {\n\t\tif (field === 'AND') {\n\t\t\tif (\n\t\t\t\t!toConditions(condition).every((part) =>\n\t\t\t\t\tmatchesWhere(part, row)\n\t\t\t\t)\n\t\t\t)\n\t\t\t\treturn false;\n\t\t\tcontinue;\n\t\t}\n\t\tif (field === 'OR') {\n\t\t\tconst parts = toConditions(condition);\n\t\t\tif (\n\t\t\t\tparts.length > 0 &&\n\t\t\t\t!parts.some((part) => matchesWhere(part, row))\n\t\t\t)\n\t\t\t\treturn false;\n\t\t\tcontinue;\n\t\t}\n\t\tif (field === 'NOT') {\n\t\t\tif (toConditions(condition).some((part) => matchesWhere(part, row)))\n\t\t\t\treturn false;\n\t\t\tcontinue;\n\t\t}\n\t\tif (!matchesField(row[field], condition)) {\n\t\t\treturn false;\n\t\t}\n\t}\n\treturn true;\n};\n",
|
|
9
9
|
"import type {\n\tCollectionContext,\n\tCollectionDefinition\n} from '../../engine/collection';\nimport type { RowKey } from '../../engine/types';\nimport { matchesWhere } from './predicate';\nimport type { PrismaWhere } from './topics';\n\nexport type PrismaCollectionOptions<T, P, Ctx> = {\n\t/** Collection name (change-feed key and topic root). */\n\tname: string;\n\t/**\n\t * The query filter, written once. Used both to hydrate from the database\n\t * (passed to `find`) and, mirrored in JS, to match changed rows\n\t * incrementally. Receives the subscription's params and context.\n\t */\n\twhere: (params: P, ctx: Ctx) => PrismaWhere;\n\t/**\n\t * Run the database read for a given `where` — your Prisma call, e.g.\n\t * `(where) => prisma.order.findMany({ where })`.\n\t */\n\tfind: (\n\t\twhere: PrismaWhere,\n\t\tparams: P,\n\t\tctx: Ctx\n\t) => Promise<Iterable<T>> | Iterable<T>;\n\t/** Row identity. Defaults to `row.id`. */\n\tkey?: (row: T) => RowKey;\n\t/** Access control; return false (or throw) to deny. */\n\tauthorize?: (params: P, ctx: Ctx) => boolean | Promise<boolean>;\n};\n\n/**\n * Build a syncable {@link CollectionDefinition} for Prisma from a single filter:\n * `where` is written once and powers both `hydrate` (via your `find`) and the\n * incremental `match` (via {@link matchesWhere}) — no restating the WHERE.\n *\n * If the filter uses an operator the JS matcher can't evaluate, that change\n * degrades to a refetch (handled by the engine), so the result stays correct.\n *\n * @example\n * prismaCollection({\n * name: 'orders',\n * where: (p) => ({ userId: p.userId, status: 'open' }),\n * find: (where) => prisma.order.findMany({ where }),\n * authorize: (p, ctx) => p.userId === ctx.userId\n * });\n */\nexport const prismaCollection = <T, P = void, Ctx = CollectionContext>(\n\toptions: PrismaCollectionOptions<T, P, Ctx>\n): CollectionDefinition<T, P, Ctx> => ({\n\tname: options.name,\n\thydrate: (params, ctx) =>\n\t\toptions.find(options.where(params, ctx), params, ctx),\n\tmatch: (row, params, ctx) =>\n\t\tmatchesWhere(\n\t\t\toptions.where(params, ctx),\n\t\t\trow as Record<string, unknown>\n\t\t),\n\tkey: options.key,\n\tauthorize: options.authorize\n});\n"
|
|
10
10
|
],
|
|
11
|
-
"mappings": "
|
|
12
|
-
"debugId": "
|
|
11
|
+
"mappings": "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",
|
|
12
|
+
"debugId": "AFF3207442A931ED64756E2164756E21",
|
|
13
13
|
"names": []
|
|
14
14
|
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
// @bun
|
|
2
|
+
var __require = import.meta.require;
|
|
3
|
+
|
|
2
4
|
// src/engine/pollingSource.ts
|
|
3
5
|
var OP_BY_NAME = {
|
|
4
6
|
insert: "insert",
|
|
@@ -124,5 +126,5 @@ export {
|
|
|
124
126
|
createPollingChangeSource
|
|
125
127
|
};
|
|
126
128
|
|
|
127
|
-
//# debugId=
|
|
129
|
+
//# debugId=55545DA3CDEDC0F664756E2164756E21
|
|
128
130
|
//# sourceMappingURL=index.js.map
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"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",
|
|
6
6
|
"/**\n * SQLite CDC adapter for @absolutejs/sync (Tier 3, M5).\n *\n * SQLite has no `LISTEN/NOTIFY`, and its `update_hook` isn't reachable from the\n * JS runtimes we target, so out-of-band writes are caught with the portable\n * changelog (outbox) pattern: install triggers that append every row change to a\n * changelog table, then tail it with {@link createPollingChangeSource}. Polling a\n * local SQLite table is cheap (same process, no network).\n *\n * Dependency-free — it only generates SQL; bring your own client (`bun:sqlite`,\n * better-sqlite3, …) to run it and to back the poll query.\n */\n\nexport {\n\tcreatePollingChangeSource,\n\tparseOutboxRow\n} from '../../engine/pollingSource';\nexport type {\n\tOutboxRow,\n\tPollingChangeSourceOptions\n} from '../../engine/pollingSource';\n\nconst DEFAULT_CHANGELOG = 'absolute_sync_changelog';\nconst DEFAULT_PREFIX = 'absolute_sync';\nconst OPS = ['insert', 'update', 'delete'] as const;\n\nexport type SqliteChangelogOptions = {\n\t/** Table name → the column names to capture in the change payload. */\n\ttables: Record<string, string[]>;\n\t/** Changelog table name. Defaults to `absolute_sync_changelog`. */\n\tchangelogTable?: string;\n\t/** Trigger name prefix. Defaults to `absolute_sync`. */\n\tprefix?: string;\n};\n\n/**\n * Generate the SQL that installs the changelog table and per-table\n * insert/update/delete triggers — run it once (e.g. in a migration). Each\n * trigger appends `{ tbl, op, payload }` (payload built with `json_object` from\n * the listed columns) to the changelog for {@link createPollingChangeSource}.\n *\n * The statements are `;`-separated; run them as a script, or split on `;` if your\n * driver executes one statement per call.\n */\nexport const sqliteChangelogSchema = (\n\toptions: SqliteChangelogOptions\n): string => {\n\tconst changelog = options.changelogTable ?? DEFAULT_CHANGELOG;\n\tconst prefix = options.prefix ?? DEFAULT_PREFIX;\n\n\tconst createTable = [\n\t\t`CREATE TABLE IF NOT EXISTS ${changelog} (`,\n\t\t'\\tseq INTEGER PRIMARY KEY AUTOINCREMENT,',\n\t\t'\\ttbl TEXT NOT NULL,',\n\t\t'\\top TEXT NOT NULL,',\n\t\t'\\tpayload TEXT NOT NULL,',\n\t\t\"\\tcreated_at TEXT NOT NULL DEFAULT (datetime('now'))\",\n\t\t');'\n\t].join('\\n');\n\n\tconst jsonObject = (columns: string[], ref: 'NEW' | 'OLD'): string =>\n\t\t`json_object(${columns\n\t\t\t.map((column) => `'${column}', ${ref}.${column}`)\n\t\t\t.join(', ')})`;\n\n\tconst triggers = Object.entries(options.tables).flatMap(\n\t\t([table, columns]) =>\n\t\t\tOPS.map((op) => {\n\t\t\t\tconst ref = op === 'delete' ? 'OLD' : 'NEW';\n\t\t\t\tconst name = `${prefix}_${table}_${op}`;\n\t\t\t\treturn [\n\t\t\t\t\t`DROP TRIGGER IF EXISTS ${name};`,\n\t\t\t\t\t`CREATE TRIGGER ${name} AFTER ${op.toUpperCase()} ON ${table}`,\n\t\t\t\t\t'BEGIN',\n\t\t\t\t\t`\\tINSERT INTO ${changelog} (tbl, op, payload)`,\n\t\t\t\t\t`\\tVALUES ('${table}', '${op}', ${jsonObject(columns, ref)});`,\n\t\t\t\t\t'END;'\n\t\t\t\t].join('\\n');\n\t\t\t})\n\t);\n\n\treturn [createTable, ...triggers].join('\\n\\n');\n};\n"
|
|
7
7
|
],
|
|
8
|
-
"mappings": "
|
|
9
|
-
"debugId": "
|
|
8
|
+
"mappings": "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",
|
|
9
|
+
"debugId": "55545DA3CDEDC0F664756E2164756E21",
|
|
10
10
|
"names": []
|
|
11
11
|
}
|
package/dist/engine/index.d.ts
CHANGED
|
@@ -42,6 +42,7 @@ export { defineSchedule } from './schedule';
|
|
|
42
42
|
export type { ScheduleContext, ScheduleDefinition } from './schedule';
|
|
43
43
|
export { defineMutation } from './mutation';
|
|
44
44
|
export type { MutationActions, MutationDefinition, MutationHandler, TableWriter, TransactionRunner } from './mutation';
|
|
45
|
+
export type { SandboxConfig } from './sandbox';
|
|
45
46
|
export { createSyncEngine, SchemaError, UnauthorizedError } from './syncEngine';
|
|
46
47
|
export type { CrdtFields, SubscribeArgs, Subscription, SyncEngine } from './syncEngine';
|
|
47
48
|
export type { CrdtMergeable } from '../crdt';
|
package/dist/engine/index.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
// @bun
|
|
2
|
+
var __require = import.meta.require;
|
|
3
|
+
|
|
2
4
|
// src/engine/materializedView.ts
|
|
3
5
|
var emptyDiff = () => ({
|
|
4
6
|
added: [],
|
|
@@ -1039,6 +1041,76 @@ var createVectorIndex = (options) => {
|
|
|
1039
1041
|
var defineSchedule = (definition) => definition;
|
|
1040
1042
|
// src/engine/mutation.ts
|
|
1041
1043
|
var defineMutation = (definition) => definition;
|
|
1044
|
+
// src/engine/sandbox.ts
|
|
1045
|
+
var isolatedJscModule;
|
|
1046
|
+
var loadIsolatedJsc = async () => {
|
|
1047
|
+
if (isolatedJscModule !== undefined)
|
|
1048
|
+
return isolatedJscModule;
|
|
1049
|
+
try {
|
|
1050
|
+
isolatedJscModule = await import("@absolutejs/isolated-jsc");
|
|
1051
|
+
return isolatedJscModule;
|
|
1052
|
+
} catch (error) {
|
|
1053
|
+
throw new Error('sandboxedHandler requires the optional peer "@absolutejs/isolated-jsc". Install it with: bun add @absolutejs/isolated-jsc', { cause: error });
|
|
1054
|
+
}
|
|
1055
|
+
};
|
|
1056
|
+
var wrap = (source) => `
|
|
1057
|
+
(async () => {
|
|
1058
|
+
const userFn = (${source});
|
|
1059
|
+
if (typeof userFn !== 'function') {
|
|
1060
|
+
throw new Error(
|
|
1061
|
+
'sandboxedHandler must evaluate to (args, ctx, actions) => result; got ' +
|
|
1062
|
+
typeof userFn
|
|
1063
|
+
);
|
|
1064
|
+
}
|
|
1065
|
+
const actions = {
|
|
1066
|
+
insert: __syncActionInsert,
|
|
1067
|
+
update: __syncActionUpdate,
|
|
1068
|
+
delete: __syncActionDelete,
|
|
1069
|
+
change: __syncActionChange
|
|
1070
|
+
};
|
|
1071
|
+
return await userFn(args, ctx, actions);
|
|
1072
|
+
})()
|
|
1073
|
+
`;
|
|
1074
|
+
var compile = async (source, config) => {
|
|
1075
|
+
const { createIsolate } = await loadIsolatedJsc();
|
|
1076
|
+
const isolate = await createIsolate({
|
|
1077
|
+
memoryLimit: config.memoryLimit ?? 32
|
|
1078
|
+
});
|
|
1079
|
+
const script = await isolate.compileScript(wrap(source));
|
|
1080
|
+
return { isolate, script, timeoutMs: config.timeout ?? 5000 };
|
|
1081
|
+
};
|
|
1082
|
+
var makeSandboxedHandler = (source, config = {}) => {
|
|
1083
|
+
let pending;
|
|
1084
|
+
const getCompiled = async () => {
|
|
1085
|
+
if (pending !== undefined) {
|
|
1086
|
+
const compiled = await pending;
|
|
1087
|
+
if (!compiled.isolate.isDisposed)
|
|
1088
|
+
return compiled;
|
|
1089
|
+
pending = undefined;
|
|
1090
|
+
}
|
|
1091
|
+
pending = compile(source, config);
|
|
1092
|
+
return pending;
|
|
1093
|
+
};
|
|
1094
|
+
return async (args, ctx, actions) => {
|
|
1095
|
+
const { Reference } = await loadIsolatedJsc();
|
|
1096
|
+
const compiled = await getCompiled();
|
|
1097
|
+
const context = await compiled.isolate.createContext();
|
|
1098
|
+
try {
|
|
1099
|
+
await context.setGlobal("args", args);
|
|
1100
|
+
await context.setGlobal("ctx", ctx);
|
|
1101
|
+
await context.setGlobal("__syncActionInsert", new Reference((table, data) => actions.insert(table, data)));
|
|
1102
|
+
await context.setGlobal("__syncActionUpdate", new Reference((table, data) => actions.update(table, data)));
|
|
1103
|
+
await context.setGlobal("__syncActionDelete", new Reference((table, row) => actions.delete(table, row)));
|
|
1104
|
+
await context.setGlobal("__syncActionChange", new Reference((collection, change) => actions.change(collection, change)));
|
|
1105
|
+
return await compiled.script.run(context, {
|
|
1106
|
+
timeout: compiled.timeoutMs
|
|
1107
|
+
});
|
|
1108
|
+
} finally {
|
|
1109
|
+
await context.dispose().catch(() => {});
|
|
1110
|
+
}
|
|
1111
|
+
};
|
|
1112
|
+
};
|
|
1113
|
+
|
|
1042
1114
|
// src/engine/syncEngine.ts
|
|
1043
1115
|
class UnauthorizedError extends Error {
|
|
1044
1116
|
constructor(subject) {
|
|
@@ -1115,6 +1187,7 @@ var equalsIgnoringScore = (a, b) => {
|
|
|
1115
1187
|
var createSyncEngine = (options = {}) => {
|
|
1116
1188
|
const registry = new Map;
|
|
1117
1189
|
const mutations = new Map;
|
|
1190
|
+
const sandboxRunners = new Map;
|
|
1118
1191
|
const writers = new Map;
|
|
1119
1192
|
const readers = new Map;
|
|
1120
1193
|
const schedules = new Map;
|
|
@@ -1160,6 +1233,37 @@ var createSyncEngine = (options = {}) => {
|
|
|
1160
1233
|
const changeLogSize = options.changeLogSize ?? 1024;
|
|
1161
1234
|
const changeLog = [];
|
|
1162
1235
|
let version = 0;
|
|
1236
|
+
const reactiveCacheMax = options.reactiveCache?.max ?? 256;
|
|
1237
|
+
const reactiveCacheTtlMs = options.reactiveCache?.ttlMs ?? 60000;
|
|
1238
|
+
const cachedReruns = new Map;
|
|
1239
|
+
const touchCacheEntry = (key, entry) => {
|
|
1240
|
+
cachedReruns.delete(key);
|
|
1241
|
+
cachedReruns.set(key, entry);
|
|
1242
|
+
};
|
|
1243
|
+
const readCacheEntry = (key) => {
|
|
1244
|
+
if (reactiveCacheMax <= 0)
|
|
1245
|
+
return;
|
|
1246
|
+
const entry = cachedReruns.get(key);
|
|
1247
|
+
if (entry === undefined)
|
|
1248
|
+
return;
|
|
1249
|
+
if (reactiveCacheTtlMs > 0 && entry.expiresAt < Date.now()) {
|
|
1250
|
+
cachedReruns.delete(key);
|
|
1251
|
+
return;
|
|
1252
|
+
}
|
|
1253
|
+
touchCacheEntry(key, entry);
|
|
1254
|
+
return entry;
|
|
1255
|
+
};
|
|
1256
|
+
const writeCacheEntry = (entry) => {
|
|
1257
|
+
if (reactiveCacheMax <= 0)
|
|
1258
|
+
return;
|
|
1259
|
+
cachedReruns.set(entry.rerunKey, entry);
|
|
1260
|
+
while (cachedReruns.size > reactiveCacheMax) {
|
|
1261
|
+
const oldest = cachedReruns.keys().next().value;
|
|
1262
|
+
if (oldest === undefined)
|
|
1263
|
+
break;
|
|
1264
|
+
cachedReruns.delete(oldest);
|
|
1265
|
+
}
|
|
1266
|
+
};
|
|
1163
1267
|
const activityListeners = new Set;
|
|
1164
1268
|
const emitActivity = (event) => {
|
|
1165
1269
|
for (const listener of activityListeners) {
|
|
@@ -1443,8 +1547,19 @@ var createSyncEngine = (options = {}) => {
|
|
|
1443
1547
|
return { added, removed, changed };
|
|
1444
1548
|
};
|
|
1445
1549
|
const inRange = (dep, change) => dep.table === change.table && (change.key !== undefined && dep.keys.has(change.key) || dep.predicate(change.row));
|
|
1446
|
-
const
|
|
1550
|
+
const readSetOverlaps = (readTables, readKeys, rangeDeps, changes) => changes.some((change) => readTables.has(change.table) || change.key !== undefined && readKeys.has(depKey(change.table, change.key)) || rangeDeps.some((dep) => inRange(dep, change)));
|
|
1551
|
+
const isReactiveAffected = (sub, changes) => readSetOverlaps(sub.readTables, sub.readKeys, sub.rangeDeps, changes);
|
|
1552
|
+
const invalidateCacheForChanges = (changes) => {
|
|
1553
|
+
if (cachedReruns.size === 0)
|
|
1554
|
+
return;
|
|
1555
|
+
for (const [key, entry] of cachedReruns) {
|
|
1556
|
+
if (readSetOverlaps(entry.readTables, entry.readKeys, entry.rangeDeps, changes)) {
|
|
1557
|
+
cachedReruns.delete(key);
|
|
1558
|
+
}
|
|
1559
|
+
}
|
|
1560
|
+
};
|
|
1447
1561
|
const reactivePairs = async (changes) => {
|
|
1562
|
+
invalidateCacheForChanges(changes);
|
|
1448
1563
|
const pairs = [];
|
|
1449
1564
|
const sharedRuns = new Map;
|
|
1450
1565
|
for (const sub of reactiveSubs) {
|
|
@@ -1465,6 +1580,19 @@ var createSyncEngine = (options = {}) => {
|
|
|
1465
1580
|
pairs.push([sub, diff]);
|
|
1466
1581
|
}
|
|
1467
1582
|
}
|
|
1583
|
+
for (const [key, runPromise] of sharedRuns) {
|
|
1584
|
+
runPromise.then(({ rows, readTables, readKeys, rangeDeps }) => {
|
|
1585
|
+
writeCacheEntry({
|
|
1586
|
+
expiresAt: Date.now() + reactiveCacheTtlMs,
|
|
1587
|
+
rangeDeps,
|
|
1588
|
+
readKeys,
|
|
1589
|
+
readTables,
|
|
1590
|
+
rerunKey: key,
|
|
1591
|
+
rows,
|
|
1592
|
+
version
|
|
1593
|
+
});
|
|
1594
|
+
}).catch(() => {});
|
|
1595
|
+
}
|
|
1468
1596
|
return pairs;
|
|
1469
1597
|
};
|
|
1470
1598
|
const ensureSearchIndex = async (definition) => {
|
|
@@ -1698,7 +1826,25 @@ var createSyncEngine = (options = {}) => {
|
|
|
1698
1826
|
const rows = [...await definition.run({ ctx, db, params })];
|
|
1699
1827
|
return { rangeDeps, readKeys, readTables, rows };
|
|
1700
1828
|
};
|
|
1701
|
-
const
|
|
1829
|
+
const rerunKey = stableSubKey(collection, params, ctx);
|
|
1830
|
+
const cached = readCacheEntry(rerunKey);
|
|
1831
|
+
const first = cached !== undefined ? {
|
|
1832
|
+
rangeDeps: cached.rangeDeps,
|
|
1833
|
+
readKeys: cached.readKeys,
|
|
1834
|
+
readTables: cached.readTables,
|
|
1835
|
+
rows: cached.rows
|
|
1836
|
+
} : await rerun();
|
|
1837
|
+
if (cached === undefined) {
|
|
1838
|
+
writeCacheEntry({
|
|
1839
|
+
expiresAt: Date.now() + reactiveCacheTtlMs,
|
|
1840
|
+
rangeDeps: first.rangeDeps,
|
|
1841
|
+
readKeys: first.readKeys,
|
|
1842
|
+
readTables: first.readTables,
|
|
1843
|
+
rerunKey,
|
|
1844
|
+
rows: first.rows,
|
|
1845
|
+
version
|
|
1846
|
+
});
|
|
1847
|
+
}
|
|
1702
1848
|
const current = new Map;
|
|
1703
1849
|
for (const row of first.rows) {
|
|
1704
1850
|
current.set(definition.key(row), row);
|
|
@@ -1709,7 +1855,7 @@ var createSyncEngine = (options = {}) => {
|
|
|
1709
1855
|
collection,
|
|
1710
1856
|
key: definition.key,
|
|
1711
1857
|
rerun,
|
|
1712
|
-
rerunKey
|
|
1858
|
+
rerunKey,
|
|
1713
1859
|
current,
|
|
1714
1860
|
readTables: first.readTables,
|
|
1715
1861
|
readKeys: first.readKeys,
|
|
@@ -1918,7 +2064,16 @@ var createSyncEngine = (options = {}) => {
|
|
|
1918
2064
|
return total;
|
|
1919
2065
|
},
|
|
1920
2066
|
registerMutation: (mutation) => {
|
|
2067
|
+
if (mutation.handler === undefined && mutation.sandboxedHandler === undefined) {
|
|
2068
|
+
throw new Error(`Mutation "${mutation.name}" must define either \`handler\` or \`sandboxedHandler\``);
|
|
2069
|
+
}
|
|
2070
|
+
if (mutation.handler !== undefined && mutation.sandboxedHandler !== undefined) {
|
|
2071
|
+
throw new Error(`Mutation "${mutation.name}" defines both \`handler\` and \`sandboxedHandler\` \u2014 pick one`);
|
|
2072
|
+
}
|
|
1921
2073
|
mutations.set(mutation.name, mutation);
|
|
2074
|
+
if (mutation.sandboxedHandler !== undefined) {
|
|
2075
|
+
sandboxRunners.set(mutation.name, makeSandboxedHandler(mutation.sandboxedHandler, mutation.sandbox));
|
|
2076
|
+
}
|
|
1922
2077
|
},
|
|
1923
2078
|
registerWriter: (table, writer) => {
|
|
1924
2079
|
writers.set(table, writer);
|
|
@@ -1958,9 +2113,11 @@ var createSyncEngine = (options = {}) => {
|
|
|
1958
2113
|
throw new UnauthorizedError(`run mutation "${name}"`);
|
|
1959
2114
|
}
|
|
1960
2115
|
}
|
|
2116
|
+
const sandboxRunner = sandboxRunners.get(name);
|
|
2117
|
+
const invokeHandler = sandboxRunner !== undefined ? sandboxRunner : (a, c, actions) => Promise.resolve(mutation.handler(a, c, actions));
|
|
1961
2118
|
const runHandler = async (tx) => {
|
|
1962
2119
|
const { actions, buffered } = makeActions(tx, ctx, true);
|
|
1963
|
-
const result = await
|
|
2120
|
+
const result = await invokeHandler(args, ctx, actions);
|
|
1964
2121
|
return { buffered, result };
|
|
1965
2122
|
};
|
|
1966
2123
|
try {
|
|
@@ -2336,5 +2493,5 @@ export {
|
|
|
2336
2493
|
SEARCH_SCORE_FIELD
|
|
2337
2494
|
};
|
|
2338
2495
|
|
|
2339
|
-
//# debugId=
|
|
2496
|
+
//# debugId=03508BC44A5CF91064756E2164756E21
|
|
2340
2497
|
//# sourceMappingURL=index.js.map
|