@stratasync/server 0.2.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 +188 -0
- package/dist/bootstrap/bootstrap-service.d.ts +41 -0
- package/dist/bootstrap/bootstrap-service.d.ts.map +1 -0
- package/dist/bootstrap/bootstrap-service.js +411 -0
- package/dist/bootstrap/bootstrap-service.js.map +1 -0
- package/dist/config.d.ts +124 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +10 -0
- package/dist/config.js.map +1 -0
- package/dist/create-sync-server.d.ts +5 -0
- package/dist/create-sync-server.d.ts.map +1 -0
- package/dist/create-sync-server.js +96 -0
- package/dist/create-sync-server.js.map +1 -0
- package/dist/dao/sync-dao.d.ts +64 -0
- package/dist/dao/sync-dao.d.ts.map +1 -0
- package/dist/dao/sync-dao.js +137 -0
- package/dist/dao/sync-dao.js.map +1 -0
- package/dist/db.d.ts +37 -0
- package/dist/db.d.ts.map +1 -0
- package/dist/db.js +2 -0
- package/dist/db.js.map +1 -0
- package/dist/delta/delta-publisher.d.ts +52 -0
- package/dist/delta/delta-publisher.d.ts.map +1 -0
- package/dist/delta/delta-publisher.js +217 -0
- package/dist/delta/delta-publisher.js.map +1 -0
- package/dist/delta/delta-service.d.ts +13 -0
- package/dist/delta/delta-service.d.ts.map +1 -0
- package/dist/delta/delta-service.js +36 -0
- package/dist/delta/delta-service.js.map +1 -0
- package/dist/fastify/index.d.ts +7 -0
- package/dist/fastify/index.d.ts.map +1 -0
- package/dist/fastify/index.js +5 -0
- package/dist/fastify/index.js.map +1 -0
- package/dist/fastify/middleware.d.ts +16 -0
- package/dist/fastify/middleware.d.ts.map +1 -0
- package/dist/fastify/middleware.js +101 -0
- package/dist/fastify/middleware.js.map +1 -0
- package/dist/fastify/routes.d.ts +17 -0
- package/dist/fastify/routes.d.ts.map +1 -0
- package/dist/fastify/routes.js +150 -0
- package/dist/fastify/routes.js.map +1 -0
- package/dist/fastify/validation.d.ts +59 -0
- package/dist/fastify/validation.d.ts.map +1 -0
- package/dist/fastify/validation.js +79 -0
- package/dist/fastify/validation.js.map +1 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +22 -0
- package/dist/index.js.map +1 -0
- package/dist/mutate/archive-mutation.d.ts +10 -0
- package/dist/mutate/archive-mutation.d.ts.map +1 -0
- package/dist/mutate/archive-mutation.js +14 -0
- package/dist/mutate/archive-mutation.js.map +1 -0
- package/dist/mutate/field-codecs.d.ts +15 -0
- package/dist/mutate/field-codecs.d.ts.map +1 -0
- package/dist/mutate/field-codecs.js +103 -0
- package/dist/mutate/field-codecs.js.map +1 -0
- package/dist/mutate/model-handlers.d.ts +32 -0
- package/dist/mutate/model-handlers.d.ts.map +1 -0
- package/dist/mutate/model-handlers.js +131 -0
- package/dist/mutate/model-handlers.js.map +1 -0
- package/dist/mutate/mutate-service.d.ts +30 -0
- package/dist/mutate/mutate-service.d.ts.map +1 -0
- package/dist/mutate/mutate-service.js +326 -0
- package/dist/mutate/mutate-service.js.map +1 -0
- package/dist/types.d.ts +101 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +29 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/composite-ids.d.ts +14 -0
- package/dist/utils/composite-ids.d.ts.map +1 -0
- package/dist/utils/composite-ids.js +15 -0
- package/dist/utils/composite-ids.js.map +1 -0
- package/dist/utils/dates.d.ts +8 -0
- package/dist/utils/dates.d.ts.map +1 -0
- package/dist/utils/dates.js +101 -0
- package/dist/utils/dates.js.map +1 -0
- package/dist/utils/sync-scope.d.ts +4 -0
- package/dist/utils/sync-scope.d.ts.map +1 -0
- package/dist/utils/sync-scope.js +17 -0
- package/dist/utils/sync-scope.js.map +1 -0
- package/dist/utils/sync-utils.d.ts +27 -0
- package/dist/utils/sync-utils.d.ts.map +1 -0
- package/dist/utils/sync-utils.js +94 -0
- package/dist/utils/sync-utils.js.map +1 -0
- package/dist/websocket/sync-websocket.d.ts +14 -0
- package/dist/websocket/sync-websocket.d.ts.map +1 -0
- package/dist/websocket/sync-websocket.js +326 -0
- package/dist/websocket/sync-websocket.js.map +1 -0
- package/package.json +70 -0
package/README.md
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
# @stratasync/server
|
|
2
|
+
|
|
3
|
+
Server-side sync SDK. Provides bootstrap streaming, delta publishing, mutation processing, and WebSocket real-time sync with a registration-based model API.
|
|
4
|
+
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
import { createSyncServer } from "@stratasync/server";
|
|
9
|
+
import { syncActions, syncGroupMemberships, tasks, labels } from "./schema";
|
|
10
|
+
|
|
11
|
+
const sync = await createSyncServer({
|
|
12
|
+
db,
|
|
13
|
+
tables: { syncActions, syncGroupMemberships },
|
|
14
|
+
auth: {
|
|
15
|
+
verifyToken: async (token) => {
|
|
16
|
+
const user = await verifyJwt(token);
|
|
17
|
+
return user ? { userId: user.id, email: user.email } : null;
|
|
18
|
+
},
|
|
19
|
+
resolveGroups: async (userId) => {
|
|
20
|
+
// Return workspace IDs the user belongs to
|
|
21
|
+
return ["workspace-1", userId];
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
models: {
|
|
25
|
+
Task: {
|
|
26
|
+
table: tasks,
|
|
27
|
+
groupKey: "workspaceId",
|
|
28
|
+
bootstrap: {
|
|
29
|
+
fields: ["id", "title", "completedAt", "workspaceId", "createdAt"],
|
|
30
|
+
instantFields: ["completedAt", "createdAt"],
|
|
31
|
+
cursor: { type: "simple", idField: "id" },
|
|
32
|
+
buildScopeWhere: (filter) =>
|
|
33
|
+
inArray(getColumn(tasks, "workspaceId"), filter.workspaceGroupIds),
|
|
34
|
+
},
|
|
35
|
+
mutate: {
|
|
36
|
+
kind: "standard",
|
|
37
|
+
actions: new Set(["I", "U", "D"]),
|
|
38
|
+
insertFields: {
|
|
39
|
+
title: { type: "string" },
|
|
40
|
+
completedAt: { type: "date" },
|
|
41
|
+
workspaceId: { type: "string" },
|
|
42
|
+
createdAt: { type: "dateNow" },
|
|
43
|
+
},
|
|
44
|
+
updateFields: new Set(["title", "completedAt"]),
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// Register on Fastify
|
|
51
|
+
sync.registerRoutes(fastifyServer);
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Architecture
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
Client Server (@stratasync/server)
|
|
58
|
+
| |
|
|
59
|
+
|-- GET /sync/bootstrap -------->| BootstrapService
|
|
60
|
+
|<-------- NDJSON stream --------| Streams all model rows with cursor pagination
|
|
61
|
+
| |
|
|
62
|
+
|-- POST /sync/mutate ---------> | MutateService
|
|
63
|
+
|<-------- { lastSyncId } -------| Validates, deduplicates, writes sync_actions
|
|
64
|
+
| |
|
|
65
|
+
|-- GET /sync/deltas ----------> | DeltaService
|
|
66
|
+
|<-------- { actions[] } --------| Fetches sync_actions after cursor
|
|
67
|
+
| |
|
|
68
|
+
|-- WS /sync/ws ---------------> | WebSocket handler
|
|
69
|
+
|<====== real-time deltas =======| Subscribe, replay, buffer, flush
|
|
70
|
+
| |
|
|
71
|
+
| DeltaPublisher
|
|
72
|
+
| Redis pub/sub + in-memory fallback
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Sync Protocol
|
|
76
|
+
|
|
77
|
+
1. **Bootstrap** — Client sends `GET /sync/bootstrap`. Server streams all model rows as NDJSON (first line = metadata with `lastSyncId`, subsequent lines = model rows with `__class` tag).
|
|
78
|
+
|
|
79
|
+
2. **Mutations** — Client sends `POST /sync/mutate` with a batch of transactions. Each transaction specifies `modelName`, `modelId`, `action` (INSERT/UPDATE/DELETE/ARCHIVE/UNARCHIVE), and `payload`. Server deduplicates via `(clientId, clientTxId)` unique constraint, applies the mutation, creates a `sync_action` row, and publishes a delta.
|
|
80
|
+
|
|
81
|
+
3. **Deltas** — Client polls `GET /sync/deltas?after={lastSyncId}` for incremental updates. Returns actions with `hasMore` flag for pagination.
|
|
82
|
+
|
|
83
|
+
4. **WebSocket** — Client connects to `/sync/ws` and sends a `subscribe` message with `afterSyncId`. Server replays missed actions, then streams live deltas. Buffers actions during replay to prevent gaps.
|
|
84
|
+
|
|
85
|
+
### Key Concepts
|
|
86
|
+
|
|
87
|
+
**Sync Groups** — Every model declares a `groupKey` (e.g., `"workspaceId"`) that determines which sync group it belongs to. Users can only see models in their groups. The special value `"__modelId__"` means the model's own ID is its group (used for User/Workspace models). `null` means globally visible.
|
|
88
|
+
|
|
89
|
+
**Field Codecs** — Field types (`string`, `stringNull`, `number`, `date`, `dateNow`, `dateOnly`) control how payload values are coerced on insert/update and serialized for sync. `dateOnly` fields use day-aligned UTC epochs (multiples of 86400000ms). `date`/`dateNow` fields use millisecond epochs.
|
|
90
|
+
|
|
91
|
+
**Cursor Pagination** — Bootstrap uses cursor-based pagination. Simple cursors use `id > cursor`. Composite cursors (for join tables like TaskLabel) use multi-level OR conditions.
|
|
92
|
+
|
|
93
|
+
**Deduplication** — Mutations include `clientId` + `clientTxId`. A unique constraint on `sync_actions(client_id, client_tx_id)` prevents duplicate processing. If a duplicate is detected, the existing `syncId` is returned.
|
|
94
|
+
|
|
95
|
+
## Model Config
|
|
96
|
+
|
|
97
|
+
Each model needs both `bootstrap` (how to stream it) and `mutate` (how to process mutations) config:
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
interface SyncModelConfig {
|
|
101
|
+
table: AnyPgTable; // Drizzle table reference
|
|
102
|
+
groupKey: string | "__modelId__" | null; // Sync group field
|
|
103
|
+
bootstrap: BootstrapModelConfig;
|
|
104
|
+
mutate: StandardMutateConfig | CompositeMutateConfig;
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Standard Models
|
|
109
|
+
|
|
110
|
+
Most models use `StandardMutateConfig` with an `id` primary key:
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
mutate: {
|
|
114
|
+
kind: "standard",
|
|
115
|
+
actions: new Set(["I", "U", "D", "A", "V"]),
|
|
116
|
+
insertFields: { title: { type: "string" }, ... },
|
|
117
|
+
updateFields: new Set(["title", "completedAt"]),
|
|
118
|
+
onBeforeInsert: async (db, modelId, payload, data) => data,
|
|
119
|
+
onBeforeUpdate: async (db, modelId, payload, data) => data,
|
|
120
|
+
onAfterMutation: (ctx) => { /* side effects */ },
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Composite Models
|
|
125
|
+
|
|
126
|
+
Join tables (e.g., TaskLabel) use `CompositeMutateConfig` with no `id` field:
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
mutate: {
|
|
130
|
+
kind: "composite",
|
|
131
|
+
actions: new Set(["I", "D"]),
|
|
132
|
+
insertFields: { taskId: { type: "string" }, labelId: { type: "string" } },
|
|
133
|
+
buildDeleteWhere: (payload) => and(eq(table.taskId, payload.taskId), ...),
|
|
134
|
+
compositeId: {
|
|
135
|
+
computeId: (modelName, modelId, payload) => uuidv5(...),
|
|
136
|
+
},
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## Auth
|
|
141
|
+
|
|
142
|
+
Auth is pluggable via two callbacks:
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
auth: {
|
|
146
|
+
verifyToken: async (token: string) => SyncAuthPayload | null,
|
|
147
|
+
resolveGroups: async (userId: string) => string[],
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
The package does not know about JWT, API keys, or any auth provider. Your app provides the verification logic.
|
|
152
|
+
|
|
153
|
+
## WebSocket Hooks
|
|
154
|
+
|
|
155
|
+
Inject app-specific WebSocket behavior (e.g., live editing) via hooks:
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
websocketHooks: {
|
|
159
|
+
onMessage: async (ws, message, context) => boolean, // return true if handled
|
|
160
|
+
onClose: async (ws, context) => void,
|
|
161
|
+
onSubscribe: async (ws, context, previousContext) => void,
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Database Requirements
|
|
166
|
+
|
|
167
|
+
The package requires two Drizzle tables passed via `config.tables`:
|
|
168
|
+
|
|
169
|
+
**`syncActions`** — columns: `id` (bigserial PK), `model` (varchar), `modelId` (uuid), `action` (char 1), `data` (jsonb), `groupId` (uuid nullable), `clientId` (varchar nullable), `clientTxId` (uuid nullable), `createdAt` (timestamp). Unique constraint on `(clientId, clientTxId)`.
|
|
170
|
+
|
|
171
|
+
**`syncGroupMemberships`** — columns: `id` (uuid PK), `userId` (uuid), `groupId` (uuid), `groupType` (varchar), `createdAt` (timestamp).
|
|
172
|
+
|
|
173
|
+
## Exports
|
|
174
|
+
|
|
175
|
+
```typescript
|
|
176
|
+
// Main entry — import from "@stratasync/server"
|
|
177
|
+
import { createSyncServer, SyncDao, BootstrapService, ... } from "@stratasync/server";
|
|
178
|
+
|
|
179
|
+
// Fastify-specific — import from "@stratasync/server/fastify"
|
|
180
|
+
import { registerSyncRoutes, createSyncAuthMiddleware, ... } from "@stratasync/server/fastify";
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
## Error Handling
|
|
184
|
+
|
|
185
|
+
- **Pub/sub callback errors** are caught and silently ignored (standard event emitter pattern). Delta delivery is best-effort.
|
|
186
|
+
- **Mutation hook errors** (`onAfterMutation`) are logged as warnings but do not fail the transaction. The sync action is already committed.
|
|
187
|
+
- **Auth failures** return 401 with descriptive error messages.
|
|
188
|
+
- **Validation failures** return 400 with field-level error details.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { SyncLogger, SyncModelConfig } from "../config.js";
|
|
2
|
+
import type { SyncDao } from "../dao/sync-dao.js";
|
|
3
|
+
import type { BootstrapRequest, SyncUserContext } from "../types.js";
|
|
4
|
+
interface BatchLoadRequestIndexed {
|
|
5
|
+
modelName: string;
|
|
6
|
+
indexedKey: string;
|
|
7
|
+
keyValue: string;
|
|
8
|
+
}
|
|
9
|
+
interface BatchLoadRequestGroup {
|
|
10
|
+
modelName: string;
|
|
11
|
+
groupId: string;
|
|
12
|
+
}
|
|
13
|
+
type BatchLoadRequest = BatchLoadRequestIndexed | BatchLoadRequestGroup;
|
|
14
|
+
export declare class BootstrapService {
|
|
15
|
+
private readonly dao;
|
|
16
|
+
private readonly db;
|
|
17
|
+
private readonly modelRegistry;
|
|
18
|
+
private readonly allowedIndexedKeys;
|
|
19
|
+
private readonly allModelNames;
|
|
20
|
+
private readonly logger;
|
|
21
|
+
constructor(db: unknown, dao: SyncDao, models: Record<string, SyncModelConfig>, logger?: SyncLogger);
|
|
22
|
+
generateBootstrapNdjson(context: SyncUserContext, request: BootstrapRequest): AsyncGenerator<string, void, unknown>;
|
|
23
|
+
batchLoadNdjson(context: SyncUserContext, requests: BatchLoadRequest[], firstSyncId?: string): AsyncGenerator<string, void, unknown>;
|
|
24
|
+
private static buildFilterContext;
|
|
25
|
+
private static buildIndexedWhere;
|
|
26
|
+
private static indexedWhereCondition;
|
|
27
|
+
private static simpleCursorCondition;
|
|
28
|
+
private static compositeCursorCondition;
|
|
29
|
+
private static compositeCursorBranch;
|
|
30
|
+
private static compositeCursorPrefix;
|
|
31
|
+
private streamModel;
|
|
32
|
+
private streamSimpleCursor;
|
|
33
|
+
private streamCompositeCursor;
|
|
34
|
+
private batchLoadModel;
|
|
35
|
+
private filterBatchRowsByFirstSyncId;
|
|
36
|
+
private static serializeBatchRow;
|
|
37
|
+
private countModelRows;
|
|
38
|
+
private streamModelRows;
|
|
39
|
+
}
|
|
40
|
+
export {};
|
|
41
|
+
//# sourceMappingURL=bootstrap-service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bootstrap-service.d.ts","sourceRoot":"","sources":["../../src/bootstrap/bootstrap-service.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAGV,UAAU,EACV,eAAe,EAChB,MAAM,cAAc,CAAC;AAEtB,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAElD,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AA4BrE,UAAU,uBAAuB;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,UAAU,qBAAqB;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,KAAK,gBAAgB,GAAG,uBAAuB,GAAG,qBAAqB,CAAC;AAsFxE,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAU;IAC9B,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAS;IAC5B,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAoC;IAClE,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAoC;IACvE,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAW;IACzC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAa;gBAGlC,EAAE,EAAE,OAAO,EACX,GAAG,EAAE,OAAO,EACZ,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,EACvC,MAAM,GAAE,UAAuB;IA8B1B,uBAAuB,CAC5B,OAAO,EAAE,eAAe,EACxB,OAAO,EAAE,gBAAgB,GACxB,cAAc,CAAC,MAAM,EAAE,IAAI,EAAE,OAAO,CAAC;IA4CjC,eAAe,CACpB,OAAO,EAAE,eAAe,EACxB,QAAQ,EAAE,gBAAgB,EAAE,EAC5B,WAAW,CAAC,EAAE,MAAM,GACnB,cAAc,CAAC,MAAM,EAAE,IAAI,EAAE,OAAO,CAAC;IA8BxC,OAAO,CAAC,MAAM,CAAC,kBAAkB;IAWjC,OAAO,CAAC,MAAM,CAAC,iBAAiB;IAmBhC,OAAO,CAAC,MAAM,CAAC,qBAAqB;IAepC,OAAO,CAAC,MAAM,CAAC,qBAAqB;IAYpC,OAAO,CAAC,MAAM,CAAC,wBAAwB;IAuCvC,OAAO,CAAC,MAAM,CAAC,qBAAqB;IAgCpC,OAAO,CAAC,MAAM,CAAC,qBAAqB;YA6BrB,WAAW;YAoBX,kBAAkB;YA8ClB,qBAAqB;YAwErB,cAAc;YAqEd,4BAA4B;IAkC3C,OAAO,CAAC,MAAM,CAAC,iBAAiB;YAclB,cAAc;IAoB5B,OAAO,CAAC,eAAe;CAMxB"}
|
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
import { and, asc, count, eq, gt, or } from "drizzle-orm";
|
|
2
|
+
import { noopLogger } from "../config.js";
|
|
3
|
+
import { toDateOnlyEpoch, toInstantEpoch } from "../utils/dates.js";
|
|
4
|
+
import { resolveRequestedSyncGroups } from "../utils/sync-scope.js";
|
|
5
|
+
import { getColumn, parseSyncIdString, serializeSyncId, } from "../utils/sync-utils.js";
|
|
6
|
+
const isIndexedRequest = (request) => "indexedKey" in request && "keyValue" in request;
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
// Helpers
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
const combineWhere = (conditions) => {
|
|
11
|
+
const active = conditions.filter((condition) => condition !== undefined);
|
|
12
|
+
if (active.length === 0) {
|
|
13
|
+
return undefined;
|
|
14
|
+
}
|
|
15
|
+
const [first, ...rest] = active;
|
|
16
|
+
if (!first) {
|
|
17
|
+
return undefined;
|
|
18
|
+
}
|
|
19
|
+
if (rest.length === 0) {
|
|
20
|
+
return first;
|
|
21
|
+
}
|
|
22
|
+
return and(first, ...rest);
|
|
23
|
+
};
|
|
24
|
+
const scopedWhere = (scope, ...conditions) => combineWhere([scope, ...conditions]) ?? scope;
|
|
25
|
+
const mapRow = (item, def) => {
|
|
26
|
+
const dateOnlySet = new Set(def.fieldDef.dateOnlyFields);
|
|
27
|
+
const instantSet = new Set(def.fieldDef.instantFields);
|
|
28
|
+
const row = {};
|
|
29
|
+
for (const field of def.fieldDef.fields) {
|
|
30
|
+
if (dateOnlySet.has(field)) {
|
|
31
|
+
row[field] = toDateOnlyEpoch(item[field]);
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
if (instantSet.has(field)) {
|
|
35
|
+
row[field] = toInstantEpoch(item[field]);
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
row[field] = item[field];
|
|
39
|
+
}
|
|
40
|
+
if (def.cursor.type === "composite") {
|
|
41
|
+
row.id = def.cursor.syntheticId(item);
|
|
42
|
+
}
|
|
43
|
+
return row;
|
|
44
|
+
};
|
|
45
|
+
const toCountNumber = (value) => {
|
|
46
|
+
if (typeof value === "number") {
|
|
47
|
+
return value;
|
|
48
|
+
}
|
|
49
|
+
if (typeof value === "bigint") {
|
|
50
|
+
return Number(value);
|
|
51
|
+
}
|
|
52
|
+
if (typeof value === "string") {
|
|
53
|
+
const parsed = Number.parseInt(value, 10);
|
|
54
|
+
return Number.isNaN(parsed) ? 0 : parsed;
|
|
55
|
+
}
|
|
56
|
+
return 0;
|
|
57
|
+
};
|
|
58
|
+
// ---------------------------------------------------------------------------
|
|
59
|
+
// Service
|
|
60
|
+
// ---------------------------------------------------------------------------
|
|
61
|
+
export class BootstrapService {
|
|
62
|
+
dao;
|
|
63
|
+
db;
|
|
64
|
+
modelRegistry;
|
|
65
|
+
allowedIndexedKeys;
|
|
66
|
+
allModelNames;
|
|
67
|
+
logger;
|
|
68
|
+
constructor(db, dao, models, logger = noopLogger) {
|
|
69
|
+
this.db = db;
|
|
70
|
+
this.dao = dao;
|
|
71
|
+
this.logger = logger;
|
|
72
|
+
// Build internal registries from config
|
|
73
|
+
this.modelRegistry = {};
|
|
74
|
+
this.allowedIndexedKeys = {};
|
|
75
|
+
for (const [name, model] of Object.entries(models)) {
|
|
76
|
+
this.modelRegistry[name] = {
|
|
77
|
+
buildScopeWhere: model.bootstrap.buildScopeWhere,
|
|
78
|
+
cursor: model.bootstrap.cursor,
|
|
79
|
+
fieldDef: {
|
|
80
|
+
dateOnlyFields: model.bootstrap.dateOnlyFields,
|
|
81
|
+
fields: model.bootstrap.fields,
|
|
82
|
+
instantFields: model.bootstrap.instantFields,
|
|
83
|
+
},
|
|
84
|
+
table: model.table,
|
|
85
|
+
};
|
|
86
|
+
if (model.bootstrap.allowedIndexedKeys) {
|
|
87
|
+
this.allowedIndexedKeys[name] = model.bootstrap.allowedIndexedKeys;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
this.allModelNames = Object.keys(this.modelRegistry);
|
|
91
|
+
}
|
|
92
|
+
async *generateBootstrapNdjson(context, request) {
|
|
93
|
+
const groups = resolveRequestedSyncGroups(context.groups, request.groups);
|
|
94
|
+
this.logger.info({ groups, userId: context.userId }, "Bootstrap started");
|
|
95
|
+
const filter = BootstrapService.buildFilterContext({
|
|
96
|
+
...context,
|
|
97
|
+
groups,
|
|
98
|
+
});
|
|
99
|
+
const lastSyncId = await this.dao.getLastSyncIdForGroups(groups);
|
|
100
|
+
const modelsToBootstrap = request.models ?? this.allModelNames;
|
|
101
|
+
const returnedModelsCount = {};
|
|
102
|
+
for (const modelName of modelsToBootstrap) {
|
|
103
|
+
const modelCount = await this.countModelRows(modelName, filter);
|
|
104
|
+
returnedModelsCount[modelName] = modelCount;
|
|
105
|
+
}
|
|
106
|
+
const metadata = {
|
|
107
|
+
lastSyncId: serializeSyncId(lastSyncId),
|
|
108
|
+
returnedModelsCount,
|
|
109
|
+
schemaHash: request.schemaHash,
|
|
110
|
+
subscribedSyncGroups: groups,
|
|
111
|
+
};
|
|
112
|
+
this.logger.debug({ metadata }, "Bootstrap metadata");
|
|
113
|
+
yield JSON.stringify(metadata);
|
|
114
|
+
for (const modelName of modelsToBootstrap) {
|
|
115
|
+
let rowCount = 0;
|
|
116
|
+
for await (const row of this.streamModelRows(modelName, filter)) {
|
|
117
|
+
rowCount += 1;
|
|
118
|
+
yield JSON.stringify({ __class: modelName, ...row });
|
|
119
|
+
}
|
|
120
|
+
this.logger.debug({ modelName, rowCount }, "Bootstrap model streamed");
|
|
121
|
+
}
|
|
122
|
+
this.logger.info({ groups, returnedModelsCount, userId: context.userId }, "Bootstrap completed");
|
|
123
|
+
}
|
|
124
|
+
async *batchLoadNdjson(context, requests, firstSyncId) {
|
|
125
|
+
await Promise.resolve();
|
|
126
|
+
const batchFirstSyncId = firstSyncId
|
|
127
|
+
? parseSyncIdString(firstSyncId)
|
|
128
|
+
: undefined;
|
|
129
|
+
for (const request of requests) {
|
|
130
|
+
let requestGroups = context.groups;
|
|
131
|
+
if (!isIndexedRequest(request)) {
|
|
132
|
+
requestGroups = resolveRequestedSyncGroups(context.groups, [
|
|
133
|
+
request.groupId,
|
|
134
|
+
]);
|
|
135
|
+
}
|
|
136
|
+
if (!isIndexedRequest(request) && requestGroups.length === 0) {
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
const filter = BootstrapService.buildFilterContext({
|
|
140
|
+
...context,
|
|
141
|
+
groups: requestGroups,
|
|
142
|
+
});
|
|
143
|
+
yield* this.batchLoadModel(request.modelName, request, filter, requestGroups, batchFirstSyncId);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
static buildFilterContext(context) {
|
|
147
|
+
const groupIds = [...new Set(context.groups)];
|
|
148
|
+
return {
|
|
149
|
+
authorizedGroupIds: groupIds,
|
|
150
|
+
userId: context.userId,
|
|
151
|
+
workspaceGroupIds: groupIds.filter((group) => group !== context.userId),
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
static buildIndexedWhere(modelName, request, allowedIndexedKeys) {
|
|
155
|
+
if (!isIndexedRequest(request)) {
|
|
156
|
+
return undefined;
|
|
157
|
+
}
|
|
158
|
+
const allowed = allowedIndexedKeys[modelName];
|
|
159
|
+
if (!allowed?.includes(request.indexedKey)) {
|
|
160
|
+
throw new Error(`Indexed key "${request.indexedKey}" is not allowed for model "${modelName}"`);
|
|
161
|
+
}
|
|
162
|
+
return { [request.indexedKey]: request.keyValue };
|
|
163
|
+
}
|
|
164
|
+
static indexedWhereCondition(table, indexedWhere) {
|
|
165
|
+
if (!indexedWhere) {
|
|
166
|
+
return undefined;
|
|
167
|
+
}
|
|
168
|
+
const conditions = Object.entries(indexedWhere).map(([key, value]) => eq(getColumn(table, key), value));
|
|
169
|
+
return combineWhere(conditions);
|
|
170
|
+
}
|
|
171
|
+
static simpleCursorCondition(table, idField, cursor) {
|
|
172
|
+
if (!cursor) {
|
|
173
|
+
return undefined;
|
|
174
|
+
}
|
|
175
|
+
return gt(getColumn(table, idField), cursor);
|
|
176
|
+
}
|
|
177
|
+
static compositeCursorCondition(table, fields, cursorValues) {
|
|
178
|
+
if (!cursorValues || fields.length === 0) {
|
|
179
|
+
return undefined;
|
|
180
|
+
}
|
|
181
|
+
const orConditions = [];
|
|
182
|
+
for (let index = 0; index < fields.length; index += 1) {
|
|
183
|
+
const branch = BootstrapService.compositeCursorBranch(table, fields, cursorValues, index);
|
|
184
|
+
if (branch) {
|
|
185
|
+
orConditions.push(branch);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
if (orConditions.length === 0) {
|
|
189
|
+
return undefined;
|
|
190
|
+
}
|
|
191
|
+
const [first, ...rest] = orConditions;
|
|
192
|
+
if (!first) {
|
|
193
|
+
return undefined;
|
|
194
|
+
}
|
|
195
|
+
if (rest.length === 0) {
|
|
196
|
+
return first;
|
|
197
|
+
}
|
|
198
|
+
return or(first, ...rest);
|
|
199
|
+
}
|
|
200
|
+
static compositeCursorBranch(table, fields, cursorValues, index) {
|
|
201
|
+
const field = fields[index];
|
|
202
|
+
if (!field) {
|
|
203
|
+
return undefined;
|
|
204
|
+
}
|
|
205
|
+
const fieldValue = cursorValues[field];
|
|
206
|
+
if (typeof fieldValue !== "string") {
|
|
207
|
+
return undefined;
|
|
208
|
+
}
|
|
209
|
+
const currentGreaterThan = gt(getColumn(table, field), fieldValue);
|
|
210
|
+
if (index === 0) {
|
|
211
|
+
return currentGreaterThan;
|
|
212
|
+
}
|
|
213
|
+
const prefix = BootstrapService.compositeCursorPrefix(table, fields, cursorValues, index);
|
|
214
|
+
return prefix
|
|
215
|
+
? and(prefix, currentGreaterThan)
|
|
216
|
+
: currentGreaterThan;
|
|
217
|
+
}
|
|
218
|
+
static compositeCursorPrefix(table, fields, cursorValues, index) {
|
|
219
|
+
const prefixConditions = [];
|
|
220
|
+
for (let prefixIndex = 0; prefixIndex < index; prefixIndex += 1) {
|
|
221
|
+
const prefixField = fields[prefixIndex];
|
|
222
|
+
if (!prefixField) {
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
const prefixValue = cursorValues[prefixField];
|
|
226
|
+
if (typeof prefixValue !== "string") {
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
prefixConditions.push(eq(getColumn(table, prefixField), prefixValue));
|
|
230
|
+
}
|
|
231
|
+
return combineWhere(prefixConditions);
|
|
232
|
+
}
|
|
233
|
+
// -------------------------------------------------------------------------
|
|
234
|
+
// Generic streaming (cursor-based pagination)
|
|
235
|
+
// -------------------------------------------------------------------------
|
|
236
|
+
async *streamModel(modelName, batchSize, filter) {
|
|
237
|
+
const def = this.modelRegistry[modelName];
|
|
238
|
+
if (!def) {
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
await Promise.resolve();
|
|
242
|
+
// oxlint-disable-next-line prefer-ternary -- yield* cannot appear inside a ternary expression
|
|
243
|
+
if (def.cursor.type === "simple") {
|
|
244
|
+
yield* this.streamSimpleCursor(def, batchSize, filter);
|
|
245
|
+
}
|
|
246
|
+
else {
|
|
247
|
+
yield* this.streamCompositeCursor(def, batchSize, filter);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
async *streamSimpleCursor(def, batchSize, filter) {
|
|
251
|
+
if (def.cursor.type !== "simple") {
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
const scopeWhere = def.buildScopeWhere(filter, this.db);
|
|
255
|
+
const idColumn = getColumn(def.table, def.cursor.idField);
|
|
256
|
+
let cursor;
|
|
257
|
+
while (true) {
|
|
258
|
+
const cursorWhere = BootstrapService.simpleCursorCondition(def.table, def.cursor.idField, cursor);
|
|
259
|
+
const where = scopedWhere(scopeWhere, cursorWhere);
|
|
260
|
+
const rows = (await this.db
|
|
261
|
+
.select()
|
|
262
|
+
.from(def.table)
|
|
263
|
+
.where(where)
|
|
264
|
+
.orderBy(asc(idColumn))
|
|
265
|
+
.limit(batchSize));
|
|
266
|
+
if (rows.length === 0) {
|
|
267
|
+
break;
|
|
268
|
+
}
|
|
269
|
+
for (const row of rows) {
|
|
270
|
+
yield mapRow(row, def);
|
|
271
|
+
}
|
|
272
|
+
const last = rows.at(-1);
|
|
273
|
+
const nextCursor = last?.[def.cursor.idField];
|
|
274
|
+
if (rows.length === batchSize && typeof nextCursor === "string") {
|
|
275
|
+
cursor = nextCursor;
|
|
276
|
+
}
|
|
277
|
+
else {
|
|
278
|
+
break;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
async *streamCompositeCursor(def, batchSize, filter) {
|
|
283
|
+
if (def.cursor.type !== "composite") {
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
const scopeWhere = def.buildScopeWhere(filter, this.db);
|
|
287
|
+
const orderByColumns = def.cursor.fields.map((field) => asc(getColumn(def.table, field)));
|
|
288
|
+
const [firstOrder, ...restOrder] = orderByColumns;
|
|
289
|
+
if (!firstOrder) {
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
let cursorValues;
|
|
293
|
+
while (true) {
|
|
294
|
+
const cursorWhere = BootstrapService.compositeCursorCondition(def.table, def.cursor.fields, cursorValues);
|
|
295
|
+
const where = scopedWhere(scopeWhere, cursorWhere);
|
|
296
|
+
const rows = (await this.db
|
|
297
|
+
.select()
|
|
298
|
+
.from(def.table)
|
|
299
|
+
.where(where)
|
|
300
|
+
.orderBy(firstOrder, ...restOrder)
|
|
301
|
+
.limit(batchSize));
|
|
302
|
+
if (rows.length === 0) {
|
|
303
|
+
break;
|
|
304
|
+
}
|
|
305
|
+
for (const row of rows) {
|
|
306
|
+
yield mapRow(row, def);
|
|
307
|
+
}
|
|
308
|
+
const last = rows.at(-1);
|
|
309
|
+
if (!(last && rows.length === batchSize)) {
|
|
310
|
+
break;
|
|
311
|
+
}
|
|
312
|
+
const nextCursor = {};
|
|
313
|
+
let validCursor = true;
|
|
314
|
+
for (const field of def.cursor.fields) {
|
|
315
|
+
const value = last[field];
|
|
316
|
+
if (typeof value !== "string") {
|
|
317
|
+
validCursor = false;
|
|
318
|
+
break;
|
|
319
|
+
}
|
|
320
|
+
nextCursor[field] = value;
|
|
321
|
+
}
|
|
322
|
+
if (!validCursor) {
|
|
323
|
+
break;
|
|
324
|
+
}
|
|
325
|
+
cursorValues = nextCursor;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
// -------------------------------------------------------------------------
|
|
329
|
+
// Generic batch loading
|
|
330
|
+
// -------------------------------------------------------------------------
|
|
331
|
+
async *batchLoadModel(modelName, request, filter, groups, firstSyncId) {
|
|
332
|
+
const def = this.modelRegistry[modelName];
|
|
333
|
+
if (!def) {
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
const indexedWhere = BootstrapService.buildIndexedWhere(modelName, request, this.allowedIndexedKeys);
|
|
337
|
+
const indexedCondition = BootstrapService.indexedWhereCondition(def.table, indexedWhere);
|
|
338
|
+
const scopeWhereVal = def.buildScopeWhere(filter, this.db);
|
|
339
|
+
const where = scopedWhere(scopeWhereVal, indexedCondition);
|
|
340
|
+
if (!indexedWhere) {
|
|
341
|
+
let bufferedRows = [];
|
|
342
|
+
for await (const row of this.streamModel(modelName, 1000, filter)) {
|
|
343
|
+
bufferedRows.push(row);
|
|
344
|
+
if (bufferedRows.length < 250) {
|
|
345
|
+
continue;
|
|
346
|
+
}
|
|
347
|
+
yield* this.filterBatchRowsByFirstSyncId(modelName, bufferedRows, groups, firstSyncId);
|
|
348
|
+
bufferedRows = [];
|
|
349
|
+
}
|
|
350
|
+
if (bufferedRows.length > 0) {
|
|
351
|
+
yield* this.filterBatchRowsByFirstSyncId(modelName, bufferedRows, groups, firstSyncId);
|
|
352
|
+
}
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
const MAX_INDEXED_ROWS = 50_000;
|
|
356
|
+
const rows = (await this.db
|
|
357
|
+
.select()
|
|
358
|
+
.from(def.table)
|
|
359
|
+
.where(where)
|
|
360
|
+
.orderBy()
|
|
361
|
+
.limit(MAX_INDEXED_ROWS));
|
|
362
|
+
const mappedRows = rows.map((row) => mapRow(row, def));
|
|
363
|
+
yield* this.filterBatchRowsByFirstSyncId(modelName, mappedRows, groups, firstSyncId);
|
|
364
|
+
}
|
|
365
|
+
async *filterBatchRowsByFirstSyncId(modelName, rows, groups, firstSyncId) {
|
|
366
|
+
if (!(firstSyncId && rows.length > 0)) {
|
|
367
|
+
for (const row of rows) {
|
|
368
|
+
yield BootstrapService.serializeBatchRow(modelName, row);
|
|
369
|
+
}
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
const modelIds = rows
|
|
373
|
+
.map((row) => row.id)
|
|
374
|
+
.filter((id) => typeof id === "string");
|
|
375
|
+
const touchedIds = await this.dao.getTouchedModelIdsAfter(firstSyncId, groups, modelName, modelIds);
|
|
376
|
+
for (const row of rows) {
|
|
377
|
+
const rowId = row.id;
|
|
378
|
+
if (typeof rowId === "string" && touchedIds.has(rowId)) {
|
|
379
|
+
continue;
|
|
380
|
+
}
|
|
381
|
+
yield BootstrapService.serializeBatchRow(modelName, row);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
static serializeBatchRow(modelName, row) {
|
|
385
|
+
return JSON.stringify({
|
|
386
|
+
__class: modelName,
|
|
387
|
+
...row,
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
// -------------------------------------------------------------------------
|
|
391
|
+
// Count / stream helpers
|
|
392
|
+
// -------------------------------------------------------------------------
|
|
393
|
+
async countModelRows(modelName, filter) {
|
|
394
|
+
const def = this.modelRegistry[modelName];
|
|
395
|
+
if (!def) {
|
|
396
|
+
return 0;
|
|
397
|
+
}
|
|
398
|
+
const scopeWhereVal = def.buildScopeWhere(filter, this.db);
|
|
399
|
+
const [result] = await this.db
|
|
400
|
+
.select({ count: count() })
|
|
401
|
+
.from(def.table)
|
|
402
|
+
.where(scopeWhereVal)
|
|
403
|
+
.orderBy()
|
|
404
|
+
.limit(1);
|
|
405
|
+
return toCountNumber(result?.count);
|
|
406
|
+
}
|
|
407
|
+
streamModelRows(modelName, filter) {
|
|
408
|
+
return this.streamModel(modelName, 1000, filter);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
//# sourceMappingURL=bootstrap-service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bootstrap-service.js","sourceRoot":"","sources":["../../src/bootstrap/bootstrap-service.ts"],"names":[],"mappings":"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"}
|