@absurd-sqlite/bun-worker 0.2.1 → 0.2.2-alpha.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/bun.lock +5 -2
- package/dist/index.d.ts +6 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +808 -7
- package/dist/sqlite.d.ts +3 -0
- package/dist/sqlite.d.ts.map +1 -1
- package/package.json +5 -4
- package/src/index.ts +102 -9
- package/src/sqlite.ts +52 -3
- package/test/cli.test.ts +83 -0
- package/test/run.test.ts +2 -2
- package/test/sqlite.test.ts +71 -1
package/bun.lock
CHANGED
|
@@ -5,8 +5,9 @@
|
|
|
5
5
|
"": {
|
|
6
6
|
"name": "@absurd-sqlite/bun-worker",
|
|
7
7
|
"dependencies": {
|
|
8
|
-
"@absurd-sqlite/sdk": "
|
|
8
|
+
"@absurd-sqlite/sdk": "next",
|
|
9
9
|
"absurd-sdk": "~0.0.7",
|
|
10
|
+
"cac": "^6.7.14",
|
|
10
11
|
},
|
|
11
12
|
"devDependencies": {
|
|
12
13
|
"bun-types": "^1.3.5",
|
|
@@ -15,7 +16,7 @@
|
|
|
15
16
|
},
|
|
16
17
|
},
|
|
17
18
|
"packages": {
|
|
18
|
-
"@absurd-sqlite/sdk": ["@absurd-sqlite/sdk@0.2.
|
|
19
|
+
"@absurd-sqlite/sdk": ["@absurd-sqlite/sdk@0.2.1-alpha.1", "", { "dependencies": { "absurd-sdk": "~0.0.7" }, "peerDependencies": { "better-sqlite3": "^12.5.0" } }, "sha512-gIly9R1ZO/9Ja/dDbVupxs5Yd4AXx+iDz5PIsq0qg/GSCCIujxt8VXI4c+BvkbvEY1ew7mpvmZa/ttMkKopFrA=="],
|
|
19
20
|
|
|
20
21
|
"@types/node": ["@types/node@25.0.3", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA=="],
|
|
21
22
|
|
|
@@ -33,6 +34,8 @@
|
|
|
33
34
|
|
|
34
35
|
"bun-types": ["bun-types@1.3.5", "", { "dependencies": { "@types/node": "*" } }, "sha512-inmAYe2PFLs0SUbFOWSVD24sg1jFlMPxOjOSSCYqUgn4Hsc3rDc7dFvfVYjFPNHtov6kgUeulV4SxbuIV/stPw=="],
|
|
35
36
|
|
|
37
|
+
"cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="],
|
|
38
|
+
|
|
36
39
|
"chownr": ["chownr@1.1.4", "", {}, "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="],
|
|
37
40
|
|
|
38
41
|
"decompress-response": ["decompress-response@6.0.0", "", { "dependencies": { "mimic-response": "^3.1.0" } }, "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ=="],
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { AbsurdClient } from "@absurd-sqlite/sdk";
|
|
2
2
|
export type { AbsurdClient } from "@absurd-sqlite/sdk";
|
|
3
|
+
export type { WorkerOptions } from "absurd-sdk";
|
|
4
|
+
export { downloadExtension, type DownloadExtensionOptions, } from "@absurd-sqlite/sdk";
|
|
3
5
|
/**
|
|
4
6
|
* Register tasks and perform any one-time setup before the worker starts.
|
|
5
7
|
*/
|
|
@@ -7,9 +9,10 @@ export type SetupFunction = (absurd: AbsurdClient) => void | Promise<void>;
|
|
|
7
9
|
/**
|
|
8
10
|
* Boots a worker using Bun's SQLite driver and Absurd's task engine.
|
|
9
11
|
*
|
|
10
|
-
*
|
|
11
|
-
* -
|
|
12
|
-
* -
|
|
12
|
+
* CLI flags:
|
|
13
|
+
* - --concurrency, -c: Number of tasks to process concurrently (default: 10)
|
|
14
|
+
* - --database-path: SQLite database file path (overrides ABSURD_DATABASE_PATH)
|
|
15
|
+
* - --extension-path: Absurd-SQLite extension path (overrides ABSURD_DATABASE_EXTENSION_PATH)
|
|
13
16
|
*/
|
|
14
17
|
export default function run(setupFunction: SetupFunction): Promise<void>;
|
|
15
18
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAKvD,YAAY,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACvD,YAAY,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD,OAAO,EACL,iBAAiB,EACjB,KAAK,wBAAwB,GAC9B,MAAM,oBAAoB,CAAC;AAE5B;;GAEG;AACH,MAAM,MAAM,aAAa,GAAG,CAAC,MAAM,EAAE,YAAY,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AA0D3E;;;;;;;GAOG;AACH,wBAA8B,GAAG,CAAC,aAAa,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CA6D7E"}
|
package/dist/index.js
CHANGED
|
@@ -5305,9 +5305,612 @@ function normalizeCancellation(policy) {
|
|
|
5305
5305
|
// src/index.ts
|
|
5306
5306
|
import { Database } from "bun:sqlite";
|
|
5307
5307
|
|
|
5308
|
+
// node_modules/cac/dist/index.mjs
|
|
5309
|
+
import { EventEmitter } from "events";
|
|
5310
|
+
function toArr(any) {
|
|
5311
|
+
return any == null ? [] : Array.isArray(any) ? any : [any];
|
|
5312
|
+
}
|
|
5313
|
+
function toVal(out, key, val, opts) {
|
|
5314
|
+
var x, old = out[key], nxt = ~opts.string.indexOf(key) ? val == null || val === true ? "" : String(val) : typeof val === "boolean" ? val : ~opts.boolean.indexOf(key) ? val === "false" ? false : val === "true" || (out._.push((x = +val, x * 0 === 0) ? x : val), !!val) : (x = +val, x * 0 === 0) ? x : val;
|
|
5315
|
+
out[key] = old == null ? nxt : Array.isArray(old) ? old.concat(nxt) : [old, nxt];
|
|
5316
|
+
}
|
|
5317
|
+
function mri2(args, opts) {
|
|
5318
|
+
args = args || [];
|
|
5319
|
+
opts = opts || {};
|
|
5320
|
+
var k, arr, arg, name, val, out = { _: [] };
|
|
5321
|
+
var i = 0, j = 0, idx = 0, len = args.length;
|
|
5322
|
+
const alibi = opts.alias !== undefined;
|
|
5323
|
+
const strict = opts.unknown !== undefined;
|
|
5324
|
+
const defaults2 = opts.default !== undefined;
|
|
5325
|
+
opts.alias = opts.alias || {};
|
|
5326
|
+
opts.string = toArr(opts.string);
|
|
5327
|
+
opts.boolean = toArr(opts.boolean);
|
|
5328
|
+
if (alibi) {
|
|
5329
|
+
for (k in opts.alias) {
|
|
5330
|
+
arr = opts.alias[k] = toArr(opts.alias[k]);
|
|
5331
|
+
for (i = 0;i < arr.length; i++) {
|
|
5332
|
+
(opts.alias[arr[i]] = arr.concat(k)).splice(i, 1);
|
|
5333
|
+
}
|
|
5334
|
+
}
|
|
5335
|
+
}
|
|
5336
|
+
for (i = opts.boolean.length;i-- > 0; ) {
|
|
5337
|
+
arr = opts.alias[opts.boolean[i]] || [];
|
|
5338
|
+
for (j = arr.length;j-- > 0; )
|
|
5339
|
+
opts.boolean.push(arr[j]);
|
|
5340
|
+
}
|
|
5341
|
+
for (i = opts.string.length;i-- > 0; ) {
|
|
5342
|
+
arr = opts.alias[opts.string[i]] || [];
|
|
5343
|
+
for (j = arr.length;j-- > 0; )
|
|
5344
|
+
opts.string.push(arr[j]);
|
|
5345
|
+
}
|
|
5346
|
+
if (defaults2) {
|
|
5347
|
+
for (k in opts.default) {
|
|
5348
|
+
name = typeof opts.default[k];
|
|
5349
|
+
arr = opts.alias[k] = opts.alias[k] || [];
|
|
5350
|
+
if (opts[name] !== undefined) {
|
|
5351
|
+
opts[name].push(k);
|
|
5352
|
+
for (i = 0;i < arr.length; i++) {
|
|
5353
|
+
opts[name].push(arr[i]);
|
|
5354
|
+
}
|
|
5355
|
+
}
|
|
5356
|
+
}
|
|
5357
|
+
}
|
|
5358
|
+
const keys = strict ? Object.keys(opts.alias) : [];
|
|
5359
|
+
for (i = 0;i < len; i++) {
|
|
5360
|
+
arg = args[i];
|
|
5361
|
+
if (arg === "--") {
|
|
5362
|
+
out._ = out._.concat(args.slice(++i));
|
|
5363
|
+
break;
|
|
5364
|
+
}
|
|
5365
|
+
for (j = 0;j < arg.length; j++) {
|
|
5366
|
+
if (arg.charCodeAt(j) !== 45)
|
|
5367
|
+
break;
|
|
5368
|
+
}
|
|
5369
|
+
if (j === 0) {
|
|
5370
|
+
out._.push(arg);
|
|
5371
|
+
} else if (arg.substring(j, j + 3) === "no-") {
|
|
5372
|
+
name = arg.substring(j + 3);
|
|
5373
|
+
if (strict && !~keys.indexOf(name)) {
|
|
5374
|
+
return opts.unknown(arg);
|
|
5375
|
+
}
|
|
5376
|
+
out[name] = false;
|
|
5377
|
+
} else {
|
|
5378
|
+
for (idx = j + 1;idx < arg.length; idx++) {
|
|
5379
|
+
if (arg.charCodeAt(idx) === 61)
|
|
5380
|
+
break;
|
|
5381
|
+
}
|
|
5382
|
+
name = arg.substring(j, idx);
|
|
5383
|
+
val = arg.substring(++idx) || (i + 1 === len || ("" + args[i + 1]).charCodeAt(0) === 45 || args[++i]);
|
|
5384
|
+
arr = j === 2 ? [name] : name;
|
|
5385
|
+
for (idx = 0;idx < arr.length; idx++) {
|
|
5386
|
+
name = arr[idx];
|
|
5387
|
+
if (strict && !~keys.indexOf(name))
|
|
5388
|
+
return opts.unknown("-".repeat(j) + name);
|
|
5389
|
+
toVal(out, name, idx + 1 < arr.length || val, opts);
|
|
5390
|
+
}
|
|
5391
|
+
}
|
|
5392
|
+
}
|
|
5393
|
+
if (defaults2) {
|
|
5394
|
+
for (k in opts.default) {
|
|
5395
|
+
if (out[k] === undefined) {
|
|
5396
|
+
out[k] = opts.default[k];
|
|
5397
|
+
}
|
|
5398
|
+
}
|
|
5399
|
+
}
|
|
5400
|
+
if (alibi) {
|
|
5401
|
+
for (k in out) {
|
|
5402
|
+
arr = opts.alias[k] || [];
|
|
5403
|
+
while (arr.length > 0) {
|
|
5404
|
+
out[arr.shift()] = out[k];
|
|
5405
|
+
}
|
|
5406
|
+
}
|
|
5407
|
+
}
|
|
5408
|
+
return out;
|
|
5409
|
+
}
|
|
5410
|
+
var removeBrackets = (v) => v.replace(/[<[].+/, "").trim();
|
|
5411
|
+
var findAllBrackets = (v) => {
|
|
5412
|
+
const ANGLED_BRACKET_RE_GLOBAL = /<([^>]+)>/g;
|
|
5413
|
+
const SQUARE_BRACKET_RE_GLOBAL = /\[([^\]]+)\]/g;
|
|
5414
|
+
const res = [];
|
|
5415
|
+
const parse = (match) => {
|
|
5416
|
+
let variadic = false;
|
|
5417
|
+
let value = match[1];
|
|
5418
|
+
if (value.startsWith("...")) {
|
|
5419
|
+
value = value.slice(3);
|
|
5420
|
+
variadic = true;
|
|
5421
|
+
}
|
|
5422
|
+
return {
|
|
5423
|
+
required: match[0].startsWith("<"),
|
|
5424
|
+
value,
|
|
5425
|
+
variadic
|
|
5426
|
+
};
|
|
5427
|
+
};
|
|
5428
|
+
let angledMatch;
|
|
5429
|
+
while (angledMatch = ANGLED_BRACKET_RE_GLOBAL.exec(v)) {
|
|
5430
|
+
res.push(parse(angledMatch));
|
|
5431
|
+
}
|
|
5432
|
+
let squareMatch;
|
|
5433
|
+
while (squareMatch = SQUARE_BRACKET_RE_GLOBAL.exec(v)) {
|
|
5434
|
+
res.push(parse(squareMatch));
|
|
5435
|
+
}
|
|
5436
|
+
return res;
|
|
5437
|
+
};
|
|
5438
|
+
var getMriOptions = (options) => {
|
|
5439
|
+
const result = { alias: {}, boolean: [] };
|
|
5440
|
+
for (const [index, option] of options.entries()) {
|
|
5441
|
+
if (option.names.length > 1) {
|
|
5442
|
+
result.alias[option.names[0]] = option.names.slice(1);
|
|
5443
|
+
}
|
|
5444
|
+
if (option.isBoolean) {
|
|
5445
|
+
if (option.negated) {
|
|
5446
|
+
const hasStringTypeOption = options.some((o, i) => {
|
|
5447
|
+
return i !== index && o.names.some((name) => option.names.includes(name)) && typeof o.required === "boolean";
|
|
5448
|
+
});
|
|
5449
|
+
if (!hasStringTypeOption) {
|
|
5450
|
+
result.boolean.push(option.names[0]);
|
|
5451
|
+
}
|
|
5452
|
+
} else {
|
|
5453
|
+
result.boolean.push(option.names[0]);
|
|
5454
|
+
}
|
|
5455
|
+
}
|
|
5456
|
+
}
|
|
5457
|
+
return result;
|
|
5458
|
+
};
|
|
5459
|
+
var findLongest = (arr) => {
|
|
5460
|
+
return arr.sort((a, b) => {
|
|
5461
|
+
return a.length > b.length ? -1 : 1;
|
|
5462
|
+
})[0];
|
|
5463
|
+
};
|
|
5464
|
+
var padRight = (str, length) => {
|
|
5465
|
+
return str.length >= length ? str : `${str}${" ".repeat(length - str.length)}`;
|
|
5466
|
+
};
|
|
5467
|
+
var camelcase = (input) => {
|
|
5468
|
+
return input.replace(/([a-z])-([a-z])/g, (_, p1, p2) => {
|
|
5469
|
+
return p1 + p2.toUpperCase();
|
|
5470
|
+
});
|
|
5471
|
+
};
|
|
5472
|
+
var setDotProp = (obj, keys, val) => {
|
|
5473
|
+
let i = 0;
|
|
5474
|
+
let length = keys.length;
|
|
5475
|
+
let t = obj;
|
|
5476
|
+
let x;
|
|
5477
|
+
for (;i < length; ++i) {
|
|
5478
|
+
x = t[keys[i]];
|
|
5479
|
+
t = t[keys[i]] = i === length - 1 ? val : x != null ? x : !!~keys[i + 1].indexOf(".") || !(+keys[i + 1] > -1) ? {} : [];
|
|
5480
|
+
}
|
|
5481
|
+
};
|
|
5482
|
+
var setByType = (obj, transforms) => {
|
|
5483
|
+
for (const key of Object.keys(transforms)) {
|
|
5484
|
+
const transform = transforms[key];
|
|
5485
|
+
if (transform.shouldTransform) {
|
|
5486
|
+
obj[key] = Array.prototype.concat.call([], obj[key]);
|
|
5487
|
+
if (typeof transform.transformFunction === "function") {
|
|
5488
|
+
obj[key] = obj[key].map(transform.transformFunction);
|
|
5489
|
+
}
|
|
5490
|
+
}
|
|
5491
|
+
}
|
|
5492
|
+
};
|
|
5493
|
+
var getFileName = (input) => {
|
|
5494
|
+
const m = /([^\\\/]+)$/.exec(input);
|
|
5495
|
+
return m ? m[1] : "";
|
|
5496
|
+
};
|
|
5497
|
+
var camelcaseOptionName = (name) => {
|
|
5498
|
+
return name.split(".").map((v, i) => {
|
|
5499
|
+
return i === 0 ? camelcase(v) : v;
|
|
5500
|
+
}).join(".");
|
|
5501
|
+
};
|
|
5502
|
+
|
|
5503
|
+
class CACError extends Error {
|
|
5504
|
+
constructor(message) {
|
|
5505
|
+
super(message);
|
|
5506
|
+
this.name = this.constructor.name;
|
|
5507
|
+
if (typeof Error.captureStackTrace === "function") {
|
|
5508
|
+
Error.captureStackTrace(this, this.constructor);
|
|
5509
|
+
} else {
|
|
5510
|
+
this.stack = new Error(message).stack;
|
|
5511
|
+
}
|
|
5512
|
+
}
|
|
5513
|
+
}
|
|
5514
|
+
|
|
5515
|
+
class Option {
|
|
5516
|
+
constructor(rawName, description, config) {
|
|
5517
|
+
this.rawName = rawName;
|
|
5518
|
+
this.description = description;
|
|
5519
|
+
this.config = Object.assign({}, config);
|
|
5520
|
+
rawName = rawName.replace(/\.\*/g, "");
|
|
5521
|
+
this.negated = false;
|
|
5522
|
+
this.names = removeBrackets(rawName).split(",").map((v) => {
|
|
5523
|
+
let name = v.trim().replace(/^-{1,2}/, "");
|
|
5524
|
+
if (name.startsWith("no-")) {
|
|
5525
|
+
this.negated = true;
|
|
5526
|
+
name = name.replace(/^no-/, "");
|
|
5527
|
+
}
|
|
5528
|
+
return camelcaseOptionName(name);
|
|
5529
|
+
}).sort((a, b) => a.length > b.length ? 1 : -1);
|
|
5530
|
+
this.name = this.names[this.names.length - 1];
|
|
5531
|
+
if (this.negated && this.config.default == null) {
|
|
5532
|
+
this.config.default = true;
|
|
5533
|
+
}
|
|
5534
|
+
if (rawName.includes("<")) {
|
|
5535
|
+
this.required = true;
|
|
5536
|
+
} else if (rawName.includes("[")) {
|
|
5537
|
+
this.required = false;
|
|
5538
|
+
} else {
|
|
5539
|
+
this.isBoolean = true;
|
|
5540
|
+
}
|
|
5541
|
+
}
|
|
5542
|
+
}
|
|
5543
|
+
var processArgs = process.argv;
|
|
5544
|
+
var platformInfo = `${process.platform}-${process.arch} node-${process.version}`;
|
|
5545
|
+
|
|
5546
|
+
class Command {
|
|
5547
|
+
constructor(rawName, description, config = {}, cli) {
|
|
5548
|
+
this.rawName = rawName;
|
|
5549
|
+
this.description = description;
|
|
5550
|
+
this.config = config;
|
|
5551
|
+
this.cli = cli;
|
|
5552
|
+
this.options = [];
|
|
5553
|
+
this.aliasNames = [];
|
|
5554
|
+
this.name = removeBrackets(rawName);
|
|
5555
|
+
this.args = findAllBrackets(rawName);
|
|
5556
|
+
this.examples = [];
|
|
5557
|
+
}
|
|
5558
|
+
usage(text) {
|
|
5559
|
+
this.usageText = text;
|
|
5560
|
+
return this;
|
|
5561
|
+
}
|
|
5562
|
+
allowUnknownOptions() {
|
|
5563
|
+
this.config.allowUnknownOptions = true;
|
|
5564
|
+
return this;
|
|
5565
|
+
}
|
|
5566
|
+
ignoreOptionDefaultValue() {
|
|
5567
|
+
this.config.ignoreOptionDefaultValue = true;
|
|
5568
|
+
return this;
|
|
5569
|
+
}
|
|
5570
|
+
version(version, customFlags = "-v, --version") {
|
|
5571
|
+
this.versionNumber = version;
|
|
5572
|
+
this.option(customFlags, "Display version number");
|
|
5573
|
+
return this;
|
|
5574
|
+
}
|
|
5575
|
+
example(example) {
|
|
5576
|
+
this.examples.push(example);
|
|
5577
|
+
return this;
|
|
5578
|
+
}
|
|
5579
|
+
option(rawName, description, config) {
|
|
5580
|
+
const option = new Option(rawName, description, config);
|
|
5581
|
+
this.options.push(option);
|
|
5582
|
+
return this;
|
|
5583
|
+
}
|
|
5584
|
+
alias(name) {
|
|
5585
|
+
this.aliasNames.push(name);
|
|
5586
|
+
return this;
|
|
5587
|
+
}
|
|
5588
|
+
action(callback) {
|
|
5589
|
+
this.commandAction = callback;
|
|
5590
|
+
return this;
|
|
5591
|
+
}
|
|
5592
|
+
isMatched(name) {
|
|
5593
|
+
return this.name === name || this.aliasNames.includes(name);
|
|
5594
|
+
}
|
|
5595
|
+
get isDefaultCommand() {
|
|
5596
|
+
return this.name === "" || this.aliasNames.includes("!");
|
|
5597
|
+
}
|
|
5598
|
+
get isGlobalCommand() {
|
|
5599
|
+
return this instanceof GlobalCommand;
|
|
5600
|
+
}
|
|
5601
|
+
hasOption(name) {
|
|
5602
|
+
name = name.split(".")[0];
|
|
5603
|
+
return this.options.find((option) => {
|
|
5604
|
+
return option.names.includes(name);
|
|
5605
|
+
});
|
|
5606
|
+
}
|
|
5607
|
+
outputHelp() {
|
|
5608
|
+
const { name, commands } = this.cli;
|
|
5609
|
+
const {
|
|
5610
|
+
versionNumber,
|
|
5611
|
+
options: globalOptions,
|
|
5612
|
+
helpCallback
|
|
5613
|
+
} = this.cli.globalCommand;
|
|
5614
|
+
let sections = [
|
|
5615
|
+
{
|
|
5616
|
+
body: `${name}${versionNumber ? `/${versionNumber}` : ""}`
|
|
5617
|
+
}
|
|
5618
|
+
];
|
|
5619
|
+
sections.push({
|
|
5620
|
+
title: "Usage",
|
|
5621
|
+
body: ` $ ${name} ${this.usageText || this.rawName}`
|
|
5622
|
+
});
|
|
5623
|
+
const showCommands = (this.isGlobalCommand || this.isDefaultCommand) && commands.length > 0;
|
|
5624
|
+
if (showCommands) {
|
|
5625
|
+
const longestCommandName = findLongest(commands.map((command) => command.rawName));
|
|
5626
|
+
sections.push({
|
|
5627
|
+
title: "Commands",
|
|
5628
|
+
body: commands.map((command) => {
|
|
5629
|
+
return ` ${padRight(command.rawName, longestCommandName.length)} ${command.description}`;
|
|
5630
|
+
}).join(`
|
|
5631
|
+
`)
|
|
5632
|
+
});
|
|
5633
|
+
sections.push({
|
|
5634
|
+
title: `For more info, run any command with the \`--help\` flag`,
|
|
5635
|
+
body: commands.map((command) => ` $ ${name}${command.name === "" ? "" : ` ${command.name}`} --help`).join(`
|
|
5636
|
+
`)
|
|
5637
|
+
});
|
|
5638
|
+
}
|
|
5639
|
+
let options = this.isGlobalCommand ? globalOptions : [...this.options, ...globalOptions || []];
|
|
5640
|
+
if (!this.isGlobalCommand && !this.isDefaultCommand) {
|
|
5641
|
+
options = options.filter((option) => option.name !== "version");
|
|
5642
|
+
}
|
|
5643
|
+
if (options.length > 0) {
|
|
5644
|
+
const longestOptionName = findLongest(options.map((option) => option.rawName));
|
|
5645
|
+
sections.push({
|
|
5646
|
+
title: "Options",
|
|
5647
|
+
body: options.map((option) => {
|
|
5648
|
+
return ` ${padRight(option.rawName, longestOptionName.length)} ${option.description} ${option.config.default === undefined ? "" : `(default: ${option.config.default})`}`;
|
|
5649
|
+
}).join(`
|
|
5650
|
+
`)
|
|
5651
|
+
});
|
|
5652
|
+
}
|
|
5653
|
+
if (this.examples.length > 0) {
|
|
5654
|
+
sections.push({
|
|
5655
|
+
title: "Examples",
|
|
5656
|
+
body: this.examples.map((example) => {
|
|
5657
|
+
if (typeof example === "function") {
|
|
5658
|
+
return example(name);
|
|
5659
|
+
}
|
|
5660
|
+
return example;
|
|
5661
|
+
}).join(`
|
|
5662
|
+
`)
|
|
5663
|
+
});
|
|
5664
|
+
}
|
|
5665
|
+
if (helpCallback) {
|
|
5666
|
+
sections = helpCallback(sections) || sections;
|
|
5667
|
+
}
|
|
5668
|
+
console.log(sections.map((section) => {
|
|
5669
|
+
return section.title ? `${section.title}:
|
|
5670
|
+
${section.body}` : section.body;
|
|
5671
|
+
}).join(`
|
|
5672
|
+
|
|
5673
|
+
`));
|
|
5674
|
+
}
|
|
5675
|
+
outputVersion() {
|
|
5676
|
+
const { name } = this.cli;
|
|
5677
|
+
const { versionNumber } = this.cli.globalCommand;
|
|
5678
|
+
if (versionNumber) {
|
|
5679
|
+
console.log(`${name}/${versionNumber} ${platformInfo}`);
|
|
5680
|
+
}
|
|
5681
|
+
}
|
|
5682
|
+
checkRequiredArgs() {
|
|
5683
|
+
const minimalArgsCount = this.args.filter((arg) => arg.required).length;
|
|
5684
|
+
if (this.cli.args.length < minimalArgsCount) {
|
|
5685
|
+
throw new CACError(`missing required args for command \`${this.rawName}\``);
|
|
5686
|
+
}
|
|
5687
|
+
}
|
|
5688
|
+
checkUnknownOptions() {
|
|
5689
|
+
const { options, globalCommand } = this.cli;
|
|
5690
|
+
if (!this.config.allowUnknownOptions) {
|
|
5691
|
+
for (const name of Object.keys(options)) {
|
|
5692
|
+
if (name !== "--" && !this.hasOption(name) && !globalCommand.hasOption(name)) {
|
|
5693
|
+
throw new CACError(`Unknown option \`${name.length > 1 ? `--${name}` : `-${name}`}\``);
|
|
5694
|
+
}
|
|
5695
|
+
}
|
|
5696
|
+
}
|
|
5697
|
+
}
|
|
5698
|
+
checkOptionValue() {
|
|
5699
|
+
const { options: parsedOptions, globalCommand } = this.cli;
|
|
5700
|
+
const options = [...globalCommand.options, ...this.options];
|
|
5701
|
+
for (const option of options) {
|
|
5702
|
+
const value = parsedOptions[option.name.split(".")[0]];
|
|
5703
|
+
if (option.required) {
|
|
5704
|
+
const hasNegated = options.some((o) => o.negated && o.names.includes(option.name));
|
|
5705
|
+
if (value === true || value === false && !hasNegated) {
|
|
5706
|
+
throw new CACError(`option \`${option.rawName}\` value is missing`);
|
|
5707
|
+
}
|
|
5708
|
+
}
|
|
5709
|
+
}
|
|
5710
|
+
}
|
|
5711
|
+
}
|
|
5712
|
+
|
|
5713
|
+
class GlobalCommand extends Command {
|
|
5714
|
+
constructor(cli) {
|
|
5715
|
+
super("@@global@@", "", {}, cli);
|
|
5716
|
+
}
|
|
5717
|
+
}
|
|
5718
|
+
var __assign = Object.assign;
|
|
5719
|
+
|
|
5720
|
+
class CAC extends EventEmitter {
|
|
5721
|
+
constructor(name = "") {
|
|
5722
|
+
super();
|
|
5723
|
+
this.name = name;
|
|
5724
|
+
this.commands = [];
|
|
5725
|
+
this.rawArgs = [];
|
|
5726
|
+
this.args = [];
|
|
5727
|
+
this.options = {};
|
|
5728
|
+
this.globalCommand = new GlobalCommand(this);
|
|
5729
|
+
this.globalCommand.usage("<command> [options]");
|
|
5730
|
+
}
|
|
5731
|
+
usage(text) {
|
|
5732
|
+
this.globalCommand.usage(text);
|
|
5733
|
+
return this;
|
|
5734
|
+
}
|
|
5735
|
+
command(rawName, description, config) {
|
|
5736
|
+
const command = new Command(rawName, description || "", config, this);
|
|
5737
|
+
command.globalCommand = this.globalCommand;
|
|
5738
|
+
this.commands.push(command);
|
|
5739
|
+
return command;
|
|
5740
|
+
}
|
|
5741
|
+
option(rawName, description, config) {
|
|
5742
|
+
this.globalCommand.option(rawName, description, config);
|
|
5743
|
+
return this;
|
|
5744
|
+
}
|
|
5745
|
+
help(callback) {
|
|
5746
|
+
this.globalCommand.option("-h, --help", "Display this message");
|
|
5747
|
+
this.globalCommand.helpCallback = callback;
|
|
5748
|
+
this.showHelpOnExit = true;
|
|
5749
|
+
return this;
|
|
5750
|
+
}
|
|
5751
|
+
version(version, customFlags = "-v, --version") {
|
|
5752
|
+
this.globalCommand.version(version, customFlags);
|
|
5753
|
+
this.showVersionOnExit = true;
|
|
5754
|
+
return this;
|
|
5755
|
+
}
|
|
5756
|
+
example(example) {
|
|
5757
|
+
this.globalCommand.example(example);
|
|
5758
|
+
return this;
|
|
5759
|
+
}
|
|
5760
|
+
outputHelp() {
|
|
5761
|
+
if (this.matchedCommand) {
|
|
5762
|
+
this.matchedCommand.outputHelp();
|
|
5763
|
+
} else {
|
|
5764
|
+
this.globalCommand.outputHelp();
|
|
5765
|
+
}
|
|
5766
|
+
}
|
|
5767
|
+
outputVersion() {
|
|
5768
|
+
this.globalCommand.outputVersion();
|
|
5769
|
+
}
|
|
5770
|
+
setParsedInfo({ args, options }, matchedCommand, matchedCommandName) {
|
|
5771
|
+
this.args = args;
|
|
5772
|
+
this.options = options;
|
|
5773
|
+
if (matchedCommand) {
|
|
5774
|
+
this.matchedCommand = matchedCommand;
|
|
5775
|
+
}
|
|
5776
|
+
if (matchedCommandName) {
|
|
5777
|
+
this.matchedCommandName = matchedCommandName;
|
|
5778
|
+
}
|
|
5779
|
+
return this;
|
|
5780
|
+
}
|
|
5781
|
+
unsetMatchedCommand() {
|
|
5782
|
+
this.matchedCommand = undefined;
|
|
5783
|
+
this.matchedCommandName = undefined;
|
|
5784
|
+
}
|
|
5785
|
+
parse(argv = processArgs, {
|
|
5786
|
+
run = true
|
|
5787
|
+
} = {}) {
|
|
5788
|
+
this.rawArgs = argv;
|
|
5789
|
+
if (!this.name) {
|
|
5790
|
+
this.name = argv[1] ? getFileName(argv[1]) : "cli";
|
|
5791
|
+
}
|
|
5792
|
+
let shouldParse = true;
|
|
5793
|
+
for (const command of this.commands) {
|
|
5794
|
+
const parsed = this.mri(argv.slice(2), command);
|
|
5795
|
+
const commandName = parsed.args[0];
|
|
5796
|
+
if (command.isMatched(commandName)) {
|
|
5797
|
+
shouldParse = false;
|
|
5798
|
+
const parsedInfo = __assign(__assign({}, parsed), {
|
|
5799
|
+
args: parsed.args.slice(1)
|
|
5800
|
+
});
|
|
5801
|
+
this.setParsedInfo(parsedInfo, command, commandName);
|
|
5802
|
+
this.emit(`command:${commandName}`, command);
|
|
5803
|
+
}
|
|
5804
|
+
}
|
|
5805
|
+
if (shouldParse) {
|
|
5806
|
+
for (const command of this.commands) {
|
|
5807
|
+
if (command.name === "") {
|
|
5808
|
+
shouldParse = false;
|
|
5809
|
+
const parsed = this.mri(argv.slice(2), command);
|
|
5810
|
+
this.setParsedInfo(parsed, command);
|
|
5811
|
+
this.emit(`command:!`, command);
|
|
5812
|
+
}
|
|
5813
|
+
}
|
|
5814
|
+
}
|
|
5815
|
+
if (shouldParse) {
|
|
5816
|
+
const parsed = this.mri(argv.slice(2));
|
|
5817
|
+
this.setParsedInfo(parsed);
|
|
5818
|
+
}
|
|
5819
|
+
if (this.options.help && this.showHelpOnExit) {
|
|
5820
|
+
this.outputHelp();
|
|
5821
|
+
run = false;
|
|
5822
|
+
this.unsetMatchedCommand();
|
|
5823
|
+
}
|
|
5824
|
+
if (this.options.version && this.showVersionOnExit && this.matchedCommandName == null) {
|
|
5825
|
+
this.outputVersion();
|
|
5826
|
+
run = false;
|
|
5827
|
+
this.unsetMatchedCommand();
|
|
5828
|
+
}
|
|
5829
|
+
const parsedArgv = { args: this.args, options: this.options };
|
|
5830
|
+
if (run) {
|
|
5831
|
+
this.runMatchedCommand();
|
|
5832
|
+
}
|
|
5833
|
+
if (!this.matchedCommand && this.args[0]) {
|
|
5834
|
+
this.emit("command:*");
|
|
5835
|
+
}
|
|
5836
|
+
return parsedArgv;
|
|
5837
|
+
}
|
|
5838
|
+
mri(argv, command) {
|
|
5839
|
+
const cliOptions = [
|
|
5840
|
+
...this.globalCommand.options,
|
|
5841
|
+
...command ? command.options : []
|
|
5842
|
+
];
|
|
5843
|
+
const mriOptions = getMriOptions(cliOptions);
|
|
5844
|
+
let argsAfterDoubleDashes = [];
|
|
5845
|
+
const doubleDashesIndex = argv.indexOf("--");
|
|
5846
|
+
if (doubleDashesIndex > -1) {
|
|
5847
|
+
argsAfterDoubleDashes = argv.slice(doubleDashesIndex + 1);
|
|
5848
|
+
argv = argv.slice(0, doubleDashesIndex);
|
|
5849
|
+
}
|
|
5850
|
+
let parsed = mri2(argv, mriOptions);
|
|
5851
|
+
parsed = Object.keys(parsed).reduce((res, name) => {
|
|
5852
|
+
return __assign(__assign({}, res), {
|
|
5853
|
+
[camelcaseOptionName(name)]: parsed[name]
|
|
5854
|
+
});
|
|
5855
|
+
}, { _: [] });
|
|
5856
|
+
const args = parsed._;
|
|
5857
|
+
const options = {
|
|
5858
|
+
"--": argsAfterDoubleDashes
|
|
5859
|
+
};
|
|
5860
|
+
const ignoreDefault = command && command.config.ignoreOptionDefaultValue ? command.config.ignoreOptionDefaultValue : this.globalCommand.config.ignoreOptionDefaultValue;
|
|
5861
|
+
let transforms = Object.create(null);
|
|
5862
|
+
for (const cliOption of cliOptions) {
|
|
5863
|
+
if (!ignoreDefault && cliOption.config.default !== undefined) {
|
|
5864
|
+
for (const name of cliOption.names) {
|
|
5865
|
+
options[name] = cliOption.config.default;
|
|
5866
|
+
}
|
|
5867
|
+
}
|
|
5868
|
+
if (Array.isArray(cliOption.config.type)) {
|
|
5869
|
+
if (transforms[cliOption.name] === undefined) {
|
|
5870
|
+
transforms[cliOption.name] = Object.create(null);
|
|
5871
|
+
transforms[cliOption.name]["shouldTransform"] = true;
|
|
5872
|
+
transforms[cliOption.name]["transformFunction"] = cliOption.config.type[0];
|
|
5873
|
+
}
|
|
5874
|
+
}
|
|
5875
|
+
}
|
|
5876
|
+
for (const key of Object.keys(parsed)) {
|
|
5877
|
+
if (key !== "_") {
|
|
5878
|
+
const keys = key.split(".");
|
|
5879
|
+
setDotProp(options, keys, parsed[key]);
|
|
5880
|
+
setByType(options, transforms);
|
|
5881
|
+
}
|
|
5882
|
+
}
|
|
5883
|
+
return {
|
|
5884
|
+
args,
|
|
5885
|
+
options
|
|
5886
|
+
};
|
|
5887
|
+
}
|
|
5888
|
+
runMatchedCommand() {
|
|
5889
|
+
const { args, options, matchedCommand: command } = this;
|
|
5890
|
+
if (!command || !command.commandAction)
|
|
5891
|
+
return;
|
|
5892
|
+
command.checkUnknownOptions();
|
|
5893
|
+
command.checkOptionValue();
|
|
5894
|
+
command.checkRequiredArgs();
|
|
5895
|
+
const actionArgs = [];
|
|
5896
|
+
command.args.forEach((arg, index) => {
|
|
5897
|
+
if (arg.variadic) {
|
|
5898
|
+
actionArgs.push(args.slice(index));
|
|
5899
|
+
} else {
|
|
5900
|
+
actionArgs.push(args[index]);
|
|
5901
|
+
}
|
|
5902
|
+
});
|
|
5903
|
+
actionArgs.push(options);
|
|
5904
|
+
return command.commandAction.apply(this, actionArgs);
|
|
5905
|
+
}
|
|
5906
|
+
}
|
|
5907
|
+
var cac = (name = "") => new CAC(name);
|
|
5908
|
+
|
|
5308
5909
|
// src/sqlite.ts
|
|
5309
5910
|
class BunSqliteConnection {
|
|
5310
5911
|
db;
|
|
5912
|
+
maxRetries = 5;
|
|
5913
|
+
baseRetryDelayMs = 50;
|
|
5311
5914
|
constructor(db) {
|
|
5312
5915
|
this.db = db;
|
|
5313
5916
|
}
|
|
@@ -5315,13 +5918,28 @@ class BunSqliteConnection {
|
|
|
5315
5918
|
const { sql: sqliteQuery, paramOrder } = rewritePostgresQuery(sql);
|
|
5316
5919
|
const sqliteParams = rewritePostgresParams(normalizeParams(params), paramOrder);
|
|
5317
5920
|
const statement = this.db.query(sqliteQuery);
|
|
5318
|
-
const rows = statement.all(...sqliteParams).map((row) => decodeRowValues(row));
|
|
5921
|
+
const rows = await this.runWithRetry(() => statement.all(...sqliteParams).map((row) => decodeRowValues(row)));
|
|
5319
5922
|
return { rows };
|
|
5320
5923
|
}
|
|
5321
5924
|
async exec(sql, params) {
|
|
5322
5925
|
const { sql: sqliteQuery, paramOrder } = rewritePostgresQuery(sql);
|
|
5323
5926
|
const sqliteParams = rewritePostgresParams(normalizeParams(params), paramOrder);
|
|
5324
|
-
this.db.query(sqliteQuery)
|
|
5927
|
+
const statement = this.db.query(sqliteQuery);
|
|
5928
|
+
await this.runWithRetry(() => statement.run(...sqliteParams));
|
|
5929
|
+
}
|
|
5930
|
+
async runWithRetry(operation) {
|
|
5931
|
+
let attempt = 0;
|
|
5932
|
+
while (true) {
|
|
5933
|
+
try {
|
|
5934
|
+
return operation();
|
|
5935
|
+
} catch (err) {
|
|
5936
|
+
if (!isRetryableSQLiteError(err) || attempt >= this.maxRetries) {
|
|
5937
|
+
throw err;
|
|
5938
|
+
}
|
|
5939
|
+
attempt++;
|
|
5940
|
+
await delay(this.baseRetryDelayMs * attempt);
|
|
5941
|
+
}
|
|
5942
|
+
}
|
|
5325
5943
|
}
|
|
5326
5944
|
}
|
|
5327
5945
|
function rewritePostgresQuery(text) {
|
|
@@ -5415,23 +6033,198 @@ function isBindParams(value) {
|
|
|
5415
6033
|
const tag = Object.prototype.toString.call(value);
|
|
5416
6034
|
return tag === "[object Object]";
|
|
5417
6035
|
}
|
|
6036
|
+
var sqliteRetryableErrorCodes = new Set(["SQLITE_BUSY", "SQLITE_LOCKED"]);
|
|
6037
|
+
var sqliteRetryableErrnos = new Set([5, 6]);
|
|
6038
|
+
function isRetryableSQLiteError(err) {
|
|
6039
|
+
if (!err || typeof err !== "object") {
|
|
6040
|
+
return false;
|
|
6041
|
+
}
|
|
6042
|
+
const code = err.code;
|
|
6043
|
+
if (typeof code === "string") {
|
|
6044
|
+
for (const retryableCode of sqliteRetryableErrorCodes) {
|
|
6045
|
+
if (code.startsWith(retryableCode)) {
|
|
6046
|
+
return true;
|
|
6047
|
+
}
|
|
6048
|
+
}
|
|
6049
|
+
}
|
|
6050
|
+
const errno = err.errno;
|
|
6051
|
+
if (typeof errno === "number" && sqliteRetryableErrnos.has(errno)) {
|
|
6052
|
+
return true;
|
|
6053
|
+
}
|
|
6054
|
+
return false;
|
|
6055
|
+
}
|
|
6056
|
+
function delay(ms) {
|
|
6057
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
6058
|
+
}
|
|
6059
|
+
|
|
6060
|
+
// node_modules/@absurd-sqlite/sdk/dist/sqlite.js
|
|
6061
|
+
var sqliteRetryableErrorCodes2 = new Set(["SQLITE_BUSY", "SQLITE_LOCKED"]);
|
|
6062
|
+
var sqliteRetryableErrnos2 = new Set([5, 6]);
|
|
5418
6063
|
|
|
6064
|
+
// node_modules/@absurd-sqlite/sdk/dist/extension-downloader.js
|
|
6065
|
+
import { existsSync, mkdirSync, writeFileSync, chmodSync, readFileSync } from "fs";
|
|
6066
|
+
import { join } from "path";
|
|
6067
|
+
import { homedir } from "os";
|
|
6068
|
+
import { createHash } from "crypto";
|
|
6069
|
+
function getPlatformInfo() {
|
|
6070
|
+
const platform = process.platform;
|
|
6071
|
+
const arch = process.arch;
|
|
6072
|
+
let os2;
|
|
6073
|
+
let ext;
|
|
6074
|
+
switch (platform) {
|
|
6075
|
+
case "darwin":
|
|
6076
|
+
os2 = "macOS";
|
|
6077
|
+
ext = "dylib";
|
|
6078
|
+
break;
|
|
6079
|
+
case "linux":
|
|
6080
|
+
os2 = "Linux";
|
|
6081
|
+
ext = "so";
|
|
6082
|
+
break;
|
|
6083
|
+
case "win32":
|
|
6084
|
+
os2 = "Windows";
|
|
6085
|
+
ext = "dll";
|
|
6086
|
+
break;
|
|
6087
|
+
default:
|
|
6088
|
+
throw new Error(`Unsupported platform: ${platform}`);
|
|
6089
|
+
}
|
|
6090
|
+
let archStr;
|
|
6091
|
+
switch (arch) {
|
|
6092
|
+
case "x64":
|
|
6093
|
+
archStr = "X64";
|
|
6094
|
+
break;
|
|
6095
|
+
case "arm64":
|
|
6096
|
+
archStr = "ARM64";
|
|
6097
|
+
break;
|
|
6098
|
+
default:
|
|
6099
|
+
throw new Error(`Unsupported architecture: ${arch}`);
|
|
6100
|
+
}
|
|
6101
|
+
return { os: os2, arch: archStr, ext };
|
|
6102
|
+
}
|
|
6103
|
+
function getDefaultCacheDir() {
|
|
6104
|
+
return join(homedir(), ".cache", "absurd-sqlite", "extensions");
|
|
6105
|
+
}
|
|
6106
|
+
function getAssetName(version, platform) {
|
|
6107
|
+
return `absurd-absurd-sqlite-extension-${version}-${platform.os}-${platform.arch}.${platform.ext}`;
|
|
6108
|
+
}
|
|
6109
|
+
function getTag(version) {
|
|
6110
|
+
return `absurd-sqlite-extension/${version}`;
|
|
6111
|
+
}
|
|
6112
|
+
async function fetchLatestVersion(owner, repo) {
|
|
6113
|
+
const url = `https://api.github.com/repos/${owner}/${repo}/releases`;
|
|
6114
|
+
const response = await fetch(url);
|
|
6115
|
+
if (!response.ok) {
|
|
6116
|
+
throw new Error(`Failed to fetch releases: ${response.status} ${response.statusText}`);
|
|
6117
|
+
}
|
|
6118
|
+
const releases = await response.json();
|
|
6119
|
+
for (const release of releases) {
|
|
6120
|
+
if (!release.draft && release.tag_name.startsWith("absurd-sqlite-extension/")) {
|
|
6121
|
+
return release.tag_name.replace("absurd-sqlite-extension/", "");
|
|
6122
|
+
}
|
|
6123
|
+
}
|
|
6124
|
+
throw new Error("No extension releases found");
|
|
6125
|
+
}
|
|
6126
|
+
async function downloadAsset(owner, repo, tag, assetName, destPath, expectedChecksum) {
|
|
6127
|
+
const url = `https://github.com/${owner}/${repo}/releases/download/${tag}/${assetName}`;
|
|
6128
|
+
const response = await fetch(url);
|
|
6129
|
+
if (!response.ok) {
|
|
6130
|
+
throw new Error(`Failed to download extension: ${response.status} ${response.statusText} from ${url}`);
|
|
6131
|
+
}
|
|
6132
|
+
const buffer = await response.arrayBuffer();
|
|
6133
|
+
writeFileSync(destPath, Buffer.from(buffer));
|
|
6134
|
+
if (expectedChecksum) {
|
|
6135
|
+
const actualChecksum = calculateChecksum(destPath);
|
|
6136
|
+
if (actualChecksum !== expectedChecksum.toLowerCase()) {
|
|
6137
|
+
throw new Error(`Checksum verification failed. Expected: ${expectedChecksum.toLowerCase()}, Got: ${actualChecksum}`);
|
|
6138
|
+
}
|
|
6139
|
+
}
|
|
6140
|
+
if (process.platform !== "win32") {
|
|
6141
|
+
chmodSync(destPath, 493);
|
|
6142
|
+
}
|
|
6143
|
+
}
|
|
6144
|
+
function calculateChecksum(filePath) {
|
|
6145
|
+
const fileBuffer = readFileSync(filePath);
|
|
6146
|
+
const hash = createHash("sha256");
|
|
6147
|
+
hash.update(fileBuffer);
|
|
6148
|
+
return hash.digest("hex");
|
|
6149
|
+
}
|
|
6150
|
+
function getCachedPath(cacheDir, version, platform) {
|
|
6151
|
+
const ext = platform.ext;
|
|
6152
|
+
return join(cacheDir, version, `libabsurd.${ext}`);
|
|
6153
|
+
}
|
|
6154
|
+
async function downloadExtension(options = {}) {
|
|
6155
|
+
const owner = options.owner ?? "b4fun";
|
|
6156
|
+
const repo = options.repo ?? "absurd-sqlite";
|
|
6157
|
+
const cacheDir = options.cacheDir ?? getDefaultCacheDir();
|
|
6158
|
+
const force = options.force ?? false;
|
|
6159
|
+
let version = options.version ?? "latest";
|
|
6160
|
+
if (version === "latest") {
|
|
6161
|
+
version = await fetchLatestVersion(owner, repo);
|
|
6162
|
+
}
|
|
6163
|
+
const platform = getPlatformInfo();
|
|
6164
|
+
const assetName = getAssetName(version, platform);
|
|
6165
|
+
const tag = getTag(version);
|
|
6166
|
+
const cachedPath = getCachedPath(cacheDir, version, platform);
|
|
6167
|
+
if (!force && existsSync(cachedPath)) {
|
|
6168
|
+
if (options.expectedChecksum) {
|
|
6169
|
+
const actualChecksum = calculateChecksum(cachedPath);
|
|
6170
|
+
if (actualChecksum !== options.expectedChecksum.toLowerCase()) {
|
|
6171
|
+
throw new Error(`Cached file checksum verification failed. Expected: ${options.expectedChecksum.toLowerCase()}, Got: ${actualChecksum}`);
|
|
6172
|
+
}
|
|
6173
|
+
}
|
|
6174
|
+
return cachedPath;
|
|
6175
|
+
}
|
|
6176
|
+
const versionDir = join(cacheDir, version);
|
|
6177
|
+
mkdirSync(versionDir, { recursive: true });
|
|
6178
|
+
await downloadAsset(owner, repo, tag, assetName, cachedPath, options.expectedChecksum);
|
|
6179
|
+
return cachedPath;
|
|
6180
|
+
}
|
|
5419
6181
|
// src/index.ts
|
|
6182
|
+
function parseCliOptions() {
|
|
6183
|
+
const cli = cac("bun-worker");
|
|
6184
|
+
cli.option("-c, --concurrency <number>", "Number of tasks to process concurrently", {
|
|
6185
|
+
default: 10
|
|
6186
|
+
}).option("--database-path <path>", "SQLite database file path").option("--extension-path <path>", "Absurd-SQLite extension path").help();
|
|
6187
|
+
const parsed = cli.parse(process.argv, { run: false });
|
|
6188
|
+
if (parsed.options.help) {
|
|
6189
|
+
process.exit(0);
|
|
6190
|
+
}
|
|
6191
|
+
const options = {};
|
|
6192
|
+
if (parsed.options.databasePath) {
|
|
6193
|
+
options.dbPath = parsed.options.databasePath;
|
|
6194
|
+
}
|
|
6195
|
+
if (parsed.options.extensionPath) {
|
|
6196
|
+
options.extensionPath = parsed.options.extensionPath;
|
|
6197
|
+
}
|
|
6198
|
+
if (parsed.options.concurrency) {
|
|
6199
|
+
const value = parseInt(parsed.options.concurrency, 10);
|
|
6200
|
+
if (!isNaN(value) && value > 0) {
|
|
6201
|
+
options.concurrency = value;
|
|
6202
|
+
} else {
|
|
6203
|
+
console.warn(`Invalid value for --concurrency: "${parsed.options.concurrency}" (must be a positive integer)`);
|
|
6204
|
+
}
|
|
6205
|
+
}
|
|
6206
|
+
return options;
|
|
6207
|
+
}
|
|
5420
6208
|
async function run(setupFunction) {
|
|
5421
|
-
const
|
|
5422
|
-
const
|
|
6209
|
+
const cliOptions = parseCliOptions();
|
|
6210
|
+
const dbPath = cliOptions.dbPath || process.env.ABSURD_DATABASE_PATH;
|
|
6211
|
+
const extensionPath = cliOptions.extensionPath || process.env.ABSURD_DATABASE_EXTENSION_PATH;
|
|
5423
6212
|
if (!dbPath) {
|
|
5424
|
-
throw new Error("
|
|
6213
|
+
throw new Error("Database path is required. Set ABSURD_DATABASE_PATH environment variable or use --database-path flag.");
|
|
5425
6214
|
}
|
|
5426
6215
|
if (!extensionPath) {
|
|
5427
|
-
throw new Error("
|
|
6216
|
+
throw new Error("Extension path is required. Set ABSURD_DATABASE_EXTENSION_PATH environment variable or use --extension-path flag.");
|
|
5428
6217
|
}
|
|
5429
6218
|
const db = new Database(dbPath);
|
|
5430
6219
|
db.loadExtension(extensionPath);
|
|
5431
6220
|
const conn = new BunSqliteConnection(db);
|
|
5432
6221
|
const absurd = new Absurd({ db: conn });
|
|
5433
6222
|
await setupFunction(absurd);
|
|
5434
|
-
const
|
|
6223
|
+
const workerOptions = {
|
|
6224
|
+
...getDefaultWorkerOptions(),
|
|
6225
|
+
...cliOptions.concurrency !== undefined ? { concurrency: cliOptions.concurrency } : {}
|
|
6226
|
+
};
|
|
6227
|
+
const worker = await absurd.startWorker(workerOptions);
|
|
5435
6228
|
let shuttingDown = false;
|
|
5436
6229
|
const shutdown = async (signal) => {
|
|
5437
6230
|
if (shuttingDown) {
|
|
@@ -5453,6 +6246,14 @@ async function run(setupFunction) {
|
|
|
5453
6246
|
shutdown("SIGTERM");
|
|
5454
6247
|
});
|
|
5455
6248
|
}
|
|
6249
|
+
function getDefaultWorkerOptions() {
|
|
6250
|
+
return {
|
|
6251
|
+
concurrency: 10,
|
|
6252
|
+
pollInterval: 5,
|
|
6253
|
+
claimTimeout: 60
|
|
6254
|
+
};
|
|
6255
|
+
}
|
|
5456
6256
|
export {
|
|
6257
|
+
downloadExtension,
|
|
5457
6258
|
run as default
|
|
5458
6259
|
};
|
package/dist/sqlite.d.ts
CHANGED
|
@@ -3,10 +3,13 @@ import type { Queryable } from "@absurd-sqlite/sdk";
|
|
|
3
3
|
import type { SQLiteRestBindParams } from "@absurd-sqlite/sdk";
|
|
4
4
|
export declare class BunSqliteConnection implements Queryable {
|
|
5
5
|
private readonly db;
|
|
6
|
+
private readonly maxRetries;
|
|
7
|
+
private readonly baseRetryDelayMs;
|
|
6
8
|
constructor(db: Database);
|
|
7
9
|
query<R extends object = Record<string, any>>(sql: string, params?: SQLiteRestBindParams): Promise<{
|
|
8
10
|
rows: R[];
|
|
9
11
|
}>;
|
|
10
12
|
exec(sql: string, params?: SQLiteRestBindParams): Promise<void>;
|
|
13
|
+
private runWithRetry;
|
|
11
14
|
}
|
|
12
15
|
//# sourceMappingURL=sqlite.d.ts.map
|
package/dist/sqlite.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sqlite.d.ts","sourceRoot":"","sources":["../src/sqlite.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAEtC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,KAAK,EAGV,oBAAoB,EACrB,MAAM,oBAAoB,CAAC;AAE5B,qBAAa,mBAAoB,YAAW,SAAS;IACnD,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAW;
|
|
1
|
+
{"version":3,"file":"sqlite.d.ts","sourceRoot":"","sources":["../src/sqlite.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAEtC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,KAAK,EAGV,oBAAoB,EACrB,MAAM,oBAAoB,CAAC;AAE5B,qBAAa,mBAAoB,YAAW,SAAS;IACnD,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAW;IAC9B,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAK;IAChC,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAM;gBAE3B,EAAE,EAAE,QAAQ;IAIlB,KAAK,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAChD,GAAG,EAAE,MAAM,EACX,MAAM,CAAC,EAAE,oBAAoB,GAC5B,OAAO,CAAC;QAAE,IAAI,EAAE,CAAC,EAAE,CAAA;KAAE,CAAC;IAiBnB,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,oBAAoB,GAAG,OAAO,CAAC,IAAI,CAAC;YAWvD,YAAY;CAc3B"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@absurd-sqlite/bun-worker",
|
|
3
|
-
"version": "0.2.1",
|
|
3
|
+
"version": "0.2.2-alpha.1",
|
|
4
4
|
"description": "Bun worker utilities for Absurd-SQLite",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -38,8 +38,9 @@
|
|
|
38
38
|
},
|
|
39
39
|
"homepage": "https://github.com/b4fun/absurd-sqlite#readme",
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@absurd-sqlite/sdk": "
|
|
42
|
-
"absurd-sdk": "~0.0.7"
|
|
41
|
+
"@absurd-sqlite/sdk": "next",
|
|
42
|
+
"absurd-sdk": "~0.0.7",
|
|
43
|
+
"cac": "^6.7.14"
|
|
43
44
|
},
|
|
44
45
|
"devDependencies": {
|
|
45
46
|
"bun-types": "^1.3.5",
|
|
@@ -48,4 +49,4 @@
|
|
|
48
49
|
"engines": {
|
|
49
50
|
"bun": ">=1.1.0"
|
|
50
51
|
}
|
|
51
|
-
}
|
|
52
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,32 +1,103 @@
|
|
|
1
|
-
import { Absurd } from "absurd-sdk";
|
|
1
|
+
import { Absurd, type WorkerOptions } from "absurd-sdk";
|
|
2
2
|
import { Database } from "bun:sqlite";
|
|
3
3
|
import type { AbsurdClient } from "@absurd-sqlite/sdk";
|
|
4
|
+
import { cac } from "cac";
|
|
4
5
|
|
|
5
6
|
import { BunSqliteConnection } from "./sqlite";
|
|
6
7
|
|
|
7
8
|
export type { AbsurdClient } from "@absurd-sqlite/sdk";
|
|
9
|
+
export type { WorkerOptions } from "absurd-sdk";
|
|
10
|
+
|
|
11
|
+
export {
|
|
12
|
+
downloadExtension,
|
|
13
|
+
type DownloadExtensionOptions,
|
|
14
|
+
} from "@absurd-sqlite/sdk";
|
|
8
15
|
|
|
9
16
|
/**
|
|
10
17
|
* Register tasks and perform any one-time setup before the worker starts.
|
|
11
18
|
*/
|
|
12
19
|
export type SetupFunction = (absurd: AbsurdClient) => void | Promise<void>;
|
|
13
20
|
|
|
21
|
+
/**
|
|
22
|
+
* Parsed CLI options.
|
|
23
|
+
*/
|
|
24
|
+
interface ParsedOptions {
|
|
25
|
+
dbPath?: string;
|
|
26
|
+
extensionPath?: string;
|
|
27
|
+
concurrency?: number;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Parses CLI arguments and returns parsed options.
|
|
32
|
+
*/
|
|
33
|
+
function parseCliOptions(): ParsedOptions {
|
|
34
|
+
const cli = cac("bun-worker");
|
|
35
|
+
|
|
36
|
+
cli
|
|
37
|
+
.option(
|
|
38
|
+
"-c, --concurrency <number>",
|
|
39
|
+
"Number of tasks to process concurrently",
|
|
40
|
+
{
|
|
41
|
+
default: 10,
|
|
42
|
+
}
|
|
43
|
+
)
|
|
44
|
+
.option("--database-path <path>", "SQLite database file path")
|
|
45
|
+
.option("--extension-path <path>", "Absurd-SQLite extension path")
|
|
46
|
+
.help();
|
|
47
|
+
|
|
48
|
+
const parsed = cli.parse(process.argv, { run: false });
|
|
49
|
+
|
|
50
|
+
// If help was requested, cac will output it and we should exit gracefully
|
|
51
|
+
if (parsed.options.help) {
|
|
52
|
+
process.exit(0);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const options: ParsedOptions = {};
|
|
56
|
+
|
|
57
|
+
if (parsed.options.databasePath) {
|
|
58
|
+
options.dbPath = parsed.options.databasePath;
|
|
59
|
+
}
|
|
60
|
+
if (parsed.options.extensionPath) {
|
|
61
|
+
options.extensionPath = parsed.options.extensionPath;
|
|
62
|
+
}
|
|
63
|
+
if (parsed.options.concurrency) {
|
|
64
|
+
const value = parseInt(parsed.options.concurrency, 10);
|
|
65
|
+
if (!isNaN(value) && value > 0) {
|
|
66
|
+
options.concurrency = value;
|
|
67
|
+
} else {
|
|
68
|
+
console.warn(
|
|
69
|
+
`Invalid value for --concurrency: "${parsed.options.concurrency}" (must be a positive integer)`
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return options;
|
|
75
|
+
}
|
|
76
|
+
|
|
14
77
|
/**
|
|
15
78
|
* Boots a worker using Bun's SQLite driver and Absurd's task engine.
|
|
16
79
|
*
|
|
17
|
-
*
|
|
18
|
-
* -
|
|
19
|
-
* -
|
|
80
|
+
* CLI flags:
|
|
81
|
+
* - --concurrency, -c: Number of tasks to process concurrently (default: 10)
|
|
82
|
+
* - --database-path: SQLite database file path (overrides ABSURD_DATABASE_PATH)
|
|
83
|
+
* - --extension-path: Absurd-SQLite extension path (overrides ABSURD_DATABASE_EXTENSION_PATH)
|
|
20
84
|
*/
|
|
21
85
|
export default async function run(setupFunction: SetupFunction): Promise<void> {
|
|
22
|
-
const
|
|
23
|
-
|
|
86
|
+
const cliOptions = parseCliOptions();
|
|
87
|
+
|
|
88
|
+
const dbPath = cliOptions.dbPath || process.env.ABSURD_DATABASE_PATH;
|
|
89
|
+
const extensionPath =
|
|
90
|
+
cliOptions.extensionPath || process.env.ABSURD_DATABASE_EXTENSION_PATH;
|
|
24
91
|
|
|
25
92
|
if (!dbPath) {
|
|
26
|
-
throw new Error(
|
|
93
|
+
throw new Error(
|
|
94
|
+
"Database path is required. Set ABSURD_DATABASE_PATH environment variable or use --database-path flag."
|
|
95
|
+
);
|
|
27
96
|
}
|
|
28
97
|
if (!extensionPath) {
|
|
29
|
-
throw new Error(
|
|
98
|
+
throw new Error(
|
|
99
|
+
"Extension path is required. Set ABSURD_DATABASE_EXTENSION_PATH environment variable or use --extension-path flag."
|
|
100
|
+
);
|
|
30
101
|
}
|
|
31
102
|
|
|
32
103
|
const db = new Database(dbPath);
|
|
@@ -38,7 +109,18 @@ export default async function run(setupFunction: SetupFunction): Promise<void> {
|
|
|
38
109
|
const absurd = new Absurd({ db: conn });
|
|
39
110
|
|
|
40
111
|
await setupFunction(absurd);
|
|
41
|
-
|
|
112
|
+
|
|
113
|
+
// Merge worker options from multiple sources:
|
|
114
|
+
// 1. Default options
|
|
115
|
+
// 2. CLI flags
|
|
116
|
+
const workerOptions: WorkerOptions = {
|
|
117
|
+
...getDefaultWorkerOptions(),
|
|
118
|
+
...(cliOptions.concurrency !== undefined
|
|
119
|
+
? { concurrency: cliOptions.concurrency }
|
|
120
|
+
: {}),
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
const worker = await absurd.startWorker(workerOptions);
|
|
42
124
|
|
|
43
125
|
let shuttingDown = false;
|
|
44
126
|
const shutdown = async (signal: string) => {
|
|
@@ -62,3 +144,14 @@ export default async function run(setupFunction: SetupFunction): Promise<void> {
|
|
|
62
144
|
void shutdown("SIGTERM");
|
|
63
145
|
});
|
|
64
146
|
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Returns default worker options.
|
|
150
|
+
*/
|
|
151
|
+
function getDefaultWorkerOptions(): WorkerOptions {
|
|
152
|
+
return {
|
|
153
|
+
concurrency: 10,
|
|
154
|
+
pollInterval: 5,
|
|
155
|
+
claimTimeout: 60,
|
|
156
|
+
};
|
|
157
|
+
}
|
package/src/sqlite.ts
CHANGED
|
@@ -9,6 +9,8 @@ import type {
|
|
|
9
9
|
|
|
10
10
|
export class BunSqliteConnection implements Queryable {
|
|
11
11
|
private readonly db: Database;
|
|
12
|
+
private readonly maxRetries = 5;
|
|
13
|
+
private readonly baseRetryDelayMs = 50;
|
|
12
14
|
|
|
13
15
|
constructor(db: Database) {
|
|
14
16
|
this.db = db;
|
|
@@ -25,8 +27,10 @@ export class BunSqliteConnection implements Queryable {
|
|
|
25
27
|
);
|
|
26
28
|
|
|
27
29
|
const statement = this.db.query(sqliteQuery);
|
|
28
|
-
const rows =
|
|
29
|
-
|
|
30
|
+
const rows = await this.runWithRetry(() =>
|
|
31
|
+
statement.all(...sqliteParams).map((row) =>
|
|
32
|
+
decodeRowValues(row as Record<string, unknown>)
|
|
33
|
+
)
|
|
30
34
|
);
|
|
31
35
|
|
|
32
36
|
return { rows: rows as R[] };
|
|
@@ -39,7 +43,23 @@ export class BunSqliteConnection implements Queryable {
|
|
|
39
43
|
paramOrder
|
|
40
44
|
);
|
|
41
45
|
|
|
42
|
-
this.db.query(sqliteQuery)
|
|
46
|
+
const statement = this.db.query(sqliteQuery);
|
|
47
|
+
await this.runWithRetry(() => statement.run(...sqliteParams));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
private async runWithRetry<T>(operation: () => T): Promise<T> {
|
|
51
|
+
let attempt = 0;
|
|
52
|
+
while (true) {
|
|
53
|
+
try {
|
|
54
|
+
return operation();
|
|
55
|
+
} catch (err) {
|
|
56
|
+
if (!isRetryableSQLiteError(err) || attempt >= this.maxRetries) {
|
|
57
|
+
throw err;
|
|
58
|
+
}
|
|
59
|
+
attempt++;
|
|
60
|
+
await delay(this.baseRetryDelayMs * attempt);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
43
63
|
}
|
|
44
64
|
}
|
|
45
65
|
|
|
@@ -167,3 +187,32 @@ function isBindParams(value: unknown): value is SQLiteBindParams {
|
|
|
167
187
|
const tag = Object.prototype.toString.call(value);
|
|
168
188
|
return tag === "[object Object]";
|
|
169
189
|
}
|
|
190
|
+
|
|
191
|
+
const sqliteRetryableErrorCodes = new Set(["SQLITE_BUSY", "SQLITE_LOCKED"]);
|
|
192
|
+
const sqliteRetryableErrnos = new Set([5, 6]);
|
|
193
|
+
|
|
194
|
+
function isRetryableSQLiteError(err: unknown): boolean {
|
|
195
|
+
if (!err || typeof err !== "object") {
|
|
196
|
+
return false;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const code = (err as any).code;
|
|
200
|
+
if (typeof code === "string") {
|
|
201
|
+
for (const retryableCode of sqliteRetryableErrorCodes) {
|
|
202
|
+
if (code.startsWith(retryableCode)) {
|
|
203
|
+
return true;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const errno = (err as any).errno;
|
|
209
|
+
if (typeof errno === "number" && sqliteRetryableErrnos.has(errno)) {
|
|
210
|
+
return true;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return false;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function delay(ms: number): Promise<void> {
|
|
217
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
218
|
+
}
|
package/test/cli.test.ts
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from "bun:test";
|
|
2
|
+
import { mkdtempSync, rmSync, existsSync } from "node:fs";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { Database } from "bun:sqlite";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
|
|
8
|
+
const testDir = fileURLToPath(new URL(".", import.meta.url));
|
|
9
|
+
const repoRoot = join(testDir, "../../..");
|
|
10
|
+
const extensionBase = join(repoRoot, "target/release/libabsurd");
|
|
11
|
+
|
|
12
|
+
function resolveExtensionPath(base: string): string {
|
|
13
|
+
const platformExt =
|
|
14
|
+
process.platform === "win32"
|
|
15
|
+
? ".dll"
|
|
16
|
+
: process.platform === "darwin"
|
|
17
|
+
? ".dylib"
|
|
18
|
+
: ".so";
|
|
19
|
+
const candidates = [base, `${base}${platformExt}`];
|
|
20
|
+
for (const candidate of candidates) {
|
|
21
|
+
if (existsSync(candidate)) {
|
|
22
|
+
return candidate;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
throw new Error(
|
|
26
|
+
`SQLite extension not found at ${base} (expected ${platformExt})`
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const extensionPath = resolveExtensionPath(extensionBase);
|
|
31
|
+
|
|
32
|
+
describe("Worker configuration", () => {
|
|
33
|
+
let tempDir: string;
|
|
34
|
+
let dbPath: string;
|
|
35
|
+
|
|
36
|
+
beforeEach(() => {
|
|
37
|
+
tempDir = mkdtempSync(join(tmpdir(), "absurd-cli-test-"));
|
|
38
|
+
dbPath = join(tempDir, "test.db");
|
|
39
|
+
|
|
40
|
+
// Initialize database with migrations
|
|
41
|
+
const db = new Database(dbPath);
|
|
42
|
+
(db as unknown as { loadExtension(path: string): void }).loadExtension(
|
|
43
|
+
extensionPath
|
|
44
|
+
);
|
|
45
|
+
db.query("select absurd_apply_migrations()").get();
|
|
46
|
+
db.close();
|
|
47
|
+
|
|
48
|
+
process.env.ABSURD_DATABASE_PATH = dbPath;
|
|
49
|
+
process.env.ABSURD_DATABASE_EXTENSION_PATH = extensionPath;
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
afterEach(() => {
|
|
53
|
+
if (tempDir) {
|
|
54
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
55
|
+
}
|
|
56
|
+
delete process.env.ABSURD_DATABASE_PATH;
|
|
57
|
+
delete process.env.ABSURD_DATABASE_EXTENSION_PATH;
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("works with default options when none provided", async () => {
|
|
61
|
+
const { default: run } = await import("../src/index");
|
|
62
|
+
|
|
63
|
+
let workerStarted = false;
|
|
64
|
+
|
|
65
|
+
const promise = run(async (absurd) => {
|
|
66
|
+
await absurd.createQueue("default");
|
|
67
|
+
absurd.registerTask({ name: "test" }, async () => {
|
|
68
|
+
return { ok: true };
|
|
69
|
+
});
|
|
70
|
+
workerStarted = true;
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// Give the worker time to start
|
|
74
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
75
|
+
|
|
76
|
+
expect(workerStarted).toBe(true);
|
|
77
|
+
|
|
78
|
+
// Clean up by sending SIGINT
|
|
79
|
+
process.emit("SIGINT");
|
|
80
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
|
package/test/run.test.ts
CHANGED
|
@@ -62,7 +62,7 @@ describe("run", () => {
|
|
|
62
62
|
const { default: run } = await import("../src/index");
|
|
63
63
|
|
|
64
64
|
await expect(run(() => {})).rejects.toThrow(
|
|
65
|
-
"
|
|
65
|
+
"Database path is required"
|
|
66
66
|
);
|
|
67
67
|
});
|
|
68
68
|
|
|
@@ -71,7 +71,7 @@ describe("run", () => {
|
|
|
71
71
|
const { default: run } = await import("../src/index");
|
|
72
72
|
|
|
73
73
|
await expect(run(() => {})).rejects.toThrow(
|
|
74
|
-
"
|
|
74
|
+
"Extension path is required"
|
|
75
75
|
);
|
|
76
76
|
});
|
|
77
77
|
|
package/test/sqlite.test.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { Database } from "bun:sqlite";
|
|
2
|
-
import { describe, expect, it } from "bun:test";
|
|
2
|
+
import { describe, expect, it, jest } from "bun:test";
|
|
3
|
+
import { mkdtempSync, rmSync } from "node:fs";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { tmpdir } from "node:os";
|
|
3
6
|
|
|
4
7
|
import { BunSqliteConnection } from "../src/sqlite";
|
|
5
8
|
|
|
@@ -86,4 +89,71 @@ describe("BunSqliteConnection", () => {
|
|
|
86
89
|
expect(rows[0]?.created_at.getTime()).toBe(now);
|
|
87
90
|
db.close();
|
|
88
91
|
});
|
|
92
|
+
|
|
93
|
+
it("retries when SQLite reports the database is busy", async () => {
|
|
94
|
+
const tempDir = mkdtempSync(join(tmpdir(), "absurd-sqlite-busy-"));
|
|
95
|
+
const dbPath = join(tempDir, "busy.db");
|
|
96
|
+
const primary = new Database(dbPath);
|
|
97
|
+
primary.run("PRAGMA busy_timeout = 1");
|
|
98
|
+
const conn = new BunSqliteConnection(primary);
|
|
99
|
+
await conn.exec("CREATE TABLE t_busy (id INTEGER PRIMARY KEY, value TEXT)");
|
|
100
|
+
|
|
101
|
+
const blocker = new Database(dbPath);
|
|
102
|
+
blocker.run("PRAGMA busy_timeout = 1");
|
|
103
|
+
blocker.run("BEGIN EXCLUSIVE");
|
|
104
|
+
|
|
105
|
+
let released = false;
|
|
106
|
+
const releaseLock = () => {
|
|
107
|
+
if (released) return;
|
|
108
|
+
released = true;
|
|
109
|
+
try {
|
|
110
|
+
blocker.run("COMMIT");
|
|
111
|
+
} catch {
|
|
112
|
+
// ignore if already closed
|
|
113
|
+
}
|
|
114
|
+
blocker.close();
|
|
115
|
+
};
|
|
116
|
+
const timer = setTimeout(releaseLock, 20);
|
|
117
|
+
|
|
118
|
+
try {
|
|
119
|
+
await conn.exec("INSERT INTO t_busy (value) VALUES ($1)", ["alpha"]);
|
|
120
|
+
const { rows } = await conn.query<{ value: string }>(
|
|
121
|
+
"SELECT value FROM t_busy"
|
|
122
|
+
);
|
|
123
|
+
expect(rows[0]?.value).toBe("alpha");
|
|
124
|
+
} finally {
|
|
125
|
+
clearTimeout(timer);
|
|
126
|
+
releaseLock();
|
|
127
|
+
primary.close();
|
|
128
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it("retries on locked error codes from SQLite", async () => {
|
|
133
|
+
const lockedError = new Error("SQLITE_LOCKED: mock lock") as any;
|
|
134
|
+
lockedError.code = "SQLITE_LOCKED_SHAREDCACHE";
|
|
135
|
+
lockedError.errno = 6;
|
|
136
|
+
|
|
137
|
+
let attempts = 0;
|
|
138
|
+
const statement = {
|
|
139
|
+
all: jest.fn(),
|
|
140
|
+
run: jest.fn(() => {
|
|
141
|
+
attempts++;
|
|
142
|
+
if (attempts === 1) {
|
|
143
|
+
throw lockedError;
|
|
144
|
+
}
|
|
145
|
+
return 1;
|
|
146
|
+
}),
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
const querySpy = jest.fn().mockReturnValue(statement as any);
|
|
150
|
+
const db = { query: querySpy } as unknown as Database;
|
|
151
|
+
const conn = new BunSqliteConnection(db);
|
|
152
|
+
|
|
153
|
+
await expect(
|
|
154
|
+
conn.exec("UPDATE locked_table SET value = $1 WHERE id = $2", [1, 1])
|
|
155
|
+
).resolves.toBeUndefined();
|
|
156
|
+
expect(statement.run).toHaveBeenCalledTimes(2);
|
|
157
|
+
expect(querySpy).toHaveBeenCalledTimes(1);
|
|
158
|
+
});
|
|
89
159
|
});
|