@factoidal/core 0.2.0 → 0.4.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 +122 -0
- package/NOTICE +31 -0
- package/README.md +131 -1
- package/bin/engine.mjs +124 -0
- package/bin/factoidal.mjs +894 -0
- package/bin/pack-host.mjs +341 -0
- package/bin/pack-worker.mjs +31 -0
- package/bin/pack.mjs +174 -0
- package/bin/store.mjs +238 -0
- package/l4-assets/l4factoidal.js +125 -5
- package/l4-assets/l4factoidal.mjs +1 -1
- package/l4-assets/l4factoidal.wasm +0 -0
- package/l4-assets/package.json +4 -0
- package/l4-assets/version.json +5 -5
- package/l4.d.ts +14 -0
- package/l4.js +15 -1
- package/package.json +17 -2
- package/sample-store/CURRENT +1 -0
- package/sample-store/gen-1/manifest.sbm2 +0 -0
- package/sample-store/gen-1/manifest.tsv +14 -0
- package/sample-store/gen-1/predicate-0.ibk3 +0 -0
- package/sample-store/gen-1/predicate-0.ibk3.merkle +1 -0
- package/sample-store/gen-1/predicate-0.ibk3.oli2 +0 -0
- package/sample-store/gen-1/predicate-0.ibk3.oli2.merkle +1 -0
- package/sample-store/gen-1/predicate-0.ibk3.sri2 +0 -0
- package/sample-store/gen-1/predicate-0.ibk3.sri2.merkle +1 -0
- package/sample-store/gen-1/predicate-0.ibk3.tli1 +0 -0
- package/sample-store/gen-1/predicate-0.ibk3.tli1.merkle +0 -0
- package/sample-store/gen-1/predicate-1.ibk3 +0 -0
- package/sample-store/gen-1/predicate-1.ibk3.merkle +0 -0
- package/sample-store/gen-1/predicate-1.ibk3.oli2 +0 -0
- package/sample-store/gen-1/predicate-1.ibk3.oli2.merkle +0 -0
- package/sample-store/gen-1/predicate-1.ibk3.sri2 +0 -0
- package/sample-store/gen-1/predicate-1.ibk3.sri2.merkle +1 -0
- package/sample-store/gen-1/predicate-1.ibk3.tli1 +0 -0
- package/sample-store/gen-1/predicate-1.ibk3.tli1.merkle +1 -0
- package/sample-store/gen-1/predicate-10.ibk3 +0 -0
- package/sample-store/gen-1/predicate-10.ibk3.merkle +1 -0
- package/sample-store/gen-1/predicate-10.ibk3.oli2 +0 -0
- package/sample-store/gen-1/predicate-10.ibk3.oli2.merkle +0 -0
- package/sample-store/gen-1/predicate-10.ibk3.sri2 +0 -0
- package/sample-store/gen-1/predicate-10.ibk3.sri2.merkle +1 -0
- package/sample-store/gen-1/predicate-10.ibk3.tli1 +0 -0
- package/sample-store/gen-1/predicate-10.ibk3.tli1.merkle +1 -0
- package/sample-store/gen-1/predicate-11.ibk3 +0 -0
- package/sample-store/gen-1/predicate-11.ibk3.merkle +0 -0
- package/sample-store/gen-1/predicate-11.ibk3.oli2 +0 -0
- package/sample-store/gen-1/predicate-11.ibk3.oli2.merkle +1 -0
- package/sample-store/gen-1/predicate-11.ibk3.sri2 +0 -0
- package/sample-store/gen-1/predicate-11.ibk3.sri2.merkle +1 -0
- package/sample-store/gen-1/predicate-11.ibk3.tli1 +0 -0
- package/sample-store/gen-1/predicate-11.ibk3.tli1.merkle +1 -0
- package/sample-store/gen-1/predicate-12.ibk3 +0 -0
- package/sample-store/gen-1/predicate-12.ibk3.merkle +1 -0
- package/sample-store/gen-1/predicate-12.ibk3.oli2 +0 -0
- package/sample-store/gen-1/predicate-12.ibk3.oli2.merkle +1 -0
- package/sample-store/gen-1/predicate-12.ibk3.sri2 +0 -0
- package/sample-store/gen-1/predicate-12.ibk3.sri2.merkle +1 -0
- package/sample-store/gen-1/predicate-12.ibk3.tli1 +0 -0
- package/sample-store/gen-1/predicate-12.ibk3.tli1.merkle +1 -0
- package/sample-store/gen-1/predicate-2.ibk3 +0 -0
- package/sample-store/gen-1/predicate-2.ibk3.merkle +1 -0
- package/sample-store/gen-1/predicate-2.ibk3.oli2 +0 -0
- package/sample-store/gen-1/predicate-2.ibk3.oli2.merkle +1 -0
- package/sample-store/gen-1/predicate-2.ibk3.sri2 +0 -0
- package/sample-store/gen-1/predicate-2.ibk3.sri2.merkle +0 -0
- package/sample-store/gen-1/predicate-2.ibk3.tli1 +0 -0
- package/sample-store/gen-1/predicate-2.ibk3.tli1.merkle +0 -0
- package/sample-store/gen-1/predicate-3.ibk3 +0 -0
- package/sample-store/gen-1/predicate-3.ibk3.merkle +1 -0
- package/sample-store/gen-1/predicate-3.ibk3.oli2 +0 -0
- package/sample-store/gen-1/predicate-3.ibk3.oli2.merkle +1 -0
- package/sample-store/gen-1/predicate-3.ibk3.sri2 +0 -0
- package/sample-store/gen-1/predicate-3.ibk3.sri2.merkle +1 -0
- package/sample-store/gen-1/predicate-3.ibk3.tli1 +0 -0
- package/sample-store/gen-1/predicate-3.ibk3.tli1.merkle +1 -0
- package/sample-store/gen-1/predicate-4.ibk3 +0 -0
- package/sample-store/gen-1/predicate-4.ibk3.merkle +0 -0
- package/sample-store/gen-1/predicate-4.ibk3.oli2 +0 -0
- package/sample-store/gen-1/predicate-4.ibk3.oli2.merkle +1 -0
- package/sample-store/gen-1/predicate-4.ibk3.sri2 +0 -0
- package/sample-store/gen-1/predicate-4.ibk3.sri2.merkle +1 -0
- package/sample-store/gen-1/predicate-4.ibk3.tli1 +0 -0
- package/sample-store/gen-1/predicate-4.ibk3.tli1.merkle +2 -0
- package/sample-store/gen-1/predicate-5.ibk3 +0 -0
- package/sample-store/gen-1/predicate-5.ibk3.merkle +1 -0
- package/sample-store/gen-1/predicate-5.ibk3.oli2 +0 -0
- package/sample-store/gen-1/predicate-5.ibk3.oli2.merkle +1 -0
- package/sample-store/gen-1/predicate-5.ibk3.sri2 +0 -0
- package/sample-store/gen-1/predicate-5.ibk3.sri2.merkle +1 -0
- package/sample-store/gen-1/predicate-5.ibk3.tli1 +0 -0
- package/sample-store/gen-1/predicate-5.ibk3.tli1.merkle +1 -0
- package/sample-store/gen-1/predicate-6.ibk3 +0 -0
- package/sample-store/gen-1/predicate-6.ibk3.merkle +1 -0
- package/sample-store/gen-1/predicate-6.ibk3.oli2 +0 -0
- package/sample-store/gen-1/predicate-6.ibk3.oli2.merkle +1 -0
- package/sample-store/gen-1/predicate-6.ibk3.sri2 +0 -0
- package/sample-store/gen-1/predicate-6.ibk3.sri2.merkle +1 -0
- package/sample-store/gen-1/predicate-6.ibk3.tli1 +0 -0
- package/sample-store/gen-1/predicate-6.ibk3.tli1.merkle +0 -0
- package/sample-store/gen-1/predicate-7.ibk3 +0 -0
- package/sample-store/gen-1/predicate-7.ibk3.merkle +0 -0
- package/sample-store/gen-1/predicate-7.ibk3.oli2 +0 -0
- package/sample-store/gen-1/predicate-7.ibk3.oli2.merkle +2 -0
- package/sample-store/gen-1/predicate-7.ibk3.sri2 +0 -0
- package/sample-store/gen-1/predicate-7.ibk3.sri2.merkle +1 -0
- package/sample-store/gen-1/predicate-7.ibk3.tli1 +0 -0
- package/sample-store/gen-1/predicate-7.ibk3.tli1.merkle +1 -0
- package/sample-store/gen-1/predicate-8.ibk3 +0 -0
- package/sample-store/gen-1/predicate-8.ibk3.merkle +1 -0
- package/sample-store/gen-1/predicate-8.ibk3.oli2 +0 -0
- package/sample-store/gen-1/predicate-8.ibk3.oli2.merkle +1 -0
- package/sample-store/gen-1/predicate-8.ibk3.sri2 +0 -0
- package/sample-store/gen-1/predicate-8.ibk3.sri2.merkle +1 -0
- package/sample-store/gen-1/predicate-8.ibk3.tli1 +0 -0
- package/sample-store/gen-1/predicate-8.ibk3.tli1.merkle +1 -0
- package/sample-store/gen-1/predicate-9.ibk3 +0 -0
- package/sample-store/gen-1/predicate-9.ibk3.merkle +1 -0
- package/sample-store/gen-1/predicate-9.ibk3.oli2 +0 -0
- package/sample-store/gen-1/predicate-9.ibk3.oli2.merkle +1 -0
- package/sample-store/gen-1/predicate-9.ibk3.sri2 +0 -0
- package/sample-store/gen-1/predicate-9.ibk3.sri2.merkle +3 -0
- package/sample-store/gen-1/predicate-9.ibk3.tli1 +0 -0
- package/sample-store/gen-1/predicate-9.ibk3.tli1.merkle +2 -0
- package/sample-store.d.ts +16 -0
- package/sample-store.mjs +37 -0
- package/store-host/deno.mjs +262 -0
- package/store-host/errors.mjs +55 -0
- package/store-host/index.mjs +247 -0
- package/store-host/node.mjs +273 -0
- package/store-host/paths.mjs +77 -0
- package/version.json +22 -21
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
�R�2 ���mcY(]HI��
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
��Q�+_�`*YN� ;B��E�<Ls�+d���
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
�c����E���EҮ���J�x?ݣ67���
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
5�z�@d���Z�����Kg���U,�"�{�l��q֪נ�c�,��@$�zo���Q���
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
�$�����B�7�!bUs�2���1��S�f
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
5c�X���,'C����8DYӻ[����t�@�/l
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
'�=�U)B:��ӯ�ٳ�{I��4FoֹJKA
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
i���<�%4�0���z."~�v4n�*$ǝ
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
��(WUVh�ؠ$w���6�zh��L�Qo���
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
�Đ��5�k��EQ2��zE��;��oɗ�
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
���2N����Y�M~8������3�(z>l�r��
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
�6��%v�.+u���}0�v�0��������
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
q�/F����AL&��z�1�z�YP�x#
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
�~��CV�L��3&#�|��D�6�� ��
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
�8}��P0�oK���5t ����8�fOR�
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
_��k��2Ǟ��bk[�r�Փ���>+�8�"
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
�
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
���]",�b��\�B\�$on�ΰ�gcEb
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
`��y�
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Zvt&��Sv�V�u¹t��ڼ{7�MP��j*
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
!'M�Uw����,GD��������A�|���a�
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** The collection root of the sample Shardborough store bundled with this
|
|
2
|
+
* package: the directory that holds CURRENT. */
|
|
3
|
+
export declare function sampleStorePath (): string
|
|
4
|
+
|
|
5
|
+
export declare const sampleStoreFacts: Readonly<{
|
|
6
|
+
triples: number
|
|
7
|
+
blocks: number
|
|
8
|
+
layout: string
|
|
9
|
+
wireVersion: number
|
|
10
|
+
source: string
|
|
11
|
+
licence: string
|
|
12
|
+
licenceUrl: string
|
|
13
|
+
publisher: string
|
|
14
|
+
}>
|
|
15
|
+
|
|
16
|
+
export default sampleStorePath
|
package/sample-store.mjs
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// The sample Shardborough store this package carries, so a fresh install
|
|
2
|
+
// answers a SPARQL query with no other download and no native binary.
|
|
3
|
+
//
|
|
4
|
+
// The store was packed by `l4block-shard-pack … ibk3` and activated by
|
|
5
|
+
// `l4block-shard-activate`. Nothing here decodes it; this module only
|
|
6
|
+
// resolves its path inside the installed package.
|
|
7
|
+
//
|
|
8
|
+
// CONTENT AND LICENCE. Five IPTC NewsCodes vocabularies, published by the
|
|
9
|
+
// IPTC under CC BY 4.0 and taken from danbri/skosdex third_party/skos:
|
|
10
|
+
// spamfstat, videoqualifier, subjectqualifier, videocodec and spct.
|
|
11
|
+
// 4,434 triples in 13 predicate blocks. See NOTICE.
|
|
12
|
+
|
|
13
|
+
import { fileUrlToPath, joinPath } from './store-host/paths.mjs'
|
|
14
|
+
|
|
15
|
+
/** The collection root of the bundled sample store: the directory holding
|
|
16
|
+
* CURRENT. Pass it to `factoidal inspect` / `factoidal query`, or to
|
|
17
|
+
* `openStore` from `@factoidal/core/store-host`. */
|
|
18
|
+
export function sampleStorePath () {
|
|
19
|
+
const packageRoot = fileUrlToPath(new URL('.', import.meta.url).href)
|
|
20
|
+
return joinPath(packageRoot, 'sample-store')
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** What the sample store holds, as recorded when it was packed. The engine
|
|
24
|
+
* is the authority on every one of these numbers at run time; they are here
|
|
25
|
+
* so a caller can print something before it opens the store. */
|
|
26
|
+
export const sampleStoreFacts = Object.freeze({
|
|
27
|
+
triples: 4434,
|
|
28
|
+
blocks: 13,
|
|
29
|
+
layout: 'predicate-ibk3-ptd1-sri2-tli1-oli2-merkle-v0',
|
|
30
|
+
wireVersion: 6,
|
|
31
|
+
source: 'IPTC NewsCodes (spamfstat, videoqualifier, subjectqualifier, videocodec, spct)',
|
|
32
|
+
licence: 'CC BY 4.0',
|
|
33
|
+
licenceUrl: 'https://creativecommons.org/licenses/by/4.0/',
|
|
34
|
+
publisher: 'https://iptc.org/'
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
export default sampleStorePath
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
// Deno implementation of the same four host primitives. Uses only Deno
|
|
2
|
+
// globals, so it never loads Deno's Node compatibility layer.
|
|
3
|
+
//
|
|
4
|
+
// Deno 2 removed the resource-id fsync entry points (`Deno.fsyncSync`);
|
|
5
|
+
// the file-handle method `FsFile.syncSync()` is the one that exists.
|
|
6
|
+
|
|
7
|
+
import { StoreHostError } from './errors.mjs'
|
|
8
|
+
import { baseName, dirName, joinPath } from './paths.mjs'
|
|
9
|
+
|
|
10
|
+
export const runtime = 'deno'
|
|
11
|
+
|
|
12
|
+
function wrap (code, message, path, cause) {
|
|
13
|
+
return new StoreHostError(code, `${message}: ${String(cause && cause.message ? cause.message : cause)}`, { path, cause })
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function openRead (path) {
|
|
17
|
+
try {
|
|
18
|
+
return Deno.openSync(path, { read: true })
|
|
19
|
+
} catch (cause) {
|
|
20
|
+
throw wrap('OPEN_FAILED', `cannot open ${path} for reading`, path, cause)
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Deno has no positional read. Each call here opens its own handle, so the
|
|
26
|
+
* seek belongs to that handle alone and no other reader can move it. That
|
|
27
|
+
* is what `pread` buys the C extern; it is not the same instruction.
|
|
28
|
+
*/
|
|
29
|
+
function readInto (file, out, length, offset, path) {
|
|
30
|
+
if (offset > 0) file.seekSync(offset, Deno.SeekMode.Start)
|
|
31
|
+
let done = 0
|
|
32
|
+
while (done < length) {
|
|
33
|
+
let read
|
|
34
|
+
try {
|
|
35
|
+
read = file.readSync(out.subarray(done, length))
|
|
36
|
+
} catch (cause) {
|
|
37
|
+
throw wrap('READ_FAILED', `read failed on ${path}`, path, cause)
|
|
38
|
+
}
|
|
39
|
+
if (read === null || read === 0) break
|
|
40
|
+
done += read
|
|
41
|
+
}
|
|
42
|
+
return done
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function readWhole (path) {
|
|
46
|
+
const file = openRead(path)
|
|
47
|
+
try {
|
|
48
|
+
const size = file.statSync().size
|
|
49
|
+
if (!Number.isSafeInteger(size)) {
|
|
50
|
+
throw new StoreHostError('FILE_TOO_LARGE', `${path} is larger than 2^53 - 1 bytes`, { path })
|
|
51
|
+
}
|
|
52
|
+
const out = new Uint8Array(size)
|
|
53
|
+
const done = readInto(file, out, size, 0, path)
|
|
54
|
+
if (done !== size) {
|
|
55
|
+
throw new StoreHostError('SHORT_READ', `${path} shrank during the read (${done} of ${size} bytes)`, { path })
|
|
56
|
+
}
|
|
57
|
+
return out
|
|
58
|
+
} finally {
|
|
59
|
+
file.close()
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function readRange (path, offset, length) {
|
|
64
|
+
if (length === 0) return new Uint8Array(0)
|
|
65
|
+
const file = openRead(path)
|
|
66
|
+
try {
|
|
67
|
+
const out = new Uint8Array(length)
|
|
68
|
+
const done = readInto(file, out, length, offset, path)
|
|
69
|
+
if (done !== length) {
|
|
70
|
+
throw new StoreHostError(
|
|
71
|
+
'SHORT_READ',
|
|
72
|
+
`${path} returned ${done} of ${length} bytes at offset ${offset}`,
|
|
73
|
+
{ path }
|
|
74
|
+
)
|
|
75
|
+
}
|
|
76
|
+
return out
|
|
77
|
+
} finally {
|
|
78
|
+
file.close()
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Read at most `length` bytes at `offset`. Unlike `readRange` a short read
|
|
84
|
+
* is NOT an error: it is how the reader learns it reached the end of the
|
|
85
|
+
* file. The packer streams a file it did not stat first, so it needs this
|
|
86
|
+
* shape rather than the exact-length one.
|
|
87
|
+
*/
|
|
88
|
+
export function readChunk (path, offset, length) {
|
|
89
|
+
if (length === 0) return new Uint8Array(0)
|
|
90
|
+
const file = openRead(path)
|
|
91
|
+
try {
|
|
92
|
+
const out = new Uint8Array(length)
|
|
93
|
+
const done = readInto(file, out, length, offset, path)
|
|
94
|
+
return done === length ? out : out.subarray(0, done)
|
|
95
|
+
} finally {
|
|
96
|
+
file.close()
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Create `path` and write `bytes`. Refuses to replace an existing file:
|
|
102
|
+
* a generation directory is immutable once written, and a packer that
|
|
103
|
+
* silently overwrote an artifact would hide a name collision.
|
|
104
|
+
*/
|
|
105
|
+
export function writeNew (path, bytes) {
|
|
106
|
+
let file
|
|
107
|
+
try {
|
|
108
|
+
file = Deno.openSync(path, { write: true, createNew: true })
|
|
109
|
+
} catch (cause) {
|
|
110
|
+
if (cause instanceof Deno.errors.AlreadyExists) {
|
|
111
|
+
throw new StoreHostError('FILE_EXISTS', `${path} already exists`, { path })
|
|
112
|
+
}
|
|
113
|
+
throw wrap('OPEN_FAILED', `cannot create ${path}`, path, cause)
|
|
114
|
+
}
|
|
115
|
+
try {
|
|
116
|
+
let done = 0
|
|
117
|
+
while (done < bytes.length) {
|
|
118
|
+
let written
|
|
119
|
+
try {
|
|
120
|
+
written = file.writeSync(bytes.subarray(done))
|
|
121
|
+
} catch (cause) {
|
|
122
|
+
throw wrap('WRITE_FAILED', `write failed on ${path}`, path, cause)
|
|
123
|
+
}
|
|
124
|
+
done += written
|
|
125
|
+
}
|
|
126
|
+
try {
|
|
127
|
+
file.syncSync()
|
|
128
|
+
} catch (cause) {
|
|
129
|
+
throw wrap('FSYNC_FAILED', `fsync failed on ${path}`, path, cause)
|
|
130
|
+
}
|
|
131
|
+
} finally {
|
|
132
|
+
file.close()
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** Create a directory and every missing parent. */
|
|
137
|
+
export function makeDirectory (path) {
|
|
138
|
+
try {
|
|
139
|
+
Deno.mkdirSync(path, { recursive: true })
|
|
140
|
+
} catch (cause) {
|
|
141
|
+
throw wrap('MKDIR_FAILED', `cannot create ${path}`, path, cause)
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export function appendSyncAtSize (path, bytes, expectedSize) {
|
|
146
|
+
let file
|
|
147
|
+
try {
|
|
148
|
+
file = Deno.openSync(path, { write: true, create: true, append: true })
|
|
149
|
+
} catch (cause) {
|
|
150
|
+
throw wrap('OPEN_FAILED', `cannot open ${path} for append`, path, cause)
|
|
151
|
+
}
|
|
152
|
+
try {
|
|
153
|
+
const size = file.statSync().size
|
|
154
|
+
if (size !== expectedSize) return false
|
|
155
|
+
let done = 0
|
|
156
|
+
while (done < bytes.length) {
|
|
157
|
+
let written
|
|
158
|
+
try {
|
|
159
|
+
written = file.writeSync(bytes.subarray(done))
|
|
160
|
+
} catch (cause) {
|
|
161
|
+
throw wrap('WRITE_FAILED', `append failed on ${path}`, path, cause)
|
|
162
|
+
}
|
|
163
|
+
done += written
|
|
164
|
+
}
|
|
165
|
+
try {
|
|
166
|
+
file.syncSync()
|
|
167
|
+
} catch (cause) {
|
|
168
|
+
throw wrap('FSYNC_FAILED', `fsync failed on ${path}`, path, cause)
|
|
169
|
+
}
|
|
170
|
+
return true
|
|
171
|
+
} finally {
|
|
172
|
+
file.close()
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function temporaryName (path) {
|
|
177
|
+
const suffix = Math.floor(Math.random() * 0xffffff).toString(16).padStart(6, '0')
|
|
178
|
+
return joinPath(dirName(path), baseName(path) + '.tmp.' + suffix)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function fsyncDirectory (directory) {
|
|
182
|
+
let handle
|
|
183
|
+
try {
|
|
184
|
+
handle = Deno.openSync(directory, { read: true })
|
|
185
|
+
} catch (cause) {
|
|
186
|
+
throw wrap('DIR_OPEN_FAILED', `cannot open ${directory} to sync it`, directory, cause)
|
|
187
|
+
}
|
|
188
|
+
try {
|
|
189
|
+
handle.syncSync()
|
|
190
|
+
} finally {
|
|
191
|
+
handle.close()
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export function atomicReplace (path, bytes) {
|
|
196
|
+
const directory = dirName(path)
|
|
197
|
+
let temporary = null
|
|
198
|
+
let file = null
|
|
199
|
+
try {
|
|
200
|
+
for (let attempt = 0; attempt < 8 && file === null; attempt += 1) {
|
|
201
|
+
temporary = temporaryName(path)
|
|
202
|
+
try {
|
|
203
|
+
file = Deno.openSync(temporary, { write: true, createNew: true })
|
|
204
|
+
} catch (cause) {
|
|
205
|
+
if (cause instanceof Deno.errors.AlreadyExists) continue
|
|
206
|
+
throw wrap('OPEN_FAILED', `cannot create ${temporary}`, temporary, cause)
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
if (file === null) {
|
|
210
|
+
throw new StoreHostError('TEMP_NAME_EXHAUSTED', `no free temporary name beside ${path}`, { path })
|
|
211
|
+
}
|
|
212
|
+
let done = 0
|
|
213
|
+
while (done < bytes.length) {
|
|
214
|
+
let written
|
|
215
|
+
try {
|
|
216
|
+
written = file.writeSync(bytes.subarray(done))
|
|
217
|
+
} catch (cause) {
|
|
218
|
+
throw wrap('WRITE_FAILED', `write failed on ${temporary}`, temporary, cause)
|
|
219
|
+
}
|
|
220
|
+
done += written
|
|
221
|
+
}
|
|
222
|
+
file.syncSync()
|
|
223
|
+
file.close()
|
|
224
|
+
file = null
|
|
225
|
+
Deno.renameSync(temporary, path)
|
|
226
|
+
temporary = null
|
|
227
|
+
try {
|
|
228
|
+
fsyncDirectory(directory)
|
|
229
|
+
} catch (_error) {
|
|
230
|
+
return false
|
|
231
|
+
}
|
|
232
|
+
return true
|
|
233
|
+
} catch (error) {
|
|
234
|
+
if (file !== null) file.close()
|
|
235
|
+
if (temporary !== null) {
|
|
236
|
+
try { Deno.removeSync(temporary) } catch (_ignored) { /* the temporary may not exist */ }
|
|
237
|
+
}
|
|
238
|
+
throw error
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export function listGeneration (directory) {
|
|
243
|
+
const out = []
|
|
244
|
+
let entries
|
|
245
|
+
try {
|
|
246
|
+
entries = [...Deno.readDirSync(directory)]
|
|
247
|
+
} catch (cause) {
|
|
248
|
+
throw wrap('DIR_READ_FAILED', `cannot list ${directory}`, directory, cause)
|
|
249
|
+
}
|
|
250
|
+
entries.sort((left, right) => (left.name < right.name ? -1 : left.name > right.name ? 1 : 0))
|
|
251
|
+
for (const entry of entries) {
|
|
252
|
+
if (!entry.isFile) continue
|
|
253
|
+
let info
|
|
254
|
+
try {
|
|
255
|
+
info = Deno.statSync(joinPath(directory, entry.name))
|
|
256
|
+
} catch (_cause) {
|
|
257
|
+
continue
|
|
258
|
+
}
|
|
259
|
+
out.push({ name: entry.name, size: info.size })
|
|
260
|
+
}
|
|
261
|
+
return out
|
|
262
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// Error type shared by the Node and Deno host-I/O implementations.
|
|
2
|
+
//
|
|
3
|
+
// The store host moves bytes. It never reports a FORMAT decision, so no
|
|
4
|
+
// error code here names a manifest, a digest, a block or a generation
|
|
5
|
+
// layout: those decisions belong to the Lean source
|
|
6
|
+
// (https://github.com/danbri/factoidal/issues/641, iron rule 7).
|
|
7
|
+
|
|
8
|
+
export class StoreHostError extends Error {
|
|
9
|
+
/**
|
|
10
|
+
* @param {string} code stable machine-readable code
|
|
11
|
+
* @param {string} message
|
|
12
|
+
* @param {{cause?: unknown, path?: string}} [detail]
|
|
13
|
+
*/
|
|
14
|
+
constructor (code, message, detail = {}) {
|
|
15
|
+
super(message, detail.cause === undefined ? undefined : { cause: detail.cause })
|
|
16
|
+
this.name = 'StoreHostError'
|
|
17
|
+
this.code = code
|
|
18
|
+
if (detail.path !== undefined) this.path = detail.path
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Reject an argument that is not a usable filesystem path. */
|
|
23
|
+
export function requirePath (value, label) {
|
|
24
|
+
if (typeof value !== 'string' || value.length === 0) {
|
|
25
|
+
throw new StoreHostError('BAD_ARGUMENT', `${label} must be a non-empty string`)
|
|
26
|
+
}
|
|
27
|
+
if (value.indexOf('\u0000') >= 0) {
|
|
28
|
+
throw new StoreHostError('BAD_ARGUMENT', `${label} must not contain a NUL byte`)
|
|
29
|
+
}
|
|
30
|
+
return value
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Reject an offset or length that a 64-bit host accepts and JavaScript
|
|
35
|
+
* cannot represent exactly. The C extern refuses `offset > INT64_MAX`;
|
|
36
|
+
* a JavaScript number stops being exact at 2^53 - 1, so that is the
|
|
37
|
+
* limit this module enforces.
|
|
38
|
+
*/
|
|
39
|
+
export function requireCount (value, label) {
|
|
40
|
+
if (typeof value !== 'number' || !Number.isSafeInteger(value) || value < 0) {
|
|
41
|
+
throw new StoreHostError(
|
|
42
|
+
'BAD_ARGUMENT',
|
|
43
|
+
`${label} must be a non-negative integer below 2^53`
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
return value
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Reject a payload that is not raw bytes. */
|
|
50
|
+
export function requireBytes (value, label) {
|
|
51
|
+
if (!(value instanceof Uint8Array)) {
|
|
52
|
+
throw new StoreHostError('BAD_ARGUMENT', `${label} must be a Uint8Array`)
|
|
53
|
+
}
|
|
54
|
+
return value
|
|
55
|
+
}
|