tranfi 0.1.2 → 0.2.0

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 (132) hide show
  1. package/LICENSE +177 -0
  2. package/NOTICE +8 -0
  3. package/README.md +272 -40
  4. package/app/assets/{index-pDFMluyz.js → index-BIAIKnrp.js} +1 -1
  5. package/app/index.html +1 -1
  6. package/binding.gyp +55 -3
  7. package/csrc/arena.c +7 -5
  8. package/csrc/batch.c +818 -71
  9. package/csrc/buffer.c +84 -8
  10. package/csrc/cJSON.c +262 -19
  11. package/csrc/cJSON.h +17 -1
  12. package/csrc/codec_csv.c +1074 -181
  13. package/csrc/codec_jsonl.c +830 -118
  14. package/csrc/codec_table.c +108 -78
  15. package/csrc/codec_text.c +286 -68
  16. package/csrc/compiler.c +31 -3
  17. package/csrc/config.h +21 -0
  18. package/csrc/dsl.c +4722 -485
  19. package/csrc/expr.c +363 -55
  20. package/csrc/expr.h +2 -0
  21. package/csrc/internal.h +316 -27
  22. package/csrc/ir.c +65 -18
  23. package/csrc/ir.h +41 -0
  24. package/csrc/ir_schema.c +20 -5
  25. package/csrc/ir_serialize.c +68 -6
  26. package/csrc/ir_sql.c +796 -185
  27. package/csrc/ir_validate.c +462 -6
  28. package/csrc/json_path.c +210 -0
  29. package/csrc/main.c +879 -30
  30. package/csrc/memory_estimate.c +477 -0
  31. package/csrc/op_acf.c +171 -21
  32. package/csrc/op_across.c +477 -0
  33. package/csrc/op_anomaly.c +167 -32
  34. package/csrc/op_assert.c +761 -0
  35. package/csrc/op_bin.c +168 -29
  36. package/csrc/op_cast.c +383 -55
  37. package/csrc/op_clip.c +30 -19
  38. package/csrc/op_date_trunc.c +208 -34
  39. package/csrc/op_datetime.c +259 -77
  40. package/csrc/op_derive.c +65 -97
  41. package/csrc/op_diff.c +146 -30
  42. package/csrc/op_ewma.c +149 -30
  43. package/csrc/op_explode.c +124 -26
  44. package/csrc/op_fill_down.c +125 -53
  45. package/csrc/op_fill_null.c +176 -31
  46. package/csrc/op_filter.c +89 -40
  47. package/csrc/op_frequency.c +571 -43
  48. package/csrc/op_grep.c +36 -18
  49. package/csrc/op_group_agg.c +1790 -119
  50. package/csrc/op_hash.c +48 -15
  51. package/csrc/op_head.c +21 -86
  52. package/csrc/op_interpolate.c +268 -62
  53. package/csrc/op_join.c +2700 -182
  54. package/csrc/op_json_extract.c +227 -0
  55. package/csrc/op_json_filter.c +384 -0
  56. package/csrc/op_json_flatten.c +293 -0
  57. package/csrc/op_json_schema.c +503 -0
  58. package/csrc/op_label_encode.c +328 -53
  59. package/csrc/op_lag.c +181 -0
  60. package/csrc/op_lead.c +141 -89
  61. package/csrc/op_normalize.c +363 -79
  62. package/csrc/op_onehot.c +345 -73
  63. package/csrc/op_pivot.c +1546 -162
  64. package/csrc/op_quarantine.c +189 -0
  65. package/csrc/op_registry.c +2062 -166
  66. package/csrc/op_rename.c +41 -50
  67. package/csrc/op_replace.c +270 -118
  68. package/csrc/op_rleid.c +297 -0
  69. package/csrc/op_rowid.c +559 -0
  70. package/csrc/op_sample.c +80 -23
  71. package/csrc/op_schema.c +1341 -0
  72. package/csrc/op_schema_infer.c +252 -0
  73. package/csrc/op_select.c +265 -65
  74. package/csrc/op_set.c +3449 -0
  75. package/csrc/op_skip.c +30 -87
  76. package/csrc/op_sort.c +670 -124
  77. package/csrc/op_source_name.c +120 -0
  78. package/csrc/op_split.c +65 -28
  79. package/csrc/op_split_data.c +41 -9
  80. package/csrc/op_stack.c +178 -222
  81. package/csrc/op_stats.c +206 -110
  82. package/csrc/op_step.c +217 -55
  83. package/csrc/op_tail.c +21 -12
  84. package/csrc/op_tee.c +338 -0
  85. package/csrc/op_top.c +260 -53
  86. package/csrc/op_trim.c +48 -19
  87. package/csrc/op_unique.c +1193 -150
  88. package/csrc/op_unpivot.c +100 -66
  89. package/csrc/op_validate.c +601 -24
  90. package/csrc/op_window.c +492 -51
  91. package/csrc/path_policy.c +85 -0
  92. package/csrc/pipeline.c +872 -99
  93. package/csrc/recipes.c +3 -1
  94. package/csrc/report.c +73 -30
  95. package/csrc/selector.c +1097 -0
  96. package/csrc/size_utils.c +348 -0
  97. package/csrc/spill.c +317 -0
  98. package/csrc/spill.h +21 -0
  99. package/csrc/tranfi.h +169 -1
  100. package/csrc/transform.h +209 -0
  101. package/csrc/transform_api.c +2237 -0
  102. package/csrc/transform_categorical.c +923 -0
  103. package/csrc/transform_internal.h +472 -0
  104. package/csrc/transform_json.c +3812 -0
  105. package/csrc/transform_numeric.c +1966 -0
  106. package/csrc/transform_sha256.c +154 -0
  107. package/csrc/transform_wasm.h +162 -0
  108. package/csrc/transform_wasm_api.c +1373 -0
  109. package/csrc/wasm_api.c +70 -9
  110. package/napi_api.c +219 -11
  111. package/napi_transform.c +1648 -0
  112. package/napi_transform.h +8 -0
  113. package/package.json +27 -11
  114. package/scripts/install-native.js +76 -0
  115. package/scripts/prepack.js +64 -0
  116. package/scripts/sync-csrc.js +23 -0
  117. package/src/cli.js +8 -11
  118. package/src/engines/duckdb.js +45 -12
  119. package/src/index.js +661 -42
  120. package/src/memory_policy.js +411 -0
  121. package/src/native.js +1 -5
  122. package/src/pipeline.js +454 -31
  123. package/src/recipe_json.js +80 -0
  124. package/src/server.js +10 -8
  125. package/src/transform.js +403 -0
  126. package/src/transform_error.js +10 -0
  127. package/src/wasm.js +6 -4
  128. package/wasm/index.js +498 -10
  129. package/wasm/tranfi_core.js +0 -0
  130. package/wasm/transform.js +1156 -0
  131. package/wasm/worker.js +786 -0
  132. package/csrc/plan.c +0 -206
package/wasm/worker.js ADDED
@@ -0,0 +1,786 @@
1
+ "use strict"
2
+
3
+ const createTranfi = require('./index.js')
4
+ const { TranfiTransformError } = require('../src/transform_error.js')
5
+
6
+ const CHAN_MAIN = 0
7
+ const CHAN_ERRORS = 1
8
+ const CHAN_STATS = 2
9
+ const CHAN_SAMPLES = 3
10
+ const DEFAULT_CHUNK_SIZE = 64 * 1024
11
+
12
+ function textEncoder() {
13
+ return new TextEncoder()
14
+ }
15
+
16
+ function textDecoder() {
17
+ return new TextDecoder()
18
+ }
19
+
20
+ function isArrayBuffer(value) {
21
+ return value instanceof ArrayBuffer || Object.prototype.toString.call(value) === '[object ArrayBuffer]'
22
+ }
23
+
24
+ function asBytes(value) {
25
+ if (value === undefined || value === null) return new Uint8Array(0)
26
+ if (value instanceof Uint8Array) return value
27
+ if (isArrayBuffer(value)) return new Uint8Array(value)
28
+ if (ArrayBuffer.isView(value)) return new Uint8Array(value.buffer, value.byteOffset, value.byteLength)
29
+ if (typeof value === 'string') return textEncoder().encode(value)
30
+ return textEncoder().encode(String(value))
31
+ }
32
+
33
+ function ownedBytes(value) {
34
+ var view = asBytes(value)
35
+ var out = new Uint8Array(view.byteLength)
36
+ out.set(view)
37
+ return out
38
+ }
39
+
40
+ function concatUint8(chunks) {
41
+ var total = chunks.reduce(function(sum, chunk) { return sum + chunk.length }, 0)
42
+ var out = new Uint8Array(total)
43
+ var offset = 0
44
+ for (var i = 0; i < chunks.length; i++) {
45
+ out.set(chunks[i], offset)
46
+ offset += chunks[i].length
47
+ }
48
+ return out
49
+ }
50
+
51
+ function makeResult(output, errors, stats, samples) {
52
+ return {
53
+ output: output || new Uint8Array(0),
54
+ errors: errors || new Uint8Array(0),
55
+ stats: stats || new Uint8Array(0),
56
+ samples: samples || new Uint8Array(0),
57
+ get outputText() { return textDecoder().decode(this.output) },
58
+ get statsText() { return textDecoder().decode(this.stats) }
59
+ }
60
+ }
61
+
62
+ function normalizeMessage(event) {
63
+ return event && event.data !== undefined ? event.data : event
64
+ }
65
+
66
+ function postMessage(endpoint, msg, transfers) {
67
+ endpoint.postMessage(msg, transfers && transfers.length ? transfers : undefined)
68
+ }
69
+
70
+ function addWorkerMessageListener(worker, fn) {
71
+ if (worker && typeof worker.addEventListener === 'function') {
72
+ var listener = function(event) { fn(normalizeMessage(event)) }
73
+ worker.addEventListener('message', listener)
74
+ return function() { worker.removeEventListener('message', listener) }
75
+ }
76
+ if (worker && typeof worker.on === 'function') {
77
+ worker.on('message', fn)
78
+ return function() {
79
+ if (typeof worker.off === 'function') worker.off('message', fn)
80
+ else if (typeof worker.removeListener === 'function') worker.removeListener('message', fn)
81
+ }
82
+ }
83
+ throw new TypeError('worker must be a Web Worker or worker_threads Worker')
84
+ }
85
+
86
+ function defaultServerEndpoint() {
87
+ if (typeof require === 'function') {
88
+ try {
89
+ var wt = require('worker_threads')
90
+ if (!wt.isMainThread && wt.parentPort) {
91
+ return {
92
+ postMessage: function(msg, transfers) { wt.parentPort.postMessage(msg, transfers || []) },
93
+ onMessage: function(fn) {
94
+ wt.parentPort.on('message', fn)
95
+ return function() { wt.parentPort.off('message', fn) }
96
+ }
97
+ }
98
+ }
99
+ } catch (_) {}
100
+ }
101
+ if (typeof self !== 'undefined' && typeof self.postMessage === 'function') {
102
+ return {
103
+ postMessage: function(msg, transfers) { self.postMessage(msg, transfers || []) },
104
+ onMessage: function(fn) {
105
+ var listener = function(event) { fn(normalizeMessage(event)) }
106
+ self.addEventListener('message', listener)
107
+ return function() { self.removeEventListener('message', listener) }
108
+ }
109
+ }
110
+ }
111
+ throw new Error('no Worker endpoint available')
112
+ }
113
+
114
+ function progressSnapshot(job, phase, finished) {
115
+ return {
116
+ bytesIn: job.bytesIn,
117
+ bytesOut: job.bytesOut,
118
+ chunksIn: job.chunksIn,
119
+ chunksOut: job.chunksOut,
120
+ phase: phase,
121
+ finished: finished ? true : false
122
+ }
123
+ }
124
+
125
+ async function runWorkerServer(endpoint) {
126
+ endpoint = endpoint || defaultServerEndpoint()
127
+ var tf = await createTranfi()
128
+ var jobs = Object.create(null)
129
+
130
+ function send(msg, transfers) {
131
+ postMessage(endpoint, msg, transfers)
132
+ }
133
+
134
+ function fail(id, err) {
135
+ send({
136
+ id: id,
137
+ type: 'error',
138
+ error: String(err && err.message ? err.message : err || 'unknown error'),
139
+ errorName: err && err.name,
140
+ errorCode: err && Number.isInteger(err.code) ? err.code : undefined
141
+ })
142
+ }
143
+
144
+ function preparedCancelToken(msg) {
145
+ if (!msg.cancelBuffer) return null
146
+ var flag = new Int32Array(msg.cancelBuffer)
147
+ return tf.createTransformCancelToken({
148
+ sharedFlag: flag,
149
+ limits: msg.limits
150
+ })
151
+ }
152
+
153
+ function reportNativePoll(token, id, phase) {
154
+ if (token) {
155
+ token._observeNextPoll(function() {
156
+ send({ id: id, type: 'transform-ready', phase: phase })
157
+ })
158
+ } else {
159
+ send({ id: id, type: 'transform-ready', phase: phase })
160
+ }
161
+ }
162
+
163
+ function handleTransformAnalyze(msg) {
164
+ var token = null
165
+ var recipe = null
166
+ var analyzer = null
167
+ var plan = null
168
+ try {
169
+ token = preparedCancelToken(msg)
170
+ recipe = tf.TransformRecipe.fromJSON(msg.recipe, { limits: msg.limits })
171
+ analyzer = recipe.analyzer(msg.schema, {
172
+ limits: msg.limits,
173
+ cancelToken: token
174
+ })
175
+ var tables = Array.isArray(msg.tables) ? msg.tables : []
176
+ for (var i = 0; i < tables.length; i++) {
177
+ reportNativePoll(token, msg.id, 'analyze')
178
+ analyzer.push(tables[i])
179
+ }
180
+ reportNativePoll(token, msg.id, 'finalize')
181
+ plan = analyzer.finalize()
182
+ var planBytes = plan.toBytes({ limits: msg.limits })
183
+ send({ id: msg.id, type: 'transform-plan', planBytes: planBytes }, [planBytes.buffer])
184
+ } finally {
185
+ if (plan) plan.close()
186
+ if (analyzer) analyzer.close()
187
+ if (recipe) recipe.close()
188
+ if (token) token.close()
189
+ }
190
+ }
191
+
192
+ function handleTransformApply(msg) {
193
+ var token = null
194
+ var plan = null
195
+ var apply = null
196
+ try {
197
+ token = preparedCancelToken(msg)
198
+ reportNativePoll(token, msg.id, 'import')
199
+ plan = tf.TransformPlan.fromBytes(asBytes(msg.planBytes), {
200
+ limits: msg.limits,
201
+ cancelToken: token
202
+ })
203
+ apply = plan.apply(msg.schema, {
204
+ limits: msg.limits,
205
+ cancelToken: token
206
+ })
207
+ reportNativePoll(token, msg.id, 'apply')
208
+ var result = apply.run(msg.table)
209
+ send({
210
+ id: msg.id,
211
+ type: 'transform-result',
212
+ result: {
213
+ rows: result.rows,
214
+ columns: result.columns,
215
+ data: result.data
216
+ }
217
+ }, [result.data.buffer])
218
+ } finally {
219
+ if (apply) apply.close()
220
+ if (plan) plan.close()
221
+ if (token) token.close()
222
+ }
223
+ }
224
+
225
+ function requireJob(id) {
226
+ var job = jobs[id]
227
+ if (!job) throw new Error('unknown or cancelled worker pipeline')
228
+ if (job.cancelled) throw new Error('worker pipeline cancelled')
229
+ return job
230
+ }
231
+
232
+ function sendProgress(job, phase, finished) {
233
+ send({ id: job.id, type: 'progress', progress: progressSnapshot(job, phase, finished) })
234
+ }
235
+
236
+ function drainMain(job) {
237
+ for (;;) {
238
+ var chunk = tf.pullChunk(job.handle, CHAN_MAIN, job.chunkSize)
239
+ if (chunk.length === 0) break
240
+ job.bytesOut += chunk.length
241
+ job.chunksOut += 1
242
+ send({ id: job.id, type: 'output', channel: CHAN_MAIN, chunk: chunk }, [chunk.buffer])
243
+ sendProgress(job, 'pull', false)
244
+ }
245
+ }
246
+
247
+ function startJob(msg) {
248
+ if (!msg || msg.id === undefined || msg.id === null) throw new Error('worker message missing id')
249
+ if (jobs[msg.id]) throw new Error('worker pipeline id already exists')
250
+ var options = msg.options || {}
251
+ var chunkSize = options.chunkSize || DEFAULT_CHUNK_SIZE
252
+ if (chunkSize <= 0) throw new Error('chunkSize must be positive')
253
+ var planJson = tf.compileDsl(msg.dsl || '')
254
+ if (typeof tf.prepareNativePlan === 'function') {
255
+ planJson = tf.prepareNativePlan(planJson, {
256
+ allowBlocking: options.allowBlocking === true,
257
+ memory: options.memory,
258
+ spillDir: options.spillDir
259
+ })
260
+ }
261
+ var handle = tf.createPipeline(planJson, {
262
+ allowFs: options.allowFs === undefined ? Boolean(options.spillDir) : Boolean(options.allowFs),
263
+ allowSpill: Boolean(options.spillDir),
264
+ allowRulesFile: Boolean(options.allowRulesFile),
265
+ workspaceRoot: options.workspaceRoot
266
+ })
267
+ var job = {
268
+ id: msg.id,
269
+ handle: handle,
270
+ chunkSize: chunkSize,
271
+ bytesIn: 0,
272
+ bytesOut: 0,
273
+ chunksIn: 0,
274
+ chunksOut: 0,
275
+ cancelled: false
276
+ }
277
+ jobs[msg.id] = job
278
+ sendProgress(job, 'start', false)
279
+ return job
280
+ }
281
+
282
+ function pushJob(job, chunk) {
283
+ var bytes = asBytes(chunk)
284
+ tf.push(job.handle, bytes)
285
+ job.bytesIn += bytes.length
286
+ job.chunksIn += 1
287
+ sendProgress(job, 'push', false)
288
+ drainMain(job)
289
+ }
290
+
291
+ function finishJob(job) {
292
+ for (;;) {
293
+ var done = tf.finishStep(job.handle)
294
+ sendProgress(job, 'finish', false)
295
+ drainMain(job)
296
+ if (done) break
297
+ if (job.cancelled) throw new Error('worker pipeline cancelled')
298
+ }
299
+ var errors = tf.pull(job.handle, CHAN_ERRORS)
300
+ var stats = tf.pull(job.handle, CHAN_STATS)
301
+ var samples = tf.pull(job.handle, CHAN_SAMPLES)
302
+ sendProgress(job, 'done', true)
303
+ send({
304
+ id: job.id,
305
+ type: 'done',
306
+ progress: progressSnapshot(job, 'done', true),
307
+ errors: errors,
308
+ stats: stats,
309
+ samples: samples
310
+ }, [errors.buffer, stats.buffer, samples.buffer])
311
+ tf.free(job.handle)
312
+ delete jobs[job.id]
313
+ }
314
+
315
+ async function handle(msg) {
316
+ msg = normalizeMessage(msg)
317
+ if (!msg || !msg.type) return
318
+ if (msg.type === 'start') {
319
+ var job = startJob(msg)
320
+ send({ id: msg.id, type: 'started' })
321
+ return
322
+ }
323
+ if (msg.type === 'push') {
324
+ var pushTarget = requireJob(msg.id)
325
+ pushJob(pushTarget, msg.chunk)
326
+ send({ id: msg.id, type: 'pushed' })
327
+ return
328
+ }
329
+ if (msg.type === 'finish') {
330
+ finishJob(requireJob(msg.id))
331
+ return
332
+ }
333
+ if (msg.type === 'run') {
334
+ var runJob = startJob(msg)
335
+ var options = msg.options || {}
336
+ var data = asBytes(msg.data)
337
+ var chunkSize = runJob.chunkSize
338
+ for (var i = 0; i < data.length; i += chunkSize) {
339
+ if (runJob.cancelled) throw new Error('worker pipeline cancelled')
340
+ pushJob(runJob, data.subarray(i, Math.min(i + chunkSize, data.length)))
341
+ }
342
+ finishJob(runJob)
343
+ return
344
+ }
345
+ if (msg.type === 'transform-analyze') {
346
+ handleTransformAnalyze(msg)
347
+ return
348
+ }
349
+ if (msg.type === 'transform-apply') {
350
+ handleTransformApply(msg)
351
+ return
352
+ }
353
+ if (msg.type === 'cancel') {
354
+ var cancelJob = jobs[msg.id]
355
+ if (cancelJob) {
356
+ cancelJob.cancelled = true
357
+ tf.free(cancelJob.handle)
358
+ delete jobs[msg.id]
359
+ }
360
+ send({ id: msg.id, type: 'cancelled' })
361
+ }
362
+ }
363
+
364
+ endpoint.onMessage(function(msg) {
365
+ Promise.resolve(handle(msg)).catch(function(err) {
366
+ var id = msg && msg.id !== undefined ? msg.id : null
367
+ if (id !== null && jobs[id]) {
368
+ try { tf.free(jobs[id].handle) } catch (_) {}
369
+ delete jobs[id]
370
+ }
371
+ fail(id, err)
372
+ })
373
+ })
374
+ }
375
+
376
+ function createWorkerClient(workerOrUrl, options) {
377
+ options = options || {}
378
+ var worker = workerOrUrl
379
+ var ownsWorker = options.terminateOnDispose === true
380
+ var workerFactory = typeof options.workerFactory === 'function'
381
+ ? options.workerFactory
382
+ : null
383
+ if (workerFactory) ownsWorker = true
384
+ if ((typeof workerOrUrl === 'string' || (typeof URL !== 'undefined' && workerOrUrl instanceof URL)) && typeof Worker !== 'undefined') {
385
+ workerFactory = function() { return new Worker(workerOrUrl, options.workerOptions) }
386
+ worker = workerFactory()
387
+ ownsWorker = true
388
+ }
389
+ if (!worker || typeof worker.postMessage !== 'function') throw new TypeError('worker must be a Worker instance or URL')
390
+
391
+ var nextId = 1
392
+ var jobs = new Map()
393
+ var transfer = options.transfer !== false
394
+ var disposed = false
395
+ var removeListener = null
396
+ var restartPromise = null
397
+ var restartError = null
398
+
399
+ function send(msg, transfers) {
400
+ if (!worker) throw new Error('worker client has no live worker; recreate the client')
401
+ worker.postMessage(msg, transfer && transfers && transfers.length ? transfers : undefined)
402
+ }
403
+
404
+ function rejectWaiters(state, err) {
405
+ var waiters = state.waiters.splice(0)
406
+ for (var i = 0; i < waiters.length; i++) waiters[i].reject(err)
407
+ }
408
+
409
+ function resolveWaiter(state, msg) {
410
+ for (var i = 0; i < state.waiters.length; i++) {
411
+ if (state.waiters[i].types.indexOf(msg.type) !== -1) {
412
+ var waiter = state.waiters.splice(i, 1)[0]
413
+ waiter.resolve(msg)
414
+ return true
415
+ }
416
+ }
417
+ return false
418
+ }
419
+
420
+ function waitFor(state, types) {
421
+ return new Promise(function(resolve, reject) {
422
+ state.waiters.push({ types: types, resolve: resolve, reject: reject })
423
+ })
424
+ }
425
+
426
+ function abortState(state) {
427
+ if (state.done || state.cancelled) return
428
+ state.cancelled = true
429
+ try { send({ id: state.id, type: 'cancel' }) } catch (_) {}
430
+ rejectWaiters(state, new Error('worker pipeline cancelled'))
431
+ }
432
+
433
+ function onMessage(raw) {
434
+ var msg = normalizeMessage(raw)
435
+ if (!msg || msg.id === undefined || msg.id === null) return
436
+ var state = jobs.get(msg.id)
437
+ if (!state) return
438
+ if (msg.type === 'output') {
439
+ var chunk = ownedBytes(msg.chunk)
440
+ if (state.onOutput) state.outputCallbacks.push(Promise.resolve().then(function() { return state.onOutput(chunk) }))
441
+ if (state.collectOutput) state.outputChunks.push(chunk)
442
+ return
443
+ }
444
+ if (msg.type === 'progress') {
445
+ if (state.onProgress) state.progressCallbacks.push(Promise.resolve().then(function() { return state.onProgress(msg.progress) }))
446
+ return
447
+ }
448
+ if (msg.type === 'transform-ready') {
449
+ if (state.onTransformReady) state.onTransformReady(msg.phase)
450
+ return
451
+ }
452
+ if (msg.type === 'error') {
453
+ var err = msg.errorName === 'TranfiTransformError' && Number.isInteger(msg.errorCode)
454
+ ? new TranfiTransformError(msg.errorCode, msg.error || 'prepared transform failed')
455
+ : new Error(msg.error || 'worker pipeline failed')
456
+ state.done = true
457
+ rejectWaiters(state, err)
458
+ return
459
+ }
460
+ if (msg.type === 'done') {
461
+ state.done = true
462
+ resolveWaiter(state, msg)
463
+ return
464
+ }
465
+ resolveWaiter(state, msg)
466
+ }
467
+
468
+ function attachWorker(nextWorker) {
469
+ if (!nextWorker || typeof nextWorker.postMessage !== 'function') {
470
+ throw new TypeError('workerFactory must return a Worker instance')
471
+ }
472
+ worker = nextWorker
473
+ restartError = null
474
+ removeListener = addWorkerMessageListener(worker, onMessage)
475
+ }
476
+
477
+ async function ensureWorker() {
478
+ var pending = restartPromise
479
+ if (pending) await pending
480
+ if (restartError) throw restartError
481
+ if (!worker) throw new Error('worker client has no live worker; recreate the client')
482
+ }
483
+
484
+ function terminateForCancellation(error) {
485
+ var oldWorker = worker
486
+ if (!oldWorker || typeof oldWorker.terminate !== 'function') {
487
+ throw new Error('whole-worker cancellation requires an owned terminable worker')
488
+ }
489
+ if (removeListener) {
490
+ removeListener()
491
+ removeListener = null
492
+ }
493
+ worker = null
494
+ for (var state of jobs.values()) {
495
+ if (!state.done) {
496
+ state.done = true
497
+ state.cancelled = true
498
+ rejectWaiters(state, error)
499
+ }
500
+ }
501
+ var terminated
502
+ try {
503
+ terminated = oldWorker.terminate()
504
+ } catch (_) {
505
+ terminated = undefined
506
+ }
507
+ if (!disposed && workerFactory) {
508
+ restartPromise = Promise.resolve(terminated).catch(function() {}).then(function() {
509
+ if (disposed) return
510
+ try {
511
+ attachWorker(workerFactory())
512
+ } catch (error) {
513
+ restartError = error
514
+ worker = null
515
+ }
516
+ }).finally(function() {
517
+ restartPromise = null
518
+ })
519
+ return restartPromise
520
+ }
521
+ return terminated
522
+ }
523
+
524
+ attachWorker(worker)
525
+
526
+ async function runChunks(dsl, chunks, runOptions) {
527
+ await ensureWorker()
528
+ runOptions = runOptions || {}
529
+ var chunkSize = runOptions.chunkSize || DEFAULT_CHUNK_SIZE
530
+ if (chunkSize <= 0) throw new Error('chunkSize must be positive')
531
+ if (runOptions.onOutput !== undefined && typeof runOptions.onOutput !== 'function') throw new TypeError('onOutput must be a function')
532
+ if (runOptions.onProgress !== undefined && typeof runOptions.onProgress !== 'function') throw new TypeError('onProgress must be a function')
533
+
534
+ var id = nextId++
535
+ var state = {
536
+ id: id,
537
+ collectOutput: runOptions.collectOutput !== false,
538
+ outputChunks: [],
539
+ waiters: [],
540
+ done: false,
541
+ cancelled: false,
542
+ onOutput: runOptions.onOutput || null,
543
+ onProgress: runOptions.onProgress || null,
544
+ outputCallbacks: [],
545
+ progressCallbacks: []
546
+ }
547
+ jobs.set(id, state)
548
+
549
+ var signal = runOptions.signal
550
+ var abortListener = null
551
+ function throwIfAborted() {
552
+ if (signal && signal.aborted) {
553
+ abortState(state)
554
+ throw new Error('worker pipeline cancelled')
555
+ }
556
+ }
557
+ if (signal && typeof signal.addEventListener === 'function') {
558
+ abortListener = function() { abortState(state) }
559
+ signal.addEventListener('abort', abortListener, { once: true })
560
+ }
561
+
562
+ try {
563
+ throwIfAborted()
564
+ send({
565
+ id: id,
566
+ type: 'start',
567
+ dsl: dsl,
568
+ options: {
569
+ chunkSize: chunkSize,
570
+ allowBlocking: runOptions.allowBlocking === true,
571
+ memory: runOptions.memory,
572
+ spillDir: runOptions.spillDir
573
+ }
574
+ })
575
+ await waitFor(state, ['started'])
576
+ for await (var chunk of chunks) {
577
+ throwIfAborted()
578
+ var bytes = ownedBytes(chunk)
579
+ send({ id: id, type: 'push', chunk: bytes }, [bytes.buffer])
580
+ await waitFor(state, ['pushed'])
581
+ }
582
+ throwIfAborted()
583
+ send({ id: id, type: 'finish' })
584
+ var done = await waitFor(state, ['done'])
585
+ await Promise.all(state.outputCallbacks)
586
+ await Promise.all(state.progressCallbacks)
587
+ var output = state.collectOutput ? concatUint8(state.outputChunks) : new Uint8Array(0)
588
+ return makeResult(output, ownedBytes(done.errors), ownedBytes(done.stats), ownedBytes(done.samples))
589
+ } catch (err) {
590
+ if (!state.done) abortState(state)
591
+ throw err
592
+ } finally {
593
+ if (signal && abortListener && typeof signal.removeEventListener === 'function') signal.removeEventListener('abort', abortListener)
594
+ jobs.delete(id)
595
+ }
596
+ }
597
+
598
+ async function *dataChunks(data, chunkSize) {
599
+ var bytes = asBytes(data)
600
+ for (var i = 0; i < bytes.length; i += chunkSize) {
601
+ yield bytes.subarray(i, Math.min(i + chunkSize, bytes.length))
602
+ }
603
+ }
604
+
605
+ async function *fileChunks(file, chunkSize) {
606
+ if (!file) return
607
+ if (typeof file.stream === 'function') {
608
+ var stream = file.stream()
609
+ if (stream && typeof stream.getReader === 'function') {
610
+ var reader = stream.getReader()
611
+ try {
612
+ for (;;) {
613
+ var next = await reader.read()
614
+ if (next.done) break
615
+ var bytes = asBytes(next.value)
616
+ for (var i = 0; i < bytes.length; i += chunkSize) yield bytes.subarray(i, Math.min(i + chunkSize, bytes.length))
617
+ }
618
+ } finally {
619
+ if (typeof reader.releaseLock === 'function') reader.releaseLock()
620
+ }
621
+ return
622
+ }
623
+ if (stream && (typeof stream[Symbol.asyncIterator] === 'function' || typeof stream[Symbol.iterator] === 'function')) {
624
+ for await (var chunk of stream) {
625
+ var streamBytes = asBytes(chunk)
626
+ for (var j = 0; j < streamBytes.length; j += chunkSize) yield streamBytes.subarray(j, Math.min(j + chunkSize, streamBytes.length))
627
+ }
628
+ return
629
+ }
630
+ }
631
+ if (typeof file.slice === 'function' && typeof file.size === 'number') {
632
+ for (var offset = 0; offset < file.size; offset += chunkSize) {
633
+ var part = file.slice(offset, Math.min(offset + chunkSize, file.size))
634
+ var buf = await part.arrayBuffer()
635
+ yield new Uint8Array(buf)
636
+ }
637
+ return
638
+ }
639
+ throw new TypeError('file must be a Blob/File with stream() or slice()')
640
+ }
641
+
642
+ async function runTransformRequest(type, payload, runOptions) {
643
+ runOptions = runOptions || {}
644
+ var signal = runOptions.signal
645
+ if (signal && signal.aborted) {
646
+ throw new TranfiTransformError(109, 'prepared transform worker request cancelled')
647
+ }
648
+ await ensureWorker()
649
+ if (signal && signal.aborted) {
650
+ throw new TranfiTransformError(109, 'prepared transform worker request cancelled')
651
+ }
652
+ var cancelFlag = null
653
+ var terminateOnAbort = false
654
+ if (signal) {
655
+ var sharedCancellation = options.sharedCancellation !== false
656
+ && typeof SharedArrayBuffer !== 'undefined'
657
+ && typeof Atomics !== 'undefined'
658
+ if (!sharedCancellation) {
659
+ if (!workerFactory || !worker || typeof worker.terminate !== 'function') {
660
+ throw new Error('prepared-transform cancellation without SharedArrayBuffer requires a worker URL or workerFactory for termination and recreation')
661
+ }
662
+ terminateOnAbort = true
663
+ } else {
664
+ cancelFlag = new Int32Array(new SharedArrayBuffer(4))
665
+ }
666
+ }
667
+ var id = nextId++
668
+ var state = {
669
+ id: id,
670
+ waiters: [],
671
+ done: false,
672
+ cancelled: false,
673
+ outputCallbacks: [],
674
+ progressCallbacks: [],
675
+ onTransformReady: typeof runOptions.onReady === 'function'
676
+ ? runOptions.onReady
677
+ : null
678
+ }
679
+ jobs.set(id, state)
680
+ var abortListener = null
681
+ if (signal && typeof signal.addEventListener === 'function') {
682
+ abortListener = terminateOnAbort
683
+ ? function() {
684
+ terminateForCancellation(new TranfiTransformError(
685
+ 109, 'prepared transform worker request cancelled by worker termination'
686
+ ))
687
+ }
688
+ : function() { Atomics.store(cancelFlag, 0, 1) }
689
+ signal.addEventListener('abort', abortListener, { once: true })
690
+ }
691
+ try {
692
+ send(Object.assign({
693
+ id: id,
694
+ type: type,
695
+ cancelBuffer: cancelFlag ? cancelFlag.buffer : null
696
+ }, payload))
697
+ var responseType = type === 'transform-analyze'
698
+ ? 'transform-plan'
699
+ : 'transform-result'
700
+ return await waitFor(state, [responseType])
701
+ } finally {
702
+ state.done = true
703
+ if (signal && abortListener && typeof signal.removeEventListener === 'function') {
704
+ signal.removeEventListener('abort', abortListener)
705
+ }
706
+ jobs.delete(id)
707
+ }
708
+ }
709
+
710
+ return {
711
+ run: function(dsl, data, runOptions) {
712
+ runOptions = runOptions || {}
713
+ var chunkSize = runOptions.chunkSize || DEFAULT_CHUNK_SIZE
714
+ return runChunks(dsl, dataChunks(data || '', chunkSize), runOptions)
715
+ },
716
+ runChunks: runChunks,
717
+ runFile: function(dsl, file, runOptions) {
718
+ runOptions = runOptions || {}
719
+ var chunkSize = runOptions.chunkSize || DEFAULT_CHUNK_SIZE
720
+ return runChunks(dsl, fileChunks(file, chunkSize), runOptions)
721
+ },
722
+ analyzeTransform: async function(recipe, schema, tables, runOptions) {
723
+ runOptions = runOptions || {}
724
+ var response = await runTransformRequest('transform-analyze', {
725
+ recipe: recipe,
726
+ schema: schema,
727
+ tables: Array.isArray(tables) ? tables : [tables],
728
+ limits: runOptions.limits
729
+ }, runOptions)
730
+ return ownedBytes(response.planBytes)
731
+ },
732
+ applyTransform: async function(planBytes, schema, table, runOptions) {
733
+ runOptions = runOptions || {}
734
+ var response = await runTransformRequest('transform-apply', {
735
+ planBytes: ownedBytes(planBytes),
736
+ schema: schema,
737
+ table: table,
738
+ limits: runOptions.limits
739
+ }, runOptions)
740
+ return {
741
+ rows: response.result.rows,
742
+ columns: response.result.columns,
743
+ data: new Float64Array(
744
+ response.result.data.buffer,
745
+ response.result.data.byteOffset,
746
+ response.result.data.length
747
+ )
748
+ }
749
+ },
750
+ cancelAll: function() {
751
+ for (var state of jobs.values()) abortState(state)
752
+ },
753
+ dispose: function() {
754
+ var pendingRestart = restartPromise
755
+ disposed = true
756
+ this.cancelAll()
757
+ if (removeListener) {
758
+ removeListener()
759
+ removeListener = null
760
+ }
761
+ if (ownsWorker && worker && typeof worker.terminate === 'function') {
762
+ var activeWorker = worker
763
+ worker = null
764
+ return activeWorker.terminate()
765
+ }
766
+ return pendingRestart || undefined
767
+ }
768
+ }
769
+ }
770
+
771
+ if (typeof module !== 'undefined') module.exports = { createWorkerClient: createWorkerClient, runWorkerServer: runWorkerServer }
772
+ if (typeof exports !== 'undefined') {
773
+ exports.createWorkerClient = createWorkerClient
774
+ exports.runWorkerServer = runWorkerServer
775
+ }
776
+
777
+ if (typeof require === 'function') {
778
+ try {
779
+ var workerThreads = require('worker_threads')
780
+ if (!workerThreads.isMainThread && workerThreads.parentPort) {
781
+ runWorkerServer().catch(function(err) {
782
+ workerThreads.parentPort.postMessage({ id: null, type: 'error', error: String(err && err.message ? err.message : err) })
783
+ })
784
+ }
785
+ } catch (_) {}
786
+ }