orez-sync-cf-host 0.5.19 → 0.5.21

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.
Files changed (52) hide show
  1. package/README.md +49 -6
  2. package/dist/bun-wasm-loader.d.ts +1 -0
  3. package/dist/bun-wasm-loader.js +20 -0
  4. package/dist/config.d.ts +3 -0
  5. package/dist/config.js +93 -0
  6. package/{src → dist}/generated/sync_wasm_bg.wasm +0 -0
  7. package/dist/host.d.ts +12 -0
  8. package/dist/host.js +1512 -0
  9. package/dist/index.d.ts +8 -0
  10. package/dist/index.js +4 -0
  11. package/dist/mutation-error.d.ts +9 -0
  12. package/dist/mutation-error.js +23 -0
  13. package/dist/node-wasm-loader.d.ts +1 -0
  14. package/dist/node-wasm-loader.js +22 -0
  15. package/dist/post-commit.d.ts +13 -0
  16. package/dist/post-commit.js +31 -0
  17. package/dist/query-compiler.d.ts +4 -0
  18. package/dist/query-compiler.js +4 -0
  19. package/dist/sql-storage-adapter.d.ts +69 -0
  20. package/dist/sql-storage-adapter.js +194 -0
  21. package/dist/transaction-query.d.ts +72 -0
  22. package/dist/transaction-query.js +379 -0
  23. package/dist/types.d.ts +159 -0
  24. package/dist/types.js +5 -0
  25. package/dist/vite-wasm-loader.d.ts +3 -0
  26. package/dist/vite-wasm-loader.js +24 -0
  27. package/dist/wasm.d.ts +1 -0
  28. package/dist/wasm.js +7 -0
  29. package/dist/write-safeguards.d.ts +53 -0
  30. package/dist/write-safeguards.js +186 -0
  31. package/package.json +59 -19
  32. package/src/bun-wasm-loader.ts +0 -16
  33. package/src/config.ts +0 -102
  34. package/src/harness-config.ts +0 -487
  35. package/src/harness-worker.ts +0 -33
  36. package/src/host.ts +0 -1958
  37. package/src/index.ts +0 -47
  38. package/src/ingest-harness-worker.ts +0 -535
  39. package/src/mutation-error.ts +0 -28
  40. package/src/platform-probe-worker.ts +0 -418
  41. package/src/post-commit.ts +0 -40
  42. package/src/query-compiler.ts +0 -16
  43. package/src/sql-storage-adapter.ts +0 -254
  44. package/src/transaction-query.ts +0 -494
  45. package/src/types.ts +0 -195
  46. package/src/wasm-platform.ts +0 -10
  47. package/src/wasm.ts +0 -34
  48. package/src/write-safeguards.ts +0 -248
  49. /package/{src → dist}/generated/package.json +0 -0
  50. /package/{src → dist}/generated/sync_wasm.d.ts +0 -0
  51. /package/{src → dist}/generated/sync_wasm.js +0 -0
  52. /package/{src → dist}/generated/sync_wasm_bg.wasm.d.ts +0 -0
@@ -1,487 +0,0 @@
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
- throw new MutationApplicationError('intentional-rollback')
329
- },
330
- })
331
-
332
- const WAKE_TOKEN_TTL_MS = 60_000
333
-
334
- type HarnessWakeTokenPayload = {
335
- namespace: string
336
- userID: string
337
- expiresAt: number
338
- }
339
-
340
- export async function mintHarnessWakeToken(
341
- namespace: string,
342
- userID: string,
343
- secret: string
344
- ): Promise<{ token: string; expiresAt: number }> {
345
- const expiresAt = Date.now() + WAKE_TOKEN_TTL_MS
346
- const payload = encodeBase64URL(
347
- new TextEncoder().encode(JSON.stringify({ namespace, userID, expiresAt }))
348
- )
349
- return {
350
- token: `${payload}.${await signWakeToken(payload, secret)}`,
351
- expiresAt,
352
- }
353
- }
354
-
355
- async function verifyHarnessWakeToken(
356
- token: string,
357
- namespace: string,
358
- secret: string
359
- ): Promise<boolean> {
360
- try {
361
- const [payload, signature, extra] = token.split('.')
362
- if (!payload || !signature || extra) return false
363
- const key = await wakeTokenKey(secret, ['verify'])
364
- const valid = await crypto.subtle.verify(
365
- 'HMAC',
366
- key,
367
- decodeBase64URL(signature),
368
- new TextEncoder().encode(payload)
369
- )
370
- if (!valid) return false
371
- const claims = JSON.parse(
372
- new TextDecoder().decode(decodeBase64URL(payload))
373
- ) as HarnessWakeTokenPayload
374
- return (
375
- claims.namespace === namespace &&
376
- typeof claims.userID === 'string' &&
377
- claims.userID.length > 0 &&
378
- Number.isFinite(claims.expiresAt) &&
379
- claims.expiresAt > Date.now()
380
- )
381
- } catch {
382
- return false
383
- }
384
- }
385
-
386
- async function signWakeToken(payload: string, secret: string): Promise<string> {
387
- const key = await wakeTokenKey(secret, ['sign'])
388
- const signature = await crypto.subtle.sign(
389
- 'HMAC',
390
- key,
391
- new TextEncoder().encode(payload)
392
- )
393
- return encodeBase64URL(new Uint8Array(signature))
394
- }
395
-
396
- function wakeTokenKey(secret: string, usages: KeyUsage[]): Promise<CryptoKey> {
397
- return crypto.subtle.importKey(
398
- 'raw',
399
- new TextEncoder().encode(secret),
400
- { name: 'HMAC', hash: 'SHA-256' },
401
- false,
402
- usages
403
- )
404
- }
405
-
406
- function encodeBase64URL(bytes: Uint8Array): string {
407
- let binary = ''
408
- for (const byte of bytes) binary += String.fromCharCode(byte)
409
- return btoa(binary).replaceAll('+', '-').replaceAll('/', '_').replace(/=+$/, '')
410
- }
411
-
412
- function decodeBase64URL(value: string): ArrayBuffer {
413
- const base64 = value.replaceAll('-', '+').replaceAll('_', '/')
414
- const binary = atob(base64.padEnd(Math.ceil(base64.length / 4) * 4, '='))
415
- return Uint8Array.from(binary, (character) => character.charCodeAt(0))
416
- .buffer as ArrayBuffer
417
- }
418
-
419
- export function harnessConfig<Env extends SyncHostEnv>(): SyncHostConfig<Env> {
420
- return {
421
- hostVersion: '0.1.0',
422
- schema: harnessSchema,
423
- mutators: harnessMutators,
424
- queryAware: false,
425
- queryTransformVersion: 1,
426
- async resolveQuery(name, args) {
427
- const delayMs = Number((args[0] as { delayMs?: unknown } | undefined)?.delayMs ?? 0)
428
- if (delayMs > 0) await scheduler.wait(delayMs)
429
- return queryNameToAst(name, args) as never
430
- },
431
- initialize: initializeHarness,
432
- namespace(request) {
433
- return new URL(request.url).pathname.split('/')[1] || null
434
- },
435
- authenticate(request) {
436
- const userID = request.headers
437
- .get('authorization')
438
- ?.match(/^Bearer token-(.+)$/)?.[1]
439
- return userID ? { userID } : null
440
- },
441
- authorizeWake(request, env) {
442
- const url = new URL(request.url)
443
- const namespace = url.pathname.split('/')[1]
444
- if (!namespace || !env.ADMIN_KEY) return false
445
- return verifyHarnessWakeToken(
446
- url.searchParams.get('wakeToken') ?? '',
447
- namespace,
448
- env.ADMIN_KEY
449
- )
450
- },
451
- authorizeNotify(request, env) {
452
- return (
453
- Boolean(env.ADMIN_KEY) && request.headers.get('x-admin-key') === env.ADMIN_KEY
454
- )
455
- },
456
- visibility: {
457
- rowLocal: false,
458
- filter(table, claims) {
459
- const user = claims.userID
460
- if (table === 'user') return { sql: '"id" = ?', params: [user] }
461
- if (table === 'project') {
462
- return {
463
- sql: '("ownerId" = ? OR EXISTS (SELECT 1 FROM member WHERE member."projectId" = project.id AND member."userId" = ?))',
464
- params: [user, user],
465
- }
466
- }
467
- if (table === 'member') {
468
- return {
469
- 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" = ?)))',
470
- params: [user, user],
471
- }
472
- }
473
- if (table === 'task')
474
- return {
475
- 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" = ?)))',
476
- params: [user, user],
477
- }
478
- if (table === 'message')
479
- return {
480
- 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" = ?)))',
481
- params: [user, user],
482
- }
483
- return undefined
484
- },
485
- },
486
- }
487
- }
@@ -1,33 +0,0 @@
1
- import { harnessConfig, mintHarnessWakeToken } 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
- const syncWorker = createSyncWorker(config)
12
-
13
- export const SyncDurableObject = createSyncDurableObject(config)
14
- export default {
15
- async fetch(request, env, ctx): Promise<Response> {
16
- const url = new URL(request.url)
17
- const namespace = config.namespace(request)
18
- const route = url.pathname.split('/').slice(2).join('/')
19
- if (namespace && route === 'auth/wake-token') {
20
- const claims = await config.authenticate(request, env)
21
- if (!claims) {
22
- return Response.json({ error: 'missing authentication' }, { status: 401 })
23
- }
24
- if (!env.ADMIN_KEY) {
25
- return Response.json({ error: 'wake token minting unavailable' }, { status: 503 })
26
- }
27
- return Response.json(
28
- await mintHarnessWakeToken(namespace, claims.userID, env.ADMIN_KEY)
29
- )
30
- }
31
- return syncWorker.fetch!(request, env, ctx)
32
- },
33
- } satisfies ExportedHandler<Env>