orez-sync-cf-host 0.4.52
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 +143 -0
- package/package.json +30 -0
- package/src/config.ts +47 -0
- package/src/generated/package.json +16 -0
- package/src/generated/sync_wasm.d.ts +123 -0
- package/src/generated/sync_wasm.js +824 -0
- package/src/generated/sync_wasm_bg.wasm +0 -0
- package/src/generated/sync_wasm_bg.wasm.d.ts +2 -0
- package/src/harness-config.ts +384 -0
- package/src/harness-worker.ts +13 -0
- package/src/host.ts +1264 -0
- package/src/index.ts +22 -0
- package/src/ingest-harness-worker.ts +119 -0
- package/src/platform-probe-worker.ts +304 -0
- package/src/sql-storage-adapter.ts +168 -0
- package/src/types.ts +153 -0
|
Binary file
|
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
import { queryNameToAst } from '../../../harness/src/query-resolver.mjs'
|
|
2
|
+
import {
|
|
3
|
+
MutationApplicationError,
|
|
4
|
+
registerMutators,
|
|
5
|
+
type SyncHostConfig,
|
|
6
|
+
type SyncHostEnv,
|
|
7
|
+
type SyncSql,
|
|
8
|
+
type ZeroSchemaConfig,
|
|
9
|
+
} from './index.js'
|
|
10
|
+
|
|
11
|
+
export const harnessSchema = {
|
|
12
|
+
tables: {
|
|
13
|
+
user: {
|
|
14
|
+
columns: { id: { type: 'string' }, name: { type: 'string' } },
|
|
15
|
+
primaryKey: ['id'],
|
|
16
|
+
},
|
|
17
|
+
project: {
|
|
18
|
+
columns: {
|
|
19
|
+
id: { type: 'string' },
|
|
20
|
+
ownerId: { type: 'string' },
|
|
21
|
+
name: { type: 'string' },
|
|
22
|
+
},
|
|
23
|
+
primaryKey: ['id'],
|
|
24
|
+
},
|
|
25
|
+
member: {
|
|
26
|
+
columns: {
|
|
27
|
+
id: { type: 'string' },
|
|
28
|
+
projectId: { type: 'string' },
|
|
29
|
+
userId: { type: 'string' },
|
|
30
|
+
},
|
|
31
|
+
primaryKey: ['id'],
|
|
32
|
+
},
|
|
33
|
+
task: {
|
|
34
|
+
columns: {
|
|
35
|
+
id: { type: 'string' },
|
|
36
|
+
projectId: { type: 'string' },
|
|
37
|
+
title: { type: 'string' },
|
|
38
|
+
rank: { type: 'number' },
|
|
39
|
+
done: { type: 'boolean' },
|
|
40
|
+
meta: { type: 'json' },
|
|
41
|
+
dueAt: { type: 'number' },
|
|
42
|
+
},
|
|
43
|
+
primaryKey: ['id'],
|
|
44
|
+
},
|
|
45
|
+
message: {
|
|
46
|
+
columns: {
|
|
47
|
+
id: { type: 'string' },
|
|
48
|
+
serverId: { type: 'string' },
|
|
49
|
+
channelId: { type: 'string' },
|
|
50
|
+
creatorId: { type: 'string' },
|
|
51
|
+
content: { type: 'string' },
|
|
52
|
+
type: { type: 'string' },
|
|
53
|
+
createdAt: { type: 'number' },
|
|
54
|
+
order: { type: 'string' },
|
|
55
|
+
meta: { type: 'json' },
|
|
56
|
+
},
|
|
57
|
+
primaryKey: ['id'],
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
} as const satisfies ZeroSchemaConfig
|
|
61
|
+
|
|
62
|
+
const DDL = [
|
|
63
|
+
'CREATE TABLE IF NOT EXISTS "user" (id TEXT PRIMARY KEY, name TEXT NOT NULL)',
|
|
64
|
+
'CREATE TABLE IF NOT EXISTS project (id TEXT PRIMARY KEY, "ownerId" TEXT NOT NULL, name TEXT NOT NULL)',
|
|
65
|
+
'CREATE TABLE IF NOT EXISTS member (id TEXT PRIMARY KEY, "projectId" TEXT NOT NULL, "userId" TEXT NOT NULL)',
|
|
66
|
+
`CREATE TABLE IF NOT EXISTS task (
|
|
67
|
+
id TEXT PRIMARY KEY,
|
|
68
|
+
"projectId" TEXT NOT NULL,
|
|
69
|
+
title TEXT NOT NULL,
|
|
70
|
+
rank REAL NOT NULL,
|
|
71
|
+
done INTEGER NOT NULL,
|
|
72
|
+
meta TEXT,
|
|
73
|
+
"dueAt" INTEGER
|
|
74
|
+
)`,
|
|
75
|
+
`CREATE TABLE IF NOT EXISTS message (
|
|
76
|
+
id TEXT PRIMARY KEY,
|
|
77
|
+
"serverId" TEXT NOT NULL,
|
|
78
|
+
"channelId" TEXT NOT NULL,
|
|
79
|
+
"creatorId" TEXT NOT NULL,
|
|
80
|
+
content TEXT NOT NULL,
|
|
81
|
+
type TEXT NOT NULL,
|
|
82
|
+
"createdAt" INTEGER NOT NULL,
|
|
83
|
+
"order" TEXT NOT NULL,
|
|
84
|
+
meta TEXT
|
|
85
|
+
)`,
|
|
86
|
+
`CREATE TABLE IF NOT EXISTS _harness_effects (
|
|
87
|
+
id TEXT PRIMARY KEY,
|
|
88
|
+
observedCommitted INTEGER NOT NULL
|
|
89
|
+
)`,
|
|
90
|
+
]
|
|
91
|
+
|
|
92
|
+
function mulberry32(seed: number) {
|
|
93
|
+
let value = seed
|
|
94
|
+
return () => {
|
|
95
|
+
value |= 0
|
|
96
|
+
value = (value + 0x6d2b79f5) | 0
|
|
97
|
+
let mixed = Math.imul(value ^ (value >>> 15), 1 | value)
|
|
98
|
+
mixed = (mixed + Math.imul(mixed ^ (mixed >>> 7), 61 | mixed)) ^ mixed
|
|
99
|
+
return ((mixed ^ (mixed >>> 14)) >>> 0) / 4_294_967_296
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function seedRows() {
|
|
104
|
+
const random = mulberry32(1)
|
|
105
|
+
const pick = <Value>(values: Value[]) => values[Math.floor(random() * values.length)]!
|
|
106
|
+
const user = Array.from({ length: 8 }, (_, index) => ({
|
|
107
|
+
id: `u${index}`,
|
|
108
|
+
name:
|
|
109
|
+
pick(['ann', 'bob 🌵', 'çelik', 'dee', 'evan fix', 'frida', 'gus', 'hana']) +
|
|
110
|
+
` ${index}`,
|
|
111
|
+
}))
|
|
112
|
+
const project = Array.from({ length: 12 }, (_, index) => ({
|
|
113
|
+
id: `p${index}`,
|
|
114
|
+
ownerId: `u${index % user.length}`,
|
|
115
|
+
name: pick(['alpha', 'fixup', 'Zenith', 'delta x', 'ütopia', 'omega']) + ` ${index}`,
|
|
116
|
+
}))
|
|
117
|
+
const member: Array<{ id: string; projectId: string; userId: string }> = []
|
|
118
|
+
let memberID = 0
|
|
119
|
+
for (const row of project) {
|
|
120
|
+
const count = 1 + Math.floor(random() * 3)
|
|
121
|
+
for (let index = 0; index < count; index++) {
|
|
122
|
+
member.push({
|
|
123
|
+
id: `m${memberID++}`,
|
|
124
|
+
projectId: row.id,
|
|
125
|
+
userId: `u${Math.floor(random() * user.length)}`,
|
|
126
|
+
})
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
const metas = [
|
|
130
|
+
null,
|
|
131
|
+
{ tags: ['a', 'b'], depth: { n: 1 } },
|
|
132
|
+
{ emoji: '✅', list: [1, 2.5, -3] },
|
|
133
|
+
{ s: 'plain' },
|
|
134
|
+
[1, 'two', null],
|
|
135
|
+
'scalar string',
|
|
136
|
+
42.5,
|
|
137
|
+
true,
|
|
138
|
+
]
|
|
139
|
+
const task = Array.from({ length: 48 }, (_, index) => ({
|
|
140
|
+
id: `t${index}`,
|
|
141
|
+
projectId: `p${Math.floor(random() * 10)}`,
|
|
142
|
+
title:
|
|
143
|
+
pick([
|
|
144
|
+
'fix login',
|
|
145
|
+
'polish ux',
|
|
146
|
+
'refactor sync',
|
|
147
|
+
'fix flaky test',
|
|
148
|
+
'ship it 🚀',
|
|
149
|
+
'triage',
|
|
150
|
+
]) + ` ${index}`,
|
|
151
|
+
rank: Math.round((random() * 20 - 4) * 100) / 100,
|
|
152
|
+
done: random() > 0.6,
|
|
153
|
+
meta: pick(metas),
|
|
154
|
+
dueAt:
|
|
155
|
+
random() > 0.3 ? 1_750_000_000_000 + Math.floor(random() * 10_000_000_000) : null,
|
|
156
|
+
}))
|
|
157
|
+
return { user, project, member, task }
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function initializeHarness(sql: SyncSql): void {
|
|
161
|
+
for (const statement of DDL) sql.exec(statement)
|
|
162
|
+
const [{ count }] = sql.query<{ count: number }>(
|
|
163
|
+
'SELECT COUNT(*) AS count FROM project'
|
|
164
|
+
)
|
|
165
|
+
if (Number(count) > 0) return
|
|
166
|
+
|
|
167
|
+
const seed = seedRows()
|
|
168
|
+
for (const row of seed.user) {
|
|
169
|
+
sql.exec('INSERT INTO "user" (id, name) VALUES (?, ?)', [row.id, row.name])
|
|
170
|
+
}
|
|
171
|
+
for (const row of seed.project) {
|
|
172
|
+
sql.exec('INSERT INTO project (id, "ownerId", name) VALUES (?, ?, ?)', [
|
|
173
|
+
row.id,
|
|
174
|
+
row.ownerId,
|
|
175
|
+
row.name,
|
|
176
|
+
])
|
|
177
|
+
}
|
|
178
|
+
for (const row of seed.member) {
|
|
179
|
+
sql.exec('INSERT INTO member (id, "projectId", "userId") VALUES (?, ?, ?)', [
|
|
180
|
+
row.id,
|
|
181
|
+
row.projectId,
|
|
182
|
+
row.userId,
|
|
183
|
+
])
|
|
184
|
+
}
|
|
185
|
+
for (const row of seed.task) {
|
|
186
|
+
sql.exec(
|
|
187
|
+
'INSERT INTO task (id, "projectId", title, rank, done, meta, "dueAt") VALUES (?, ?, ?, ?, ?, ?, ?)',
|
|
188
|
+
[
|
|
189
|
+
row.id,
|
|
190
|
+
row.projectId,
|
|
191
|
+
row.title,
|
|
192
|
+
row.rank,
|
|
193
|
+
row.done ? 1 : 0,
|
|
194
|
+
row.meta === null ? null : JSON.stringify(row.meta),
|
|
195
|
+
row.dueAt,
|
|
196
|
+
]
|
|
197
|
+
)
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const harnessMutators = registerMutators({
|
|
202
|
+
async 'project.create'(tx, args) {
|
|
203
|
+
const value = args as { id: string; ownerId: string; name: string }
|
|
204
|
+
const exists = await tx.query('SELECT 1 FROM project WHERE id = ?', [value.id])
|
|
205
|
+
if (exists.length > 0) throw new MutationApplicationError('exists')
|
|
206
|
+
await tx.exec('INSERT INTO project (id, "ownerId", name) VALUES (?, ?, ?)', [
|
|
207
|
+
value.id,
|
|
208
|
+
value.ownerId,
|
|
209
|
+
value.name,
|
|
210
|
+
])
|
|
211
|
+
},
|
|
212
|
+
async 'project.rename'(tx, args) {
|
|
213
|
+
const value = args as { id: string; name: string }
|
|
214
|
+
await tx.exec('UPDATE project SET name = ? WHERE id = ?', [value.name, value.id])
|
|
215
|
+
},
|
|
216
|
+
async 'project.delete'(tx, args) {
|
|
217
|
+
const value = args as { id: string }
|
|
218
|
+
await tx.exec('DELETE FROM project WHERE id = ?', [value.id])
|
|
219
|
+
},
|
|
220
|
+
async 'member.add'(tx, args) {
|
|
221
|
+
const value = args as { id: string; projectId: string; userId: string }
|
|
222
|
+
await tx.exec('INSERT INTO member (id, "projectId", "userId") VALUES (?, ?, ?)', [
|
|
223
|
+
value.id,
|
|
224
|
+
value.projectId,
|
|
225
|
+
value.userId,
|
|
226
|
+
])
|
|
227
|
+
},
|
|
228
|
+
async 'member.remove'(tx, args) {
|
|
229
|
+
const value = args as { id: string }
|
|
230
|
+
await tx.exec('DELETE FROM member WHERE id = ?', [value.id])
|
|
231
|
+
},
|
|
232
|
+
async 'task.create'(tx, args) {
|
|
233
|
+
const value = args as {
|
|
234
|
+
id: string
|
|
235
|
+
projectId: string
|
|
236
|
+
title: string
|
|
237
|
+
rank: number
|
|
238
|
+
done: boolean
|
|
239
|
+
meta?: unknown
|
|
240
|
+
dueAt?: number
|
|
241
|
+
}
|
|
242
|
+
await tx.exec(
|
|
243
|
+
'INSERT INTO task (id, "projectId", title, rank, done, meta, "dueAt") VALUES (?, ?, ?, ?, ?, ?, ?)',
|
|
244
|
+
[
|
|
245
|
+
value.id,
|
|
246
|
+
value.projectId,
|
|
247
|
+
value.title,
|
|
248
|
+
value.rank,
|
|
249
|
+
value.done ? 1 : 0,
|
|
250
|
+
value.meta == null ? null : JSON.stringify(value.meta),
|
|
251
|
+
value.dueAt ?? null,
|
|
252
|
+
]
|
|
253
|
+
)
|
|
254
|
+
},
|
|
255
|
+
async 'task.toggle'(tx, args) {
|
|
256
|
+
const value = args as { id: string }
|
|
257
|
+
const rows = await tx.query<{ done: number }>('SELECT done FROM task WHERE id = ?', [
|
|
258
|
+
value.id,
|
|
259
|
+
])
|
|
260
|
+
if (rows.length === 0) throw new MutationApplicationError('not-found')
|
|
261
|
+
await tx.exec('UPDATE task SET done = ? WHERE id = ?', [
|
|
262
|
+
rows[0]!.done ? 0 : 1,
|
|
263
|
+
value.id,
|
|
264
|
+
])
|
|
265
|
+
},
|
|
266
|
+
async 'task.setRank'(tx, args) {
|
|
267
|
+
const value = args as { id: string; rank: number }
|
|
268
|
+
await tx.exec('UPDATE task SET rank = ? WHERE id = ?', [value.rank, value.id])
|
|
269
|
+
},
|
|
270
|
+
async 'message.send'(tx, args) {
|
|
271
|
+
const value = args as {
|
|
272
|
+
id: string
|
|
273
|
+
serverId: string
|
|
274
|
+
channelId: string
|
|
275
|
+
creatorId: string
|
|
276
|
+
content: string
|
|
277
|
+
type: string
|
|
278
|
+
createdAt: number
|
|
279
|
+
order: string
|
|
280
|
+
meta?: unknown
|
|
281
|
+
}
|
|
282
|
+
await tx.exec(
|
|
283
|
+
'INSERT INTO message (id, "serverId", "channelId", "creatorId", content, type, "createdAt", "order", meta) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)',
|
|
284
|
+
[
|
|
285
|
+
value.id,
|
|
286
|
+
value.serverId,
|
|
287
|
+
value.channelId,
|
|
288
|
+
value.creatorId,
|
|
289
|
+
value.content,
|
|
290
|
+
value.type,
|
|
291
|
+
value.createdAt,
|
|
292
|
+
value.order,
|
|
293
|
+
value.meta == null ? null : JSON.stringify(value.meta),
|
|
294
|
+
]
|
|
295
|
+
)
|
|
296
|
+
},
|
|
297
|
+
async 'test.effectSuccess'(tx, args, context) {
|
|
298
|
+
const value = args as { id: string; clientID: string; mutationID: number }
|
|
299
|
+
await tx.exec('INSERT INTO project (id, "ownerId", name) VALUES (?, ?, ?)', [
|
|
300
|
+
value.id,
|
|
301
|
+
context.claims.userID,
|
|
302
|
+
'deferred-effect-success',
|
|
303
|
+
])
|
|
304
|
+
context.defer(async () => {
|
|
305
|
+
const rows = await tx.query<{ committed: number }>(
|
|
306
|
+
`SELECT COUNT(*) AS committed FROM _zsync_clients
|
|
307
|
+
WHERE clientID = ? AND lastMutationID >= ?`,
|
|
308
|
+
[value.clientID, value.mutationID]
|
|
309
|
+
)
|
|
310
|
+
await tx.exec(
|
|
311
|
+
'INSERT INTO _harness_effects (id, observedCommitted) VALUES (?, ?)',
|
|
312
|
+
[value.id, Number(rows[0]?.committed ?? 0) > 0 ? 1 : 0]
|
|
313
|
+
)
|
|
314
|
+
})
|
|
315
|
+
},
|
|
316
|
+
async 'test.effectRollback'(tx, args, context) {
|
|
317
|
+
const value = args as { id: string }
|
|
318
|
+
await tx.exec('INSERT INTO project (id, "ownerId", name) VALUES (?, ?, ?)', [
|
|
319
|
+
value.id,
|
|
320
|
+
context.claims.userID,
|
|
321
|
+
'must-roll-back',
|
|
322
|
+
])
|
|
323
|
+
context.defer(() =>
|
|
324
|
+
tx.exec('INSERT INTO _harness_effects (id, observedCommitted) VALUES (?, 0)', [
|
|
325
|
+
value.id,
|
|
326
|
+
])
|
|
327
|
+
)
|
|
328
|
+
await scheduler.wait(1)
|
|
329
|
+
throw new MutationApplicationError('intentional-rollback')
|
|
330
|
+
},
|
|
331
|
+
})
|
|
332
|
+
|
|
333
|
+
export function harnessConfig<Env extends SyncHostEnv>(): SyncHostConfig<Env> {
|
|
334
|
+
return {
|
|
335
|
+
hostVersion: '0.1.0',
|
|
336
|
+
schema: harnessSchema,
|
|
337
|
+
mutators: harnessMutators,
|
|
338
|
+
queryAware: false,
|
|
339
|
+
queryTransformVersion: 1,
|
|
340
|
+
resolveQuery(name, args) {
|
|
341
|
+
return queryNameToAst(name, args) as never
|
|
342
|
+
},
|
|
343
|
+
initialize: initializeHarness,
|
|
344
|
+
namespace(request) {
|
|
345
|
+
return new URL(request.url).pathname.split('/')[1] || null
|
|
346
|
+
},
|
|
347
|
+
authenticate(request) {
|
|
348
|
+
const userID = request.headers
|
|
349
|
+
.get('authorization')
|
|
350
|
+
?.match(/^Bearer token-(.+)$/)?.[1]
|
|
351
|
+
return userID ? { userID } : null
|
|
352
|
+
},
|
|
353
|
+
visibility: {
|
|
354
|
+
rowLocal: false,
|
|
355
|
+
filter(table, claims) {
|
|
356
|
+
const user = claims.userID
|
|
357
|
+
if (table === 'user') return { sql: '"id" = ?', params: [user] }
|
|
358
|
+
if (table === 'project') {
|
|
359
|
+
return {
|
|
360
|
+
sql: '("ownerId" = ? OR EXISTS (SELECT 1 FROM member WHERE member."projectId" = project.id AND member."userId" = ?))',
|
|
361
|
+
params: [user, user],
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
if (table === 'member') {
|
|
365
|
+
return {
|
|
366
|
+
sql: 'EXISTS (SELECT 1 FROM project p WHERE p.id = member."projectId" AND (p."ownerId" = ? OR EXISTS (SELECT 1 FROM member access WHERE access."projectId" = p.id AND access."userId" = ?)))',
|
|
367
|
+
params: [user, user],
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
if (table === 'task')
|
|
371
|
+
return {
|
|
372
|
+
sql: 'EXISTS (SELECT 1 FROM project WHERE project.id = task."projectId" AND (project."ownerId" = ? OR EXISTS (SELECT 1 FROM member WHERE member."projectId" = project.id AND member."userId" = ?)))',
|
|
373
|
+
params: [user, user],
|
|
374
|
+
}
|
|
375
|
+
if (table === 'message')
|
|
376
|
+
return {
|
|
377
|
+
sql: 'EXISTS (SELECT 1 FROM project WHERE project.id = message."serverId" AND (project."ownerId" = ? OR EXISTS (SELECT 1 FROM member WHERE member."projectId" = project.id AND member."userId" = ?)))',
|
|
378
|
+
params: [user, user],
|
|
379
|
+
}
|
|
380
|
+
return undefined
|
|
381
|
+
},
|
|
382
|
+
},
|
|
383
|
+
}
|
|
384
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { harnessConfig } from './harness-config.js'
|
|
2
|
+
import { createSyncDurableObject, createSyncWorker } from './index.js'
|
|
3
|
+
|
|
4
|
+
import type { SyncHostEnv } from './index.js'
|
|
5
|
+
|
|
6
|
+
interface Env extends SyncHostEnv {
|
|
7
|
+
SYNC_DO: DurableObjectNamespace
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const config = harnessConfig<Env>()
|
|
11
|
+
|
|
12
|
+
export const SyncDurableObject = createSyncDurableObject(config)
|
|
13
|
+
export default createSyncWorker(config)
|