@bandeira-tech/b3nd-save 0.8.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/LICENSE +21 -0
- package/esm/_dnt.shims.d.ts +2 -0
- package/esm/_dnt.shims.d.ts.map +1 -0
- package/esm/_dnt.shims.js +57 -0
- package/esm/byte-entity-shim.d.ts +47 -0
- package/esm/byte-entity-shim.d.ts.map +1 -0
- package/esm/byte-entity-shim.js +138 -0
- package/esm/clients/mod.d.ts +15 -0
- package/esm/clients/mod.d.ts.map +1 -0
- package/esm/clients/mod.js +14 -0
- package/esm/clients/save-client.d.ts +87 -0
- package/esm/clients/save-client.d.ts.map +1 -0
- package/esm/clients/save-client.js +174 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/mod.d.ts +40 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/mod.d.ts.map +1 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/mod.js +31 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/client-console/client.d.ts +30 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/client-console/client.d.ts.map +1 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/client-console/client.js +70 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/encoding/encoding.d.ts +16 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/encoding/encoding.d.ts.map +1 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/encoding/encoding.js +50 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/encrypt/mod.d.ts +235 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/encrypt/mod.d.ts.map +1 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/encrypt/mod.js +619 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/functional-client/functional-client.d.ts +43 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/functional-client/functional-client.d.ts.map +1 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/functional-client/functional-client.js +54 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/match-pattern/match-pattern.d.ts +47 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/match-pattern/match-pattern.d.ts.map +1 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/match-pattern/match-pattern.js +134 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/decorators.d.ts +20 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/decorators.d.ts.map +1 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/decorators.js +34 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/network.d.ts +46 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/network.d.ts.map +1 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/network.js +131 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/peer.d.ts +27 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/peer.d.ts.map +1 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/peer.js +25 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/policies/flood.d.ts +59 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/policies/flood.d.ts.map +1 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/policies/flood.js +188 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/policies/path-vector.d.ts +35 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/policies/path-vector.d.ts.map +1 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/policies/path-vector.js +57 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/policies/tell-and-read.d.ts +117 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/policies/tell-and-read.d.ts.map +1 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/policies/tell-and-read.js +125 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/types.d.ts +112 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/types.d.ts.map +1 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/types.js +21 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/observe-emitter/observe-emitter.d.ts +35 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/observe-emitter/observe-emitter.d.ts.map +1 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/observe-emitter/observe-emitter.js +106 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/connection.d.ts +88 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/connection.d.ts.map +1 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/connection.js +86 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/events.d.ts +83 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/events.d.ts.map +1 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/events.js +115 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/hooks.d.ts +164 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/hooks.d.ts.map +1 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/hooks.js +67 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/identity.d.ts +99 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/identity.d.ts.map +1 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/identity.js +190 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/operation-handle.d.ts +185 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/operation-handle.d.ts.map +1 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/operation-handle.js +103 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/reactions.d.ts +58 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/reactions.d.ts.map +1 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/reactions.js +64 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/rig.d.ts +295 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/rig.d.ts.map +1 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/rig.js +898 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/types.d.ts +167 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/types.d.ts.map +1 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/types.js +5 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/types/types.d.ts +278 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/types/types.d.ts.map +1 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/types/types.js +89 -0
- package/esm/dispatch.d.ts +35 -0
- package/esm/dispatch.d.ts.map +1 -0
- package/esm/dispatch.js +40 -0
- package/esm/elasticsearch/mod.d.ts +31 -0
- package/esm/elasticsearch/mod.d.ts.map +1 -0
- package/esm/elasticsearch/mod.js +8 -0
- package/esm/elasticsearch/store.d.ts +44 -0
- package/esm/elasticsearch/store.d.ts.map +1 -0
- package/esm/elasticsearch/store.js +213 -0
- package/esm/entity-store.d.ts +106 -0
- package/esm/entity-store.d.ts.map +1 -0
- package/esm/entity-store.js +46 -0
- package/esm/entity.d.ts +125 -0
- package/esm/entity.d.ts.map +1 -0
- package/esm/entity.js +66 -0
- package/esm/errors.d.ts +23 -0
- package/esm/errors.d.ts.map +1 -0
- package/esm/errors.js +25 -0
- package/esm/fs/mod.d.ts +28 -0
- package/esm/fs/mod.d.ts.map +1 -0
- package/esm/fs/mod.js +13 -0
- package/esm/fs/store.d.ts +45 -0
- package/esm/fs/store.d.ts.map +1 -0
- package/esm/fs/store.js +194 -0
- package/esm/indexeddb/mod.d.ts +9 -0
- package/esm/indexeddb/mod.d.ts.map +1 -0
- package/esm/indexeddb/mod.js +8 -0
- package/esm/indexeddb/store.d.ts +47 -0
- package/esm/indexeddb/store.d.ts.map +1 -0
- package/esm/indexeddb/store.js +299 -0
- package/esm/ipfs/mod.d.ts +24 -0
- package/esm/ipfs/mod.d.ts.map +1 -0
- package/esm/ipfs/mod.js +13 -0
- package/esm/ipfs/store.d.ts +42 -0
- package/esm/ipfs/store.d.ts.map +1 -0
- package/esm/ipfs/store.js +197 -0
- package/esm/localstorage/mod.d.ts +9 -0
- package/esm/localstorage/mod.d.ts.map +1 -0
- package/esm/localstorage/mod.js +8 -0
- package/esm/localstorage/store.d.ts +45 -0
- package/esm/localstorage/store.d.ts.map +1 -0
- package/esm/localstorage/store.js +147 -0
- package/esm/memory/mod.d.ts +12 -0
- package/esm/memory/mod.d.ts.map +1 -0
- package/esm/memory/mod.js +11 -0
- package/esm/memory/store.d.ts +73 -0
- package/esm/memory/store.d.ts.map +1 -0
- package/esm/memory/store.js +300 -0
- package/esm/mod.d.ts +36 -0
- package/esm/mod.d.ts.map +1 -0
- package/esm/mod.js +31 -0
- package/esm/mongo/mod.d.ts +35 -0
- package/esm/mongo/mod.d.ts.map +1 -0
- package/esm/mongo/mod.js +8 -0
- package/esm/mongo/store.d.ts +42 -0
- package/esm/mongo/store.d.ts.map +1 -0
- package/esm/mongo/store.js +185 -0
- package/esm/package.json +3 -0
- package/esm/payload.d.ts +24 -0
- package/esm/payload.d.ts.map +1 -0
- package/esm/payload.js +30 -0
- package/esm/postgres/columns.d.ts +33 -0
- package/esm/postgres/columns.d.ts.map +1 -0
- package/esm/postgres/columns.js +51 -0
- package/esm/postgres/mod.d.ts +19 -0
- package/esm/postgres/mod.d.ts.map +1 -0
- package/esm/postgres/mod.js +9 -0
- package/esm/postgres/store.d.ts +75 -0
- package/esm/postgres/store.d.ts.map +1 -0
- package/esm/postgres/store.js +364 -0
- package/esm/read.d.ts +40 -0
- package/esm/read.d.ts.map +1 -0
- package/esm/read.js +67 -0
- package/esm/s3/mod.d.ts +23 -0
- package/esm/s3/mod.d.ts.map +1 -0
- package/esm/s3/mod.js +13 -0
- package/esm/s3/store.d.ts +46 -0
- package/esm/s3/store.d.ts.map +1 -0
- package/esm/s3/store.js +183 -0
- package/esm/sqlite/mod.d.ts +18 -0
- package/esm/sqlite/mod.d.ts.map +1 -0
- package/esm/sqlite/mod.js +8 -0
- package/esm/sqlite/schema.d.ts +10 -0
- package/esm/sqlite/schema.d.ts.map +1 -0
- package/esm/sqlite/schema.js +26 -0
- package/esm/sqlite/store.d.ts +42 -0
- package/esm/sqlite/store.d.ts.map +1 -0
- package/esm/sqlite/store.js +184 -0
- package/esm/types.d.ts +101 -0
- package/esm/types.d.ts.map +1 -0
- package/esm/types.js +21 -0
- package/esm/url.d.ts +108 -0
- package/esm/url.d.ts.map +1 -0
- package/esm/url.js +151 -0
- package/package.json +93 -0
package/esm/read.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-process post-processing for `fn=ls` results.
|
|
3
|
+
*
|
|
4
|
+
* Stores that cannot push `sortBy`/`limit`/`page`/`format` down to
|
|
5
|
+
* their backend collect the raw `Output[]` rows under a prefix and
|
|
6
|
+
* pipe them through `applyReadParams`. Stores that CAN push these
|
|
7
|
+
* down (postgres, mongo, elasticsearch, s3) should skip this helper
|
|
8
|
+
* and handle the params in their query.
|
|
9
|
+
*
|
|
10
|
+
* Throws on unsupported params — programmer errors are not silent
|
|
11
|
+
* "misses." See project decisions in `project_core_upgrade.md`.
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Validate standard ReadParams and throw on anything we cannot honor.
|
|
15
|
+
*
|
|
16
|
+
* Push-down stores (postgres, mongo, ES, S3) call this first, then
|
|
17
|
+
* translate the surviving params into their backend query language.
|
|
18
|
+
* Stores that post-process in memory call `applyReadParams` instead,
|
|
19
|
+
* which validates and applies in one go.
|
|
20
|
+
*
|
|
21
|
+
* Project-wide baseline: `pattern` and `cursor` are unsupported
|
|
22
|
+
* everywhere; `sortBy` only accepts `"uri"`; `format` only accepts
|
|
23
|
+
* `"full"` (default) or `"uris"`. Per-store relaxations should be
|
|
24
|
+
* added explicitly as features land.
|
|
25
|
+
*/
|
|
26
|
+
export function validateReadParams(params, storeName) {
|
|
27
|
+
if (params.pattern !== undefined) {
|
|
28
|
+
throw new Error(`${storeName}: pattern filter not supported`);
|
|
29
|
+
}
|
|
30
|
+
if (params.cursor !== undefined) {
|
|
31
|
+
throw new Error(`${storeName}: cursor not supported`);
|
|
32
|
+
}
|
|
33
|
+
if (params.sortBy !== undefined && params.sortBy !== "uri") {
|
|
34
|
+
throw new Error(`${storeName}: unsupported sortBy: ${params.sortBy}`);
|
|
35
|
+
}
|
|
36
|
+
const format = params.format ?? "full";
|
|
37
|
+
if (format !== "full" && format !== "uris") {
|
|
38
|
+
throw new Error(`${storeName}: unsupported format: ${format}`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Apply standard ReadParams to a list of rows.
|
|
43
|
+
*
|
|
44
|
+
* @param rows raw `[uri, payload]` entries collected from the backend
|
|
45
|
+
* @param params parsed read params (from `parseUrl(...).params`)
|
|
46
|
+
* @param storeName label used in thrown error messages
|
|
47
|
+
*
|
|
48
|
+
* Returns `Output[]` when `format` is `"full"` (default) or `string[]`
|
|
49
|
+
* when `format` is `"uris"`.
|
|
50
|
+
*/
|
|
51
|
+
export function applyReadParams(rows, params, storeName) {
|
|
52
|
+
validateReadParams(params, storeName);
|
|
53
|
+
const format = params.format ?? "full";
|
|
54
|
+
let out = rows;
|
|
55
|
+
if (params.sortBy === "uri") {
|
|
56
|
+
const dir = params.sortOrder === "desc" ? -1 : 1;
|
|
57
|
+
out = [...out].sort(([a], [b]) => a.localeCompare(b) * dir);
|
|
58
|
+
}
|
|
59
|
+
if (params.limit !== undefined) {
|
|
60
|
+
const page = params.page ?? 1;
|
|
61
|
+
const start = (page - 1) * params.limit;
|
|
62
|
+
out = out.slice(start, start + params.limit);
|
|
63
|
+
}
|
|
64
|
+
if (format === "uris")
|
|
65
|
+
return out.map(([uri]) => uri);
|
|
66
|
+
return out;
|
|
67
|
+
}
|
package/esm/s3/mod.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* S3 backend for b3nd.
|
|
3
|
+
*
|
|
4
|
+
* Store implementation backed by Amazon S3. Requires an injected
|
|
5
|
+
* S3Executor so the package does not depend on a specific S3 library.
|
|
6
|
+
* Object bodies hold raw payload bytes — the store is opaque.
|
|
7
|
+
*
|
|
8
|
+
* `putObject` accepts the `StorePayload` union (fetch handles either
|
|
9
|
+
* bytes or a `ReadableStream` as a request body); `getObject` returns
|
|
10
|
+
* a `ReadableStream<Uint8Array>` so callers don't have to materialize
|
|
11
|
+
* the whole object in memory unless they want to.
|
|
12
|
+
*/
|
|
13
|
+
import type { StorePayload } from "../types.js";
|
|
14
|
+
export interface S3Executor {
|
|
15
|
+
putObject: (key: string, body: StorePayload, contentType: string) => Promise<void>;
|
|
16
|
+
getObject: (key: string) => Promise<ReadableStream<Uint8Array> | null>;
|
|
17
|
+
deleteObject: (key: string) => Promise<void>;
|
|
18
|
+
listObjects: (prefix: string) => Promise<string[]>;
|
|
19
|
+
headBucket: () => Promise<boolean>;
|
|
20
|
+
cleanup?: () => Promise<void>;
|
|
21
|
+
}
|
|
22
|
+
export { S3Store } from "./store.js";
|
|
23
|
+
//# sourceMappingURL=mod.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mod.d.ts","sourceRoot":"","sources":["../../src/s3/mod.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,MAAM,WAAW,UAAU;IACzB,SAAS,EAAE,CACT,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,YAAY,EAClB,WAAW,EAAE,MAAM,KAChB,OAAO,CAAC,IAAI,CAAC,CAAC;IACnB,SAAS,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,cAAc,CAAC,UAAU,CAAC,GAAG,IAAI,CAAC,CAAC;IACvE,YAAY,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7C,WAAW,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IACnD,UAAU,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;IACnC,OAAO,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/B;AAED,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC"}
|
package/esm/s3/mod.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* S3 backend for b3nd.
|
|
3
|
+
*
|
|
4
|
+
* Store implementation backed by Amazon S3. Requires an injected
|
|
5
|
+
* S3Executor so the package does not depend on a specific S3 library.
|
|
6
|
+
* Object bodies hold raw payload bytes — the store is opaque.
|
|
7
|
+
*
|
|
8
|
+
* `putObject` accepts the `StorePayload` union (fetch handles either
|
|
9
|
+
* bytes or a `ReadableStream` as a request body); `getObject` returns
|
|
10
|
+
* a `ReadableStream<Uint8Array>` so callers don't have to materialize
|
|
11
|
+
* the whole object in memory unless they want to.
|
|
12
|
+
*/
|
|
13
|
+
export { S3Store } from "./store.js";
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* S3Store — Amazon S3 implementation of Store.
|
|
3
|
+
*
|
|
4
|
+
* Pure mechanical byte storage with no protocol awareness. One S3
|
|
5
|
+
* object per entry. Object body is the payload bytes verbatim; the
|
|
6
|
+
* store is opaque.
|
|
7
|
+
*
|
|
8
|
+
* `fn=ls` / `fn=count` are shallow direct-leaves only: list objects
|
|
9
|
+
* under the URI's key prefix, then filter to keys whose remainder
|
|
10
|
+
* contains no further `/`. The `format=uris` fast path returns the
|
|
11
|
+
* URIs without issuing `getObject`; `format=full` only fetches the
|
|
12
|
+
* selected page.
|
|
13
|
+
*/
|
|
14
|
+
import type { DeleteResult, Output, StatusResult } from "../deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/types/types.js";
|
|
15
|
+
import { type BytesOnlyEntityMeta } from "../byte-entity-shim.js";
|
|
16
|
+
import type { EntityStore } from "../entity-store.js";
|
|
17
|
+
import type { EntityRecord, EntitySchema } from "../entity.js";
|
|
18
|
+
import type { StoreCapabilities, StoreWriteResult } from "../types.js";
|
|
19
|
+
import type { S3Executor } from "./mod.js";
|
|
20
|
+
export declare class S3Store implements EntityStore<BytesOnlyEntityMeta> {
|
|
21
|
+
private readonly bucket;
|
|
22
|
+
private readonly executor;
|
|
23
|
+
private readonly prefix;
|
|
24
|
+
constructor(bucket: string, executor: S3Executor, prefix?: string);
|
|
25
|
+
entitySupport(schema: EntitySchema): BytesOnlyEntityMeta;
|
|
26
|
+
entityStatus(meta: BytesOnlyEntityMeta): Promise<"live" | "unprovisioned">;
|
|
27
|
+
provisionEntity(_meta: BytesOnlyEntityMeta): Promise<void>;
|
|
28
|
+
write(meta: BytesOnlyEntityMeta, entries: {
|
|
29
|
+
uri: string;
|
|
30
|
+
record: EntityRecord;
|
|
31
|
+
}[]): Promise<StoreWriteResult[]>;
|
|
32
|
+
read<T = EntityRecord | undefined>(meta: BytesOnlyEntityMeta, urls: string[]): Promise<Output<T>[]>;
|
|
33
|
+
delete(meta: BytesOnlyEntityMeta, uris: string[]): Promise<DeleteResult[]>;
|
|
34
|
+
private _writeBytes;
|
|
35
|
+
private _readBytes;
|
|
36
|
+
private _readOne;
|
|
37
|
+
/** List direct-child URIs under a prefix (no recursion into sub-prefixes). */
|
|
38
|
+
private _listChildUris;
|
|
39
|
+
private _ls;
|
|
40
|
+
private _count;
|
|
41
|
+
private _deleteBytes;
|
|
42
|
+
status(): Promise<StatusResult>;
|
|
43
|
+
capabilities(): StoreCapabilities;
|
|
44
|
+
private _resolveKey;
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/s3/store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EACV,YAAY,EACZ,MAAM,EACN,YAAY,EACb,MAAM,mEAAmE,CAAC;AAC3E,OAAO,EAEL,KAAK,mBAAmB,EAIzB,MAAM,wBAAwB,CAAC;AAChC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAM/D,OAAO,KAAK,EACV,iBAAiB,EAEjB,gBAAgB,EACjB,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAgB3C,qBAAa,OAAQ,YAAW,WAAW,CAAC,mBAAmB,CAAC;IAC9D,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAa;IACtC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;gBAEpB,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,CAAC,EAAE,MAAM;IAWjE,aAAa,CAAC,MAAM,EAAE,YAAY,GAAG,mBAAmB;IAIxD,YAAY,CAAC,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAAC,MAAM,GAAG,eAAe,CAAC;IAI1E,eAAe,CAAC,KAAK,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC;IAI1D,KAAK,CACH,IAAI,EAAE,mBAAmB,EACzB,OAAO,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,YAAY,CAAA;KAAE,EAAE,GAC/C,OAAO,CAAC,gBAAgB,EAAE,CAAC;IAS9B,IAAI,CAAC,CAAC,GAAG,YAAY,GAAG,SAAS,EAC/B,IAAI,EAAE,mBAAmB,EACzB,IAAI,EAAE,MAAM,EAAE,GACb,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;IASvB,MAAM,CAAC,IAAI,EAAE,mBAAmB,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;YAW5D,WAAW;IAwBzB,OAAO,CAAC,UAAU;YAQJ,QAAQ;IAOtB,8EAA8E;YAChE,cAAc;YAad,GAAG;YA0BH,MAAM;YAON,YAAY;IAoBpB,MAAM,IAAI,OAAO,CAAC,YAAY,CAAC;IA4BrC,YAAY,IAAI,iBAAiB;IAIjC,OAAO,CAAC,WAAW;CAGpB"}
|
package/esm/s3/store.js
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* S3Store — Amazon S3 implementation of Store.
|
|
3
|
+
*
|
|
4
|
+
* Pure mechanical byte storage with no protocol awareness. One S3
|
|
5
|
+
* object per entry. Object body is the payload bytes verbatim; the
|
|
6
|
+
* store is opaque.
|
|
7
|
+
*
|
|
8
|
+
* `fn=ls` / `fn=count` are shallow direct-leaves only: list objects
|
|
9
|
+
* under the URI's key prefix, then filter to keys whose remainder
|
|
10
|
+
* contains no further `/`. The `format=uris` fast path returns the
|
|
11
|
+
* URIs without issuing `getObject`; `format=full` only fetches the
|
|
12
|
+
* selected page.
|
|
13
|
+
*/
|
|
14
|
+
import { bytesOnlyDelete, bytesOnlyEntitySupport, bytesOnlyRead, bytesOnlyWrite, } from "../byte-entity-shim.js";
|
|
15
|
+
import { dispatchRead } from "../dispatch.js";
|
|
16
|
+
import { storageFailure } from "../errors.js";
|
|
17
|
+
import { validateReadParams } from "../read.js";
|
|
18
|
+
const STORE_NAME = "S3Store";
|
|
19
|
+
const EXT = ".bin";
|
|
20
|
+
/** `protocol://host/path` → `protocol_host/path`. */
|
|
21
|
+
function uriToKey(uri) {
|
|
22
|
+
return uri.replace("://", "_").replace(/^\//, "");
|
|
23
|
+
}
|
|
24
|
+
/** Inverse of `uriToKey` with `.bin` suffix stripping. */
|
|
25
|
+
function keyTailToUri(tail) {
|
|
26
|
+
const noExt = tail.endsWith(EXT) ? tail.slice(0, -EXT.length) : tail;
|
|
27
|
+
return noExt.replace("_", "://");
|
|
28
|
+
}
|
|
29
|
+
export class S3Store {
|
|
30
|
+
bucket;
|
|
31
|
+
executor;
|
|
32
|
+
prefix;
|
|
33
|
+
constructor(bucket, executor, prefix) {
|
|
34
|
+
if (!bucket)
|
|
35
|
+
throw new Error("bucket is required");
|
|
36
|
+
if (!executor)
|
|
37
|
+
throw new Error("executor is required");
|
|
38
|
+
this.bucket = bucket;
|
|
39
|
+
this.executor = executor;
|
|
40
|
+
this.prefix = prefix ?? "";
|
|
41
|
+
}
|
|
42
|
+
// ── EntityStore surface ──────────────────────────────────────────
|
|
43
|
+
entitySupport(schema) {
|
|
44
|
+
return bytesOnlyEntitySupport(schema);
|
|
45
|
+
}
|
|
46
|
+
entityStatus(meta) {
|
|
47
|
+
return Promise.resolve(meta.isBytes ? "live" : "unprovisioned");
|
|
48
|
+
}
|
|
49
|
+
provisionEntity(_meta) {
|
|
50
|
+
return Promise.resolve();
|
|
51
|
+
}
|
|
52
|
+
write(meta, entries) {
|
|
53
|
+
return bytesOnlyWrite(meta, STORE_NAME, entries, (e) => this._writeBytes(e));
|
|
54
|
+
}
|
|
55
|
+
read(meta, urls) {
|
|
56
|
+
return bytesOnlyRead(meta, STORE_NAME, urls, (u) => this._readBytes(u));
|
|
57
|
+
}
|
|
58
|
+
delete(meta, uris) {
|
|
59
|
+
return bytesOnlyDelete(meta, STORE_NAME, uris, (u) => this._deleteBytes(u));
|
|
60
|
+
}
|
|
61
|
+
// ── Byte ops (BYTES_ENTITY routing) ──────────────────────────────
|
|
62
|
+
async _writeBytes(entries) {
|
|
63
|
+
const results = [];
|
|
64
|
+
for (const entry of entries) {
|
|
65
|
+
try {
|
|
66
|
+
await this.executor.putObject(this._resolveKey(entry.uri), entry.payload, "application/octet-stream");
|
|
67
|
+
results.push({ success: true });
|
|
68
|
+
}
|
|
69
|
+
catch (err) {
|
|
70
|
+
results.push({
|
|
71
|
+
success: false,
|
|
72
|
+
...storageFailure(err, "Write failed", entry.uri),
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return results;
|
|
77
|
+
}
|
|
78
|
+
_readBytes(urls) {
|
|
79
|
+
return dispatchRead(urls, STORE_NAME, {
|
|
80
|
+
read: (p) => this._readOne(p.uri),
|
|
81
|
+
ls: (p) => this._ls(p),
|
|
82
|
+
count: (p) => this._count(p),
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
async _readOne(uri) {
|
|
86
|
+
const content = await this.executor.getObject(this._resolveKey(uri));
|
|
87
|
+
return content ?? undefined;
|
|
88
|
+
}
|
|
89
|
+
/** List direct-child URIs under a prefix (no recursion into sub-prefixes). */
|
|
90
|
+
async _listChildUris(prefixUri) {
|
|
91
|
+
const keyPrefix = `${this.prefix}${uriToKey(prefixUri)}`;
|
|
92
|
+
const keys = await this.executor.listObjects(keyPrefix);
|
|
93
|
+
const uris = [];
|
|
94
|
+
for (const key of keys) {
|
|
95
|
+
if (!key.endsWith(EXT))
|
|
96
|
+
continue;
|
|
97
|
+
const tail = key.slice(keyPrefix.length);
|
|
98
|
+
if (tail.includes("/"))
|
|
99
|
+
continue; // nested, not a direct leaf
|
|
100
|
+
uris.push(`${prefixUri}${keyTailToUri(tail)}`);
|
|
101
|
+
}
|
|
102
|
+
return uris;
|
|
103
|
+
}
|
|
104
|
+
async _ls(parsed) {
|
|
105
|
+
validateReadParams(parsed.params, STORE_NAME);
|
|
106
|
+
const { params } = parsed;
|
|
107
|
+
const format = params.format ?? "full";
|
|
108
|
+
let uris = await this._listChildUris(parsed.uri);
|
|
109
|
+
if (params.sortBy === "uri") {
|
|
110
|
+
const dir = params.sortOrder === "desc" ? -1 : 1;
|
|
111
|
+
uris = [...uris].sort((a, b) => a.localeCompare(b) * dir);
|
|
112
|
+
}
|
|
113
|
+
if (params.limit !== undefined) {
|
|
114
|
+
const page = params.page ?? 1;
|
|
115
|
+
const start = (page - 1) * params.limit;
|
|
116
|
+
uris = uris.slice(start, start + params.limit);
|
|
117
|
+
}
|
|
118
|
+
if (format === "uris")
|
|
119
|
+
return uris;
|
|
120
|
+
const out = [];
|
|
121
|
+
for (const uri of uris) {
|
|
122
|
+
out.push([uri, await this._readOne(uri)]);
|
|
123
|
+
}
|
|
124
|
+
return out;
|
|
125
|
+
}
|
|
126
|
+
async _count(parsed) {
|
|
127
|
+
if (parsed.params.pattern !== undefined) {
|
|
128
|
+
throw new Error(`${STORE_NAME}: pattern filter not supported`);
|
|
129
|
+
}
|
|
130
|
+
return (await this._listChildUris(parsed.uri)).length;
|
|
131
|
+
}
|
|
132
|
+
async _deleteBytes(uris) {
|
|
133
|
+
const results = [];
|
|
134
|
+
for (const uri of uris) {
|
|
135
|
+
try {
|
|
136
|
+
await this.executor.deleteObject(this._resolveKey(uri));
|
|
137
|
+
results.push({ success: true });
|
|
138
|
+
}
|
|
139
|
+
catch (err) {
|
|
140
|
+
results.push({
|
|
141
|
+
success: false,
|
|
142
|
+
...storageFailure(err, "Delete failed", uri),
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return results;
|
|
147
|
+
}
|
|
148
|
+
// ── Status ───────────────────────────────────────────────────────
|
|
149
|
+
async status() {
|
|
150
|
+
try {
|
|
151
|
+
const ok = await this.executor.headBucket();
|
|
152
|
+
if (!ok) {
|
|
153
|
+
return {
|
|
154
|
+
status: "unhealthy",
|
|
155
|
+
message: `Bucket not accessible: ${this.bucket}`,
|
|
156
|
+
fns: ["read", "ls", "count"],
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
return {
|
|
160
|
+
status: "healthy",
|
|
161
|
+
message: "S3 store is operational",
|
|
162
|
+
fns: ["read", "ls", "count"],
|
|
163
|
+
details: {
|
|
164
|
+
bucket: this.bucket,
|
|
165
|
+
prefix: this.prefix || "(none)",
|
|
166
|
+
},
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
catch (error) {
|
|
170
|
+
return {
|
|
171
|
+
status: "unhealthy",
|
|
172
|
+
message: error instanceof Error ? error.message : String(error),
|
|
173
|
+
fns: ["read", "ls", "count"],
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
capabilities() {
|
|
178
|
+
return { atomicBatch: false };
|
|
179
|
+
}
|
|
180
|
+
_resolveKey(uri) {
|
|
181
|
+
return `${this.prefix}${uriToKey(uri)}${EXT}`;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SQLite backend for b3nd.
|
|
3
|
+
*
|
|
4
|
+
* Store implementation backed by SQLite. Requires an injected SqliteExecutor
|
|
5
|
+
* so the SDK does not depend on a specific SQLite driver.
|
|
6
|
+
*/
|
|
7
|
+
export interface SqliteExecutorResult {
|
|
8
|
+
rows: Record<string, unknown>[];
|
|
9
|
+
rowCount?: number;
|
|
10
|
+
}
|
|
11
|
+
export interface SqliteExecutor {
|
|
12
|
+
query: (sql: string, args?: unknown[]) => SqliteExecutorResult;
|
|
13
|
+
transaction: <T>(fn: (tx: SqliteExecutor) => T) => T;
|
|
14
|
+
cleanup?: () => void;
|
|
15
|
+
}
|
|
16
|
+
export { SqliteStore } from "./store.js";
|
|
17
|
+
export { generateSqliteSchema } from "./schema.js";
|
|
18
|
+
//# sourceMappingURL=mod.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mod.d.ts","sourceRoot":"","sources":["../../src/sqlite/mod.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;IAChC,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,EAAE,KAAK,oBAAoB,CAAC;IAC/D,WAAW,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,cAAc,KAAK,CAAC,KAAK,CAAC,CAAC;IACrD,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;CACtB;AAED,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SQLite backend for b3nd.
|
|
3
|
+
*
|
|
4
|
+
* Store implementation backed by SQLite. Requires an injected SqliteExecutor
|
|
5
|
+
* so the SDK does not depend on a specific SQLite driver.
|
|
6
|
+
*/
|
|
7
|
+
export { SqliteStore } from "./store.js";
|
|
8
|
+
export { generateSqliteSchema } from "./schema.js";
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SQLite schema utilities for b3nd.
|
|
3
|
+
*
|
|
4
|
+
* Generates SQLite-compatible DDL. The schema is intentionally
|
|
5
|
+
* minimal: `uri` (PK) + `payload` (BLOB) + timestamps. The store is
|
|
6
|
+
* opaque — `payload` is raw bytes, not JSON; higher layers own
|
|
7
|
+
* serialization.
|
|
8
|
+
*/
|
|
9
|
+
export declare function generateSqliteSchema(tablePrefix: string): string;
|
|
10
|
+
//# sourceMappingURL=schema.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/sqlite/schema.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAqBhE"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SQLite schema utilities for b3nd.
|
|
3
|
+
*
|
|
4
|
+
* Generates SQLite-compatible DDL. The schema is intentionally
|
|
5
|
+
* minimal: `uri` (PK) + `payload` (BLOB) + timestamps. The store is
|
|
6
|
+
* opaque — `payload` is raw bytes, not JSON; higher layers own
|
|
7
|
+
* serialization.
|
|
8
|
+
*/
|
|
9
|
+
export function generateSqliteSchema(tablePrefix) {
|
|
10
|
+
if (!tablePrefix) {
|
|
11
|
+
throw new Error("tablePrefix is required and cannot be empty");
|
|
12
|
+
}
|
|
13
|
+
if (!/^[a-zA-Z][a-zA-Z0-9_]*$/.test(tablePrefix)) {
|
|
14
|
+
throw new Error("tablePrefix must start with a letter and contain only letters, numbers, and underscores");
|
|
15
|
+
}
|
|
16
|
+
return `-- SQLite schema for b3nd storage
|
|
17
|
+
-- Table prefix: ${tablePrefix}
|
|
18
|
+
|
|
19
|
+
CREATE TABLE IF NOT EXISTS ${tablePrefix}_data (
|
|
20
|
+
uri TEXT PRIMARY KEY,
|
|
21
|
+
payload BLOB NOT NULL,
|
|
22
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
23
|
+
updated_at TEXT DEFAULT (datetime('now'))
|
|
24
|
+
);
|
|
25
|
+
`;
|
|
26
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SqliteStore — SQLite implementation of Store.
|
|
3
|
+
*
|
|
4
|
+
* Pure mechanical byte storage with no protocol awareness. Pushes
|
|
5
|
+
* fn=ls/fn=count down to SQL using the shallow-direct-leaves
|
|
6
|
+
* predicate: `uri LIKE ? || '%' AND uri NOT LIKE ? || '%/%'`.
|
|
7
|
+
*
|
|
8
|
+
* Uses an injected synchronous SqliteExecutor; results are wrapped in
|
|
9
|
+
* Promise.resolve() to satisfy the async Store interface. `payload`
|
|
10
|
+
* is a BLOB; the store does not inspect or transform its contents.
|
|
11
|
+
*/
|
|
12
|
+
import type { DeleteResult, Output, StatusResult } from "../deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/types/types.js";
|
|
13
|
+
import { type BytesOnlyEntityMeta } from "../byte-entity-shim.js";
|
|
14
|
+
import type { EntityStore } from "../entity-store.js";
|
|
15
|
+
import type { EntityRecord, EntitySchema } from "../entity.js";
|
|
16
|
+
import type { StoreCapabilities, StoreWriteResult } from "../types.js";
|
|
17
|
+
import type { SqliteExecutor } from "./mod.js";
|
|
18
|
+
export declare class SqliteStore implements EntityStore<BytesOnlyEntityMeta> {
|
|
19
|
+
private readonly tableName;
|
|
20
|
+
private readonly executor;
|
|
21
|
+
private readonly provisioned;
|
|
22
|
+
constructor(tablePrefix: string, executor: SqliteExecutor);
|
|
23
|
+
entitySupport(schema: EntitySchema): BytesOnlyEntityMeta;
|
|
24
|
+
entityStatus(meta: BytesOnlyEntityMeta): Promise<"live" | "unprovisioned">;
|
|
25
|
+
provisionEntity(meta: BytesOnlyEntityMeta): Promise<void>;
|
|
26
|
+
write(meta: BytesOnlyEntityMeta, entries: {
|
|
27
|
+
uri: string;
|
|
28
|
+
record: EntityRecord;
|
|
29
|
+
}[]): Promise<StoreWriteResult[]>;
|
|
30
|
+
read<T = EntityRecord | undefined>(meta: BytesOnlyEntityMeta, urls: string[]): Promise<Output<T>[]>;
|
|
31
|
+
delete(meta: BytesOnlyEntityMeta, uris: string[]): Promise<DeleteResult[]>;
|
|
32
|
+
private _writeBytes;
|
|
33
|
+
private _readBytes;
|
|
34
|
+
private _readOne;
|
|
35
|
+
private _ls;
|
|
36
|
+
private _count;
|
|
37
|
+
private _deleteBytes;
|
|
38
|
+
status(): Promise<StatusResult>;
|
|
39
|
+
private schemaUris;
|
|
40
|
+
capabilities(): StoreCapabilities;
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/sqlite/store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EACV,YAAY,EACZ,MAAM,EACN,YAAY,EACb,MAAM,mEAAmE,CAAC;AAC3E,OAAO,EAEL,KAAK,mBAAmB,EAIzB,MAAM,wBAAwB,CAAC;AAChC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAO/D,OAAO,KAAK,EACV,iBAAiB,EAEjB,gBAAgB,EACjB,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAW/C,qBAAa,WAAY,YAAW,WAAW,CAAC,mBAAmB,CAAC;IAClE,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAiB;IAK1C,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAqB;gBAErC,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,cAAc;IAUzD,aAAa,CAAC,MAAM,EAAE,YAAY,GAAG,mBAAmB;IAIxD,YAAY,CAAC,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAAC,MAAM,GAAG,eAAe,CAAC;IAI1E,eAAe,CAAC,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC;IASzD,KAAK,CACH,IAAI,EAAE,mBAAmB,EACzB,OAAO,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,YAAY,CAAA;KAAE,EAAE,GAC/C,OAAO,CAAC,gBAAgB,EAAE,CAAC;IAS9B,IAAI,CAAC,CAAC,GAAG,YAAY,GAAG,SAAS,EAC/B,IAAI,EAAE,mBAAmB,EACzB,IAAI,EAAE,MAAM,EAAE,GACb,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;IASvB,MAAM,CAAC,IAAI,EAAE,mBAAmB,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;YAW5D,WAAW;IAqCzB,OAAO,CAAC,UAAU;IAQlB,OAAO,CAAC,QAAQ;IAShB,OAAO,CAAC,GAAG;IA2BX,OAAO,CAAC,MAAM;IAYd,OAAO,CAAC,YAAY;IAwBpB,MAAM,IAAI,OAAO,CAAC,YAAY,CAAC;IAoB/B,OAAO,CAAC,UAAU;IAIlB,YAAY,IAAI,iBAAiB;CAGlC"}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SqliteStore — SQLite implementation of Store.
|
|
3
|
+
*
|
|
4
|
+
* Pure mechanical byte storage with no protocol awareness. Pushes
|
|
5
|
+
* fn=ls/fn=count down to SQL using the shallow-direct-leaves
|
|
6
|
+
* predicate: `uri LIKE ? || '%' AND uri NOT LIKE ? || '%/%'`.
|
|
7
|
+
*
|
|
8
|
+
* Uses an injected synchronous SqliteExecutor; results are wrapped in
|
|
9
|
+
* Promise.resolve() to satisfy the async Store interface. `payload`
|
|
10
|
+
* is a BLOB; the store does not inspect or transform its contents.
|
|
11
|
+
*/
|
|
12
|
+
import { bytesOnlyDelete, bytesOnlyEntitySupport, bytesOnlyRead, bytesOnlyWrite, } from "../byte-entity-shim.js";
|
|
13
|
+
import { dispatchRead } from "../dispatch.js";
|
|
14
|
+
import { storageFailure } from "../errors.js";
|
|
15
|
+
import { toBytes } from "../payload.js";
|
|
16
|
+
import { validateReadParams } from "../read.js";
|
|
17
|
+
const STORE_NAME = "SqliteStore";
|
|
18
|
+
function rowToBytes(value) {
|
|
19
|
+
if (value instanceof Uint8Array)
|
|
20
|
+
return value;
|
|
21
|
+
if (value instanceof ArrayBuffer)
|
|
22
|
+
return new Uint8Array(value);
|
|
23
|
+
if (Array.isArray(value))
|
|
24
|
+
return new Uint8Array(value);
|
|
25
|
+
throw new Error(`SqliteStore: unexpected payload type ${typeof value}`);
|
|
26
|
+
}
|
|
27
|
+
export class SqliteStore {
|
|
28
|
+
tableName;
|
|
29
|
+
executor;
|
|
30
|
+
// Names of entities provisioned on this store. SqliteStore currently
|
|
31
|
+
// routes only BYTES_ENTITY, so this is at most `{"bytes"}` — but the
|
|
32
|
+
// tracking shape matches MemoryStore so `status().schema` reads the
|
|
33
|
+
// same way across backends.
|
|
34
|
+
provisioned = new Set();
|
|
35
|
+
constructor(tablePrefix, executor) {
|
|
36
|
+
if (!tablePrefix)
|
|
37
|
+
throw new Error("tablePrefix is required");
|
|
38
|
+
if (!executor)
|
|
39
|
+
throw new Error("executor is required");
|
|
40
|
+
this.tableName = `${tablePrefix}_data`;
|
|
41
|
+
this.executor = executor;
|
|
42
|
+
}
|
|
43
|
+
// ── EntityStore surface ──────────────────────────────────────────
|
|
44
|
+
entitySupport(schema) {
|
|
45
|
+
return bytesOnlyEntitySupport(schema);
|
|
46
|
+
}
|
|
47
|
+
entityStatus(meta) {
|
|
48
|
+
return Promise.resolve(meta.isBytes ? "live" : "unprovisioned");
|
|
49
|
+
}
|
|
50
|
+
provisionEntity(meta) {
|
|
51
|
+
// Only BYTES_ENTITY is honored at the storage layer; record the
|
|
52
|
+
// name so `status()` surfaces what callers can actually use.
|
|
53
|
+
// Non-bytes schemas are silently ignored — `write`/`read` already
|
|
54
|
+
// surface "Unsupported schema" via the shim.
|
|
55
|
+
if (meta.isBytes)
|
|
56
|
+
this.provisioned.add(meta.schema.name);
|
|
57
|
+
return Promise.resolve();
|
|
58
|
+
}
|
|
59
|
+
write(meta, entries) {
|
|
60
|
+
return bytesOnlyWrite(meta, STORE_NAME, entries, (e) => this._writeBytes(e));
|
|
61
|
+
}
|
|
62
|
+
read(meta, urls) {
|
|
63
|
+
return bytesOnlyRead(meta, STORE_NAME, urls, (u) => this._readBytes(u));
|
|
64
|
+
}
|
|
65
|
+
delete(meta, uris) {
|
|
66
|
+
return bytesOnlyDelete(meta, STORE_NAME, uris, (u) => this._deleteBytes(u));
|
|
67
|
+
}
|
|
68
|
+
// ── Byte ops (BYTES_ENTITY routing) ──────────────────────────────
|
|
69
|
+
async _writeBytes(entries) {
|
|
70
|
+
if (entries.length === 0)
|
|
71
|
+
return [];
|
|
72
|
+
// Collect streams up front: the executor is synchronous (BLOB
|
|
73
|
+
// wants bytes), and we don't want to await mid-transaction.
|
|
74
|
+
const prepared = await Promise.all(entries.map(async (e) => ({
|
|
75
|
+
uri: e.uri,
|
|
76
|
+
bytes: await toBytes(e.payload),
|
|
77
|
+
})));
|
|
78
|
+
// `capabilities.atomicBatch` is true: the whole batch commits or
|
|
79
|
+
// nothing does. On failure every result is `{ success: false }`
|
|
80
|
+
// with the same root-cause error.
|
|
81
|
+
try {
|
|
82
|
+
this.executor.transaction((tx) => {
|
|
83
|
+
for (const entry of prepared) {
|
|
84
|
+
tx.query(`INSERT INTO ${this.tableName} (uri, payload, updated_at)
|
|
85
|
+
VALUES (?, ?, datetime('now'))
|
|
86
|
+
ON CONFLICT(uri) DO UPDATE SET
|
|
87
|
+
payload = excluded.payload,
|
|
88
|
+
updated_at = datetime('now')`, [entry.uri, entry.bytes]);
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
return entries.map(() => ({ success: true }));
|
|
92
|
+
}
|
|
93
|
+
catch (err) {
|
|
94
|
+
const failure = storageFailure(err, "Write failed");
|
|
95
|
+
return entries.map(() => ({ success: false, ...failure }));
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
_readBytes(urls) {
|
|
99
|
+
return dispatchRead(urls, STORE_NAME, {
|
|
100
|
+
read: (p) => this._readOne(p.uri),
|
|
101
|
+
ls: (p) => this._ls(p),
|
|
102
|
+
count: (p) => this._count(p),
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
_readOne(uri) {
|
|
106
|
+
const result = this.executor.query(`SELECT payload FROM ${this.tableName} WHERE uri = ?`, [uri]);
|
|
107
|
+
if (!result.rows.length)
|
|
108
|
+
return undefined;
|
|
109
|
+
return rowToBytes(result.rows[0].payload);
|
|
110
|
+
}
|
|
111
|
+
_ls(parsed) {
|
|
112
|
+
validateReadParams(parsed.params, STORE_NAME);
|
|
113
|
+
const { params } = parsed;
|
|
114
|
+
const format = params.format ?? "full";
|
|
115
|
+
const select = format === "uris" ? "uri" : "uri, payload";
|
|
116
|
+
const order = params.sortBy === "uri"
|
|
117
|
+
? ` ORDER BY uri ${params.sortOrder === "desc" ? "DESC" : "ASC"}`
|
|
118
|
+
: "";
|
|
119
|
+
let sql = `SELECT ${select} FROM ${this.tableName} WHERE uri LIKE ? || '%' AND uri NOT LIKE ? || '%/%'${order}`;
|
|
120
|
+
const args = [parsed.uri, parsed.uri];
|
|
121
|
+
if (params.limit !== undefined) {
|
|
122
|
+
const page = params.page ?? 1;
|
|
123
|
+
sql += ` LIMIT ? OFFSET ?`;
|
|
124
|
+
args.push(params.limit, (page - 1) * params.limit);
|
|
125
|
+
}
|
|
126
|
+
const result = this.executor.query(sql, args);
|
|
127
|
+
const rows = result.rows;
|
|
128
|
+
if (format === "uris")
|
|
129
|
+
return rows.map((r) => r.uri);
|
|
130
|
+
return rows.map((r) => [r.uri, rowToBytes(r.payload)]);
|
|
131
|
+
}
|
|
132
|
+
_count(parsed) {
|
|
133
|
+
if (parsed.params.pattern !== undefined) {
|
|
134
|
+
throw new Error(`${STORE_NAME}: pattern filter not supported`);
|
|
135
|
+
}
|
|
136
|
+
const result = this.executor.query(`SELECT COUNT(*) AS n FROM ${this.tableName} WHERE uri LIKE ? || '%' AND uri NOT LIKE ? || '%/%'`, [parsed.uri, parsed.uri]);
|
|
137
|
+
const row = result.rows[0];
|
|
138
|
+
return row?.n ?? 0;
|
|
139
|
+
}
|
|
140
|
+
_deleteBytes(uris) {
|
|
141
|
+
if (uris.length === 0)
|
|
142
|
+
return Promise.resolve([]);
|
|
143
|
+
// Atomic batch: every uri either deletes together or not at all.
|
|
144
|
+
try {
|
|
145
|
+
this.executor.transaction((tx) => {
|
|
146
|
+
for (const uri of uris) {
|
|
147
|
+
tx.query(`DELETE FROM ${this.tableName} WHERE uri = ?`, [uri]);
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
return Promise.resolve(uris.map(() => ({ success: true })));
|
|
151
|
+
}
|
|
152
|
+
catch (err) {
|
|
153
|
+
const failure = storageFailure(err, "Delete failed");
|
|
154
|
+
return Promise.resolve(uris.map(() => ({ success: false, ...failure })));
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
// ── Status ───────────────────────────────────────────────────────
|
|
158
|
+
status() {
|
|
159
|
+
try {
|
|
160
|
+
this.executor.query("SELECT 1");
|
|
161
|
+
return Promise.resolve({
|
|
162
|
+
status: "healthy",
|
|
163
|
+
message: "SQLite store is operational",
|
|
164
|
+
schema: this.schemaUris(),
|
|
165
|
+
fns: ["read", "ls", "count"],
|
|
166
|
+
details: { tableName: this.tableName },
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
catch (error) {
|
|
170
|
+
return Promise.resolve({
|
|
171
|
+
status: "unhealthy",
|
|
172
|
+
message: error instanceof Error ? error.message : String(error),
|
|
173
|
+
schema: this.schemaUris(),
|
|
174
|
+
fns: ["read", "ls", "count"],
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
schemaUris() {
|
|
179
|
+
return [...this.provisioned].map((name) => `entity:${name}`);
|
|
180
|
+
}
|
|
181
|
+
capabilities() {
|
|
182
|
+
return { atomicBatch: true };
|
|
183
|
+
}
|
|
184
|
+
}
|