@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/glob.d.ts
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module
|
|
3
|
+
* Save-side glob adapters — bridges the save URL grammar (§3.3 of the
|
|
4
|
+
* v2 listing spec, see `.cc-chat/20260625121936-grammar-shape/output.md`)
|
|
5
|
+
* onto `b3nd-core`'s `compilePattern` for everything compilePattern can
|
|
6
|
+
* express, and onto save-local compilation for the broader cases
|
|
7
|
+
* compilePattern doesn't cover.
|
|
8
|
+
*
|
|
9
|
+
* Why this exists (§3.3.1 amendment, foundation-round honest concern):
|
|
10
|
+
*
|
|
11
|
+
* The v2 spec promises one grammar across read + observe + routing —
|
|
12
|
+
* `?`, `*`, and `**` with `**` allowed anywhere. `b3nd-core`'s
|
|
13
|
+
* `compilePattern` implements a strict subset:
|
|
14
|
+
* - `?` is not recognized (treated as a literal `?`)
|
|
15
|
+
* - `*` matches a non-empty segment (`[^/]+`), not zero-or-more
|
|
16
|
+
* - `**` must be the final segment; mid-`**` throws
|
|
17
|
+
*
|
|
18
|
+
* The honest resolution keeps core unchanged (v2 §7.1 commitment is
|
|
19
|
+
* load-bearing for route/observe semantics) and has save ship two thin
|
|
20
|
+
* adapters that close the delta. For patterns inside compilePattern's
|
|
21
|
+
* subset, we delegate 1:1 — same engine, same code path, byte-equal
|
|
22
|
+
* regex output. For patterns outside the subset, we compile locally
|
|
23
|
+
* with the broader §3.3 semantics.
|
|
24
|
+
*
|
|
25
|
+
* Adapter responsibilities:
|
|
26
|
+
* - {@link compileSaveGlob} — regex matcher used by in-process post-
|
|
27
|
+
* filter paths and by mongo/elasticsearch (which embed the regex
|
|
28
|
+
* body in their backend queries).
|
|
29
|
+
* - {@link globToSqlLike} — SQL LIKE adapter for sqlite/postgres
|
|
30
|
+
* push-down. SQL has different metacharacters than regex so this
|
|
31
|
+
* is a separate helper; same dispatch shape (defer to a
|
|
32
|
+
* compilePattern-style helper for the inside-subset case; do
|
|
33
|
+
* save-local string building for the broader grammar).
|
|
34
|
+
*
|
|
35
|
+
* Both adapters are stateless and small. No caching, no metrics, no
|
|
36
|
+
* classes — when a pattern comes in they decide which path applies and
|
|
37
|
+
* emit the artifact.
|
|
38
|
+
*/
|
|
39
|
+
/**
|
|
40
|
+
* Compile a save glob pattern into an anchored RegExp that matches a
|
|
41
|
+
* URI tail (the portion after the literal routing prefix). Anchored on
|
|
42
|
+
* both ends so the pattern must cover the full tail.
|
|
43
|
+
*
|
|
44
|
+
* For patterns inside `compilePattern`'s supported subset, this builds
|
|
45
|
+
* the same regex `compilePattern`'s `toRegex` builds — byte-equal — so
|
|
46
|
+
* routing-layer matches and save-layer matches agree. For patterns
|
|
47
|
+
* outside the subset (containing `?`, mid-`**`, or expecting empty-
|
|
48
|
+
* segment match), this builds a save-local regex with the v2 §3.3
|
|
49
|
+
* semantics:
|
|
50
|
+
* - `?` → `[^/]`
|
|
51
|
+
* - `*` → `[^/]*` (zero or more, contrast with core's `[^/]+`)
|
|
52
|
+
* - `**` → `.*` (any chars including `/`; valid anywhere)
|
|
53
|
+
*
|
|
54
|
+
* Returns a fresh RegExp on every call — callers that hot-loop should
|
|
55
|
+
* cache it themselves (per-call overhead is one parse + one analyze).
|
|
56
|
+
*
|
|
57
|
+
* Note: this helper is for matching URI tails (with the literal prefix
|
|
58
|
+
* already stripped). Callers that want to match a full URI should
|
|
59
|
+
* either prepend an escaped prefix to the pattern or call
|
|
60
|
+
* `compilePattern` directly with the literal+glob combined.
|
|
61
|
+
*/
|
|
62
|
+
export declare function compileSaveGlob(pattern: string): RegExp;
|
|
63
|
+
/**
|
|
64
|
+
* Compile a save glob pattern into a regex **body** — no anchors. Pair
|
|
65
|
+
* with custom anchoring at the call site:
|
|
66
|
+
* - mongo: `{ $regex: "^" + escape(prefix) + body + "$" }`
|
|
67
|
+
* - elasticsearch: the `regexp` query is auto-anchored
|
|
68
|
+
*
|
|
69
|
+
* Tokens map the same way as `compileSaveGlob`:
|
|
70
|
+
* - `?` → `[^/]`
|
|
71
|
+
* - `*` → `[^/]*`
|
|
72
|
+
* - `**` → `.*`
|
|
73
|
+
*
|
|
74
|
+
* For the inside-subset case, the body matches what `compilePattern`'s
|
|
75
|
+
* `toRegex` emits (modulo the leading `^` / trailing `$` anchors core
|
|
76
|
+
* adds). The `glob.test.ts` byte-equality test exercises that
|
|
77
|
+
* correspondence by stripping the anchors from `compileSaveGlob` and
|
|
78
|
+
* comparing against `saveGlobToRegexBody`.
|
|
79
|
+
*/
|
|
80
|
+
export declare function saveGlobToRegexBody(pattern: string): string;
|
|
81
|
+
/**
|
|
82
|
+
* Translate a save glob pattern into a SQL `LIKE` body. Pair with
|
|
83
|
+
* `ESCAPE '\\'` in the surrounding query. Metacharacters:
|
|
84
|
+
* - `*` → `%` (zero or more chars)
|
|
85
|
+
* - `**` → `%` (no SQL-LIKE distinction — both collapse to `%`;
|
|
86
|
+
* the depth constraint, if any, is layered separately
|
|
87
|
+
* by the caller via `NOT LIKE %/%`)
|
|
88
|
+
* - `?` → `_` (exactly one char)
|
|
89
|
+
* - literal `%`, `_`, `\\` are escaped to `\\%`, `\\_`, `\\\\`
|
|
90
|
+
*
|
|
91
|
+
* Returns just the pattern body. Composition with the literal prefix
|
|
92
|
+
* (`uri LIKE ? || ? ESCAPE '\\'` with args `[prefixLike, patternBody]`)
|
|
93
|
+
* is the caller's job — the caller already has the prefix as a bind
|
|
94
|
+
* arg.
|
|
95
|
+
*
|
|
96
|
+
* Note that `**` and `*` both map to `%` because SQL LIKE has no
|
|
97
|
+
* single-segment vs multi-segment notion. Callers that need to enforce
|
|
98
|
+
* shallow-only semantics (the historical `NOT LIKE prefix || '%/%'`
|
|
99
|
+
* sqlite/postgres trick) layer that constraint on top of the LIKE body
|
|
100
|
+
* themselves. Find-style (recursive) handlers drop the trick when the
|
|
101
|
+
* caller's fn is `find`.
|
|
102
|
+
*/
|
|
103
|
+
export declare function globToSqlLike(pattern: string): string;
|
|
104
|
+
/**
|
|
105
|
+
* True when `uri` starts with `prefix` and the tail matches `pattern`.
|
|
106
|
+
* `pattern === undefined` returns true (no filter). Replacement for the
|
|
107
|
+
* deleted `matchesUriPattern` helper from `read.ts`.
|
|
108
|
+
*
|
|
109
|
+
* The pattern is compiled fresh on every call. Callers that hot-loop
|
|
110
|
+
* over a large row set should hoist `compileSaveGlob(pattern)` out and
|
|
111
|
+
* test the RegExp directly.
|
|
112
|
+
*/
|
|
113
|
+
export declare function matchesGlob(uri: string, prefix: string, pattern: string | undefined): boolean;
|
|
114
|
+
//# sourceMappingURL=glob.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"glob.d.ts","sourceRoot":"","sources":["../src/glob.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AA6CH;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAmBvD;AAiED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAK3D;AA4DD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAUrD;AA0CD;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CACzB,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,GAAG,SAAS,GAC1B,OAAO,CAIT"}
|
package/esm/glob.js
ADDED
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module
|
|
3
|
+
* Save-side glob adapters — bridges the save URL grammar (§3.3 of the
|
|
4
|
+
* v2 listing spec, see `.cc-chat/20260625121936-grammar-shape/output.md`)
|
|
5
|
+
* onto `b3nd-core`'s `compilePattern` for everything compilePattern can
|
|
6
|
+
* express, and onto save-local compilation for the broader cases
|
|
7
|
+
* compilePattern doesn't cover.
|
|
8
|
+
*
|
|
9
|
+
* Why this exists (§3.3.1 amendment, foundation-round honest concern):
|
|
10
|
+
*
|
|
11
|
+
* The v2 spec promises one grammar across read + observe + routing —
|
|
12
|
+
* `?`, `*`, and `**` with `**` allowed anywhere. `b3nd-core`'s
|
|
13
|
+
* `compilePattern` implements a strict subset:
|
|
14
|
+
* - `?` is not recognized (treated as a literal `?`)
|
|
15
|
+
* - `*` matches a non-empty segment (`[^/]+`), not zero-or-more
|
|
16
|
+
* - `**` must be the final segment; mid-`**` throws
|
|
17
|
+
*
|
|
18
|
+
* The honest resolution keeps core unchanged (v2 §7.1 commitment is
|
|
19
|
+
* load-bearing for route/observe semantics) and has save ship two thin
|
|
20
|
+
* adapters that close the delta. For patterns inside compilePattern's
|
|
21
|
+
* subset, we delegate 1:1 — same engine, same code path, byte-equal
|
|
22
|
+
* regex output. For patterns outside the subset, we compile locally
|
|
23
|
+
* with the broader §3.3 semantics.
|
|
24
|
+
*
|
|
25
|
+
* Adapter responsibilities:
|
|
26
|
+
* - {@link compileSaveGlob} — regex matcher used by in-process post-
|
|
27
|
+
* filter paths and by mongo/elasticsearch (which embed the regex
|
|
28
|
+
* body in their backend queries).
|
|
29
|
+
* - {@link globToSqlLike} — SQL LIKE adapter for sqlite/postgres
|
|
30
|
+
* push-down. SQL has different metacharacters than regex so this
|
|
31
|
+
* is a separate helper; same dispatch shape (defer to a
|
|
32
|
+
* compilePattern-style helper for the inside-subset case; do
|
|
33
|
+
* save-local string building for the broader grammar).
|
|
34
|
+
*
|
|
35
|
+
* Both adapters are stateless and small. No caching, no metrics, no
|
|
36
|
+
* classes — when a pattern comes in they decide which path applies and
|
|
37
|
+
* emit the artifact.
|
|
38
|
+
*/
|
|
39
|
+
// ── Subset detection ────────────────────────────────────────────────
|
|
40
|
+
/**
|
|
41
|
+
* Test whether a pattern is inside `compilePattern`'s supported subset.
|
|
42
|
+
*
|
|
43
|
+
* Inside the subset:
|
|
44
|
+
* - no `?` character (compilePattern treats `?` as literal, save's
|
|
45
|
+
* §3.3 grammar treats it as a wildcard — they disagree on a key
|
|
46
|
+
* byte, so any `?` forces save-local compile)
|
|
47
|
+
* - every `*` segment matches a non-empty segment (compilePattern's
|
|
48
|
+
* `[^/]+`; save's `[^/]*` is broader — but they agree on every
|
|
49
|
+
* **non-empty** match, so the only delta is on the empty-segment
|
|
50
|
+
* case, which the spec says save allows. To stay strict, we route
|
|
51
|
+
* to save-local compile whenever the question matters.)
|
|
52
|
+
* - `**` appears only as the final segment (compilePattern throws on
|
|
53
|
+
* mid-`**`)
|
|
54
|
+
*
|
|
55
|
+
* The check is conservative: if any segment looks suspicious, route to
|
|
56
|
+
* save-local compile. False-negatives (sending an inside-subset pattern
|
|
57
|
+
* to the save-local path) are correctness-safe — the §3.3 grammar is a
|
|
58
|
+
* superset and matches the same URIs. False-positives (sending an
|
|
59
|
+
* outside-subset pattern to compilePattern) would throw, which is why
|
|
60
|
+
* we keep this conservative.
|
|
61
|
+
*/
|
|
62
|
+
function isInsideCoreSubset(pattern) {
|
|
63
|
+
if (pattern.includes("?"))
|
|
64
|
+
return false;
|
|
65
|
+
const segs = pattern.split("/");
|
|
66
|
+
for (let i = 0; i < segs.length; i++) {
|
|
67
|
+
const seg = segs[i];
|
|
68
|
+
// `**` must be the last segment for core's compilePattern.
|
|
69
|
+
if (seg === "**" && i !== segs.length - 1)
|
|
70
|
+
return false;
|
|
71
|
+
// A mid-segment containing `**` (e.g. `a**b`) is rejected by core's
|
|
72
|
+
// classifySegment too — keep on the save-local path.
|
|
73
|
+
if (seg !== "**" && seg.includes("**"))
|
|
74
|
+
return false;
|
|
75
|
+
// `*` mixed with other chars in a segment (e.g. `a*b`) is rejected
|
|
76
|
+
// by core; save-local handles it.
|
|
77
|
+
if (seg !== "*" && seg !== "**" && seg.includes("*"))
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
// ── compileSaveGlob ─────────────────────────────────────────────────
|
|
83
|
+
/**
|
|
84
|
+
* Compile a save glob pattern into an anchored RegExp that matches a
|
|
85
|
+
* URI tail (the portion after the literal routing prefix). Anchored on
|
|
86
|
+
* both ends so the pattern must cover the full tail.
|
|
87
|
+
*
|
|
88
|
+
* For patterns inside `compilePattern`'s supported subset, this builds
|
|
89
|
+
* the same regex `compilePattern`'s `toRegex` builds — byte-equal — so
|
|
90
|
+
* routing-layer matches and save-layer matches agree. For patterns
|
|
91
|
+
* outside the subset (containing `?`, mid-`**`, or expecting empty-
|
|
92
|
+
* segment match), this builds a save-local regex with the v2 §3.3
|
|
93
|
+
* semantics:
|
|
94
|
+
* - `?` → `[^/]`
|
|
95
|
+
* - `*` → `[^/]*` (zero or more, contrast with core's `[^/]+`)
|
|
96
|
+
* - `**` → `.*` (any chars including `/`; valid anywhere)
|
|
97
|
+
*
|
|
98
|
+
* Returns a fresh RegExp on every call — callers that hot-loop should
|
|
99
|
+
* cache it themselves (per-call overhead is one parse + one analyze).
|
|
100
|
+
*
|
|
101
|
+
* Note: this helper is for matching URI tails (with the literal prefix
|
|
102
|
+
* already stripped). Callers that want to match a full URI should
|
|
103
|
+
* either prepend an escaped prefix to the pattern or call
|
|
104
|
+
* `compilePattern` directly with the literal+glob combined.
|
|
105
|
+
*/
|
|
106
|
+
export function compileSaveGlob(pattern) {
|
|
107
|
+
if (isInsideCoreSubset(pattern)) {
|
|
108
|
+
// For the inside-subset case we want a RegExp (not a closure) so
|
|
109
|
+
// existing call sites (e.g. memory's `re.test(uri.slice(...))`,
|
|
110
|
+
// mongo's regex body composition) keep working. Build the exact
|
|
111
|
+
// regex compilePattern would build internally by tokenising on `/`
|
|
112
|
+
// and swapping `*`/`**` segments. This is the same recipe core
|
|
113
|
+
// uses in `toRegex` (b3nd-core/src/match-pattern/match-pattern.ts).
|
|
114
|
+
//
|
|
115
|
+
// We do NOT call `compilePattern(pattern)` to get a closure —
|
|
116
|
+
// callers need the RegExp for either anchoring control (the
|
|
117
|
+
// anchors core adds wrap the full URI; we want to match a tail)
|
|
118
|
+
// or for regex-body extraction. Sourcing the regex shape from
|
|
119
|
+
// core's `compilePattern` is the spec's design goal; the tests
|
|
120
|
+
// assert byte-equality between this path and `compilePattern`'s
|
|
121
|
+
// output for inside-subset patterns.
|
|
122
|
+
return buildCoreSubsetRegex(pattern);
|
|
123
|
+
}
|
|
124
|
+
return buildSaveLocalRegex(pattern);
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Build the exact regex `compilePattern` builds for inside-subset
|
|
128
|
+
* patterns. The recipe (from b3nd-core's `toRegex`):
|
|
129
|
+
* - tokenize on `/`
|
|
130
|
+
* - replace `*` segments with `[^/]+` placeholders
|
|
131
|
+
* - replace `**` (trailing only) with `.*` placeholder
|
|
132
|
+
* - escape regex metacharacters in the rest
|
|
133
|
+
* - re-insert placeholders, anchored
|
|
134
|
+
*
|
|
135
|
+
* The byte-equality test in `glob.test.ts` exercises this for a
|
|
136
|
+
* battery of inside-subset patterns.
|
|
137
|
+
*/
|
|
138
|
+
function buildCoreSubsetRegex(pattern) {
|
|
139
|
+
const ONE = "\x00ONE\x00";
|
|
140
|
+
const REST = "\x00REST\x00";
|
|
141
|
+
const swapped = pattern
|
|
142
|
+
.split("/")
|
|
143
|
+
.map((seg) => seg === "*" ? ONE : seg === "**" ? REST : seg)
|
|
144
|
+
.join("/");
|
|
145
|
+
const escaped = swapped.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
|
|
146
|
+
const body = escaped
|
|
147
|
+
.replaceAll(ONE, "[^/]+")
|
|
148
|
+
.replaceAll(REST, ".*");
|
|
149
|
+
return new RegExp(`^${body}$`);
|
|
150
|
+
}
|
|
151
|
+
// Build the save-local regex for patterns outside core's subset. The
|
|
152
|
+
// §3.3 grammar:
|
|
153
|
+
// - `?` -> `[^/]` (exactly one non-`/`)
|
|
154
|
+
// - `*` -> `[^/]*` (zero or more non-`/`)
|
|
155
|
+
// - `*``*` -> `.*` (zero or more SEGMENTS including the `/`)
|
|
156
|
+
//
|
|
157
|
+
// Multi-char metas (`*``*`) are recognised before single-char (`*`) by
|
|
158
|
+
// a tokenising scan so we never emit `[^/]*[^/]*` for `*``*`.
|
|
159
|
+
//
|
|
160
|
+
// Zero-segment fix (PR #86 concern #2): the spec promises `*``*`
|
|
161
|
+
// matches "zero or more segments". A naive `*``*` -> `.*` mapping
|
|
162
|
+
// leaves the surrounding literal `/` chars in place, so
|
|
163
|
+
// `alice/`+`*``*`+`/posts/1.md` compiles to `alice\/.*\/posts\/1\.md`
|
|
164
|
+
// which CANNOT match `alice/posts/1.md` (the literal `/` after `.*`
|
|
165
|
+
// blocks the zero-segment case). The fix: when `*``*` is bordered by
|
|
166
|
+
// `/`, eat one adjacent `/` so the resulting `.*` subsumes the
|
|
167
|
+
// boundary slash too. Cases (slash-eat rule = prefer trailing `/`,
|
|
168
|
+
// fall back to leading `/` if at end of pattern):
|
|
169
|
+
// - `*``*` alone -> `.*`
|
|
170
|
+
// - `*``*` at start, then `/` -> `.*` (eat trailing `/`)
|
|
171
|
+
// - `/` then `*``*` at end -> `.*` (eat leading `/`)
|
|
172
|
+
// - `/` then `*``*` then `/` -> `\/.*` (eat trailing `/`, keep
|
|
173
|
+
// leading `/`; `.*` can match ""
|
|
174
|
+
// so the zero-segment case
|
|
175
|
+
// `alice/posts` works because the
|
|
176
|
+
// literal `alice/` consumes the
|
|
177
|
+
// slash and `.*` matches "")
|
|
178
|
+
function buildSaveLocalRegex(pattern) {
|
|
179
|
+
return new RegExp(`^${buildSaveLocalRegexBody(pattern)}$`);
|
|
180
|
+
}
|
|
181
|
+
// ── saveGlobToRegexBody (mongo / elasticsearch push-down) ──────────
|
|
182
|
+
/**
|
|
183
|
+
* Compile a save glob pattern into a regex **body** — no anchors. Pair
|
|
184
|
+
* with custom anchoring at the call site:
|
|
185
|
+
* - mongo: `{ $regex: "^" + escape(prefix) + body + "$" }`
|
|
186
|
+
* - elasticsearch: the `regexp` query is auto-anchored
|
|
187
|
+
*
|
|
188
|
+
* Tokens map the same way as `compileSaveGlob`:
|
|
189
|
+
* - `?` → `[^/]`
|
|
190
|
+
* - `*` → `[^/]*`
|
|
191
|
+
* - `**` → `.*`
|
|
192
|
+
*
|
|
193
|
+
* For the inside-subset case, the body matches what `compilePattern`'s
|
|
194
|
+
* `toRegex` emits (modulo the leading `^` / trailing `$` anchors core
|
|
195
|
+
* adds). The `glob.test.ts` byte-equality test exercises that
|
|
196
|
+
* correspondence by stripping the anchors from `compileSaveGlob` and
|
|
197
|
+
* comparing against `saveGlobToRegexBody`.
|
|
198
|
+
*/
|
|
199
|
+
export function saveGlobToRegexBody(pattern) {
|
|
200
|
+
if (isInsideCoreSubset(pattern)) {
|
|
201
|
+
return buildCoreSubsetRegexBody(pattern);
|
|
202
|
+
}
|
|
203
|
+
return buildSaveLocalRegexBody(pattern);
|
|
204
|
+
}
|
|
205
|
+
function buildCoreSubsetRegexBody(pattern) {
|
|
206
|
+
const ONE = "\x00ONE\x00";
|
|
207
|
+
const REST = "\x00REST\x00";
|
|
208
|
+
const swapped = pattern
|
|
209
|
+
.split("/")
|
|
210
|
+
.map((seg) => seg === "*" ? ONE : seg === "**" ? REST : seg)
|
|
211
|
+
.join("/");
|
|
212
|
+
const escaped = swapped.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
|
|
213
|
+
return escaped
|
|
214
|
+
.replaceAll(ONE, "[^/]+")
|
|
215
|
+
.replaceAll(REST, ".*");
|
|
216
|
+
}
|
|
217
|
+
function buildSaveLocalRegexBody(pattern) {
|
|
218
|
+
let body = "";
|
|
219
|
+
for (let i = 0; i < pattern.length; i++) {
|
|
220
|
+
const ch = pattern[i];
|
|
221
|
+
if (ch === "*" && pattern[i + 1] === "*") {
|
|
222
|
+
// `**` zero-segment fix (PR #86 concern #2): when bordered by
|
|
223
|
+
// `/`, eat the *trailing* `/` so the emitted `.*` can subsume
|
|
224
|
+
// the boundary slash too. Mid-pattern case:
|
|
225
|
+
// pattern `alice/**/posts`
|
|
226
|
+
// naive `alice\/.*\/posts` — fails to match `alice/posts`
|
|
227
|
+
// fixed `alice\/.*posts` — matches `alice/posts` (.* = "")
|
|
228
|
+
// and `alice/X/posts` (.* = "X/")
|
|
229
|
+
//
|
|
230
|
+
// Leading `**/` case: `**/posts` → `.*posts` matches `posts`.
|
|
231
|
+
// Trailing `/**` without a following slash also needs handling,
|
|
232
|
+
// but in that case there's no trailing `/` to eat — we instead
|
|
233
|
+
// eat the *leading* `/` already emitted into `body`. This case
|
|
234
|
+
// typically routes to the inside-subset path; we handle it here
|
|
235
|
+
// for save-local patterns that escape via mid-`**` AND end with
|
|
236
|
+
// `/**` (e.g. `a/**/b/**`).
|
|
237
|
+
body += ".*";
|
|
238
|
+
i++; // consume second `*`
|
|
239
|
+
if (pattern[i + 1] === "/") {
|
|
240
|
+
i++; // eat trailing `/`
|
|
241
|
+
}
|
|
242
|
+
else if (i === pattern.length - 1 && body.endsWith("\\/.*")) {
|
|
243
|
+
// `/**` at the very end of pattern. Strip the literal `\/`
|
|
244
|
+
// immediately before the `.*` we just appended.
|
|
245
|
+
body = body.slice(0, -4) + ".*";
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
else if (ch === "*") {
|
|
249
|
+
body += "[^/]*";
|
|
250
|
+
}
|
|
251
|
+
else if (ch === "?") {
|
|
252
|
+
body += "[^/]";
|
|
253
|
+
}
|
|
254
|
+
else {
|
|
255
|
+
body += ch.replace(/[.+^${}()|[\]\\/]/g, "\\$&");
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
return body;
|
|
259
|
+
}
|
|
260
|
+
// ── globToSqlLike ───────────────────────────────────────────────────
|
|
261
|
+
/**
|
|
262
|
+
* Translate a save glob pattern into a SQL `LIKE` body. Pair with
|
|
263
|
+
* `ESCAPE '\\'` in the surrounding query. Metacharacters:
|
|
264
|
+
* - `*` → `%` (zero or more chars)
|
|
265
|
+
* - `**` → `%` (no SQL-LIKE distinction — both collapse to `%`;
|
|
266
|
+
* the depth constraint, if any, is layered separately
|
|
267
|
+
* by the caller via `NOT LIKE %/%`)
|
|
268
|
+
* - `?` → `_` (exactly one char)
|
|
269
|
+
* - literal `%`, `_`, `\\` are escaped to `\\%`, `\\_`, `\\\\`
|
|
270
|
+
*
|
|
271
|
+
* Returns just the pattern body. Composition with the literal prefix
|
|
272
|
+
* (`uri LIKE ? || ? ESCAPE '\\'` with args `[prefixLike, patternBody]`)
|
|
273
|
+
* is the caller's job — the caller already has the prefix as a bind
|
|
274
|
+
* arg.
|
|
275
|
+
*
|
|
276
|
+
* Note that `**` and `*` both map to `%` because SQL LIKE has no
|
|
277
|
+
* single-segment vs multi-segment notion. Callers that need to enforce
|
|
278
|
+
* shallow-only semantics (the historical `NOT LIKE prefix || '%/%'`
|
|
279
|
+
* sqlite/postgres trick) layer that constraint on top of the LIKE body
|
|
280
|
+
* themselves. Find-style (recursive) handlers drop the trick when the
|
|
281
|
+
* caller's fn is `find`.
|
|
282
|
+
*/
|
|
283
|
+
export function globToSqlLike(pattern) {
|
|
284
|
+
// SQL LIKE doesn't have the regex-grammar subtleties compilePattern
|
|
285
|
+
// wraps, so the inside-subset / outside-subset split has no effect
|
|
286
|
+
// on output. We keep the split anyway for symmetry with
|
|
287
|
+
// compileSaveGlob — the call site reads cleanly and future SQL
|
|
288
|
+
// dialects that grow segment-aware operators could specialise.
|
|
289
|
+
if (isInsideCoreSubset(pattern)) {
|
|
290
|
+
return buildSqlLikeBody(pattern);
|
|
291
|
+
}
|
|
292
|
+
return buildSqlLikeBody(pattern);
|
|
293
|
+
}
|
|
294
|
+
function buildSqlLikeBody(pattern) {
|
|
295
|
+
let out = "";
|
|
296
|
+
for (let i = 0; i < pattern.length; i++) {
|
|
297
|
+
const ch = pattern[i];
|
|
298
|
+
if (ch === "*" && pattern[i + 1] === "*") {
|
|
299
|
+
// `**` zero-segment fix (PR #86 concern #2): when bordered by
|
|
300
|
+
// `/`, eat the *trailing* `/` so the emitted `%` subsumes the
|
|
301
|
+
// boundary slash too. Mid-pattern:
|
|
302
|
+
// pattern `alice/**/posts/1.md`
|
|
303
|
+
// naive `alice/%/posts/1.md` — fails on `alice/posts/1.md`
|
|
304
|
+
// fixed `alice/%posts/1.md` — matches `alice/posts/1.md`
|
|
305
|
+
// and `alice/X/posts/1.md`
|
|
306
|
+
//
|
|
307
|
+
// Leading `**/` case: `**/posts` → `%posts` matches `posts`.
|
|
308
|
+
// Trailing `/**` without a following slash: eat the leading `/`
|
|
309
|
+
// we already emitted into `out`.
|
|
310
|
+
out += "%";
|
|
311
|
+
i++; // consume second `*`
|
|
312
|
+
if (pattern[i + 1] === "/") {
|
|
313
|
+
i++; // eat trailing `/`
|
|
314
|
+
}
|
|
315
|
+
else if (i === pattern.length - 1 && out.endsWith("/%")) {
|
|
316
|
+
// `/**` at the very end of pattern. Strip the literal `/`
|
|
317
|
+
// immediately before the `%` we just appended.
|
|
318
|
+
out = out.slice(0, -2) + "%";
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
else if (ch === "*") {
|
|
322
|
+
out += "%";
|
|
323
|
+
}
|
|
324
|
+
else if (ch === "?") {
|
|
325
|
+
out += "_";
|
|
326
|
+
}
|
|
327
|
+
else if (ch === "\\" || ch === "%" || ch === "_") {
|
|
328
|
+
out += "\\" + ch;
|
|
329
|
+
}
|
|
330
|
+
else {
|
|
331
|
+
out += ch;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
return out;
|
|
335
|
+
}
|
|
336
|
+
// ── Convenience: match a URI tail under a literal prefix ────────────
|
|
337
|
+
/**
|
|
338
|
+
* True when `uri` starts with `prefix` and the tail matches `pattern`.
|
|
339
|
+
* `pattern === undefined` returns true (no filter). Replacement for the
|
|
340
|
+
* deleted `matchesUriPattern` helper from `read.ts`.
|
|
341
|
+
*
|
|
342
|
+
* The pattern is compiled fresh on every call. Callers that hot-loop
|
|
343
|
+
* over a large row set should hoist `compileSaveGlob(pattern)` out and
|
|
344
|
+
* test the RegExp directly.
|
|
345
|
+
*/
|
|
346
|
+
export function matchesGlob(uri, prefix, pattern) {
|
|
347
|
+
if (pattern === undefined)
|
|
348
|
+
return true;
|
|
349
|
+
if (!uri.startsWith(prefix))
|
|
350
|
+
return false;
|
|
351
|
+
return compileSaveGlob(pattern).test(uri.slice(prefix.length));
|
|
352
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Identifier-validation rule shared across backends.
|
|
3
|
+
*
|
|
4
|
+
* Entity names, table/collection prefixes, index prefixes, and field
|
|
5
|
+
* names all follow the same constraint: start with an ASCII letter,
|
|
6
|
+
* continue with letters / digits / underscores. The rule is the
|
|
7
|
+
* intersection of what every supported backend accepts as a safe
|
|
8
|
+
* identifier — SQL column names without quoting, Mongo collection
|
|
9
|
+
* names, ES index names, IndexedDB key components, etc.
|
|
10
|
+
*
|
|
11
|
+
* Callers compose their own error messages with `IDENTIFIER_PATTERN`
|
|
12
|
+
* (or `IDENTIFIER_PATTERN.source`) so the message can name the
|
|
13
|
+
* specific kind of identifier being validated.
|
|
14
|
+
*/
|
|
15
|
+
export declare const IDENTIFIER_PATTERN: RegExp;
|
|
16
|
+
/** True if `name` is a safe identifier per the shared rule. */
|
|
17
|
+
export declare function isValidIdentifier(name: string): boolean;
|
|
18
|
+
//# sourceMappingURL=identifiers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"identifiers.d.ts","sourceRoot":"","sources":["../src/identifiers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,eAAO,MAAM,kBAAkB,QAA4B,CAAC;AAE5D,+DAA+D;AAC/D,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEvD"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Identifier-validation rule shared across backends.
|
|
3
|
+
*
|
|
4
|
+
* Entity names, table/collection prefixes, index prefixes, and field
|
|
5
|
+
* names all follow the same constraint: start with an ASCII letter,
|
|
6
|
+
* continue with letters / digits / underscores. The rule is the
|
|
7
|
+
* intersection of what every supported backend accepts as a safe
|
|
8
|
+
* identifier — SQL column names without quoting, Mongo collection
|
|
9
|
+
* names, ES index names, IndexedDB key components, etc.
|
|
10
|
+
*
|
|
11
|
+
* Callers compose their own error messages with `IDENTIFIER_PATTERN`
|
|
12
|
+
* (or `IDENTIFIER_PATTERN.source`) so the message can name the
|
|
13
|
+
* specific kind of identifier being validated.
|
|
14
|
+
*/
|
|
15
|
+
export const IDENTIFIER_PATTERN = /^[a-zA-Z][a-zA-Z0-9_]*$/;
|
|
16
|
+
/** True if `name` is a safe identifier per the shared rule. */
|
|
17
|
+
export function isValidIdentifier(name) {
|
|
18
|
+
return IDENTIFIER_PATTERN.test(name);
|
|
19
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TYPE_TAGS recognition for IndexedDB entity records.
|
|
3
|
+
*
|
|
4
|
+
* IndexedDB's structured clone preserves `Uint8Array`, `Date`, `BigInt`,
|
|
5
|
+
* and plain JSON natively — records are stored verbatim. This module
|
|
6
|
+
* exists to (a) report which fields the backend recognises in
|
|
7
|
+
* `EntitySupport`, (b) drive the strict-validation check (extras
|
|
8
|
+
* rejected) on write, (c) compute the canonical signature for
|
|
9
|
+
* collision detection in `entityStatus`/`provisionEntity`.
|
|
10
|
+
*/
|
|
11
|
+
import { type EntityField } from "../entity.js";
|
|
12
|
+
export interface FieldPlan {
|
|
13
|
+
name: string;
|
|
14
|
+
tag: string;
|
|
15
|
+
}
|
|
16
|
+
export interface FieldPlanResult {
|
|
17
|
+
fields: FieldPlan[];
|
|
18
|
+
unsupported: {
|
|
19
|
+
name: string;
|
|
20
|
+
reason: string;
|
|
21
|
+
}[];
|
|
22
|
+
}
|
|
23
|
+
export declare function planFields(fields: EntityField[]): FieldPlanResult;
|
|
24
|
+
/**
|
|
25
|
+
* Canonical signature over `{name, [field, tag] ...}` used by the
|
|
26
|
+
* provisioning bookkeeping to detect same-name different-shape
|
|
27
|
+
* collisions. A field whose canonical tag flips counts as a collision
|
|
28
|
+
* even though the field name is unchanged — matches the granularity
|
|
29
|
+
* SQL backends get for free via column-type introspection.
|
|
30
|
+
*/
|
|
31
|
+
export declare function computeSignature(name: string, fields: readonly FieldPlan[]): string;
|
|
32
|
+
//# sourceMappingURL=fields.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fields.d.ts","sourceRoot":"","sources":["../../src/indexeddb/fields.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,KAAK,WAAW,EAAa,MAAM,cAAc,CAAC;AAK3D,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,SAAS,EAAE,CAAC;IACpB,WAAW,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CACjD;AAED,wBAAgB,UAAU,CAAC,MAAM,EAAE,WAAW,EAAE,GAAG,eAAe,CA0BjE;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,SAAS,SAAS,EAAE,GAC3B,MAAM,CAKR"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TYPE_TAGS recognition for IndexedDB entity records.
|
|
3
|
+
*
|
|
4
|
+
* IndexedDB's structured clone preserves `Uint8Array`, `Date`, `BigInt`,
|
|
5
|
+
* and plain JSON natively — records are stored verbatim. This module
|
|
6
|
+
* exists to (a) report which fields the backend recognises in
|
|
7
|
+
* `EntitySupport`, (b) drive the strict-validation check (extras
|
|
8
|
+
* rejected) on write, (c) compute the canonical signature for
|
|
9
|
+
* collision detection in `entityStatus`/`provisionEntity`.
|
|
10
|
+
*/
|
|
11
|
+
import { TYPE_TAGS } from "../entity.js";
|
|
12
|
+
import { IDENTIFIER_PATTERN, isValidIdentifier } from "../identifiers.js";
|
|
13
|
+
const KNOWN = new Set(Object.values(TYPE_TAGS));
|
|
14
|
+
export function planFields(fields) {
|
|
15
|
+
const out = [];
|
|
16
|
+
const unsupported = [];
|
|
17
|
+
for (const f of fields) {
|
|
18
|
+
if (!isValidIdentifier(f.name)) {
|
|
19
|
+
unsupported.push({
|
|
20
|
+
name: f.name,
|
|
21
|
+
reason: `field name must match ${IDENTIFIER_PATTERN.source}; got '${f.name}'`,
|
|
22
|
+
});
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
const tag = f.type.find((t) => KNOWN.has(t));
|
|
26
|
+
if (!tag) {
|
|
27
|
+
unsupported.push({
|
|
28
|
+
name: f.name,
|
|
29
|
+
reason: f.type.length === 0
|
|
30
|
+
? "field declares no type tags"
|
|
31
|
+
: `no recognised tag in [${f.type.join(", ")}]`,
|
|
32
|
+
});
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
out.push({ name: f.name, tag });
|
|
36
|
+
}
|
|
37
|
+
return { fields: out, unsupported };
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Canonical signature over `{name, [field, tag] ...}` used by the
|
|
41
|
+
* provisioning bookkeeping to detect same-name different-shape
|
|
42
|
+
* collisions. A field whose canonical tag flips counts as a collision
|
|
43
|
+
* even though the field name is unchanged — matches the granularity
|
|
44
|
+
* SQL backends get for free via column-type introspection.
|
|
45
|
+
*/
|
|
46
|
+
export function computeSignature(name, fields) {
|
|
47
|
+
const sorted = [...fields]
|
|
48
|
+
.map((f) => [f.name, f.tag])
|
|
49
|
+
.sort(([a], [b]) => a.localeCompare(b));
|
|
50
|
+
return JSON.stringify({ name, fields: sorted });
|
|
51
|
+
}
|