recurram 0.1.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.
@@ -0,0 +1,35 @@
1
+ export type RuntimeKind = "napi" | "wasm";
2
+ export interface TransportValueObj {
3
+ t: string;
4
+ v?: unknown;
5
+ }
6
+ export interface RuntimeSessionEncoder {
7
+ encodeTransportJson(valueJson: string): Uint8Array;
8
+ encodeWithSchemaTransportJson(schemaJson: string, valueJson: string): Uint8Array;
9
+ encodeBatchTransportJson(valuesJson: string): Uint8Array;
10
+ encodePatchTransportJson(valueJson: string): Uint8Array;
11
+ encodeMicroBatchTransportJson(valuesJson: string): Uint8Array;
12
+ encodeDirect(value: TransportValueObj): Uint8Array;
13
+ encodeBatchDirect(values: TransportValueObj[]): Uint8Array;
14
+ encodePatchDirect(value: TransportValueObj): Uint8Array;
15
+ encodeMicroBatchDirect(values: TransportValueObj[]): Uint8Array;
16
+ encodeCompactJson(json: string): Uint8Array;
17
+ encodeBatchCompactJson(json: string): Uint8Array;
18
+ encodePatchCompactJson(json: string): Uint8Array;
19
+ encodeMicroBatchCompactJson(json: string): Uint8Array;
20
+ reset(): void;
21
+ }
22
+ export interface RuntimeBackend {
23
+ kind: RuntimeKind;
24
+ encodeTransportJson(valueJson: string): Uint8Array;
25
+ decodeToTransportJson(bytes: Uint8Array): string;
26
+ decodeToCompactJson(bytes: Uint8Array): string;
27
+ encodeWithSchemaTransportJson(schemaJson: string, valueJson: string): Uint8Array;
28
+ encodeBatchTransportJson(valuesJson: string): Uint8Array;
29
+ encodeDirect(value: TransportValueObj): Uint8Array;
30
+ decodeDirect(bytes: Uint8Array): TransportValueObj;
31
+ encodeBatchDirect(values: TransportValueObj[]): Uint8Array;
32
+ encodeCompactJson(json: string): Uint8Array;
33
+ encodeBatchCompactJson(json: string): Uint8Array;
34
+ createSessionEncoder(optionsJson?: string): RuntimeSessionEncoder;
35
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,2 @@
1
+ import type { RuntimeBackend } from "./types.js";
2
+ export declare function loadWasmBackend(wasmInput?: unknown): Promise<RuntimeBackend>;
@@ -0,0 +1,44 @@
1
+ export async function loadWasmBackend(wasmInput) {
2
+ const moduleUrl = new URL("../../wasm/pkg/recurram_wasm.js", import.meta.url);
3
+ const wasm = (await import(moduleUrl.href));
4
+ await wasm.default(wasmInput);
5
+ return {
6
+ kind: "wasm",
7
+ encodeTransportJson: (valueJson) => wasm.encodeTransportJson(valueJson),
8
+ decodeToTransportJson: (bytes) => wasm.decodeToTransportJson(bytes),
9
+ decodeToCompactJson: (bytes) => wasm.decodeToTransportJson(bytes), // WASM fallback
10
+ encodeWithSchemaTransportJson: (schemaJson, valueJson) => wasm.encodeWithSchemaTransportJson(schemaJson, valueJson),
11
+ encodeBatchTransportJson: (valuesJson) => wasm.encodeBatchTransportJson(valuesJson),
12
+ // WASM fallback: serialize to JSON then use JSON API
13
+ encodeDirect: (value) => wasm.encodeTransportJson(JSON.stringify(value)),
14
+ decodeDirect: (bytes) => JSON.parse(wasm.decodeToTransportJson(bytes)),
15
+ encodeBatchDirect: (values) => wasm.encodeBatchTransportJson(JSON.stringify(values)),
16
+ // WASM fallback: compact not available, fall through to transport JSON
17
+ encodeCompactJson: (json) => wasm.encodeTransportJson(json),
18
+ encodeBatchCompactJson: (json) => wasm.encodeBatchTransportJson(json),
19
+ createSessionEncoder: (optionsJson) => {
20
+ const inner = wasm.createSessionEncoder(optionsJson);
21
+ return wrapSessionEncoder(inner);
22
+ },
23
+ };
24
+ }
25
+ function wrapSessionEncoder(inner) {
26
+ return {
27
+ encodeTransportJson: (valueJson) => inner.encodeTransportJson(valueJson),
28
+ encodeWithSchemaTransportJson: (schemaJson, valueJson) => inner.encodeWithSchemaTransportJson(schemaJson, valueJson),
29
+ encodeBatchTransportJson: (valuesJson) => inner.encodeBatchTransportJson(valuesJson),
30
+ encodePatchTransportJson: (valueJson) => inner.encodePatchTransportJson(valueJson),
31
+ encodeMicroBatchTransportJson: (valuesJson) => inner.encodeMicroBatchTransportJson(valuesJson),
32
+ // WASM fallback: serialize to JSON then use JSON API
33
+ encodeDirect: (value) => inner.encodeTransportJson(JSON.stringify(value)),
34
+ encodeBatchDirect: (values) => inner.encodeBatchTransportJson(JSON.stringify(values)),
35
+ encodePatchDirect: (value) => inner.encodePatchTransportJson(JSON.stringify(value)),
36
+ encodeMicroBatchDirect: (values) => inner.encodeMicroBatchTransportJson(JSON.stringify(values)),
37
+ // WASM fallback: compact not available, fall through to transport JSON
38
+ encodeCompactJson: (json) => inner.encodeTransportJson(json),
39
+ encodeBatchCompactJson: (json) => inner.encodeBatchTransportJson(json),
40
+ encodePatchCompactJson: (json) => inner.encodePatchTransportJson(json),
41
+ encodeMicroBatchCompactJson: (json) => inner.encodeMicroBatchTransportJson(json),
42
+ reset: () => inner.reset(),
43
+ };
44
+ }
@@ -0,0 +1,39 @@
1
+ import type { RecurramValue, Schema, SessionOptions } from "./types.js";
2
+ export type TransportValue = {
3
+ t: "null";
4
+ } | {
5
+ t: "bool";
6
+ v: boolean;
7
+ } | {
8
+ t: "i64";
9
+ v: string;
10
+ } | {
11
+ t: "u64";
12
+ v: string;
13
+ } | {
14
+ t: "f64";
15
+ v: number;
16
+ } | {
17
+ t: "string";
18
+ v: string;
19
+ } | {
20
+ t: "binary";
21
+ v: string;
22
+ } | {
23
+ t: "array";
24
+ v: TransportValue[];
25
+ } | {
26
+ t: "map";
27
+ v: Array<[string, TransportValue]>;
28
+ };
29
+ export declare function serializeValue(value: RecurramValue): string;
30
+ export declare function deserializeValue(json: string): RecurramValue;
31
+ export declare function serializeValues(values: RecurramValue[]): string;
32
+ export declare function serializeSchema(schema: Schema): string;
33
+ export declare function serializeSessionOptions(options?: SessionOptions): string;
34
+ export declare function toTransportValue(value: RecurramValue): TransportValue;
35
+ export declare function toTransportValues(values: RecurramValue[]): TransportValue[];
36
+ export declare function fromTransportValue(value: TransportValue): RecurramValue;
37
+ export declare function serializeCompact(value: RecurramValue): string;
38
+ export declare function deserializeCompact(json: string): RecurramValue;
39
+ export declare function serializeCompactBatch(values: RecurramValue[]): string;
@@ -0,0 +1,341 @@
1
+ const MAX_U64 = (1n << 64n) - 1n;
2
+ const MIN_I64 = -(1n << 63n);
3
+ const MAX_I64 = (1n << 63n) - 1n;
4
+ export function serializeValue(value) {
5
+ return JSON.stringify(toTransportValue(value));
6
+ }
7
+ export function deserializeValue(json) {
8
+ const parsed = JSON.parse(json);
9
+ return fromTransportValue(parsed);
10
+ }
11
+ export function serializeValues(values) {
12
+ const out = new Array(values.length);
13
+ for (let index = 0; index < values.length; index += 1) {
14
+ out[index] = toTransportValue(values[index]);
15
+ }
16
+ return JSON.stringify(out);
17
+ }
18
+ export function serializeSchema(schema) {
19
+ const fields = new Array(schema.fields.length);
20
+ for (let index = 0; index < schema.fields.length; index += 1) {
21
+ const field = schema.fields[index];
22
+ const out = {
23
+ number: toTransportInteger(field.number, "field.number", true),
24
+ name: field.name,
25
+ logicalType: field.logicalType,
26
+ required: field.required,
27
+ };
28
+ if (field.defaultValue !== undefined) {
29
+ out.defaultValue = toTransportValue(field.defaultValue);
30
+ }
31
+ if (field.min !== undefined) {
32
+ out.min = toTransportInteger(field.min, "field.min", false);
33
+ }
34
+ if (field.max !== undefined) {
35
+ out.max = toTransportInteger(field.max, "field.max", false);
36
+ }
37
+ if (field.enumValues !== undefined) {
38
+ out.enumValues = field.enumValues;
39
+ }
40
+ fields[index] = out;
41
+ }
42
+ const payload = {
43
+ schemaId: toTransportInteger(schema.schemaId, "schemaId", true),
44
+ name: schema.name,
45
+ fields,
46
+ };
47
+ return JSON.stringify(payload);
48
+ }
49
+ export function serializeSessionOptions(options = {}) {
50
+ const payload = {};
51
+ if (options.maxBaseSnapshots !== undefined) {
52
+ if (!Number.isInteger(options.maxBaseSnapshots) ||
53
+ options.maxBaseSnapshots < 0) {
54
+ throw new Error("maxBaseSnapshots must be a non-negative integer");
55
+ }
56
+ payload.maxBaseSnapshots = options.maxBaseSnapshots;
57
+ }
58
+ if (options.enableStatePatch !== undefined) {
59
+ payload.enableStatePatch = options.enableStatePatch;
60
+ }
61
+ if (options.enableTemplateBatch !== undefined) {
62
+ payload.enableTemplateBatch = options.enableTemplateBatch;
63
+ }
64
+ if (options.enableTrainedDictionary !== undefined) {
65
+ payload.enableTrainedDictionary = options.enableTrainedDictionary;
66
+ }
67
+ if (options.unknownReferencePolicy !== undefined) {
68
+ payload.unknownReferencePolicy = options.unknownReferencePolicy;
69
+ }
70
+ return JSON.stringify(payload);
71
+ }
72
+ export function toTransportValue(value) {
73
+ if (value === null) {
74
+ return { t: "null" };
75
+ }
76
+ if (typeof value === "boolean") {
77
+ return { t: "bool", v: value };
78
+ }
79
+ if (typeof value === "number") {
80
+ if (!Number.isFinite(value)) {
81
+ throw new Error("number values must be finite");
82
+ }
83
+ if (Number.isInteger(value)) {
84
+ if (!Number.isSafeInteger(value)) {
85
+ throw new Error("unsafe integer number detected; use bigint for 64-bit integers");
86
+ }
87
+ return value >= 0
88
+ ? { t: "u64", v: String(value) }
89
+ : { t: "i64", v: String(value) };
90
+ }
91
+ return { t: "f64", v: value };
92
+ }
93
+ if (typeof value === "bigint") {
94
+ if (value >= 0n) {
95
+ if (value > MAX_U64) {
96
+ throw new Error("u64 overflow");
97
+ }
98
+ return { t: "u64", v: value.toString() };
99
+ }
100
+ if (value < MIN_I64 || value > MAX_I64) {
101
+ throw new Error("i64 overflow");
102
+ }
103
+ return { t: "i64", v: value.toString() };
104
+ }
105
+ if (typeof value === "string") {
106
+ return { t: "string", v: value };
107
+ }
108
+ if (value instanceof Uint8Array) {
109
+ return { t: "binary", v: toBase64(value) };
110
+ }
111
+ if (Array.isArray(value)) {
112
+ const length = value.length;
113
+ const out = new Array(length);
114
+ for (let index = 0; index < length; index += 1) {
115
+ out[index] = toTransportValue(value[index]);
116
+ }
117
+ return { t: "array", v: out };
118
+ }
119
+ if (typeof value !== "object" || value === null) {
120
+ throw new Error("unsupported value type");
121
+ }
122
+ const prototype = Object.getPrototypeOf(value);
123
+ if (prototype !== Object.prototype && prototype !== null) {
124
+ throw new Error("unsupported value type");
125
+ }
126
+ const entries = [];
127
+ const objectValue = value;
128
+ for (const key in objectValue) {
129
+ entries.push([key, toTransportValue(objectValue[key])]);
130
+ }
131
+ return { t: "map", v: entries };
132
+ }
133
+ export function toTransportValues(values) {
134
+ const out = new Array(values.length);
135
+ for (let index = 0; index < values.length; index += 1) {
136
+ out[index] = toTransportValue(values[index]);
137
+ }
138
+ return out;
139
+ }
140
+ export function fromTransportValue(value) {
141
+ switch (value.t) {
142
+ case "null":
143
+ return null;
144
+ case "bool":
145
+ return value.v;
146
+ case "i64":
147
+ return BigInt(value.v);
148
+ case "u64":
149
+ return BigInt(value.v);
150
+ case "f64":
151
+ return value.v;
152
+ case "string":
153
+ return value.v;
154
+ case "binary":
155
+ return fromBase64(value.v);
156
+ case "array": {
157
+ const length = value.v.length;
158
+ const out = new Array(length);
159
+ for (let index = 0; index < length; index += 1) {
160
+ out[index] = fromTransportValue(value.v[index]);
161
+ }
162
+ return out;
163
+ }
164
+ case "map": {
165
+ const out = {};
166
+ const length = value.v.length;
167
+ for (let index = 0; index < length; index += 1) {
168
+ const entry = value.v[index];
169
+ out[entry[0]] = fromTransportValue(entry[1]);
170
+ }
171
+ return out;
172
+ }
173
+ default:
174
+ throw new Error("unknown transport value kind");
175
+ }
176
+ }
177
+ function toTransportInteger(value, fieldName, unsigned) {
178
+ if (typeof value === "number") {
179
+ if (!Number.isInteger(value) || !Number.isSafeInteger(value)) {
180
+ throw new Error(`${fieldName} must be a safe integer number or bigint`);
181
+ }
182
+ if (unsigned && value < 0) {
183
+ throw new Error(`${fieldName} must be unsigned`);
184
+ }
185
+ return value;
186
+ }
187
+ if (unsigned) {
188
+ if (value < 0n || value > MAX_U64) {
189
+ throw new Error(`${fieldName} must fit u64`);
190
+ }
191
+ return value.toString();
192
+ }
193
+ if (value < MIN_I64 || value > MAX_I64) {
194
+ throw new Error(`${fieldName} must fit i64`);
195
+ }
196
+ return value.toString();
197
+ }
198
+ function toBase64(bytes) {
199
+ if (typeof Buffer !== "undefined") {
200
+ return Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength).toString("base64");
201
+ }
202
+ if (typeof btoa === "function") {
203
+ let binary = "";
204
+ for (let index = 0; index < bytes.length; index += 1) {
205
+ binary += String.fromCharCode(bytes[index]);
206
+ }
207
+ return btoa(binary);
208
+ }
209
+ throw new Error("base64 encoding is not available in this runtime");
210
+ }
211
+ function fromBase64(encoded) {
212
+ if (typeof Buffer !== "undefined") {
213
+ return Buffer.from(encoded, "base64");
214
+ }
215
+ if (typeof atob === "function") {
216
+ const binary = atob(encoded);
217
+ const bytes = new Uint8Array(binary.length);
218
+ for (let index = 0; index < binary.length; index += 1) {
219
+ bytes[index] = binary.charCodeAt(index);
220
+ }
221
+ return bytes;
222
+ }
223
+ throw new Error("base64 decoding is not available in this runtime");
224
+ }
225
+ export function serializeCompact(value) {
226
+ return JSON.stringify(toCompactValue(value));
227
+ }
228
+ export function deserializeCompact(json) {
229
+ const parsed = JSON.parse(json);
230
+ return fromCompactValue(parsed);
231
+ }
232
+ export function serializeCompactBatch(values) {
233
+ const out = new Array(values.length);
234
+ for (let index = 0; index < values.length; index += 1) {
235
+ out[index] = toCompactValue(values[index]);
236
+ }
237
+ return JSON.stringify(out);
238
+ }
239
+ function toCompactValue(value) {
240
+ if (value === null) {
241
+ return [0];
242
+ }
243
+ if (typeof value === "boolean") {
244
+ return [1, value];
245
+ }
246
+ if (typeof value === "number") {
247
+ if (!Number.isFinite(value)) {
248
+ throw new Error("number values must be finite");
249
+ }
250
+ if (Number.isInteger(value)) {
251
+ if (!Number.isSafeInteger(value)) {
252
+ throw new Error("unsafe integer number detected; use bigint for 64-bit integers");
253
+ }
254
+ return value >= 0 ? [3, String(value)] : [2, String(value)];
255
+ }
256
+ return [4, value];
257
+ }
258
+ if (typeof value === "bigint") {
259
+ if (value >= 0n) {
260
+ if (value > MAX_U64) {
261
+ throw new Error("u64 overflow");
262
+ }
263
+ return [3, value.toString()];
264
+ }
265
+ if (value < MIN_I64 || value > MAX_I64) {
266
+ throw new Error("i64 overflow");
267
+ }
268
+ return [2, value.toString()];
269
+ }
270
+ if (typeof value === "string") {
271
+ return [5, value];
272
+ }
273
+ if (value instanceof Uint8Array) {
274
+ return [6, toBase64(value)];
275
+ }
276
+ if (Array.isArray(value)) {
277
+ const length = value.length;
278
+ const out = new Array(length);
279
+ for (let index = 0; index < length; index += 1) {
280
+ out[index] = toCompactValue(value[index]);
281
+ }
282
+ return [7, out];
283
+ }
284
+ if (typeof value !== "object" || value === null) {
285
+ throw new Error("unsupported value type");
286
+ }
287
+ const prototype = Object.getPrototypeOf(value);
288
+ if (prototype !== Object.prototype && prototype !== null) {
289
+ throw new Error("unsupported value type");
290
+ }
291
+ // Map: flat array [key1, val1, key2, val2, ...]
292
+ const objectValue = value;
293
+ const keys = Object.keys(objectValue);
294
+ const flat = new Array(keys.length * 2);
295
+ for (let index = 0; index < keys.length; index += 1) {
296
+ flat[index * 2] = keys[index];
297
+ flat[index * 2 + 1] = toCompactValue(objectValue[keys[index]]);
298
+ }
299
+ return [8, flat];
300
+ }
301
+ function fromCompactValue(cv) {
302
+ const tag = cv[0];
303
+ switch (tag) {
304
+ case 0: // null
305
+ return null;
306
+ case 1: // bool
307
+ return cv[1];
308
+ case 2: // i64
309
+ return BigInt(cv[1]);
310
+ case 3: // u64
311
+ return BigInt(cv[1]);
312
+ case 4: // f64
313
+ return cv[1];
314
+ case 5: // string
315
+ return cv[1];
316
+ case 6: // binary
317
+ return fromBase64(cv[1]);
318
+ case 7: {
319
+ // array
320
+ const items = cv[1];
321
+ const length = items.length;
322
+ const out = new Array(length);
323
+ for (let index = 0; index < length; index += 1) {
324
+ out[index] = fromCompactValue(items[index]);
325
+ }
326
+ return out;
327
+ }
328
+ case 8: {
329
+ // map: flat array [key1, val1, key2, val2, ...]
330
+ const flat = cv[1];
331
+ const out = {};
332
+ const length = flat.length;
333
+ for (let index = 0; index < length; index += 2) {
334
+ out[flat[index]] = fromCompactValue(flat[index + 1]);
335
+ }
336
+ return out;
337
+ }
338
+ default:
339
+ throw new Error(`unknown compact tag: ${tag}`);
340
+ }
341
+ }
@@ -0,0 +1,30 @@
1
+ export type RecurramValue = null | boolean | number | bigint | string | Uint8Array | RecurramValue[] | {
2
+ [key: string]: RecurramValue;
3
+ };
4
+ export interface SchemaField {
5
+ number: number | bigint;
6
+ name: string;
7
+ logicalType: string;
8
+ required: boolean;
9
+ defaultValue?: RecurramValue;
10
+ min?: number | bigint;
11
+ max?: number | bigint;
12
+ enumValues?: string[];
13
+ }
14
+ export interface Schema {
15
+ schemaId: number | bigint;
16
+ name: string;
17
+ fields: SchemaField[];
18
+ }
19
+ export type UnknownReferencePolicy = "failFast" | "statelessRetry";
20
+ export interface SessionOptions {
21
+ maxBaseSnapshots?: number;
22
+ enableStatePatch?: boolean;
23
+ enableTemplateBatch?: boolean;
24
+ enableTrainedDictionary?: boolean;
25
+ unknownReferencePolicy?: UnknownReferencePolicy;
26
+ }
27
+ export interface InitOptions {
28
+ prefer?: "napi" | "wasm";
29
+ wasmInput?: unknown;
30
+ }
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
Binary file
package/package.json ADDED
@@ -0,0 +1,65 @@
1
+ {
2
+ "name": "recurram",
3
+ "version": "0.1.0",
4
+ "description": "Node.js and TypeScript implementation of a fast, compact binary wire format for modern data transport.",
5
+ "type": "module",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/recurram/recurram-js.git"
9
+ },
10
+ "homepage": "https://github.com/recurram/recurram-js#readme",
11
+ "bugs": {
12
+ "url": "https://github.com/recurram/recurram-js/issues"
13
+ },
14
+ "main": "./dist/index.js",
15
+ "types": "./dist/index.d.ts",
16
+ "exports": {
17
+ ".": {
18
+ "types": "./dist/index.d.ts",
19
+ "import": "./dist/index.js"
20
+ }
21
+ },
22
+ "files": [
23
+ "dist",
24
+ "native",
25
+ "wasm/pkg",
26
+ "crates",
27
+ "scripts",
28
+ "README.md",
29
+ "LICENSE"
30
+ ],
31
+ "keywords": [
32
+ "recurram",
33
+ "rust",
34
+ "napi",
35
+ "wasm",
36
+ "typescript"
37
+ ],
38
+ "license": "MIT",
39
+ "publishConfig": {
40
+ "access": "public"
41
+ },
42
+ "engines": {
43
+ "node": ">=24"
44
+ },
45
+ "devDependencies": {
46
+ "@types/node": "^24.1.0",
47
+ "prettier": "^3.6.2",
48
+ "rimraf": "^6.0.1",
49
+ "typescript": "^5.8.2"
50
+ },
51
+ "scripts": {
52
+ "build": "pnpm build:napi && pnpm build:wasm && pnpm build:ts",
53
+ "build:ts": "tsc -p tsconfig.json",
54
+ "build:napi": "cargo build --manifest-path Cargo.toml -p recurram-napi --release && node ./scripts/copy-napi.mjs",
55
+ "build:wasm": "wasm-pack build crates/recurram-wasm --target bundler --out-dir ../../wasm/pkg --out-name recurram_wasm --no-opt",
56
+ "format": "prettier --write \"{README.md,package.json,tsconfig.json,.github/workflows/*.yml,scripts/**/*.mjs,src/**/*.ts,tests/**/*.mjs}\"",
57
+ "format:check": "prettier --check \"{README.md,package.json,tsconfig.json,.github/workflows/*.yml,scripts/**/*.mjs,src/**/*.ts,tests/**/*.mjs}\"",
58
+ "typecheck": "tsc --noEmit",
59
+ "test:rust": "cargo test --manifest-path Cargo.toml -p recurram-bridge",
60
+ "test:node": "pnpm build:napi && pnpm build:ts && node --test --test-concurrency=1 \"tests/**/*.test.mjs\"",
61
+ "test": "pnpm test:rust && pnpm test:node",
62
+ "release:check": "node ./scripts/verify-tag-version.mjs",
63
+ "clean": "rimraf dist native wasm target"
64
+ }
65
+ }
@@ -0,0 +1,27 @@
1
+ import { mkdir, copyFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+
5
+ const __filename = fileURLToPath(import.meta.url);
6
+ const __dirname = path.dirname(__filename);
7
+ const root = path.resolve(__dirname, "..");
8
+ const targetDir = path.join(root, "native");
9
+ const targetFile = path.join(targetDir, "recurram_napi.node");
10
+
11
+ const sourceFile = resolveSourceBinary(path.join(root, "target", "release"));
12
+
13
+ await mkdir(targetDir, { recursive: true });
14
+ await copyFile(sourceFile, targetFile);
15
+
16
+ function resolveSourceBinary(releaseDir) {
17
+ if (process.platform === "darwin") {
18
+ return path.join(releaseDir, "librecurram_napi.dylib");
19
+ }
20
+ if (process.platform === "linux") {
21
+ return path.join(releaseDir, "librecurram_napi.so");
22
+ }
23
+ if (process.platform === "win32") {
24
+ return path.join(releaseDir, "recurram_napi.dll");
25
+ }
26
+ throw new Error(`unsupported platform: ${process.platform}`);
27
+ }
@@ -0,0 +1,20 @@
1
+ import { readFileSync } from "node:fs";
2
+ import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+
5
+ const __filename = fileURLToPath(import.meta.url);
6
+ const __dirname = path.dirname(__filename);
7
+ const packageJsonPath = path.resolve(__dirname, "..", "package.json");
8
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
9
+
10
+ const tag = process.env.GITHUB_REF_NAME || process.argv[2];
11
+ if (!tag) {
12
+ throw new Error("tag is required (set GITHUB_REF_NAME or pass tag as arg)");
13
+ }
14
+
15
+ const normalizedTag = tag.startsWith("v") ? tag.slice(1) : tag;
16
+ if (normalizedTag !== packageJson.version) {
17
+ throw new Error(
18
+ `tag/version mismatch: tag=${tag} package.json version=${packageJson.version}`,
19
+ );
20
+ }