@happyvertical/smrt-web 0.42.4 → 0.42.6
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/AGENTS.md +7 -0
- package/dist/index.d.ts +139 -0
- package/dist/index.js +240 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/AGENTS.md
CHANGED
|
@@ -39,6 +39,7 @@ module you are editing. This file keeps what holds in every module.
|
|
|
39
39
|
| `offline/` | durable offline writes — config, sync-apply-only replay, idempotency, the shared namespace-keyed engine, and Web Locks leader election | [agents/offline-outbox.md](agents/offline-outbox.md) |
|
|
40
40
|
| `sse-client.ts` | the client half of live cache invalidation — the app-wide subscriber, the wire contract it consumes, and SSE-vs-polling behaviour | [agents/live-invalidation.md](agents/live-invalidation.md) |
|
|
41
41
|
| `persistence/` + `update-state.ts` | the read-cache rehydrate capability and the framework-free `updateAvailable` primitive (bundle + contract signals) | [agents/version-persistence.md](agents/version-persistence.md) |
|
|
42
|
+
| `data-query.ts` | dependency-free browser mirror and defensive response normalizer for the canonical bounded data-query envelope (#2444) | — |
|
|
42
43
|
|
|
43
44
|
## The engine-absorption boundary (ratified conditions, #1761)
|
|
44
45
|
|
|
@@ -60,6 +61,12 @@ module you are editing. This file keeps what holds in every module.
|
|
|
60
61
|
|
|
61
62
|
- **No inter-smrt dependencies** — depends only on TanStack packages
|
|
62
63
|
(dependency-DAG guardrails). Definitions and fetchers arrive as arguments.
|
|
64
|
+
- **Data-query mirror** — `data-query.ts` mirrors the portable
|
|
65
|
+
`smrt-types` request/result shape structurally because this package cannot
|
|
66
|
+
depend on another SMRT package. Server adapters own authorization and full
|
|
67
|
+
query/result policy; browser code calls `executeSmrtWebDataQuery()` to reject
|
|
68
|
+
malformed or over-limit returned envelopes — including a response for a
|
|
69
|
+
different request id — before they reach a UI surface.
|
|
63
70
|
- Rows are plain DTOs with a required `id`; optimistic inserts use
|
|
64
71
|
`newLocalId()` — the generated REST layer strips client ids on create
|
|
65
72
|
(#1540), so the post-persist refetch reconciles server-assigned ids.
|
package/dist/index.d.ts
CHANGED
|
@@ -215,6 +215,11 @@ export declare interface DurableStoreKey {
|
|
|
215
215
|
*/
|
|
216
216
|
export declare function durableStoreNamespace(key: DurableStoreKey): string;
|
|
217
217
|
|
|
218
|
+
/** Run a browser transport and return the defensive normalized envelope. */
|
|
219
|
+
export declare function executeSmrtWebDataQuery(transport: SmrtWebDataQueryTransport, request: SmrtWebDataQueryRequest, options?: {
|
|
220
|
+
signal?: AbortSignal;
|
|
221
|
+
}): Promise<SmrtWebDataQueryResult>;
|
|
222
|
+
|
|
218
223
|
/**
|
|
219
224
|
* Retrieve the underlying engine collection backing a handle — an advanced
|
|
220
225
|
* bridge for trusted framework bindings (e.g. the smrt-svelte live-query
|
|
@@ -260,6 +265,25 @@ export declare interface LiveInvalidationConfig {
|
|
|
260
265
|
tableName: string;
|
|
261
266
|
}
|
|
262
267
|
|
|
268
|
+
export declare const MAX_SMRT_WEB_DATA_QUERY_CONTAINER_ITEMS = 1000;
|
|
269
|
+
|
|
270
|
+
export declare const MAX_SMRT_WEB_DATA_QUERY_FACET_VALUES = 1000;
|
|
271
|
+
|
|
272
|
+
export declare const MAX_SMRT_WEB_DATA_QUERY_FACETS = 20;
|
|
273
|
+
|
|
274
|
+
export declare const MAX_SMRT_WEB_DATA_QUERY_OFFSET = 1000000;
|
|
275
|
+
|
|
276
|
+
export declare const MAX_SMRT_WEB_DATA_QUERY_PAGE_LIMIT = 1000;
|
|
277
|
+
|
|
278
|
+
/** Mirrors core's hard output limits without adding an inter-SMRT dependency. */
|
|
279
|
+
export declare const MAX_SMRT_WEB_DATA_QUERY_RESULT_BYTES = 10000000;
|
|
280
|
+
|
|
281
|
+
export declare const MAX_SMRT_WEB_DATA_QUERY_ROWS = 1000;
|
|
282
|
+
|
|
283
|
+
export declare const MAX_SMRT_WEB_DATA_QUERY_STRING_LENGTH = 65536;
|
|
284
|
+
|
|
285
|
+
export declare const MAX_SMRT_WEB_DATA_QUERY_WARNINGS = 100;
|
|
286
|
+
|
|
263
287
|
/** The settle outcome handed to {@link SmrtWebCapability.onSettled}. */
|
|
264
288
|
export declare type MutationSettleOutcome = {
|
|
265
289
|
ok: true;
|
|
@@ -277,6 +301,13 @@ export declare type MutationSettleOutcome = {
|
|
|
277
301
|
*/
|
|
278
302
|
export declare function newLocalId(): string;
|
|
279
303
|
|
|
304
|
+
/**
|
|
305
|
+
* Check the server-produced, normalized result envelope before a browser
|
|
306
|
+
* surface consumes it. This validates portable shape only; the server's core
|
|
307
|
+
* normalizer remains the authorization, projection, and query-policy boundary.
|
|
308
|
+
*/
|
|
309
|
+
export declare function normalizeSmrtWebDataQueryResult(value: unknown): SmrtWebDataQueryResult;
|
|
310
|
+
|
|
280
311
|
/**
|
|
281
312
|
* Build a durable offline-outbox capability for a collection. Add the returned
|
|
282
313
|
* capability to the collection's `capabilities` array; a collection without it
|
|
@@ -735,6 +766,114 @@ export declare interface SmrtWebCollectionDefinition<TData extends object = obje
|
|
|
735
766
|
_row?: TData;
|
|
736
767
|
}
|
|
737
768
|
|
|
769
|
+
export declare interface SmrtWebDataQueryFacetResult {
|
|
770
|
+
field: string;
|
|
771
|
+
values: Array<{
|
|
772
|
+
value: SmrtWebDataQueryScalar;
|
|
773
|
+
count: number;
|
|
774
|
+
}>;
|
|
775
|
+
truncated: boolean;
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
export declare type SmrtWebDataQueryFilter = {
|
|
779
|
+
kind: 'condition';
|
|
780
|
+
field: string;
|
|
781
|
+
operator: SmrtWebDataQueryFilterOperator;
|
|
782
|
+
value: SmrtWebDataQueryScalar | SmrtWebDataQueryScalar[];
|
|
783
|
+
} | {
|
|
784
|
+
kind: 'all' | 'any';
|
|
785
|
+
filters: SmrtWebDataQueryFilter[];
|
|
786
|
+
} | {
|
|
787
|
+
kind: 'not';
|
|
788
|
+
filter: SmrtWebDataQueryFilter;
|
|
789
|
+
};
|
|
790
|
+
|
|
791
|
+
export declare type SmrtWebDataQueryFilterOperator = 'eq' | 'ne' | 'gt' | 'gte' | 'lt' | 'lte' | 'in' | 'notIn' | 'like';
|
|
792
|
+
|
|
793
|
+
export declare interface SmrtWebDataQueryRequest {
|
|
794
|
+
version: 1;
|
|
795
|
+
requestId: string;
|
|
796
|
+
mode: 'rows' | 'count' | 'facets';
|
|
797
|
+
projection?: string[];
|
|
798
|
+
filter?: SmrtWebDataQueryFilter;
|
|
799
|
+
sort?: Array<{
|
|
800
|
+
field: string;
|
|
801
|
+
direction: 'asc' | 'desc';
|
|
802
|
+
}>;
|
|
803
|
+
page?: {
|
|
804
|
+
kind: 'offset';
|
|
805
|
+
offset: number;
|
|
806
|
+
limit: number;
|
|
807
|
+
} | {
|
|
808
|
+
kind: 'cursor';
|
|
809
|
+
after?: string;
|
|
810
|
+
limit: number;
|
|
811
|
+
};
|
|
812
|
+
consistency?: {
|
|
813
|
+
mode: 'eventual' | 'snapshot';
|
|
814
|
+
asOf?: string;
|
|
815
|
+
};
|
|
816
|
+
facets?: Array<{
|
|
817
|
+
field: string;
|
|
818
|
+
limit: number;
|
|
819
|
+
}>;
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
export declare interface SmrtWebDataQueryResult {
|
|
823
|
+
version: 1;
|
|
824
|
+
requestId: string;
|
|
825
|
+
queryFingerprint: string;
|
|
826
|
+
identityField: string;
|
|
827
|
+
rows: Array<Record<string, unknown>>;
|
|
828
|
+
page?: {
|
|
829
|
+
kind: 'offset';
|
|
830
|
+
limit: number;
|
|
831
|
+
offset: number;
|
|
832
|
+
hasMore: boolean;
|
|
833
|
+
} | {
|
|
834
|
+
kind: 'cursor';
|
|
835
|
+
limit: number;
|
|
836
|
+
nextCursor?: string;
|
|
837
|
+
hasMore: boolean;
|
|
838
|
+
};
|
|
839
|
+
total: SmrtWebDataQueryTotal;
|
|
840
|
+
facets?: SmrtWebDataQueryFacetResult[];
|
|
841
|
+
freshness: {
|
|
842
|
+
state: 'fresh' | 'stale' | 'unknown';
|
|
843
|
+
asOf?: string;
|
|
844
|
+
};
|
|
845
|
+
warnings: string[];
|
|
846
|
+
truncated: boolean;
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
/**
|
|
850
|
+
* Browser transport for the canonical bounded data-query envelope (#2444).
|
|
851
|
+
*
|
|
852
|
+
* This is a textual structural mirror of `@happyvertical/smrt-types` rather
|
|
853
|
+
* than an import: smrt-web deliberately has no inter-SMRT dependencies. The
|
|
854
|
+
* authenticated server adapter owns allowlist, authorization, fingerprint,
|
|
855
|
+
* and byte-limit enforcement. This module gives browser callers a stable
|
|
856
|
+
* request/result shape and rejects malformed response envelopes before they
|
|
857
|
+
* reach a mounted surface.
|
|
858
|
+
*/
|
|
859
|
+
export declare type SmrtWebDataQueryScalar = string | number | boolean | null;
|
|
860
|
+
|
|
861
|
+
export declare type SmrtWebDataQueryTotal = {
|
|
862
|
+
kind: 'exact' | 'estimated';
|
|
863
|
+
value: number;
|
|
864
|
+
asOf?: string;
|
|
865
|
+
} | {
|
|
866
|
+
kind: 'unavailable';
|
|
867
|
+
reason?: string;
|
|
868
|
+
};
|
|
869
|
+
|
|
870
|
+
/** Browser transport seam; REST, WebMCP, and test transports share it. */
|
|
871
|
+
export declare interface SmrtWebDataQueryTransport {
|
|
872
|
+
query(request: SmrtWebDataQueryRequest, options?: {
|
|
873
|
+
signal?: AbortSignal;
|
|
874
|
+
}): Promise<unknown>;
|
|
875
|
+
}
|
|
876
|
+
|
|
738
877
|
/**
|
|
739
878
|
* The minimal `EventSource` surface the subscriber uses — declared here so a
|
|
740
879
|
* test injects a fake without a real DOM, and so this module compiles without
|
package/dist/index.js
CHANGED
|
@@ -14,6 +14,245 @@ async function runWrapMutation(capabilities, envelope, ctx) {
|
|
|
14
14
|
return { handled: false };
|
|
15
15
|
}
|
|
16
16
|
//#endregion
|
|
17
|
+
//#region src/data-query.ts
|
|
18
|
+
var FORBIDDEN_KEYS = /* @__PURE__ */ new Set([
|
|
19
|
+
"__proto__",
|
|
20
|
+
"constructor",
|
|
21
|
+
"prototype"
|
|
22
|
+
]);
|
|
23
|
+
var MAX_SMRT_WEB_DATA_QUERY_RESULT_BYTES = 1e7;
|
|
24
|
+
var MAX_SMRT_WEB_DATA_QUERY_ROWS = 1e3;
|
|
25
|
+
var MAX_SMRT_WEB_DATA_QUERY_FACETS = 20;
|
|
26
|
+
var MAX_SMRT_WEB_DATA_QUERY_FACET_VALUES = 1e3;
|
|
27
|
+
var MAX_SMRT_WEB_DATA_QUERY_WARNINGS = 100;
|
|
28
|
+
var MAX_SMRT_WEB_DATA_QUERY_PAGE_LIMIT = 1e3;
|
|
29
|
+
var MAX_SMRT_WEB_DATA_QUERY_OFFSET = 1e6;
|
|
30
|
+
var MAX_SMRT_WEB_DATA_QUERY_CONTAINER_ITEMS = 1e3;
|
|
31
|
+
var MAX_SMRT_WEB_DATA_QUERY_STRING_LENGTH = 65536;
|
|
32
|
+
function consumeBytes(budget, text, label) {
|
|
33
|
+
const bytes = new TextEncoder().encode(text).byteLength;
|
|
34
|
+
if (bytes > budget.remaining) throw new TypeError(`${label} exceeds the maximum byte limit`);
|
|
35
|
+
budget.remaining -= bytes;
|
|
36
|
+
}
|
|
37
|
+
function plainObject(value, label) {
|
|
38
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw new TypeError(`${label} must be a plain object`);
|
|
39
|
+
const prototype = Object.getPrototypeOf(value);
|
|
40
|
+
if (prototype !== Object.prototype && prototype !== null) throw new TypeError(`${label} must be a plain object`);
|
|
41
|
+
for (const key of Object.keys(value)) if (FORBIDDEN_KEYS.has(key)) throw new TypeError(`${label} contains a forbidden key`);
|
|
42
|
+
return value;
|
|
43
|
+
}
|
|
44
|
+
function exactKeys(value, allowed, label) {
|
|
45
|
+
const keys = new Set(allowed);
|
|
46
|
+
for (const key of Object.keys(value)) if (!keys.has(key)) throw new TypeError(`${label} contains ${key}`);
|
|
47
|
+
}
|
|
48
|
+
function stringValue(value, label, maxLength = 2048) {
|
|
49
|
+
if (typeof value !== "string" || value.length === 0 || value.length > maxLength) throw new TypeError(`${label} must be a bounded non-empty string`);
|
|
50
|
+
return value;
|
|
51
|
+
}
|
|
52
|
+
function nonNegativeInteger(value, label) {
|
|
53
|
+
if (!Number.isSafeInteger(value) || value < 0) throw new TypeError(`${label} must be a non-negative safe integer`);
|
|
54
|
+
return value;
|
|
55
|
+
}
|
|
56
|
+
function scalar(value, label, budget) {
|
|
57
|
+
if (typeof value === "string") {
|
|
58
|
+
if (value.length > 65536) throw new TypeError(`${label} exceeds the string limit`);
|
|
59
|
+
consumeBytes(budget, JSON.stringify(value), label);
|
|
60
|
+
return value;
|
|
61
|
+
}
|
|
62
|
+
if (value === null || typeof value === "boolean" || typeof value === "number" && Number.isFinite(value)) {
|
|
63
|
+
consumeBytes(budget, JSON.stringify(value), label);
|
|
64
|
+
return value;
|
|
65
|
+
}
|
|
66
|
+
throw new TypeError(`${label} must be a JSON scalar`);
|
|
67
|
+
}
|
|
68
|
+
function jsonValue(value, label, budget, depth = 0) {
|
|
69
|
+
if (depth > 16) throw new TypeError(`${label} exceeds JSON depth`);
|
|
70
|
+
if (typeof value === "string") {
|
|
71
|
+
if (value.length > 65536) throw new TypeError(`${label} exceeds the string limit`);
|
|
72
|
+
consumeBytes(budget, JSON.stringify(value), label);
|
|
73
|
+
return value;
|
|
74
|
+
}
|
|
75
|
+
if (value === null || typeof value === "boolean" || typeof value === "number" && Number.isFinite(value)) {
|
|
76
|
+
consumeBytes(budget, JSON.stringify(value), label);
|
|
77
|
+
return value;
|
|
78
|
+
}
|
|
79
|
+
if (Array.isArray(value)) {
|
|
80
|
+
if (value.length > 1e3) throw new TypeError(`${label} exceeds the container-item limit`);
|
|
81
|
+
consumeBytes(budget, "[", label);
|
|
82
|
+
const result2 = [];
|
|
83
|
+
for (const [index, item] of value.entries()) {
|
|
84
|
+
if (index > 0) consumeBytes(budget, ",", label);
|
|
85
|
+
result2.push(jsonValue(item, `${label}[${index}]`, budget, depth + 1));
|
|
86
|
+
}
|
|
87
|
+
consumeBytes(budget, "]", label);
|
|
88
|
+
return result2;
|
|
89
|
+
}
|
|
90
|
+
const object = plainObject(value, label);
|
|
91
|
+
if (Object.keys(object).length > 1e3) throw new TypeError(`${label} exceeds the container-item limit`);
|
|
92
|
+
const result = /* @__PURE__ */ Object.create(null);
|
|
93
|
+
consumeBytes(budget, "{", label);
|
|
94
|
+
for (const [index, key] of Object.keys(object).sort().entries()) {
|
|
95
|
+
if (key.length > 65536) throw new TypeError(`${label}.${key} exceeds the string limit`);
|
|
96
|
+
if (index > 0) consumeBytes(budget, ",", label);
|
|
97
|
+
consumeBytes(budget, JSON.stringify(key), `${label}.${key}`);
|
|
98
|
+
consumeBytes(budget, ":", label);
|
|
99
|
+
result[key] = jsonValue(object[key], `${label}.${key}`, budget, depth + 1);
|
|
100
|
+
}
|
|
101
|
+
consumeBytes(budget, "}", label);
|
|
102
|
+
return result;
|
|
103
|
+
}
|
|
104
|
+
function normalizeTotal(value) {
|
|
105
|
+
const total = plainObject(value, "Data query total");
|
|
106
|
+
const kind = stringValue(total.kind, "Data query total kind");
|
|
107
|
+
if (kind === "unavailable") {
|
|
108
|
+
exactKeys(total, ["kind", "reason"], "Data query unavailable total");
|
|
109
|
+
return {
|
|
110
|
+
kind,
|
|
111
|
+
...total.reason === void 0 ? {} : { reason: stringValue(total.reason, "Data query total reason") }
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
if (kind !== "exact" && kind !== "estimated") throw new TypeError("Data query total kind is invalid");
|
|
115
|
+
exactKeys(total, [
|
|
116
|
+
"kind",
|
|
117
|
+
"value",
|
|
118
|
+
"asOf"
|
|
119
|
+
], "Data query total");
|
|
120
|
+
return {
|
|
121
|
+
kind,
|
|
122
|
+
value: nonNegativeInteger(total.value, "Data query total value"),
|
|
123
|
+
...total.asOf === void 0 ? {} : { asOf: stringValue(total.asOf, "Data query total asOf", 128) }
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
function normalizeFacets(value, budget) {
|
|
127
|
+
if (value === void 0) return void 0;
|
|
128
|
+
if (!Array.isArray(value)) throw new TypeError("Data query facets must be an array");
|
|
129
|
+
if (value.length > 20) throw new TypeError("Data query facets exceed the maximum");
|
|
130
|
+
const fields = /* @__PURE__ */ new Set();
|
|
131
|
+
return value.map((candidate, index) => {
|
|
132
|
+
const facet = plainObject(candidate, `Data query facet ${index}`);
|
|
133
|
+
exactKeys(facet, [
|
|
134
|
+
"field",
|
|
135
|
+
"values",
|
|
136
|
+
"truncated"
|
|
137
|
+
], "Data query facet");
|
|
138
|
+
const field = stringValue(facet.field, "Data query facet field");
|
|
139
|
+
if (fields.has(field)) throw new TypeError("Data query facet fields must be unique");
|
|
140
|
+
fields.add(field);
|
|
141
|
+
if (!Array.isArray(facet.values)) throw new TypeError("Data query facet values must be an array");
|
|
142
|
+
if (facet.values.length > 1e3) throw new TypeError("Data query facet values exceed the maximum");
|
|
143
|
+
if (typeof facet.truncated !== "boolean") throw new TypeError("Data query facet truncated must be boolean");
|
|
144
|
+
return {
|
|
145
|
+
field,
|
|
146
|
+
values: facet.values.map((value2, valueIndex) => {
|
|
147
|
+
const entry = plainObject(value2, `Data query facet ${index} value ${valueIndex}`);
|
|
148
|
+
exactKeys(entry, ["value", "count"], "Data query facet value");
|
|
149
|
+
return {
|
|
150
|
+
value: scalar(entry.value, "Data query facet value", budget),
|
|
151
|
+
count: nonNegativeInteger(entry.count, "Data query facet count")
|
|
152
|
+
};
|
|
153
|
+
}),
|
|
154
|
+
truncated: facet.truncated
|
|
155
|
+
};
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
function normalizeSmrtWebDataQueryResult(value) {
|
|
159
|
+
const result = plainObject(value, "Data query result");
|
|
160
|
+
exactKeys(result, [
|
|
161
|
+
"version",
|
|
162
|
+
"requestId",
|
|
163
|
+
"queryFingerprint",
|
|
164
|
+
"identityField",
|
|
165
|
+
"rows",
|
|
166
|
+
"page",
|
|
167
|
+
"total",
|
|
168
|
+
"facets",
|
|
169
|
+
"freshness",
|
|
170
|
+
"warnings",
|
|
171
|
+
"truncated"
|
|
172
|
+
], "Data query result");
|
|
173
|
+
if (result.version !== 1) throw new TypeError("Unsupported data query version");
|
|
174
|
+
const identityField = stringValue(result.identityField, "Data query identity field");
|
|
175
|
+
if (!Array.isArray(result.rows)) throw new TypeError("Data query rows must be an array");
|
|
176
|
+
if (result.rows.length > 1e3) throw new TypeError("Data query rows exceed the maximum");
|
|
177
|
+
const budget = { remaining: MAX_SMRT_WEB_DATA_QUERY_RESULT_BYTES };
|
|
178
|
+
const rows = result.rows.map((row, index) => {
|
|
179
|
+
const object = plainObject(jsonValue(row, `Data query row ${index}`, budget), `Data query row ${index}`);
|
|
180
|
+
const identity = object[identityField];
|
|
181
|
+
if (typeof identity !== "string" && typeof identity !== "number" || identity === "") throw new TypeError("Data query row is missing its stable identity");
|
|
182
|
+
return object;
|
|
183
|
+
});
|
|
184
|
+
let page;
|
|
185
|
+
if (result.page !== void 0) {
|
|
186
|
+
const candidate = plainObject(result.page, "Data query page");
|
|
187
|
+
exactKeys(candidate, [
|
|
188
|
+
"kind",
|
|
189
|
+
"limit",
|
|
190
|
+
"offset",
|
|
191
|
+
"nextCursor",
|
|
192
|
+
"hasMore"
|
|
193
|
+
], "Data query page");
|
|
194
|
+
const kind = stringValue(candidate.kind, "Data query page kind");
|
|
195
|
+
if (kind !== "offset" && kind !== "cursor") throw new TypeError("Data query page kind is invalid");
|
|
196
|
+
if (typeof candidate.hasMore !== "boolean") throw new TypeError("Data query page hasMore must be boolean");
|
|
197
|
+
const limit = nonNegativeInteger(candidate.limit, "Data query page limit");
|
|
198
|
+
if (limit === 0) throw new TypeError("Data query page limit must be positive");
|
|
199
|
+
if (limit > 1e3) throw new TypeError("Data query page limit exceeds the maximum");
|
|
200
|
+
if (kind === "offset") {
|
|
201
|
+
if (candidate.nextCursor !== void 0 || candidate.offset === void 0) throw new TypeError("Offset data query page must carry only an offset");
|
|
202
|
+
const offset = nonNegativeInteger(candidate.offset, "Data query offset");
|
|
203
|
+
if (offset > 1e6) throw new TypeError("Data query offset exceeds the maximum");
|
|
204
|
+
page = {
|
|
205
|
+
kind,
|
|
206
|
+
limit,
|
|
207
|
+
offset,
|
|
208
|
+
hasMore: candidate.hasMore
|
|
209
|
+
};
|
|
210
|
+
} else {
|
|
211
|
+
if (candidate.offset !== void 0) throw new TypeError("Cursor data query page cannot carry an offset");
|
|
212
|
+
const nextCursor = candidate.nextCursor === void 0 ? void 0 : stringValue(candidate.nextCursor, "Data query next cursor");
|
|
213
|
+
if (candidate.hasMore !== Boolean(nextCursor)) throw new TypeError("Cursor data query page hasMore must match nextCursor");
|
|
214
|
+
page = {
|
|
215
|
+
kind,
|
|
216
|
+
limit,
|
|
217
|
+
hasMore: candidate.hasMore,
|
|
218
|
+
...nextCursor === void 0 ? {} : { nextCursor }
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
if (page && rows.length > page.limit) throw new TypeError("Data query rows exceed the declared page limit");
|
|
223
|
+
const freshness = plainObject(result.freshness, "Data query freshness");
|
|
224
|
+
exactKeys(freshness, ["state", "asOf"], "Data query freshness");
|
|
225
|
+
const state = stringValue(freshness.state, "Data query freshness state");
|
|
226
|
+
if (state !== "fresh" && state !== "stale" && state !== "unknown") throw new TypeError("Data query freshness state is invalid");
|
|
227
|
+
if (!Array.isArray(result.warnings) || result.warnings.length > 100 || result.warnings.some((item) => typeof item !== "string")) throw new TypeError("Data query warnings must be strings");
|
|
228
|
+
if (typeof result.truncated !== "boolean") throw new TypeError("Data query truncated must be boolean");
|
|
229
|
+
const facets = normalizeFacets(result.facets, budget);
|
|
230
|
+
const warnings = result.warnings.map((warning) => stringValue(warning, "Data query warning", 512));
|
|
231
|
+
const normalized = {
|
|
232
|
+
version: 1,
|
|
233
|
+
requestId: stringValue(result.requestId, "Data query request id"),
|
|
234
|
+
queryFingerprint: stringValue(result.queryFingerprint, "Data query fingerprint"),
|
|
235
|
+
identityField,
|
|
236
|
+
rows,
|
|
237
|
+
...page === void 0 ? {} : { page },
|
|
238
|
+
total: normalizeTotal(result.total),
|
|
239
|
+
...facets === void 0 ? {} : { facets },
|
|
240
|
+
freshness: {
|
|
241
|
+
state,
|
|
242
|
+
...freshness.asOf === void 0 ? {} : { asOf: stringValue(freshness.asOf, "Data query freshness asOf", 128) }
|
|
243
|
+
},
|
|
244
|
+
warnings: [...new Set(warnings)].sort(),
|
|
245
|
+
truncated: result.truncated
|
|
246
|
+
};
|
|
247
|
+
if (new TextEncoder().encode(JSON.stringify(normalized)).byteLength > 1e7) throw new TypeError("Data query result exceeds the maximum byte limit");
|
|
248
|
+
return normalized;
|
|
249
|
+
}
|
|
250
|
+
async function executeSmrtWebDataQuery(transport, request, options) {
|
|
251
|
+
const result = normalizeSmrtWebDataQueryResult(await transport.query(request, options));
|
|
252
|
+
if (result.requestId !== request.requestId) throw new TypeError("Data query result request id does not match its request");
|
|
253
|
+
return result;
|
|
254
|
+
}
|
|
255
|
+
//#endregion
|
|
17
256
|
//#region src/durable-store.ts
|
|
18
257
|
function durableStoreNamespace(key) {
|
|
19
258
|
const optional = (value) => value === void 0 ? "" : `_${encodeURIComponent(value)}`;
|
|
@@ -1923,6 +2162,6 @@ function createSmrtCollection(definition, options) {
|
|
|
1923
2162
|
return handle;
|
|
1924
2163
|
}
|
|
1925
2164
|
//#endregion
|
|
1926
|
-
export { DEFAULT_PERSIST_DEBOUNCE_MS, SmrtWebRequestError, buildListQuery, createDefinitionFetchers, createSmrtCollection, createSmrtWebClient, createSmrtWebEventSubscriber, createUpdateState, durableStoreNamespace, getEngineCollection, getOutboxHandle, liveInvalidation, newLocalId, offlineOutbox, persistCollection, registerDurableResource, registerWebMcpTools, runWrapMutation, unwrapItemResult, unwrapListResult, wipeDurableStore };
|
|
2165
|
+
export { DEFAULT_PERSIST_DEBOUNCE_MS, MAX_SMRT_WEB_DATA_QUERY_CONTAINER_ITEMS, MAX_SMRT_WEB_DATA_QUERY_FACETS, MAX_SMRT_WEB_DATA_QUERY_FACET_VALUES, MAX_SMRT_WEB_DATA_QUERY_OFFSET, MAX_SMRT_WEB_DATA_QUERY_PAGE_LIMIT, MAX_SMRT_WEB_DATA_QUERY_RESULT_BYTES, MAX_SMRT_WEB_DATA_QUERY_ROWS, MAX_SMRT_WEB_DATA_QUERY_STRING_LENGTH, MAX_SMRT_WEB_DATA_QUERY_WARNINGS, SmrtWebRequestError, buildListQuery, createDefinitionFetchers, createSmrtCollection, createSmrtWebClient, createSmrtWebEventSubscriber, createUpdateState, durableStoreNamespace, executeSmrtWebDataQuery, getEngineCollection, getOutboxHandle, liveInvalidation, newLocalId, normalizeSmrtWebDataQueryResult, offlineOutbox, persistCollection, registerDurableResource, registerWebMcpTools, runWrapMutation, unwrapItemResult, unwrapListResult, wipeDurableStore };
|
|
1927
2166
|
|
|
1928
2167
|
//# sourceMappingURL=index.js.map
|