tranfi 0.1.2 → 0.2.1
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 -0
- package/NOTICE +8 -0
- package/README.md +443 -51
- package/app/assets/{index-pDFMluyz.js → index-BIAIKnrp.js} +1 -1
- package/app/index.html +1 -1
- package/binding.gyp +55 -3
- package/csrc/arena.c +7 -5
- package/csrc/batch.c +818 -71
- package/csrc/buffer.c +84 -8
- package/csrc/cJSON.c +262 -19
- package/csrc/cJSON.h +17 -1
- package/csrc/codec_csv.c +1074 -181
- package/csrc/codec_jsonl.c +830 -118
- package/csrc/codec_table.c +108 -78
- package/csrc/codec_text.c +286 -68
- package/csrc/compiler.c +31 -3
- package/csrc/config.h +21 -0
- package/csrc/dsl.c +4722 -485
- package/csrc/expr.c +363 -55
- package/csrc/expr.h +2 -0
- package/csrc/internal.h +316 -27
- package/csrc/ir.c +65 -18
- package/csrc/ir.h +41 -0
- package/csrc/ir_schema.c +20 -5
- package/csrc/ir_serialize.c +68 -6
- package/csrc/ir_sql.c +796 -185
- package/csrc/ir_validate.c +462 -6
- package/csrc/json_path.c +210 -0
- package/csrc/main.c +879 -30
- package/csrc/memory_estimate.c +477 -0
- package/csrc/op_acf.c +171 -21
- package/csrc/op_across.c +477 -0
- package/csrc/op_anomaly.c +167 -32
- package/csrc/op_assert.c +761 -0
- package/csrc/op_bin.c +168 -29
- package/csrc/op_cast.c +383 -55
- package/csrc/op_clip.c +30 -19
- package/csrc/op_date_trunc.c +208 -34
- package/csrc/op_datetime.c +259 -77
- package/csrc/op_derive.c +65 -97
- package/csrc/op_diff.c +146 -30
- package/csrc/op_ewma.c +149 -30
- package/csrc/op_explode.c +124 -26
- package/csrc/op_fill_down.c +125 -53
- package/csrc/op_fill_null.c +176 -31
- package/csrc/op_filter.c +89 -40
- package/csrc/op_frequency.c +571 -43
- package/csrc/op_grep.c +36 -18
- package/csrc/op_group_agg.c +1790 -119
- package/csrc/op_hash.c +48 -15
- package/csrc/op_head.c +21 -86
- package/csrc/op_interpolate.c +268 -62
- package/csrc/op_join.c +2700 -182
- 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 +328 -53
- package/csrc/op_lag.c +181 -0
- package/csrc/op_lead.c +141 -89
- package/csrc/op_normalize.c +363 -79
- package/csrc/op_onehot.c +345 -73
- package/csrc/op_pivot.c +1546 -162
- package/csrc/op_quarantine.c +189 -0
- package/csrc/op_registry.c +2062 -166
- package/csrc/op_rename.c +41 -50
- package/csrc/op_replace.c +270 -118
- package/csrc/op_rleid.c +297 -0
- package/csrc/op_rowid.c +559 -0
- package/csrc/op_sample.c +80 -23
- package/csrc/op_schema.c +1341 -0
- package/csrc/op_schema_infer.c +252 -0
- package/csrc/op_select.c +265 -65
- package/csrc/op_set.c +3449 -0
- package/csrc/op_skip.c +30 -87
- package/csrc/op_sort.c +670 -124
- package/csrc/op_source_name.c +120 -0
- package/csrc/op_split.c +65 -28
- package/csrc/op_split_data.c +41 -9
- package/csrc/op_stack.c +178 -222
- package/csrc/op_stats.c +206 -110
- package/csrc/op_step.c +217 -55
- package/csrc/op_tail.c +21 -12
- package/csrc/op_tee.c +338 -0
- package/csrc/op_top.c +260 -53
- package/csrc/op_trim.c +48 -19
- package/csrc/op_unique.c +1193 -150
- package/csrc/op_unpivot.c +100 -66
- package/csrc/op_validate.c +601 -24
- package/csrc/op_window.c +492 -51
- package/csrc/path_policy.c +85 -0
- package/csrc/pipeline.c +872 -99
- package/csrc/recipes.c +3 -1
- package/csrc/report.c +73 -30
- package/csrc/selector.c +1097 -0
- package/csrc/size_utils.c +352 -0
- package/csrc/spill.c +317 -0
- package/csrc/spill.h +21 -0
- package/csrc/tranfi.h +169 -1
- 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 +70 -9
- package/napi_api.c +219 -11
- package/napi_transform.c +1648 -0
- package/napi_transform.h +8 -0
- package/package.json +27 -11
- package/scripts/install-native.js +76 -0
- package/scripts/prepack.js +64 -0
- package/scripts/sync-csrc.js +23 -0
- package/src/cli.js +81 -41
- package/src/engines/duckdb.js +45 -12
- package/src/index.js +661 -42
- package/src/memory_policy.js +411 -0
- package/src/native.js +1 -5
- package/src/pipeline.js +454 -31
- package/src/recipe_json.js +80 -0
- package/src/server.js +10 -8
- package/src/transform.js +403 -0
- package/src/transform_error.js +10 -0
- package/src/wasm.js +6 -4
- package/wasm/index.js +498 -10
- package/wasm/tranfi_core.js +0 -0
- package/wasm/transform.js +1156 -0
- package/wasm/worker.js +786 -0
- package/csrc/plan.c +0 -206
package/src/server.js
CHANGED
|
@@ -6,12 +6,12 @@
|
|
|
6
6
|
* built-in http, fs, path, url modules only.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
9
|
+
const { createServer } = require('http')
|
|
10
|
+
const { readFileSync, readdirSync, statSync, readFile: readFileCb, existsSync } = require('fs')
|
|
11
|
+
const { join, extname, resolve, basename } = require('path')
|
|
12
|
+
const { URL } = require('url')
|
|
13
|
+
const { pipeline, recipes } = require('./index.js')
|
|
14
|
+
const nativeBinding = require('./native.js')
|
|
15
15
|
|
|
16
16
|
const MIME_TYPES = {
|
|
17
17
|
'.html': 'text/html; charset=utf-8',
|
|
@@ -101,7 +101,7 @@ function isSafeName (name) {
|
|
|
101
101
|
return name && !name.includes('..') && !name.includes('/') && !name.includes('\\')
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
-
|
|
104
|
+
function startServer ({ port = 3000, dataDir, appDir }) {
|
|
105
105
|
dataDir = resolve(dataDir)
|
|
106
106
|
appDir = resolve(appDir)
|
|
107
107
|
|
|
@@ -214,7 +214,7 @@ async function handleRun (res, dataDir, body) {
|
|
|
214
214
|
if (!existsSync(filePath)) return sendError(res, `File not found: ${file}`, 404)
|
|
215
215
|
|
|
216
216
|
const p = pipeline(dsl)
|
|
217
|
-
const result = await p.run({ inputFile: filePath })
|
|
217
|
+
const result = await p.run({ inputFile: filePath, hostedAuditDefaults: true })
|
|
218
218
|
|
|
219
219
|
const output = result.outputText
|
|
220
220
|
const stats = result.statsText
|
|
@@ -275,3 +275,5 @@ function serveStatic (res, appDir, pathname, indexHtml) {
|
|
|
275
275
|
res.end(data)
|
|
276
276
|
})
|
|
277
277
|
}
|
|
278
|
+
|
|
279
|
+
module.exports = { startServer }
|
package/src/transform.js
ADDED
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prepared, reusable typed transforms backed by Tranfi's C engine.
|
|
3
|
+
*
|
|
4
|
+
* These objects are separate from the byte-stream Pipeline API. Recipes analyze
|
|
5
|
+
* one or more typed batches, finalize an immutable plan, and apply that plan to
|
|
6
|
+
* a second pass over the reference data or to later compatible batches.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const { stringifyRecipe } = require('./recipe_json.js')
|
|
10
|
+
const nativeBinding = require('./native.js')
|
|
11
|
+
const { TranfiTransformError } = require('./transform_error.js')
|
|
12
|
+
|
|
13
|
+
const SCHEMA_INPUT = 1
|
|
14
|
+
const SCHEMA_OUTPUT = 2
|
|
15
|
+
|
|
16
|
+
const LIMIT_FIELDS = new Set([
|
|
17
|
+
'maxRecipeBytes',
|
|
18
|
+
'maxPlanBytes',
|
|
19
|
+
'maxJsonDepth',
|
|
20
|
+
'maxObjectKeys',
|
|
21
|
+
'maxSteps',
|
|
22
|
+
'maxInputColumns',
|
|
23
|
+
'maxOutputColumns',
|
|
24
|
+
'maxCategoriesPerColumn',
|
|
25
|
+
'maxTotalCategories',
|
|
26
|
+
'maxStringBytes',
|
|
27
|
+
'maxDecodedStringBytes',
|
|
28
|
+
'maxAnalyzerRows',
|
|
29
|
+
'maxAnalyzerInputBytes',
|
|
30
|
+
'maxResidentStateBytes',
|
|
31
|
+
'maxSpillBytes',
|
|
32
|
+
'maxApplyRows',
|
|
33
|
+
'maxApplyInputBytes',
|
|
34
|
+
'maxOutputElementsPerCall',
|
|
35
|
+
'maxAllocationBytes',
|
|
36
|
+
'maxAllocationsPerSession',
|
|
37
|
+
'maxLiveHandles',
|
|
38
|
+
'maxRetiredHandleSlots'
|
|
39
|
+
])
|
|
40
|
+
const RUNTIME_OPTION_FIELDS = new Set([
|
|
41
|
+
'limits', 'cancelFlag', 'hostPolicy', 'spillDir'
|
|
42
|
+
])
|
|
43
|
+
|
|
44
|
+
function requireNative() {
|
|
45
|
+
if (!nativeBinding || typeof nativeBinding.transformRecipeFromJson !== 'function') {
|
|
46
|
+
throw new Error('prepared transforms require the Tranfi native addon; WASM support is not available in this build')
|
|
47
|
+
}
|
|
48
|
+
return nativeBinding
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function callNative(name, ...args) {
|
|
52
|
+
const binding = requireNative()
|
|
53
|
+
try {
|
|
54
|
+
return binding[name](...args)
|
|
55
|
+
} catch (error) {
|
|
56
|
+
if (error && error.name === 'TranfiTransformError' && Number.isInteger(error.code)) {
|
|
57
|
+
throw new TranfiTransformError(error.code, error.message, error)
|
|
58
|
+
}
|
|
59
|
+
throw error
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function assertPlainObject(value, label) {
|
|
64
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
65
|
+
throw new TypeError(`${label} must be an object`)
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function assertWellFormedUnicode(value, label, runtime, rejectNul = false) {
|
|
70
|
+
for (let index = 0; index < value.length; index++) {
|
|
71
|
+
if (index % 4096 === 0) throwIfCancelled(runtime)
|
|
72
|
+
const unit = value.charCodeAt(index)
|
|
73
|
+
if (rejectNul && unit === 0) {
|
|
74
|
+
throw new TypeError(`${label} must not contain NUL`)
|
|
75
|
+
}
|
|
76
|
+
if (unit >= 0xd800 && unit <= 0xdbff) {
|
|
77
|
+
const next = index + 1 < value.length ? value.charCodeAt(index + 1) : 0
|
|
78
|
+
if (next < 0xdc00 || next > 0xdfff) {
|
|
79
|
+
throw new TypeError(`${label} must not contain an unpaired UTF-16 surrogate`)
|
|
80
|
+
}
|
|
81
|
+
index++
|
|
82
|
+
} else if (unit >= 0xdc00 && unit <= 0xdfff) {
|
|
83
|
+
throw new TypeError(`${label} must not contain an unpaired UTF-16 surrogate`)
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
throwIfCancelled(runtime)
|
|
87
|
+
return value
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function utf8ByteLength(value, label, runtime, rejectNul = false, maxBytes = null) {
|
|
91
|
+
let bytes = 0
|
|
92
|
+
for (let index = 0; index < value.length; index++) {
|
|
93
|
+
if (index % 4096 === 0) throwIfCancelled(runtime)
|
|
94
|
+
const unit = value.charCodeAt(index)
|
|
95
|
+
if (rejectNul && unit === 0) {
|
|
96
|
+
throw new TypeError(`${label} must not contain NUL`)
|
|
97
|
+
}
|
|
98
|
+
if (unit <= 0x7f) {
|
|
99
|
+
bytes += 1
|
|
100
|
+
} else if (unit <= 0x7ff) {
|
|
101
|
+
bytes += 2
|
|
102
|
+
} else if (unit >= 0xd800 && unit <= 0xdbff) {
|
|
103
|
+
const next = index + 1 < value.length ? value.charCodeAt(index + 1) : 0
|
|
104
|
+
if (next < 0xdc00 || next > 0xdfff) {
|
|
105
|
+
throw new TypeError(`${label} must not contain an unpaired UTF-16 surrogate`)
|
|
106
|
+
}
|
|
107
|
+
bytes += 4
|
|
108
|
+
index++
|
|
109
|
+
} else if (unit >= 0xdc00 && unit <= 0xdfff) {
|
|
110
|
+
throw new TypeError(`${label} must not contain an unpaired UTF-16 surrogate`)
|
|
111
|
+
} else {
|
|
112
|
+
bytes += 3
|
|
113
|
+
}
|
|
114
|
+
if (maxBytes !== null && bytes > maxBytes) {
|
|
115
|
+
throw new TranfiTransformError(104, `${label} exceeds its configured byte limit`)
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
throwIfCancelled(runtime)
|
|
119
|
+
return bytes
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function normalizeLimits(limits) {
|
|
123
|
+
if (limits === undefined || limits === null) return undefined
|
|
124
|
+
assertPlainObject(limits, 'limits')
|
|
125
|
+
const normalized = {}
|
|
126
|
+
for (const [name, value] of Object.entries(limits)) {
|
|
127
|
+
if (!LIMIT_FIELDS.has(name)) throw new TypeError(`unknown transform limit field: ${name}`)
|
|
128
|
+
if (!Number.isSafeInteger(value) || value <= 0) {
|
|
129
|
+
throw new TranfiTransformError(100, `${name} must be a positive safe integer`)
|
|
130
|
+
}
|
|
131
|
+
normalized[name] = value
|
|
132
|
+
}
|
|
133
|
+
return normalized
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function safeTransformLimits(overrides = {}) {
|
|
137
|
+
const normalized = normalizeLimits(overrides) || {}
|
|
138
|
+
return { ...callNative('transformSafeLimits'), ...normalized }
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function normalizeCancelFlag(cancelFlag) {
|
|
142
|
+
if (cancelFlag === undefined || cancelFlag === null) return undefined
|
|
143
|
+
const shared = typeof SharedArrayBuffer !== 'undefined'
|
|
144
|
+
&& cancelFlag instanceof Int32Array
|
|
145
|
+
&& Object.prototype.toString.call(cancelFlag.buffer) === '[object SharedArrayBuffer]'
|
|
146
|
+
if (!shared || cancelFlag.length < 1) {
|
|
147
|
+
throw new TypeError('cancelFlag must be a nonempty SharedArrayBuffer-backed Int32Array')
|
|
148
|
+
}
|
|
149
|
+
return cancelFlag
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function normalizeRuntimeOptions(options = {}) {
|
|
153
|
+
if (!options || typeof options !== 'object' || Array.isArray(options)) {
|
|
154
|
+
throw new TypeError('transform runtime options must be an object')
|
|
155
|
+
}
|
|
156
|
+
for (const name of Object.keys(options)) {
|
|
157
|
+
if (!RUNTIME_OPTION_FIELDS.has(name)) {
|
|
158
|
+
throw new TypeError(`unknown transform runtime option: ${name}`)
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
if (options.hostPolicy !== undefined && options.hostPolicy !== null) {
|
|
162
|
+
throw new TranfiTransformError(
|
|
163
|
+
113, 'prepared-transform hostPolicy is reserved but not implemented'
|
|
164
|
+
)
|
|
165
|
+
}
|
|
166
|
+
if (options.spillDir !== undefined && options.spillDir !== null &&
|
|
167
|
+
options.spillDir !== '') {
|
|
168
|
+
throw new TranfiTransformError(
|
|
169
|
+
113, 'prepared-transform spillDir is reserved but not implemented'
|
|
170
|
+
)
|
|
171
|
+
}
|
|
172
|
+
const limits = normalizeLimits(options.limits)
|
|
173
|
+
const cancelFlag = normalizeCancelFlag(options.cancelFlag)
|
|
174
|
+
if (limits === undefined && cancelFlag === undefined) return undefined
|
|
175
|
+
return { limits, cancelFlag }
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function throwIfCancelled(runtime) {
|
|
179
|
+
if (runtime && runtime.cancelFlag
|
|
180
|
+
&& Atomics.load(runtime.cancelFlag, 0) !== 0) {
|
|
181
|
+
throw new TranfiTransformError(109, 'prepared transform cancelled')
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function encodeUtf8(value, label, runtime, maxBytes, knownLength = null) {
|
|
186
|
+
const total = knownLength === null
|
|
187
|
+
? utf8ByteLength(value, label, runtime, true, maxBytes)
|
|
188
|
+
: knownLength
|
|
189
|
+
const encoded = Buffer.allocUnsafe(total)
|
|
190
|
+
let destination = 0
|
|
191
|
+
for (let offset = 0; offset < value.length;) {
|
|
192
|
+
throwIfCancelled(runtime)
|
|
193
|
+
let end = Math.min(offset + 4096, value.length)
|
|
194
|
+
if (end < value.length) {
|
|
195
|
+
const last = value.charCodeAt(end - 1)
|
|
196
|
+
if (last >= 0xd800 && last <= 0xdbff) end--
|
|
197
|
+
}
|
|
198
|
+
destination += encoded.write(
|
|
199
|
+
value.slice(offset, end), destination, total - destination, 'utf8'
|
|
200
|
+
)
|
|
201
|
+
offset = end
|
|
202
|
+
}
|
|
203
|
+
throwIfCancelled(runtime)
|
|
204
|
+
if (destination !== total) {
|
|
205
|
+
throw new Error(`internal UTF-8 length mismatch for ${label}`)
|
|
206
|
+
}
|
|
207
|
+
return encoded
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function normalizeSchema(schema, runtime, limits) {
|
|
211
|
+
const fields = Array.isArray(schema)
|
|
212
|
+
? schema
|
|
213
|
+
: schema && typeof schema === 'object' && Array.isArray(schema.fields)
|
|
214
|
+
? schema.fields
|
|
215
|
+
: null
|
|
216
|
+
if (!fields || fields.length === 0) {
|
|
217
|
+
throw new TypeError('schema must be a nonempty array or { fields } object')
|
|
218
|
+
}
|
|
219
|
+
if (fields.length > limits.maxInputColumns) {
|
|
220
|
+
throw new TranfiTransformError(104, 'prepared-transform input column limit exceeded')
|
|
221
|
+
}
|
|
222
|
+
const prepared = []
|
|
223
|
+
let decodedBytes = 0
|
|
224
|
+
for (let index = 0; index < fields.length; index++) {
|
|
225
|
+
if (index % 4096 === 0) throwIfCancelled(runtime)
|
|
226
|
+
const field = fields[index]
|
|
227
|
+
assertPlainObject(field, `schema field ${index}`)
|
|
228
|
+
const id = field.id
|
|
229
|
+
const name = field.name === undefined ? id : field.name
|
|
230
|
+
const dtype = field.dtype
|
|
231
|
+
if (typeof id !== 'string' || id.length === 0) {
|
|
232
|
+
throw new TypeError(`schema field ${index} id must be a nonempty string`)
|
|
233
|
+
}
|
|
234
|
+
if (typeof name !== 'string' || name.length === 0) {
|
|
235
|
+
throw new TypeError(`schema field ${index} name must be a nonempty string`)
|
|
236
|
+
}
|
|
237
|
+
if (dtype !== 'float32' && dtype !== 'float64') {
|
|
238
|
+
throw new TypeError(`schema field ${index} dtype must be float32 or float64`)
|
|
239
|
+
}
|
|
240
|
+
const maxEncodedBytes = Math.min(
|
|
241
|
+
limits.maxStringBytes, limits.maxAllocationBytes - 1
|
|
242
|
+
)
|
|
243
|
+
const idLength = utf8ByteLength(
|
|
244
|
+
id, `schema field ${index} id`, runtime, true, maxEncodedBytes
|
|
245
|
+
)
|
|
246
|
+
const nameLength = utf8ByteLength(
|
|
247
|
+
name, `schema field ${index} name`, runtime, true, maxEncodedBytes
|
|
248
|
+
)
|
|
249
|
+
if (decodedBytes + idLength + nameLength > limits.maxDecodedStringBytes) {
|
|
250
|
+
throw new TranfiTransformError(104, 'prepared-transform decoded string limit exceeded')
|
|
251
|
+
}
|
|
252
|
+
decodedBytes += idLength + nameLength
|
|
253
|
+
prepared.push({ id, name, dtype, idLength, nameLength })
|
|
254
|
+
}
|
|
255
|
+
throwIfCancelled(runtime)
|
|
256
|
+
|
|
257
|
+
const normalized = []
|
|
258
|
+
for (let index = 0; index < prepared.length; index++) {
|
|
259
|
+
if (index % 4096 === 0) throwIfCancelled(runtime)
|
|
260
|
+
const { id, name, dtype, idLength, nameLength } = prepared[index]
|
|
261
|
+
const maxEncodedBytes = Math.min(
|
|
262
|
+
limits.maxStringBytes, limits.maxAllocationBytes - 1
|
|
263
|
+
)
|
|
264
|
+
const idBytes = encodeUtf8(
|
|
265
|
+
id, `schema field ${index} id`, runtime, maxEncodedBytes, idLength
|
|
266
|
+
)
|
|
267
|
+
const nameBytes = encodeUtf8(
|
|
268
|
+
name, `schema field ${index} name`, runtime, maxEncodedBytes, nameLength
|
|
269
|
+
)
|
|
270
|
+
normalized.push({ id: idBytes, name: nameBytes, dtype })
|
|
271
|
+
}
|
|
272
|
+
throwIfCancelled(runtime)
|
|
273
|
+
return {
|
|
274
|
+
fields: normalized
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function normalizeRecipeJSON(recipe, limits) {
|
|
279
|
+
if (typeof recipe === 'string') {
|
|
280
|
+
return assertWellFormedUnicode(recipe, 'recipe')
|
|
281
|
+
}
|
|
282
|
+
if (Buffer.isBuffer(recipe) || recipe instanceof Uint8Array) {
|
|
283
|
+
return recipe
|
|
284
|
+
}
|
|
285
|
+
assertPlainObject(recipe, 'recipe')
|
|
286
|
+
return stringifyRecipe(recipe, limits)
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
class OwnedTransform {
|
|
290
|
+
constructor(handle) {
|
|
291
|
+
this._handle = handle
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
_requireOpen() {
|
|
295
|
+
if (this._handle === null) {
|
|
296
|
+
throw new TranfiTransformError(112, 'prepared transform object is closed')
|
|
297
|
+
}
|
|
298
|
+
return this._handle
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
close() {
|
|
302
|
+
if (this._handle !== null) {
|
|
303
|
+
callNative('transformDispose', this._handle)
|
|
304
|
+
this._handle = null
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
dispose() {
|
|
309
|
+
this.close()
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
class TransformRecipe extends OwnedTransform {
|
|
314
|
+
static fromJSON(recipe, { limits } = {}) {
|
|
315
|
+
const normalized = normalizeLimits(limits)
|
|
316
|
+
const effective = safeTransformLimits(normalized)
|
|
317
|
+
return new TransformRecipe(callNative(
|
|
318
|
+
'transformRecipeFromJson', normalizeRecipeJSON(recipe, effective), normalized
|
|
319
|
+
))
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
analyzer(schema, options = {}) {
|
|
323
|
+
const recipeHandle = this._requireOpen()
|
|
324
|
+
const runtime = normalizeRuntimeOptions(options)
|
|
325
|
+
throwIfCancelled(runtime)
|
|
326
|
+
const effectiveLimits = safeTransformLimits(runtime && runtime.limits)
|
|
327
|
+
throwIfCancelled(runtime)
|
|
328
|
+
const normalizedSchema = normalizeSchema(schema, runtime, effectiveLimits)
|
|
329
|
+
throwIfCancelled(runtime)
|
|
330
|
+
return new TransformAnalyzer(callNative(
|
|
331
|
+
'transformAnalyzerCreate', recipeHandle, normalizedSchema, runtime
|
|
332
|
+
))
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
class TransformAnalyzer extends OwnedTransform {
|
|
337
|
+
push(table) {
|
|
338
|
+
callNative('transformAnalyzerPush', this._requireOpen(), table)
|
|
339
|
+
return this
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
finalize() {
|
|
343
|
+
return new TransformPlan(callNative('transformAnalyzerFinalize', this._requireOpen()))
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
class TransformPlan extends OwnedTransform {
|
|
348
|
+
static fromBytes(bytes, options = {}) {
|
|
349
|
+
return new TransformPlan(callNative(
|
|
350
|
+
'transformPlanImport', bytes, normalizeRuntimeOptions(options)
|
|
351
|
+
))
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
toBytes({ limits } = {}) {
|
|
355
|
+
return callNative('transformPlanExport', this._requireOpen(), normalizeLimits(limits))
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
schemaJSON(which = 'output', { limits } = {}) {
|
|
359
|
+
const selector = which === 'input'
|
|
360
|
+
? SCHEMA_INPUT
|
|
361
|
+
: which === 'output'
|
|
362
|
+
? SCHEMA_OUTPUT
|
|
363
|
+
: null
|
|
364
|
+
if (selector === null) throw new TypeError("which must be 'input' or 'output'")
|
|
365
|
+
return callNative(
|
|
366
|
+
'transformPlanSchemaJson', this._requireOpen(), selector, normalizeLimits(limits)
|
|
367
|
+
)
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
recipeSha256({ limits } = {}) {
|
|
371
|
+
return callNative(
|
|
372
|
+
'transformPlanRecipeSha256', this._requireOpen(), normalizeLimits(limits)
|
|
373
|
+
)
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
apply(schema, options = {}) {
|
|
377
|
+
const planHandle = this._requireOpen()
|
|
378
|
+
const runtime = normalizeRuntimeOptions(options)
|
|
379
|
+
throwIfCancelled(runtime)
|
|
380
|
+
const effectiveLimits = safeTransformLimits(runtime && runtime.limits)
|
|
381
|
+
throwIfCancelled(runtime)
|
|
382
|
+
const normalizedSchema = normalizeSchema(schema, runtime, effectiveLimits)
|
|
383
|
+
throwIfCancelled(runtime)
|
|
384
|
+
return new TransformApply(callNative(
|
|
385
|
+
'transformApplyCreate', planHandle, normalizedSchema, runtime
|
|
386
|
+
))
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
class TransformApply extends OwnedTransform {
|
|
391
|
+
run(table) {
|
|
392
|
+
return callNative('transformApplyRun', this._requireOpen(), table)
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
module.exports = {
|
|
397
|
+
TranfiTransformError,
|
|
398
|
+
TransformRecipe,
|
|
399
|
+
TransformAnalyzer,
|
|
400
|
+
TransformPlan,
|
|
401
|
+
TransformApply,
|
|
402
|
+
safeTransformLimits
|
|
403
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
class TranfiTransformError extends Error {
|
|
2
|
+
constructor(code, message, cause) {
|
|
3
|
+
super(message)
|
|
4
|
+
this.name = 'TranfiTransformError'
|
|
5
|
+
this.code = Number(code)
|
|
6
|
+
if (cause !== undefined) this.cause = cause
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
module.exports = { TranfiTransformError }
|
package/src/wasm.js
CHANGED
|
@@ -5,15 +5,17 @@
|
|
|
5
5
|
|
|
6
6
|
let instance = null
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
async function loadWasm() {
|
|
9
9
|
if (instance) return instance
|
|
10
10
|
|
|
11
|
-
//
|
|
12
|
-
const
|
|
11
|
+
// Load the WASM module
|
|
12
|
+
const createTranfi = require('../wasm/tranfi_core.js')
|
|
13
13
|
instance = await createTranfi()
|
|
14
14
|
return instance
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
function getWasm() {
|
|
18
18
|
return instance
|
|
19
19
|
}
|
|
20
|
+
|
|
21
|
+
module.exports = { loadWasm, getWasm }
|