@arkiv-network/sdk 0.8.1-dev.1 → 0.9.0-next.2
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/README.md +180 -61
- package/dist/chains/index.d.ts +17 -17
- package/dist/changeOwnership-Cy9gfyjN.d.cts +1160 -0
- package/dist/changeOwnership-Cy9gfyjN.d.cts.map +1 -0
- package/dist/changeOwnership-gXf-lBqI.d.ts +1160 -0
- package/dist/changeOwnership-gXf-lBqI.d.ts.map +1 -0
- package/dist/entity/index.cjs +4 -10
- package/dist/entity/index.d.cts +3 -3
- package/dist/entity/index.d.ts +3 -3
- package/dist/entity/index.js +3 -4
- package/dist/entity-BM1unrQf.cjs +41 -0
- package/dist/{entity-DJ0Gml9c.cjs → entity-BgaFJSaV.cjs} +4 -4
- package/dist/entity-CFAI1KyG.js +36 -0
- package/dist/entity-CFAI1KyG.js.map +1 -0
- package/dist/{entity-CniBgBSd.js → entity-CGqx_AU0.js} +4 -4
- package/dist/entity-CGqx_AU0.js.map +1 -0
- package/dist/{payload-AfUfSctb.d.ts → expirationTime-C7PZK49K.d.ts} +8 -37
- package/dist/expirationTime-C7PZK49K.d.ts.map +1 -0
- package/dist/expirationTime-CkIGNAg8.cjs +140 -0
- package/dist/{payload-D167BlJR.d.cts → expirationTime-DOB8V4B8.d.cts} +8 -37
- package/dist/expirationTime-DOB8V4B8.d.cts.map +1 -0
- package/dist/{expiry-Cgdb_dbq.js → expirationTime-UKPuA-LM.js} +62 -27
- package/dist/expirationTime-UKPuA-LM.js.map +1 -0
- package/dist/expiry-B48I-yTA.d.cts +40 -0
- package/dist/expiry-B48I-yTA.d.cts.map +1 -0
- package/dist/expiry-S2PUL8Hg.d.ts +40 -0
- package/dist/expiry-S2PUL8Hg.d.ts.map +1 -0
- package/dist/index-C-mMF5WZ.d.cts +735 -0
- package/dist/index-C-mMF5WZ.d.cts.map +1 -0
- package/dist/{index-BkqSnGJH.d.ts → index-C4zZqJ9S.d.ts} +5 -27
- package/dist/index-C4zZqJ9S.d.ts.map +1 -0
- package/dist/{index-CLhsFyVH.d.cts → index-CcVWYB6v.d.cts} +5 -27
- package/dist/index-CcVWYB6v.d.cts.map +1 -0
- package/dist/index-DCx2XC6S.d.ts +489 -0
- package/dist/index-DCx2XC6S.d.ts.map +1 -0
- package/dist/index-DLb7BfIw.d.ts +735 -0
- package/dist/index-DLb7BfIw.d.ts.map +1 -0
- package/dist/index-DzriOUm8.d.cts +489 -0
- package/dist/index-DzriOUm8.d.cts.map +1 -0
- package/dist/index.cjs +162 -146
- package/dist/index.d.cts +180 -133
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +180 -133
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +93 -83
- package/dist/index.js.map +1 -1
- package/dist/params-BuIrJA9a.cjs +45 -0
- package/dist/params-DDJOnsNi.js +22 -0
- package/dist/params-DDJOnsNi.js.map +1 -0
- package/dist/query/index.cjs +9 -4
- package/dist/query/index.d.cts +6 -6
- package/dist/query/index.d.ts +6 -6
- package/dist/query/index.js +4 -5
- package/dist/query-D0KtuFh2.cjs +1625 -0
- package/dist/query-DpRDozmj.js +1417 -0
- package/dist/query-DpRDozmj.js.map +1 -0
- package/dist/schema/index.cjs +46 -0
- package/dist/schema/index.d.cts +2 -0
- package/dist/schema/index.d.ts +2 -0
- package/dist/schema/index.js +3 -0
- package/dist/{attr-BXTkpnoc.cjs → schema-B9AyXcgU.cjs} +513 -246
- package/dist/{attr-tmyREHtw.js → schema-DY4sqehl.js} +427 -244
- package/dist/schema-DY4sqehl.js.map +1 -0
- package/dist/types/index.cjs +1 -1
- package/dist/types/index.d.cts +5 -5
- package/dist/types/index.d.ts +5 -5
- package/dist/types/index.js +1 -1
- package/dist/utils/index.cjs +3 -5
- package/dist/utils/index.d.cts +3 -3
- package/dist/utils/index.d.ts +3 -3
- package/dist/utils/index.js +3 -3
- package/package.json +9 -12
- package/src/actions/public/getEntity.test.ts +69 -0
- package/src/actions/public/getEntity.ts +34 -20
- package/src/actions/public/predictEntityKeys.ts +8 -7
- package/src/actions/public/query.test.ts +58 -0
- package/src/actions/public/query.ts +41 -13
- package/src/actions/wallet/createEntity.ts +34 -63
- package/src/actions/wallet/executeBatch.test.ts +38 -0
- package/src/actions/wallet/executeBatch.ts +26 -9
- package/src/actions/wallet/extendEntity.ts +9 -8
- package/src/actions/wallet/patchEntity.ts +43 -52
- package/src/clients/createPublicClient.test.ts +10 -2
- package/src/clients/createWalletClient.ts +4 -1
- package/src/clients/decorators/arkivPublic.ts +123 -80
- package/src/clients/decorators/arkivWallet.ts +113 -65
- package/src/entity/create.test.ts +25 -21
- package/src/entity/expiry.ts +8 -6
- package/src/entity/flags.ts +1 -1
- package/src/entity/index.ts +2 -4
- package/src/entity/key.ts +3 -3
- package/src/entity/operations.ts +1 -1
- package/src/errors.ts +2 -11
- package/src/index.ts +18 -6
- package/src/query/builderBase.ts +166 -0
- package/src/query/countBuilder.test.ts +91 -0
- package/src/query/countBuilder.ts +67 -0
- package/src/query/engine.ts +9 -3
- package/src/query/errors.ts +24 -14
- package/src/query/expression.test.ts +129 -21
- package/src/query/expression.ts +323 -94
- package/src/query/index.ts +26 -4
- package/src/query/injection.test.ts +5 -11
- package/src/query/queryBuilder.test.ts +467 -58
- package/src/query/queryBuilder.ts +191 -98
- package/src/query/queryResult.ts +35 -9
- package/src/query/selection.test.ts +278 -24
- package/src/query/selection.ts +427 -86
- package/src/schema/attributes.test.ts +266 -0
- package/src/schema/attributes.ts +212 -0
- package/src/{attr → schema}/codec.test.ts +37 -5
- package/src/{attr → schema}/codec.ts +40 -11
- package/src/schema/column.ts +193 -0
- package/src/{attr → schema}/errors.ts +67 -10
- package/src/schema/index.ts +76 -0
- package/src/{attr → schema}/names.ts +4 -2
- package/src/schema/schema.test.ts +171 -0
- package/src/schema/schema.ts +250 -0
- package/src/{attr → schema}/types.ts +36 -30
- package/src/{attr → schema}/values.test.ts +62 -3
- package/src/{attr → schema}/values.ts +246 -65
- package/src/types/entity.test.ts +1 -44
- package/src/types/entity.ts +9 -55
- package/src/types/events.ts +1 -1
- package/src/types/index.ts +11 -4
- package/src/types/rpcSchema.ts +22 -16
- package/src/utils/arkivTransactions.test.ts +356 -116
- package/src/utils/arkivTransactions.ts +30 -38
- package/src/utils/entities.ts +10 -9
- package/src/utils/expirationTime.ts +5 -5
- package/src/utils/index.ts +0 -1
- package/src/utils/logger.test.ts +45 -0
- package/src/utils/logger.ts +5 -39
- package/src/utils/revert.test.ts +25 -0
- package/src/utils/revert.ts +28 -3
- package/dist/attr/index.cjs +0 -34
- package/dist/attr/index.d.cts +0 -2
- package/dist/attr/index.d.ts +0 -2
- package/dist/attr/index.js +0 -3
- package/dist/attr-tmyREHtw.js.map +0 -1
- package/dist/entity-B0t6sN7F.cjs +0 -71
- package/dist/entity-BIp_df9u.js +0 -67
- package/dist/entity-BIp_df9u.js.map +0 -1
- package/dist/entity-CniBgBSd.js.map +0 -1
- package/dist/expiry-BxOlvVWC.d.ts +0 -79
- package/dist/expiry-BxOlvVWC.d.ts.map +0 -1
- package/dist/expiry-CG9GhVXv.cjs +0 -135
- package/dist/expiry-Cgdb_dbq.js.map +0 -1
- package/dist/expiry-DRV29F1r.d.cts +0 -79
- package/dist/expiry-DRV29F1r.d.cts.map +0 -1
- package/dist/flags-DCD26uhe.cjs +0 -43
- package/dist/flags-DUT2gHEU.js +0 -33
- package/dist/flags-DUT2gHEU.js.map +0 -1
- package/dist/index-BkqSnGJH.d.ts.map +0 -1
- package/dist/index-C7fbakoI.d.cts +0 -278
- package/dist/index-C7fbakoI.d.cts.map +0 -1
- package/dist/index-CIcHPx2j.d.cts +0 -424
- package/dist/index-CIcHPx2j.d.cts.map +0 -1
- package/dist/index-CLhsFyVH.d.cts.map +0 -1
- package/dist/index-Cd2aaWme.d.ts +0 -424
- package/dist/index-Cd2aaWme.d.ts.map +0 -1
- package/dist/index-Cq6fa8AI.d.ts +0 -278
- package/dist/index-Cq6fa8AI.d.ts.map +0 -1
- package/dist/index-DaNeJUw8.d.ts +0 -955
- package/dist/index-DaNeJUw8.d.ts.map +0 -1
- package/dist/index-Dv16LO2s.d.cts +0 -955
- package/dist/index-Dv16LO2s.d.cts.map +0 -1
- package/dist/payload-AfUfSctb.d.ts.map +0 -1
- package/dist/payload-D167BlJR.d.cts.map +0 -1
- package/dist/payload-DE7wMztL.js +0 -92
- package/dist/payload-DE7wMztL.js.map +0 -1
- package/dist/payload-DqFweqiO.cjs +0 -108
- package/dist/query-9oojUdM7.cjs +0 -1128
- package/dist/query-Ber-lOJQ.js +0 -968
- package/dist/query-Ber-lOJQ.js.map +0 -1
- package/src/actions/public/getEntityCount.ts +0 -10
- package/src/attr/attributes.test.ts +0 -120
- package/src/attr/attributes.ts +0 -196
- package/src/attr/index.ts +0 -50
- package/src/types/mimeTypes.ts +0 -27
- package/src/utils/compression.test.ts +0 -32
- package/src/utils/compression.ts +0 -102
- package/src/utils/payload.ts +0 -36
- /package/src/{attr → schema}/decimal.ts +0 -0
|
@@ -0,0 +1,1625 @@
|
|
|
1
|
+
const require_schema = require('./schema-B9AyXcgU.cjs');
|
|
2
|
+
const require_errors = require('./errors-DhV00966.cjs');
|
|
3
|
+
const require_entity = require('./entity-BM1unrQf.cjs');
|
|
4
|
+
let viem = require("viem");
|
|
5
|
+
|
|
6
|
+
//#region src/utils/logger.ts
|
|
7
|
+
const ROOT_NAMESPACE = "arkiv";
|
|
8
|
+
function getDebugEnv() {
|
|
9
|
+
if (typeof process !== "undefined") {
|
|
10
|
+
const debugEnv = process?.env?.DEBUG;
|
|
11
|
+
if (debugEnv) return debugEnv;
|
|
12
|
+
}
|
|
13
|
+
if (typeof globalThis !== "undefined" && "localStorage" in globalThis) try {
|
|
14
|
+
return globalThis.localStorage?.getItem("DEBUG") || "";
|
|
15
|
+
} catch {}
|
|
16
|
+
return "";
|
|
17
|
+
}
|
|
18
|
+
function matchesPattern(namespace, pattern) {
|
|
19
|
+
if (pattern === namespace) return true;
|
|
20
|
+
if (pattern.endsWith("*")) {
|
|
21
|
+
const prefix = pattern.slice(0, -1);
|
|
22
|
+
return namespace.startsWith(prefix);
|
|
23
|
+
}
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
function isEnabled(namespace) {
|
|
27
|
+
const debugEnv = getDebugEnv();
|
|
28
|
+
if (!debugEnv) return false;
|
|
29
|
+
const patterns = debugEnv.split(/[\s,]+/).filter((p) => p.length > 0);
|
|
30
|
+
if (patterns.length === 0) return false;
|
|
31
|
+
let enabled = false;
|
|
32
|
+
for (const pattern of patterns) if (pattern.startsWith("-")) {
|
|
33
|
+
if (matchesPattern(namespace, pattern.slice(1))) return false;
|
|
34
|
+
} else if (matchesPattern(namespace, pattern)) enabled = true;
|
|
35
|
+
return enabled;
|
|
36
|
+
}
|
|
37
|
+
const COLOR_PALETTE = [
|
|
38
|
+
"#0000FF",
|
|
39
|
+
"#008000",
|
|
40
|
+
"#FF0000",
|
|
41
|
+
"#FF00FF",
|
|
42
|
+
"#FFA500",
|
|
43
|
+
"#800080",
|
|
44
|
+
"#00CED1",
|
|
45
|
+
"#FF1493",
|
|
46
|
+
"#32CD32",
|
|
47
|
+
"#1E90FF",
|
|
48
|
+
"#FF4500",
|
|
49
|
+
"#9370DB",
|
|
50
|
+
"#00FA9A",
|
|
51
|
+
"#FFD700",
|
|
52
|
+
"#DC143C",
|
|
53
|
+
"#00BFFF",
|
|
54
|
+
"#FF69B4",
|
|
55
|
+
"#20B2AA",
|
|
56
|
+
"#DA70D6",
|
|
57
|
+
"#4682B4"
|
|
58
|
+
];
|
|
59
|
+
const isNode = typeof process !== "undefined" && process.versions?.node != null;
|
|
60
|
+
const ANSI_COLORS = [
|
|
61
|
+
"\x1B[94m",
|
|
62
|
+
"\x1B[92m",
|
|
63
|
+
"\x1B[91m",
|
|
64
|
+
"\x1B[95m",
|
|
65
|
+
"\x1B[93m",
|
|
66
|
+
"\x1B[96m",
|
|
67
|
+
"\x1B[35m",
|
|
68
|
+
"\x1B[36m",
|
|
69
|
+
"\x1B[32m",
|
|
70
|
+
"\x1B[34m",
|
|
71
|
+
"\x1B[33m",
|
|
72
|
+
"\x1B[31m",
|
|
73
|
+
"\x1B[90m",
|
|
74
|
+
"\x1B[37m",
|
|
75
|
+
"\x1B[1m"
|
|
76
|
+
];
|
|
77
|
+
const ANSI_RESET = "\x1B[0m";
|
|
78
|
+
const ANSI_BOLD = "\x1B[1m";
|
|
79
|
+
const namespaceColors = /* @__PURE__ */ new Map();
|
|
80
|
+
function getColorForNamespace(namespace) {
|
|
81
|
+
const cachedColor = namespaceColors.get(namespace);
|
|
82
|
+
if (cachedColor) return cachedColor;
|
|
83
|
+
let hash = 0;
|
|
84
|
+
for (let i = 0; i < namespace.length; i++) {
|
|
85
|
+
const char = namespace.charCodeAt(i);
|
|
86
|
+
hash = (hash << 5) - hash + char;
|
|
87
|
+
hash = hash & hash;
|
|
88
|
+
}
|
|
89
|
+
const colorIndex = Math.abs(hash) % COLOR_PALETTE.length;
|
|
90
|
+
const color = {
|
|
91
|
+
css: COLOR_PALETTE[colorIndex] ?? "inherit",
|
|
92
|
+
ansi: ANSI_COLORS[colorIndex % ANSI_COLORS.length] ?? ""
|
|
93
|
+
};
|
|
94
|
+
namespaceColors.set(namespace, color);
|
|
95
|
+
return color;
|
|
96
|
+
}
|
|
97
|
+
function createDebugLogger(namespace) {
|
|
98
|
+
const colors = getColorForNamespace(namespace);
|
|
99
|
+
return (...args) => {
|
|
100
|
+
if (isEnabled(namespace)) if (isNode) {
|
|
101
|
+
const coloredNamespace = `${ANSI_BOLD}${colors.ansi}${namespace}${ANSI_RESET}`;
|
|
102
|
+
console.log(coloredNamespace, ...args);
|
|
103
|
+
} else {
|
|
104
|
+
const style1 = `color: ${colors.css}; font-weight: bold`;
|
|
105
|
+
console.log(`%c${namespace}%c`, style1, "color: inherit; font-weight: normal", ...args);
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
const getLogger = (scope) => {
|
|
110
|
+
return createDebugLogger(`${ROOT_NAMESPACE}:${scope}`);
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
//#endregion
|
|
114
|
+
//#region src/errors.ts
|
|
115
|
+
var EntityMutationError = class extends Error {
|
|
116
|
+
txHash;
|
|
117
|
+
constructor(message, options) {
|
|
118
|
+
super(message, { cause: options?.cause });
|
|
119
|
+
this.name = "EntityMutationError";
|
|
120
|
+
this.txHash = options?.txHash;
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
var NoMoreResultsError = class extends Error {
|
|
124
|
+
constructor() {
|
|
125
|
+
super("No more results — the last page carried no cursor. Check hasNextPage() before next().");
|
|
126
|
+
this.name = "NoMoreResultsError";
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
var NoEntityFoundError = class extends Error {
|
|
130
|
+
/** The key that matched nothing, when the lookup was by key. */
|
|
131
|
+
entityKey;
|
|
132
|
+
constructor(entityKey) {
|
|
133
|
+
super(entityKey === void 0 ? "No entity found" : `No live entity with key ${entityKey}. It was never created, or it has been deleted or has expired.`);
|
|
134
|
+
this.name = "NoEntityFoundError";
|
|
135
|
+
this.entityKey = entityKey;
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
var EmptyPatchError = class extends Error {
|
|
139
|
+
entityKey;
|
|
140
|
+
constructor(entityKey) {
|
|
141
|
+
super(`The patch for entity ${entityKey} has nothing to apply. Pass "set" to write columns, or "unset" to remove optional ones.`);
|
|
142
|
+
this.name = "EmptyPatchError";
|
|
143
|
+
this.entityKey = entityKey;
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
//#endregion
|
|
148
|
+
//#region src/query/selection.ts
|
|
149
|
+
/**
|
|
150
|
+
* The entity fields a row can carry. Each is `$`-prefixed, like the system attributes `where`
|
|
151
|
+
* filters on: an attribute name cannot start with `$`, so no attribute can collide with one.
|
|
152
|
+
*/
|
|
153
|
+
const SYSTEM_FIELDS = [
|
|
154
|
+
"$key",
|
|
155
|
+
"$owner",
|
|
156
|
+
"$creator",
|
|
157
|
+
"$createdAt",
|
|
158
|
+
"$updatedAt",
|
|
159
|
+
"$expiresAt",
|
|
160
|
+
"$creationFlags"
|
|
161
|
+
];
|
|
162
|
+
/** The selection keys with a fixed meaning, in the order they are written into the RPC `select`. */
|
|
163
|
+
const SELECTION_KEYS = [
|
|
164
|
+
...SYSTEM_FIELDS,
|
|
165
|
+
"$attributeSchema",
|
|
166
|
+
"$attributes"
|
|
167
|
+
];
|
|
168
|
+
/**
|
|
169
|
+
* Converts a selection into the RPC `select` object — the same request {@link projectSelection}
|
|
170
|
+
* makes without a schema.
|
|
171
|
+
*
|
|
172
|
+
* Every field is opt-in — anything not explicitly selected is sent as `false`, including the key.
|
|
173
|
+
* Sending the full object rather than omitting the `false`s keeps what the SDK asked for legible in
|
|
174
|
+
* a request log, and pins the answer even if a node's defaults change.
|
|
175
|
+
*
|
|
176
|
+
* `undefined` or `"*"` selects everything.
|
|
177
|
+
*
|
|
178
|
+
* @throws If the selection asks for nothing, or names an attribute outside the name grammar.
|
|
179
|
+
*
|
|
180
|
+
* @example
|
|
181
|
+
* toRpcSelect() // everything
|
|
182
|
+
* toRpcSelect({ $key: true }) // only the key
|
|
183
|
+
* toRpcSelect({ $key: true, projectId: true }) // the key and one attribute
|
|
184
|
+
*/
|
|
185
|
+
function toRpcSelect(selection) {
|
|
186
|
+
return projectSelection(selection).select;
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* A frozen copy of a selection, so that editing the object passed to `select()` afterwards cannot
|
|
190
|
+
* change what the builder sends.
|
|
191
|
+
*/
|
|
192
|
+
function snapshotSelection(selection) {
|
|
193
|
+
if (typeof selection !== "object" || selection === null) return selection;
|
|
194
|
+
return Object.freeze({ ...selection });
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Resolves a selection, through a schema or without one. A key that does not start with `$` names
|
|
198
|
+
* an attribute — through a schema, it must be a column.
|
|
199
|
+
*
|
|
200
|
+
* `undefined` or `"*"` selects every entity field and, through a schema, every column; without one,
|
|
201
|
+
* every attribute as the `$attributes` map. The `$attributeSchema` is only selected by name.
|
|
202
|
+
*
|
|
203
|
+
* Attributes are fetched by name where possible, so the ones a row does not carry stay on the node.
|
|
204
|
+
* A named schema's tag is fetched too, so each entity can be checked against it.
|
|
205
|
+
*
|
|
206
|
+
* @throws If the selection asks for nothing, names a `$` key that is not an entity field, names an
|
|
207
|
+
* attribute outside the name grammar, or — through a schema — names an attribute that is not a
|
|
208
|
+
* column.
|
|
209
|
+
*/
|
|
210
|
+
function projectSelection(selection, schema) {
|
|
211
|
+
const fields = [];
|
|
212
|
+
const columns = [];
|
|
213
|
+
let attributes = false;
|
|
214
|
+
let attributeSchema = false;
|
|
215
|
+
if (selection === void 0 || selection === "*") {
|
|
216
|
+
fields.push(...SYSTEM_FIELDS);
|
|
217
|
+
if (schema === void 0) attributes = true;
|
|
218
|
+
else columns.push(...Object.entries(schema.columns));
|
|
219
|
+
} else {
|
|
220
|
+
if (typeof selection !== "object" || selection === null || Array.isArray(selection)) throw new TypeError("select() takes \"*\" or an object of fields set to true");
|
|
221
|
+
for (const [name, wanted] of Object.entries(selection)) {
|
|
222
|
+
if (typeof wanted !== "boolean" && wanted !== void 0) throw new Error(name === "$attributes" ? "select() takes true or false for \"$attributes\", which reads every attribute. To read some of them, select each by name: select({ title: true, body: true })." : `select() takes true or false for "${name}", got ${typeof wanted}.`);
|
|
223
|
+
const fixed = SELECTION_KEYS.includes(name);
|
|
224
|
+
if (!fixed && name.startsWith("$")) throw new Error(`select() names "${name}", which is not an entity field. The entity fields are ${SYSTEM_FIELDS.join(", ")}, and $attributes and $attributeSchema read the attributes as a whole.`);
|
|
225
|
+
const column = fixed || schema === void 0 ? void 0 : require_schema.lookupColumn(schema, name);
|
|
226
|
+
if (!fixed) {
|
|
227
|
+
if (schema !== void 0 && column === void 0) throw new Error(`select() names "${name}", which is not a column of the schema. Its columns are ${require_schema.describeColumns(schema)}.`);
|
|
228
|
+
require_schema.validateAttributeName(name);
|
|
229
|
+
}
|
|
230
|
+
if (!wanted) continue;
|
|
231
|
+
if (name === "$attributes") attributes = true;
|
|
232
|
+
else if (name === "$attributeSchema") attributeSchema = true;
|
|
233
|
+
else if (fixed) fields.push(name);
|
|
234
|
+
else columns.push([name, column]);
|
|
235
|
+
}
|
|
236
|
+
if (fields.length === 0 && columns.length === 0 && !attributes && !attributeSchema) throw nothingSelected();
|
|
237
|
+
}
|
|
238
|
+
const select = {};
|
|
239
|
+
for (const field of SYSTEM_FIELDS) select[entityField(field)] = fields.includes(field);
|
|
240
|
+
select.attributeSchema = attributeSchema;
|
|
241
|
+
if (attributes) select.attributes = true;
|
|
242
|
+
else {
|
|
243
|
+
const names = new Set(columns.map(([name]) => name));
|
|
244
|
+
if (schema?.tag !== void 0) names.add(require_schema.SCHEMA_ATTRIBUTE);
|
|
245
|
+
select.attributes = names.size > 0 ? Object.fromEntries([...names].map((n) => [n, true])) : false;
|
|
246
|
+
}
|
|
247
|
+
return {
|
|
248
|
+
select,
|
|
249
|
+
fields,
|
|
250
|
+
columns,
|
|
251
|
+
attributes,
|
|
252
|
+
attributeSchema,
|
|
253
|
+
tag: schema?.tag
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Lays an entity out as a row of `projection` — checking it against the schema, when there is one.
|
|
258
|
+
*
|
|
259
|
+
* @throws {SchemaMismatchError} If a named schema's entity is not tagged with its name, or the
|
|
260
|
+
* entity lacks a selected required column or holds one with a type other than the schema's — it
|
|
261
|
+
* was not written through this schema, or was patched since.
|
|
262
|
+
*/
|
|
263
|
+
function toRow(entity, projection) {
|
|
264
|
+
const subject = entity.key === void 0 ? "the entity" : `entity ${entity.key}`;
|
|
265
|
+
const { tag } = projection;
|
|
266
|
+
if (tag !== void 0) {
|
|
267
|
+
const found = attributeOf(entity, require_schema.SCHEMA_ATTRIBUTE);
|
|
268
|
+
if (found?.type !== "str" || found.value !== tag.value) throw new require_schema.SchemaMismatchError(require_schema.SCHEMA_ATTRIBUTE, `${subject} is ${describeTag(found)}, not ${JSON.stringify(tag.value)}, so it was not written through this schema`);
|
|
269
|
+
}
|
|
270
|
+
const row = {};
|
|
271
|
+
for (const field of projection.fields) row[field] = entity[entityField(field)];
|
|
272
|
+
for (const [name, column] of projection.columns) {
|
|
273
|
+
const value = attributeOf(entity, name);
|
|
274
|
+
if (column !== void 0) {
|
|
275
|
+
if (value === void 0) {
|
|
276
|
+
if (!column.isOptional) throw new require_schema.SchemaMismatchError(name, `${subject} has no value for this required ${column.type} column, so it does not match the schema. Declare the column .optional() if entities may leave it unset`);
|
|
277
|
+
} else if (value.type !== column.type) throw new require_schema.SchemaMismatchError(name, `${subject} holds a ${value.type}, not the ${column.type} the schema declares`);
|
|
278
|
+
}
|
|
279
|
+
row[name] = value;
|
|
280
|
+
}
|
|
281
|
+
if (projection.attributes) row.$attributes = withoutTag(entity.attributes ?? {}, tag);
|
|
282
|
+
if (projection.attributeSchema) row.$attributeSchema = withoutTag(entity.attributeSchema ?? {}, tag);
|
|
283
|
+
return row;
|
|
284
|
+
}
|
|
285
|
+
/** The {@link Entity} property, and RPC `select` key, a system field reads: its name without `$`. */
|
|
286
|
+
function entityField(field) {
|
|
287
|
+
return field.slice(1);
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* A copy of `record` without a named schema's tag: the tag is how a query finds the schema's
|
|
291
|
+
* entities, not one of their attributes, so a row never carries it — and a map read back from a
|
|
292
|
+
* row drops into a write without sending the reserved name.
|
|
293
|
+
*/
|
|
294
|
+
function withoutTag(record, tag) {
|
|
295
|
+
if (tag === void 0 || !Object.hasOwn(record, require_schema.SCHEMA_ATTRIBUTE)) return record;
|
|
296
|
+
const { [require_schema.SCHEMA_ATTRIBUTE]: _tag,...rest } = record;
|
|
297
|
+
return rest;
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* The entity's attribute `name`, or `undefined` if it has none. An own-property read: a column may
|
|
301
|
+
* be named `constructor` or `toString`, and an entity that leaves it unset must not find the
|
|
302
|
+
* inherited one instead.
|
|
303
|
+
*/
|
|
304
|
+
function attributeOf(entity, name) {
|
|
305
|
+
const { attributes } = entity;
|
|
306
|
+
return attributes !== void 0 && Object.hasOwn(attributes, name) ? attributes[name] : void 0;
|
|
307
|
+
}
|
|
308
|
+
/** How an entity's schema tag reads in a mismatch — whatever type another writer gave it. */
|
|
309
|
+
function describeTag(found) {
|
|
310
|
+
if (found === void 0) return "not tagged with a schema";
|
|
311
|
+
if (found.type === "str") return `tagged ${JSON.stringify(found.value)}`;
|
|
312
|
+
return `tagged with a value of type ${found.type}`;
|
|
313
|
+
}
|
|
314
|
+
function nothingSelected() {
|
|
315
|
+
return /* @__PURE__ */ new Error("select() requires at least one field to be selected. Pass select() or select(\"*\") to select everything.");
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
//#endregion
|
|
319
|
+
//#region src/entity/flags.ts
|
|
320
|
+
/** Bit positions in the `creationFlags` byte. Bits 2-7 are reserved and must be zero. */
|
|
321
|
+
const READONLY_BIT = 1;
|
|
322
|
+
const PERMISSIONLESS_EXTENSION_BIT = 2;
|
|
323
|
+
/**
|
|
324
|
+
* Packs creation flags into the `creationFlags` byte.
|
|
325
|
+
*
|
|
326
|
+
* @throws {InvalidCreationFlagsError} If a value is not a boolean.
|
|
327
|
+
*/
|
|
328
|
+
function encodeCreationFlags(flags = {}) {
|
|
329
|
+
for (const [name, value] of Object.entries(flags)) if (value !== void 0 && typeof value !== "boolean") throw new require_errors.InvalidCreationFlagsError(`"${name}" must be a boolean, got ${typeof value}`);
|
|
330
|
+
return (flags.readonly ? READONLY_BIT : 0) | (flags.permissionlessExtension ? PERMISSIONLESS_EXTENSION_BIT : 0);
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Reads the `creationFlags` byte back.
|
|
334
|
+
*
|
|
335
|
+
* @param raw - The `creationFlags` byte.
|
|
336
|
+
* @throws {InvalidCreationFlagsError} Malformed input
|
|
337
|
+
*/
|
|
338
|
+
function decodeCreationFlags(raw) {
|
|
339
|
+
if (!Number.isInteger(raw) || raw < 0 || raw > 255) throw new require_errors.InvalidCreationFlagsError(`${raw} is not a byte`);
|
|
340
|
+
return {
|
|
341
|
+
readonly: (raw & READONLY_BIT) !== 0,
|
|
342
|
+
permissionlessExtension: (raw & PERMISSIONLESS_EXTENSION_BIT) !== 0,
|
|
343
|
+
raw
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
//#endregion
|
|
348
|
+
//#region src/utils/entities.ts
|
|
349
|
+
const logger$1 = getLogger("utils:entities");
|
|
350
|
+
/**
|
|
351
|
+
* Turns the attribute array a query returns into a name-keyed map of typed values.
|
|
352
|
+
*
|
|
353
|
+
* Attribute names are unique per entity, so the map loses nothing and gives callers
|
|
354
|
+
* `entity.attributes.level.value` instead of a linear search.
|
|
355
|
+
*/
|
|
356
|
+
function decodeAttributes(rpcAttributes) {
|
|
357
|
+
const attributes = {};
|
|
358
|
+
for (const { name, type, value } of rpcAttributes) attributes[name] = require_schema.decodeRpcValue(type, value);
|
|
359
|
+
return attributes;
|
|
360
|
+
}
|
|
361
|
+
/** The same, for the values-free `attributeSchema` projection. */
|
|
362
|
+
function decodeSchema(entries) {
|
|
363
|
+
const schema = {};
|
|
364
|
+
for (const { name, type, indexed } of entries) schema[name] = {
|
|
365
|
+
type: require_schema.asTypeTag(type),
|
|
366
|
+
indexed: typeof indexed === "boolean" ? indexed : void 0
|
|
367
|
+
};
|
|
368
|
+
return schema;
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* Builds an {@link Entity} from a query result row.
|
|
372
|
+
*
|
|
373
|
+
* A field the query did not select is absent from the response and stays `undefined` here — the
|
|
374
|
+
* decoder never substitutes an empty value for one that was not asked for.
|
|
375
|
+
*/
|
|
376
|
+
function entityFromRpcResult(rpcEntity) {
|
|
377
|
+
logger$1("entityFromRpcResult %o", rpcEntity);
|
|
378
|
+
return new require_entity.Entity({
|
|
379
|
+
key: rpcEntity.key,
|
|
380
|
+
owner: toAddress(rpcEntity.owner),
|
|
381
|
+
creator: toAddress(rpcEntity.creator),
|
|
382
|
+
createdAt: toBlock(rpcEntity.createdAt),
|
|
383
|
+
updatedAt: toBlock(rpcEntity.updatedAt),
|
|
384
|
+
expiresAt: toBlock(rpcEntity.expiresAt),
|
|
385
|
+
creationFlags: decodeFlags(rpcEntity.creationFlags),
|
|
386
|
+
attributeSchema: rpcEntity.attributeSchema !== void 0 ? decodeSchema(rpcEntity.attributeSchema) : void 0,
|
|
387
|
+
attributes: rpcEntity.attributes !== void 0 ? decodeAttributes(rpcEntity.attributes) : void 0
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
function toBlock(value) {
|
|
391
|
+
return value === void 0 ? void 0 : BigInt(value);
|
|
392
|
+
}
|
|
393
|
+
function toAddress(value) {
|
|
394
|
+
if (value === void 0) return void 0;
|
|
395
|
+
if (!/^0x[0-9a-fA-F]{40}$/.test(value)) {
|
|
396
|
+
logger$1("unreadable address %o", value);
|
|
397
|
+
return value;
|
|
398
|
+
}
|
|
399
|
+
return (0, viem.checksumAddress)(value);
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* Resolves the `creationFlags` projection, preferring the byte.
|
|
403
|
+
*/
|
|
404
|
+
function decodeFlags(value) {
|
|
405
|
+
if (value === void 0) return void 0;
|
|
406
|
+
const raw = toFlagsByte(typeof value === "number" ? value : value.raw);
|
|
407
|
+
if (raw !== void 0) return decodeCreationFlags(raw);
|
|
408
|
+
if (typeof value === "object" && (value.readonly ?? value.permissionlessExtension) !== void 0) return decodeCreationFlags(encodeCreationFlags({
|
|
409
|
+
readonly: value.readonly ?? false,
|
|
410
|
+
permissionlessExtension: value.permissionlessExtension ?? false
|
|
411
|
+
}));
|
|
412
|
+
logger$1("unreadable creationFlags %o", value);
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* A flags byte in whichever spelling arrived: a JSON number, or the `0x` QUANTITY that every other
|
|
416
|
+
* numeric field on {@link RpcEntity} already uses. `undefined` for anything that is not a byte.
|
|
417
|
+
*/
|
|
418
|
+
function toFlagsByte(value) {
|
|
419
|
+
const parsed = typeof value === "number" ? value : typeof value === "string" && /^(0x[0-9a-fA-F]+|\d+)$/.test(value) ? Number(value) : NaN;
|
|
420
|
+
return Number.isInteger(parsed) && parsed >= 0 && parsed <= 255 ? parsed : void 0;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
//#endregion
|
|
424
|
+
//#region src/query/errors.ts
|
|
425
|
+
/**
|
|
426
|
+
* A predicate asked for an operator the query language does not define for that type.
|
|
427
|
+
*
|
|
428
|
+
* The operator × type matrix is part of the language, not a policy: a prefix test on a type other
|
|
429
|
+
* than `str` is a **type error** at the node (a {@link QueryError} of kind `"type"`), not
|
|
430
|
+
* an empty result. Catching it here turns a round trip and an RPC error code into a stack trace
|
|
431
|
+
* pointing at the call site.
|
|
432
|
+
*
|
|
433
|
+
* | | `=` `!=` `<` `<=` `>` `>=` `BETWEEN` | `STARTSWITH` |
|
|
434
|
+
* | --- | --- | --- |
|
|
435
|
+
* | `str` | yes | yes |
|
|
436
|
+
* | every other indexable type | yes | no |
|
|
437
|
+
*/
|
|
438
|
+
var UnsupportedOperatorError = class extends Error {
|
|
439
|
+
/** The attribute the predicate names. */
|
|
440
|
+
attributeName;
|
|
441
|
+
/** The operator, as it would have been written in the query. */
|
|
442
|
+
operator;
|
|
443
|
+
/** The type of the value it was applied to. */
|
|
444
|
+
tag;
|
|
445
|
+
constructor(attributeName, operator, tag, hint) {
|
|
446
|
+
super(`The query language does not define ${operator} for a ${tag}, so "${attributeName} ${operator} ..." cannot be expressed.${hint ? ` ${hint}` : ""}`);
|
|
447
|
+
this.name = "UnsupportedOperatorError";
|
|
448
|
+
this.attributeName = attributeName;
|
|
449
|
+
this.operator = operator;
|
|
450
|
+
this.tag = tag;
|
|
451
|
+
}
|
|
452
|
+
};
|
|
453
|
+
/**
|
|
454
|
+
* A predicate or query could not be expressed in the query language for a reason other than its
|
|
455
|
+
* operator. The main causes:
|
|
456
|
+
*
|
|
457
|
+
* - a `$` name that is not a queryable system attribute — one returned in results but never
|
|
458
|
+
* indexed (`$createdAt`, `$updatedAt`, `$creationFlags`), or none at all;
|
|
459
|
+
* - a system attribute compared against the wrong type, or tested with `exists()` / `hasType()`,
|
|
460
|
+
* which on a system attribute would be constants;
|
|
461
|
+
* - a comparison against a `bytes` value, which is never indexed;
|
|
462
|
+
* - a `BETWEEN` whose bounds have different types;
|
|
463
|
+
* - a `hasType()` tag that is not an indexable type;
|
|
464
|
+
* - a combinator with nothing to combine;
|
|
465
|
+
* - a query with no filter at all — a named schema's tag counts as one;
|
|
466
|
+
* - through `.from(schema)`, a filter that does not fit the schema: a name that is not a column, a
|
|
467
|
+
* column that is not indexed, or a value of another type than the column's.
|
|
468
|
+
*/
|
|
469
|
+
var InvalidPredicateError = class extends Error {
|
|
470
|
+
/** The attribute the predicate names, where there is one. */
|
|
471
|
+
attributeName;
|
|
472
|
+
constructor(message, attributeName) {
|
|
473
|
+
super(message);
|
|
474
|
+
this.name = "InvalidPredicateError";
|
|
475
|
+
this.attributeName = attributeName;
|
|
476
|
+
}
|
|
477
|
+
};
|
|
478
|
+
/** The JSON-RPC error codes `arkiv_query` answers with, and what each one means. */
|
|
479
|
+
const QUERY_ERROR_KINDS = {
|
|
480
|
+
[-32001]: "parse",
|
|
481
|
+
[-32002]: "type",
|
|
482
|
+
[-32003]: "literal",
|
|
483
|
+
[-32004]: "limits",
|
|
484
|
+
[-32005]: "cursor",
|
|
485
|
+
[-32006]: "block"
|
|
486
|
+
};
|
|
487
|
+
/**
|
|
488
|
+
* The node rejected a query.
|
|
489
|
+
*
|
|
490
|
+
* These codes are part of the frozen `arkiv_query` surface, so {@link kind} and {@link data} are
|
|
491
|
+
* stable enough to branch on — which the underlying JSON-RPC error is not, because the codes
|
|
492
|
+
* overlap Ethereum's own EIP-1474 numbering and get wrapped accordingly on the way up.
|
|
493
|
+
*
|
|
494
|
+
* @example
|
|
495
|
+
* try {
|
|
496
|
+
* await result.next()
|
|
497
|
+
* } catch (error) {
|
|
498
|
+
* if (error instanceof QueryError && error.kind === "cursor") {
|
|
499
|
+
* // The page is gone — start the query over.
|
|
500
|
+
* }
|
|
501
|
+
* }
|
|
502
|
+
*/
|
|
503
|
+
var QueryError = class extends Error {
|
|
504
|
+
/** Which failure this is. */
|
|
505
|
+
kind;
|
|
506
|
+
/** The JSON-RPC error code. */
|
|
507
|
+
code;
|
|
508
|
+
/** The machine-readable detail the node sent, if any — e.g. `{ position, expected, got }`. */
|
|
509
|
+
data;
|
|
510
|
+
/** The query string that was rejected. */
|
|
511
|
+
query;
|
|
512
|
+
constructor(kind, code, query, data, detail) {
|
|
513
|
+
super(`Query rejected (${kind}, ${code})${detail ? `: ${detail}` : ""}. Query: ${query}`, { cause: data });
|
|
514
|
+
this.name = "QueryError";
|
|
515
|
+
this.kind = kind;
|
|
516
|
+
this.code = code;
|
|
517
|
+
this.data = data;
|
|
518
|
+
this.query = query;
|
|
519
|
+
}
|
|
520
|
+
};
|
|
521
|
+
/**
|
|
522
|
+
* Recognises an `arkiv_query` failure in whatever the transport threw.
|
|
523
|
+
*
|
|
524
|
+
* The codes are dug out of the error chain rather than read off the top: viem maps this range onto
|
|
525
|
+
* its own EIP-1474 error classes (`-32001` is its "resource not found"), so by the time an error
|
|
526
|
+
* surfaces the Arkiv meaning is one or more `cause` links down.
|
|
527
|
+
*
|
|
528
|
+
* @returns The typed error, or `undefined` if this was not a query rejection.
|
|
529
|
+
*/
|
|
530
|
+
function asQueryError(error, query) {
|
|
531
|
+
let code;
|
|
532
|
+
let data;
|
|
533
|
+
let detail;
|
|
534
|
+
let current = error;
|
|
535
|
+
for (let depth = 0; current != null && depth < 8; depth++) {
|
|
536
|
+
const link = current;
|
|
537
|
+
if (code === void 0 && typeof link.code === "number") code = link.code;
|
|
538
|
+
if (data === void 0 && link.data !== void 0) data = link.data;
|
|
539
|
+
if (detail === void 0 && typeof link.shortMessage === "string") detail = link.shortMessage;
|
|
540
|
+
current = link.cause;
|
|
541
|
+
}
|
|
542
|
+
const kind = code === void 0 ? void 0 : QUERY_ERROR_KINDS[code];
|
|
543
|
+
if (kind === void 0 || code === void 0) return void 0;
|
|
544
|
+
return new QueryError(kind, code, query, data, detail);
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
//#endregion
|
|
548
|
+
//#region src/query/engine.ts
|
|
549
|
+
const logger = getLogger("query:engine");
|
|
550
|
+
const MAX_LIMIT = 100;
|
|
551
|
+
/**
|
|
552
|
+
* Runs one query and decodes the page it returns.
|
|
553
|
+
*
|
|
554
|
+
* @throws If `limit` is not an integer from 1 to {@link MAX_LIMIT}.
|
|
555
|
+
* @throws {QueryError} If the node rejects the query.
|
|
556
|
+
*/
|
|
557
|
+
async function runQuery(client, request) {
|
|
558
|
+
const { query, select, limit, cursor, atBlock, count } = request;
|
|
559
|
+
if (limit !== void 0) {
|
|
560
|
+
if (!Number.isInteger(limit) || limit < 1) throw new Error(`limit must be a positive integer, got ${limit}.`);
|
|
561
|
+
if (limit > MAX_LIMIT) throw new Error(`limit ${limit} exceeds the node maximum of ${MAX_LIMIT}. Page through the results with the cursor instead.`);
|
|
562
|
+
}
|
|
563
|
+
const queryOptions = {
|
|
564
|
+
select: count ? {
|
|
565
|
+
...select,
|
|
566
|
+
count: true
|
|
567
|
+
} : select,
|
|
568
|
+
...atBlock !== void 0 && { atBlock: (0, viem.numberToHex)(atBlock) },
|
|
569
|
+
...limit !== void 0 && { limit: (0, viem.numberToHex)(limit) },
|
|
570
|
+
...cursor !== void 0 && { cursor }
|
|
571
|
+
};
|
|
572
|
+
logger("arkiv_query %s %o", query, queryOptions);
|
|
573
|
+
const result = await client.request({
|
|
574
|
+
method: "arkiv_query",
|
|
575
|
+
params: [query, queryOptions]
|
|
576
|
+
}).catch((error) => {
|
|
577
|
+
throw asQueryError(error, query) ?? error;
|
|
578
|
+
});
|
|
579
|
+
logger("Raw result from query %o", result);
|
|
580
|
+
return {
|
|
581
|
+
entities: result.data.map((rpcEntity) => entityFromRpcResult(rpcEntity)),
|
|
582
|
+
blockNumber: BigInt(result.blockNumber),
|
|
583
|
+
cursor: result.cursor,
|
|
584
|
+
count: result.count === void 0 ? void 0 : BigInt(result.count)
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
//#endregion
|
|
589
|
+
//#region src/query/expression.ts
|
|
590
|
+
/**
|
|
591
|
+
* The system attributes a query may filter on, and the type each one carries.
|
|
592
|
+
*
|
|
593
|
+
* Every other system attribute (`$createdAt`, `$updatedAt`, `$creationFlags`) is returned in
|
|
594
|
+
* query results — selected and read back as `createdAt`, `updatedAt` and `creationFlags` — but is
|
|
595
|
+
* not indexed, so it cannot appear in a predicate.
|
|
596
|
+
*/
|
|
597
|
+
const QUERYABLE_SYSTEM_ATTRIBUTES = {
|
|
598
|
+
$key: "key",
|
|
599
|
+
$owner: "addr",
|
|
600
|
+
$creator: "addr",
|
|
601
|
+
$expiresAt: "u64"
|
|
602
|
+
};
|
|
603
|
+
/** System attributes a query can ask for but not filter on — they carry no index. */
|
|
604
|
+
const RESULT_ONLY_SYSTEM_ATTRIBUTES = [
|
|
605
|
+
"$createdAt",
|
|
606
|
+
"$updatedAt",
|
|
607
|
+
"$creationFlags"
|
|
608
|
+
];
|
|
609
|
+
/**
|
|
610
|
+
* Checks an attribute reference, returning its fixed type when it names a system attribute.
|
|
611
|
+
*
|
|
612
|
+
* User attribute names go through the same grammar the write path enforces, so a name that could
|
|
613
|
+
* never have been written is rejected here rather than matching nothing.
|
|
614
|
+
*/
|
|
615
|
+
function resolveReference(name) {
|
|
616
|
+
if (typeof name !== "string" || !name.startsWith("$")) {
|
|
617
|
+
require_schema.validateAttributeName(name);
|
|
618
|
+
return;
|
|
619
|
+
}
|
|
620
|
+
const system = QUERYABLE_SYSTEM_ATTRIBUTES[name];
|
|
621
|
+
if (system !== void 0) return system;
|
|
622
|
+
if (RESULT_ONLY_SYSTEM_ATTRIBUTES.includes(name)) throw new InvalidPredicateError(`"${name}" is returned in query results but carries no index, so it cannot be filtered on. Select it as \`${name.slice(1)}\` and filter the results in JavaScript instead.`, name);
|
|
623
|
+
throw new InvalidPredicateError(`"${name}" is not a system attribute. The queryable ones are ${Object.keys(QUERYABLE_SYSTEM_ATTRIBUTES).join(", ")}. "\$" is reserved for these, so a user attribute cannot be named this.`, name);
|
|
624
|
+
}
|
|
625
|
+
/**
|
|
626
|
+
* Whether a string names a type that can be indexed — every type but `bytes`.
|
|
627
|
+
*
|
|
628
|
+
* Takes a `string` rather than an `IndexableTypeTag` on purpose — the callers' static types
|
|
629
|
+
* already say as much, and this is the runtime check for the code that has no types.
|
|
630
|
+
*/
|
|
631
|
+
function isIndexableTag(tag) {
|
|
632
|
+
return Object.hasOwn(require_schema.TYPE_IDS, tag) && require_schema.isIndexable(tag);
|
|
633
|
+
}
|
|
634
|
+
/** Freezes a node and gives it a (non-enumerable, so still `toEqual`-able) rendering. */
|
|
635
|
+
function node(value) {
|
|
636
|
+
Object.defineProperty(value, "toString", {
|
|
637
|
+
value: () => render(value),
|
|
638
|
+
enumerable: false
|
|
639
|
+
});
|
|
640
|
+
return Object.freeze(value);
|
|
641
|
+
}
|
|
642
|
+
/**
|
|
643
|
+
* Resolves a value a predicate compares `name` against, and checks it can be: not `bytes`, which is
|
|
644
|
+
* never indexed, and of the fixed type when `name` is a system attribute.
|
|
645
|
+
*/
|
|
646
|
+
function predicateValue(name, system, input) {
|
|
647
|
+
const value = require_schema.toValue(input, name);
|
|
648
|
+
if (value.type === "bytes") throw new InvalidPredicateError(`"${name}" is compared against a bytes value, and bytes are stored but never indexed, so they cannot be filtered on. Index a str, bytes32 or key derived from them instead.`, name);
|
|
649
|
+
if (system !== void 0 && value.type !== system) throw new InvalidPredicateError(`"${name}" is a ${system}, but the value is a ${value.type}. System attributes have a fixed type — write ${system}(...) around the value.`, name);
|
|
650
|
+
return value;
|
|
651
|
+
}
|
|
652
|
+
function comparison(name, operator, input) {
|
|
653
|
+
return node({
|
|
654
|
+
kind: "comparison",
|
|
655
|
+
name,
|
|
656
|
+
operator,
|
|
657
|
+
value: predicateValue(name, resolveReference(name), input)
|
|
658
|
+
});
|
|
659
|
+
}
|
|
660
|
+
/**
|
|
661
|
+
* Attribute equals value — set with this exact type, and holding this value.
|
|
662
|
+
*
|
|
663
|
+
* Bare booleans, numbers, bigints and strings take the same default types they take when written
|
|
664
|
+
* (`number` is an `i32`, `bigint` a `u256`, `string` a `str`); anything else names its type.
|
|
665
|
+
*
|
|
666
|
+
* @throws {InvalidAttributeNameError} If the name is outside the attribute name grammar.
|
|
667
|
+
* @throws {InvalidPredicateError} If the name starts with `$` but is not a queryable system
|
|
668
|
+
* attribute, a system attribute is compared against the wrong type, or the value is `bytes`.
|
|
669
|
+
*
|
|
670
|
+
* @example
|
|
671
|
+
* eq("status", str("open")) // status = str('open')
|
|
672
|
+
* eq("flagged", true) // flagged = true
|
|
673
|
+
* eq("$owner", addr(account)) // $owner = addr(0xd8dA…)
|
|
674
|
+
*/
|
|
675
|
+
function eq(name, value) {
|
|
676
|
+
return comparison(name, "=", value);
|
|
677
|
+
}
|
|
678
|
+
/**
|
|
679
|
+
* Attribute is set with this type and holds a **different** value.
|
|
680
|
+
*
|
|
681
|
+
* This is typed value-negation, not the complement: an entity that never set the attribute, or set
|
|
682
|
+
* it with another type, does not match. Use `not(eq(...))` for the full complement.
|
|
683
|
+
*
|
|
684
|
+
* @throws {InvalidAttributeNameError} If the name is outside the attribute name grammar.
|
|
685
|
+
* @throws {InvalidPredicateError} If the name starts with `$` but is not a queryable system
|
|
686
|
+
* attribute, a system attribute is compared against the wrong type, or the value is `bytes`.
|
|
687
|
+
*
|
|
688
|
+
* @example
|
|
689
|
+
* ne("status", str("closed")) // status != str('closed')
|
|
690
|
+
* not(eq("status", str("closed"))) // NOT status = str('closed') — also matches entities with no status
|
|
691
|
+
*/
|
|
692
|
+
function ne(name, value) {
|
|
693
|
+
return comparison(name, "!=", value);
|
|
694
|
+
}
|
|
695
|
+
/**
|
|
696
|
+
* Attribute is greater than value, in index order — see {@link ComparisonOperator}.
|
|
697
|
+
*
|
|
698
|
+
* @throws {InvalidAttributeNameError} If the name is outside the attribute name grammar.
|
|
699
|
+
* @throws {InvalidPredicateError} If the name starts with `$` but is not a queryable system
|
|
700
|
+
* attribute, a system attribute is compared against the wrong type, or the value is `bytes`.
|
|
701
|
+
*
|
|
702
|
+
* @example
|
|
703
|
+
* gt("level", i32(10)) // level > i32(10)
|
|
704
|
+
* gt("$expiresAt", u64(1_200_000n)) // $expiresAt > u64(1200000)
|
|
705
|
+
*/
|
|
706
|
+
function gt(name, value) {
|
|
707
|
+
return comparison(name, ">", value);
|
|
708
|
+
}
|
|
709
|
+
/**
|
|
710
|
+
* Attribute is greater than or equal to value, in index order — see {@link ComparisonOperator}.
|
|
711
|
+
*
|
|
712
|
+
* @throws {InvalidAttributeNameError} If the name is outside the attribute name grammar.
|
|
713
|
+
* @throws {InvalidPredicateError} If the name starts with `$` but is not a queryable system
|
|
714
|
+
* attribute, a system attribute is compared against the wrong type, or the value is `bytes`.
|
|
715
|
+
*
|
|
716
|
+
* @example
|
|
717
|
+
* gte("score", dec("3.5")) // score >= dec(3.5)
|
|
718
|
+
*/
|
|
719
|
+
function gte(name, value) {
|
|
720
|
+
return comparison(name, ">=", value);
|
|
721
|
+
}
|
|
722
|
+
/**
|
|
723
|
+
* Attribute is less than value, in index order — see {@link ComparisonOperator}.
|
|
724
|
+
*
|
|
725
|
+
* @throws {InvalidAttributeNameError} If the name is outside the attribute name grammar.
|
|
726
|
+
* @throws {InvalidPredicateError} If the name starts with `$` but is not a queryable system
|
|
727
|
+
* attribute, a system attribute is compared against the wrong type, or the value is `bytes`.
|
|
728
|
+
*
|
|
729
|
+
* @example
|
|
730
|
+
* lt("level", i32(10)) // level < i32(10)
|
|
731
|
+
*/
|
|
732
|
+
function lt(name, value) {
|
|
733
|
+
return comparison(name, "<", value);
|
|
734
|
+
}
|
|
735
|
+
/**
|
|
736
|
+
* Attribute is less than or equal to value, in index order — see {@link ComparisonOperator}.
|
|
737
|
+
*
|
|
738
|
+
* @throws {InvalidAttributeNameError} If the name is outside the attribute name grammar.
|
|
739
|
+
* @throws {InvalidPredicateError} If the name starts with `$` but is not a queryable system
|
|
740
|
+
* attribute, a system attribute is compared against the wrong type, or the value is `bytes`.
|
|
741
|
+
*
|
|
742
|
+
* @example
|
|
743
|
+
* lte("score", dec("5")) // score <= dec(5)
|
|
744
|
+
*/
|
|
745
|
+
function lte(name, value) {
|
|
746
|
+
return comparison(name, "<=", value);
|
|
747
|
+
}
|
|
748
|
+
/**
|
|
749
|
+
* Attribute lies between two values, **inclusive** at both ends: it matches exactly what
|
|
750
|
+
* `and(gte(name, low), lte(name, high))` matches, in one predicate. Both bounds must have one type,
|
|
751
|
+
* and a `low` above `high` matches nothing.
|
|
752
|
+
*
|
|
753
|
+
* @throws {InvalidAttributeNameError} If the name is outside the attribute name grammar.
|
|
754
|
+
* @throws {InvalidPredicateError} If the bounds have different types, the name starts with `$` but
|
|
755
|
+
* is not a queryable system attribute, a system attribute is compared against the wrong type, or a
|
|
756
|
+
* bound is `bytes`.
|
|
757
|
+
*
|
|
758
|
+
* @example
|
|
759
|
+
* between("score", dec("3.5"), dec("5")) // score BETWEEN dec(3.5) AND dec(5)
|
|
760
|
+
* between("$expiresAt", u64(100n), u64(200n))
|
|
761
|
+
*/
|
|
762
|
+
function between(name, low, high) {
|
|
763
|
+
const system = resolveReference(name);
|
|
764
|
+
const lowValue = predicateValue(name, system, low);
|
|
765
|
+
const highValue = predicateValue(name, system, high);
|
|
766
|
+
if (lowValue.type !== highValue.type) throw new InvalidPredicateError(`BETWEEN on "${name}" has a ${lowValue.type} and a ${highValue.type} bound. Both bounds must have one type, the type the attribute is compared as.`, name);
|
|
767
|
+
return node({
|
|
768
|
+
kind: "between",
|
|
769
|
+
name,
|
|
770
|
+
low: lowValue,
|
|
771
|
+
high: highValue
|
|
772
|
+
});
|
|
773
|
+
}
|
|
774
|
+
/**
|
|
775
|
+
* Attribute is a `str` beginning with this prefix.
|
|
776
|
+
*
|
|
777
|
+
* The match is on **raw UTF-8 bytes** with no normalization — it is the index encoding, so
|
|
778
|
+
* `startsWith("name", "é")` matches only the same byte sequence, not another normal form of it.
|
|
779
|
+
*
|
|
780
|
+
* @param prefix - The prefix, as a bare string or a `str` value.
|
|
781
|
+
* @throws {InvalidAttributeNameError} If the name is outside the attribute name grammar.
|
|
782
|
+
* @throws {InvalidPredicateError} If the name starts with `$` but is not a queryable system
|
|
783
|
+
* attribute.
|
|
784
|
+
* @throws {UnsupportedOperatorError} If given a value that is not a `str`, or a system attribute
|
|
785
|
+
* (none of which is a `str`).
|
|
786
|
+
*
|
|
787
|
+
* @example
|
|
788
|
+
* startsWith("desc", "ab") // desc STARTSWITH str('ab')
|
|
789
|
+
*/
|
|
790
|
+
function startsWith(name, prefix) {
|
|
791
|
+
const system = resolveReference(name);
|
|
792
|
+
const value = typeof prefix === "string" ? require_schema.str(prefix) : require_schema.toValue(prefix, name);
|
|
793
|
+
if (value.type !== "str") throw new UnsupportedOperatorError(name, "STARTSWITH", value.type, "Only str carries a prefix index.");
|
|
794
|
+
if (system !== void 0) throw new UnsupportedOperatorError(name, "STARTSWITH", system, "No queryable system attribute is a str.");
|
|
795
|
+
return node({
|
|
796
|
+
kind: "startsWith",
|
|
797
|
+
name,
|
|
798
|
+
value
|
|
799
|
+
});
|
|
800
|
+
}
|
|
801
|
+
/**
|
|
802
|
+
* The attribute is set, with any type.
|
|
803
|
+
*
|
|
804
|
+
* @throws {InvalidAttributeNameError} If the name is outside the attribute name grammar.
|
|
805
|
+
* @throws {InvalidPredicateError} If given a system attribute — every entity has all of them, so
|
|
806
|
+
* the test would be a constant — or a `$` name that is not one.
|
|
807
|
+
*
|
|
808
|
+
* @example
|
|
809
|
+
* exists("reviewedBy") // EXISTS(reviewedBy)
|
|
810
|
+
* not(exists("closedAt")) // NOT EXISTS(closedAt)
|
|
811
|
+
*/
|
|
812
|
+
function exists(name) {
|
|
813
|
+
if (resolveReference(name) !== void 0) throw new InvalidPredicateError(`"${name}" is a system attribute, so it is set on every entity and EXISTS(${name}) is always true.`, name);
|
|
814
|
+
return node({
|
|
815
|
+
kind: "exists",
|
|
816
|
+
name
|
|
817
|
+
});
|
|
818
|
+
}
|
|
819
|
+
/**
|
|
820
|
+
* The attribute is set with exactly this type, whatever its value.
|
|
821
|
+
*
|
|
822
|
+
* Useful when the same name is written with different types across entities — a comparison already
|
|
823
|
+
* asserts the type, so this is for asking about the type alone.
|
|
824
|
+
*
|
|
825
|
+
* @param tag - The type tag: `bool`, `i32`, `u64`, `u256`, `dec`, `bytes32`, `str`, `addr` or
|
|
826
|
+
* `key` — any type but `bytes`, which is never indexed.
|
|
827
|
+
* @throws {InvalidAttributeNameError} If the name is outside the attribute name grammar.
|
|
828
|
+
* @throws {InvalidPredicateError} If the tag is not an indexable type, the name is a system
|
|
829
|
+
* attribute (whose type is fixed), or the name starts with `$` but is not a system attribute.
|
|
830
|
+
*
|
|
831
|
+
* @example
|
|
832
|
+
* hasType("age", "i32") // TYPEOF(age) = i32
|
|
833
|
+
*/
|
|
834
|
+
function hasType(name, tag) {
|
|
835
|
+
const system = resolveReference(name);
|
|
836
|
+
if (system !== void 0) throw new InvalidPredicateError(`"${name}" is always a ${system} — a system attribute has a fixed type, so TYPEOF(${name}) is a constant.`, name);
|
|
837
|
+
if (!isIndexableTag(tag)) throw new InvalidPredicateError(`"${tag}" is not an indexable attribute type. Use one of bool, i32, u64, u256, dec, bytes32, str, addr, key.`, name);
|
|
838
|
+
return node({
|
|
839
|
+
kind: "typeOf",
|
|
840
|
+
name,
|
|
841
|
+
tag
|
|
842
|
+
});
|
|
843
|
+
}
|
|
844
|
+
/**
|
|
845
|
+
* The complement of an expression, evaluated against the set of live entities — unlike `!=`, it
|
|
846
|
+
* matches entities that never set the attribute at all.
|
|
847
|
+
*
|
|
848
|
+
* @example
|
|
849
|
+
* not(exists("closedAt")) // NOT EXISTS(closedAt)
|
|
850
|
+
* not(and(eq("a", 1), eq("b", 2))) // NOT (a = i32(1) AND b = i32(2))
|
|
851
|
+
*/
|
|
852
|
+
function not(expression) {
|
|
853
|
+
return node({
|
|
854
|
+
kind: "not",
|
|
855
|
+
expression
|
|
856
|
+
});
|
|
857
|
+
}
|
|
858
|
+
function and(...expressions) {
|
|
859
|
+
return combine("and", expressions.flat());
|
|
860
|
+
}
|
|
861
|
+
function or(...expressions) {
|
|
862
|
+
return combine("or", expressions.flat());
|
|
863
|
+
}
|
|
864
|
+
function combine(kind, expressions) {
|
|
865
|
+
if (expressions.length === 0) throw new InvalidPredicateError(`${kind}() needs at least one expression. An empty ${kind === "and" ? "conjunction" : "disjunction"} has no query-language spelling — leave the ${kind}() out. A query still needs at least one filter, so to match broadly narrow by owner or creator, or read through a named schema, whose tag counts as one.`);
|
|
866
|
+
const only = expressions[0];
|
|
867
|
+
if (expressions.length === 1 && only !== void 0) return only;
|
|
868
|
+
return node(kind === "and" ? {
|
|
869
|
+
kind: "and",
|
|
870
|
+
expressions: Object.freeze(expressions)
|
|
871
|
+
} : {
|
|
872
|
+
kind: "or",
|
|
873
|
+
expressions: Object.freeze(expressions)
|
|
874
|
+
});
|
|
875
|
+
}
|
|
876
|
+
/**
|
|
877
|
+
* Checks every attribute an expression names against a schema — the runtime half of the check
|
|
878
|
+
* {@link SchemaRef} makes at compile time, for the callers the type system does not reach.
|
|
879
|
+
*
|
|
880
|
+
* @throws {InvalidPredicateError} If a predicate names something that is not a column of the
|
|
881
|
+
* schema, a column that is not indexed, or compares a column against a value of another type.
|
|
882
|
+
*/
|
|
883
|
+
function checkAgainstSchema(expression, schema) {
|
|
884
|
+
switch (expression.kind) {
|
|
885
|
+
case "not":
|
|
886
|
+
checkAgainstSchema(expression.expression, schema);
|
|
887
|
+
return;
|
|
888
|
+
case "and":
|
|
889
|
+
case "or":
|
|
890
|
+
for (const sub of expression.expressions) checkAgainstSchema(sub, schema);
|
|
891
|
+
return;
|
|
892
|
+
}
|
|
893
|
+
const { name } = expression;
|
|
894
|
+
if (name.startsWith("$")) return;
|
|
895
|
+
const column = require_schema.lookupColumn(schema, name);
|
|
896
|
+
if (column === void 0) throw new InvalidPredicateError(`"${name}" is not a column of the schema, so a predicate on it can only match entities written some other way. Its columns are ${require_schema.describeColumns(schema)}.`, name);
|
|
897
|
+
if (!column.isIndexed) {
|
|
898
|
+
if (column.type === "bytes") throw new InvalidPredicateError(`"${name}" is a bytes column, and bytes are stored but never indexed, so it cannot be filtered on. Index a str, bytes32 or key derived from it instead, or select it and filter the results in JavaScript.`, name);
|
|
899
|
+
throw new InvalidPredicateError(`"${name}" is not indexed, and only indexed attributes can be filtered on. Declare it ${column.type}().indexed() — entities written before that are indexed by their next patch of it — or select it and filter the results in JavaScript.`, name);
|
|
900
|
+
}
|
|
901
|
+
const asserted = expression.kind === "comparison" || expression.kind === "startsWith" ? expression.value.type : expression.kind === "between" ? expression.low.type : expression.kind === "typeOf" ? expression.tag : void 0;
|
|
902
|
+
if (asserted !== void 0 && asserted !== column.type) throw new InvalidPredicateError(`"${name}" is a ${column.type} column, but the predicate asserts a ${asserted}, so it could never match. Write ${column.type}(...) around the value.`, name);
|
|
903
|
+
}
|
|
904
|
+
/**
|
|
905
|
+
* Binding strength, tightest last — `NOT` binds tighter than `AND`, which binds tighter than `OR`.
|
|
906
|
+
* A leaf never needs parentheses, so it sits above them all.
|
|
907
|
+
*/
|
|
908
|
+
const PRECEDENCE = {
|
|
909
|
+
or: 1,
|
|
910
|
+
and: 2,
|
|
911
|
+
not: 3,
|
|
912
|
+
leaf: 4
|
|
913
|
+
};
|
|
914
|
+
function precedenceOf(expression) {
|
|
915
|
+
switch (expression.kind) {
|
|
916
|
+
case "or": return PRECEDENCE.or;
|
|
917
|
+
case "and": return PRECEDENCE.and;
|
|
918
|
+
case "not": return PRECEDENCE.not;
|
|
919
|
+
default: return PRECEDENCE.leaf;
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
/** Renders a subexpression, parenthesising it only where the precedence would otherwise change it. */
|
|
923
|
+
function nested(expression, minimum) {
|
|
924
|
+
const rendered = render(expression);
|
|
925
|
+
return precedenceOf(expression) < minimum ? `(${rendered})` : rendered;
|
|
926
|
+
}
|
|
927
|
+
/**
|
|
928
|
+
* Renders an expression as the query string the node parses. Equivalent to `String(expression)`.
|
|
929
|
+
*
|
|
930
|
+
* Literals are always written tagged, even where the language would infer them: an untagged number
|
|
931
|
+
* defaults to `i32`, but spelling it `i32(10)` keeps the exact-type assertion visible in the query
|
|
932
|
+
* that actually went over the wire.
|
|
933
|
+
*/
|
|
934
|
+
function render(expression) {
|
|
935
|
+
switch (expression.kind) {
|
|
936
|
+
case "comparison": return `${expression.name} ${expression.operator} ${renderLiteral(expression.value)}`;
|
|
937
|
+
case "between": return `${expression.name} BETWEEN ${renderLiteral(expression.low)} AND ${renderLiteral(expression.high)}`;
|
|
938
|
+
case "startsWith": return `${expression.name} STARTSWITH ${renderLiteral(expression.value)}`;
|
|
939
|
+
case "exists": return `EXISTS(${expression.name})`;
|
|
940
|
+
case "typeOf": return `TYPEOF(${expression.name}) = ${expression.tag}`;
|
|
941
|
+
case "not": return `NOT ${nested(expression.expression, PRECEDENCE.not)}`;
|
|
942
|
+
case "and": return expression.expressions.map((sub) => nested(sub, PRECEDENCE.and)).join(" AND ");
|
|
943
|
+
case "or": return expression.expressions.map((sub) => nested(sub, PRECEDENCE.or)).join(" OR ");
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
/** Renders a typed value as its query literal. `bool` is the one type written bare. */
|
|
947
|
+
function renderLiteral(value) {
|
|
948
|
+
switch (value.type) {
|
|
949
|
+
case "bool": return value.value ? "true" : "false";
|
|
950
|
+
case "i32": return `i32(${value.value})`;
|
|
951
|
+
case "u64": return `u64(${value.value})`;
|
|
952
|
+
case "u256": return `u256(${value.value})`;
|
|
953
|
+
case "dec": return `dec(${value.value})`;
|
|
954
|
+
case "bytes32": return `bytes32(${value.value})`;
|
|
955
|
+
case "key": return `key(${value.value})`;
|
|
956
|
+
case "addr": return `addr(${value.value})`;
|
|
957
|
+
case "str": return `str('${value.value.replaceAll("'", "''")}')`;
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
//#endregion
|
|
962
|
+
//#region src/query/builderBase.ts
|
|
963
|
+
/**
|
|
964
|
+
* What every query builder shares: the client, the filters — `where`, `ownedBy`, `createdBy` and a
|
|
965
|
+
* named schema's tag — and the block to read at. `client.select()` and `client.count()` build on it,
|
|
966
|
+
* so the same filters always send the same query string, whichever of them runs it.
|
|
967
|
+
*
|
|
968
|
+
* Not constructed directly: create a builder with `client.select(...)` or `client.count()`.
|
|
969
|
+
*
|
|
970
|
+
* @typeParam TSchema - The schema named with `from()`, if any, which `where` is checked against.
|
|
971
|
+
*/
|
|
972
|
+
var QueryBuilderBase = class {
|
|
973
|
+
_client;
|
|
974
|
+
_schema;
|
|
975
|
+
_filters = [];
|
|
976
|
+
_ownedBy;
|
|
977
|
+
_createdBy;
|
|
978
|
+
_atBlock;
|
|
979
|
+
constructor(client) {
|
|
980
|
+
this._client = client;
|
|
981
|
+
}
|
|
982
|
+
/**
|
|
983
|
+
* This builder's state in a new builder of the same class, re-typed by the caller. Every field is
|
|
984
|
+
* carried over, so one a subclass adds is not dropped by `from()`; the filter list, which is
|
|
985
|
+
* mutated in place, is copied rather than shared.
|
|
986
|
+
*
|
|
987
|
+
* A copy, not `this` re-typed: a builder typed for one schema must never be the same object as
|
|
988
|
+
* one typed for another, or `base.from(Posts)` and `base.from(Users)` would share their state.
|
|
989
|
+
*/
|
|
990
|
+
copy() {
|
|
991
|
+
const copy = Object.create(Object.getPrototypeOf(this));
|
|
992
|
+
return Object.assign(copy, this, { _filters: [...this._filters] });
|
|
993
|
+
}
|
|
994
|
+
/**
|
|
995
|
+
* Adds filters. Everything passed here, and across repeated calls, must hold.
|
|
996
|
+
*
|
|
997
|
+
* After `from()`, every attribute a filter names must be an indexed column of the schema,
|
|
998
|
+
* compared against a value of its type — anything else is a compile error.
|
|
999
|
+
*
|
|
1000
|
+
* @param expressions - The expressions, as separate arguments or one array.
|
|
1001
|
+
*
|
|
1002
|
+
* @example
|
|
1003
|
+
* builder.where(eq("category", "docs"))
|
|
1004
|
+
* builder.where(gte("level", i32(10)), lt("level", i32(20)))
|
|
1005
|
+
* builder.where(or(eq("status", "open"), not(exists("closedAt"))))
|
|
1006
|
+
*/
|
|
1007
|
+
where(...expressions) {
|
|
1008
|
+
this._filters.push(...expressions.flat());
|
|
1009
|
+
return this;
|
|
1010
|
+
}
|
|
1011
|
+
/**
|
|
1012
|
+
* Restricts the query to entities this account owns — shorthand for
|
|
1013
|
+
* `where(eq("$owner", addr(owner)))`. Calling it again replaces the filter.
|
|
1014
|
+
*
|
|
1015
|
+
* @example
|
|
1016
|
+
* builder.ownedBy("0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045")
|
|
1017
|
+
*/
|
|
1018
|
+
ownedBy(owner) {
|
|
1019
|
+
this._ownedBy = eq("$owner", require_schema.addr(owner));
|
|
1020
|
+
return this;
|
|
1021
|
+
}
|
|
1022
|
+
/**
|
|
1023
|
+
* Restricts the query to entities this account created — shorthand for
|
|
1024
|
+
* `where(eq("$creator", addr(creator)))`. Calling it again replaces the filter.
|
|
1025
|
+
*
|
|
1026
|
+
* The creator never changes; the owner does, so these differ after a transfer.
|
|
1027
|
+
*/
|
|
1028
|
+
createdBy(creator) {
|
|
1029
|
+
this._createdBy = eq("$creator", require_schema.addr(creator));
|
|
1030
|
+
return this;
|
|
1031
|
+
}
|
|
1032
|
+
/**
|
|
1033
|
+
* Reads the state as of a given block rather than the head. The block must be within the range
|
|
1034
|
+
* the node retains.
|
|
1035
|
+
*
|
|
1036
|
+
* @example
|
|
1037
|
+
* builder.atBlock(1_297_000n)
|
|
1038
|
+
*/
|
|
1039
|
+
atBlock(block) {
|
|
1040
|
+
this._atBlock = block;
|
|
1041
|
+
return this;
|
|
1042
|
+
}
|
|
1043
|
+
/**
|
|
1044
|
+
* The query string this builder will send — the same text the node parses: its filters,
|
|
1045
|
+
* `ownedBy` and `createdBy`, and a named schema's tag, ANDed.
|
|
1046
|
+
*
|
|
1047
|
+
* @throws {InvalidPredicateError} If no filter has been added, or — after `from()` — a filter
|
|
1048
|
+
* does not fit the schema. A named schema's own tag filter counts as a filter.
|
|
1049
|
+
*/
|
|
1050
|
+
toString() {
|
|
1051
|
+
const filters = [...this._filters];
|
|
1052
|
+
if (this._ownedBy) filters.push(this._ownedBy);
|
|
1053
|
+
if (this._createdBy) filters.push(this._createdBy);
|
|
1054
|
+
const schema = this._schema;
|
|
1055
|
+
if (schema !== void 0) {
|
|
1056
|
+
for (const filter of filters) checkAgainstSchema(filter, schema);
|
|
1057
|
+
if (schema.tag !== void 0) filters.unshift(eq(require_schema.SCHEMA_ATTRIBUTE, schema.tag));
|
|
1058
|
+
}
|
|
1059
|
+
if (filters.length === 0) throw new InvalidPredicateError("A query needs at least one filter — the language has no spelling for 'match every entity'. Add a where(...) predicate, or narrow by ownedBy(...) / createdBy(...).");
|
|
1060
|
+
return and(filters).toString();
|
|
1061
|
+
}
|
|
1062
|
+
};
|
|
1063
|
+
|
|
1064
|
+
//#endregion
|
|
1065
|
+
//#region src/query/countBuilder.ts
|
|
1066
|
+
/**
|
|
1067
|
+
* Builds and runs a count: how many entities a query matches, without fetching any of them.
|
|
1068
|
+
*
|
|
1069
|
+
* It filters exactly as `client.select()` does — {@link from}, `where`, `ownedBy`, `createdBy` and
|
|
1070
|
+
* `atBlock` are the same methods, from the same base — but has nothing to select and no pages, so
|
|
1071
|
+
* {@link fetch} resolves to a `bigint`. Create one with `client.count()`.
|
|
1072
|
+
*
|
|
1073
|
+
* To show the total next to a page of rows, use `select(...).withCount()` instead, which counts in
|
|
1074
|
+
* the same request.
|
|
1075
|
+
*
|
|
1076
|
+
* @typeParam TSchema - The schema named with {@link from}, if any.
|
|
1077
|
+
*
|
|
1078
|
+
* @example
|
|
1079
|
+
* const total = await client.count().from(Posts).ownedBy(account.address).fetch()
|
|
1080
|
+
*/
|
|
1081
|
+
var CountQueryBuilder = class extends QueryBuilderBase {
|
|
1082
|
+
/**
|
|
1083
|
+
* Counts through a schema: `where` is checked against its indexed columns, and a named schema
|
|
1084
|
+
* counts only entities tagged with its name — so it needs no other filter.
|
|
1085
|
+
*
|
|
1086
|
+
* Returns a new builder, carrying whatever this one already holds; this one is left as it was.
|
|
1087
|
+
*
|
|
1088
|
+
* @throws {SchemaDefinitionError} If `schema` was not built with `defineSchema`.
|
|
1089
|
+
*
|
|
1090
|
+
* @example
|
|
1091
|
+
* client.count().from(Posts) // every post
|
|
1092
|
+
*/
|
|
1093
|
+
from(schema) {
|
|
1094
|
+
if (!require_schema.isSchema(schema)) throw new require_schema.SchemaDefinitionError("from() takes a schema built with defineSchema(...)");
|
|
1095
|
+
const typed = this.copy();
|
|
1096
|
+
typed._schema = schema;
|
|
1097
|
+
return typed;
|
|
1098
|
+
}
|
|
1099
|
+
/**
|
|
1100
|
+
* Runs the count.
|
|
1101
|
+
*
|
|
1102
|
+
* Counts every entity the filters match at the block, whether or not it fits the schema — what
|
|
1103
|
+
* `select().from(schema)` leaves out of its pages is still counted.
|
|
1104
|
+
*
|
|
1105
|
+
* @throws {InvalidPredicateError} If no filter has been added, or a filter does not fit the
|
|
1106
|
+
* schema.
|
|
1107
|
+
* @throws {QueryError} If the node rejects the query.
|
|
1108
|
+
* @throws If the node returns no count — it predates the count projection.
|
|
1109
|
+
*/
|
|
1110
|
+
async fetch() {
|
|
1111
|
+
const response = await runQuery(this._client, {
|
|
1112
|
+
query: this.toString(),
|
|
1113
|
+
select: {},
|
|
1114
|
+
atBlock: this._atBlock,
|
|
1115
|
+
count: true
|
|
1116
|
+
});
|
|
1117
|
+
if (response.count === void 0) throw new Error("The node returned no count. It may predate the count projection.");
|
|
1118
|
+
return response.count;
|
|
1119
|
+
}
|
|
1120
|
+
};
|
|
1121
|
+
|
|
1122
|
+
//#endregion
|
|
1123
|
+
//#region src/query/queryResult.ts
|
|
1124
|
+
/**
|
|
1125
|
+
* One page of query results.
|
|
1126
|
+
*
|
|
1127
|
+
* A page is immutable: {@link next} returns the following page rather than mutating this one, so a
|
|
1128
|
+
* page that has been read stays readable.
|
|
1129
|
+
*
|
|
1130
|
+
* @typeParam TEntity - The shape of each row, inferred from the selection by `client.select()`.
|
|
1131
|
+
* @typeParam TCount - `bigint` when the query was built with `withCount()`, and `undefined` when it
|
|
1132
|
+
* was not.
|
|
1133
|
+
*
|
|
1134
|
+
* @example
|
|
1135
|
+
* let page = await client.select({ $key: true }).where(eq("category", "docs")).limit(100).fetch()
|
|
1136
|
+
* while (page.hasNextPage()) {
|
|
1137
|
+
* page = await page.next()
|
|
1138
|
+
* }
|
|
1139
|
+
*/
|
|
1140
|
+
var QueryResult = class {
|
|
1141
|
+
/** The rows on this page — flat, as the selection lays them out. */
|
|
1142
|
+
entities;
|
|
1143
|
+
/**
|
|
1144
|
+
* The block this page was read at.
|
|
1145
|
+
*
|
|
1146
|
+
* Every page of one walk reads the same block — the cursor is bound to it — so results cannot
|
|
1147
|
+
* shift under a paginating reader.
|
|
1148
|
+
*/
|
|
1149
|
+
blockNumber;
|
|
1150
|
+
/** The cursor for the next page, or `undefined` when this was the last one. */
|
|
1151
|
+
cursor;
|
|
1152
|
+
/**
|
|
1153
|
+
* How many matching entities this page left out because they did not fit the schema — always
|
|
1154
|
+
* `0` without a schema, or with `.from(schema, { onMismatch: "throw" })`.
|
|
1155
|
+
*
|
|
1156
|
+
* Counted per page, not per walk: `entities.length + skipped` is what the node returned for this
|
|
1157
|
+
* page, so a short or empty page with a cursor is explained here rather than mistaken for the
|
|
1158
|
+
* end of the results.
|
|
1159
|
+
*/
|
|
1160
|
+
skipped;
|
|
1161
|
+
/**
|
|
1162
|
+
* How many entities the query matches in all, regardless of the page size — a `bigint` when the
|
|
1163
|
+
* query was built with `withCount()`, and `undefined` otherwise. Every page of one walk reads the
|
|
1164
|
+
* same block, so every page carries the same count. It includes entities left out for not fitting
|
|
1165
|
+
* the schema, which {@link skipped} reports per page.
|
|
1166
|
+
*/
|
|
1167
|
+
count;
|
|
1168
|
+
fetchPage;
|
|
1169
|
+
constructor(entities, blockNumber, cursor, skipped, count, fetchPage$1) {
|
|
1170
|
+
this.entities = entities;
|
|
1171
|
+
this.blockNumber = blockNumber;
|
|
1172
|
+
this.cursor = cursor;
|
|
1173
|
+
this.skipped = skipped;
|
|
1174
|
+
this.count = count;
|
|
1175
|
+
this.fetchPage = fetchPage$1;
|
|
1176
|
+
}
|
|
1177
|
+
/**
|
|
1178
|
+
* Whether another page follows.
|
|
1179
|
+
*
|
|
1180
|
+
* The node omits the cursor once nothing remains, so this is an answer rather than the guess a
|
|
1181
|
+
* short final page would be — a full last page is reported correctly.
|
|
1182
|
+
*/
|
|
1183
|
+
hasNextPage() {
|
|
1184
|
+
return this.cursor !== void 0;
|
|
1185
|
+
}
|
|
1186
|
+
/**
|
|
1187
|
+
* Fetches the next page.
|
|
1188
|
+
*
|
|
1189
|
+
* @throws {NoMoreResultsError} If this was the last page — check {@link hasNextPage} first.
|
|
1190
|
+
* @throws {QueryError} If the node rejects the request; `kind === "cursor"` means the cursor
|
|
1191
|
+
* expired and the walk has to start again.
|
|
1192
|
+
*/
|
|
1193
|
+
async next() {
|
|
1194
|
+
if (this.cursor === void 0) throw new NoMoreResultsError();
|
|
1195
|
+
return this.fetchPage(this.cursor);
|
|
1196
|
+
}
|
|
1197
|
+
};
|
|
1198
|
+
|
|
1199
|
+
//#endregion
|
|
1200
|
+
//#region src/query/queryBuilder.ts
|
|
1201
|
+
/**
|
|
1202
|
+
* Builds and runs a query.
|
|
1203
|
+
*
|
|
1204
|
+
* The selection is fixed when the builder is created and the filter is added by chaining, so the
|
|
1205
|
+
* result type is known before a single predicate is written. Create one with `client.select(...)`
|
|
1206
|
+
* rather than constructing it directly — that is what infers the result shape from the selection.
|
|
1207
|
+
*
|
|
1208
|
+
* The query returns flat rows: the selected entity fields and attributes side by side. Naming a
|
|
1209
|
+
* schema with {@link from} changes what the selection means for nothing but the selected
|
|
1210
|
+
* attributes — they come back typed and checked as the schema declares — and has `where` checked
|
|
1211
|
+
* against it, so that filtering on a column the schema does not index, or comparing one against the
|
|
1212
|
+
* wrong type, is a compile error. A named schema also restricts the query to entities tagged with
|
|
1213
|
+
* its name, and leaves that tag out of the rows' `$attributes` and `$attributeSchema`.
|
|
1214
|
+
*
|
|
1215
|
+
* Every filter added with {@link where}, {@link ownedBy} and {@link createdBy} must hold; use
|
|
1216
|
+
* {@link or} for alternatives inside one call.
|
|
1217
|
+
*
|
|
1218
|
+
* @typeParam TSelection - The selection, inferred by `client.select()`.
|
|
1219
|
+
* @typeParam TSchema - The schema named with {@link from}, if any.
|
|
1220
|
+
*
|
|
1221
|
+
* @example
|
|
1222
|
+
* import { createPublicClient } from "@arkiv-network/sdk"
|
|
1223
|
+
* import { dec } from "@arkiv-network/sdk/schema"
|
|
1224
|
+
* import { tiramisu } from "@arkiv-network/sdk/chains"
|
|
1225
|
+
* import { and, eq, gt } from "@arkiv-network/sdk/query"
|
|
1226
|
+
* import { http } from "viem"
|
|
1227
|
+
*
|
|
1228
|
+
* const client = createPublicClient({ chain: tiramisu, transport: http() })
|
|
1229
|
+
*
|
|
1230
|
+
* const page = await client
|
|
1231
|
+
* .select({ $key: true, author: true, title: true })
|
|
1232
|
+
* .from(Posts)
|
|
1233
|
+
* .where(and(eq("category", "docs"), gt("score", dec("4.0"))))
|
|
1234
|
+
* .limit(20)
|
|
1235
|
+
* .fetch()
|
|
1236
|
+
*
|
|
1237
|
+
* for (const row of page.entities) console.log(row.$key, row.title.value)
|
|
1238
|
+
*/
|
|
1239
|
+
var SelectQueryBuilder = class extends QueryBuilderBase {
|
|
1240
|
+
_selection;
|
|
1241
|
+
_projection;
|
|
1242
|
+
_onMismatch = "skip";
|
|
1243
|
+
_limit;
|
|
1244
|
+
_cursor;
|
|
1245
|
+
_withCount = false;
|
|
1246
|
+
/**
|
|
1247
|
+
* @throws If the selection is not `"*"` or an object, asks for nothing, or names an attribute
|
|
1248
|
+
* outside the name grammar.
|
|
1249
|
+
*/
|
|
1250
|
+
constructor(client, selection) {
|
|
1251
|
+
super(client);
|
|
1252
|
+
this._selection = snapshotSelection(selection);
|
|
1253
|
+
this._projection = projectSelection(this._selection);
|
|
1254
|
+
}
|
|
1255
|
+
/**
|
|
1256
|
+
* Runs the query through a schema. The selection keeps its meaning and the rows their shape;
|
|
1257
|
+
* each selected attribute becomes a column — typed as the schema declares, and checked against
|
|
1258
|
+
* it — and `where` is checked against the schema's indexed columns.
|
|
1259
|
+
*
|
|
1260
|
+
* A named schema also filters: only entities tagged with its name match, so a query through it
|
|
1261
|
+
* sees only entities written through it, and needs no other filter. The tag is how the query finds
|
|
1262
|
+
* them rather than one of their attributes, so rows leave it out of `$attributes` and
|
|
1263
|
+
* `$attributeSchema`. An unnamed schema types the query without filtering it — `where` alone
|
|
1264
|
+
* decides which entities match.
|
|
1265
|
+
*
|
|
1266
|
+
* A matching entity that does not fit — a selected required column missing or of another type,
|
|
1267
|
+
* or, for a named schema, a tag that does not match — is left out of the page, or fails the fetch
|
|
1268
|
+
* with a {@link SchemaMismatchError} with `{ onMismatch: "throw" }`. Columns the selection leaves
|
|
1269
|
+
* out are not checked.
|
|
1270
|
+
*
|
|
1271
|
+
* Returns a new builder, carrying whatever this one already holds; this one is left as it was.
|
|
1272
|
+
*
|
|
1273
|
+
* @param schema - The schema, built with `defineSchema`.
|
|
1274
|
+
* @param options - How to treat entities that do not fit. {@link FromOptions}
|
|
1275
|
+
* @throws {SchemaDefinitionError} If `schema` was not built with `defineSchema`.
|
|
1276
|
+
* @throws If the selection names something that is neither an entity field nor a column.
|
|
1277
|
+
*
|
|
1278
|
+
* @example
|
|
1279
|
+
* client.select({ $key: true, title: true }).from(Posts) // rows typed { $key; title }
|
|
1280
|
+
* client.select().from(Posts) // every field and every column
|
|
1281
|
+
* client.select().from(Posts, { onMismatch: "throw" }) // fail on entities that do not fit
|
|
1282
|
+
*/
|
|
1283
|
+
from(schema, options = {}) {
|
|
1284
|
+
if (!require_schema.isSchema(schema)) throw new require_schema.SchemaDefinitionError("from() takes a schema built with defineSchema(...)");
|
|
1285
|
+
const { onMismatch = "skip" } = options;
|
|
1286
|
+
if (onMismatch !== "throw" && onMismatch !== "skip") throw new TypeError(`onMismatch must be "throw" or "skip", got ${String(onMismatch)}`);
|
|
1287
|
+
const projection = projectSelection(this._selection, schema);
|
|
1288
|
+
const typed = this.copy();
|
|
1289
|
+
typed._schema = schema;
|
|
1290
|
+
typed._projection = projection;
|
|
1291
|
+
typed._onMismatch = onMismatch;
|
|
1292
|
+
return typed;
|
|
1293
|
+
}
|
|
1294
|
+
/**
|
|
1295
|
+
* Sets the page size, up to the node maximum of 100. Without it the node picks the page size.
|
|
1296
|
+
*
|
|
1297
|
+
* @example
|
|
1298
|
+
* builder.limit(100)
|
|
1299
|
+
*/
|
|
1300
|
+
limit(limit) {
|
|
1301
|
+
this._limit = limit;
|
|
1302
|
+
return this;
|
|
1303
|
+
}
|
|
1304
|
+
/**
|
|
1305
|
+
* Starts from a cursor returned by an earlier page.
|
|
1306
|
+
*
|
|
1307
|
+
* Pagination normally goes through {@link QueryResult.next}, which carries the cursor for you;
|
|
1308
|
+
* this is for resuming a walk in a later process. A cursor is bound to the query, block and
|
|
1309
|
+
* selection it came from, so it must be used with an identically-built query.
|
|
1310
|
+
*/
|
|
1311
|
+
cursor(cursor) {
|
|
1312
|
+
this._cursor = cursor;
|
|
1313
|
+
return this;
|
|
1314
|
+
}
|
|
1315
|
+
/**
|
|
1316
|
+
* Also counts every entity the query matches, regardless of the page size. Each page then carries
|
|
1317
|
+
* the total as {@link QueryResult.count}, typed `bigint`, so a paginated list can show it without
|
|
1318
|
+
* a second query. To count without fetching rows, use `client.count()` instead.
|
|
1319
|
+
*
|
|
1320
|
+
* The node counts what the filters match. Through a schema, that includes entities the pages
|
|
1321
|
+
* leave out for not fitting it, so the rows of a whole walk can add up to less than the count:
|
|
1322
|
+
* the difference is the sum of every page's {@link QueryResult.skipped}.
|
|
1323
|
+
*
|
|
1324
|
+
* @throws If, when fetched, the node returns no count — it predates the count projection.
|
|
1325
|
+
*
|
|
1326
|
+
* Returns a new builder, carrying whatever this one already holds; this one is left as it was.
|
|
1327
|
+
*
|
|
1328
|
+
* @example
|
|
1329
|
+
* const page = await builder.withCount().limit(20).fetch()
|
|
1330
|
+
* page.count // every match, not just these 20
|
|
1331
|
+
*/
|
|
1332
|
+
withCount() {
|
|
1333
|
+
const counted = this.copy();
|
|
1334
|
+
counted._withCount = true;
|
|
1335
|
+
return counted;
|
|
1336
|
+
}
|
|
1337
|
+
/**
|
|
1338
|
+
* Runs the query and returns the first page.
|
|
1339
|
+
*
|
|
1340
|
+
* @throws {InvalidPredicateError} If no filter has been added, or a filter does not fit the
|
|
1341
|
+
* schema.
|
|
1342
|
+
* @throws If the {@link limit} is not an integer from 1 to 100.
|
|
1343
|
+
* @throws {SchemaMismatchError} If, through a schema, a matching entity does not fit the selected
|
|
1344
|
+
* columns or a named schema's tag, and `from()` was given `{ onMismatch: "throw" }`.
|
|
1345
|
+
* @throws {QueryError} If the node rejects the query.
|
|
1346
|
+
*
|
|
1347
|
+
* @example
|
|
1348
|
+
* const page = await client.select({ $key: true }).where(eq("category", "docs")).fetch()
|
|
1349
|
+
* page.entities // this page
|
|
1350
|
+
* await page.next() // the next one
|
|
1351
|
+
*/
|
|
1352
|
+
fetch() {
|
|
1353
|
+
return this.fetchFirstPage();
|
|
1354
|
+
}
|
|
1355
|
+
async fetchFirstPage() {
|
|
1356
|
+
const projection = this._projection;
|
|
1357
|
+
const request = Object.freeze({
|
|
1358
|
+
query: this.toString(),
|
|
1359
|
+
select: projection.select,
|
|
1360
|
+
limit: this._limit,
|
|
1361
|
+
atBlock: this._atBlock,
|
|
1362
|
+
count: this._withCount
|
|
1363
|
+
});
|
|
1364
|
+
const skip = this._onMismatch === "skip";
|
|
1365
|
+
const decode = ((entity) => {
|
|
1366
|
+
try {
|
|
1367
|
+
return toRow(entity, projection);
|
|
1368
|
+
} catch (error) {
|
|
1369
|
+
if (skip && error instanceof require_schema.SchemaMismatchError) return SKIPPED;
|
|
1370
|
+
throw error;
|
|
1371
|
+
}
|
|
1372
|
+
});
|
|
1373
|
+
return fetchPage(this._client, request, this._cursor, decode);
|
|
1374
|
+
}
|
|
1375
|
+
/**
|
|
1376
|
+
* Walks every page, yielding one row at a time.
|
|
1377
|
+
*
|
|
1378
|
+
* @example
|
|
1379
|
+
* for await (const row of client.select({ $key: true }).where(eq("category", "docs"))) {
|
|
1380
|
+
* console.log(row.$key)
|
|
1381
|
+
* }
|
|
1382
|
+
*/
|
|
1383
|
+
async *[Symbol.asyncIterator]() {
|
|
1384
|
+
let page = await this.fetchFirstPage();
|
|
1385
|
+
while (page) {
|
|
1386
|
+
yield* page.entities;
|
|
1387
|
+
page = page.hasNextPage() ? await page.next() : void 0;
|
|
1388
|
+
}
|
|
1389
|
+
}
|
|
1390
|
+
};
|
|
1391
|
+
/** What `decode` returns for an entity `{ onMismatch: "skip" }` leaves out of its page. */
|
|
1392
|
+
const SKIPPED = Symbol("skipped");
|
|
1393
|
+
/**
|
|
1394
|
+
* Fetches one page and wires up the next.
|
|
1395
|
+
*
|
|
1396
|
+
* Free-standing rather than a method: a page holds this closure for its lifetime, and closing over
|
|
1397
|
+
* a frozen request keeps the builder — and everything it references — out of that reference.
|
|
1398
|
+
*/
|
|
1399
|
+
async function fetchPage(client, request, cursor, decode) {
|
|
1400
|
+
const response = await runQuery(client, {
|
|
1401
|
+
...request,
|
|
1402
|
+
cursor
|
|
1403
|
+
});
|
|
1404
|
+
if (request.count && response.count === void 0) throw new Error("The node returned no count. It may predate the count projection.");
|
|
1405
|
+
const results = [];
|
|
1406
|
+
let skipped = 0;
|
|
1407
|
+
for (const entity of response.entities) {
|
|
1408
|
+
const result = decode(entity);
|
|
1409
|
+
if (result === SKIPPED) skipped++;
|
|
1410
|
+
else results.push(result);
|
|
1411
|
+
}
|
|
1412
|
+
return new QueryResult(results, response.blockNumber, response.cursor, skipped, response.count, (next) => fetchPage(client, request, next, decode));
|
|
1413
|
+
}
|
|
1414
|
+
|
|
1415
|
+
//#endregion
|
|
1416
|
+
Object.defineProperty(exports, 'CountQueryBuilder', {
|
|
1417
|
+
enumerable: true,
|
|
1418
|
+
get: function () {
|
|
1419
|
+
return CountQueryBuilder;
|
|
1420
|
+
}
|
|
1421
|
+
});
|
|
1422
|
+
Object.defineProperty(exports, 'EmptyPatchError', {
|
|
1423
|
+
enumerable: true,
|
|
1424
|
+
get: function () {
|
|
1425
|
+
return EmptyPatchError;
|
|
1426
|
+
}
|
|
1427
|
+
});
|
|
1428
|
+
Object.defineProperty(exports, 'EntityMutationError', {
|
|
1429
|
+
enumerable: true,
|
|
1430
|
+
get: function () {
|
|
1431
|
+
return EntityMutationError;
|
|
1432
|
+
}
|
|
1433
|
+
});
|
|
1434
|
+
Object.defineProperty(exports, 'InvalidPredicateError', {
|
|
1435
|
+
enumerable: true,
|
|
1436
|
+
get: function () {
|
|
1437
|
+
return InvalidPredicateError;
|
|
1438
|
+
}
|
|
1439
|
+
});
|
|
1440
|
+
Object.defineProperty(exports, 'MAX_LIMIT', {
|
|
1441
|
+
enumerable: true,
|
|
1442
|
+
get: function () {
|
|
1443
|
+
return MAX_LIMIT;
|
|
1444
|
+
}
|
|
1445
|
+
});
|
|
1446
|
+
Object.defineProperty(exports, 'NoEntityFoundError', {
|
|
1447
|
+
enumerable: true,
|
|
1448
|
+
get: function () {
|
|
1449
|
+
return NoEntityFoundError;
|
|
1450
|
+
}
|
|
1451
|
+
});
|
|
1452
|
+
Object.defineProperty(exports, 'NoMoreResultsError', {
|
|
1453
|
+
enumerable: true,
|
|
1454
|
+
get: function () {
|
|
1455
|
+
return NoMoreResultsError;
|
|
1456
|
+
}
|
|
1457
|
+
});
|
|
1458
|
+
Object.defineProperty(exports, 'QueryError', {
|
|
1459
|
+
enumerable: true,
|
|
1460
|
+
get: function () {
|
|
1461
|
+
return QueryError;
|
|
1462
|
+
}
|
|
1463
|
+
});
|
|
1464
|
+
Object.defineProperty(exports, 'QueryResult', {
|
|
1465
|
+
enumerable: true,
|
|
1466
|
+
get: function () {
|
|
1467
|
+
return QueryResult;
|
|
1468
|
+
}
|
|
1469
|
+
});
|
|
1470
|
+
Object.defineProperty(exports, 'SYSTEM_FIELDS', {
|
|
1471
|
+
enumerable: true,
|
|
1472
|
+
get: function () {
|
|
1473
|
+
return SYSTEM_FIELDS;
|
|
1474
|
+
}
|
|
1475
|
+
});
|
|
1476
|
+
Object.defineProperty(exports, 'SelectQueryBuilder', {
|
|
1477
|
+
enumerable: true,
|
|
1478
|
+
get: function () {
|
|
1479
|
+
return SelectQueryBuilder;
|
|
1480
|
+
}
|
|
1481
|
+
});
|
|
1482
|
+
Object.defineProperty(exports, 'UnsupportedOperatorError', {
|
|
1483
|
+
enumerable: true,
|
|
1484
|
+
get: function () {
|
|
1485
|
+
return UnsupportedOperatorError;
|
|
1486
|
+
}
|
|
1487
|
+
});
|
|
1488
|
+
Object.defineProperty(exports, 'and', {
|
|
1489
|
+
enumerable: true,
|
|
1490
|
+
get: function () {
|
|
1491
|
+
return and;
|
|
1492
|
+
}
|
|
1493
|
+
});
|
|
1494
|
+
Object.defineProperty(exports, 'between', {
|
|
1495
|
+
enumerable: true,
|
|
1496
|
+
get: function () {
|
|
1497
|
+
return between;
|
|
1498
|
+
}
|
|
1499
|
+
});
|
|
1500
|
+
Object.defineProperty(exports, 'checkAgainstSchema', {
|
|
1501
|
+
enumerable: true,
|
|
1502
|
+
get: function () {
|
|
1503
|
+
return checkAgainstSchema;
|
|
1504
|
+
}
|
|
1505
|
+
});
|
|
1506
|
+
Object.defineProperty(exports, 'decodeCreationFlags', {
|
|
1507
|
+
enumerable: true,
|
|
1508
|
+
get: function () {
|
|
1509
|
+
return decodeCreationFlags;
|
|
1510
|
+
}
|
|
1511
|
+
});
|
|
1512
|
+
Object.defineProperty(exports, 'encodeCreationFlags', {
|
|
1513
|
+
enumerable: true,
|
|
1514
|
+
get: function () {
|
|
1515
|
+
return encodeCreationFlags;
|
|
1516
|
+
}
|
|
1517
|
+
});
|
|
1518
|
+
Object.defineProperty(exports, 'entityFromRpcResult', {
|
|
1519
|
+
enumerable: true,
|
|
1520
|
+
get: function () {
|
|
1521
|
+
return entityFromRpcResult;
|
|
1522
|
+
}
|
|
1523
|
+
});
|
|
1524
|
+
Object.defineProperty(exports, 'eq', {
|
|
1525
|
+
enumerable: true,
|
|
1526
|
+
get: function () {
|
|
1527
|
+
return eq;
|
|
1528
|
+
}
|
|
1529
|
+
});
|
|
1530
|
+
Object.defineProperty(exports, 'exists', {
|
|
1531
|
+
enumerable: true,
|
|
1532
|
+
get: function () {
|
|
1533
|
+
return exists;
|
|
1534
|
+
}
|
|
1535
|
+
});
|
|
1536
|
+
Object.defineProperty(exports, 'getLogger', {
|
|
1537
|
+
enumerable: true,
|
|
1538
|
+
get: function () {
|
|
1539
|
+
return getLogger;
|
|
1540
|
+
}
|
|
1541
|
+
});
|
|
1542
|
+
Object.defineProperty(exports, 'gt', {
|
|
1543
|
+
enumerable: true,
|
|
1544
|
+
get: function () {
|
|
1545
|
+
return gt;
|
|
1546
|
+
}
|
|
1547
|
+
});
|
|
1548
|
+
Object.defineProperty(exports, 'gte', {
|
|
1549
|
+
enumerable: true,
|
|
1550
|
+
get: function () {
|
|
1551
|
+
return gte;
|
|
1552
|
+
}
|
|
1553
|
+
});
|
|
1554
|
+
Object.defineProperty(exports, 'hasType', {
|
|
1555
|
+
enumerable: true,
|
|
1556
|
+
get: function () {
|
|
1557
|
+
return hasType;
|
|
1558
|
+
}
|
|
1559
|
+
});
|
|
1560
|
+
Object.defineProperty(exports, 'lt', {
|
|
1561
|
+
enumerable: true,
|
|
1562
|
+
get: function () {
|
|
1563
|
+
return lt;
|
|
1564
|
+
}
|
|
1565
|
+
});
|
|
1566
|
+
Object.defineProperty(exports, 'lte', {
|
|
1567
|
+
enumerable: true,
|
|
1568
|
+
get: function () {
|
|
1569
|
+
return lte;
|
|
1570
|
+
}
|
|
1571
|
+
});
|
|
1572
|
+
Object.defineProperty(exports, 'ne', {
|
|
1573
|
+
enumerable: true,
|
|
1574
|
+
get: function () {
|
|
1575
|
+
return ne;
|
|
1576
|
+
}
|
|
1577
|
+
});
|
|
1578
|
+
Object.defineProperty(exports, 'not', {
|
|
1579
|
+
enumerable: true,
|
|
1580
|
+
get: function () {
|
|
1581
|
+
return not;
|
|
1582
|
+
}
|
|
1583
|
+
});
|
|
1584
|
+
Object.defineProperty(exports, 'or', {
|
|
1585
|
+
enumerable: true,
|
|
1586
|
+
get: function () {
|
|
1587
|
+
return or;
|
|
1588
|
+
}
|
|
1589
|
+
});
|
|
1590
|
+
Object.defineProperty(exports, 'projectSelection', {
|
|
1591
|
+
enumerable: true,
|
|
1592
|
+
get: function () {
|
|
1593
|
+
return projectSelection;
|
|
1594
|
+
}
|
|
1595
|
+
});
|
|
1596
|
+
Object.defineProperty(exports, 'render', {
|
|
1597
|
+
enumerable: true,
|
|
1598
|
+
get: function () {
|
|
1599
|
+
return render;
|
|
1600
|
+
}
|
|
1601
|
+
});
|
|
1602
|
+
Object.defineProperty(exports, 'runQuery', {
|
|
1603
|
+
enumerable: true,
|
|
1604
|
+
get: function () {
|
|
1605
|
+
return runQuery;
|
|
1606
|
+
}
|
|
1607
|
+
});
|
|
1608
|
+
Object.defineProperty(exports, 'startsWith', {
|
|
1609
|
+
enumerable: true,
|
|
1610
|
+
get: function () {
|
|
1611
|
+
return startsWith;
|
|
1612
|
+
}
|
|
1613
|
+
});
|
|
1614
|
+
Object.defineProperty(exports, 'toRow', {
|
|
1615
|
+
enumerable: true,
|
|
1616
|
+
get: function () {
|
|
1617
|
+
return toRow;
|
|
1618
|
+
}
|
|
1619
|
+
});
|
|
1620
|
+
Object.defineProperty(exports, 'toRpcSelect', {
|
|
1621
|
+
enumerable: true,
|
|
1622
|
+
get: function () {
|
|
1623
|
+
return toRpcSelect;
|
|
1624
|
+
}
|
|
1625
|
+
});
|