@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
package/src/query/expression.ts
CHANGED
|
@@ -1,65 +1,107 @@
|
|
|
1
1
|
import {
|
|
2
|
-
type
|
|
2
|
+
type ColumnsOf,
|
|
3
|
+
describeColumns,
|
|
4
|
+
type IndexableTypeTag,
|
|
5
|
+
type IndexableValue,
|
|
6
|
+
type IndexedColumnName,
|
|
7
|
+
lookupColumn,
|
|
8
|
+
type Schema,
|
|
3
9
|
type StrValue,
|
|
4
10
|
str,
|
|
11
|
+
type TagOf,
|
|
5
12
|
type TypeTag,
|
|
6
13
|
toValue,
|
|
7
|
-
type UserTypeTag,
|
|
8
|
-
type ValueInput,
|
|
9
14
|
validateAttributeName,
|
|
10
|
-
} from "../
|
|
15
|
+
} from "../schema"
|
|
11
16
|
// Internal seam: the typeId table is the protocol's, not part of the package's public surface.
|
|
12
|
-
import { TYPE_IDS } from "../
|
|
17
|
+
import { isIndexable, TYPE_IDS } from "../schema/types"
|
|
13
18
|
import { InvalidPredicateError, UnsupportedOperatorError } from "./errors"
|
|
14
19
|
|
|
15
20
|
/**
|
|
16
|
-
* The comparison operators the query language defines.
|
|
17
|
-
* `i32`, `u64`, `u256`
|
|
21
|
+
* The comparison operators the query language defines. Every indexable type accepts all of them;
|
|
22
|
+
* `<` `<=` `>` `>=` compare in index order — numerically for `i32`, `u64`, `u256` and `dec`,
|
|
23
|
+
* `false < true` for `bool`, and by unsigned bytes for `str`, `bytes32`, `addr` and `key`.
|
|
18
24
|
*/
|
|
19
25
|
export type ComparisonOperator = "=" | "!=" | "<" | "<=" | ">" | ">="
|
|
20
26
|
|
|
27
|
+
/**
|
|
28
|
+
* A value a predicate can compare against: a value of any indexable type, or a bare boolean,
|
|
29
|
+
* number, bigint or string. `bytes` is excluded — it is never indexed, so never filterable.
|
|
30
|
+
*/
|
|
31
|
+
export type PredicateValue = IndexableValue | boolean | number | bigint | string
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* `between`'s check that both bounds have one type — `unknown` when they do, and otherwise an
|
|
35
|
+
* object whose one property is named after the problem. No value has that property, so the
|
|
36
|
+
* compiler reports the sentence as the property it is missing.
|
|
37
|
+
*/
|
|
38
|
+
type SameTypeCheck<L, H> = [TagOf<L>, TagOf<H>] extends [TagOf<H>, TagOf<L>]
|
|
39
|
+
? unknown
|
|
40
|
+
: { [K in `BETWEEN bounds must have one type, got ${TagOf<L>} and ${TagOf<H>}`]: never }
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* The attribute a predicate names, and the type it asserts — `never` when it asserts none, as
|
|
44
|
+
* `EXISTS` does. Carried by an {@link Expression} at the type level only, so a query against a
|
|
45
|
+
* schema can be checked at compile time: every name must be an indexed column, and every type the
|
|
46
|
+
* column's.
|
|
47
|
+
*/
|
|
48
|
+
export type AttributeRef = { readonly name: string; readonly type: IndexableTypeTag }
|
|
49
|
+
|
|
50
|
+
declare const refs: unique symbol
|
|
51
|
+
|
|
52
|
+
/** The type-level record of the attributes an expression names. Never present at runtime. */
|
|
53
|
+
type Refs<R extends AttributeRef> = { readonly [refs]?: R }
|
|
54
|
+
|
|
21
55
|
/** A comparison between an attribute and a typed literal. */
|
|
22
|
-
export
|
|
56
|
+
export interface ComparisonNode<R extends AttributeRef = AttributeRef> extends Refs<R> {
|
|
23
57
|
readonly kind: "comparison"
|
|
24
58
|
readonly name: string
|
|
25
59
|
readonly operator: ComparisonOperator
|
|
26
|
-
readonly value:
|
|
60
|
+
readonly value: IndexableValue
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** An inclusive range test, `name BETWEEN low AND high`. Both bounds have one type. */
|
|
64
|
+
export interface BetweenNode<R extends AttributeRef = AttributeRef> extends Refs<R> {
|
|
65
|
+
readonly kind: "between"
|
|
66
|
+
readonly name: string
|
|
67
|
+
readonly low: IndexableValue
|
|
68
|
+
readonly high: IndexableValue
|
|
27
69
|
}
|
|
28
70
|
|
|
29
71
|
/** A `STARTSWITH` prefix test, the language's only pattern operator. */
|
|
30
|
-
export
|
|
72
|
+
export interface StartsWithNode<R extends AttributeRef = AttributeRef> extends Refs<R> {
|
|
31
73
|
readonly kind: "startsWith"
|
|
32
74
|
readonly name: string
|
|
33
75
|
readonly value: StrValue
|
|
34
76
|
}
|
|
35
77
|
|
|
36
78
|
/** An `EXISTS(name)` test — true when the attribute is set with any type. */
|
|
37
|
-
export
|
|
79
|
+
export interface ExistsNode<R extends AttributeRef = AttributeRef> extends Refs<R> {
|
|
38
80
|
readonly kind: "exists"
|
|
39
81
|
readonly name: string
|
|
40
82
|
}
|
|
41
83
|
|
|
42
84
|
/** A `TYPEOF(name) = tag` test. */
|
|
43
|
-
export
|
|
85
|
+
export interface TypeOfNode<R extends AttributeRef = AttributeRef> extends Refs<R> {
|
|
44
86
|
readonly kind: "typeOf"
|
|
45
87
|
readonly name: string
|
|
46
|
-
readonly tag:
|
|
88
|
+
readonly tag: IndexableTypeTag
|
|
47
89
|
}
|
|
48
90
|
|
|
49
91
|
/** The complement of an expression, evaluated against the active-entity set. */
|
|
50
|
-
export
|
|
92
|
+
export interface NotNode<R extends AttributeRef = AttributeRef> extends Refs<R> {
|
|
51
93
|
readonly kind: "not"
|
|
52
94
|
readonly expression: Expression
|
|
53
95
|
}
|
|
54
96
|
|
|
55
97
|
/** A conjunction. */
|
|
56
|
-
export
|
|
98
|
+
export interface AndNode<R extends AttributeRef = AttributeRef> extends Refs<R> {
|
|
57
99
|
readonly kind: "and"
|
|
58
100
|
readonly expressions: readonly Expression[]
|
|
59
101
|
}
|
|
60
102
|
|
|
61
103
|
/** A disjunction. */
|
|
62
|
-
export
|
|
104
|
+
export interface OrNode<R extends AttributeRef = AttributeRef> extends Refs<R> {
|
|
63
105
|
readonly kind: "or"
|
|
64
106
|
readonly expressions: readonly Expression[]
|
|
65
107
|
}
|
|
@@ -68,24 +110,38 @@ export type OrNode = {
|
|
|
68
110
|
* A query expression: an immutable tree built by the combinators below, whose `toString()` is the
|
|
69
111
|
* exact query string sent over the wire.
|
|
70
112
|
*
|
|
113
|
+
* @typeParam R - The attributes the expression names, as {@link AttributeRef}s — inferred by the
|
|
114
|
+
* combinators, and checked against the schema by `select(...).from(schema).where(...)`.
|
|
115
|
+
*
|
|
71
116
|
* @example
|
|
72
117
|
* const q = and(gte("level", i32(10)), or(eq("status", str("open")), not(exists("closedAt"))))
|
|
73
118
|
* String(q) // level >= i32(10) AND (status = str('open') OR NOT EXISTS(closedAt))
|
|
74
119
|
*/
|
|
75
|
-
export type Expression =
|
|
76
|
-
| ComparisonNode
|
|
77
|
-
|
|
|
78
|
-
|
|
|
79
|
-
|
|
|
80
|
-
|
|
|
81
|
-
|
|
|
82
|
-
|
|
|
120
|
+
export type Expression<R extends AttributeRef = AttributeRef> =
|
|
121
|
+
| ComparisonNode<R>
|
|
122
|
+
| BetweenNode<R>
|
|
123
|
+
| StartsWithNode<R>
|
|
124
|
+
| ExistsNode<R>
|
|
125
|
+
| TypeOfNode<R>
|
|
126
|
+
| NotNode<R>
|
|
127
|
+
| AndNode<R>
|
|
128
|
+
| OrNode<R>
|
|
129
|
+
|
|
130
|
+
/** The attributes an expression names — the `R` of an {@link Expression}. */
|
|
131
|
+
export type RefsOf<E> = E extends Refs<infer R> ? Exclude<R, undefined> : never
|
|
132
|
+
|
|
133
|
+
/** The reference a comparison against `V` makes to `N`. */
|
|
134
|
+
type ValueRef<N extends string, V> = {
|
|
135
|
+
readonly name: N
|
|
136
|
+
readonly type: TagOf<V> & IndexableTypeTag
|
|
137
|
+
}
|
|
83
138
|
|
|
84
139
|
/**
|
|
85
140
|
* The system attributes a query may filter on, and the type each one carries.
|
|
86
141
|
*
|
|
87
|
-
* Every other system attribute (`$createdAt`, `$updatedAt`, `$creationFlags
|
|
88
|
-
*
|
|
142
|
+
* Every other system attribute (`$createdAt`, `$updatedAt`, `$creationFlags`) is returned in
|
|
143
|
+
* query results — selected and read back as `createdAt`, `updatedAt` and `creationFlags` — but is
|
|
144
|
+
* not indexed, so it cannot appear in a predicate.
|
|
89
145
|
*/
|
|
90
146
|
const QUERYABLE_SYSTEM_ATTRIBUTES = {
|
|
91
147
|
$key: "key",
|
|
@@ -96,24 +152,30 @@ const QUERYABLE_SYSTEM_ATTRIBUTES = {
|
|
|
96
152
|
|
|
97
153
|
type SystemAttributeName = keyof typeof QUERYABLE_SYSTEM_ATTRIBUTES
|
|
98
154
|
|
|
155
|
+
/** The references a filter on a system attribute makes — each with its fixed type. */
|
|
156
|
+
export type SystemAttributeRef = {
|
|
157
|
+
[N in SystemAttributeName]: {
|
|
158
|
+
readonly name: N
|
|
159
|
+
readonly type: (typeof QUERYABLE_SYSTEM_ATTRIBUTES)[N]
|
|
160
|
+
}
|
|
161
|
+
}[SystemAttributeName]
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* The references a filter through schema `S` may make: each indexed column with its declared
|
|
165
|
+
* type, and the queryable system attributes with theirs. An {@link Expression} naming anything else
|
|
166
|
+
* is a compile error in `select(...).from(S).where(...)`.
|
|
167
|
+
*/
|
|
168
|
+
export type SchemaRef<S extends Schema> =
|
|
169
|
+
| {
|
|
170
|
+
[K in IndexedColumnName<S>]: {
|
|
171
|
+
readonly name: K
|
|
172
|
+
readonly type: ColumnsOf<S>[K]["type"] & IndexableTypeTag
|
|
173
|
+
}
|
|
174
|
+
}[IndexedColumnName<S>]
|
|
175
|
+
| SystemAttributeRef
|
|
176
|
+
|
|
99
177
|
/** System attributes a query can ask for but not filter on — they carry no index. */
|
|
100
|
-
const RESULT_ONLY_SYSTEM_ATTRIBUTES = [
|
|
101
|
-
"$createdAt",
|
|
102
|
-
"$updatedAt",
|
|
103
|
-
"$creationFlags",
|
|
104
|
-
"$contentType",
|
|
105
|
-
"$payload",
|
|
106
|
-
]
|
|
107
|
-
|
|
108
|
-
/** The types that carry an ordered index, and so accept `<` `<=` `>` `>=`. */
|
|
109
|
-
const ORDERED_TAGS: ReadonlySet<TypeTag> = new Set<TypeTag>(["i32", "u64", "u256", "dec"])
|
|
110
|
-
|
|
111
|
-
const RANGE_OPERATORS: ReadonlySet<ComparisonOperator> = new Set<ComparisonOperator>([
|
|
112
|
-
"<",
|
|
113
|
-
"<=",
|
|
114
|
-
">",
|
|
115
|
-
">=",
|
|
116
|
-
])
|
|
178
|
+
const RESULT_ONLY_SYSTEM_ATTRIBUTES = ["$createdAt", "$updatedAt", "$creationFlags"]
|
|
117
179
|
|
|
118
180
|
/**
|
|
119
181
|
* Checks an attribute reference, returning its fixed type when it names a system attribute.
|
|
@@ -130,9 +192,11 @@ function resolveReference(name: string): TypeTag | undefined {
|
|
|
130
192
|
if (system !== undefined) return system
|
|
131
193
|
|
|
132
194
|
if (RESULT_ONLY_SYSTEM_ATTRIBUTES.includes(name)) {
|
|
195
|
+
// Selected, and read back, as the entity field of the same name without the `$`.
|
|
196
|
+
const field = name.slice(1)
|
|
133
197
|
throw new InvalidPredicateError(
|
|
134
198
|
`"${name}" is returned in query results but carries no index, so it cannot be filtered ` +
|
|
135
|
-
|
|
199
|
+
`on. Select it as \`${field}\` and filter the results in JavaScript instead.`,
|
|
136
200
|
name,
|
|
137
201
|
)
|
|
138
202
|
}
|
|
@@ -145,15 +209,14 @@ function resolveReference(name: string): TypeTag | undefined {
|
|
|
145
209
|
}
|
|
146
210
|
|
|
147
211
|
/**
|
|
148
|
-
* Whether a string names a type
|
|
149
|
-
* is neither settable nor queryable.
|
|
212
|
+
* Whether a string names a type that can be indexed — every type but `bytes`.
|
|
150
213
|
*
|
|
151
|
-
* Takes a `string` rather than
|
|
152
|
-
* as much, and this is the runtime check for the code that has no types.
|
|
214
|
+
* Takes a `string` rather than an `IndexableTypeTag` on purpose — the callers' static types
|
|
215
|
+
* already say as much, and this is the runtime check for the code that has no types.
|
|
153
216
|
*/
|
|
154
|
-
function
|
|
217
|
+
function isIndexableTag(tag: string): tag is IndexableTypeTag {
|
|
155
218
|
// `Object.hasOwn` rather than `in`, which would also accept every Object.prototype key.
|
|
156
|
-
return Object.hasOwn(TYPE_IDS, tag) && tag
|
|
219
|
+
return Object.hasOwn(TYPE_IDS, tag) && isIndexable(tag as TypeTag)
|
|
157
220
|
}
|
|
158
221
|
|
|
159
222
|
/** Freezes a node and gives it a (non-enumerable, so still `toEqual`-able) rendering. */
|
|
@@ -165,25 +228,39 @@ function node<T extends Expression>(value: T): T {
|
|
|
165
228
|
return Object.freeze(value)
|
|
166
229
|
}
|
|
167
230
|
|
|
168
|
-
|
|
169
|
-
|
|
231
|
+
/**
|
|
232
|
+
* Resolves a value a predicate compares `name` against, and checks it can be: not `bytes`, which is
|
|
233
|
+
* never indexed, and of the fixed type when `name` is a system attribute.
|
|
234
|
+
*/
|
|
235
|
+
function predicateValue(
|
|
236
|
+
name: string,
|
|
237
|
+
system: TypeTag | undefined,
|
|
238
|
+
input: PredicateValue,
|
|
239
|
+
): IndexableValue {
|
|
170
240
|
const value = toValue(input, name)
|
|
171
|
-
|
|
172
|
-
if (system !== undefined && value.type !== system) {
|
|
241
|
+
if (value.type === "bytes") {
|
|
173
242
|
throw new InvalidPredicateError(
|
|
174
|
-
`"${name}" is a
|
|
175
|
-
|
|
243
|
+
`"${name}" is compared against a bytes value, and bytes are stored but never indexed, so ` +
|
|
244
|
+
"they cannot be filtered on. Index a str, bytes32 or key derived from them instead.",
|
|
176
245
|
name,
|
|
177
246
|
)
|
|
178
247
|
}
|
|
179
|
-
if (
|
|
180
|
-
throw new
|
|
248
|
+
if (system !== undefined && value.type !== system) {
|
|
249
|
+
throw new InvalidPredicateError(
|
|
250
|
+
`"${name}" is a ${system}, but the value is a ${value.type}. ` +
|
|
251
|
+
`System attributes have a fixed type — write ${system}(...) around the value.`,
|
|
181
252
|
name,
|
|
182
|
-
operator,
|
|
183
|
-
value.type,
|
|
184
|
-
`Only i32, u64, u256 and dec carry an ordered index; ${value.type} is equality-only.`,
|
|
185
253
|
)
|
|
186
254
|
}
|
|
255
|
+
return value
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function comparison<R extends AttributeRef>(
|
|
259
|
+
name: string,
|
|
260
|
+
operator: ComparisonOperator,
|
|
261
|
+
input: PredicateValue,
|
|
262
|
+
): ComparisonNode<R> {
|
|
263
|
+
const value = predicateValue(name, resolveReference(name), input)
|
|
187
264
|
return node({ kind: "comparison", name, operator, value })
|
|
188
265
|
}
|
|
189
266
|
|
|
@@ -193,15 +270,19 @@ function comparison(name: string, operator: ComparisonOperator, input: ValueInpu
|
|
|
193
270
|
* Bare booleans, numbers, bigints and strings take the same default types they take when written
|
|
194
271
|
* (`number` is an `i32`, `bigint` a `u256`, `string` a `str`); anything else names its type.
|
|
195
272
|
*
|
|
196
|
-
* @throws {
|
|
197
|
-
*
|
|
273
|
+
* @throws {InvalidAttributeNameError} If the name is outside the attribute name grammar.
|
|
274
|
+
* @throws {InvalidPredicateError} If the name starts with `$` but is not a queryable system
|
|
275
|
+
* attribute, a system attribute is compared against the wrong type, or the value is `bytes`.
|
|
198
276
|
*
|
|
199
277
|
* @example
|
|
200
278
|
* eq("status", str("open")) // status = str('open')
|
|
201
279
|
* eq("flagged", true) // flagged = true
|
|
202
280
|
* eq("$owner", addr(account)) // $owner = addr(0xd8dA…)
|
|
203
281
|
*/
|
|
204
|
-
export function eq
|
|
282
|
+
export function eq<N extends string, V extends PredicateValue>(
|
|
283
|
+
name: N,
|
|
284
|
+
value: V,
|
|
285
|
+
): ComparisonNode<ValueRef<N, V>> {
|
|
205
286
|
return comparison(name, "=", value)
|
|
206
287
|
}
|
|
207
288
|
|
|
@@ -211,63 +292,122 @@ export function eq(name: string, value: ValueInput): ComparisonNode {
|
|
|
211
292
|
* This is typed value-negation, not the complement: an entity that never set the attribute, or set
|
|
212
293
|
* it with another type, does not match. Use `not(eq(...))` for the full complement.
|
|
213
294
|
*
|
|
295
|
+
* @throws {InvalidAttributeNameError} If the name is outside the attribute name grammar.
|
|
296
|
+
* @throws {InvalidPredicateError} If the name starts with `$` but is not a queryable system
|
|
297
|
+
* attribute, a system attribute is compared against the wrong type, or the value is `bytes`.
|
|
298
|
+
*
|
|
214
299
|
* @example
|
|
215
300
|
* ne("status", str("closed")) // status != str('closed')
|
|
216
301
|
* not(eq("status", str("closed"))) // NOT status = str('closed') — also matches entities with no status
|
|
217
302
|
*/
|
|
218
|
-
export function ne
|
|
303
|
+
export function ne<N extends string, V extends PredicateValue>(
|
|
304
|
+
name: N,
|
|
305
|
+
value: V,
|
|
306
|
+
): ComparisonNode<ValueRef<N, V>> {
|
|
219
307
|
return comparison(name, "!=", value)
|
|
220
308
|
}
|
|
221
309
|
|
|
222
310
|
/**
|
|
223
|
-
* Attribute is greater than value
|
|
311
|
+
* Attribute is greater than value, in index order — see {@link ComparisonOperator}.
|
|
224
312
|
*
|
|
225
|
-
* @throws {
|
|
313
|
+
* @throws {InvalidAttributeNameError} If the name is outside the attribute name grammar.
|
|
314
|
+
* @throws {InvalidPredicateError} If the name starts with `$` but is not a queryable system
|
|
315
|
+
* attribute, a system attribute is compared against the wrong type, or the value is `bytes`.
|
|
226
316
|
*
|
|
227
317
|
* @example
|
|
228
318
|
* gt("level", i32(10)) // level > i32(10)
|
|
229
319
|
* gt("$expiresAt", u64(1_200_000n)) // $expiresAt > u64(1200000)
|
|
230
320
|
*/
|
|
231
|
-
export function gt
|
|
321
|
+
export function gt<N extends string, V extends PredicateValue>(
|
|
322
|
+
name: N,
|
|
323
|
+
value: V,
|
|
324
|
+
): ComparisonNode<ValueRef<N, V>> {
|
|
232
325
|
return comparison(name, ">", value)
|
|
233
326
|
}
|
|
234
327
|
|
|
235
328
|
/**
|
|
236
|
-
* Attribute is greater than or equal to value
|
|
329
|
+
* Attribute is greater than or equal to value, in index order — see {@link ComparisonOperator}.
|
|
237
330
|
*
|
|
238
|
-
* @throws {
|
|
331
|
+
* @throws {InvalidAttributeNameError} If the name is outside the attribute name grammar.
|
|
332
|
+
* @throws {InvalidPredicateError} If the name starts with `$` but is not a queryable system
|
|
333
|
+
* attribute, a system attribute is compared against the wrong type, or the value is `bytes`.
|
|
239
334
|
*
|
|
240
335
|
* @example
|
|
241
336
|
* gte("score", dec("3.5")) // score >= dec(3.5)
|
|
242
337
|
*/
|
|
243
|
-
export function gte
|
|
338
|
+
export function gte<N extends string, V extends PredicateValue>(
|
|
339
|
+
name: N,
|
|
340
|
+
value: V,
|
|
341
|
+
): ComparisonNode<ValueRef<N, V>> {
|
|
244
342
|
return comparison(name, ">=", value)
|
|
245
343
|
}
|
|
246
344
|
|
|
247
345
|
/**
|
|
248
|
-
* Attribute is less than value
|
|
346
|
+
* Attribute is less than value, in index order — see {@link ComparisonOperator}.
|
|
249
347
|
*
|
|
250
|
-
* @throws {
|
|
348
|
+
* @throws {InvalidAttributeNameError} If the name is outside the attribute name grammar.
|
|
349
|
+
* @throws {InvalidPredicateError} If the name starts with `$` but is not a queryable system
|
|
350
|
+
* attribute, a system attribute is compared against the wrong type, or the value is `bytes`.
|
|
251
351
|
*
|
|
252
352
|
* @example
|
|
253
353
|
* lt("level", i32(10)) // level < i32(10)
|
|
254
354
|
*/
|
|
255
|
-
export function lt
|
|
355
|
+
export function lt<N extends string, V extends PredicateValue>(
|
|
356
|
+
name: N,
|
|
357
|
+
value: V,
|
|
358
|
+
): ComparisonNode<ValueRef<N, V>> {
|
|
256
359
|
return comparison(name, "<", value)
|
|
257
360
|
}
|
|
258
361
|
|
|
259
362
|
/**
|
|
260
|
-
* Attribute is less than or equal to value
|
|
363
|
+
* Attribute is less than or equal to value, in index order — see {@link ComparisonOperator}.
|
|
261
364
|
*
|
|
262
|
-
* @throws {
|
|
365
|
+
* @throws {InvalidAttributeNameError} If the name is outside the attribute name grammar.
|
|
366
|
+
* @throws {InvalidPredicateError} If the name starts with `$` but is not a queryable system
|
|
367
|
+
* attribute, a system attribute is compared against the wrong type, or the value is `bytes`.
|
|
263
368
|
*
|
|
264
369
|
* @example
|
|
265
370
|
* lte("score", dec("5")) // score <= dec(5)
|
|
266
371
|
*/
|
|
267
|
-
export function lte
|
|
372
|
+
export function lte<N extends string, V extends PredicateValue>(
|
|
373
|
+
name: N,
|
|
374
|
+
value: V,
|
|
375
|
+
): ComparisonNode<ValueRef<N, V>> {
|
|
268
376
|
return comparison(name, "<=", value)
|
|
269
377
|
}
|
|
270
378
|
|
|
379
|
+
/**
|
|
380
|
+
* Attribute lies between two values, **inclusive** at both ends: it matches exactly what
|
|
381
|
+
* `and(gte(name, low), lte(name, high))` matches, in one predicate. Both bounds must have one type,
|
|
382
|
+
* and a `low` above `high` matches nothing.
|
|
383
|
+
*
|
|
384
|
+
* @throws {InvalidAttributeNameError} If the name is outside the attribute name grammar.
|
|
385
|
+
* @throws {InvalidPredicateError} If the bounds have different types, the name starts with `$` but
|
|
386
|
+
* is not a queryable system attribute, a system attribute is compared against the wrong type, or a
|
|
387
|
+
* bound is `bytes`.
|
|
388
|
+
*
|
|
389
|
+
* @example
|
|
390
|
+
* between("score", dec("3.5"), dec("5")) // score BETWEEN dec(3.5) AND dec(5)
|
|
391
|
+
* between("$expiresAt", u64(100n), u64(200n))
|
|
392
|
+
*/
|
|
393
|
+
export function between<N extends string, L extends PredicateValue, H extends PredicateValue>(
|
|
394
|
+
name: N,
|
|
395
|
+
low: L,
|
|
396
|
+
high: H & SameTypeCheck<L, H>,
|
|
397
|
+
): BetweenNode<ValueRef<N, L>> {
|
|
398
|
+
const system = resolveReference(name)
|
|
399
|
+
const lowValue = predicateValue(name, system, low)
|
|
400
|
+
const highValue = predicateValue(name, system, high)
|
|
401
|
+
if (lowValue.type !== highValue.type) {
|
|
402
|
+
throw new InvalidPredicateError(
|
|
403
|
+
`BETWEEN on "${name}" has a ${lowValue.type} and a ${highValue.type} bound. Both bounds ` +
|
|
404
|
+
"must have one type, the type the attribute is compared as.",
|
|
405
|
+
name,
|
|
406
|
+
)
|
|
407
|
+
}
|
|
408
|
+
return node({ kind: "between", name, low: lowValue, high: highValue })
|
|
409
|
+
}
|
|
410
|
+
|
|
271
411
|
/**
|
|
272
412
|
* Attribute is a `str` beginning with this prefix.
|
|
273
413
|
*
|
|
@@ -275,14 +415,19 @@ export function lte(name: string, value: ValueInput): ComparisonNode {
|
|
|
275
415
|
* `startsWith("name", "é")` matches only the same byte sequence, not another normal form of it.
|
|
276
416
|
*
|
|
277
417
|
* @param prefix - The prefix, as a bare string or a `str` value.
|
|
278
|
-
* @throws {
|
|
418
|
+
* @throws {InvalidAttributeNameError} If the name is outside the attribute name grammar.
|
|
419
|
+
* @throws {InvalidPredicateError} If the name starts with `$` but is not a queryable system
|
|
420
|
+
* attribute.
|
|
279
421
|
* @throws {UnsupportedOperatorError} If given a value that is not a `str`, or a system attribute
|
|
280
422
|
* (none of which is a `str`).
|
|
281
423
|
*
|
|
282
424
|
* @example
|
|
283
425
|
* startsWith("desc", "ab") // desc STARTSWITH str('ab')
|
|
284
426
|
*/
|
|
285
|
-
export function startsWith
|
|
427
|
+
export function startsWith<N extends string>(
|
|
428
|
+
name: N,
|
|
429
|
+
prefix: string | StrValue,
|
|
430
|
+
): StartsWithNode<{ readonly name: N; readonly type: "str" }> {
|
|
286
431
|
const system = resolveReference(name)
|
|
287
432
|
const value = typeof prefix === "string" ? str(prefix) : toValue(prefix, name)
|
|
288
433
|
if (value.type !== "str") {
|
|
@@ -307,14 +452,17 @@ export function startsWith(name: string, prefix: string | StrValue): StartsWithN
|
|
|
307
452
|
/**
|
|
308
453
|
* The attribute is set, with any type.
|
|
309
454
|
*
|
|
455
|
+
* @throws {InvalidAttributeNameError} If the name is outside the attribute name grammar.
|
|
310
456
|
* @throws {InvalidPredicateError} If given a system attribute — every entity has all of them, so
|
|
311
|
-
* the test would be a constant.
|
|
457
|
+
* the test would be a constant — or a `$` name that is not one.
|
|
312
458
|
*
|
|
313
459
|
* @example
|
|
314
460
|
* exists("reviewedBy") // EXISTS(reviewedBy)
|
|
315
461
|
* not(exists("closedAt")) // NOT EXISTS(closedAt)
|
|
316
462
|
*/
|
|
317
|
-
export function exists
|
|
463
|
+
export function exists<N extends string>(
|
|
464
|
+
name: N,
|
|
465
|
+
): ExistsNode<{ readonly name: N; readonly type: never }> {
|
|
318
466
|
if (resolveReference(name) !== undefined) {
|
|
319
467
|
throw new InvalidPredicateError(
|
|
320
468
|
`"${name}" is a system attribute, so it is set on every entity and EXISTS(${name}) is ` +
|
|
@@ -332,14 +480,18 @@ export function exists(name: string): ExistsNode {
|
|
|
332
480
|
* asserts the type, so this is for asking about the type alone.
|
|
333
481
|
*
|
|
334
482
|
* @param tag - The type tag: `bool`, `i32`, `u64`, `u256`, `dec`, `bytes32`, `str`, `addr` or
|
|
335
|
-
* `key
|
|
336
|
-
* @throws {
|
|
337
|
-
*
|
|
483
|
+
* `key` — any type but `bytes`, which is never indexed.
|
|
484
|
+
* @throws {InvalidAttributeNameError} If the name is outside the attribute name grammar.
|
|
485
|
+
* @throws {InvalidPredicateError} If the tag is not an indexable type, the name is a system
|
|
486
|
+
* attribute (whose type is fixed), or the name starts with `$` but is not a system attribute.
|
|
338
487
|
*
|
|
339
488
|
* @example
|
|
340
489
|
* hasType("age", "i32") // TYPEOF(age) = i32
|
|
341
490
|
*/
|
|
342
|
-
export function hasType
|
|
491
|
+
export function hasType<N extends string, T extends IndexableTypeTag>(
|
|
492
|
+
name: N,
|
|
493
|
+
tag: T,
|
|
494
|
+
): TypeOfNode<{ readonly name: N; readonly type: T }> {
|
|
343
495
|
const system = resolveReference(name)
|
|
344
496
|
if (system !== undefined) {
|
|
345
497
|
throw new InvalidPredicateError(
|
|
@@ -348,9 +500,9 @@ export function hasType(name: string, tag: UserTypeTag): TypeOfNode {
|
|
|
348
500
|
name,
|
|
349
501
|
)
|
|
350
502
|
}
|
|
351
|
-
if (!
|
|
503
|
+
if (!isIndexableTag(tag)) {
|
|
352
504
|
throw new InvalidPredicateError(
|
|
353
|
-
`"${tag}" is not
|
|
505
|
+
`"${tag}" is not an indexable attribute type. Use one of ` +
|
|
354
506
|
"bool, i32, u64, u256, dec, bytes32, str, addr, key.",
|
|
355
507
|
name,
|
|
356
508
|
)
|
|
@@ -366,7 +518,7 @@ export function hasType(name: string, tag: UserTypeTag): TypeOfNode {
|
|
|
366
518
|
* not(exists("closedAt")) // NOT EXISTS(closedAt)
|
|
367
519
|
* not(and(eq("a", 1), eq("b", 2))) // NOT (a = i32(1) AND b = i32(2))
|
|
368
520
|
*/
|
|
369
|
-
export function not(expression: Expression): NotNode {
|
|
521
|
+
export function not<R extends AttributeRef>(expression: Expression<R>): NotNode<R> {
|
|
370
522
|
return node({ kind: "not", expression })
|
|
371
523
|
}
|
|
372
524
|
|
|
@@ -383,8 +535,12 @@ export function not(expression: Expression): NotNode {
|
|
|
383
535
|
* and(gte("level", i32(10)), eq("status", "open"))
|
|
384
536
|
* and(filters) // an array works too
|
|
385
537
|
*/
|
|
386
|
-
export function and
|
|
387
|
-
|
|
538
|
+
export function and<const E extends readonly Expression[]>(
|
|
539
|
+
expressions: E,
|
|
540
|
+
): Expression<RefsOf<E[number]>>
|
|
541
|
+
export function and<const E extends readonly Expression[]>(
|
|
542
|
+
...expressions: E
|
|
543
|
+
): Expression<RefsOf<E[number]>>
|
|
388
544
|
export function and(...expressions: (Expression | readonly Expression[])[]): Expression {
|
|
389
545
|
return combine("and", expressions.flat())
|
|
390
546
|
}
|
|
@@ -400,8 +556,12 @@ export function and(...expressions: (Expression | readonly Expression[])[]): Exp
|
|
|
400
556
|
* @example
|
|
401
557
|
* or(eq("status", "open"), not(exists("closedAt")))
|
|
402
558
|
*/
|
|
403
|
-
export function or
|
|
404
|
-
|
|
559
|
+
export function or<const E extends readonly Expression[]>(
|
|
560
|
+
expressions: E,
|
|
561
|
+
): Expression<RefsOf<E[number]>>
|
|
562
|
+
export function or<const E extends readonly Expression[]>(
|
|
563
|
+
...expressions: E
|
|
564
|
+
): Expression<RefsOf<E[number]>>
|
|
405
565
|
export function or(...expressions: (Expression | readonly Expression[])[]): Expression {
|
|
406
566
|
return combine("or", expressions.flat())
|
|
407
567
|
}
|
|
@@ -410,7 +570,9 @@ function combine(kind: "and" | "or", expressions: Expression[]): Expression {
|
|
|
410
570
|
if (expressions.length === 0) {
|
|
411
571
|
throw new InvalidPredicateError(
|
|
412
572
|
`${kind}() needs at least one expression. An empty ${kind === "and" ? "conjunction" : "disjunction"} ` +
|
|
413
|
-
`has no query-language spelling — leave the ${kind}() out
|
|
573
|
+
`has no query-language spelling — leave the ${kind}() out. A query still needs at least one ` +
|
|
574
|
+
"filter, so to match broadly narrow by owner or creator, or read through a named schema, " +
|
|
575
|
+
"whose tag counts as one.",
|
|
414
576
|
)
|
|
415
577
|
}
|
|
416
578
|
// A one-element conjunction *is* its element; keeping the wrapper would only add parentheses.
|
|
@@ -423,6 +585,69 @@ function combine(kind: "and" | "or", expressions: Expression[]): Expression {
|
|
|
423
585
|
)
|
|
424
586
|
}
|
|
425
587
|
|
|
588
|
+
/**
|
|
589
|
+
* Checks every attribute an expression names against a schema — the runtime half of the check
|
|
590
|
+
* {@link SchemaRef} makes at compile time, for the callers the type system does not reach.
|
|
591
|
+
*
|
|
592
|
+
* @throws {InvalidPredicateError} If a predicate names something that is not a column of the
|
|
593
|
+
* schema, a column that is not indexed, or compares a column against a value of another type.
|
|
594
|
+
*/
|
|
595
|
+
export function checkAgainstSchema(expression: Expression, schema: Schema): void {
|
|
596
|
+
switch (expression.kind) {
|
|
597
|
+
case "not":
|
|
598
|
+
checkAgainstSchema(expression.expression, schema)
|
|
599
|
+
return
|
|
600
|
+
case "and":
|
|
601
|
+
case "or":
|
|
602
|
+
for (const sub of expression.expressions) checkAgainstSchema(sub, schema)
|
|
603
|
+
return
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
const { name } = expression
|
|
607
|
+
// System attributes were checked against their fixed types when the predicate was built.
|
|
608
|
+
if (name.startsWith("$")) return
|
|
609
|
+
|
|
610
|
+
const column = lookupColumn(schema, name)
|
|
611
|
+
if (column === undefined) {
|
|
612
|
+
throw new InvalidPredicateError(
|
|
613
|
+
`"${name}" is not a column of the schema, so a predicate on it can only match entities ` +
|
|
614
|
+
`written some other way. Its columns are ${describeColumns(schema)}.`,
|
|
615
|
+
name,
|
|
616
|
+
)
|
|
617
|
+
}
|
|
618
|
+
if (!column.isIndexed) {
|
|
619
|
+
if (column.type === "bytes") {
|
|
620
|
+
throw new InvalidPredicateError(
|
|
621
|
+
`"${name}" is a bytes column, and bytes are stored but never indexed, so it cannot be ` +
|
|
622
|
+
"filtered on. Index a str, bytes32 or key derived from it instead, or select it and " +
|
|
623
|
+
"filter the results in JavaScript.",
|
|
624
|
+
name,
|
|
625
|
+
)
|
|
626
|
+
}
|
|
627
|
+
throw new InvalidPredicateError(
|
|
628
|
+
`"${name}" is not indexed, and only indexed attributes can be filtered on. Declare it ` +
|
|
629
|
+
`${column.type}().indexed() — entities written before that are indexed by their next ` +
|
|
630
|
+
"patch of it — or select it and filter the results in JavaScript.",
|
|
631
|
+
name,
|
|
632
|
+
)
|
|
633
|
+
}
|
|
634
|
+
const asserted =
|
|
635
|
+
expression.kind === "comparison" || expression.kind === "startsWith"
|
|
636
|
+
? expression.value.type
|
|
637
|
+
: expression.kind === "between"
|
|
638
|
+
? expression.low.type
|
|
639
|
+
: expression.kind === "typeOf"
|
|
640
|
+
? expression.tag
|
|
641
|
+
: undefined
|
|
642
|
+
if (asserted !== undefined && asserted !== column.type) {
|
|
643
|
+
throw new InvalidPredicateError(
|
|
644
|
+
`"${name}" is a ${column.type} column, but the predicate asserts a ${asserted}, so it could ` +
|
|
645
|
+
`never match. Write ${column.type}(...) around the value.`,
|
|
646
|
+
name,
|
|
647
|
+
)
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
|
|
426
651
|
/**
|
|
427
652
|
* Binding strength, tightest last — `NOT` binds tighter than `AND`, which binds tighter than `OR`.
|
|
428
653
|
* A leaf never needs parentheses, so it sits above them all.
|
|
@@ -459,6 +684,10 @@ export function render(expression: Expression): string {
|
|
|
459
684
|
switch (expression.kind) {
|
|
460
685
|
case "comparison":
|
|
461
686
|
return `${expression.name} ${expression.operator} ${renderLiteral(expression.value)}`
|
|
687
|
+
case "between":
|
|
688
|
+
// A leaf, never parenthesised: the AND inside BETWEEN belongs to it, so
|
|
689
|
+
// `a BETWEEN 1 AND 5 AND b = 2` already reads as `(a BETWEEN 1 AND 5) AND b = 2`.
|
|
690
|
+
return `${expression.name} BETWEEN ${renderLiteral(expression.low)} AND ${renderLiteral(expression.high)}`
|
|
462
691
|
case "startsWith":
|
|
463
692
|
return `${expression.name} STARTSWITH ${renderLiteral(expression.value)}`
|
|
464
693
|
case "exists":
|
|
@@ -475,7 +704,7 @@ export function render(expression: Expression): string {
|
|
|
475
704
|
}
|
|
476
705
|
|
|
477
706
|
/** Renders a typed value as its query literal. `bool` is the one type written bare. */
|
|
478
|
-
function renderLiteral(value:
|
|
707
|
+
function renderLiteral(value: IndexableValue): string {
|
|
479
708
|
switch (value.type) {
|
|
480
709
|
case "bool":
|
|
481
710
|
return value.value ? "true" : "false"
|