@absolutejs/sync 0.7.0 → 0.9.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 +11 -8
- package/dist/devtools.d.ts +69 -0
- package/dist/engine/devtools.d.ts +55 -0
- package/dist/engine/index.d.ts +4 -1
- package/dist/engine/index.js +157 -14
- package/dist/engine/index.js.map +5 -4
- package/dist/engine/schema.d.ts +39 -0
- package/dist/engine/syncEngine.d.ts +40 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +117 -1
- package/dist/index.js.map +4 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -33,7 +33,8 @@ top-N ordering are maintained incrementally through a composable operator graph
|
|
|
33
33
|
> write-behind cache), Tier 2 (Drizzle + Prisma topic adapters, `createLiveQuery`),
|
|
34
34
|
> and Tier 3 (sync engine: collections, WebSocket diff transport, optimistic
|
|
35
35
|
> mutations + offline queue, a local-first client cache, declarative row-level
|
|
36
|
-
> permissions,
|
|
36
|
+
> permissions, schema validation + lazy migrations, live full-text + vector
|
|
37
|
+
> search, scheduled functions, a live devtools dashboard, CDC for
|
|
37
38
|
> Postgres/MySQL/SQLite, incremental aggregations + joins, and a declarative
|
|
38
39
|
> operator graph) are in place. Everything ships as subpaths of this one package.
|
|
39
40
|
|
|
@@ -332,13 +333,14 @@ it, ~3 store round-trips every 20ms ran the voice pipeline far slower than real
|
|
|
332
333
|
|
|
333
334
|
### `@absolutejs/sync`
|
|
334
335
|
|
|
335
|
-
| Export | What it is
|
|
336
|
-
| ------------------------------------------------------------------------------------------ |
|
|
337
|
-
| `createReactiveHub()` | In-memory topic pub/sub (`publish`, `subscribe`, `subscriberCount`).
|
|
338
|
-
| `sync({ hub, path?, resolveTopics?, heartbeatMs? })` | Elysia plugin: SSE stream of hub events.
|
|
339
|
-
| `syncSocket({ engine, path?, resolveContext? })` | Elysia WebSocket plugin for the sync engine.
|
|
340
|
-
| `scheduled({ engine, prefix?, onError? })` _(`/scheduled` subpath)_ | Elysia plugin: fires the engine's registered schedules on their cron patterns (via `@elysiajs/cron`). Kept off the main entry so `syncSocket` needs no cron dep.
|
|
341
|
-
| `
|
|
336
|
+
| Export | What it is |
|
|
337
|
+
| ------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
338
|
+
| `createReactiveHub()` | In-memory topic pub/sub (`publish`, `subscribe`, `subscriberCount`). |
|
|
339
|
+
| `sync({ hub, path?, resolveTopics?, heartbeatMs? })` | Elysia plugin: SSE stream of hub events. |
|
|
340
|
+
| `syncSocket({ engine, path?, resolveContext? })` | Elysia WebSocket plugin for the sync engine. |
|
|
341
|
+
| `scheduled({ engine, prefix?, onError? })` _(`/scheduled` subpath)_ | Elysia plugin: fires the engine's registered schedules on their cron patterns (via `@elysiajs/cron`). Kept off the main entry so `syncSocket` needs no cron dep. |
|
|
342
|
+
| `syncDevtools({ engine, path?, snapshotMs? })` | Elysia plugin: a live devtools dashboard (collections, subscription counts, mutations, schedules, change feed) over SSE. Backed by `engine.inspect()` + `engine.onActivity()`. |
|
|
343
|
+
| `createWriteBehindCache({ load, persist, remove?, debounceMs?, evict?, onPersistError? })` | In-memory cache + write-behind persistence. |
|
|
342
344
|
|
|
343
345
|
### `@absolutejs/sync/client`
|
|
344
346
|
|
|
@@ -401,6 +403,7 @@ mutate({
|
|
|
401
403
|
| `defineGraphCollection({ name, query, key, authorize? })` | Run a `query` as a live collection. |
|
|
402
404
|
| `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. |
|
|
403
405
|
| `definePermissions({ [table]: { read?, insert?, update?, delete?, write? } })` | Declarative row-level access control. Pass as `createSyncEngine({ permissions })` or `registerPermissions(table, rules)`. Read rules filter every row emitted; write rules gate `actions.insert/update/delete`. |
|
|
406
|
+
| `defineSchema({ [table]: { fields, version?, migrate? } })` + `field` kit | Declarative row schema. Pass as `createSyncEngine({ schemas })` or `registerSchema(table, schema)`. Writes are validated (bad write → `SchemaError`); `migrate` lazily upcasts rows on read (no DB migration needed). |
|
|
404
407
|
| `defineSearchCollection({ name, table, index, source, key, limit? })` + `registerSearch` | Live search collection: the subscription's `params` are the query (string/vector), the ranked top-K stream back as a normal collection, re-ranked as rows change. Each row carries its score under `_score`. |
|
|
405
408
|
| `createTextIndex({ key, fields, tokenize?, stopwords?, k1?, b? })` | Incremental BM25 full-text index (keyword search). Implements `SearchIndex`; usable standalone or inside a search collection. |
|
|
406
409
|
| `createVectorIndex({ key, embedding, metric? })` | Incremental vector index (cosine/dot/euclidean exact k-NN) for semantic search — pairs with `@absolutejs/ai` / `@absolutejs/rag` for RAG retrieval on your own data. |
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { Elysia } from 'elysia';
|
|
2
|
+
import type { SyncEngine } from './engine/syncEngine';
|
|
3
|
+
export type SyncDevtoolsOptions = {
|
|
4
|
+
/** The engine to inspect. */
|
|
5
|
+
engine: SyncEngine;
|
|
6
|
+
/** Route the dashboard is served from (its SSE feed is `<path>/stream`). Default `/sync/devtools`. */
|
|
7
|
+
path?: string;
|
|
8
|
+
/** Snapshot refresh interval (ms) — keeps subscription counts/version current. Default 2000. */
|
|
9
|
+
snapshotMs?: number;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Elysia plugin: a live devtools dashboard for a {@link SyncEngine}. Mount it and
|
|
13
|
+
* open `path` in a browser to watch registered collections (kind, source tables,
|
|
14
|
+
* live subscription counts), mutations, schedules, readers/writers, the
|
|
15
|
+
* change-feed version, and a streaming log of changes + mutation outcomes — over
|
|
16
|
+
* Server-Sent Events. Read-only; safe to leave mounted in dev.
|
|
17
|
+
*/
|
|
18
|
+
export declare const syncDevtools: ({ engine, path, snapshotMs }: SyncDevtoolsOptions) => Elysia<"", {
|
|
19
|
+
decorator: {};
|
|
20
|
+
store: {};
|
|
21
|
+
derive: {};
|
|
22
|
+
resolve: {};
|
|
23
|
+
}, {
|
|
24
|
+
typebox: {};
|
|
25
|
+
error: {};
|
|
26
|
+
}, {
|
|
27
|
+
schema: {};
|
|
28
|
+
standaloneSchema: {};
|
|
29
|
+
macro: {};
|
|
30
|
+
macroFn: {};
|
|
31
|
+
parser: {};
|
|
32
|
+
response: {};
|
|
33
|
+
}, {
|
|
34
|
+
[x: string]: {
|
|
35
|
+
get: {
|
|
36
|
+
body: unknown;
|
|
37
|
+
params: {};
|
|
38
|
+
query: unknown;
|
|
39
|
+
headers: unknown;
|
|
40
|
+
response: {
|
|
41
|
+
200: Response;
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
} & {
|
|
46
|
+
[x: string]: {
|
|
47
|
+
get: {
|
|
48
|
+
body: unknown;
|
|
49
|
+
params: {};
|
|
50
|
+
query: unknown;
|
|
51
|
+
headers: unknown;
|
|
52
|
+
response: {
|
|
53
|
+
200: Response;
|
|
54
|
+
};
|
|
55
|
+
};
|
|
56
|
+
};
|
|
57
|
+
}, {
|
|
58
|
+
derive: {};
|
|
59
|
+
resolve: {};
|
|
60
|
+
schema: {};
|
|
61
|
+
standaloneSchema: {};
|
|
62
|
+
response: {};
|
|
63
|
+
}, {
|
|
64
|
+
derive: {};
|
|
65
|
+
resolve: {};
|
|
66
|
+
schema: {};
|
|
67
|
+
standaloneSchema: {};
|
|
68
|
+
response: {};
|
|
69
|
+
}>;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { RowOp } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Devtools introspection — a live window into a running {@link SyncEngine}: what
|
|
4
|
+
* collections are registered, how many clients subscribe to each, which
|
|
5
|
+
* mutations/schedules/readers/writers exist, the change-feed version, and a tail
|
|
6
|
+
* of recent changes. Paired with the activity stream ({@link EngineActivity}) it
|
|
7
|
+
* powers the `syncDevtools` dashboard. Read-only — purely observational.
|
|
8
|
+
*/
|
|
9
|
+
export type CollectionKind = 'view' | 'join' | 'graph' | 'reactive' | 'search';
|
|
10
|
+
/** One registered collection's current state. */
|
|
11
|
+
export type CollectionInspection = {
|
|
12
|
+
name: string;
|
|
13
|
+
kind: CollectionKind;
|
|
14
|
+
/** Source tables it reads (empty for a reactive query — its deps are dynamic). */
|
|
15
|
+
tables: string[];
|
|
16
|
+
/** Active client subscriptions to it right now. */
|
|
17
|
+
subscriptions: number;
|
|
18
|
+
};
|
|
19
|
+
/** A point-in-time snapshot of the engine (see {@link SyncEngine.inspect}). */
|
|
20
|
+
export type EngineInspection = {
|
|
21
|
+
/** Current change-feed version (monotonic). */
|
|
22
|
+
version: number;
|
|
23
|
+
collections: CollectionInspection[];
|
|
24
|
+
mutations: string[];
|
|
25
|
+
schedules: {
|
|
26
|
+
name: string;
|
|
27
|
+
pattern: string;
|
|
28
|
+
}[];
|
|
29
|
+
/** Tables with a registered reader / writer. */
|
|
30
|
+
readers: string[];
|
|
31
|
+
writers: string[];
|
|
32
|
+
/** Most recent changes from the change log (oldest first). */
|
|
33
|
+
recentChanges: {
|
|
34
|
+
version: number;
|
|
35
|
+
table: string;
|
|
36
|
+
op: RowOp;
|
|
37
|
+
}[];
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* A live engine event (see {@link SyncEngine.onActivity}): a committed change or
|
|
41
|
+
* a mutation outcome. `at` is `Date.now()`. (Live subscription counts come from
|
|
42
|
+
* the {@link EngineInspection} snapshot.)
|
|
43
|
+
*/
|
|
44
|
+
export type EngineActivity = {
|
|
45
|
+
type: 'change';
|
|
46
|
+
at: number;
|
|
47
|
+
table: string;
|
|
48
|
+
op: RowOp;
|
|
49
|
+
version: number;
|
|
50
|
+
} | {
|
|
51
|
+
type: 'mutation';
|
|
52
|
+
at: number;
|
|
53
|
+
name: string;
|
|
54
|
+
status: 'ok' | 'error';
|
|
55
|
+
};
|
package/dist/engine/index.d.ts
CHANGED
|
@@ -42,8 +42,11 @@ 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 { createSyncEngine, UnauthorizedError } from './syncEngine';
|
|
45
|
+
export { createSyncEngine, SchemaError, UnauthorizedError } from './syncEngine';
|
|
46
46
|
export type { SubscribeArgs, Subscription, SyncEngine } from './syncEngine';
|
|
47
|
+
export { defineSchema, field } from './schema';
|
|
48
|
+
export type { FieldValidator, SchemaDefinition, TableSchema } from './schema';
|
|
49
|
+
export type { CollectionInspection, CollectionKind, EngineActivity, EngineInspection } from './devtools';
|
|
47
50
|
export { hydrateRoute, mutateRoute } from './routes';
|
|
48
51
|
export type { SyncRouteContext } from './routes';
|
|
49
52
|
export { createSyncConnection } from './connection';
|
package/dist/engine/index.js
CHANGED
|
@@ -1046,6 +1046,13 @@ class UnauthorizedError extends Error {
|
|
|
1046
1046
|
this.name = "UnauthorizedError";
|
|
1047
1047
|
}
|
|
1048
1048
|
}
|
|
1049
|
+
|
|
1050
|
+
class SchemaError extends Error {
|
|
1051
|
+
constructor(table, fieldName) {
|
|
1052
|
+
super(`Schema violation on "${table}": invalid field "${fieldName}"`);
|
|
1053
|
+
this.name = "SchemaError";
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1049
1056
|
var defaultKey = (row) => row.id;
|
|
1050
1057
|
var shallowEqual4 = (a, b) => {
|
|
1051
1058
|
if (a === b) {
|
|
@@ -1082,6 +1089,30 @@ var createSyncEngine = (options = {}) => {
|
|
|
1082
1089
|
const rules = permissions.get(table);
|
|
1083
1090
|
return rules?.[op] ?? rules?.write;
|
|
1084
1091
|
};
|
|
1092
|
+
const schemas = new Map;
|
|
1093
|
+
for (const [table, schema] of Object.entries(options.schemas ?? {})) {
|
|
1094
|
+
schemas.set(table, schema);
|
|
1095
|
+
}
|
|
1096
|
+
const validateWrite = (table, op, row) => {
|
|
1097
|
+
const schema = schemas.get(table);
|
|
1098
|
+
if (schema === undefined || typeof row !== "object" || row === null) {
|
|
1099
|
+
return;
|
|
1100
|
+
}
|
|
1101
|
+
const record = row;
|
|
1102
|
+
for (const [fieldName, validate] of Object.entries(schema.fields)) {
|
|
1103
|
+
const present = fieldName in record;
|
|
1104
|
+
if (op === "update" && !present) {
|
|
1105
|
+
continue;
|
|
1106
|
+
}
|
|
1107
|
+
if (!validate(record[fieldName])) {
|
|
1108
|
+
throw new SchemaError(table, fieldName);
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
};
|
|
1112
|
+
const migrateRow = (table, row) => {
|
|
1113
|
+
const migrate = schemas.get(table)?.migrate;
|
|
1114
|
+
return migrate ? migrate(row) : row;
|
|
1115
|
+
};
|
|
1085
1116
|
const reactiveSubs = new Set;
|
|
1086
1117
|
const searchSubs = new Set;
|
|
1087
1118
|
const searchIndexes = new Map;
|
|
@@ -1090,6 +1121,12 @@ var createSyncEngine = (options = {}) => {
|
|
|
1090
1121
|
const changeLogSize = options.changeLogSize ?? 1024;
|
|
1091
1122
|
const changeLog = [];
|
|
1092
1123
|
let version = 0;
|
|
1124
|
+
const activityListeners = new Set;
|
|
1125
|
+
const emitActivity = (event) => {
|
|
1126
|
+
for (const listener of activityListeners) {
|
|
1127
|
+
listener(event);
|
|
1128
|
+
}
|
|
1129
|
+
};
|
|
1093
1130
|
const runInTransaction = options.transaction;
|
|
1094
1131
|
const instanceId = globalThis.crypto?.randomUUID?.() ?? `i${Math.random()}`;
|
|
1095
1132
|
let clusterBus;
|
|
@@ -1216,7 +1253,7 @@ var createSyncEngine = (options = {}) => {
|
|
|
1216
1253
|
return {
|
|
1217
1254
|
all: async (table) => {
|
|
1218
1255
|
readTables.add(table);
|
|
1219
|
-
const rows = [...await readerFor(table).all(ctx)];
|
|
1256
|
+
const rows = [...await readerFor(table).all(ctx)].map((row) => migrateRow(table, row));
|
|
1220
1257
|
const rule = ruleFor(table);
|
|
1221
1258
|
return rule ? rows.filter((row) => rule(ctx, row)) : rows;
|
|
1222
1259
|
},
|
|
@@ -1230,7 +1267,8 @@ var createSyncEngine = (options = {}) => {
|
|
|
1230
1267
|
} else {
|
|
1231
1268
|
readTables.add(table);
|
|
1232
1269
|
}
|
|
1233
|
-
const
|
|
1270
|
+
const raw = await reader.get(key, ctx);
|
|
1271
|
+
const row = raw === undefined ? undefined : migrateRow(table, raw);
|
|
1234
1272
|
const rule = ruleFor(table);
|
|
1235
1273
|
return rule && row !== undefined && !rule(ctx, row) ? undefined : row;
|
|
1236
1274
|
},
|
|
@@ -1238,7 +1276,7 @@ var createSyncEngine = (options = {}) => {
|
|
|
1238
1276
|
const reader = readerFor(table);
|
|
1239
1277
|
const rule = ruleFor(table);
|
|
1240
1278
|
const effective = rule ? (row) => predicate(row) && rule(ctx, row) : predicate;
|
|
1241
|
-
const matched = [...await reader.all(ctx)].filter(effective);
|
|
1279
|
+
const matched = [...await reader.all(ctx)].map((row) => migrateRow(table, row)).filter(effective);
|
|
1242
1280
|
if (reader.key !== undefined) {
|
|
1243
1281
|
const key = reader.key;
|
|
1244
1282
|
rangeDeps.push({
|
|
@@ -1293,6 +1331,7 @@ var createSyncEngine = (options = {}) => {
|
|
|
1293
1331
|
return Promise.resolve();
|
|
1294
1332
|
},
|
|
1295
1333
|
insert: async (table, data) => {
|
|
1334
|
+
validateWrite(table, "insert", data);
|
|
1296
1335
|
if (enforce) {
|
|
1297
1336
|
await authorizeWrite(table, "insert", data, ctx);
|
|
1298
1337
|
}
|
|
@@ -1301,6 +1340,7 @@ var createSyncEngine = (options = {}) => {
|
|
|
1301
1340
|
return row;
|
|
1302
1341
|
},
|
|
1303
1342
|
update: async (table, data) => {
|
|
1343
|
+
validateWrite(table, "update", data);
|
|
1304
1344
|
if (enforce) {
|
|
1305
1345
|
await authorizeWrite(table, "update", data, ctx);
|
|
1306
1346
|
}
|
|
@@ -1412,6 +1452,13 @@ var createSyncEngine = (options = {}) => {
|
|
|
1412
1452
|
version += 1;
|
|
1413
1453
|
const changeVersion = version;
|
|
1414
1454
|
logChange(changeVersion, { version: changeVersion, table, change });
|
|
1455
|
+
emitActivity({
|
|
1456
|
+
type: "change",
|
|
1457
|
+
at: Date.now(),
|
|
1458
|
+
table,
|
|
1459
|
+
op: change.op,
|
|
1460
|
+
version: changeVersion
|
|
1461
|
+
});
|
|
1415
1462
|
const emissions = [];
|
|
1416
1463
|
for (const subscription of subscriptionsForTable(table)) {
|
|
1417
1464
|
const diff = await subscriptionDiff(subscription, table, change);
|
|
@@ -1440,6 +1487,13 @@ var createSyncEngine = (options = {}) => {
|
|
|
1440
1487
|
const reactiveChanges = [];
|
|
1441
1488
|
for (const { table, change } of changes) {
|
|
1442
1489
|
logChange(batchVersion, { version: batchVersion, table, change });
|
|
1490
|
+
emitActivity({
|
|
1491
|
+
type: "change",
|
|
1492
|
+
at: Date.now(),
|
|
1493
|
+
table,
|
|
1494
|
+
op: change.op,
|
|
1495
|
+
version: batchVersion
|
|
1496
|
+
});
|
|
1443
1497
|
reactiveChanges.push({
|
|
1444
1498
|
table,
|
|
1445
1499
|
key: changedKeyFor(table, change),
|
|
@@ -1705,8 +1759,13 @@ var createSyncEngine = (options = {}) => {
|
|
|
1705
1759
|
const key = definition.key ?? defaultKey;
|
|
1706
1760
|
const match = definition.match;
|
|
1707
1761
|
const tables = definition.tables ?? [collection];
|
|
1708
|
-
const
|
|
1709
|
-
const
|
|
1762
|
+
const scopedTable = tables.length === 1 ? tables[0] : undefined;
|
|
1763
|
+
const readRule = scopedTable !== undefined ? readRuleFor(scopedTable) : undefined;
|
|
1764
|
+
const rehydrate = async () => {
|
|
1765
|
+
const raw = [...await definition.hydrate(params, ctx)];
|
|
1766
|
+
const rows = scopedTable !== undefined ? raw.map((row) => migrateRow(scopedTable, row)) : raw;
|
|
1767
|
+
return readRule ? rows.filter((row) => readRule(ctx, row)) : rows;
|
|
1768
|
+
};
|
|
1710
1769
|
const incremental = match !== undefined && tables.length === 1;
|
|
1711
1770
|
const boundMatch = incremental ? (row) => match(row, params, ctx) && (readRule ? readRule(ctx, row) : true) : () => true;
|
|
1712
1771
|
const view = createMaterializedView({
|
|
@@ -1754,9 +1813,11 @@ var createSyncEngine = (options = {}) => {
|
|
|
1754
1813
|
throw new UnauthorizedError(`hydrate collection "${collection}"`);
|
|
1755
1814
|
}
|
|
1756
1815
|
}
|
|
1757
|
-
const
|
|
1816
|
+
const raw = [...await definition.hydrate(params, ctx)];
|
|
1758
1817
|
const tables = definition.tables ?? [collection];
|
|
1759
|
-
const
|
|
1818
|
+
const scopedTable = tables.length === 1 ? tables[0] : undefined;
|
|
1819
|
+
const rows = scopedTable !== undefined ? raw.map((row) => migrateRow(scopedTable, row)) : raw;
|
|
1820
|
+
const readRule = scopedTable !== undefined ? readRuleFor(scopedTable) : undefined;
|
|
1760
1821
|
return readRule ? rows.filter((row) => readRule(ctx, row)) : rows;
|
|
1761
1822
|
},
|
|
1762
1823
|
applyChange: (table, change) => applyChange(table, change),
|
|
@@ -1804,6 +1865,10 @@ var createSyncEngine = (options = {}) => {
|
|
|
1804
1865
|
registerPermissions: (table, rules) => {
|
|
1805
1866
|
permissions.set(table, rules);
|
|
1806
1867
|
},
|
|
1868
|
+
registerSchema: (table, schema) => {
|
|
1869
|
+
schemas.set(table, schema);
|
|
1870
|
+
},
|
|
1871
|
+
migrate: (table, row) => migrateRow(table, row),
|
|
1807
1872
|
runMutation: async (name, args, ctx) => {
|
|
1808
1873
|
const mutation = mutations.get(name);
|
|
1809
1874
|
if (mutation === undefined) {
|
|
@@ -1816,13 +1881,29 @@ var createSyncEngine = (options = {}) => {
|
|
|
1816
1881
|
}
|
|
1817
1882
|
}
|
|
1818
1883
|
const runHandler = async (tx) => {
|
|
1819
|
-
const { actions, buffered
|
|
1820
|
-
const
|
|
1821
|
-
return { buffered
|
|
1884
|
+
const { actions, buffered } = makeActions(tx, ctx, true);
|
|
1885
|
+
const result = await mutation.handler(args, ctx, actions);
|
|
1886
|
+
return { buffered, result };
|
|
1822
1887
|
};
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1888
|
+
try {
|
|
1889
|
+
const { buffered, result } = runInTransaction !== undefined ? await runInTransaction((tx) => runHandler(tx)) : await runHandler(undefined);
|
|
1890
|
+
await applyChangeBatch(buffered);
|
|
1891
|
+
emitActivity({
|
|
1892
|
+
type: "mutation",
|
|
1893
|
+
at: Date.now(),
|
|
1894
|
+
name,
|
|
1895
|
+
status: "ok"
|
|
1896
|
+
});
|
|
1897
|
+
return result;
|
|
1898
|
+
} catch (error) {
|
|
1899
|
+
emitActivity({
|
|
1900
|
+
type: "mutation",
|
|
1901
|
+
at: Date.now(),
|
|
1902
|
+
name,
|
|
1903
|
+
status: "error"
|
|
1904
|
+
});
|
|
1905
|
+
throw error;
|
|
1906
|
+
}
|
|
1826
1907
|
},
|
|
1827
1908
|
registerSchedule: (schedule) => {
|
|
1828
1909
|
schedules.set(schedule.name, schedule);
|
|
@@ -1841,9 +1922,68 @@ var createSyncEngine = (options = {}) => {
|
|
|
1841
1922
|
};
|
|
1842
1923
|
const buffered = runInTransaction !== undefined ? await runInTransaction((tx) => runHandler(tx)) : await runHandler(undefined);
|
|
1843
1924
|
await applyChangeBatch(buffered);
|
|
1925
|
+
},
|
|
1926
|
+
inspect: () => {
|
|
1927
|
+
const collections = [...registry.entries()].map(([name, def]) => {
|
|
1928
|
+
const kind = def.kind ?? "view";
|
|
1929
|
+
let tables = [];
|
|
1930
|
+
if (kind === "join") {
|
|
1931
|
+
const join = def;
|
|
1932
|
+
tables = [join.left.table, join.right.table];
|
|
1933
|
+
} else if (kind === "graph") {
|
|
1934
|
+
tables = def.query.tables();
|
|
1935
|
+
} else if (kind === "search") {
|
|
1936
|
+
tables = [
|
|
1937
|
+
def.table
|
|
1938
|
+
];
|
|
1939
|
+
} else if (kind === "view") {
|
|
1940
|
+
tables = def.tables ?? [name];
|
|
1941
|
+
}
|
|
1942
|
+
return {
|
|
1943
|
+
name,
|
|
1944
|
+
kind,
|
|
1945
|
+
tables,
|
|
1946
|
+
subscriptions: active.get(name)?.size ?? 0
|
|
1947
|
+
};
|
|
1948
|
+
});
|
|
1949
|
+
const DEVTOOLS_RECENT = 50;
|
|
1950
|
+
return {
|
|
1951
|
+
version,
|
|
1952
|
+
collections,
|
|
1953
|
+
mutations: [...mutations.keys()],
|
|
1954
|
+
schedules: [...schedules.values()].map((schedule) => ({
|
|
1955
|
+
name: schedule.name,
|
|
1956
|
+
pattern: schedule.pattern
|
|
1957
|
+
})),
|
|
1958
|
+
readers: [...readers.keys()],
|
|
1959
|
+
writers: [...writers.keys()],
|
|
1960
|
+
recentChanges: changeLog.slice(-DEVTOOLS_RECENT).map((entry) => ({
|
|
1961
|
+
version: entry.version,
|
|
1962
|
+
table: entry.table,
|
|
1963
|
+
op: entry.change.op
|
|
1964
|
+
}))
|
|
1965
|
+
};
|
|
1966
|
+
},
|
|
1967
|
+
onActivity: (listener) => {
|
|
1968
|
+
activityListeners.add(listener);
|
|
1969
|
+
return () => {
|
|
1970
|
+
activityListeners.delete(listener);
|
|
1971
|
+
};
|
|
1844
1972
|
}
|
|
1845
1973
|
};
|
|
1846
1974
|
};
|
|
1975
|
+
// src/engine/schema.ts
|
|
1976
|
+
var defineSchema = (schemas) => schemas;
|
|
1977
|
+
var isFiniteNumber = (value) => typeof value === "number" && Number.isFinite(value);
|
|
1978
|
+
var field = {
|
|
1979
|
+
string: (value) => typeof value === "string",
|
|
1980
|
+
number: isFiniteNumber,
|
|
1981
|
+
boolean: (value) => typeof value === "boolean",
|
|
1982
|
+
any: () => true,
|
|
1983
|
+
optional: (inner) => (value) => value === undefined || inner(value),
|
|
1984
|
+
array: (inner) => (value) => Array.isArray(value) && value.every(inner),
|
|
1985
|
+
enum: (...values) => (value) => values.includes(value)
|
|
1986
|
+
};
|
|
1847
1987
|
// src/engine/routes.ts
|
|
1848
1988
|
var emptyContext = () => ({});
|
|
1849
1989
|
var hydrateRoute = (engine, collection, resolveContext = emptyContext) => {
|
|
@@ -2091,7 +2231,9 @@ export {
|
|
|
2091
2231
|
hydrateRoute,
|
|
2092
2232
|
fromRowChange,
|
|
2093
2233
|
filterOp,
|
|
2234
|
+
field,
|
|
2094
2235
|
defineSearchCollection,
|
|
2236
|
+
defineSchema,
|
|
2095
2237
|
defineSchedule,
|
|
2096
2238
|
defineReactiveQuery,
|
|
2097
2239
|
definePermissions,
|
|
@@ -2112,8 +2254,9 @@ export {
|
|
|
2112
2254
|
chain,
|
|
2113
2255
|
aggregateOp,
|
|
2114
2256
|
UnauthorizedError,
|
|
2257
|
+
SchemaError,
|
|
2115
2258
|
SEARCH_SCORE_FIELD
|
|
2116
2259
|
};
|
|
2117
2260
|
|
|
2118
|
-
//# debugId=
|
|
2261
|
+
//# debugId=B040890280D3C67864756E2164756E21
|
|
2119
2262
|
//# sourceMappingURL=index.js.map
|