@procedures/store 0.2.2 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +63 -0
- package/README.md +109 -2
- package/dist/.tsbuildinfo +1 -1
- package/dist/indexeddb/idb.d.ts +100 -0
- package/dist/indexeddb/idb.d.ts.map +1 -0
- package/dist/indexeddb/idb.js +137 -0
- package/dist/indexeddb/idb.js.map +1 -0
- package/dist/indexeddb/program-store.d.ts +42 -0
- package/dist/indexeddb/program-store.d.ts.map +1 -0
- package/dist/indexeddb/program-store.js +71 -0
- package/dist/indexeddb/program-store.js.map +1 -0
- package/dist/indexeddb/queue-store.d.ts +38 -0
- package/dist/indexeddb/queue-store.d.ts.map +1 -0
- package/dist/indexeddb/queue-store.js +73 -0
- package/dist/indexeddb/queue-store.js.map +1 -0
- package/dist/indexeddb.d.ts +19 -0
- package/dist/indexeddb.d.ts.map +1 -0
- package/dist/indexeddb.js +17 -0
- package/dist/indexeddb.js.map +1 -0
- package/dist/program-store/file.d.ts +5 -0
- package/dist/program-store/file.d.ts.map +1 -1
- package/dist/program-store/file.js +5 -2
- package/dist/program-store/file.js.map +1 -1
- package/dist/program-store/procedures.d.ts.map +1 -1
- package/dist/program-store/procedures.js +5 -2
- package/dist/program-store/procedures.js.map +1 -1
- package/dist/storage/api.d.ts +18 -7
- package/dist/storage/api.d.ts.map +1 -1
- package/dist/storage/api.js +34 -3
- package/dist/storage/api.js.map +1 -1
- package/dist/storage/factory.d.ts +13 -2
- package/dist/storage/factory.d.ts.map +1 -1
- package/dist/storage/factory.js +12 -4
- package/dist/storage/factory.js.map +1 -1
- package/dist/storage/file-queue.d.ts +32 -0
- package/dist/storage/file-queue.d.ts.map +1 -0
- package/dist/storage/file-queue.js +101 -0
- package/dist/storage/file-queue.js.map +1 -0
- package/dist/storage/hybrid.d.ts +355 -42
- package/dist/storage/hybrid.d.ts.map +1 -1
- package/dist/storage/hybrid.js +1093 -250
- package/dist/storage/hybrid.js.map +1 -1
- package/dist/storage/index.d.ts +4 -2
- package/dist/storage/index.d.ts.map +1 -1
- package/dist/storage/index.js +5 -1
- package/dist/storage/index.js.map +1 -1
- package/dist/storage/queue.d.ts +67 -0
- package/dist/storage/queue.d.ts.map +1 -0
- package/dist/storage/queue.js +85 -0
- package/dist/storage/queue.js.map +1 -0
- package/dist/storage/synced-registry.d.ts +18 -2
- package/dist/storage/synced-registry.d.ts.map +1 -1
- package/dist/storage/synced-registry.js +37 -3
- package/dist/storage/synced-registry.js.map +1 -1
- package/dist/storage/types.d.ts +5 -2
- package/dist/storage/types.d.ts.map +1 -1
- package/package.json +16 -6
- package/src/indexeddb/idb.ts +190 -0
- package/src/indexeddb/program-store.ts +80 -0
- package/src/indexeddb/queue-store.ts +80 -0
- package/src/indexeddb.ts +19 -0
- package/src/program-store/file.ts +5 -2
- package/src/program-store/procedures.ts +5 -2
- package/src/storage/api.ts +50 -10
- package/src/storage/factory.ts +21 -4
- package/src/storage/file-queue.ts +103 -0
- package/src/storage/hybrid.ts +1142 -274
- package/src/storage/index.ts +15 -2
- package/src/storage/queue.ts +128 -0
- package/src/storage/synced-registry.ts +49 -4
- package/src/storage/types.ts +6 -2
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A queue store in a JSON Lines file, for Node (roadmap R28).
|
|
3
|
+
*
|
|
4
|
+
* This module is the entry `@procedures/store/file-queue`. It uses `node:fs`, so the root of the
|
|
5
|
+
* package does not import it.
|
|
6
|
+
*
|
|
7
|
+
* - Each line of the file is one `QueuedOperation`, the oldest first.
|
|
8
|
+
* - `append` adds a line. `replace` and `clear` write a temporary file and rename it, so a reader
|
|
9
|
+
* never sees a part of a replace.
|
|
10
|
+
* - A process that stops during an append can leave a part of a line at the end. `load` drops
|
|
11
|
+
* that part and writes the file again. Its write was not acknowledged.
|
|
12
|
+
* - One storage uses one file. Two processes must not use the same file.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { appendFile, mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
16
|
+
import { dirname } from "node:path";
|
|
17
|
+
import { retryOnContention } from "../program-store/file.js";
|
|
18
|
+
import { isQueuedOperation, type QueuedOperation, type QueueStore } from "./queue.js";
|
|
19
|
+
|
|
20
|
+
export type { QueuedOperation, QueueStore } from "./queue.js";
|
|
21
|
+
|
|
22
|
+
/** A queue store in a JSON Lines file. */
|
|
23
|
+
export class FileQueueStore implements QueueStore {
|
|
24
|
+
/** The calls of this instance, one at a time */
|
|
25
|
+
private chain: Promise<unknown> = Promise.resolve();
|
|
26
|
+
|
|
27
|
+
/** @param path - The file. The store makes it, and its folder, at the first write. */
|
|
28
|
+
constructor(readonly path: string) {}
|
|
29
|
+
|
|
30
|
+
load(): Promise<QueuedOperation[]> {
|
|
31
|
+
return this.serial(async () => {
|
|
32
|
+
let text: string;
|
|
33
|
+
try {
|
|
34
|
+
text = await retryOnContention(() => readFile(this.path, "utf8"));
|
|
35
|
+
} catch (error) {
|
|
36
|
+
if ((error as NodeJS.ErrnoException).code === "ENOENT") return [];
|
|
37
|
+
throw error;
|
|
38
|
+
}
|
|
39
|
+
const lines = text.split("\n");
|
|
40
|
+
// The text after the last newline is a part of a line, or empty
|
|
41
|
+
const tail = lines.pop() ?? "";
|
|
42
|
+
const operations = lines.filter((line) => line.trim() !== "").map((line, index) => this.parse(line, index + 1));
|
|
43
|
+
if (tail.trim() !== "") {
|
|
44
|
+
let last: QueuedOperation | undefined;
|
|
45
|
+
try {
|
|
46
|
+
last = this.parse(tail, lines.length + 1);
|
|
47
|
+
} catch {
|
|
48
|
+
// An append that did not finish: drop it
|
|
49
|
+
}
|
|
50
|
+
if (last) operations.push(last);
|
|
51
|
+
await this.write(operations);
|
|
52
|
+
}
|
|
53
|
+
return operations;
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
append(operation: QueuedOperation): Promise<void> {
|
|
58
|
+
return this.serial(async () => {
|
|
59
|
+
await mkdir(dirname(this.path), { recursive: true });
|
|
60
|
+
await retryOnContention(() => appendFile(this.path, `${JSON.stringify(operation)}\n`, "utf8"));
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
replace(operations: QueuedOperation[]): Promise<void> {
|
|
65
|
+
return this.serial(() => this.write(operations));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
clear(): Promise<void> {
|
|
69
|
+
return this.serial(() => this.write([]));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
private parse(line: string, number: number): QueuedOperation {
|
|
73
|
+
let value: unknown;
|
|
74
|
+
try {
|
|
75
|
+
value = JSON.parse(line);
|
|
76
|
+
} catch {
|
|
77
|
+
throw new Error(`The queue file ${this.path} has a line that is not JSON (line ${number}). The store does not write over it.`);
|
|
78
|
+
}
|
|
79
|
+
if (!isQueuedOperation(value)) {
|
|
80
|
+
throw new Error(`The queue file ${this.path} has a line that is not a queued operation (line ${number})`);
|
|
81
|
+
}
|
|
82
|
+
return value;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Write the file through a temporary file and a rename. */
|
|
86
|
+
private async write(operations: QueuedOperation[]): Promise<void> {
|
|
87
|
+
await mkdir(dirname(this.path), { recursive: true });
|
|
88
|
+
const temporary = `${this.path}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
89
|
+
try {
|
|
90
|
+
await writeFile(temporary, operations.map((operation) => `${JSON.stringify(operation)}\n`).join(""), "utf8");
|
|
91
|
+
await retryOnContention(() => rename(temporary, this.path));
|
|
92
|
+
} catch (error) {
|
|
93
|
+
await rm(temporary, { force: true });
|
|
94
|
+
throw error;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
private serial<R>(run: () => Promise<R>): Promise<R> {
|
|
99
|
+
const next = this.chain.then(run, run);
|
|
100
|
+
this.chain = next.catch(() => undefined);
|
|
101
|
+
return next;
|
|
102
|
+
}
|
|
103
|
+
}
|