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.
Binary file
@@ -0,0 +1,2 @@
1
+ declare const module: WebAssembly.Module
2
+ export default module
@@ -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)