orez-sync-cf-host 0.5.18 → 0.5.19

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.
@@ -0,0 +1,494 @@
1
+ export type TransactionQueryWireValue =
2
+ | { kind: 'null' }
3
+ | { kind: 'integer'; value: string }
4
+ | { kind: 'real'; value: number }
5
+ | { kind: 'text'; value: string }
6
+ | { kind: 'blob'; value: number[] }
7
+
8
+ export type TransactionQueryBinding =
9
+ | { kind: 'literal'; value: TransactionQueryWireValue }
10
+ | { kind: 'parent_field'; field: string }
11
+
12
+ export type TransactionQueryColumnType = 'string' | 'number' | 'boolean' | 'json' | 'null'
13
+
14
+ export type TransactionQueryColumn = {
15
+ name: string
16
+ columnType: TransactionQueryColumnType
17
+ }
18
+
19
+ export type CompiledTransactionQueryRelationship = {
20
+ name: string
21
+ node: CompiledTransactionQueryNode
22
+ }
23
+
24
+ export type CompiledTransactionQueryNode = {
25
+ table: string
26
+ singular: boolean
27
+ sql: string
28
+ bindings: TransactionQueryBinding[]
29
+ columns: TransactionQueryColumn[]
30
+ relationships: CompiledTransactionQueryRelationship[]
31
+ }
32
+
33
+ export type CompiledTransactionQueryPlan = {
34
+ rootTable: string
35
+ planHash: string
36
+ root: CompiledTransactionQueryNode
37
+ }
38
+
39
+ export type TransactionQueryBudget = {
40
+ maxSelects: number
41
+ maxRows: number
42
+ }
43
+
44
+ export type TransactionQueryExecutionOptions = {
45
+ queryName?: string
46
+ budget?: Partial<TransactionQueryBudget>
47
+ }
48
+
49
+ export type TransactionQueryFormat = {
50
+ singular: boolean
51
+ relationships: Readonly<Record<string, TransactionQueryFormat>>
52
+ }
53
+
54
+ export const DEFAULT_TRANSACTION_QUERY_BUDGET: TransactionQueryBudget = {
55
+ maxSelects: 256,
56
+ maxRows: 10_000,
57
+ }
58
+
59
+ export class TransactionQueryBudgetError extends Error {
60
+ readonly code = 'transaction_query_budget_exceeded'
61
+
62
+ constructor(
63
+ readonly query: string,
64
+ readonly selects: number,
65
+ readonly rows: number,
66
+ readonly maxSelects: number,
67
+ readonly maxRows: number
68
+ ) {
69
+ super(
70
+ `transaction_query_budget_exceeded: query=${query} selects=${selects} rows=${rows} maxSelects=${maxSelects} maxRows=${maxRows}`
71
+ )
72
+ this.name = 'TransactionQueryBudgetError'
73
+ }
74
+ }
75
+
76
+ type MaterializedRow = Record<string, unknown>
77
+ type ExecuteSelect = (
78
+ sql: string,
79
+ params: readonly unknown[]
80
+ ) => readonly MaterializedRow[]
81
+ type ExecuteSelectAsync = (
82
+ sql: string,
83
+ params: readonly unknown[]
84
+ ) => readonly MaterializedRow[] | Promise<readonly MaterializedRow[]>
85
+
86
+ const MAX_SAFE = BigInt(Number.MAX_SAFE_INTEGER)
87
+ const MIN_SAFE = BigInt(Number.MIN_SAFE_INTEGER)
88
+ const PLAN_HASH = /^[0-9a-f]{16}$/
89
+ const COLUMN_TYPES = new Set<TransactionQueryColumnType>([
90
+ 'string',
91
+ 'number',
92
+ 'boolean',
93
+ 'json',
94
+ 'null',
95
+ ])
96
+
97
+ function isObject(value: unknown): value is Record<string, unknown> {
98
+ return typeof value === 'object' && value !== null && !Array.isArray(value)
99
+ }
100
+
101
+ function malformed(detail: string): TypeError {
102
+ return new TypeError(`compiled transaction query plan ${detail}`)
103
+ }
104
+
105
+ function assertWireValue(value: unknown, path: string): TransactionQueryWireValue {
106
+ if (!isObject(value) || typeof value.kind !== 'string') {
107
+ throw malformed(`${path} has an invalid literal`)
108
+ }
109
+ switch (value.kind) {
110
+ case 'null':
111
+ return value as TransactionQueryWireValue
112
+ case 'integer':
113
+ if (typeof value.value !== 'string' || !/^-?[0-9]+$/.test(value.value)) {
114
+ throw malformed(`${path} has an invalid integer literal`)
115
+ }
116
+ return value as TransactionQueryWireValue
117
+ case 'real':
118
+ if (typeof value.value !== 'number' || !Number.isFinite(value.value)) {
119
+ throw malformed(`${path} has an invalid real literal`)
120
+ }
121
+ return value as TransactionQueryWireValue
122
+ case 'text':
123
+ if (typeof value.value !== 'string') {
124
+ throw malformed(`${path} has an invalid text literal`)
125
+ }
126
+ return value as TransactionQueryWireValue
127
+ case 'blob':
128
+ if (
129
+ !Array.isArray(value.value) ||
130
+ value.value.some(
131
+ (byte) => !Number.isInteger(byte) || Number(byte) < 0 || Number(byte) > 255
132
+ )
133
+ ) {
134
+ throw malformed(`${path} has an invalid blob literal`)
135
+ }
136
+ return value as TransactionQueryWireValue
137
+ default:
138
+ throw malformed(`${path} has an unknown literal kind`)
139
+ }
140
+ }
141
+
142
+ function assertNode(
143
+ value: unknown,
144
+ path: string,
145
+ depth: number
146
+ ): asserts value is CompiledTransactionQueryNode {
147
+ if (depth > 64) throw malformed(`${path} exceeds the maximum relationship depth`)
148
+ if (!isObject(value)) throw malformed(`${path} must be an object`)
149
+ if (typeof value.table !== 'string' || value.table.length === 0) {
150
+ throw malformed(`${path}.table must be a non-empty string`)
151
+ }
152
+ if (typeof value.singular !== 'boolean') {
153
+ throw malformed(`${path}.singular must be a boolean`)
154
+ }
155
+ if (typeof value.sql !== 'string' || value.sql.length === 0) {
156
+ throw malformed(`${path}.sql must be a non-empty string`)
157
+ }
158
+ if (!Array.isArray(value.bindings)) {
159
+ throw malformed(`${path}.bindings must be an array`)
160
+ }
161
+ for (const [index, binding] of value.bindings.entries()) {
162
+ if (!isObject(binding)) throw malformed(`${path}.bindings[${index}] is invalid`)
163
+ if (binding.kind === 'literal') {
164
+ assertWireValue(binding.value, `${path}.bindings[${index}]`)
165
+ } else if (
166
+ binding.kind !== 'parent_field' ||
167
+ typeof binding.field !== 'string' ||
168
+ binding.field.length === 0
169
+ ) {
170
+ throw malformed(`${path}.bindings[${index}] is invalid`)
171
+ }
172
+ }
173
+ if (!Array.isArray(value.columns) || value.columns.length === 0) {
174
+ throw malformed(`${path}.columns must be a non-empty array`)
175
+ }
176
+ const names = new Set<string>()
177
+ for (const [index, column] of value.columns.entries()) {
178
+ if (
179
+ !isObject(column) ||
180
+ typeof column.name !== 'string' ||
181
+ column.name.length === 0 ||
182
+ typeof column.columnType !== 'string' ||
183
+ !COLUMN_TYPES.has(column.columnType as TransactionQueryColumnType)
184
+ ) {
185
+ throw malformed(`${path}.columns[${index}] is invalid`)
186
+ }
187
+ if (names.has(column.name)) {
188
+ throw malformed(`${path}.columns contains duplicate '${column.name}'`)
189
+ }
190
+ names.add(column.name)
191
+ }
192
+ if (!Array.isArray(value.relationships)) {
193
+ throw malformed(`${path}.relationships must be an array`)
194
+ }
195
+ const relationships = new Set<string>()
196
+ for (const [index, relationship] of value.relationships.entries()) {
197
+ if (
198
+ !isObject(relationship) ||
199
+ typeof relationship.name !== 'string' ||
200
+ relationship.name.length === 0
201
+ ) {
202
+ throw malformed(`${path}.relationships[${index}] is invalid`)
203
+ }
204
+ if (relationships.has(relationship.name) || names.has(relationship.name)) {
205
+ throw malformed(`${path}.relationships contains conflicting '${relationship.name}'`)
206
+ }
207
+ relationships.add(relationship.name)
208
+ assertNode(relationship.node, `${path}.relationships[${index}].node`, depth + 1)
209
+ }
210
+ }
211
+
212
+ function assertPlan(value: unknown): asserts value is CompiledTransactionQueryPlan {
213
+ if (!isObject(value)) throw malformed('must be an object')
214
+ if (typeof value.rootTable !== 'string' || value.rootTable.length === 0) {
215
+ throw malformed('rootTable must be a non-empty string')
216
+ }
217
+ if (typeof value.planHash !== 'string' || !PLAN_HASH.test(value.planHash)) {
218
+ throw malformed('planHash must be 16 lowercase hexadecimal characters')
219
+ }
220
+ assertNode(value.root, 'root', 0)
221
+ if (value.root.table !== value.rootTable) {
222
+ throw malformed('root.table must match rootTable')
223
+ }
224
+ }
225
+
226
+ function resolveBudget(
227
+ budget: Partial<TransactionQueryBudget> | undefined
228
+ ): TransactionQueryBudget {
229
+ const resolved = { ...DEFAULT_TRANSACTION_QUERY_BUDGET, ...budget }
230
+ for (const [name, value] of Object.entries(resolved)) {
231
+ if (!Number.isSafeInteger(value) || value < 1) {
232
+ throw new TypeError(
233
+ `transaction query budget ${name} must be a positive safe integer`
234
+ )
235
+ }
236
+ }
237
+ return resolved
238
+ }
239
+
240
+ function decodeWireValue(value: TransactionQueryWireValue): unknown {
241
+ switch (value.kind) {
242
+ case 'null':
243
+ return null
244
+ case 'integer': {
245
+ const exact = BigInt(value.value)
246
+ return exact >= MIN_SAFE && exact <= MAX_SAFE ? Number(exact) : value.value
247
+ }
248
+ case 'real':
249
+ case 'text':
250
+ return value.value
251
+ case 'blob':
252
+ return Uint8Array.from(value.value)
253
+ }
254
+ }
255
+
256
+ function bytes(value: unknown): Uint8Array | undefined {
257
+ if (value instanceof ArrayBuffer) return new Uint8Array(value)
258
+ if (ArrayBuffer.isView(value)) {
259
+ return new Uint8Array(value.buffer, value.byteOffset, value.byteLength)
260
+ }
261
+ return undefined
262
+ }
263
+
264
+ function decodeColumn(
265
+ value: unknown,
266
+ columnType: TransactionQueryColumnType,
267
+ path: string
268
+ ): unknown {
269
+ if (value === null) return null
270
+ switch (columnType) {
271
+ case 'string':
272
+ if (typeof value === 'string') return value
273
+ break
274
+ case 'number':
275
+ if (typeof value === 'number' && Number.isFinite(value)) return value
276
+ if (typeof value === 'bigint' && value >= MIN_SAFE && value <= MAX_SAFE) {
277
+ return Number(value)
278
+ }
279
+ break
280
+ case 'boolean':
281
+ if (typeof value === 'boolean') return value
282
+ if (value === 0 || value === 0n || value === '0') return false
283
+ if (value === 1 || value === 1n || value === '1') return true
284
+ break
285
+ case 'json': {
286
+ if (typeof value === 'string') {
287
+ try {
288
+ return JSON.parse(value)
289
+ } catch {
290
+ throw new TypeError(`transaction query row ${path} contains invalid JSON`)
291
+ }
292
+ }
293
+ const encoded = bytes(value)
294
+ if (encoded) {
295
+ try {
296
+ return JSON.parse(new TextDecoder().decode(encoded))
297
+ } catch {
298
+ throw new TypeError(`transaction query row ${path} contains invalid JSON`)
299
+ }
300
+ }
301
+ if (typeof value === 'object') return value
302
+ break
303
+ }
304
+ case 'null':
305
+ break
306
+ }
307
+ throw new TypeError(
308
+ `transaction query row ${path} does not match schema type ${columnType}`
309
+ )
310
+ }
311
+
312
+ function decodeRow(
313
+ raw: MaterializedRow,
314
+ node: CompiledTransactionQueryNode
315
+ ): MaterializedRow {
316
+ const decoded: MaterializedRow = {}
317
+ for (const column of node.columns) {
318
+ if (!Object.hasOwn(raw, column.name)) {
319
+ throw new TypeError(
320
+ `transaction query row for ${node.table} is missing column ${column.name}`
321
+ )
322
+ }
323
+ decoded[column.name] = decodeColumn(
324
+ raw[column.name],
325
+ column.columnType,
326
+ `${node.table}.${column.name}`
327
+ )
328
+ }
329
+ return decoded
330
+ }
331
+
332
+ type TransactionQueryExecutionState = {
333
+ budget: TransactionQueryBudget
334
+ query: string
335
+ rows: number
336
+ selects: number
337
+ }
338
+
339
+ type PreparedSelect = {
340
+ params: unknown[]
341
+ state: TransactionQueryExecutionState
342
+ }
343
+
344
+ type DecodedSelect = {
345
+ rows: Array<{ decoded: MaterializedRow; raw: MaterializedRow }>
346
+ state: TransactionQueryExecutionState
347
+ }
348
+
349
+ type NodeExecution = {
350
+ state: TransactionQueryExecutionState
351
+ value: MaterializedRow[] | MaterializedRow | null | undefined
352
+ }
353
+
354
+ function createExecutionState(
355
+ plan: CompiledTransactionQueryPlan,
356
+ options: TransactionQueryExecutionOptions
357
+ ): TransactionQueryExecutionState {
358
+ assertPlan(plan)
359
+ return {
360
+ budget: resolveBudget(options.budget),
361
+ query: options.queryName?.trim() || `${plan.rootTable}:${plan.planHash}`,
362
+ rows: 0,
363
+ selects: 0,
364
+ }
365
+ }
366
+
367
+ function assertWithinBudget(state: TransactionQueryExecutionState): void {
368
+ if (state.selects <= state.budget.maxSelects && state.rows <= state.budget.maxRows) {
369
+ return
370
+ }
371
+ throw new TransactionQueryBudgetError(
372
+ state.query,
373
+ state.selects,
374
+ state.rows,
375
+ state.budget.maxSelects,
376
+ state.budget.maxRows
377
+ )
378
+ }
379
+
380
+ function prepareSelect(
381
+ state: TransactionQueryExecutionState,
382
+ node: CompiledTransactionQueryNode,
383
+ parent?: MaterializedRow
384
+ ): PreparedSelect {
385
+ const params = node.bindings.map((binding) => {
386
+ if (binding.kind === 'literal') return decodeWireValue(binding.value)
387
+ if (!parent || !Object.hasOwn(parent, binding.field)) {
388
+ throw new TypeError(
389
+ `transaction query parent row for ${node.table} is missing field ${binding.field}`
390
+ )
391
+ }
392
+ return parent[binding.field]
393
+ })
394
+ const next = { ...state, selects: state.selects + 1 }
395
+ assertWithinBudget(next)
396
+ return { params, state: next }
397
+ }
398
+
399
+ function decodeSelect(
400
+ state: TransactionQueryExecutionState,
401
+ node: CompiledTransactionQueryNode,
402
+ materialized: readonly MaterializedRow[]
403
+ ): DecodedSelect {
404
+ if (!Array.isArray(materialized)) {
405
+ throw new TypeError('transaction query select must return a materialized row array')
406
+ }
407
+ const next = { ...state, rows: state.rows + materialized.length }
408
+ assertWithinBudget(next)
409
+ const rows = materialized.map((raw) => {
410
+ if (!isObject(raw)) {
411
+ throw new TypeError(
412
+ `transaction query select for ${node.table} returned a non-object row`
413
+ )
414
+ }
415
+ return { decoded: decodeRow(raw, node), raw }
416
+ })
417
+ return { rows, state: next }
418
+ }
419
+
420
+ function shapeRows(
421
+ node: CompiledTransactionQueryNode,
422
+ rows: MaterializedRow[]
423
+ ): NodeExecution['value'] {
424
+ return node.singular ? rows[0] : rows
425
+ }
426
+
427
+ export function executeTransactionQueryPlan<Result = unknown>(
428
+ plan: CompiledTransactionQueryPlan,
429
+ execute: ExecuteSelect,
430
+ options: TransactionQueryExecutionOptions = {}
431
+ ): Result {
432
+ const run = (
433
+ node: CompiledTransactionQueryNode,
434
+ state: TransactionQueryExecutionState,
435
+ parent?: MaterializedRow
436
+ ): NodeExecution => {
437
+ const prepared = prepareSelect(state, node, parent)
438
+ const selected = decodeSelect(
439
+ prepared.state,
440
+ node,
441
+ execute(node.sql, prepared.params)
442
+ )
443
+ const hydrated: MaterializedRow[] = []
444
+ let current = selected.state
445
+ for (const { decoded, raw } of selected.rows) {
446
+ for (const relationship of node.relationships) {
447
+ const related = run(relationship.node, current, raw)
448
+ current = related.state
449
+ decoded[relationship.name] =
450
+ relationship.node.singular && related.value === undefined ? null : related.value
451
+ }
452
+ hydrated.push(decoded)
453
+ }
454
+ return { state: current, value: shapeRows(node, hydrated) }
455
+ }
456
+
457
+ const state = createExecutionState(plan, options)
458
+ return run(plan.root, state).value as Result
459
+ }
460
+
461
+ export async function executeTransactionQueryPlanAsync<Result = unknown>(
462
+ plan: CompiledTransactionQueryPlan,
463
+ execute: ExecuteSelectAsync,
464
+ options: TransactionQueryExecutionOptions = {}
465
+ ): Promise<Result> {
466
+ const run = async (
467
+ node: CompiledTransactionQueryNode,
468
+ state: TransactionQueryExecutionState,
469
+ parent?: MaterializedRow
470
+ ): Promise<NodeExecution> => {
471
+ const prepared = prepareSelect(state, node, parent)
472
+ const selected = decodeSelect(
473
+ prepared.state,
474
+ node,
475
+ await execute(node.sql, prepared.params)
476
+ )
477
+ const hydrated: MaterializedRow[] = []
478
+ let current = selected.state
479
+ for (const { decoded, raw } of selected.rows) {
480
+ for (const relationship of node.relationships) {
481
+ const related = await run(relationship.node, current, raw)
482
+ current = related.state
483
+ decoded[relationship.name] =
484
+ relationship.node.singular && related.value === undefined ? null : related.value
485
+ }
486
+ hydrated.push(decoded)
487
+ }
488
+ return { state: current, value: shapeRows(node, hydrated) }
489
+ }
490
+
491
+ const state = createExecutionState(plan, options)
492
+ const { value } = await run(plan.root, state)
493
+ return value as Result
494
+ }
package/src/types.ts CHANGED
@@ -1,3 +1,11 @@
1
+ import type { DeferredEffect } from './post-commit.js'
2
+ import type {
3
+ TransactionQueryBudget,
4
+ TransactionQueryFormat,
5
+ } from './transaction-query.js'
6
+
7
+ export type { DeferredEffect } from './post-commit.js'
8
+
1
9
  export type JsonPrimitive = string | number | boolean | null
2
10
  export type JsonValue = JsonPrimitive | JsonValue[] | { [key: string]: JsonValue }
3
11
 
@@ -12,7 +20,11 @@ export type ZeroSchemaConfig = {
12
20
  Record<
13
21
  string,
14
22
  {
15
- readonly columns: Readonly<Record<string, { readonly type: string }>>
23
+ readonly name?: string
24
+ readonly serverName?: string
25
+ readonly columns: Readonly<
26
+ Record<string, { readonly type: string; readonly serverName?: string }>
27
+ >
16
28
  readonly primaryKey: readonly string[]
17
29
  }
18
30
  >
@@ -34,13 +46,13 @@ export interface MutatorSql {
34
46
  params?: readonly unknown[]
35
47
  ): Promise<Row[]>
36
48
  /** Execute a validated Zero AST inside the current application transaction. */
37
- queryAst<Row extends Record<string, unknown> = Record<string, unknown>>(
38
- ast: JsonValue
39
- ): Promise<Row[]>
49
+ queryAst<Result = unknown>(
50
+ ast: JsonValue,
51
+ format: TransactionQueryFormat,
52
+ queryName?: string
53
+ ): Promise<Result>
40
54
  }
41
55
 
42
- export type DeferredEffect = () => void | Promise<void>
43
-
44
56
  export type MutatorContext = {
45
57
  claims: NormalizedClaims
46
58
  /** Current Zero mutation identity, when invoked by the production host. */
@@ -64,15 +76,7 @@ export function registerMutators<
64
76
  return Object.freeze({ ...registry })
65
77
  }
66
78
 
67
- export class MutationApplicationError extends Error {
68
- constructor(
69
- readonly details: string,
70
- message = details
71
- ) {
72
- super(message)
73
- this.name = 'MutationApplicationError'
74
- }
75
- }
79
+ export { MutationApplicationError, isMutationApplicationError } from './mutation-error.js'
76
80
 
77
81
  export type VisibilityFilter = {
78
82
  /** SQL WHERE fragment only, without the WHERE keyword. */
@@ -141,11 +145,19 @@ export type SyncHostConfig<Env extends SyncHostEnv = SyncHostEnv> = {
141
145
  hostVersion: string
142
146
  schema: ZeroSchemaConfig
143
147
  mutators?: MutatorRegistry
144
- /** Absolute app push path on the upstream service binding. */
148
+ /**
149
+ * absolute app push path on the delegated mutation service. a successful
150
+ * response must be causally visible through the configured upstream data
151
+ * feed before the app returns, because the host ingests effects before it
152
+ * records the mutation's lmid.
153
+ */
145
154
  mutateUrl?: string
146
155
  /** Absolute origin used for delegated push requests through the service binding. */
147
156
  mutateOrigin?: string
148
- /** Env binding for delegated pushes; defaults to upstream.binding. */
157
+ /**
158
+ * env binding for delegated pushes; defaults to upstream.binding. the bound
159
+ * service and upstream feed must satisfy the mutateUrl causality contract.
160
+ */
149
161
  mutateBinding?: string
150
162
  delegatedPushRetry?: DelegatedPushRetryConfig
151
163
  /** Required for delegated push; forbidden with local mutators (no dual apply). */
@@ -177,5 +189,7 @@ export type SyncHostConfig<Env extends SyncHostEnv = SyncHostEnv> = {
177
189
  caps?: Partial<PullCaps>
178
190
  idleTeardownMs?: number
179
191
  wakeCoalesceMs?: number
192
+ /** per-query guard for recursive transaction query materialization. */
193
+ transactionQueryBudget?: Partial<TransactionQueryBudget>
180
194
  authorizeAdmin?: (request: Request, env: Env) => boolean | Promise<boolean>
181
195
  }
@@ -0,0 +1,10 @@
1
+ import './wasm.js'
2
+
3
+ export {
4
+ init_probe_schema,
5
+ pull_snapshot,
6
+ push_finalize,
7
+ push_preflight,
8
+ rust_panic_after_writes,
9
+ value_round_trip,
10
+ } from './generated/sync_wasm.js'
package/src/wasm.ts ADDED
@@ -0,0 +1,34 @@
1
+ import { initSync } from './generated/sync_wasm.js'
2
+ import wasmModule from './generated/sync_wasm_bg.wasm'
3
+
4
+ if (!(wasmModule instanceof WebAssembly.Module)) {
5
+ throw new TypeError(
6
+ '.wasm import did not resolve to WebAssembly.Module; Bun consumers must register orez-sync-cf-host/bun-wasm-loader via bunfig preload'
7
+ )
8
+ }
9
+
10
+ initSync({ module: wasmModule })
11
+
12
+ export {
13
+ engine_apply_snapshot_changes,
14
+ engine_apply_snapshot_page,
15
+ engine_apply_upstream,
16
+ engine_assemble_push_response,
17
+ engine_begin_snapshot_generation,
18
+ engine_compile_query,
19
+ engine_finalize,
20
+ engine_finalize_snapshot_generation,
21
+ engine_handle_pull,
22
+ engine_handle_query_pull,
23
+ engine_init_query_schema,
24
+ engine_init_schema,
25
+ engine_invalidate,
26
+ engine_memory_bytes,
27
+ engine_preflight,
28
+ engine_prune,
29
+ engine_push_validate,
30
+ engine_read_snapshot_progress,
31
+ engine_record_app_error,
32
+ engine_state,
33
+ engine_version,
34
+ } from './generated/sync_wasm.js'