@bandeira-tech/b3nd-save 0.8.1 → 0.13.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/esm/clients/save-client.d.ts +63 -29
- package/esm/clients/save-client.d.ts.map +1 -1
- package/esm/clients/save-client.js +148 -68
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/mod.d.ts +1 -1
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.25.0/mod.d.ts.map +1 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/client-console/client.d.ts.map +1 -1
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/encoding/encoding.d.ts.map +1 -1
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/encrypt/mod.d.ts +1 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.25.0/src/encrypt/mod.d.ts.map +1 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/encrypt/mod.js +1 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.25.0/src/encrypt/utils.d.ts +6 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.25.0/src/encrypt/utils.d.ts.map +1 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.25.0/src/encrypt/utils.js +12 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/functional-client/functional-client.d.ts.map +1 -1
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/match-pattern/match-pattern.d.ts.map +1 -1
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/network/decorators.d.ts.map +1 -1
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/network/network.d.ts.map +1 -1
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/network/peer.d.ts.map +1 -1
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/network/policies/flood.d.ts.map +1 -1
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/network/policies/path-vector.d.ts +3 -1
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.25.0/src/network/policies/path-vector.d.ts.map +1 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/network/policies/path-vector.js +3 -1
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/network/policies/tell-and-read.d.ts.map +1 -1
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/network/types.d.ts.map +1 -1
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/observe-emitter/observe-emitter.d.ts.map +1 -1
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/rig/connection.d.ts +13 -5
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.25.0/src/rig/connection.d.ts.map +1 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/rig/connection.js +1 -1
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/rig/events.d.ts.map +1 -1
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/rig/hooks.d.ts.map +1 -1
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/rig/identity.d.ts.map +1 -1
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/rig/operation-handle.d.ts.map +1 -1
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/rig/reactions.d.ts.map +1 -1
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/rig/rig.d.ts +3 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.25.0/src/rig/rig.d.ts.map +1 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/rig/rig.js +46 -4
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/rig/types.d.ts +4 -4
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.25.0/src/rig/types.d.ts.map +1 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/types/types.d.ts +119 -80
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.25.0/src/types/types.d.ts.map +1 -0
- package/esm/dispatch.d.ts +116 -3
- package/esm/dispatch.d.ts.map +1 -1
- package/esm/dispatch.js +381 -5
- package/esm/elasticsearch/store.d.ts +124 -25
- package/esm/elasticsearch/store.d.ts.map +1 -1
- package/esm/elasticsearch/store.js +451 -92
- package/esm/entity-store.d.ts +1 -1
- package/esm/errors.d.ts +1 -1
- package/esm/errors.js +1 -1
- package/esm/fs/mod.d.ts +26 -0
- package/esm/fs/mod.d.ts.map +1 -1
- package/esm/fs/store.d.ts +100 -23
- package/esm/fs/store.d.ts.map +1 -1
- package/esm/fs/store.js +250 -66
- package/esm/glob.d.ts +114 -0
- package/esm/glob.d.ts.map +1 -0
- package/esm/glob.js +352 -0
- package/esm/identifiers.d.ts +18 -0
- package/esm/identifiers.d.ts.map +1 -0
- package/esm/identifiers.js +19 -0
- package/esm/indexeddb/fields.d.ts +32 -0
- package/esm/indexeddb/fields.d.ts.map +1 -0
- package/esm/indexeddb/fields.js +51 -0
- package/esm/indexeddb/store.d.ts +72 -17
- package/esm/indexeddb/store.d.ts.map +1 -1
- package/esm/indexeddb/store.js +362 -106
- package/esm/ipfs/mod.d.ts +19 -0
- package/esm/ipfs/mod.d.ts.map +1 -1
- package/esm/ipfs/mod.js +19 -0
- package/esm/ipfs/store.d.ts +82 -21
- package/esm/ipfs/store.d.ts.map +1 -1
- package/esm/ipfs/store.js +269 -68
- package/esm/json-fields.d.ts +61 -0
- package/esm/json-fields.d.ts.map +1 -0
- package/esm/json-fields.js +121 -0
- package/esm/localstorage/store.d.ts +89 -21
- package/esm/localstorage/store.d.ts.map +1 -1
- package/esm/localstorage/store.js +247 -54
- package/esm/memory/store.d.ts +35 -4
- package/esm/memory/store.d.ts.map +1 -1
- package/esm/memory/store.js +69 -47
- package/esm/mod.d.ts +2 -0
- package/esm/mod.d.ts.map +1 -1
- package/esm/mod.js +1 -0
- package/esm/mongo/fields.d.ts +53 -0
- package/esm/mongo/fields.d.ts.map +1 -0
- package/esm/mongo/fields.js +71 -0
- package/esm/mongo/mod.d.ts +19 -4
- package/esm/mongo/mod.d.ts.map +1 -1
- package/esm/mongo/mod.js +4 -2
- package/esm/mongo/store.d.ts +114 -25
- package/esm/mongo/store.d.ts.map +1 -1
- package/esm/mongo/store.js +355 -102
- package/esm/postgres/store.d.ts +50 -1
- package/esm/postgres/store.d.ts.map +1 -1
- package/esm/postgres/store.js +133 -13
- package/esm/read.d.ts +54 -7
- package/esm/read.d.ts.map +1 -1
- package/esm/read.js +141 -16
- package/esm/s3/store.d.ts +125 -23
- package/esm/s3/store.d.ts.map +1 -1
- package/esm/s3/store.js +313 -57
- package/esm/sqlite/columns.d.ts +33 -0
- package/esm/sqlite/columns.d.ts.map +1 -0
- package/esm/sqlite/columns.js +51 -0
- package/esm/sqlite/store.d.ts +73 -25
- package/esm/sqlite/store.d.ts.map +1 -1
- package/esm/sqlite/store.js +411 -107
- package/esm/types.d.ts +1 -1
- package/esm/url.d.ts +122 -19
- package/esm/url.d.ts.map +1 -1
- package/esm/url.js +218 -24
- package/esm/walk-via-ls.d.ts +122 -0
- package/esm/walk-via-ls.d.ts.map +1 -0
- package/esm/walk-via-ls.js +178 -0
- package/package.json +1 -1
- package/esm/byte-entity-shim.d.ts +0 -47
- package/esm/byte-entity-shim.d.ts.map +0 -1
- package/esm/byte-entity-shim.js +0 -138
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/mod.d.ts.map +0 -1
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/encrypt/mod.d.ts.map +0 -1
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/policies/path-vector.d.ts.map +0 -1
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/connection.d.ts.map +0 -1
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/rig.d.ts.map +0 -1
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/types.d.ts.map +0 -1
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/types/types.d.ts.map +0 -1
- /package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/mod.js +0 -0
- /package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/client-console/client.d.ts +0 -0
- /package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/client-console/client.js +0 -0
- /package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/encoding/encoding.d.ts +0 -0
- /package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/encoding/encoding.js +0 -0
- /package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/functional-client/functional-client.d.ts +0 -0
- /package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/functional-client/functional-client.js +0 -0
- /package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/match-pattern/match-pattern.d.ts +0 -0
- /package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/match-pattern/match-pattern.js +0 -0
- /package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/network/decorators.d.ts +0 -0
- /package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/network/decorators.js +0 -0
- /package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/network/network.d.ts +0 -0
- /package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/network/network.js +0 -0
- /package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/network/peer.d.ts +0 -0
- /package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/network/peer.js +0 -0
- /package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/network/policies/flood.d.ts +0 -0
- /package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/network/policies/flood.js +0 -0
- /package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/network/policies/tell-and-read.d.ts +0 -0
- /package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/network/policies/tell-and-read.js +0 -0
- /package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/network/types.d.ts +0 -0
- /package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/network/types.js +0 -0
- /package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/observe-emitter/observe-emitter.d.ts +0 -0
- /package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/observe-emitter/observe-emitter.js +0 -0
- /package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/rig/events.d.ts +0 -0
- /package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/rig/events.js +0 -0
- /package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/rig/hooks.d.ts +0 -0
- /package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/rig/hooks.js +0 -0
- /package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/rig/identity.d.ts +0 -0
- /package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/rig/identity.js +0 -0
- /package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/rig/operation-handle.d.ts +0 -0
- /package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/rig/operation-handle.js +0 -0
- /package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/rig/reactions.d.ts +0 -0
- /package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/rig/reactions.js +0 -0
- /package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/rig/types.js +0 -0
- /package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/types/types.js +0 -0
package/esm/dispatch.js
CHANGED
|
@@ -3,30 +3,398 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Every store parses the input url, switches on `fn`, and calls back
|
|
5
5
|
* into store-supplied handlers. Centralising the dispatch keeps the
|
|
6
|
-
* `read`/`ls`/`count`/`x-*` switch identical across stores so
|
|
7
|
-
* cannot drift apart.
|
|
6
|
+
* `read`/`ls`/`find`/`count`/`x-*` switch identical across stores so
|
|
7
|
+
* they cannot drift apart.
|
|
8
8
|
*
|
|
9
9
|
* Stores call `dispatchRead(urls, handlers)`; the helper handles
|
|
10
10
|
* parsing, the fn switch, and tuple assembly. Each handler receives
|
|
11
11
|
* the parsed url and returns the payload — the helper wraps it into
|
|
12
12
|
* `[inputUrl, payload]`.
|
|
13
|
+
*
|
|
14
|
+
* v2 listing-spec additions (see `.cc-chat/20260625121936-grammar-shape`):
|
|
15
|
+
*
|
|
16
|
+
* - **`find` case** — requires an explicit `handlers.find` registration
|
|
17
|
+
* (mirrors the `ext?:` opt-in pattern). When `handlers.find` is
|
|
18
|
+
* absent, dispatch throws via the same "unsupported fn" path as any
|
|
19
|
+
* unknown verb — no silent fall-back through `ls`. Stores that
|
|
20
|
+
* support deep walks must wire their own `find` handler (push-down
|
|
21
|
+
* into the backend, or an in-process recursive walk built on `ls`).
|
|
22
|
+
* See spec §3.5: a store that does not advertise `"find"` in
|
|
23
|
+
* `status().fns` MUST throw on `fn=find`.
|
|
24
|
+
*
|
|
25
|
+
* - **`count` case (URL-shape-aware)** — `fn=count` is valid under any
|
|
26
|
+
* URL shape (spec §3.2). Dispatch keeps the existing `handlers.count`
|
|
27
|
+
* path for no-wildcard and shallow-wildcard URLs. When the URL carries
|
|
28
|
+
* a `**` glob, `count` mirrors `find`'s deep walk: either the store
|
|
29
|
+
* has set `pushDownCount: true` (dispatch trusts `handlers.count` to
|
|
30
|
+
* run the deep COUNT in-backend) or dispatch calls `handlers.find` and
|
|
31
|
+
* returns the length of the walked result. If the store advertises
|
|
32
|
+
* neither (`pushDownCount` false AND no `handlers.find`), dispatch
|
|
33
|
+
* throws "unsupported fn 'count'" — no silent shallow-count fall-back,
|
|
34
|
+
* same loud-failure rule as `fn=find` (§3.5).
|
|
35
|
+
*
|
|
36
|
+
* - **cursor-as-trailing-slot** — when `find` or `ls` is called with
|
|
37
|
+
* `limit=<n>`, dispatch appends ONE extra `Output` to the returned
|
|
38
|
+
* array: `[<cursor-uri>, { next: "<opaque>" | null }]`. The cursor
|
|
39
|
+
* URI is the original locator with `cursor=<opaque>` added (or
|
|
40
|
+
* replaced). `next: null` signals end-of-result; otherwise `next` is
|
|
41
|
+
* the opaque token to re-issue the request. The caller can do
|
|
42
|
+
* `read([slot.uri])` literally and walk the next page.
|
|
13
43
|
*/
|
|
44
|
+
import { compareSortable, projectRecord } from "./read.js";
|
|
45
|
+
import { compileSaveGlob, matchesGlob } from "./glob.js";
|
|
14
46
|
import { parseUrl } from "./url.js";
|
|
47
|
+
/**
|
|
48
|
+
* Filter a sorted row list to entries strictly past the cursor.
|
|
49
|
+
* `cursor` is a URI from a previous page; with `sortOrder=asc` (default)
|
|
50
|
+
* we drop rows up to and including the cursor, with `desc` we drop rows
|
|
51
|
+
* at or after it. Comparison is the same `localeCompare` used by the
|
|
52
|
+
* default URI sort, so cursor pagination tracks the sort direction.
|
|
53
|
+
*/
|
|
54
|
+
function filterByCursor(rows, cursor, sortOrder) {
|
|
55
|
+
const desc = sortOrder === "desc";
|
|
56
|
+
return rows.filter(([uri]) => desc ? uri.localeCompare(cursor) < 0 : uri.localeCompare(cursor) > 0);
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Read a named field from a payload, returning `undefined` for any
|
|
60
|
+
* non-record payload (eg `Uint8Array`, `ReadableStream`, miss). Used
|
|
61
|
+
* by the dispatch post-sort path so non-uri sortBy works against the
|
|
62
|
+
* `EntityRecord` shape returned by handlers.
|
|
63
|
+
*/
|
|
64
|
+
function recordField(value, field) {
|
|
65
|
+
if (value === undefined || value === null)
|
|
66
|
+
return undefined;
|
|
67
|
+
if (typeof value !== "object")
|
|
68
|
+
return undefined;
|
|
69
|
+
if (value instanceof Uint8Array)
|
|
70
|
+
return undefined;
|
|
71
|
+
if (value instanceof ReadableStream)
|
|
72
|
+
return undefined;
|
|
73
|
+
return value[field];
|
|
74
|
+
}
|
|
75
|
+
/** Same as `filterByCursor` but for `format=uris` payloads. */
|
|
76
|
+
function filterUrisByCursor(uris, cursor, sortOrder) {
|
|
77
|
+
const desc = sortOrder === "desc";
|
|
78
|
+
return uris.filter((uri) => desc ? uri.localeCompare(cursor) < 0 : uri.localeCompare(cursor) > 0);
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Compose the trailing cursor slot for a paginated `find` / `ls`
|
|
82
|
+
* response. Returns `[cursorUri, { next }]` where `cursorUri` is the
|
|
83
|
+
* original input url with `cursor=<opaque>` added (or replaced; every
|
|
84
|
+
* other param is preserved). `next` is the opaque token (the URI of
|
|
85
|
+
* the last entry returned) when more results are available, `null`
|
|
86
|
+
* when the page was the tail of the result set.
|
|
87
|
+
*
|
|
88
|
+
* The slot is appended UNCONDITIONALLY when `limit` was set in the
|
|
89
|
+
* request — callers shouldn't have to special-case "this might be the
|
|
90
|
+
* last page" or "this is empty"; the `next: null` case carries that
|
|
91
|
+
* signal. Re-issue is `read([slot.uri])` literally.
|
|
92
|
+
*
|
|
93
|
+
* URI composition: we operate on the original input url string (not
|
|
94
|
+
* `parsed.uri`) so the URI-embedded glob portion (`**`, etc.) is
|
|
95
|
+
* preserved. `cursor=` is set/replaced in place via URLSearchParams.
|
|
96
|
+
*/
|
|
97
|
+
function buildCursorSlot(inputUrl, cursorForUri, next) {
|
|
98
|
+
const qIdx = inputUrl.indexOf("?");
|
|
99
|
+
const uriPart = qIdx < 0 ? inputUrl : inputUrl.slice(0, qIdx);
|
|
100
|
+
const queryPart = qIdx < 0 ? "" : inputUrl.slice(qIdx + 1);
|
|
101
|
+
const sp = new URLSearchParams(queryPart);
|
|
102
|
+
// The cursor URI is the re-issuable locator: every other param
|
|
103
|
+
// preserved, `cursor=` set to the last row's URI we returned (so the
|
|
104
|
+
// caller can re-issue and get the next page — or, if we already
|
|
105
|
+
// returned the tail, get an empty page + `next: null`). When the
|
|
106
|
+
// page was empty entirely (no rows at all), there is no last URI to
|
|
107
|
+
// hang a cursor on; we drop it so the URI matches a fresh request.
|
|
108
|
+
if (cursorForUri !== null) {
|
|
109
|
+
sp.set("cursor", cursorForUri);
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
sp.delete("cursor");
|
|
113
|
+
}
|
|
114
|
+
const qs = sp.toString();
|
|
115
|
+
const cursorUri = qs ? `${uriPart}?${qs}` : uriPart;
|
|
116
|
+
return [cursorUri, { next }];
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Type guard: is the row a plain `Output` tuple (`[uri, payload]`).
|
|
120
|
+
* Used after we strip the cursor slot before applying further
|
|
121
|
+
* transformations to find/ls payloads.
|
|
122
|
+
*/
|
|
123
|
+
function lastUriOfRows(rows) {
|
|
124
|
+
if (rows.length === 0)
|
|
125
|
+
return null;
|
|
126
|
+
const last = rows[rows.length - 1];
|
|
127
|
+
if (typeof last === "string")
|
|
128
|
+
return last;
|
|
129
|
+
if (Array.isArray(last) && typeof last[0] === "string")
|
|
130
|
+
return last[0];
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
15
133
|
export async function dispatchRead(urls, storeName, handlers) {
|
|
16
134
|
const out = [];
|
|
17
135
|
for (const url of urls) {
|
|
18
136
|
const parsed = parseUrl(url);
|
|
137
|
+
// Validate the combos dispatch itself enforces before stripping
|
|
138
|
+
// anything from the handler call, otherwise validateReadParams
|
|
139
|
+
// inside the handler never sees the original shape.
|
|
140
|
+
if (parsed.params.cursor !== undefined && parsed.params.page !== undefined) {
|
|
141
|
+
throw new Error(`${storeName}: cursor and page cannot be combined — pick one pagination mode`);
|
|
142
|
+
}
|
|
19
143
|
let payload;
|
|
144
|
+
const fields = parsed.params.fields;
|
|
145
|
+
const pattern = parsed.params.pattern;
|
|
146
|
+
const cursor = parsed.params.cursor;
|
|
147
|
+
const sortBy = parsed.params.sortBy;
|
|
148
|
+
const needsPatternPostFilter = pattern !== undefined &&
|
|
149
|
+
!handlers.pushDownPattern;
|
|
150
|
+
const needsCursorPostFilter = cursor !== undefined &&
|
|
151
|
+
!handlers.pushDownCursor;
|
|
152
|
+
// Non-uri sortBy needs dispatch post-sort unless the backend
|
|
153
|
+
// explicitly handles arbitrary sortBy itself. `leaf` is a
|
|
154
|
+
// dispatch-level sort (basename localeCompare); it always
|
|
155
|
+
// post-sorts.
|
|
156
|
+
const needsSortByPostSort = sortBy !== undefined && sortBy !== "uri" &&
|
|
157
|
+
!handlers.pushDownSortBy;
|
|
20
158
|
switch (parsed.fn) {
|
|
21
159
|
case "read":
|
|
22
160
|
payload = await handlers.read(parsed);
|
|
161
|
+
if (fields && fields.length > 0) {
|
|
162
|
+
payload = projectRecord(payload, fields);
|
|
163
|
+
}
|
|
23
164
|
break;
|
|
24
165
|
case "ls":
|
|
25
|
-
|
|
166
|
+
case "find": {
|
|
167
|
+
const isFind = parsed.fn === "find";
|
|
168
|
+
// v2 §3.5: a store that does not advertise `"find"` in
|
|
169
|
+
// status().fns MUST throw on fn=find. We enforce that at the
|
|
170
|
+
// dispatch level by requiring an explicit `handlers.find`
|
|
171
|
+
// registration — no silent fall-back through `ls`. Error shape
|
|
172
|
+
// matches the unknown-fn throw below so existing callers can
|
|
173
|
+
// detect it the same way.
|
|
174
|
+
if (isFind && !handlers.find) {
|
|
175
|
+
throw new Error(`${storeName}: unsupported fn 'find'`);
|
|
176
|
+
}
|
|
177
|
+
const format = parsed.params.format ?? "full";
|
|
178
|
+
// For find, `handlers.find` is guaranteed present by the guard
|
|
179
|
+
// above. For ls, the handler is `handlers.ls`.
|
|
180
|
+
const lsLikeHandler = isFind ? handlers.find : handlers.ls;
|
|
181
|
+
if (needsPatternPostFilter || needsCursorPostFilter ||
|
|
182
|
+
needsSortByPostSort) {
|
|
183
|
+
// Dispatch handles pattern/cursor/sortBy + pagination +
|
|
184
|
+
// projection itself, so it asks the backend for the full
|
|
185
|
+
// result first. Stripping `limit`/`page` is necessary so the
|
|
186
|
+
// backend doesn't truncate the set before the filters run;
|
|
187
|
+
// stripping `pattern`/`cursor`/non-uri `sortBy` is necessary
|
|
188
|
+
// so backends that don't recognise them don't error out.
|
|
189
|
+
// For non-uri sortBy we also have to force `format=full`
|
|
190
|
+
// since records (not just URIs) are needed to read the
|
|
191
|
+
// sort field.
|
|
192
|
+
const handlerParams = { ...parsed.params };
|
|
193
|
+
delete handlerParams.limit;
|
|
194
|
+
delete handlerParams.page;
|
|
195
|
+
delete handlerParams.fields;
|
|
196
|
+
delete handlerParams.cursor;
|
|
197
|
+
if (needsPatternPostFilter)
|
|
198
|
+
delete handlerParams.pattern;
|
|
199
|
+
const handlerFormat = needsSortByPostSort && format === "uris"
|
|
200
|
+
? "full"
|
|
201
|
+
: format;
|
|
202
|
+
handlerParams.format = handlerFormat;
|
|
203
|
+
if (needsSortByPostSort)
|
|
204
|
+
handlerParams.sortBy = "uri";
|
|
205
|
+
const raw = await lsLikeHandler({
|
|
206
|
+
...parsed,
|
|
207
|
+
params: handlerParams,
|
|
208
|
+
});
|
|
209
|
+
if (!Array.isArray(raw)) {
|
|
210
|
+
payload = raw;
|
|
211
|
+
break;
|
|
212
|
+
}
|
|
213
|
+
let arr = raw;
|
|
214
|
+
if (needsPatternPostFilter) {
|
|
215
|
+
// Hoist the compiled glob — the row loop is the only hot
|
|
216
|
+
// spot we know about and the compile cost is non-trivial.
|
|
217
|
+
const re = compileSaveGlob(pattern);
|
|
218
|
+
arr = handlerFormat === "uris"
|
|
219
|
+
? arr.filter((uri) => uri.startsWith(parsed.uri) &&
|
|
220
|
+
re.test(uri.slice(parsed.uri.length)))
|
|
221
|
+
: arr.filter(([uri]) => uri.startsWith(parsed.uri) &&
|
|
222
|
+
re.test(uri.slice(parsed.uri.length)));
|
|
223
|
+
}
|
|
224
|
+
if (needsCursorPostFilter) {
|
|
225
|
+
arr = handlerFormat === "uris"
|
|
226
|
+
? filterUrisByCursor(arr, cursor, parsed.params.sortOrder)
|
|
227
|
+
: filterByCursor(arr, cursor, parsed.params.sortOrder);
|
|
228
|
+
}
|
|
229
|
+
if (needsSortByPostSort) {
|
|
230
|
+
// We forced format=full above, so arr is Array<Output>.
|
|
231
|
+
const dir = parsed.params.sortOrder === "desc" ? -1 : 1;
|
|
232
|
+
if (sortBy === "leaf") {
|
|
233
|
+
arr = [...arr].sort(([a], [b]) => leafOf(a).localeCompare(leafOf(b)) * dir);
|
|
234
|
+
}
|
|
235
|
+
else {
|
|
236
|
+
arr = [...arr].sort(([, a], [, b]) => {
|
|
237
|
+
const av = recordField(a, sortBy);
|
|
238
|
+
const bv = recordField(b, sortBy);
|
|
239
|
+
return compareSortable(av, bv) * dir;
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
let truncated = false;
|
|
244
|
+
if (parsed.params.limit !== undefined) {
|
|
245
|
+
// page is incompatible with cursor (validateReadParams
|
|
246
|
+
// throws on that combo); when only `page` is set the
|
|
247
|
+
// page-based offset still applies.
|
|
248
|
+
const start = needsCursorPostFilter
|
|
249
|
+
? 0
|
|
250
|
+
: ((parsed.params.page ?? 1) - 1) * parsed.params.limit;
|
|
251
|
+
const end = start + parsed.params.limit;
|
|
252
|
+
truncated = arr.length > end;
|
|
253
|
+
arr = arr.slice(start, end);
|
|
254
|
+
}
|
|
255
|
+
// If we forced format=full to access the sort field but the
|
|
256
|
+
// caller asked for uris, project down to uris at the end.
|
|
257
|
+
if (handlerFormat === "full" && format === "uris") {
|
|
258
|
+
arr = arr.map(([uri]) => uri);
|
|
259
|
+
}
|
|
260
|
+
else if (fields && fields.length > 0 && format === "full") {
|
|
261
|
+
arr = arr.map(([uri, record]) => [
|
|
262
|
+
uri,
|
|
263
|
+
projectRecord(record, fields),
|
|
264
|
+
]);
|
|
265
|
+
}
|
|
266
|
+
// Append cursor-as-trailing-slot whenever `limit` was set.
|
|
267
|
+
// `cursorForUri`: the URI of the last row returned (or null if
|
|
268
|
+
// the page was empty) — used to compose a re-issuable locator.
|
|
269
|
+
// `next`: the opaque cursor for the next page, or `null` when
|
|
270
|
+
// the page was the tail of the result set.
|
|
271
|
+
if (parsed.params.limit !== undefined) {
|
|
272
|
+
const lastUri = lastUriOfRows(arr);
|
|
273
|
+
const next = truncated ? lastUri : null;
|
|
274
|
+
arr = [...arr, buildCursorSlot(url, lastUri, next)];
|
|
275
|
+
}
|
|
276
|
+
payload = arr;
|
|
277
|
+
break;
|
|
278
|
+
}
|
|
279
|
+
// Fast pass-through: the backend handled (or accepted) every
|
|
280
|
+
// param itself. We only need to optionally project the
|
|
281
|
+
// returned records and append the cursor slot.
|
|
282
|
+
const raw = await lsLikeHandler(parsed);
|
|
283
|
+
if (fields && fields.length > 0 && Array.isArray(raw) &&
|
|
284
|
+
format === "full") {
|
|
285
|
+
payload = raw.map(([uri, record]) => [
|
|
286
|
+
uri,
|
|
287
|
+
projectRecord(record, fields),
|
|
288
|
+
]);
|
|
289
|
+
}
|
|
290
|
+
else {
|
|
291
|
+
payload = raw;
|
|
292
|
+
}
|
|
293
|
+
if (parsed.params.limit !== undefined && Array.isArray(payload)) {
|
|
294
|
+
// We don't know if the backend truncated — the only safe
|
|
295
|
+
// signal we have is "did we return `limit` items"; if we
|
|
296
|
+
// did, callers should re-issue. False positives (cursor
|
|
297
|
+
// points past the end) are correctness-safe: the next call
|
|
298
|
+
// returns an empty page + a `next: null` slot.
|
|
299
|
+
const arr = payload;
|
|
300
|
+
const isFull = arr.length >= parsed.params.limit;
|
|
301
|
+
const lastUri = lastUriOfRows(arr);
|
|
302
|
+
const next = isFull ? lastUri : null;
|
|
303
|
+
payload = [...arr, buildCursorSlot(url, lastUri, next)];
|
|
304
|
+
}
|
|
26
305
|
break;
|
|
27
|
-
|
|
28
|
-
|
|
306
|
+
}
|
|
307
|
+
case "count": {
|
|
308
|
+
// v2 §3.2: `count` honors any URL shape. A `**` glob in the URL
|
|
309
|
+
// means "count of all descendants" (the find-flavored count).
|
|
310
|
+
// The shallow ls-based filtered-count path below cannot serve
|
|
311
|
+
// that — handlers.ls returns one segment deep — so dispatch
|
|
312
|
+
// either routes to handlers.count (push-down opt-in) or walks
|
|
313
|
+
// via handlers.find and returns the length. Without either,
|
|
314
|
+
// throw rather than silently return a shallow count.
|
|
315
|
+
const isDeepCount = parsed.glob.includes("**");
|
|
316
|
+
if (isDeepCount && handlers.pushDownCount) {
|
|
317
|
+
// Push-down deep count: trust the backend to translate the
|
|
318
|
+
// full URL (glob included) into an efficient COUNT. Skip the
|
|
319
|
+
// ls-based filtered-count path below — that path was designed
|
|
320
|
+
// for shallow `?` / `*` filters and would short-circuit
|
|
321
|
+
// through ls instead of count.
|
|
322
|
+
payload = await handlers.count(parsed);
|
|
323
|
+
break;
|
|
324
|
+
}
|
|
325
|
+
if (isDeepCount && !handlers.pushDownCount) {
|
|
326
|
+
if (!handlers.find) {
|
|
327
|
+
throw new Error(`${storeName}: unsupported fn 'count'`);
|
|
328
|
+
}
|
|
329
|
+
// In-process deep count via the find walk. We post-filter
|
|
330
|
+
// against the user's glob (same rule the find path applies
|
|
331
|
+
// when pushDownPattern is off) so a handler that walks an
|
|
332
|
+
// over-broad prefix cannot inflate the count. Pagination
|
|
333
|
+
// params are stripped — we want the full set to count, never
|
|
334
|
+
// a page.
|
|
335
|
+
const handlerParams = { ...parsed.params };
|
|
336
|
+
delete handlerParams.limit;
|
|
337
|
+
delete handlerParams.page;
|
|
338
|
+
delete handlerParams.fields;
|
|
339
|
+
delete handlerParams.cursor;
|
|
340
|
+
if (!handlers.pushDownPattern)
|
|
341
|
+
delete handlerParams.pattern;
|
|
342
|
+
// Force a record-shape result so we can count tuples without
|
|
343
|
+
// dealing with the `string[]` / `Output[]` branch — the format
|
|
344
|
+
// is invisible to the caller, who only sees the count number.
|
|
345
|
+
handlerParams.format = "full";
|
|
346
|
+
const raw = await handlers.find({
|
|
347
|
+
...parsed,
|
|
348
|
+
params: handlerParams,
|
|
349
|
+
});
|
|
350
|
+
if (!Array.isArray(raw)) {
|
|
351
|
+
payload = 0;
|
|
352
|
+
break;
|
|
353
|
+
}
|
|
354
|
+
let rows = raw;
|
|
355
|
+
if (pattern !== undefined && !handlers.pushDownPattern) {
|
|
356
|
+
const re = compileSaveGlob(pattern);
|
|
357
|
+
rows = rows.filter(([uri]) => uri.startsWith(parsed.uri) &&
|
|
358
|
+
re.test(uri.slice(parsed.uri.length)));
|
|
359
|
+
}
|
|
360
|
+
payload = rows.length;
|
|
361
|
+
break;
|
|
362
|
+
}
|
|
363
|
+
if (needsPatternPostFilter || needsCursorPostFilter) {
|
|
364
|
+
// Filtered count: list the matching URIs (cheap
|
|
365
|
+
// `format=uris` path, no payload load) and return the
|
|
366
|
+
// length. Slower than push-down COUNT, but the only way to
|
|
367
|
+
// honour pattern/cursor without per-backend query
|
|
368
|
+
// translation.
|
|
369
|
+
const lsParams = {
|
|
370
|
+
...parsed.params,
|
|
371
|
+
format: "uris",
|
|
372
|
+
};
|
|
373
|
+
delete lsParams.fields;
|
|
374
|
+
delete lsParams.limit;
|
|
375
|
+
delete lsParams.page;
|
|
376
|
+
delete lsParams.cursor;
|
|
377
|
+
if (needsPatternPostFilter)
|
|
378
|
+
delete lsParams.pattern;
|
|
379
|
+
const raw = await handlers.ls({ ...parsed, params: lsParams });
|
|
380
|
+
if (!Array.isArray(raw)) {
|
|
381
|
+
payload = 0;
|
|
382
|
+
break;
|
|
383
|
+
}
|
|
384
|
+
let uris = raw;
|
|
385
|
+
if (needsPatternPostFilter) {
|
|
386
|
+
uris = uris.filter((uri) => matchesGlob(uri, parsed.uri, pattern));
|
|
387
|
+
}
|
|
388
|
+
if (needsCursorPostFilter) {
|
|
389
|
+
uris = filterUrisByCursor(uris, cursor, parsed.params.sortOrder);
|
|
390
|
+
}
|
|
391
|
+
payload = uris.length;
|
|
392
|
+
}
|
|
393
|
+
else {
|
|
394
|
+
payload = await handlers.count(parsed);
|
|
395
|
+
}
|
|
29
396
|
break;
|
|
397
|
+
}
|
|
30
398
|
default:
|
|
31
399
|
if (parsed.fn.startsWith("x-") && handlers.ext) {
|
|
32
400
|
payload = await handlers.ext(parsed);
|
|
@@ -38,3 +406,11 @@ export async function dispatchRead(urls, storeName, handlers) {
|
|
|
38
406
|
}
|
|
39
407
|
return out;
|
|
40
408
|
}
|
|
409
|
+
// Local copy of leafOf to avoid an extra import — same recipe as
|
|
410
|
+
// read.ts's exported helper. Keeping it local keeps the dispatch
|
|
411
|
+
// module's import surface tight (we already pull compareSortable,
|
|
412
|
+
// projectRecord, and the glob helpers).
|
|
413
|
+
function leafOf(uri) {
|
|
414
|
+
const idx = uri.lastIndexOf("/");
|
|
415
|
+
return idx < 0 ? uri : uri.slice(idx + 1);
|
|
416
|
+
}
|
|
@@ -1,44 +1,143 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* ElasticsearchStore — Elasticsearch implementation of
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
2
|
+
* ElasticsearchStore — Elasticsearch implementation of `EntityStore`.
|
|
3
|
+
*
|
|
4
|
+
* One backend, many entities. Per-entity index naming partitions
|
|
5
|
+
* documents in a way that keeps queries simple:
|
|
6
|
+
*
|
|
7
|
+
* - `BYTES_ENTITY` → index `{indexPrefix}_{protocol}_{hostname}`,
|
|
8
|
+
* docId = uri path. Document `_source` carries `payload` (base64)
|
|
9
|
+
* and a mirrored `path` field that `ls`/`count` regex-queries hit
|
|
10
|
+
* (ES 8 disallows regex on `_id`). This is the original layout —
|
|
11
|
+
* no migration.
|
|
12
|
+
* - any other schema → index
|
|
13
|
+
* `{indexPrefix}_{entityName}_{protocol}_{hostname}`, same docId
|
|
14
|
+
* convention. Document `_source` carries the record fields encoded
|
|
15
|
+
* per canonical `TYPE_TAGS` (bytes → base64, bigint → string,
|
|
16
|
+
* timestamp → ISO-8601 — see `./fields.ts`) plus the mirrored
|
|
17
|
+
* `path` field.
|
|
18
|
+
*
|
|
19
|
+
* ## Lifecycle
|
|
20
|
+
*
|
|
21
|
+
* `entitySupport(schema)` is pure — it plans the field set and computes
|
|
22
|
+
* a collision signature. `provisionEntity(meta)` writes a tiny meta
|
|
23
|
+
* doc to `{indexPrefix}__meta__/entities` keyed by entity name, body
|
|
24
|
+
* `{signature}`; subsequent `entityStatus(meta)` reads it back. The
|
|
25
|
+
* meta doc is also used by `status()` to list provisioned entities.
|
|
26
|
+
*
|
|
27
|
+
* ## Read params
|
|
28
|
+
*
|
|
29
|
+
* `fn=ls` / `fn=count` push down to an ES regex query against the
|
|
30
|
+
* mirrored `path.keyword` field with the shallow-direct-leaves
|
|
31
|
+
* predicate `<docPrefix>[^/]+`. Standard params (`limit`/`page`/
|
|
32
|
+
* `sortBy=uri`/`sortOrder`/`format`) translate to `size`/`from`/`sort`,
|
|
33
|
+
* with `_source: false` for the `format=uris` fast path.
|
|
34
|
+
*
|
|
35
|
+
* `fn=find` (v2 §3.3) reuses the regex query shape but composes the
|
|
36
|
+
* body from `saveGlobToRegexBody(pattern)` directly — `**` → `.*`
|
|
37
|
+
* (crosses `/`), so the same query that backs `ls` walks the entire
|
|
38
|
+
* subtree. The query stays prefix-anchored on `path.keyword` (a
|
|
39
|
+
* `keyword` field with a term dictionary), which Lucene can serve via
|
|
40
|
+
* its `RegexpQuery` using DFA traversal of the term dictionary;
|
|
41
|
+
* prefix-anchored regexes are the fast case (the engine restricts the
|
|
42
|
+
* scan to terms whose common prefix matches the regex's required
|
|
43
|
+
* prefix). The default `[^/]+` shallow body that `_leafQuery` falls
|
|
44
|
+
* back to when no pattern is set is NOT reachable from the find path:
|
|
45
|
+
* `parseUrl` guarantees `params.pattern` is populated whenever
|
|
46
|
+
* `fn=find` dispatches here (URI-embedded glob or the dual-accept
|
|
47
|
+
* `?pattern=` form).
|
|
48
|
+
*
|
|
49
|
+
* `pushDownCount: true` — ES has a native `_count` API that runs the
|
|
50
|
+
* same query shape without scoring or document materialisation. Deep
|
|
51
|
+
* counts (URLs containing `**`) route directly to `_count` with the
|
|
52
|
+
* `saveGlobToRegexBody`-composed regex; dispatch never walks via
|
|
53
|
+
* `handlers.find` to count.
|
|
14
54
|
*/
|
|
15
|
-
import type { DeleteResult, Output, StatusResult } from "../deps/jsr.io/@bandeira-tech/b3nd-core/0.
|
|
16
|
-
import { type BytesOnlyEntityMeta } from "../byte-entity-shim.js";
|
|
55
|
+
import type { DeleteResult, Output, StatusResult } from "../deps/jsr.io/@bandeira-tech/b3nd-core/0.25.0/src/types/types.js";
|
|
17
56
|
import type { EntityStore } from "../entity-store.js";
|
|
18
|
-
import type
|
|
57
|
+
import { type EntityMeta, type EntityRecord, type EntitySchema } from "../entity.js";
|
|
19
58
|
import type { StoreCapabilities, StoreWriteResult } from "../types.js";
|
|
59
|
+
import { type FieldPlan } from "../json-fields.js";
|
|
20
60
|
import type { ElasticsearchExecutor } from "./mod.js";
|
|
21
|
-
|
|
61
|
+
/**
|
|
62
|
+
* ElasticsearchStore-specific entity handle.
|
|
63
|
+
*
|
|
64
|
+
* `indexInfix` is `""` for BYTES_ENTITY (current layout) or
|
|
65
|
+
* `{entityName}_` for custom entities — it's spliced between the
|
|
66
|
+
* store-level `indexPrefix` and the URI's `protocol_hostname` to
|
|
67
|
+
* partition documents by entity without changing the docId scheme.
|
|
68
|
+
* `isBytes` selects the legacy bytes-payload doc shape vs the
|
|
69
|
+
* JSON-encoded record shape. `fields`/`declared`/`bytesFields` drive
|
|
70
|
+
* write-time validation and encoding; `signature` is used for
|
|
71
|
+
* collision detection in `entityStatus` / `provisionEntity`.
|
|
72
|
+
*/
|
|
73
|
+
export interface ElasticsearchEntityMeta extends EntityMeta {
|
|
74
|
+
readonly indexInfix: string;
|
|
75
|
+
readonly isBytes: boolean;
|
|
76
|
+
readonly fields: readonly FieldPlan[];
|
|
77
|
+
readonly declared: ReadonlySet<string>;
|
|
78
|
+
readonly bytesFields: ReadonlySet<string>;
|
|
79
|
+
readonly signature: string;
|
|
80
|
+
}
|
|
81
|
+
export declare class ElasticsearchStore implements EntityStore<ElasticsearchEntityMeta> {
|
|
22
82
|
private readonly indexPrefix;
|
|
23
83
|
private readonly executor;
|
|
24
84
|
constructor(indexPrefix: string, executor: ElasticsearchExecutor);
|
|
25
|
-
entitySupport(schema: EntitySchema):
|
|
26
|
-
entityStatus(meta:
|
|
27
|
-
provisionEntity(
|
|
28
|
-
write(meta:
|
|
85
|
+
entitySupport(schema: EntitySchema): ElasticsearchEntityMeta;
|
|
86
|
+
entityStatus(meta: ElasticsearchEntityMeta): Promise<"live" | "unprovisioned">;
|
|
87
|
+
provisionEntity(meta: ElasticsearchEntityMeta): Promise<void>;
|
|
88
|
+
write(meta: ElasticsearchEntityMeta, entries: {
|
|
29
89
|
uri: string;
|
|
30
90
|
record: EntityRecord;
|
|
31
91
|
}[]): Promise<StoreWriteResult[]>;
|
|
32
|
-
read<T = EntityRecord | undefined>(meta:
|
|
33
|
-
delete(meta: BytesOnlyEntityMeta, uris: string[]): Promise<DeleteResult[]>;
|
|
34
|
-
private _writeBytes;
|
|
35
|
-
private _readBytes;
|
|
92
|
+
read<T = EntityRecord | undefined>(meta: ElasticsearchEntityMeta, urls: string[]): Promise<Output<T>[]>;
|
|
36
93
|
private _readOne;
|
|
94
|
+
/**
|
|
95
|
+
* Lucene regex query for shallow direct-leaves under `docPrefix`.
|
|
96
|
+
* Pattern push-down: splice the glob's regex body in place of the
|
|
97
|
+
* default `[^/]+`. Cursor push-down: wrap the regex in a `bool`
|
|
98
|
+
* with a `range` on `path.keyword` (`gt` for asc, `lt` for desc).
|
|
99
|
+
*/
|
|
37
100
|
private _leafQuery;
|
|
38
101
|
private _ls;
|
|
102
|
+
/**
|
|
103
|
+
* Lucene regex query for the deep walk (`fn=find`) under
|
|
104
|
+
* `docPrefix`. Mirrors `_leafQuery` but REQUIRES a pattern: parseUrl
|
|
105
|
+
* guarantees `params.pattern` is populated whenever fn=find
|
|
106
|
+
* dispatches here (URI-embedded glob or dual-accept `?pattern=`).
|
|
107
|
+
* If pattern is absent we throw rather than silently widening to
|
|
108
|
+
* `.*` — that would conflate "find everything" with a parser bug.
|
|
109
|
+
*
|
|
110
|
+
* The composed body uses `saveGlobToRegexBody` (which emits `.*` for
|
|
111
|
+
* `**`, crossing `/`), prepended with the escaped `docPrefix`. The
|
|
112
|
+
* resulting regex is prefix-anchored on the keyword field — Lucene
|
|
113
|
+
* restricts the term-dictionary scan to terms starting with the
|
|
114
|
+
* required literal prefix, so even broad `**` queries don't degrade
|
|
115
|
+
* to a full term scan.
|
|
116
|
+
*/
|
|
117
|
+
private _subtreeQuery;
|
|
118
|
+
private _find;
|
|
39
119
|
private _count;
|
|
40
|
-
|
|
120
|
+
delete(meta: ElasticsearchEntityMeta, uris: string[]): Promise<DeleteResult[]>;
|
|
41
121
|
status(): Promise<StatusResult>;
|
|
42
122
|
capabilities(): StoreCapabilities;
|
|
123
|
+
/** Per-entity, per-`protocol_hostname` data index. */
|
|
124
|
+
private _dataIndex;
|
|
125
|
+
/** Single meta index holding `{name → signature}` provisioning records. */
|
|
126
|
+
private _metaIndex;
|
|
127
|
+
/**
|
|
128
|
+
* Read the persisted signature for an entity, or `null` if no
|
|
129
|
+
* provisioning record exists.
|
|
130
|
+
*/
|
|
131
|
+
private _readMetaSignature;
|
|
132
|
+
/**
|
|
133
|
+
* Scan the meta index for every provisioned entity name. Used by
|
|
134
|
+
* `status()` to advertise the entities a peer can talk to.
|
|
135
|
+
*/
|
|
136
|
+
private _listProvisionedEntities;
|
|
137
|
+
/**
|
|
138
|
+
* Collect any `ReadableStream` values on `bytes`-tagged fields into
|
|
139
|
+
* `Uint8Array` and return a shallow-copied record.
|
|
140
|
+
*/
|
|
141
|
+
private _normaliseBytesFields;
|
|
43
142
|
}
|
|
44
143
|
//# sourceMappingURL=store.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/elasticsearch/store.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/elasticsearch/store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqDG;AAEH,OAAO,KAAK,EACV,YAAY,EACZ,MAAM,EACN,YAAY,EACb,MAAM,mEAAmE,CAAC;AAQ3E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAEL,KAAK,UAAU,EACf,KAAK,YAAY,EACjB,KAAK,YAAY,EAClB,MAAM,cAAc,CAAC;AAEtB,OAAO,KAAK,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AACvE,OAAO,EAIL,KAAK,SAAS,EAEf,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,UAAU,CAAC;AA4BtD;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,uBAAwB,SAAQ,UAAU;IACzD,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,SAAS,SAAS,EAAE,CAAC;IACtC,QAAQ,CAAC,QAAQ,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IACvC,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAC1C,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAmCD,qBAAa,kBACX,YAAW,WAAW,CAAC,uBAAuB,CAAC;IAC/C,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAwB;gBAErC,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,qBAAqB;IAUhE,aAAa,CAAC,MAAM,EAAE,YAAY,GAAG,uBAAuB;IA0CtD,YAAY,CAChB,IAAI,EAAE,uBAAuB,GAC5B,OAAO,CAAC,MAAM,GAAG,eAAe,CAAC;IAM9B,eAAe,CAAC,IAAI,EAAE,uBAAuB,GAAG,OAAO,CAAC,IAAI,CAAC;IAoB7D,KAAK,CACT,IAAI,EAAE,uBAAuB,EAC7B,OAAO,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,YAAY,CAAA;KAAE,EAAE,GAC/C,OAAO,CAAC,gBAAgB,EAAE,CAAC;IA0E9B,IAAI,CAAC,CAAC,GAAG,YAAY,GAAG,SAAS,EAC/B,IAAI,EAAE,uBAAuB,EAC7B,IAAI,EAAE,MAAM,EAAE,GACb,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;YAcT,QAAQ;IAkBtB;;;;;OAKG;IACH,OAAO,CAAC,UAAU;YAwBJ,GAAG;IAyEjB;;;;;;;;;;;;;;OAcG;IACH,OAAO,CAAC,aAAa;YA8BP,KAAK;YA0DL,MAAM;IAyBd,MAAM,CACV,IAAI,EAAE,uBAAuB,EAC7B,IAAI,EAAE,MAAM,EAAE,GACb,OAAO,CAAC,YAAY,EAAE,CAAC;IAmCpB,MAAM,IAAI,OAAO,CAAC,YAAY,CAAC;IA2BrC,YAAY,IAAI,iBAAiB;IAMjC,sDAAsD;IACtD,OAAO,CAAC,UAAU;IAQlB,2EAA2E;IAC3E,OAAO,CAAC,UAAU;IAIlB;;;OAGG;YACW,kBAAkB;IAYhC;;;OAGG;YACW,wBAAwB;IAYtC;;;OAGG;YACW,qBAAqB;CAmBpC"}
|