@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.
Files changed (132) hide show
  1. package/CHANGELOG.md +122 -0
  2. package/NOTICE +31 -0
  3. package/README.md +131 -1
  4. package/bin/engine.mjs +124 -0
  5. package/bin/factoidal.mjs +894 -0
  6. package/bin/pack-host.mjs +341 -0
  7. package/bin/pack-worker.mjs +31 -0
  8. package/bin/pack.mjs +174 -0
  9. package/bin/store.mjs +238 -0
  10. package/l4-assets/l4factoidal.js +125 -5
  11. package/l4-assets/l4factoidal.mjs +1 -1
  12. package/l4-assets/l4factoidal.wasm +0 -0
  13. package/l4-assets/package.json +4 -0
  14. package/l4-assets/version.json +5 -5
  15. package/l4.d.ts +14 -0
  16. package/l4.js +15 -1
  17. package/package.json +17 -2
  18. package/sample-store/CURRENT +1 -0
  19. package/sample-store/gen-1/manifest.sbm2 +0 -0
  20. package/sample-store/gen-1/manifest.tsv +14 -0
  21. package/sample-store/gen-1/predicate-0.ibk3 +0 -0
  22. package/sample-store/gen-1/predicate-0.ibk3.merkle +1 -0
  23. package/sample-store/gen-1/predicate-0.ibk3.oli2 +0 -0
  24. package/sample-store/gen-1/predicate-0.ibk3.oli2.merkle +1 -0
  25. package/sample-store/gen-1/predicate-0.ibk3.sri2 +0 -0
  26. package/sample-store/gen-1/predicate-0.ibk3.sri2.merkle +1 -0
  27. package/sample-store/gen-1/predicate-0.ibk3.tli1 +0 -0
  28. package/sample-store/gen-1/predicate-0.ibk3.tli1.merkle +0 -0
  29. package/sample-store/gen-1/predicate-1.ibk3 +0 -0
  30. package/sample-store/gen-1/predicate-1.ibk3.merkle +0 -0
  31. package/sample-store/gen-1/predicate-1.ibk3.oli2 +0 -0
  32. package/sample-store/gen-1/predicate-1.ibk3.oli2.merkle +0 -0
  33. package/sample-store/gen-1/predicate-1.ibk3.sri2 +0 -0
  34. package/sample-store/gen-1/predicate-1.ibk3.sri2.merkle +1 -0
  35. package/sample-store/gen-1/predicate-1.ibk3.tli1 +0 -0
  36. package/sample-store/gen-1/predicate-1.ibk3.tli1.merkle +1 -0
  37. package/sample-store/gen-1/predicate-10.ibk3 +0 -0
  38. package/sample-store/gen-1/predicate-10.ibk3.merkle +1 -0
  39. package/sample-store/gen-1/predicate-10.ibk3.oli2 +0 -0
  40. package/sample-store/gen-1/predicate-10.ibk3.oli2.merkle +0 -0
  41. package/sample-store/gen-1/predicate-10.ibk3.sri2 +0 -0
  42. package/sample-store/gen-1/predicate-10.ibk3.sri2.merkle +1 -0
  43. package/sample-store/gen-1/predicate-10.ibk3.tli1 +0 -0
  44. package/sample-store/gen-1/predicate-10.ibk3.tli1.merkle +1 -0
  45. package/sample-store/gen-1/predicate-11.ibk3 +0 -0
  46. package/sample-store/gen-1/predicate-11.ibk3.merkle +0 -0
  47. package/sample-store/gen-1/predicate-11.ibk3.oli2 +0 -0
  48. package/sample-store/gen-1/predicate-11.ibk3.oli2.merkle +1 -0
  49. package/sample-store/gen-1/predicate-11.ibk3.sri2 +0 -0
  50. package/sample-store/gen-1/predicate-11.ibk3.sri2.merkle +1 -0
  51. package/sample-store/gen-1/predicate-11.ibk3.tli1 +0 -0
  52. package/sample-store/gen-1/predicate-11.ibk3.tli1.merkle +1 -0
  53. package/sample-store/gen-1/predicate-12.ibk3 +0 -0
  54. package/sample-store/gen-1/predicate-12.ibk3.merkle +1 -0
  55. package/sample-store/gen-1/predicate-12.ibk3.oli2 +0 -0
  56. package/sample-store/gen-1/predicate-12.ibk3.oli2.merkle +1 -0
  57. package/sample-store/gen-1/predicate-12.ibk3.sri2 +0 -0
  58. package/sample-store/gen-1/predicate-12.ibk3.sri2.merkle +1 -0
  59. package/sample-store/gen-1/predicate-12.ibk3.tli1 +0 -0
  60. package/sample-store/gen-1/predicate-12.ibk3.tli1.merkle +1 -0
  61. package/sample-store/gen-1/predicate-2.ibk3 +0 -0
  62. package/sample-store/gen-1/predicate-2.ibk3.merkle +1 -0
  63. package/sample-store/gen-1/predicate-2.ibk3.oli2 +0 -0
  64. package/sample-store/gen-1/predicate-2.ibk3.oli2.merkle +1 -0
  65. package/sample-store/gen-1/predicate-2.ibk3.sri2 +0 -0
  66. package/sample-store/gen-1/predicate-2.ibk3.sri2.merkle +0 -0
  67. package/sample-store/gen-1/predicate-2.ibk3.tli1 +0 -0
  68. package/sample-store/gen-1/predicate-2.ibk3.tli1.merkle +0 -0
  69. package/sample-store/gen-1/predicate-3.ibk3 +0 -0
  70. package/sample-store/gen-1/predicate-3.ibk3.merkle +1 -0
  71. package/sample-store/gen-1/predicate-3.ibk3.oli2 +0 -0
  72. package/sample-store/gen-1/predicate-3.ibk3.oli2.merkle +1 -0
  73. package/sample-store/gen-1/predicate-3.ibk3.sri2 +0 -0
  74. package/sample-store/gen-1/predicate-3.ibk3.sri2.merkle +1 -0
  75. package/sample-store/gen-1/predicate-3.ibk3.tli1 +0 -0
  76. package/sample-store/gen-1/predicate-3.ibk3.tli1.merkle +1 -0
  77. package/sample-store/gen-1/predicate-4.ibk3 +0 -0
  78. package/sample-store/gen-1/predicate-4.ibk3.merkle +0 -0
  79. package/sample-store/gen-1/predicate-4.ibk3.oli2 +0 -0
  80. package/sample-store/gen-1/predicate-4.ibk3.oli2.merkle +1 -0
  81. package/sample-store/gen-1/predicate-4.ibk3.sri2 +0 -0
  82. package/sample-store/gen-1/predicate-4.ibk3.sri2.merkle +1 -0
  83. package/sample-store/gen-1/predicate-4.ibk3.tli1 +0 -0
  84. package/sample-store/gen-1/predicate-4.ibk3.tli1.merkle +2 -0
  85. package/sample-store/gen-1/predicate-5.ibk3 +0 -0
  86. package/sample-store/gen-1/predicate-5.ibk3.merkle +1 -0
  87. package/sample-store/gen-1/predicate-5.ibk3.oli2 +0 -0
  88. package/sample-store/gen-1/predicate-5.ibk3.oli2.merkle +1 -0
  89. package/sample-store/gen-1/predicate-5.ibk3.sri2 +0 -0
  90. package/sample-store/gen-1/predicate-5.ibk3.sri2.merkle +1 -0
  91. package/sample-store/gen-1/predicate-5.ibk3.tli1 +0 -0
  92. package/sample-store/gen-1/predicate-5.ibk3.tli1.merkle +1 -0
  93. package/sample-store/gen-1/predicate-6.ibk3 +0 -0
  94. package/sample-store/gen-1/predicate-6.ibk3.merkle +1 -0
  95. package/sample-store/gen-1/predicate-6.ibk3.oli2 +0 -0
  96. package/sample-store/gen-1/predicate-6.ibk3.oli2.merkle +1 -0
  97. package/sample-store/gen-1/predicate-6.ibk3.sri2 +0 -0
  98. package/sample-store/gen-1/predicate-6.ibk3.sri2.merkle +1 -0
  99. package/sample-store/gen-1/predicate-6.ibk3.tli1 +0 -0
  100. package/sample-store/gen-1/predicate-6.ibk3.tli1.merkle +0 -0
  101. package/sample-store/gen-1/predicate-7.ibk3 +0 -0
  102. package/sample-store/gen-1/predicate-7.ibk3.merkle +0 -0
  103. package/sample-store/gen-1/predicate-7.ibk3.oli2 +0 -0
  104. package/sample-store/gen-1/predicate-7.ibk3.oli2.merkle +2 -0
  105. package/sample-store/gen-1/predicate-7.ibk3.sri2 +0 -0
  106. package/sample-store/gen-1/predicate-7.ibk3.sri2.merkle +1 -0
  107. package/sample-store/gen-1/predicate-7.ibk3.tli1 +0 -0
  108. package/sample-store/gen-1/predicate-7.ibk3.tli1.merkle +1 -0
  109. package/sample-store/gen-1/predicate-8.ibk3 +0 -0
  110. package/sample-store/gen-1/predicate-8.ibk3.merkle +1 -0
  111. package/sample-store/gen-1/predicate-8.ibk3.oli2 +0 -0
  112. package/sample-store/gen-1/predicate-8.ibk3.oli2.merkle +1 -0
  113. package/sample-store/gen-1/predicate-8.ibk3.sri2 +0 -0
  114. package/sample-store/gen-1/predicate-8.ibk3.sri2.merkle +1 -0
  115. package/sample-store/gen-1/predicate-8.ibk3.tli1 +0 -0
  116. package/sample-store/gen-1/predicate-8.ibk3.tli1.merkle +1 -0
  117. package/sample-store/gen-1/predicate-9.ibk3 +0 -0
  118. package/sample-store/gen-1/predicate-9.ibk3.merkle +1 -0
  119. package/sample-store/gen-1/predicate-9.ibk3.oli2 +0 -0
  120. package/sample-store/gen-1/predicate-9.ibk3.oli2.merkle +1 -0
  121. package/sample-store/gen-1/predicate-9.ibk3.sri2 +0 -0
  122. package/sample-store/gen-1/predicate-9.ibk3.sri2.merkle +3 -0
  123. package/sample-store/gen-1/predicate-9.ibk3.tli1 +0 -0
  124. package/sample-store/gen-1/predicate-9.ibk3.tli1.merkle +2 -0
  125. package/sample-store.d.ts +16 -0
  126. package/sample-store.mjs +37 -0
  127. package/store-host/deno.mjs +262 -0
  128. package/store-host/errors.mjs +55 -0
  129. package/store-host/index.mjs +247 -0
  130. package/store-host/node.mjs +273 -0
  131. package/store-host/paths.mjs +77 -0
  132. package/version.json +22 -21
@@ -0,0 +1 @@
1
+ �R�2 ���mcY( ]HI��
@@ -0,0 +1 @@
1
+ ��Q�+_�`*YN� ;B��E�<Ls�+d���
@@ -0,0 +1 @@
1
+ �c����E���EҮ���J�x?ݣ67���
@@ -0,0 +1 @@
1
+ 5�z�@d���Z�����Kg� ��U,�"�{�l��q֪נ�c�,��@$�zo���Q���
@@ -0,0 +1 @@
1
+ �$�����B�7�!bUs�2���1��S�f
@@ -0,0 +1 @@
1
+ 5c�X���,'C����8DYӻ[����t�@�/l
@@ -0,0 +1,2 @@
1
+ X��N�����(`tw8a�sdR���
2
+ H�:���
@@ -0,0 +1 @@
1
+ '�=�U)B:��ӯ�ٳ�{I��4FoֹJKA
@@ -0,0 +1 @@
1
+ i���<�%4�0���z."~�v4n�* $ǝ
@@ -0,0 +1 @@
1
+ ��(WUVh�ؠ$w���6�zh��L�Qo���
@@ -0,0 +1 @@
1
+ �Đ��5 �k��EQ2��zE��;��oɗ�
@@ -0,0 +1 @@
1
+ ���2N����Y�M~8������3�(z>l�r��
@@ -0,0 +1 @@
1
+ �6�� %v� .+u���}0�v�0��������
@@ -0,0 +1 @@
1
+ q�/F����AL&��z�1�z�YP�x#
@@ -0,0 +1,2 @@
1
+ �w�#���H��Ѐ:4����<V�
2
+ Q�n��
@@ -0,0 +1 @@
1
+ �~��CV�L��3&#�|��D�6�� ��
@@ -0,0 +1 @@
1
+ �8}� �P0�oK���5t ����8�fOR�
@@ -0,0 +1 @@
1
+ _��k��2Ǟ��bk[�r�Փ���>+�8�"
@@ -0,0 +1 @@
1
+ ���]",�b��\�B\�$o n�ΰ�gcEb
@@ -0,0 +1 @@
1
+ `��y�
@@ -0,0 +1 @@
1
+ Zvt&��Sv�V�u¹t��ڼ{7�MP��j*
@@ -0,0 +1 @@
1
+ !'M�Uw����,GD��������A�|���a�
@@ -0,0 +1,3 @@
1
+ ��9\X��
2
+
3
+ �cGC�%Mο�c���*��G?
@@ -0,0 +1,2 @@
1
+
2
+ >��fa���?��i�+-N���>R��
@@ -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
@@ -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
+ }