@ultimat3/query 1.2.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +404 -0
- package/README.md +179 -10
- package/package.json +7 -5
- package/src/cache.ts +180 -71
- package/src/client.ts +83 -2
- package/src/cursor-value.ts +65 -0
- package/src/deprecation.ts +81 -0
- package/src/errors.ts +97 -1
- package/src/facade.ts +2 -0
- package/src/http.ts +109 -0
- package/src/index.ts +40 -9
- package/src/input-shape.ts +74 -0
- package/src/live.ts +27 -3
- package/src/matcher.ts +56 -13
- package/src/mcp-tool.ts +11 -5
- package/src/naming.ts +5 -9
- package/src/pagination.ts +25 -3
- package/src/policy-gate.ts +13 -2
- package/src/query.ts +108 -8
- package/src/read.ts +116 -16
- package/src/shape.ts +103 -6
- package/src/source.ts +137 -49
- package/src/sql.ts +2 -2
- package/src/stable.ts +23 -12
- package/src/tags.ts +0 -17
package/src/stable.ts
CHANGED
|
@@ -13,8 +13,15 @@ export function stableStringify(value: unknown): string {
|
|
|
13
13
|
switch (typeof value) {
|
|
14
14
|
case 'string':
|
|
15
15
|
return JSON.stringify(value);
|
|
16
|
+
// A bare token, never `'null'` and never a quoted string: this output is only ever hashed, so
|
|
17
|
+
// an unquoted word cannot collide with the `string` branch (which always quotes) while
|
|
18
|
+
// `'null'` collided with JSON `null` itself — `{ n: NaN }`, `{ n: Infinity }` and `{ n: null }`
|
|
19
|
+
// fingerprinted identically and shared one cache entry and one cursor scope. `-0` is spelled
|
|
20
|
+
// out for the same reason: `String(-0)` is `"0"`, so `-0` and `0` were one key too.
|
|
16
21
|
case 'number':
|
|
17
|
-
|
|
22
|
+
if (Number.isNaN(value)) return 'NaN';
|
|
23
|
+
if (!Number.isFinite(value)) return value > 0 ? 'Infinity' : '-Infinity';
|
|
24
|
+
return Object.is(value, -0) ? '-0' : String(value);
|
|
18
25
|
case 'boolean':
|
|
19
26
|
return String(value);
|
|
20
27
|
case 'bigint':
|
|
@@ -35,18 +42,22 @@ export function stableStringify(value: unknown): string {
|
|
|
35
42
|
return `{${entries.join(',')}}`;
|
|
36
43
|
}
|
|
37
44
|
|
|
38
|
-
/**
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
45
|
+
/**
|
|
46
|
+
* SHA-256, first 16 hex characters — the same primitive and the same width `@ultimat3/realtime`'s
|
|
47
|
+
* `stableDigest` and `@ultimat3/entity`'s `planScope` already chose, and for the same reason.
|
|
48
|
+
*
|
|
49
|
+
* A fingerprint is a SHARING key, not a checksum: it decides which read-cache entry two callers
|
|
50
|
+
* are served from and which scope a cursor is bound to, over input a client chooses. FNV-1a/32 —
|
|
51
|
+
* what this was — is 4x10^9 values, brute-forceable offline in seconds, so an attacker could mint
|
|
52
|
+
* an input that lands on another read's entry or another page's scope. It identifies, and here
|
|
53
|
+
* identifying IS the boundary.
|
|
54
|
+
*
|
|
55
|
+
* The canonical form above is unchanged, so the only thing that moved is the hash: a cursor issued
|
|
56
|
+
* before this fails its scope check as `X_CURSOR_INVALID` — cleanly, with "request the first page
|
|
57
|
+
* again" as its fix — and a warm read cache is cold once.
|
|
58
|
+
*/
|
|
48
59
|
export function fingerprint(value: unknown): string {
|
|
49
|
-
return
|
|
60
|
+
return new Bun.CryptoHasher('sha256').update(stableStringify(value)).digest('hex').slice(0, 16);
|
|
50
61
|
}
|
|
51
62
|
|
|
52
63
|
/** Column read that works for interfaces without an index signature. */
|
package/src/tags.ts
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Cache tags stay opaque here — they belong to @ultimat3/cache. This package only
|
|
3
|
-
* needs the wire string per tag so a read's key can be found by the invalidation
|
|
4
|
-
* graph an action's `invalidates` drives.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import type { CacheTag } from '@ultimat3/cache';
|
|
8
|
-
import { serializeTag } from '@ultimat3/cache';
|
|
9
|
-
|
|
10
|
-
export function tagKey(value: CacheTag): string {
|
|
11
|
-
return serializeTag(value);
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
/** Sorted + de-duplicated: descriptor output must not depend on declaration order. */
|
|
15
|
-
export function tagKeys(tags: readonly CacheTag[]): readonly string[] {
|
|
16
|
-
return [...new Set(tags.map(tagKey))].sort();
|
|
17
|
-
}
|