tranfi 0.0.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.
- package/LICENSE +177 -21
- package/NOTICE +8 -0
- package/README.md +627 -0
- package/app/assets/index-6quYZ5Ap.css +5 -0
- package/app/assets/index-BIAIKnrp.js +160 -0
- package/app/assets/materialdesignicons-webfont-B7mPwVP_.ttf +0 -0
- package/app/assets/materialdesignicons-webfont-CSr8KVlo.eot +0 -0
- package/app/assets/materialdesignicons-webfont-Dp5v-WZN.woff2 +0 -0
- package/app/assets/materialdesignicons-webfont-PXm3-2wK.woff +0 -0
- package/app/index.html +13 -0
- package/binding.gyp +121 -0
- package/csrc/arena.c +93 -0
- package/csrc/batch.c +976 -0
- package/csrc/buffer.c +154 -0
- package/csrc/cJSON.c +3386 -0
- package/csrc/cJSON.h +316 -0
- package/csrc/codec_csv.c +1951 -0
- package/csrc/codec_jsonl.c +1086 -0
- package/csrc/codec_table.c +248 -0
- package/csrc/codec_text.c +447 -0
- package/csrc/compiler.c +130 -0
- package/csrc/config.h +21 -0
- package/csrc/date_utils.h +94 -0
- package/csrc/dsl.c +5417 -0
- package/csrc/dsl.h +22 -0
- package/csrc/expr.c +1553 -0
- package/csrc/expr.h +58 -0
- package/csrc/internal.h +539 -0
- package/csrc/ir.c +166 -0
- package/csrc/ir.h +208 -0
- package/csrc/ir_schema.c +75 -0
- package/csrc/ir_serialize.c +166 -0
- package/csrc/ir_sql.c +1822 -0
- package/csrc/ir_validate.c +576 -0
- package/csrc/json_path.c +210 -0
- package/csrc/main.c +1241 -0
- package/csrc/memory_estimate.c +477 -0
- package/csrc/op_acf.c +283 -0
- package/csrc/op_across.c +477 -0
- package/csrc/op_anomaly.c +255 -0
- package/csrc/op_assert.c +761 -0
- package/csrc/op_bin.c +248 -0
- package/csrc/op_cast.c +523 -0
- package/csrc/op_clip.c +99 -0
- package/csrc/op_date_trunc.c +355 -0
- package/csrc/op_datetime.c +394 -0
- package/csrc/op_derive.c +216 -0
- package/csrc/op_diff.c +250 -0
- package/csrc/op_ewma.c +222 -0
- package/csrc/op_explode.c +206 -0
- package/csrc/op_fill_down.c +235 -0
- package/csrc/op_fill_null.c +268 -0
- package/csrc/op_filter.c +181 -0
- package/csrc/op_frequency.c +721 -0
- package/csrc/op_grep.c +181 -0
- package/csrc/op_group_agg.c +1956 -0
- package/csrc/op_hash.c +159 -0
- package/csrc/op_head.c +84 -0
- package/csrc/op_interpolate.c +445 -0
- package/csrc/op_join.c +2902 -0
- package/csrc/op_json_extract.c +227 -0
- package/csrc/op_json_filter.c +384 -0
- package/csrc/op_json_flatten.c +293 -0
- package/csrc/op_json_schema.c +503 -0
- package/csrc/op_label_encode.c +419 -0
- package/csrc/op_lag.c +181 -0
- package/csrc/op_lead.c +242 -0
- package/csrc/op_normalize.c +510 -0
- package/csrc/op_onehot.c +457 -0
- package/csrc/op_pivot.c +1754 -0
- package/csrc/op_quarantine.c +189 -0
- package/csrc/op_registry.c +3044 -0
- package/csrc/op_rename.c +129 -0
- package/csrc/op_replace.c +354 -0
- package/csrc/op_rleid.c +297 -0
- package/csrc/op_rowid.c +559 -0
- package/csrc/op_sample.c +158 -0
- package/csrc/op_schema.c +1341 -0
- package/csrc/op_schema_infer.c +252 -0
- package/csrc/op_select.c +340 -0
- package/csrc/op_set.c +3449 -0
- package/csrc/op_skip.c +95 -0
- package/csrc/op_sort.c +819 -0
- package/csrc/op_source_name.c +120 -0
- package/csrc/op_split.c +151 -0
- package/csrc/op_split_data.c +119 -0
- package/csrc/op_stack.c +271 -0
- package/csrc/op_stats.c +875 -0
- package/csrc/op_step.c +333 -0
- package/csrc/op_tail.c +105 -0
- package/csrc/op_tee.c +338 -0
- package/csrc/op_top.c +357 -0
- package/csrc/op_trim.c +138 -0
- package/csrc/op_unique.c +1343 -0
- package/csrc/op_unpivot.c +193 -0
- package/csrc/op_validate.c +648 -0
- package/csrc/op_window.c +591 -0
- package/csrc/path_policy.c +85 -0
- package/csrc/pipeline.c +1088 -0
- package/csrc/recipes.c +104 -0
- package/csrc/recipes.h +27 -0
- package/csrc/report.c +506 -0
- package/csrc/report.h +22 -0
- package/csrc/selector.c +1097 -0
- package/csrc/size_utils.c +348 -0
- package/csrc/spill.c +317 -0
- package/csrc/spill.h +21 -0
- package/csrc/tranfi.h +291 -0
- package/csrc/transform.h +209 -0
- package/csrc/transform_api.c +2237 -0
- package/csrc/transform_categorical.c +923 -0
- package/csrc/transform_internal.h +472 -0
- package/csrc/transform_json.c +3812 -0
- package/csrc/transform_numeric.c +1966 -0
- package/csrc/transform_sha256.c +154 -0
- package/csrc/transform_wasm.h +162 -0
- package/csrc/transform_wasm_api.c +1373 -0
- package/csrc/wasm_api.c +218 -0
- package/napi_api.c +534 -0
- package/napi_transform.c +1648 -0
- package/napi_transform.h +8 -0
- package/package.json +64 -59
- package/scripts/install-native.js +76 -0
- package/scripts/prepack.js +64 -0
- package/scripts/sync-csrc.js +23 -0
- package/src/cli.js +190 -0
- package/src/engines/duckdb.js +142 -0
- package/src/index.js +925 -0
- package/src/memory_policy.js +411 -0
- package/src/native.js +18 -0
- package/src/pipeline.js +709 -0
- package/src/recipe_json.js +80 -0
- package/src/server.js +279 -0
- package/src/transform.js +403 -0
- package/src/transform_error.js +10 -0
- package/src/wasm.js +21 -0
- package/wasm/index.js +732 -0
- package/wasm/package.json +1 -0
- package/wasm/tranfi_core.js +0 -0
- package/wasm/transform.js +1156 -0
- package/wasm/worker.js +786 -0
- package/dist/bundle.js +0 -1
- package/index.html +0 -18
- package/src/app.css +0 -169
- package/src/app.js +0 -203
- package/src/app.vue +0 -250
- package/src/bulma-input.vue +0 -110
- package/src/common-inputs.js +0 -28
- package/src/main.js +0 -20
- package/src/transforms.js +0 -166
- package/webpack.config.js +0 -108
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
|
+
}
|