@absolutejs/sync 0.1.0 → 0.3.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 +60 -36
- package/dist/adapters/drizzle/collection.d.ts +27 -0
- package/dist/adapters/drizzle/index.d.ts +3 -0
- package/dist/adapters/drizzle/index.js +139 -2
- package/dist/adapters/drizzle/index.js.map +5 -3
- package/dist/adapters/drizzle/predicate.d.ts +20 -0
- package/dist/angular/index.js +99 -5
- package/dist/angular/index.js.map +3 -3
- package/dist/client/index.d.ts +6 -2
- package/dist/client/index.js +456 -5
- package/dist/client/index.js.map +6 -4
- package/dist/client/presence.d.ts +37 -0
- package/dist/client/syncClient.d.ts +53 -0
- package/dist/client/syncCollection.d.ts +52 -0
- package/dist/engine/cluster.d.ts +41 -0
- package/dist/engine/connection.d.ts +33 -1
- package/dist/engine/index.d.ts +8 -2
- package/dist/engine/index.js +542 -37
- package/dist/engine/index.js.map +9 -6
- package/dist/engine/mutation.d.ts +39 -3
- package/dist/engine/presence.d.ts +46 -0
- package/dist/engine/reactive.d.ts +67 -0
- package/dist/engine/socket.d.ts +4 -1
- package/dist/engine/syncEngine.d.ts +33 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +175 -9
- package/dist/index.js.map +6 -5
- package/dist/react/index.js +99 -5
- package/dist/react/index.js.map +3 -3
- package/dist/svelte/index.js +99 -5
- package/dist/svelte/index.js.map +3 -3
- package/dist/vue/index.js +99 -5
- package/dist/vue/index.js.map +3 -3
- package/package.json +3 -1
package/README.md
CHANGED
|
@@ -149,7 +149,11 @@ import { syncSocket } from '@absolutejs/sync';
|
|
|
149
149
|
import { createSyncEngine, defineMutation } from '@absolutejs/sync/engine';
|
|
150
150
|
import { prismaCollection } from '@absolutejs/sync/prisma';
|
|
151
151
|
|
|
152
|
-
|
|
152
|
+
// `transaction` runs every mutation in your DB's transaction (any ORM), so its
|
|
153
|
+
// writes are ACID and the diff is emitted only after the commit.
|
|
154
|
+
const engine = createSyncEngine({
|
|
155
|
+
transaction: (run) => prisma.$transaction(run)
|
|
156
|
+
});
|
|
153
157
|
|
|
154
158
|
engine.register(
|
|
155
159
|
prismaCollection({
|
|
@@ -160,16 +164,22 @@ engine.register(
|
|
|
160
164
|
})
|
|
161
165
|
);
|
|
162
166
|
|
|
167
|
+
// Teach the engine how to persist the table once — now writes auto-emit. The
|
|
168
|
+
// third arg is the transaction handle, so the write joins the mutation's tx.
|
|
169
|
+
engine.registerWriter('orders', {
|
|
170
|
+
insert: (data, ctx, tx) =>
|
|
171
|
+
tx.order.create({ data: { ...data, userId: ctx.userId } }),
|
|
172
|
+
update: (data, _ctx, tx) =>
|
|
173
|
+
tx.order.update({ where: { id: data.id }, data }),
|
|
174
|
+
delete: (row, _ctx, tx) => tx.order.delete({ where: { id: row.id } })
|
|
175
|
+
});
|
|
176
|
+
|
|
163
177
|
engine.registerMutation(
|
|
164
178
|
defineMutation({
|
|
165
179
|
name: 'createOrder',
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
});
|
|
170
|
-
await actions.change('orders', { op: 'insert', row: order });
|
|
171
|
-
return order;
|
|
172
|
-
}
|
|
180
|
+
// Persists AND goes live in one step — you can't forget to emit, and the
|
|
181
|
+
// diff carries the stored row (db-assigned id). Commits atomically.
|
|
182
|
+
handler: (args, ctx, actions) => actions.insert('orders', args)
|
|
173
183
|
})
|
|
174
184
|
);
|
|
175
185
|
|
|
@@ -211,8 +221,12 @@ await orders.mutate({
|
|
|
211
221
|
`ChangeSource` — e.g. `postgresChangeSource` (`/postgres`) over `LISTEN/NOTIFY`,
|
|
212
222
|
wired with `engine.connectSource(...)` and the trigger SQL from
|
|
213
223
|
`postgresNotifyTrigger`.
|
|
214
|
-
- **Offline.** Pending mutations replay on reconnect; pass `storage`
|
|
215
|
-
(e.g. `localStorageMutationStorage`) to
|
|
224
|
+
- **Offline & local-first.** Pending mutations replay on reconnect; pass `storage`
|
|
225
|
+
(e.g. `localStorageMutationStorage`) to let unconfirmed writes survive a reload.
|
|
226
|
+
Pass `cache` (`localStorageCollectionCache` or `indexedDbCollectionCache`) to
|
|
227
|
+
persist the confirmed rows too — reads are then instant on reload and available
|
|
228
|
+
offline, and the socket resumes from the cached version (a catch-up diff if the
|
|
229
|
+
server's changelog still covers it, a fresh snapshot otherwise).
|
|
216
230
|
- **Access control is mandatory.** Each collection's `authorize` gates subscribe and
|
|
217
231
|
its filter scopes rows, so a change to a row a caller can't see never reaches them.
|
|
218
232
|
|
|
@@ -251,13 +265,17 @@ it, ~3 store round-trips every 20ms ran the voice pipeline far slower than real
|
|
|
251
265
|
|
|
252
266
|
### `@absolutejs/sync/client`
|
|
253
267
|
|
|
254
|
-
| Export | What it is
|
|
255
|
-
| ------------------------------------------------- |
|
|
256
|
-
| `createSyncSubscriber({ topics, onEvent, url? })` | Browser SSE client.
|
|
257
|
-
| `createLiveQuery({ topics, fetcher, ... })` | Hydrate-once, refetch-on-event observable query store.
|
|
258
|
-
| `jsonFetcher(url, init?)` | Default `fetcher`: GET + JSON parse, forwards the abort signal.
|
|
259
|
-
| `createSyncCollection({ url, collection, ... })` | Live diff-driven collection store with optimistic `mutate`.
|
|
260
|
-
| `
|
|
268
|
+
| Export | What it is |
|
|
269
|
+
| ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
270
|
+
| `createSyncSubscriber({ topics, onEvent, url? })` | Browser SSE client. |
|
|
271
|
+
| `createLiveQuery({ topics, fetcher, ... })` | Hydrate-once, refetch-on-event observable query store. |
|
|
272
|
+
| `jsonFetcher(url, init?)` | Default `fetcher`: GET + JSON parse, forwards the abort signal. |
|
|
273
|
+
| `createSyncCollection({ url, collection, ... })` | Live diff-driven collection store with optimistic `mutate`. |
|
|
274
|
+
| `createSyncClient({ url })` | One socket, many collections (`client.collection(...)`). Applies a multi-collection mutation's diffs as one **consistent frame** — no torn cross-collection paint. |
|
|
275
|
+
| `createPresence({ url, room, state })` | Join a presence room: see who's online / typing (`get` + `subscribe`) and publish your own state (`set`). |
|
|
276
|
+
| `localStorageMutationStorage(key)` | `localStorage`-backed offline write queue for `createSyncCollection`. |
|
|
277
|
+
| `localStorageCollectionCache(key)` | `localStorage`-backed local-first read cache: confirmed rows survive a reload, resume from the cached version. |
|
|
278
|
+
| `indexedDbCollectionCache({ key, ... })` | IndexedDB-backed local-first read cache — durable, large-capacity. Same resume semantics, async storage. |
|
|
261
279
|
|
|
262
280
|
### Framework bindings — `@absolutejs/sync/{react,vue,svelte,angular}`
|
|
263
281
|
|
|
@@ -291,16 +309,20 @@ mutate({
|
|
|
291
309
|
|
|
292
310
|
### `@absolutejs/sync/engine`
|
|
293
311
|
|
|
294
|
-
| Export
|
|
295
|
-
|
|
|
296
|
-
| `createSyncEngine()`
|
|
297
|
-
| `defineCollection({ name, hydrate, key?, match?, authorize?, tables? })`
|
|
298
|
-
| `defineMutation({ name, handler, authorize? })`
|
|
299
|
-
| `
|
|
300
|
-
| `
|
|
301
|
-
| `
|
|
302
|
-
| `
|
|
303
|
-
| `
|
|
312
|
+
| Export | What it is |
|
|
313
|
+
| --------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
314
|
+
| `createSyncEngine()` | Registry + view syncer: `register`, `subscribe`, `applyChange`, `connectSource`, `registerMutation`, `registerWriter`, `runMutation`. |
|
|
315
|
+
| `defineCollection({ name, hydrate, key?, match?, authorize?, tables? })` | Define a syncable collection. |
|
|
316
|
+
| `defineMutation({ name, handler, authorize? })` | Define a server mutation. Its `handler` gets `actions.insert/update/delete` (write through a registered `TableWriter` → persists + emits in one step) plus `actions.change` (escape hatch). Changes commit atomically. |
|
|
317
|
+
| `registerWriter(table, { insert, update, delete })` | Teach the engine how to persist a table (any ORM), so writes auto-emit — you can't write without going live. |
|
|
318
|
+
| `createAggregate({ key, groupBy?, value? })` | Incremental count/sum/avg/min/max by group. |
|
|
319
|
+
| `createMaterializedView({ key, match, equals? })` | The predicate-matching IVM primitive (`apply`/`reset` → diffs). |
|
|
320
|
+
| `createPollingChangeSource({ poll, intervalMs?, startSeq?, onProcessed? })` | DB-agnostic CDC `ChangeSource` that tails a changelog (outbox) table. |
|
|
321
|
+
| `engine.connectCluster(bus)` + `createInMemoryClusterBus()` | Horizontal scale: fan changes across server instances over a `ClusterBus` (BYO Redis/Postgres; in-memory bus for dev). |
|
|
322
|
+
| `createPresenceHub()` + `syncSocket({ engine, presence })` | Ephemeral room-scoped presence (online / typing / cursors) over the same socket — not persisted, auto-cleaned on disconnect. |
|
|
323
|
+
| `query(source).filter().map().join().leftJoin().groupBy().orderBy()` | Declarative incremental query builder (the operator graph). |
|
|
324
|
+
| `defineGraphCollection({ name, query, key, authorize? })` | Run a `query` as a live collection. |
|
|
325
|
+
| `defineReactiveQuery({ name, run, key })` + `registerReactive` / `registerReader` | Read-set-tracked query: `run(ctx)` reads via `ctx.db` (`all`/`get`/`where`) and re-runs only when the rows/ranges it read change — no `match`, no manual emit. |
|
|
304
326
|
|
|
305
327
|
### `@absolutejs/sync/postgres`
|
|
306
328
|
|
|
@@ -327,15 +349,17 @@ mutate({
|
|
|
327
349
|
|
|
328
350
|
### `@absolutejs/sync/drizzle` and `@absolutejs/sync/prisma`
|
|
329
351
|
|
|
330
|
-
| Export | What it is
|
|
331
|
-
| --------------------------------------------------------------------- |
|
|
332
|
-
| `deriveReadTopics(table\|model, where?, options?)` | Topics a read depends on (`{ topics, rowLevel }`).
|
|
333
|
-
| `publishChange(hub, table\|model, { keys?, op? })` | Publish the table topic + a row topic per key.
|
|
334
|
-
| `publishRows(hub, table\|model, rows, { keyField?/keyColumn?, op? })` | Publish topics for returned/created records.
|
|
335
|
-
| `publishWhere(hub, table\|model, where, { ..., op? })` | Publish topics for an update/delete filter.
|
|
336
|
-
| `tableTopic` / `keyTopic` | The shared topic vocabulary both sides speak.
|
|
337
|
-
| `prismaCollection({ name, where, find, ... })` (prisma) | A sync-engine collection; one `where` → hydrate + matcher.
|
|
338
|
-
| `matchesWhere(where, row)` (prisma) | Evaluate a Prisma `where` against a row (the matcher).
|
|
352
|
+
| Export | What it is |
|
|
353
|
+
| --------------------------------------------------------------------- | ----------------------------------------------------------- |
|
|
354
|
+
| `deriveReadTopics(table\|model, where?, options?)` | Topics a read depends on (`{ topics, rowLevel }`). |
|
|
355
|
+
| `publishChange(hub, table\|model, { keys?, op? })` | Publish the table topic + a row topic per key. |
|
|
356
|
+
| `publishRows(hub, table\|model, rows, { keyField?/keyColumn?, op? })` | Publish topics for returned/created records. |
|
|
357
|
+
| `publishWhere(hub, table\|model, where, { ..., op? })` | Publish topics for an update/delete filter. |
|
|
358
|
+
| `tableTopic` / `keyTopic` | The shared topic vocabulary both sides speak. |
|
|
359
|
+
| `prismaCollection({ name, where, find, ... })` (prisma) | A sync-engine collection; one `where` → hydrate + matcher. |
|
|
360
|
+
| `matchesWhere(where, row)` (prisma) | Evaluate a Prisma `where` against a row (the matcher). |
|
|
361
|
+
| `drizzleCollection({ name, table, where, find, ... })` (drizzle) | Same one-`where`→hydrate+matcher, for Drizzle. |
|
|
362
|
+
| `matchesDrizzleWhere(table, where, row)` (drizzle) | Evaluate a Drizzle SQL `where` against a row (the matcher). |
|
|
339
363
|
|
|
340
364
|
## License
|
|
341
365
|
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { SQL, Table } from 'drizzle-orm';
|
|
2
|
+
import type { CollectionContext, CollectionDefinition } from '../../engine/collection';
|
|
3
|
+
import type { RowKey } from '../../engine/types';
|
|
4
|
+
export type DrizzleCollectionOptions<T, P = void, Ctx = CollectionContext> = {
|
|
5
|
+
/** Collection name clients subscribe to. */
|
|
6
|
+
name: string;
|
|
7
|
+
/** The Drizzle table this collection reads (drives change routing + key). */
|
|
8
|
+
table: Table;
|
|
9
|
+
/** The query filter, written once — powers both hydrate and the matcher. */
|
|
10
|
+
where: (params: P, ctx: Ctx) => SQL;
|
|
11
|
+
/** Run the read for `where` (your `db.select()...`), returning the rows. */
|
|
12
|
+
find: (where: SQL, params: P, ctx: Ctx) => Promise<Iterable<T>> | Iterable<T>;
|
|
13
|
+
/** Row identity. Defaults to the table's single primary key (else `row.id`). */
|
|
14
|
+
key?: (row: T) => RowKey;
|
|
15
|
+
/** Key column JS property, if not the table's primary key. */
|
|
16
|
+
keyColumn?: string;
|
|
17
|
+
/** Access control; return false (or throw) to deny the subscription. */
|
|
18
|
+
authorize?: (params: P, ctx: Ctx) => boolean | Promise<boolean>;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* A sync-engine collection from one Drizzle query — the Drizzle counterpart to
|
|
22
|
+
* `prismaCollection`. You write the `where` once: it drives the DB read
|
|
23
|
+
* (`hydrate`) AND the incremental `match` (via {@link matchesDrizzleWhere}), so
|
|
24
|
+
* the two can't drift and you never hand-maintain a separate predicate. A filter
|
|
25
|
+
* the matcher can't evaluate falls back to a refetch, never a wrong result.
|
|
26
|
+
*/
|
|
27
|
+
export declare const drizzleCollection: <T, P = void, Ctx = CollectionContext>(options: DrizzleCollectionOptions<T, P, Ctx>) => CollectionDefinition<T, P, Ctx>;
|
|
@@ -11,6 +11,9 @@
|
|
|
11
11
|
* narrowing to a single row when a filter is a simple primary-key equality.
|
|
12
12
|
*/
|
|
13
13
|
export { keyTopic, tableTopic } from './topics';
|
|
14
|
+
export { drizzleCollection } from './collection';
|
|
15
|
+
export type { DrizzleCollectionOptions } from './collection';
|
|
16
|
+
export { matchesDrizzleWhere, UnsupportedDrizzleFilterError } from './predicate';
|
|
14
17
|
export { deriveReadTopics } from './read';
|
|
15
18
|
export type { DeriveReadTopicsOptions, DerivedReadTopics } from './read';
|
|
16
19
|
export { publishChange, publishRows, publishWhere } from './write';
|
|
@@ -83,6 +83,140 @@ var extractRowKeys = (table, rows, keyColumn) => {
|
|
|
83
83
|
}
|
|
84
84
|
return keys;
|
|
85
85
|
};
|
|
86
|
+
// src/adapters/drizzle/collection.ts
|
|
87
|
+
import { getTableName as getTableName2 } from "drizzle-orm";
|
|
88
|
+
|
|
89
|
+
// src/adapters/drizzle/predicate.ts
|
|
90
|
+
import { Column as Column2, getTableColumns as getTableColumns2, is as is2, Param as Param2, SQL as SQL2 } from "drizzle-orm";
|
|
91
|
+
|
|
92
|
+
class UnsupportedDrizzleFilterError extends Error {
|
|
93
|
+
constructor(detail) {
|
|
94
|
+
super(`Cannot evaluate Drizzle filter "${detail}" incrementally`);
|
|
95
|
+
this.name = "UnsupportedDrizzleFilterError";
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
var isDate = (value) => value instanceof Date;
|
|
99
|
+
var equals = (value, operand) => {
|
|
100
|
+
if (operand === null) {
|
|
101
|
+
return value === null || value === undefined;
|
|
102
|
+
}
|
|
103
|
+
if (isDate(value) && isDate(operand)) {
|
|
104
|
+
return value.getTime() === operand.getTime();
|
|
105
|
+
}
|
|
106
|
+
return value === operand;
|
|
107
|
+
};
|
|
108
|
+
var order = (value) => isDate(value) ? value.getTime() : value;
|
|
109
|
+
var compare = (value, operand) => {
|
|
110
|
+
const a = order(value);
|
|
111
|
+
const b = order(operand);
|
|
112
|
+
if (a < b) {
|
|
113
|
+
return -1;
|
|
114
|
+
}
|
|
115
|
+
if (a > b) {
|
|
116
|
+
return 1;
|
|
117
|
+
}
|
|
118
|
+
return 0;
|
|
119
|
+
};
|
|
120
|
+
var comparable = (value) => value !== null && value !== undefined;
|
|
121
|
+
var classify = (chunks) => {
|
|
122
|
+
const cols = [];
|
|
123
|
+
const params = [];
|
|
124
|
+
const arrays = [];
|
|
125
|
+
const sqls = [];
|
|
126
|
+
const ops = [];
|
|
127
|
+
for (const chunk of chunks) {
|
|
128
|
+
if (is2(chunk, SQL2)) {
|
|
129
|
+
sqls.push(chunk);
|
|
130
|
+
} else if (is2(chunk, Column2)) {
|
|
131
|
+
cols.push(chunk);
|
|
132
|
+
} else if (is2(chunk, Param2)) {
|
|
133
|
+
params.push(chunk.value);
|
|
134
|
+
} else if (Array.isArray(chunk)) {
|
|
135
|
+
arrays.push(chunk.map((element) => is2(element, Param2) ? element.value : element));
|
|
136
|
+
} else {
|
|
137
|
+
const raw = chunk.value;
|
|
138
|
+
const text = (Array.isArray(raw) ? raw.join("") : String(raw ?? "")).trim();
|
|
139
|
+
if (text !== "" && text !== "(" && text !== ")") {
|
|
140
|
+
ops.push(text);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return { arrays, cols, ops, params, sqls };
|
|
145
|
+
};
|
|
146
|
+
var evaluateLeaf = (column, op, params, arrays, row, propFor) => {
|
|
147
|
+
const prop = propFor(column);
|
|
148
|
+
if (prop === undefined) {
|
|
149
|
+
throw new UnsupportedDrizzleFilterError(`column ${column.name}`);
|
|
150
|
+
}
|
|
151
|
+
const value = row[prop];
|
|
152
|
+
const operand = params[0];
|
|
153
|
+
switch (op) {
|
|
154
|
+
case "=":
|
|
155
|
+
return equals(value, operand);
|
|
156
|
+
case "<>":
|
|
157
|
+
return !equals(value, operand);
|
|
158
|
+
case ">":
|
|
159
|
+
return comparable(value) && compare(value, operand) > 0;
|
|
160
|
+
case ">=":
|
|
161
|
+
return comparable(value) && compare(value, operand) >= 0;
|
|
162
|
+
case "<":
|
|
163
|
+
return comparable(value) && compare(value, operand) < 0;
|
|
164
|
+
case "<=":
|
|
165
|
+
return comparable(value) && compare(value, operand) <= 0;
|
|
166
|
+
case "in":
|
|
167
|
+
return (arrays[0] ?? []).some((item) => equals(value, item));
|
|
168
|
+
case "not in":
|
|
169
|
+
return !(arrays[0] ?? []).some((item) => equals(value, item));
|
|
170
|
+
case "is null":
|
|
171
|
+
return value === null || value === undefined;
|
|
172
|
+
case "is not null":
|
|
173
|
+
return value !== null && value !== undefined;
|
|
174
|
+
default:
|
|
175
|
+
throw new UnsupportedDrizzleFilterError(op);
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
var evaluateCondition = (node, row, propFor) => {
|
|
179
|
+
const { cols, params, arrays, sqls, ops } = classify(node.queryChunks);
|
|
180
|
+
if (ops.length === 1 && ops[0] === "not" && sqls.length === 1 && cols.length === 0) {
|
|
181
|
+
return !evaluateCondition(sqls[0], row, propFor);
|
|
182
|
+
}
|
|
183
|
+
if (cols.length === 0 && sqls.length === 1 && ops.length === 0) {
|
|
184
|
+
return evaluateCondition(sqls[0], row, propFor);
|
|
185
|
+
}
|
|
186
|
+
if (cols.length === 0 && sqls.length >= 2 && ops.length > 0) {
|
|
187
|
+
const connective = ops[0];
|
|
188
|
+
if ((connective === "and" || connective === "or") && ops.every((op) => op === connective)) {
|
|
189
|
+
const results = sqls.map((sql) => evaluateCondition(sql, row, propFor));
|
|
190
|
+
return connective === "and" ? results.every(Boolean) : results.some(Boolean);
|
|
191
|
+
}
|
|
192
|
+
throw new UnsupportedDrizzleFilterError(ops.join(" "));
|
|
193
|
+
}
|
|
194
|
+
if (cols.length === 1 && sqls.length === 0 && ops.length === 1) {
|
|
195
|
+
return evaluateLeaf(cols[0], ops[0], params, arrays, row, propFor);
|
|
196
|
+
}
|
|
197
|
+
throw new UnsupportedDrizzleFilterError(ops.join(" ") || "unrecognized condition");
|
|
198
|
+
};
|
|
199
|
+
var matchesDrizzleWhere = (table, where, row) => {
|
|
200
|
+
const nameToProp = new Map;
|
|
201
|
+
for (const [prop, column] of Object.entries(getTableColumns2(table))) {
|
|
202
|
+
nameToProp.set(column.name, prop);
|
|
203
|
+
}
|
|
204
|
+
return evaluateCondition(where, row, (column) => nameToProp.get(column.name));
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
// src/adapters/drizzle/collection.ts
|
|
208
|
+
var drizzleCollection = (options) => {
|
|
209
|
+
const keyProp = resolveKeyColumn(options.table, options.keyColumn)?.property;
|
|
210
|
+
const key = options.key ?? ((row) => keyProp !== undefined ? row[keyProp] : row.id);
|
|
211
|
+
return {
|
|
212
|
+
name: options.name,
|
|
213
|
+
tables: [getTableName2(options.table)],
|
|
214
|
+
hydrate: (params, ctx) => options.find(options.where(params, ctx), params, ctx),
|
|
215
|
+
match: (row, params, ctx) => matchesDrizzleWhere(options.table, options.where(params, ctx), row),
|
|
216
|
+
key,
|
|
217
|
+
authorize: options.authorize
|
|
218
|
+
};
|
|
219
|
+
};
|
|
86
220
|
// src/adapters/drizzle/read.ts
|
|
87
221
|
var deriveReadTopics = (table, where, options = {}) => {
|
|
88
222
|
const key = where === undefined ? undefined : extractKeyFromWhere(table, where, options.keyColumn);
|
|
@@ -120,9 +254,12 @@ export {
|
|
|
120
254
|
publishWhere,
|
|
121
255
|
publishRows,
|
|
122
256
|
publishChange,
|
|
257
|
+
matchesDrizzleWhere,
|
|
123
258
|
keyTopic,
|
|
124
|
-
|
|
259
|
+
drizzleCollection,
|
|
260
|
+
deriveReadTopics,
|
|
261
|
+
UnsupportedDrizzleFilterError
|
|
125
262
|
};
|
|
126
263
|
|
|
127
|
-
//# debugId=
|
|
264
|
+
//# debugId=A28CA296A764961764756E2164756E21
|
|
128
265
|
//# sourceMappingURL=index.js.map
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
|
-
"sources": ["../src/adapters/drizzle/topics.ts", "../src/adapters/drizzle/read.ts", "../src/adapters/drizzle/write.ts"],
|
|
3
|
+
"sources": ["../src/adapters/drizzle/topics.ts", "../src/adapters/drizzle/collection.ts", "../src/adapters/drizzle/predicate.ts", "../src/adapters/drizzle/read.ts", "../src/adapters/drizzle/write.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
5
|
"import {\n\tColumn,\n\tgetTableColumns,\n\tgetTableName,\n\tis,\n\tParam,\n\tSQL\n} from 'drizzle-orm';\nimport type { Table } from 'drizzle-orm';\n\n/**\n * Shared topic vocabulary + key resolution for the Drizzle adapter. Both the\n * read side (derive the topics a query depends on) and the write side (publish\n * the topics a mutation invalidates) build on these so the two always agree.\n */\n\n/** The coarse topic every read/write of `table` touches, e.g. `users`. */\nexport const tableTopic = (table: Table): string => getTableName(table);\n\n/** The row-level topic for one key of `table`, e.g. `users:5`. */\nexport const keyTopic = (table: Table, key: string | number): string =>\n\t`${getTableName(table)}:${key}`;\n\ntype ResolvedKey = {\n\t/** JS property name of the key column on the table / result rows. */\n\tproperty: string;\n\t/** Underlying DB column name, as it appears in a SQL expression. */\n\tcolumn: string;\n};\n\n/**\n * Resolve the column to use as the row key: an explicitly requested column (by\n * JS property name), otherwise the table's sole primary key. Returns `undefined`\n * when no single key column applies (composite or missing primary key).\n */\nexport const resolveKeyColumn = (\n\ttable: Table,\n\tkeyColumn?: string\n): ResolvedKey | undefined => {\n\tconst columns = getTableColumns(table);\n\tif (keyColumn !== undefined) {\n\t\tconst column = columns[keyColumn];\n\t\treturn column === undefined\n\t\t\t? undefined\n\t\t\t: { property: keyColumn, column: column.name };\n\t}\n\tconst primaries = Object.entries(columns).filter(\n\t\t([, column]) => column.primary\n\t);\n\tconst primary = primaries.length === 1 ? primaries[0] : undefined;\n\treturn primary === undefined\n\t\t? undefined\n\t\t: { property: primary[0], column: primary[1].name };\n};\n\n/**\n * Best-effort: pull a single key-column equality value out of a Drizzle `where`\n * expression. Recognises only the simple `eq(keyColumn, scalar)` shape — any\n * nesting (`and`/`or`), extra columns/params, a non-`=` operator, or a\n * non-key/cross-table column yields `undefined`.\n *\n * Reads Drizzle's internal `queryChunks`, which is not a stable public API;\n * every branch degrades to `undefined` (coarser topic) rather than throwing, so\n * a Drizzle version bump can only cost precision, never correctness.\n */\nexport const extractKeyFromWhere = (\n\ttable: Table,\n\twhere: SQL,\n\tkeyColumn?: string\n): string | number | undefined => {\n\tconst resolved = resolveKeyColumn(table, keyColumn);\n\tif (resolved === undefined) {\n\t\treturn undefined;\n\t}\n\n\tconst chunks: unknown = (where as { queryChunks?: unknown }).queryChunks;\n\tif (!Array.isArray(chunks)) {\n\t\treturn undefined;\n\t}\n\n\tlet column: Column | undefined;\n\tlet param: Param | undefined;\n\tlet tooComplex = false;\n\tlet operator = '';\n\n\tfor (const chunk of chunks) {\n\t\tif (is(chunk, SQL)) {\n\t\t\t// Nested expression (e.g. and/or) — not a simple equality.\n\t\t\treturn undefined;\n\t\t}\n\t\tif (is(chunk, Column)) {\n\t\t\tif (column !== undefined) {\n\t\t\t\ttooComplex = true;\n\t\t\t}\n\t\t\tcolumn = chunk;\n\t\t} else if (is(chunk, Param)) {\n\t\t\tif (param !== undefined) {\n\t\t\t\ttooComplex = true;\n\t\t\t}\n\t\t\tparam = chunk;\n\t\t} else {\n\t\t\tconst value: unknown = (chunk as { value?: unknown }).value;\n\t\t\tif (Array.isArray(value)) {\n\t\t\t\toperator += value.join('');\n\t\t\t}\n\t\t}\n\t}\n\n\tif (tooComplex || column === undefined || param === undefined) {\n\t\treturn undefined;\n\t}\n\tif (operator.trim() !== '=') {\n\t\treturn undefined;\n\t}\n\tif (column.name !== resolved.column) {\n\t\treturn undefined;\n\t}\n\tif (getTableName(column.table) !== getTableName(table)) {\n\t\treturn undefined;\n\t}\n\n\tconst value: unknown = param.value;\n\treturn typeof value === 'string' || typeof value === 'number'\n\t\t? value\n\t\t: undefined;\n};\n\n/**\n * Read the key value from each row (e.g. the output of a mutation's\n * `.returning()`), using the table's primary-key column or an explicit\n * `keyColumn`. Rows without a string/number key are skipped.\n */\nexport const extractRowKeys = (\n\ttable: Table,\n\trows: ReadonlyArray<Record<string, unknown>>,\n\tkeyColumn?: string\n): (string | number)[] => {\n\tconst resolved = resolveKeyColumn(table, keyColumn);\n\tif (resolved === undefined) {\n\t\treturn [];\n\t}\n\tconst keys: (string | number)[] = [];\n\tfor (const row of rows) {\n\t\tconst value = row[resolved.property];\n\t\tif (typeof value === 'string' || typeof value === 'number') {\n\t\t\tkeys.push(value);\n\t\t}\n\t}\n\treturn keys;\n};\n",
|
|
6
|
+
"import { getTableName } from 'drizzle-orm';\nimport type { SQL, Table } from 'drizzle-orm';\nimport type {\n\tCollectionContext,\n\tCollectionDefinition\n} from '../../engine/collection';\nimport type { RowKey } from '../../engine/types';\nimport { matchesDrizzleWhere } from './predicate';\nimport { resolveKeyColumn } from './topics';\n\nexport type DrizzleCollectionOptions<T, P = void, Ctx = CollectionContext> = {\n\t/** Collection name clients subscribe to. */\n\tname: string;\n\t/** The Drizzle table this collection reads (drives change routing + key). */\n\ttable: Table;\n\t/** The query filter, written once — powers both hydrate and the matcher. */\n\twhere: (params: P, ctx: Ctx) => SQL;\n\t/** Run the read for `where` (your `db.select()...`), returning the rows. */\n\tfind: (\n\t\twhere: SQL,\n\t\tparams: P,\n\t\tctx: Ctx\n\t) => Promise<Iterable<T>> | Iterable<T>;\n\t/** Row identity. Defaults to the table's single primary key (else `row.id`). */\n\tkey?: (row: T) => RowKey;\n\t/** Key column JS property, if not the table's primary key. */\n\tkeyColumn?: string;\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 * A sync-engine collection from one Drizzle query — the Drizzle counterpart to\n * `prismaCollection`. You write the `where` once: it drives the DB read\n * (`hydrate`) AND the incremental `match` (via {@link matchesDrizzleWhere}), so\n * the two can't drift and you never hand-maintain a separate predicate. A filter\n * the matcher can't evaluate falls back to a refetch, never a wrong result.\n */\nexport const drizzleCollection = <T, P = void, Ctx = CollectionContext>(\n\toptions: DrizzleCollectionOptions<T, P, Ctx>\n): CollectionDefinition<T, P, Ctx> => {\n\tconst keyProp = resolveKeyColumn(\n\t\toptions.table,\n\t\toptions.keyColumn\n\t)?.property;\n\tconst key =\n\t\toptions.key ??\n\t\t((row: T) =>\n\t\t\tkeyProp !== undefined\n\t\t\t\t? (row as Record<string, RowKey>)[keyProp]!\n\t\t\t\t: (row as { id: RowKey }).id);\n\n\treturn {\n\t\tname: options.name,\n\t\ttables: [getTableName(options.table)],\n\t\thydrate: (params, ctx) =>\n\t\t\toptions.find(options.where(params, ctx), params, ctx),\n\t\tmatch: (row, params, ctx) =>\n\t\t\tmatchesDrizzleWhere(\n\t\t\t\toptions.table,\n\t\t\t\toptions.where(params, ctx),\n\t\t\t\trow as Record<string, unknown>\n\t\t\t),\n\t\tkey,\n\t\tauthorize: options.authorize\n\t};\n};\n",
|
|
7
|
+
"import { Column, getTableColumns, is, Param, SQL } from 'drizzle-orm';\nimport type { Table } from 'drizzle-orm';\n\n/**\n * Thrown when a Drizzle `where` uses something the incremental matcher can't\n * evaluate in JS (an unsupported operator, a function, a cross-table column…).\n * The sync engine catches it and degrades that subscription to a refetch, so the\n * result is never wrong — only less efficient. Mirrors the Prisma adapter.\n */\nexport class UnsupportedDrizzleFilterError extends Error {\n\tconstructor(detail: string) {\n\t\tsuper(`Cannot evaluate Drizzle filter \"${detail}\" incrementally`);\n\t\tthis.name = 'UnsupportedDrizzleFilterError';\n\t}\n}\n\nconst isDate = (value: unknown): value is Date => value instanceof Date;\n\nconst equals = (value: unknown, operand: unknown): boolean => {\n\tif (operand === null) {\n\t\treturn value === null || value === undefined;\n\t}\n\tif (isDate(value) && isDate(operand)) {\n\t\treturn value.getTime() === operand.getTime();\n\t}\n\treturn value === operand;\n};\n\nconst order = (value: unknown): number | string =>\n\tisDate(value) ? 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\ntype Classified = {\n\tcols: Column[];\n\tparams: unknown[];\n\tarrays: unknown[][];\n\tsqls: SQL[];\n\tops: string[];\n};\n\n/**\n * Split a condition's `queryChunks` into its parts. Drizzle interleaves columns,\n * bound `Param`s, value arrays (for `in`), nested `SQL` (connectives), and string\n * chunks that carry the operators/parens. Reading `queryChunks` is Drizzle's\n * internal shape, not a stable API — but every unrecognized form throws below\n * (→ refetch), so a version bump can only cost efficiency, never correctness.\n */\nconst classify = (chunks: unknown[]): Classified => {\n\tconst cols: Column[] = [];\n\tconst params: unknown[] = [];\n\tconst arrays: unknown[][] = [];\n\tconst sqls: SQL[] = [];\n\tconst ops: string[] = [];\n\tfor (const chunk of chunks) {\n\t\tif (is(chunk, SQL)) {\n\t\t\tsqls.push(chunk);\n\t\t} else if (is(chunk, Column)) {\n\t\t\tcols.push(chunk);\n\t\t} else if (is(chunk, Param)) {\n\t\t\tparams.push(chunk.value);\n\t\t} else if (Array.isArray(chunk)) {\n\t\t\tarrays.push(\n\t\t\t\tchunk.map((element) =>\n\t\t\t\t\tis(element, Param) ? element.value : element\n\t\t\t\t)\n\t\t\t);\n\t\t} else {\n\t\t\tconst raw = (chunk as { value?: unknown }).value;\n\t\t\tconst text = (\n\t\t\t\tArray.isArray(raw) ? raw.join('') : String(raw ?? '')\n\t\t\t).trim();\n\t\t\tif (text !== '' && text !== '(' && text !== ')') {\n\t\t\t\tops.push(text);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn { arrays, cols, ops, params, sqls };\n};\n\nconst evaluateLeaf = (\n\tcolumn: Column,\n\top: string,\n\tparams: unknown[],\n\tarrays: unknown[][],\n\trow: Record<string, unknown>,\n\tpropFor: (column: Column) => string | undefined\n): boolean => {\n\tconst prop = propFor(column);\n\tif (prop === undefined) {\n\t\tthrow new UnsupportedDrizzleFilterError(`column ${column.name}`);\n\t}\n\tconst value = row[prop];\n\tconst operand = params[0];\n\tswitch (op) {\n\t\tcase '=':\n\t\t\treturn equals(value, operand);\n\t\tcase '<>':\n\t\t\treturn !equals(value, operand);\n\t\tcase '>':\n\t\t\treturn comparable(value) && compare(value, operand) > 0;\n\t\tcase '>=':\n\t\t\treturn comparable(value) && compare(value, operand) >= 0;\n\t\tcase '<':\n\t\t\treturn comparable(value) && compare(value, operand) < 0;\n\t\tcase '<=':\n\t\t\treturn comparable(value) && compare(value, operand) <= 0;\n\t\tcase 'in':\n\t\t\treturn (arrays[0] ?? []).some((item) => equals(value, item));\n\t\tcase 'not in':\n\t\t\treturn !(arrays[0] ?? []).some((item) => equals(value, item));\n\t\tcase 'is null':\n\t\t\treturn value === null || value === undefined;\n\t\tcase 'is not null':\n\t\t\treturn value !== null && value !== undefined;\n\t\tdefault:\n\t\t\tthrow new UnsupportedDrizzleFilterError(op);\n\t}\n};\n\nconst evaluateCondition = (\n\tnode: SQL,\n\trow: Record<string, unknown>,\n\tpropFor: (column: Column) => string | undefined\n): boolean => {\n\tconst { cols, params, arrays, sqls, ops } = classify(\n\t\t(node as unknown as { queryChunks: unknown[] }).queryChunks\n\t);\n\n\t// not (cond)\n\tif (\n\t\tops.length === 1 &&\n\t\tops[0] === 'not' &&\n\t\tsqls.length === 1 &&\n\t\tcols.length === 0\n\t) {\n\t\treturn !evaluateCondition(sqls[0]!, row, propFor);\n\t}\n\t// A lone nested condition wrapped in parens — unwrap and recurse.\n\tif (cols.length === 0 && sqls.length === 1 && ops.length === 0) {\n\t\treturn evaluateCondition(sqls[0]!, row, propFor);\n\t}\n\t// and / or over sub-conditions.\n\tif (cols.length === 0 && sqls.length >= 2 && ops.length > 0) {\n\t\tconst connective = ops[0];\n\t\tif (\n\t\t\t(connective === 'and' || connective === 'or') &&\n\t\t\tops.every((op) => op === connective)\n\t\t) {\n\t\t\tconst results = sqls.map((sql) =>\n\t\t\t\tevaluateCondition(sql, row, propFor)\n\t\t\t);\n\t\t\treturn connective === 'and'\n\t\t\t\t? results.every(Boolean)\n\t\t\t\t: results.some(Boolean);\n\t\t}\n\t\tthrow new UnsupportedDrizzleFilterError(ops.join(' '));\n\t}\n\t// Leaf comparison: one column, one operator.\n\tif (cols.length === 1 && sqls.length === 0 && ops.length === 1) {\n\t\treturn evaluateLeaf(cols[0]!, ops[0]!, params, arrays, row, propFor);\n\t}\n\tthrow new UnsupportedDrizzleFilterError(\n\t\tops.join(' ') || 'unrecognized condition'\n\t);\n};\n\n/**\n * Evaluate a Drizzle `where` condition against a plain row in JS — the\n * incremental matcher for {@link drizzleCollection}. Supports\n * `eq`/`ne`/`gt`/`gte`/`lt`/`lte`, `isNull`/`isNotNull`,\n * `inArray`/`notInArray`, and nested `and`/`or`/`not`; anything else throws\n * {@link UnsupportedDrizzleFilterError} (the engine then refetches). Rows are\n * read by JS property name, as Drizzle returns them.\n */\nexport const matchesDrizzleWhere = (\n\ttable: Table,\n\twhere: SQL,\n\trow: Record<string, unknown>\n): boolean => {\n\tconst nameToProp = new Map<string, string>();\n\tfor (const [prop, column] of Object.entries(getTableColumns(table))) {\n\t\tnameToProp.set(column.name, prop);\n\t}\n\n\treturn evaluateCondition(where, row, (column) =>\n\t\tnameToProp.get(column.name)\n\t);\n};\n",
|
|
6
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",
|
|
7
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"
|
|
8
10
|
],
|
|
9
|
-
"mappings": ";;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAiBO,IAAM,aAAa,CAAC,UAAyB,aAAa,KAAK;AAG/D,IAAM,WAAW,CAAC,OAAc,QACtC,GAAG,aAAa,KAAK,KAAK;AAcpB,IAAM,mBAAmB,CAC/B,OACA,cAC6B;AAAA,EAC7B,MAAM,UAAU,gBAAgB,KAAK;AAAA,EACrC,IAAI,cAAc,WAAW;AAAA,IAC5B,MAAM,SAAS,QAAQ;AAAA,IACvB,OAAO,WAAW,YACf,YACA,EAAE,UAAU,WAAW,QAAQ,OAAO,KAAK;AAAA,EAC/C;AAAA,EACA,MAAM,YAAY,OAAO,QAAQ,OAAO,EAAE,OACzC,IAAI,YAAY,OAAO,OACxB;AAAA,EACA,MAAM,UAAU,UAAU,WAAW,IAAI,UAAU,KAAK;AAAA,EACxD,OAAO,YAAY,YAChB,YACA,EAAE,UAAU,QAAQ,IAAI,QAAQ,QAAQ,GAAG,KAAK;AAAA;AAa7C,IAAM,sBAAsB,CAClC,OACA,OACA,cACiC;AAAA,EACjC,MAAM,WAAW,iBAAiB,OAAO,SAAS;AAAA,EAClD,IAAI,aAAa,WAAW;AAAA,IAC3B;AAAA,EACD;AAAA,EAEA,MAAM,SAAmB,MAAoC;AAAA,EAC7D,IAAI,CAAC,MAAM,QAAQ,MAAM,GAAG;AAAA,IAC3B;AAAA,EACD;AAAA,EAEA,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI,aAAa;AAAA,EACjB,IAAI,WAAW;AAAA,EAEf,WAAW,SAAS,QAAQ;AAAA,IAC3B,IAAI,GAAG,OAAO,GAAG,GAAG;AAAA,MAEnB;AAAA,IACD;AAAA,IACA,IAAI,GAAG,OAAO,MAAM,GAAG;AAAA,MACtB,IAAI,WAAW,WAAW;AAAA,QACzB,aAAa;AAAA,MACd;AAAA,MACA,SAAS;AAAA,IACV,EAAO,SAAI,GAAG,OAAO,KAAK,GAAG;AAAA,MAC5B,IAAI,UAAU,WAAW;AAAA,QACxB,aAAa;AAAA,MACd;AAAA,MACA,QAAQ;AAAA,IACT,EAAO;AAAA,MACN,MAAM,SAAkB,MAA8B;AAAA,MACtD,IAAI,MAAM,QAAQ,MAAK,GAAG;AAAA,QACzB,YAAY,OAAM,KAAK,EAAE;AAAA,MAC1B;AAAA;AAAA,EAEF;AAAA,EAEA,IAAI,cAAc,WAAW,aAAa,UAAU,WAAW;AAAA,IAC9D;AAAA,EACD;AAAA,EACA,IAAI,SAAS,KAAK,MAAM,KAAK;AAAA,IAC5B;AAAA,EACD;AAAA,EACA,IAAI,OAAO,SAAS,SAAS,QAAQ;AAAA,IACpC;AAAA,EACD;AAAA,EACA,IAAI,aAAa,OAAO,KAAK,MAAM,aAAa,KAAK,GAAG;AAAA,IACvD;AAAA,EACD;AAAA,EAEA,MAAM,QAAiB,MAAM;AAAA,EAC7B,OAAO,OAAO,UAAU,YAAY,OAAO,UAAU,WAClD,QACA;AAAA;AAQG,IAAM,iBAAiB,CAC7B,OACA,MACA,cACyB;AAAA,EACzB,MAAM,WAAW,iBAAiB,OAAO,SAAS;AAAA,EAClD,IAAI,aAAa,WAAW;AAAA,IAC3B,OAAO,CAAC;AAAA,EACT;AAAA,EACA,MAAM,OAA4B,CAAC;AAAA,EACnC,WAAW,OAAO,MAAM;AAAA,IACvB,MAAM,QAAQ,IAAI,SAAS;AAAA,IAC3B,IAAI,OAAO,UAAU,YAAY,OAAO,UAAU,UAAU;AAAA,MAC3D,KAAK,KAAK,KAAK;AAAA,IAChB;AAAA,EACD;AAAA,EACA,OAAO;AAAA;;
|
|
10
|
-
"debugId": "
|
|
11
|
+
"mappings": "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",
|
|
12
|
+
"debugId": "A28CA296A764961764756E2164756E21",
|
|
11
13
|
"names": []
|
|
12
14
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { SQL } from 'drizzle-orm';
|
|
2
|
+
import type { Table } from 'drizzle-orm';
|
|
3
|
+
/**
|
|
4
|
+
* Thrown when a Drizzle `where` uses something the incremental matcher can't
|
|
5
|
+
* evaluate in JS (an unsupported operator, a function, a cross-table column…).
|
|
6
|
+
* The sync engine catches it and degrades that subscription to a refetch, so the
|
|
7
|
+
* result is never wrong — only less efficient. Mirrors the Prisma adapter.
|
|
8
|
+
*/
|
|
9
|
+
export declare class UnsupportedDrizzleFilterError extends Error {
|
|
10
|
+
constructor(detail: string);
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Evaluate a Drizzle `where` condition against a plain row in JS — the
|
|
14
|
+
* incremental matcher for {@link drizzleCollection}. Supports
|
|
15
|
+
* `eq`/`ne`/`gt`/`gte`/`lt`/`lte`, `isNull`/`isNotNull`,
|
|
16
|
+
* `inArray`/`notInArray`, and nested `and`/`or`/`not`; anything else throws
|
|
17
|
+
* {@link UnsupportedDrizzleFilterError} (the engine then refetches). Rows are
|
|
18
|
+
* read by JS property name, as Drizzle returns them.
|
|
19
|
+
*/
|
|
20
|
+
export declare const matchesDrizzleWhere: (table: Table, where: SQL, row: Record<string, unknown>) => boolean;
|
package/dist/angular/index.js
CHANGED
|
@@ -81,6 +81,57 @@ var localStorageMutationStorage = (key) => ({
|
|
|
81
81
|
globalThis.localStorage?.setItem(key, JSON.stringify(records));
|
|
82
82
|
}
|
|
83
83
|
});
|
|
84
|
+
var localStorageCollectionCache = (key) => ({
|
|
85
|
+
load: () => {
|
|
86
|
+
const raw = globalThis.localStorage?.getItem(key);
|
|
87
|
+
return raw ? JSON.parse(raw) : undefined;
|
|
88
|
+
},
|
|
89
|
+
save: (snapshot) => {
|
|
90
|
+
globalThis.localStorage?.setItem(key, JSON.stringify(snapshot));
|
|
91
|
+
},
|
|
92
|
+
clear: () => {
|
|
93
|
+
globalThis.localStorage?.removeItem(key);
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
var openIndexedDb = (databaseName, storeName) => new Promise((resolve, reject) => {
|
|
97
|
+
const request = globalThis.indexedDB.open(databaseName, 1);
|
|
98
|
+
request.onupgradeneeded = () => {
|
|
99
|
+
request.result.createObjectStore(storeName);
|
|
100
|
+
};
|
|
101
|
+
request.onsuccess = () => resolve(request.result);
|
|
102
|
+
request.onerror = () => reject(request.error);
|
|
103
|
+
});
|
|
104
|
+
var indexedDbCollectionCache = ({
|
|
105
|
+
key,
|
|
106
|
+
databaseName = "absolutejs-sync",
|
|
107
|
+
storeName = "collections"
|
|
108
|
+
}) => {
|
|
109
|
+
let handle;
|
|
110
|
+
const database = () => {
|
|
111
|
+
handle ??= openIndexedDb(databaseName, storeName);
|
|
112
|
+
return handle;
|
|
113
|
+
};
|
|
114
|
+
const withStore = async (mode, run) => {
|
|
115
|
+
if (globalThis.indexedDB === undefined) {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
const db = await database();
|
|
119
|
+
return new Promise((resolve, reject) => {
|
|
120
|
+
const request = run(db.transaction(storeName, mode).objectStore(storeName));
|
|
121
|
+
request.onsuccess = () => resolve(request.result);
|
|
122
|
+
request.onerror = () => reject(request.error);
|
|
123
|
+
});
|
|
124
|
+
};
|
|
125
|
+
return {
|
|
126
|
+
load: () => withStore("readonly", (store) => store.get(key)),
|
|
127
|
+
save: async (snapshot) => {
|
|
128
|
+
await withStore("readwrite", (store) => store.put(snapshot, key));
|
|
129
|
+
},
|
|
130
|
+
clear: async () => {
|
|
131
|
+
await withStore("readwrite", (store) => store.delete(key));
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
};
|
|
84
135
|
var SUBSCRIPTION_ID = "s";
|
|
85
136
|
var createSyncCollection = (options) => {
|
|
86
137
|
const key = options.key ?? ((row) => row.id);
|
|
@@ -129,6 +180,20 @@ var createSyncCollection = (options) => {
|
|
|
129
180
|
args: mutation.args
|
|
130
181
|
})));
|
|
131
182
|
};
|
|
183
|
+
let cacheScheduled = false;
|
|
184
|
+
const persistCache = () => {
|
|
185
|
+
if (options.cache === undefined || cacheScheduled) {
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
cacheScheduled = true;
|
|
189
|
+
queueMicrotask(() => {
|
|
190
|
+
cacheScheduled = false;
|
|
191
|
+
options.cache?.save({
|
|
192
|
+
rows: [...confirmed.values()],
|
|
193
|
+
version: appliedVersion
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
};
|
|
132
197
|
const settlePending = (mutationId) => {
|
|
133
198
|
const index = pending.findIndex((mutation2) => mutation2.mutationId === mutationId);
|
|
134
199
|
if (index === -1) {
|
|
@@ -147,6 +212,7 @@ var createSyncCollection = (options) => {
|
|
|
147
212
|
if (frame.version !== undefined) {
|
|
148
213
|
appliedVersion = frame.version;
|
|
149
214
|
}
|
|
215
|
+
persistCache();
|
|
150
216
|
recompute({ status: "ready", error: undefined });
|
|
151
217
|
} else if (frame.type === "diff") {
|
|
152
218
|
for (const row of frame.removed) {
|
|
@@ -161,7 +227,8 @@ var createSyncCollection = (options) => {
|
|
|
161
227
|
if (frame.version !== undefined) {
|
|
162
228
|
appliedVersion = Math.max(appliedVersion, frame.version);
|
|
163
229
|
}
|
|
164
|
-
|
|
230
|
+
persistCache();
|
|
231
|
+
recompute({ status: "ready", error: undefined });
|
|
165
232
|
} else if (frame.type === "error") {
|
|
166
233
|
setState({ error: frame.message });
|
|
167
234
|
options.onError?.(frame.message);
|
|
@@ -171,7 +238,7 @@ var createSyncCollection = (options) => {
|
|
|
171
238
|
recompute();
|
|
172
239
|
mutation.resolve(frame.result);
|
|
173
240
|
}
|
|
174
|
-
} else {
|
|
241
|
+
} else if (frame.type === "reject") {
|
|
175
242
|
const mutation = settlePending(frame.mutationId);
|
|
176
243
|
if (mutation !== undefined) {
|
|
177
244
|
recompute();
|
|
@@ -225,7 +292,6 @@ var createSyncCollection = (options) => {
|
|
|
225
292
|
reconnectTimer = setTimeout(connect, delay);
|
|
226
293
|
};
|
|
227
294
|
};
|
|
228
|
-
connect();
|
|
229
295
|
const hydratePersisted = async () => {
|
|
230
296
|
if (options.storage === undefined) {
|
|
231
297
|
return;
|
|
@@ -250,7 +316,35 @@ var createSyncCollection = (options) => {
|
|
|
250
316
|
}
|
|
251
317
|
}
|
|
252
318
|
};
|
|
253
|
-
|
|
319
|
+
const hydrateCache = async () => {
|
|
320
|
+
if (options.cache === undefined) {
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
let snapshot;
|
|
324
|
+
try {
|
|
325
|
+
snapshot = await options.cache.load();
|
|
326
|
+
} catch {
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
if (snapshot === undefined || appliedVersion > 0) {
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
for (const row of snapshot.rows) {
|
|
333
|
+
confirmed.set(key(row), row);
|
|
334
|
+
}
|
|
335
|
+
appliedVersion = snapshot.version;
|
|
336
|
+
recompute();
|
|
337
|
+
};
|
|
338
|
+
if (options.cache === undefined) {
|
|
339
|
+
connect();
|
|
340
|
+
hydratePersisted();
|
|
341
|
+
} else {
|
|
342
|
+
(async () => {
|
|
343
|
+
await hydrateCache();
|
|
344
|
+
await hydratePersisted();
|
|
345
|
+
connect();
|
|
346
|
+
})();
|
|
347
|
+
}
|
|
254
348
|
return {
|
|
255
349
|
get: () => state,
|
|
256
350
|
subscribe: (listener) => {
|
|
@@ -343,5 +437,5 @@ export {
|
|
|
343
437
|
SyncCollectionService
|
|
344
438
|
};
|
|
345
439
|
|
|
346
|
-
//# debugId=
|
|
440
|
+
//# debugId=C534ED0FEAC9CC6664756E2164756E21
|
|
347
441
|
//# sourceMappingURL=index.js.map
|