@arkiv-network/sdk 0.7.0 → 0.8.0-dev.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -16
- package/dist/attr/index.cjs +34 -0
- package/dist/attr/index.d.cts +2 -0
- package/dist/attr/index.d.ts +2 -0
- package/dist/attr/index.js +3 -0
- package/dist/attr-BDZ-APg5.js +809 -0
- package/dist/attr-BDZ-APg5.js.map +1 -0
- package/dist/attr-BvxHN99W.cjs +1029 -0
- package/dist/chains/index.cjs +2 -3
- package/dist/chains/index.d.cts +39 -77
- package/dist/chains/index.d.cts.map +1 -1
- package/dist/chains/index.d.ts +22 -60
- package/dist/chains/index.d.ts.map +1 -1
- package/dist/chains/index.js +2 -2
- package/dist/chains-BJrZs6sG.js +57 -0
- package/dist/chains-BJrZs6sG.js.map +1 -0
- package/dist/chains-BYRTTipa.cjs +73 -0
- package/dist/changeOwnership-B7bEgla4.d.cts +509 -0
- package/dist/changeOwnership-B7bEgla4.d.cts.map +1 -0
- package/dist/changeOwnership-DwgtdSFc.d.ts +509 -0
- package/dist/changeOwnership-DwgtdSFc.d.ts.map +1 -0
- package/dist/entity/index.cjs +19 -0
- package/dist/entity/index.d.cts +4 -0
- package/dist/entity/index.d.ts +4 -0
- package/dist/entity/index.js +6 -0
- package/dist/entity-B0t6sN7F.cjs +71 -0
- package/dist/entity-BIp_df9u.js +67 -0
- package/dist/entity-BIp_df9u.js.map +1 -0
- package/dist/entity-CFhbAv0R.js +70 -0
- package/dist/entity-CFhbAv0R.js.map +1 -0
- package/dist/entity-wfBnZd91.cjs +98 -0
- package/dist/errors-D-eJkGVc.cjs +56 -0
- package/dist/errors-DGB8AgT2.js +33 -0
- package/dist/errors-DGB8AgT2.js.map +1 -0
- package/dist/expiry-BUYIXTGb.cjs +135 -0
- package/dist/expiry-BxOlvVWC.d.ts +79 -0
- package/dist/expiry-BxOlvVWC.d.ts.map +1 -0
- package/dist/expiry-DGRzrSan.js +95 -0
- package/dist/expiry-DGRzrSan.js.map +1 -0
- package/dist/expiry-DRV29F1r.d.cts +79 -0
- package/dist/expiry-DRV29F1r.d.cts.map +1 -0
- package/dist/flags-0Vl5rrHq.d.cts +41 -0
- package/dist/flags-0Vl5rrHq.d.cts.map +1 -0
- package/dist/flags-Ct8k0gpB.cjs +43 -0
- package/dist/flags-ZtlvOIIk.js +33 -0
- package/dist/flags-ZtlvOIIk.js.map +1 -0
- package/dist/flags-wmcMoAnN.d.ts +41 -0
- package/dist/flags-wmcMoAnN.d.ts.map +1 -0
- package/dist/index-Cgw1u5aY.d.cts +278 -0
- package/dist/index-Cgw1u5aY.d.cts.map +1 -0
- package/dist/index-ClgIuxJ8.d.ts +159 -0
- package/dist/index-ClgIuxJ8.d.ts.map +1 -0
- package/dist/index-D0CmtiWT.d.cts +159 -0
- package/dist/index-D0CmtiWT.d.cts.map +1 -0
- package/dist/index-D8LZt-3J.d.cts +417 -0
- package/dist/index-D8LZt-3J.d.cts.map +1 -0
- package/dist/index-DVYFaXNV.d.ts +417 -0
- package/dist/index-DVYFaXNV.d.ts.map +1 -0
- package/dist/index-VCqgn29D.d.ts +278 -0
- package/dist/index-VCqgn29D.d.ts.map +1 -0
- package/dist/index.cjs +675 -357
- package/dist/index.d.cts +251 -203
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +251 -203
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +626 -355
- package/dist/index.js.map +1 -1
- package/dist/payload-Bnw1hrvb.cjs +83 -0
- package/dist/payload-CIxSn_DW.js +67 -0
- package/dist/payload-CIxSn_DW.js.map +1 -0
- package/dist/payload-Dgx3PQeK.d.ts +94 -0
- package/dist/payload-Dgx3PQeK.d.ts.map +1 -0
- package/dist/payload-P0nWyjLg.d.cts +94 -0
- package/dist/payload-P0nWyjLg.d.cts.map +1 -0
- package/dist/query/index.cjs +24 -149
- package/dist/query/index.d.cts +5 -3
- package/dist/query/index.d.ts +5 -3
- package/dist/query/index.js +6 -137
- package/dist/queryBuilder-BXDvX1K_.cjs +1123 -0
- package/dist/queryBuilder-CbLL4pG5.js +963 -0
- package/dist/queryBuilder-CbLL4pG5.js.map +1 -0
- package/dist/selection-BvK6yX0g.d.cts +433 -0
- package/dist/selection-BvK6yX0g.d.cts.map +1 -0
- package/dist/selection-fpmI3ADQ.d.ts +433 -0
- package/dist/selection-fpmI3ADQ.d.ts.map +1 -0
- package/dist/types/index.cjs +4 -1
- package/dist/types/index.d.cts +7 -2
- package/dist/types/index.d.ts +7 -2
- package/dist/types/index.js +4 -1
- package/dist/utils/index.cjs +4 -17
- package/dist/utils/index.d.cts +3 -17
- package/dist/utils/index.d.ts +3 -17
- package/dist/utils/index.js +4 -18
- package/package.json +26 -5
- package/src/actions/public/getEntity.ts +27 -43
- package/src/actions/public/getEntityNonce.ts +30 -0
- package/src/actions/public/predictEntityKeys.test.ts +143 -0
- package/src/actions/public/predictEntityKeys.ts +138 -0
- package/src/actions/public/query.ts +33 -69
- package/src/actions/public/watchEntityEvents.test.ts +414 -0
- package/src/actions/public/watchEntityEvents.ts +225 -0
- package/src/actions/wallet/changeOwnership.ts +8 -8
- package/src/actions/wallet/createEntity.ts +104 -26
- package/src/actions/wallet/deleteEntity.ts +2 -3
- package/src/actions/wallet/extendEntity.ts +39 -16
- package/src/actions/wallet/mutateEntities.test.ts +76 -0
- package/src/actions/wallet/mutateEntities.ts +29 -74
- package/src/actions/wallet/patchEntity.ts +90 -0
- package/src/attr/attributes.test.ts +120 -0
- package/src/attr/attributes.ts +190 -0
- package/src/attr/codec.test.ts +162 -0
- package/src/attr/codec.ts +220 -0
- package/src/attr/decimal.ts +86 -0
- package/src/attr/errors.ts +145 -0
- package/src/attr/index.ts +50 -0
- package/src/attr/names.ts +106 -0
- package/src/attr/types.ts +150 -0
- package/src/attr/values.test.ts +271 -0
- package/src/attr/values.ts +502 -0
- package/src/chains/cheesecake.ts +22 -0
- package/src/chains/index.ts +1 -2
- package/src/clients/createPublicClient.test.ts +3 -5
- package/src/clients/createPublicClient.ts +5 -4
- package/src/clients/createWalletClient.test.ts +2 -2
- package/src/clients/createWalletClient.ts +13 -7
- package/src/clients/decorators/arkivPublic.ts +160 -139
- package/src/clients/decorators/arkivWallet.ts +91 -95
- package/src/consts.ts +2 -1
- package/src/entity/create.test.ts +184 -0
- package/src/entity/errors.ts +38 -0
- package/src/entity/events.ts +16 -0
- package/src/entity/expiry.test.ts +195 -0
- package/src/entity/expiry.ts +181 -0
- package/src/entity/flags.ts +65 -0
- package/src/entity/index.ts +14 -0
- package/src/entity/key.ts +68 -0
- package/src/entity/operations.ts +134 -0
- package/src/entity/params.ts +15 -0
- package/src/errors.ts +33 -28
- package/src/index.ts +6 -0
- package/src/query/engine.ts +59 -134
- package/src/query/errors.ts +151 -0
- package/src/query/expression.test.ts +286 -0
- package/src/query/expression.ts +493 -0
- package/src/query/index.ts +40 -4
- package/src/query/injection.test.ts +151 -0
- package/src/query/queryBuilder.test.ts +322 -637
- package/src/query/queryBuilder.ts +126 -359
- package/src/query/queryResult.ts +54 -51
- package/src/query/selection.test.ts +48 -98
- package/src/query/selection.ts +77 -65
- package/src/types/entity.test.ts +44 -155
- package/src/types/entity.ts +62 -44
- package/src/types/events.ts +64 -23
- package/src/types/index.ts +20 -16
- package/src/types/rpcSchema.ts +70 -33
- package/src/utils/arkivTransactions.test.ts +681 -38
- package/src/utils/arkivTransactions.ts +249 -104
- package/src/utils/chains.test.ts +21 -24
- package/src/utils/compression.ts +1 -1
- package/src/utils/entities.test.ts +62 -0
- package/src/utils/entities.ts +111 -29
- package/src/utils/expirationTime.ts +114 -10
- package/src/utils/logger.ts +2 -2
- package/src/utils/payload.ts +4 -2
- package/src/utils/revert.test.ts +99 -0
- package/src/utils/revert.ts +145 -0
- package/dist/chains-BifaY04V.cjs +0 -108
- package/dist/chains-CrtkVbBh.js +0 -86
- package/dist/chains-CrtkVbBh.js.map +0 -1
- package/dist/consts-AUoL-v42.cjs +0 -18
- package/dist/consts-BV4PR-de.js +0 -7
- package/dist/consts-BV4PR-de.js.map +0 -1
- package/dist/entity-B6g_EPm1.d.cts +0 -382
- package/dist/entity-B6g_EPm1.d.cts.map +0 -1
- package/dist/entity-qoLMAuuk.d.ts +0 -382
- package/dist/entity-qoLMAuuk.d.ts.map +0 -1
- package/dist/index-DdspxNAA.d.ts +0 -481
- package/dist/index-DdspxNAA.d.ts.map +0 -1
- package/dist/index-Ds9dwqb5.d.cts +0 -481
- package/dist/index-Ds9dwqb5.d.cts.map +0 -1
- package/dist/payload-B2kFJyah.d.ts +0 -6
- package/dist/payload-B2kFJyah.d.ts.map +0 -1
- package/dist/payload-CJN8fTvX.d.cts +0 -6
- package/dist/payload-CJN8fTvX.d.cts.map +0 -1
- package/dist/payload-Claf2jUO.js +0 -13
- package/dist/payload-Claf2jUO.js.map +0 -1
- package/dist/payload-DpA5PA-w.cjs +0 -23
- package/dist/query/index.js.map +0 -1
- package/dist/queryBuilder-D2uwP9jI.js +0 -704
- package/dist/queryBuilder-D2uwP9jI.js.map +0 -1
- package/dist/queryBuilder-UW2MCaX4.cjs +0 -792
- package/dist/utils/index.d.cts.map +0 -1
- package/dist/utils/index.d.ts.map +0 -1
- package/dist/utils/index.js.map +0 -1
- package/src/actions/public/buildQuery.ts +0 -10
- package/src/actions/public/subscribeEntityEvents.ts +0 -119
- package/src/actions/wallet/updateEntity.ts +0 -60
- package/src/chains/braga.ts +0 -26
- package/src/chains/kaolin.ts +0 -29
- package/src/query/engine.test.ts +0 -526
- package/src/query/predicate.test.ts +0 -461
- package/src/query/predicate.ts +0 -144
- package/src/query/queryResult.test.ts +0 -270
- package/src/query/selectQueryBuilder.test.ts +0 -210
- package/src/types/attributes.ts +0 -16
- package/src/utils/validation.test.ts +0 -60
- package/src/utils/validation.ts +0 -32
- /package/dist/{types-H-TSmRpm.js → types-Bx1EKe0v.js} +0 -0
- /package/dist/{types-o3zWarRp.cjs → types-CKPddlfS.cjs} +0 -0
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
import { describe, expect, it } from "bun:test"
|
|
2
|
+
import { addr, bytes32, dec, i32, key, str, u64, u256 } from "../attr"
|
|
3
|
+
import { InvalidAttributeNameError } from "../attr/errors"
|
|
4
|
+
import { InvalidPredicateError, UnsupportedOperatorError } from "./errors"
|
|
5
|
+
import {
|
|
6
|
+
and,
|
|
7
|
+
eq,
|
|
8
|
+
exists,
|
|
9
|
+
gt,
|
|
10
|
+
gte,
|
|
11
|
+
hasType,
|
|
12
|
+
lt,
|
|
13
|
+
lte,
|
|
14
|
+
ne,
|
|
15
|
+
not,
|
|
16
|
+
or,
|
|
17
|
+
render,
|
|
18
|
+
startsWith,
|
|
19
|
+
} from "./expression"
|
|
20
|
+
|
|
21
|
+
const ENTITY_KEY = `0x${"ab".repeat(32)}` as const
|
|
22
|
+
const ACCOUNT = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"
|
|
23
|
+
|
|
24
|
+
describe("literals", () => {
|
|
25
|
+
it("writes every type in its query spelling", () => {
|
|
26
|
+
expect(String(eq("level", i32(10)))).toBe("level = i32(10)")
|
|
27
|
+
expect(String(eq("level", i32(-42)))).toBe("level = i32(-42)")
|
|
28
|
+
expect(String(eq("balance", u256(1_000_000n)))).toBe("balance = u256(1000000)")
|
|
29
|
+
expect(String(eq("score", dec("3.5")))).toBe("score = dec(3.5)")
|
|
30
|
+
expect(String(eq("name", str("Bob")))).toBe("name = str('Bob')")
|
|
31
|
+
expect(String(eq("parent", key(ENTITY_KEY)))).toBe(`parent = key(${ENTITY_KEY})`)
|
|
32
|
+
expect(String(eq("hash", bytes32(ENTITY_KEY)))).toBe(`hash = bytes32(${ENTITY_KEY})`)
|
|
33
|
+
expect(String(eq("who", addr(ACCOUNT)))).toBe(`who = addr(${ACCOUNT})`)
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
it("writes bool bare — it is the one type with no wrapper", () => {
|
|
37
|
+
expect(String(eq("flagged", true))).toBe("flagged = true")
|
|
38
|
+
expect(String(eq("flagged", false))).toBe("flagged = false")
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
it("tags what the language would have inferred", () => {
|
|
42
|
+
// An untagged number literal defaults to i32, so `level = 10` would parse the same. Writing it
|
|
43
|
+
// out keeps the exact-type assertion — the thing that decides whether a u256 `level` matches —
|
|
44
|
+
// visible in the query that actually went over the wire.
|
|
45
|
+
expect(String(eq("level", 10))).toBe("level = i32(10)")
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
it("gives bare values the same types they take when written", () => {
|
|
49
|
+
expect(String(eq("level", 10))).toBe("level = i32(10)")
|
|
50
|
+
expect(String(eq("balance", 10n))).toBe("balance = u256(10)")
|
|
51
|
+
expect(String(eq("name", "Bob"))).toBe("name = str('Bob')")
|
|
52
|
+
expect(String(eq("flagged", true))).toBe("flagged = true")
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
it("escapes a quote by doubling it", () => {
|
|
56
|
+
expect(String(eq("name", "O'Brien"))).toBe("name = str('O''Brien')")
|
|
57
|
+
expect(String(eq("name", "''"))).toBe("name = str('''''')")
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
it("keeps the checksummed form of an address", () => {
|
|
61
|
+
expect(String(eq("$owner", addr(ACCOUNT.toLowerCase())))).toBe(`$owner = addr(${ACCOUNT})`)
|
|
62
|
+
})
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
describe("comparisons", () => {
|
|
66
|
+
it("renders each operator", () => {
|
|
67
|
+
expect(String(eq("a", 1))).toBe("a = i32(1)")
|
|
68
|
+
expect(String(ne("a", 1))).toBe("a != i32(1)")
|
|
69
|
+
expect(String(gt("a", 1))).toBe("a > i32(1)")
|
|
70
|
+
expect(String(gte("a", 1))).toBe("a >= i32(1)")
|
|
71
|
+
expect(String(lt("a", 1))).toBe("a < i32(1)")
|
|
72
|
+
expect(String(lte("a", 1))).toBe("a <= i32(1)")
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
it("allows range operators only on the ordered types", () => {
|
|
76
|
+
expect(String(gt("level", i32(1)))).toBe("level > i32(1)")
|
|
77
|
+
expect(String(gt("balance", u256(1n)))).toBe("balance > u256(1)")
|
|
78
|
+
expect(String(gt("score", dec("1.5")))).toBe("score > dec(1.5)")
|
|
79
|
+
|
|
80
|
+
// The node treats a range operator on an equality-only type as a parse error rather than an
|
|
81
|
+
// empty result, so catching it here saves a round trip for an error code.
|
|
82
|
+
for (const value of [str("a"), addr(ACCOUNT), key(ENTITY_KEY), bytes32(ENTITY_KEY), true]) {
|
|
83
|
+
expect(() => gt("a", value)).toThrow(UnsupportedOperatorError)
|
|
84
|
+
}
|
|
85
|
+
expect(() => lte("name", "Bob")).toThrow(/does not define <= for a str/)
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
it("carries the offending operator and type on the error", () => {
|
|
89
|
+
try {
|
|
90
|
+
gte("name", "Bob")
|
|
91
|
+
expect.unreachable()
|
|
92
|
+
} catch (error) {
|
|
93
|
+
expect(error).toBeInstanceOf(UnsupportedOperatorError)
|
|
94
|
+
const { attributeName, operator, tag } = error as UnsupportedOperatorError
|
|
95
|
+
expect({ attributeName, operator, tag }).toEqual({
|
|
96
|
+
attributeName: "name",
|
|
97
|
+
operator: ">=",
|
|
98
|
+
tag: "str",
|
|
99
|
+
})
|
|
100
|
+
}
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
it("validates the attribute name against the write-path grammar", () => {
|
|
104
|
+
expect(() => eq("2fast", 1)).toThrow(InvalidAttributeNameError)
|
|
105
|
+
expect(() => eq("has space", 1)).toThrow(InvalidAttributeNameError)
|
|
106
|
+
// Reserved by the query language: `not = i32(1)` could never be parsed as a comparison.
|
|
107
|
+
expect(() => eq("not", 1)).toThrow(InvalidAttributeNameError)
|
|
108
|
+
expect(() => eq("str", 1)).toThrow(InvalidAttributeNameError)
|
|
109
|
+
expect(() => eq("a".repeat(33), 1)).toThrow(InvalidAttributeNameError)
|
|
110
|
+
})
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
describe("system attributes", () => {
|
|
114
|
+
it("filters on the four that carry an index", () => {
|
|
115
|
+
expect(String(eq("$key", key(ENTITY_KEY)))).toBe(`$key = key(${ENTITY_KEY})`)
|
|
116
|
+
expect(String(eq("$owner", addr(ACCOUNT)))).toBe(`$owner = addr(${ACCOUNT})`)
|
|
117
|
+
expect(String(ne("$creator", addr(ACCOUNT)))).toBe(`$creator != addr(${ACCOUNT})`)
|
|
118
|
+
expect(String(lt("$expiresAt", u64(1_200_000n)))).toBe("$expiresAt < u64(1200000)")
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
it("holds each one to its fixed type", () => {
|
|
122
|
+
// The trap this closes: a bare string is a `str`, so `eq("$owner", "0xabc…")` would otherwise
|
|
123
|
+
// send `$owner = str('0xabc…')` and match nothing at all.
|
|
124
|
+
expect(() => eq("$owner", ACCOUNT)).toThrow(InvalidPredicateError)
|
|
125
|
+
expect(() => eq("$owner", ACCOUNT)).toThrow(/is a addr, but the value is a str/)
|
|
126
|
+
expect(() => eq("$key", ENTITY_KEY)).toThrow(/write key\(\.\.\.\) around the value/)
|
|
127
|
+
expect(() => eq("$expiresAt", 1_200_000)).toThrow(/is a u64, but the value is a i32/)
|
|
128
|
+
expect(() => eq("$expiresAt", 1_200_000n)).toThrow(/write u64\(\.\.\.\) around the value/)
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
it("allows range comparisons only on $expiresAt", () => {
|
|
132
|
+
expect(() => gt("$owner", addr(ACCOUNT))).toThrow(UnsupportedOperatorError)
|
|
133
|
+
expect(() => gt("$key", key(ENTITY_KEY))).toThrow(UnsupportedOperatorError)
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
it("rejects the ones that are returned but not indexed", () => {
|
|
137
|
+
for (const name of ["$createdAt", "$updatedAt", "$creationFlags", "$contentType", "$payload"]) {
|
|
138
|
+
expect(() => eq(name, 1)).toThrow(/carries no index, so it cannot be filtered on/)
|
|
139
|
+
}
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
it("rejects a $ name that is not a system attribute at all", () => {
|
|
143
|
+
expect(() => eq("$nope", 1)).toThrow(/is not a system attribute/)
|
|
144
|
+
expect(() => eq("$nope", 1)).toThrow(/\$key, \$owner, \$creator, \$expiresAt/)
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
it("rejects EXISTS and TYPEOF on them, which would be constants", () => {
|
|
148
|
+
expect(() => exists("$owner")).toThrow(/set on every entity/)
|
|
149
|
+
expect(() => hasType("$owner", "addr")).toThrow(/always a addr/)
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
it("rejects STARTSWITH — none of them is a str", () => {
|
|
153
|
+
expect(() => startsWith("$owner", "0xd8")).toThrow(UnsupportedOperatorError)
|
|
154
|
+
})
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
describe("startsWith", () => {
|
|
158
|
+
it("takes a bare string or a str value", () => {
|
|
159
|
+
expect(String(startsWith("desc", "ab"))).toBe("desc STARTSWITH str('ab')")
|
|
160
|
+
expect(String(startsWith("desc", str("ab")))).toBe("desc STARTSWITH str('ab')")
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
it("escapes the prefix like any other string literal", () => {
|
|
164
|
+
expect(String(startsWith("desc", "O'"))).toBe("desc STARTSWITH str('O''')")
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
it("rejects a non-str value", () => {
|
|
168
|
+
// @ts-expect-error - only a str has a prefix index
|
|
169
|
+
expect(() => startsWith("level", i32(1))).toThrow(UnsupportedOperatorError)
|
|
170
|
+
})
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
describe("exists and hasType", () => {
|
|
174
|
+
it("renders the function-call forms", () => {
|
|
175
|
+
expect(String(exists("reviewedBy"))).toBe("EXISTS(reviewedBy)")
|
|
176
|
+
expect(String(hasType("age", "i32"))).toBe("TYPEOF(age) = i32")
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
it("rejects a type that cannot be set on an attribute", () => {
|
|
180
|
+
// @ts-expect-error - bytes backs $payload and is never a user attribute
|
|
181
|
+
expect(() => hasType("blob", "bytes")).toThrow(/is not a settable attribute type/)
|
|
182
|
+
// @ts-expect-error - not a type at all
|
|
183
|
+
expect(() => hasType("age", "int")).toThrow(InvalidPredicateError)
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
it("does not mistake an inherited Object key for a type tag", () => {
|
|
187
|
+
// `tag in TYPE_IDS` walks the prototype chain, so `hasType("a", "toString")` rendered
|
|
188
|
+
// `TYPEOF(a) = toString` — a query the node can only answer with a parse error.
|
|
189
|
+
for (const inherited of ["constructor", "toString", "valueOf", "hasOwnProperty"]) {
|
|
190
|
+
// @ts-expect-error - not a type tag
|
|
191
|
+
expect(() => hasType("age", inherited)).toThrow(/is not a settable attribute type/)
|
|
192
|
+
}
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
it("validates the name", () => {
|
|
196
|
+
expect(() => exists("2fast")).toThrow(InvalidAttributeNameError)
|
|
197
|
+
expect(() => hasType("2fast", "i32")).toThrow(InvalidAttributeNameError)
|
|
198
|
+
})
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
describe("combinators", () => {
|
|
202
|
+
it("joins with the keyword operators", () => {
|
|
203
|
+
expect(String(and(eq("a", 1), eq("b", 2)))).toBe("a = i32(1) AND b = i32(2)")
|
|
204
|
+
expect(String(or(eq("a", 1), eq("b", 2)))).toBe("a = i32(1) OR b = i32(2)")
|
|
205
|
+
expect(String(not(exists("closedAt")))).toBe("NOT EXISTS(closedAt)")
|
|
206
|
+
})
|
|
207
|
+
|
|
208
|
+
it("takes an array as well as separate arguments", () => {
|
|
209
|
+
expect(String(and([eq("a", 1), eq("b", 2)]))).toBe("a = i32(1) AND b = i32(2)")
|
|
210
|
+
expect(String(or([eq("a", 1), eq("b", 2)]))).toBe("a = i32(1) OR b = i32(2)")
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
it("returns a lone expression unwrapped", () => {
|
|
214
|
+
const one = eq("a", 1)
|
|
215
|
+
expect(and(one)).toBe(one)
|
|
216
|
+
expect(or([one])).toBe(one)
|
|
217
|
+
})
|
|
218
|
+
|
|
219
|
+
it("refuses to combine nothing", () => {
|
|
220
|
+
expect(() => and()).toThrow(InvalidPredicateError)
|
|
221
|
+
expect(() => or([])).toThrow(/needs at least one expression/)
|
|
222
|
+
})
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
describe("precedence", () => {
|
|
226
|
+
it("parenthesises an OR inside an AND, and nothing else", () => {
|
|
227
|
+
expect(String(and(eq("a", 1), or(eq("b", 2), eq("c", 3))))).toBe(
|
|
228
|
+
"a = i32(1) AND (b = i32(2) OR c = i32(3))",
|
|
229
|
+
)
|
|
230
|
+
// AND binds tighter than OR, so the nesting the other way round needs no parentheses.
|
|
231
|
+
expect(String(or(eq("a", 1), and(eq("b", 2), eq("c", 3))))).toBe(
|
|
232
|
+
"a = i32(1) OR b = i32(2) AND c = i32(3)",
|
|
233
|
+
)
|
|
234
|
+
})
|
|
235
|
+
|
|
236
|
+
it("flattens same-operator nesting rather than bracketing it", () => {
|
|
237
|
+
expect(String(and(eq("a", 1), and(eq("b", 2), eq("c", 3))))).toBe(
|
|
238
|
+
"a = i32(1) AND b = i32(2) AND c = i32(3)",
|
|
239
|
+
)
|
|
240
|
+
})
|
|
241
|
+
|
|
242
|
+
it("parenthesises anything under NOT that is not a leaf", () => {
|
|
243
|
+
expect(String(not(and(eq("a", 1), eq("b", 2))))).toBe("NOT (a = i32(1) AND b = i32(2))")
|
|
244
|
+
expect(String(not(or(eq("a", 1), eq("b", 2))))).toBe("NOT (a = i32(1) OR b = i32(2))")
|
|
245
|
+
// A comparison is a primary, so it needs none.
|
|
246
|
+
expect(String(not(eq("a", 1)))).toBe("NOT a = i32(1)")
|
|
247
|
+
expect(String(not(not(eq("a", 1))))).toBe("NOT NOT a = i32(1)")
|
|
248
|
+
})
|
|
249
|
+
|
|
250
|
+
it("renders the spec's worked example", () => {
|
|
251
|
+
const q = and(
|
|
252
|
+
gte("level", i32(10)),
|
|
253
|
+
gte("score", dec("3.5")),
|
|
254
|
+
lte("score", dec("5")),
|
|
255
|
+
eq("parent", key(ENTITY_KEY)),
|
|
256
|
+
eq("flagged", true),
|
|
257
|
+
startsWith("desc", str("ab")),
|
|
258
|
+
or(eq("status", str("open")), not(exists("closedAt"))),
|
|
259
|
+
lt("$expiresAt", u64(1_200_000n)),
|
|
260
|
+
)
|
|
261
|
+
expect(q.toString()).toBe(
|
|
262
|
+
"level >= i32(10) AND score >= dec(3.5) AND score <= dec(5) AND " +
|
|
263
|
+
`parent = key(${ENTITY_KEY}) AND flagged = true AND desc STARTSWITH str('ab') AND ` +
|
|
264
|
+
"(status = str('open') OR NOT EXISTS(closedAt)) AND $expiresAt < u64(1200000)",
|
|
265
|
+
)
|
|
266
|
+
})
|
|
267
|
+
})
|
|
268
|
+
|
|
269
|
+
describe("the expression tree", () => {
|
|
270
|
+
it("is inspectable, frozen data", () => {
|
|
271
|
+
const expression = eq("level", i32(10))
|
|
272
|
+
expect(expression).toEqual({
|
|
273
|
+
kind: "comparison",
|
|
274
|
+
name: "level",
|
|
275
|
+
operator: "=",
|
|
276
|
+
value: i32(10),
|
|
277
|
+
})
|
|
278
|
+
expect(Object.isFrozen(expression)).toBe(true)
|
|
279
|
+
})
|
|
280
|
+
|
|
281
|
+
it("renders the same string through toString, String() and render()", () => {
|
|
282
|
+
const expression = and(eq("a", 1), not(exists("b")))
|
|
283
|
+
expect(expression.toString()).toBe(render(expression))
|
|
284
|
+
expect(`${expression}`).toBe(render(expression))
|
|
285
|
+
})
|
|
286
|
+
})
|