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.
- package/README.md +49 -6
- package/dist/bun-wasm-loader.d.ts +1 -0
- package/dist/bun-wasm-loader.js +20 -0
- package/dist/config.d.ts +3 -0
- package/dist/config.js +93 -0
- package/{src → dist}/generated/sync_wasm_bg.wasm +0 -0
- package/dist/host.d.ts +12 -0
- package/dist/host.js +1512 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +4 -0
- package/dist/mutation-error.d.ts +9 -0
- package/dist/mutation-error.js +23 -0
- package/dist/node-wasm-loader.d.ts +1 -0
- package/dist/node-wasm-loader.js +22 -0
- package/dist/post-commit.d.ts +13 -0
- package/dist/post-commit.js +31 -0
- package/dist/query-compiler.d.ts +4 -0
- package/dist/query-compiler.js +4 -0
- package/dist/sql-storage-adapter.d.ts +69 -0
- package/dist/sql-storage-adapter.js +194 -0
- package/dist/transaction-query.d.ts +72 -0
- package/dist/transaction-query.js +379 -0
- package/dist/types.d.ts +159 -0
- package/dist/types.js +5 -0
- package/dist/vite-wasm-loader.d.ts +3 -0
- package/dist/vite-wasm-loader.js +24 -0
- package/dist/wasm.d.ts +1 -0
- package/dist/wasm.js +7 -0
- package/dist/write-safeguards.d.ts +53 -0
- package/dist/write-safeguards.js +186 -0
- package/package.json +59 -19
- package/src/bun-wasm-loader.ts +0 -16
- package/src/config.ts +0 -102
- package/src/harness-config.ts +0 -487
- package/src/harness-worker.ts +0 -33
- package/src/host.ts +0 -1958
- package/src/index.ts +0 -47
- package/src/ingest-harness-worker.ts +0 -535
- package/src/mutation-error.ts +0 -28
- package/src/platform-probe-worker.ts +0 -418
- package/src/post-commit.ts +0 -40
- package/src/query-compiler.ts +0 -16
- package/src/sql-storage-adapter.ts +0 -254
- package/src/transaction-query.ts +0 -494
- package/src/types.ts +0 -195
- package/src/wasm-platform.ts +0 -10
- package/src/wasm.ts +0 -34
- package/src/write-safeguards.ts +0 -248
- /package/{src → dist}/generated/package.json +0 -0
- /package/{src → dist}/generated/sync_wasm.d.ts +0 -0
- /package/{src → dist}/generated/sync_wasm.js +0 -0
- /package/{src → dist}/generated/sync_wasm_bg.wasm.d.ts +0 -0
package/src/harness-config.ts
DELETED
|
@@ -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
|
-
}
|
package/src/harness-worker.ts
DELETED
|
@@ -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>
|