@arkiv-network/sdk 0.6.8 → 0.7.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 +52 -16
- package/dist/consts-Bz0LZQZY.js +7 -0
- package/dist/consts-Bz0LZQZY.js.map +1 -0
- package/dist/consts-CxT1-RXU.cjs +18 -0
- package/dist/entity-BhSXaB23.cjs +81 -0
- package/dist/{query-CO6f35AE.d.cts → entity-CN41IOrq.d.cts} +125 -59
- package/dist/entity-CN41IOrq.d.cts.map +1 -0
- package/dist/entity-DGfVyB-a.js +71 -0
- package/dist/entity-DGfVyB-a.js.map +1 -0
- package/dist/{query-BhdmAzYg.d.ts → entity-NnEVlePN.d.ts} +125 -59
- package/dist/entity-NnEVlePN.d.ts.map +1 -0
- package/dist/index-ClkpS6s_.d.ts +481 -0
- package/dist/index-ClkpS6s_.d.ts.map +1 -0
- package/dist/index-w4qop0Pe.d.cts +481 -0
- package/dist/index-w4qop0Pe.d.cts.map +1 -0
- package/dist/index.cjs +285 -233
- package/dist/index.d.cts +100 -6
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +101 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +280 -230
- package/dist/index.js.map +1 -1
- package/dist/payload-BpNUPOnY.js +13 -0
- package/dist/payload-BpNUPOnY.js.map +1 -0
- package/dist/{payload-Da9gJMG5.d.ts → payload-C2KHOUoj.d.ts} +1 -1
- package/dist/{payload-Da9gJMG5.d.ts.map → payload-C2KHOUoj.d.ts.map} +1 -1
- package/dist/{payload-vwU3Rc6P.cjs → payload-D8YLKcJj.cjs} +1 -18
- package/dist/query/index.cjs +11 -28
- package/dist/query/index.d.cts +3 -3
- package/dist/query/index.d.ts +3 -3
- package/dist/query/index.js +8 -28
- package/dist/query/index.js.map +1 -1
- package/dist/{queryBuilder-DonAg1aq.js → queryBuilder-BE_XhLLa.js} +303 -152
- package/dist/queryBuilder-BE_XhLLa.js.map +1 -0
- package/dist/{queryBuilder-DM-YO1Y7.cjs → queryBuilder-J37buGCi.cjs} +372 -149
- package/dist/types/index.cjs +4 -1
- package/dist/types/index.d.cts +2 -2
- package/dist/types/index.d.ts +2 -2
- package/dist/types/index.js +4 -1
- package/dist/utils/index.cjs +4 -10
- package/dist/utils/index.d.cts +0 -1
- package/dist/utils/index.d.cts.map +1 -1
- package/dist/utils/index.d.ts +1 -2
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +2 -3
- package/dist/utils/index.js.map +1 -1
- package/package.json +11 -15
- package/src/actions/public/buildQuery.ts +4 -0
- package/src/actions/public/query.ts +10 -0
- package/src/actions/public/subscribeEntityEvents.ts +25 -51
- package/src/actions/wallet/changeOwnership.ts +6 -6
- package/src/actions/wallet/createEntity.ts +19 -6
- package/src/actions/wallet/deleteEntity.ts +2 -3
- package/src/actions/wallet/extendEntity.ts +9 -5
- package/src/actions/wallet/mutateEntities.ts +11 -54
- package/src/actions/wallet/updateEntity.ts +15 -6
- package/src/clients/createPublicClient.test.ts +2 -0
- package/src/clients/decorators/arkivPublic.ts +67 -2
- package/src/clients/decorators/arkivWallet.ts +23 -1
- package/src/consts.ts +2 -1
- package/src/errors.ts +46 -0
- package/src/index.ts +0 -3
- package/src/query/engine.test.ts +45 -0
- package/src/query/engine.ts +28 -14
- package/src/query/index.ts +1 -0
- package/src/query/predicate.test.ts +43 -0
- package/src/query/predicate.ts +14 -10
- package/src/query/queryBuilder.test.ts +22 -0
- package/src/query/queryBuilder.ts +221 -106
- package/src/query/queryResult.ts +13 -7
- package/src/query/selectQueryBuilder.test.ts +213 -0
- package/src/query/selection.test.ts +120 -0
- package/src/query/selection.ts +116 -0
- package/src/types/attributes.ts +25 -1
- package/src/types/entity.ts +12 -2
- package/src/types/events.ts +14 -16
- package/src/types/index.ts +1 -0
- package/src/types/rpcSchema.ts +7 -2
- package/src/utils/arkivTransactions.test.ts +260 -0
- package/src/utils/arkivTransactions.ts +255 -92
- package/src/utils/entities.ts +17 -7
- package/src/utils/index.ts +0 -1
- package/src/utils/payload.ts +4 -2
- package/src/utils/validation.test.ts +106 -0
- package/src/utils/validation.ts +80 -0
- package/dist/accounts/index.cjs +0 -9
- package/dist/accounts/index.d.cts +0 -1
- package/dist/accounts/index.d.ts +0 -1
- package/dist/accounts/index.js +0 -1
- package/dist/payload-DKBYYjeA.js +0 -18
- package/dist/payload-DKBYYjeA.js.map +0 -1
- package/dist/query-BhdmAzYg.d.ts.map +0 -1
- package/dist/query-CO6f35AE.d.cts.map +0 -1
- package/dist/queryBuilder-BvoQXa_u.d.ts +0 -328
- package/dist/queryBuilder-BvoQXa_u.d.ts.map +0 -1
- package/dist/queryBuilder-DonAg1aq.js.map +0 -1
- package/dist/queryBuilder-zTsKXctT.d.cts +0 -328
- package/dist/queryBuilder-zTsKXctT.d.cts.map +0 -1
- package/src/accounts/index.ts +0 -1
- /package/dist/{types-H-TSmRpm.js → types-Ci4BRClw.js} +0 -0
- /package/dist/{types-o3zWarRp.cjs → types-a8gm_IaQ.cjs} +0 -0
|
@@ -4,7 +4,8 @@ import { getEntity } from "../../actions/public/getEntity"
|
|
|
4
4
|
import { getEntityCount } from "../../actions/public/getEntityCount"
|
|
5
5
|
import { type QueryOptions, type QueryReturnType, query } from "../../actions/public/query"
|
|
6
6
|
import { subscribeEntityEvents } from "../../actions/public/subscribeEntityEvents"
|
|
7
|
-
import { QueryBuilder } from "../../query/queryBuilder"
|
|
7
|
+
import { QueryBuilder, SelectQueryBuilder } from "../../query/queryBuilder"
|
|
8
|
+
import type { EntitySelection, FullEntity, ProjectedEntity, SelectArg } from "../../query/selection"
|
|
8
9
|
import type { Entity } from "../../types/entity"
|
|
9
10
|
import type {
|
|
10
11
|
OnEntityCreatedEvent,
|
|
@@ -55,12 +56,76 @@ export type PublicArkivActions<
|
|
|
55
56
|
*/
|
|
56
57
|
getEntity: (key: Hex) => Promise<Entity>
|
|
57
58
|
|
|
59
|
+
/**
|
|
60
|
+
* Returns a SelectQueryBuilder for building and executing queries — the recommended way to
|
|
61
|
+
* read entities. You declare up front which parts of an entity you want returned, so results
|
|
62
|
+
* always contain exactly the data you asked for.
|
|
63
|
+
*
|
|
64
|
+
* - Docs: https://docs.arkiv.network/ts-sdk/actions/public/query
|
|
65
|
+
*
|
|
66
|
+
* @param selection - What to include in the results. Omit it (or pass `"*"`) to select everything,
|
|
67
|
+
* or pass an object to select specific parts (at least one field is required). Every part is
|
|
68
|
+
* opt-in, including the `key`. The selection is flat — each field maps to an entity field.
|
|
69
|
+
* {@link SelectArg}
|
|
70
|
+
* @returns A SelectQueryBuilder instance for building and executing queries. {@link SelectQueryBuilder}
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* import { createPublicClient, http } from 'arkiv'
|
|
74
|
+
* import { braga } from 'arkiv/chains'
|
|
75
|
+
* import { eq } from 'arkiv/query'
|
|
76
|
+
*
|
|
77
|
+
* const client = createPublicClient({
|
|
78
|
+
* chain: braga,
|
|
79
|
+
* transport: http(),
|
|
80
|
+
* })
|
|
81
|
+
* // select everything
|
|
82
|
+
* await client.select().where(eq("category", "docs")).fetch()
|
|
83
|
+
* await client.select("*").where(eq("category", "docs")).fetch()
|
|
84
|
+
* // only the key
|
|
85
|
+
* await client.select({ key: true }).where(eq("category", "docs")).fetch()
|
|
86
|
+
* // select specific fields — result typed { owner: Hex; attributes: Attribute[] }
|
|
87
|
+
* await client.select({ owner: true, attributes: true }).fetch()
|
|
88
|
+
* // a single field — result typed { owner: Hex }
|
|
89
|
+
* await client.select({ owner: true }).fetch()
|
|
90
|
+
*/
|
|
91
|
+
select: {
|
|
92
|
+
/** Select every field. Pass nothing or `"*"`; the returned entities contain all fields. */
|
|
93
|
+
(selection?: "*"): SelectQueryBuilder<FullEntity>
|
|
94
|
+
/**
|
|
95
|
+
* Pick the entity fields to return. Set the ones you want to `true` (at least one is required);
|
|
96
|
+
* the result is typed to exactly those fields, so reading anything else is a compile error.
|
|
97
|
+
*
|
|
98
|
+
* Available fields: `key`, `owner`, `creator`, `contentType`, `payload`, `attributes`,
|
|
99
|
+
* `expiresAtBlock`, `createdAtBlock`, `lastModifiedAtBlock`, `transactionIndexInBlock`,
|
|
100
|
+
* `operationIndexInTransaction`.
|
|
101
|
+
*
|
|
102
|
+
* Pass the selection inline so its fields stay literal `true`. A selection stored in a `let`/
|
|
103
|
+
* `const` variable widens to `boolean` and the result type can no longer be narrowed — annotate
|
|
104
|
+
* it `as const` (e.g. `const sel = { owner: true } as const`) in that case.
|
|
105
|
+
*
|
|
106
|
+
* @example
|
|
107
|
+
* client.select({ owner: true, attributes: true }) // entities typed { owner, attributes }
|
|
108
|
+
* client.select({ key: true, payload: true }) // includes payload → toText()/toJson() too
|
|
109
|
+
*/
|
|
110
|
+
<const S extends EntitySelection>(selection: S): SelectQueryBuilder<ProjectedEntity<S>>
|
|
111
|
+
/**
|
|
112
|
+
* Dynamic selection: accepts a value typed {@link SelectArg} (e.g. built at runtime). The
|
|
113
|
+
* result cannot be narrowed in this case, so the entities are typed as the full entity.
|
|
114
|
+
*/
|
|
115
|
+
(selection: SelectArg): SelectQueryBuilder<FullEntity>
|
|
116
|
+
}
|
|
117
|
+
|
|
58
118
|
/**
|
|
59
119
|
* Returns a QueryBuilder instance for building and executing queries.
|
|
60
120
|
* The QueryBuilder object follows the Builder pattern, allowing you to chain methods to build a query and then execute it.
|
|
61
121
|
*
|
|
62
122
|
* - Docs: https://docs.arkiv.network/ts-sdk/actions/public/query
|
|
63
123
|
*
|
|
124
|
+
* @deprecated Use {@link select} instead. `buildQuery()` returns only the entity `key` unless
|
|
125
|
+
* you remember to opt in to data with `withAttributes()`/`withMetadata()`/`withPayload()`, which
|
|
126
|
+
* is an easy mistake. `select()` makes the selection explicit. This method remains for backwards
|
|
127
|
+
* compatibility and will be removed in a future release.
|
|
128
|
+
*
|
|
64
129
|
* @returns A QueryBuilder instance for building and executing queries. {@link QueryBuilder}
|
|
65
130
|
*
|
|
66
131
|
* @example
|
|
@@ -100,7 +165,6 @@ export type PublicArkivActions<
|
|
|
100
165
|
* payload: true,
|
|
101
166
|
* metadata: true,
|
|
102
167
|
* },
|
|
103
|
-
* orderBy: [{ name: "key", type: "string", desc: "asc" }],
|
|
104
168
|
* resultsPerPage: 10,
|
|
105
169
|
* cursor: undefined,
|
|
106
170
|
* atBlock: undefined,
|
|
@@ -200,6 +264,7 @@ export function publicArkivActions<
|
|
|
200
264
|
getEntity: (key: Hex) => getEntity(client, key),
|
|
201
265
|
query: (rawQuery: string, queryOptions?: QueryOptions) => query(client, rawQuery, queryOptions),
|
|
202
266
|
buildQuery: () => new QueryBuilder(client),
|
|
267
|
+
select: (selection?: SelectArg) => new SelectQueryBuilder(client, selection),
|
|
203
268
|
getBlockTiming: () => getBlockTiming(client),
|
|
204
269
|
getEntityCount: () => getEntityCount(client),
|
|
205
270
|
subscribeEntityEvents: (
|
|
@@ -35,7 +35,10 @@ export type WalletArkivActions<
|
|
|
35
35
|
transport extends Transport = Transport,
|
|
36
36
|
chain extends Chain | undefined = Chain | undefined,
|
|
37
37
|
account extends Account | undefined = Account | undefined,
|
|
38
|
-
> = Pick<
|
|
38
|
+
> = Pick<
|
|
39
|
+
PublicActions<transport, chain, account>,
|
|
40
|
+
"waitForTransactionReceipt" | "call" | "simulateContract" | "readContract" | "getBlockNumber"
|
|
41
|
+
> &
|
|
39
42
|
Pick<
|
|
40
43
|
WalletActions<chain, account>,
|
|
41
44
|
| "addChain"
|
|
@@ -45,6 +48,7 @@ export type WalletArkivActions<
|
|
|
45
48
|
| "sendRawTransaction"
|
|
46
49
|
| "signMessage"
|
|
47
50
|
| "signTransaction"
|
|
51
|
+
| "writeContract"
|
|
48
52
|
> & {
|
|
49
53
|
/**
|
|
50
54
|
* Creates a new entity.
|
|
@@ -56,6 +60,11 @@ export type WalletArkivActions<
|
|
|
56
60
|
* @param txParams - Optional transaction parameters
|
|
57
61
|
* @returns The created entity with transaction hash
|
|
58
62
|
*
|
|
63
|
+
* @throws {InvalidExpirationError} If `expiresIn` is not a positive integer
|
|
64
|
+
* that is a multiple of the block time (2 seconds).
|
|
65
|
+
* @throws {InvalidAttributeError} If a numeric attribute value is not an
|
|
66
|
+
* integer.
|
|
67
|
+
*
|
|
59
68
|
* @example
|
|
60
69
|
* import { createPublicClient, http } from 'arkiv'
|
|
61
70
|
* import { braga } from 'arkiv/chains'
|
|
@@ -91,6 +100,11 @@ export type WalletArkivActions<
|
|
|
91
100
|
* @param txParams - Optional transaction parameters
|
|
92
101
|
* @returns The updated entity with transaction hash
|
|
93
102
|
*
|
|
103
|
+
* @throws {InvalidExpirationError} If `expiresIn` is not a positive integer
|
|
104
|
+
* that is a multiple of the block time (2 seconds).
|
|
105
|
+
* @throws {InvalidAttributeError} If a numeric attribute value is not an
|
|
106
|
+
* integer.
|
|
107
|
+
*
|
|
94
108
|
* @example
|
|
95
109
|
* import { createWalletClient, http } from 'arkiv'
|
|
96
110
|
* import { braga } from 'arkiv/chains'
|
|
@@ -146,6 +160,9 @@ export type WalletArkivActions<
|
|
|
146
160
|
* @param txParams - Optional transaction parameters
|
|
147
161
|
* @returns The updated entity with transaction hash
|
|
148
162
|
*
|
|
163
|
+
* @throws {InvalidExpirationError} If `expiresIn` is not a positive integer
|
|
164
|
+
* that is a multiple of the block time (2 seconds).
|
|
165
|
+
*
|
|
149
166
|
* @example
|
|
150
167
|
* import { createWalletClient, http } from 'arkiv'
|
|
151
168
|
* import { braga } from 'arkiv/chains'
|
|
@@ -194,6 +211,11 @@ export type WalletArkivActions<
|
|
|
194
211
|
* @param txParams - Optional transaction parameters
|
|
195
212
|
* @returns The mutation result with transaction hash
|
|
196
213
|
*
|
|
214
|
+
* @throws {InvalidExpirationError} If any create/update/extension `expiresIn`
|
|
215
|
+
* is not a positive integer that is a multiple of the block time (2 seconds).
|
|
216
|
+
* @throws {InvalidAttributeError} If a numeric attribute value is not an
|
|
217
|
+
* integer.
|
|
218
|
+
*
|
|
197
219
|
* @example
|
|
198
220
|
* import { createWalletClient, http } from 'arkiv'
|
|
199
221
|
* import { braga } from 'arkiv/chains'
|
package/src/consts.ts
CHANGED
|
@@ -1,2 +1,3 @@
|
|
|
1
|
-
export const ARKIV_ADDRESS = "0x00000000000000000000000000000061726b6976" as `0x${string}`
|
|
1
|
+
//export const ARKIV_ADDRESS = "0x00000000000000000000000000000061726b6976" as `0x${string}`
|
|
2
|
+
export const ARKIV_ADDRESS = "0x4400000000000000000000000000000000000044" as `0x${string}`
|
|
2
3
|
export const BLOCK_TIME = 2 // 2 seconds
|
package/src/errors.ts
CHANGED
|
@@ -1,9 +1,46 @@
|
|
|
1
|
+
import { BLOCK_TIME } from "./consts"
|
|
2
|
+
|
|
1
3
|
export class EntityMutationError extends Error {
|
|
2
4
|
constructor(message: string) {
|
|
3
5
|
super(message)
|
|
4
6
|
this.name = "EntityMutationError"
|
|
5
7
|
}
|
|
6
8
|
}
|
|
9
|
+
export class InvalidExpirationError extends Error {
|
|
10
|
+
constructor(expiresIn: unknown) {
|
|
11
|
+
super(
|
|
12
|
+
`Invalid expiresIn: ${String(expiresIn)}. expiresIn must be a positive integer and a multiple of the Arkiv block time (${BLOCK_TIME} seconds), because expiration is measured in whole blocks (1 block = ${BLOCK_TIME} seconds).`,
|
|
13
|
+
)
|
|
14
|
+
this.name = "InvalidExpirationError"
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export class InvalidAttributeError extends Error {
|
|
18
|
+
constructor(key: string, value: number) {
|
|
19
|
+
const remedy = Number.isInteger(value)
|
|
20
|
+
? `Offset it into the non-negative range (e.g. store ${String(value)} + OFFSET, subtracting OFFSET on read) to keep numeric ordering/range queries; otherwise store it as a string (e.g. "${String(value)}")`
|
|
21
|
+
: `To keep numeric ordering/range queries, scale it to an integer (e.g. Math.round(${String(value)} * 1000), dividing by 1000 on read); otherwise store it as a string (e.g. "${String(value)}")`
|
|
22
|
+
super(
|
|
23
|
+
`Invalid numeric value for attribute "${key}": ${String(value)}. Numeric attribute values must be non-negative integers (stored on-chain as unsigned 256-bit). ${remedy}.`,
|
|
24
|
+
)
|
|
25
|
+
this.name = "InvalidAttributeError"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export class InvalidAttributeKeyError extends Error {
|
|
30
|
+
constructor(key: string, reason: string) {
|
|
31
|
+
super(
|
|
32
|
+
`Invalid attribute key "${key}": ${reason}. Keys must be 1-32 bytes, start with a lowercase letter (a-z), and contain only a-z, 0-9, ".", "-" or "_".`,
|
|
33
|
+
)
|
|
34
|
+
this.name = "InvalidAttributeKeyError"
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export class DuplicateAttributeError extends Error {
|
|
39
|
+
constructor(key: string) {
|
|
40
|
+
super(`Duplicate attribute key "${key}". Attribute keys must be unique per entity.`)
|
|
41
|
+
this.name = "DuplicateAttributeError"
|
|
42
|
+
}
|
|
43
|
+
}
|
|
7
44
|
|
|
8
45
|
export class NoMoreResultsError extends Error {
|
|
9
46
|
constructor() {
|
|
@@ -25,3 +62,12 @@ export class NoEntityFoundError extends Error {
|
|
|
25
62
|
this.name = "NoEntityFoundError"
|
|
26
63
|
}
|
|
27
64
|
}
|
|
65
|
+
|
|
66
|
+
export class InvalidContentTypeError extends Error {
|
|
67
|
+
constructor(contentType: string) {
|
|
68
|
+
super(
|
|
69
|
+
`Invalid content type "${contentType}". Must follow RFC 2045 MIME grammar and be lowercase only (e.g. "text/plain").`,
|
|
70
|
+
)
|
|
71
|
+
this.name = "InvalidContentTypeError"
|
|
72
|
+
}
|
|
73
|
+
}
|
package/src/index.ts
CHANGED
package/src/query/engine.test.ts
CHANGED
|
@@ -47,6 +47,51 @@ describe("processQuery tests", () => {
|
|
|
47
47
|
})
|
|
48
48
|
})
|
|
49
49
|
|
|
50
|
+
it("should serialize 32-byte hex values as unquoted literals and other hex as strings", async () => {
|
|
51
|
+
const hexValue = "0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"
|
|
52
|
+
const predicates = [
|
|
53
|
+
{ type: "eq" as const, key: "hexattr", value: hexValue },
|
|
54
|
+
{ type: "eq" as const, key: "strattr", value: "value" },
|
|
55
|
+
// hex-looking but not 32 bytes — stored as strings, so compared quoted
|
|
56
|
+
{ type: "eq" as const, key: "shorthex", value: "0xab" },
|
|
57
|
+
{ type: "eq" as const, key: "addrhex", value: "0x6186b0dba9652262942d5a465d49686eb560834c" },
|
|
58
|
+
]
|
|
59
|
+
await processQuery(client, {
|
|
60
|
+
predicates,
|
|
61
|
+
limit: undefined,
|
|
62
|
+
cursor: undefined,
|
|
63
|
+
ownedBy: undefined,
|
|
64
|
+
createdBy: undefined,
|
|
65
|
+
orderBy: undefined,
|
|
66
|
+
validAtBlock: undefined,
|
|
67
|
+
withAttributes: undefined,
|
|
68
|
+
withMetadata: undefined,
|
|
69
|
+
withPayload: undefined,
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
expect(client.request).lastCalledWith({
|
|
73
|
+
method: "arkiv_query",
|
|
74
|
+
params: [
|
|
75
|
+
`hexattr = ${hexValue} && strattr = "value" && shorthex = "0xab" && addrhex = "0x6186b0dba9652262942d5a465d49686eb560834c"`,
|
|
76
|
+
{
|
|
77
|
+
includeData: {
|
|
78
|
+
key: true,
|
|
79
|
+
attributes: false,
|
|
80
|
+
contentType: false,
|
|
81
|
+
payload: false,
|
|
82
|
+
expiration: false,
|
|
83
|
+
owner: false,
|
|
84
|
+
creator: false,
|
|
85
|
+
createdAtBlock: false,
|
|
86
|
+
lastModifiedAtBlock: false,
|
|
87
|
+
transactionIndexInBlock: false,
|
|
88
|
+
operationIndexInTransaction: false,
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
],
|
|
92
|
+
})
|
|
93
|
+
})
|
|
94
|
+
|
|
50
95
|
it("should process all simple predicates - flat", async () => {
|
|
51
96
|
const predicates = [
|
|
52
97
|
{ type: "eq" as const, key: "key", value: "value" },
|
package/src/query/engine.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { hexToNumber, numberToHex } from "viem"
|
|
|
3
3
|
import type { ArkivClient } from "../clients/baseClient"
|
|
4
4
|
import type { RpcIncludeData, RpcOrderByAttribute, RpcQueryOptions } from "../types/rpcSchema"
|
|
5
5
|
import { getLogger } from "../utils/logger"
|
|
6
|
+
import { isEntityKey } from "../utils/validation"
|
|
6
7
|
import type { Predicate } from "./predicate"
|
|
7
8
|
|
|
8
9
|
const logger = getLogger("query:engine")
|
|
@@ -10,7 +11,11 @@ const logger = getLogger("query:engine")
|
|
|
10
11
|
function processPredicates(predicates: Predicate[]): string {
|
|
11
12
|
const processValue = (value: string | number) => {
|
|
12
13
|
if (typeof value === "string") {
|
|
13
|
-
|
|
14
|
+
// 32-byte hex strings are stored as the EntityKey attribute type (see
|
|
15
|
+
// encodeAttribute), which the query language only matches against unquoted
|
|
16
|
+
// hex literals. All other strings — including shorter hex — are stored and
|
|
17
|
+
// compared as quoted strings.
|
|
18
|
+
return isEntityKey(value) ? value : `"${value}"`
|
|
14
19
|
}
|
|
15
20
|
return value
|
|
16
21
|
}
|
|
@@ -55,6 +60,11 @@ export async function processQuery(
|
|
|
55
60
|
withAttributes?: boolean | undefined
|
|
56
61
|
withMetadata?: boolean | undefined
|
|
57
62
|
withPayload?: boolean | undefined
|
|
63
|
+
/**
|
|
64
|
+
* Fully-resolved include-data for fine-grained selection. When provided it takes
|
|
65
|
+
* precedence over the `withAttributes`/`withMetadata`/`withPayload` booleans.
|
|
66
|
+
*/
|
|
67
|
+
includeData?: RpcIncludeData | undefined
|
|
58
68
|
},
|
|
59
69
|
) {
|
|
60
70
|
const {
|
|
@@ -68,6 +78,7 @@ export async function processQuery(
|
|
|
68
78
|
withAttributes,
|
|
69
79
|
withMetadata,
|
|
70
80
|
withPayload,
|
|
81
|
+
includeData,
|
|
71
82
|
} = queryParams
|
|
72
83
|
|
|
73
84
|
logger("Processing query with params %o", {
|
|
@@ -81,6 +92,7 @@ export async function processQuery(
|
|
|
81
92
|
withAttributes,
|
|
82
93
|
withMetadata,
|
|
83
94
|
withPayload,
|
|
95
|
+
includeData,
|
|
84
96
|
})
|
|
85
97
|
|
|
86
98
|
let query = processPredicates(predicates)
|
|
@@ -98,19 +110,21 @@ export async function processQuery(
|
|
|
98
110
|
}
|
|
99
111
|
|
|
100
112
|
const queryOptions: RpcQueryOptions = {
|
|
101
|
-
includeData:
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
113
|
+
includeData:
|
|
114
|
+
includeData ??
|
|
115
|
+
({
|
|
116
|
+
key: true,
|
|
117
|
+
attributes: withAttributes ?? false,
|
|
118
|
+
payload: withPayload ?? false,
|
|
119
|
+
contentType: withMetadata ?? false,
|
|
120
|
+
expiration: withMetadata ?? false,
|
|
121
|
+
owner: withMetadata ?? false,
|
|
122
|
+
creator: withMetadata ?? false,
|
|
123
|
+
createdAtBlock: withMetadata ?? false,
|
|
124
|
+
lastModifiedAtBlock: withMetadata ?? false,
|
|
125
|
+
transactionIndexInBlock: withMetadata ?? false,
|
|
126
|
+
operationIndexInTransaction: withMetadata ?? false,
|
|
127
|
+
} as RpcIncludeData),
|
|
114
128
|
}
|
|
115
129
|
|
|
116
130
|
if (validAtBlock !== undefined) {
|
package/src/query/index.ts
CHANGED
|
@@ -244,6 +244,27 @@ describe("Predicate Factory Functions", () => {
|
|
|
244
244
|
})
|
|
245
245
|
})
|
|
246
246
|
|
|
247
|
+
test("creates or predicate from varargs", () => {
|
|
248
|
+
const result = or(eq("type", "admin"), eq("type", "moderator"))
|
|
249
|
+
|
|
250
|
+
expect(result).toEqual({
|
|
251
|
+
type: "or",
|
|
252
|
+
predicates: [
|
|
253
|
+
{ type: "eq", key: "type", value: "admin" },
|
|
254
|
+
{ type: "eq", key: "type", value: "moderator" },
|
|
255
|
+
],
|
|
256
|
+
})
|
|
257
|
+
})
|
|
258
|
+
|
|
259
|
+
test("creates or predicate with no arguments", () => {
|
|
260
|
+
const result = or()
|
|
261
|
+
|
|
262
|
+
expect(result).toEqual({
|
|
263
|
+
type: "or",
|
|
264
|
+
predicates: [],
|
|
265
|
+
})
|
|
266
|
+
})
|
|
267
|
+
|
|
247
268
|
test("creates or predicate with nested and", () => {
|
|
248
269
|
const nestedAnd = and([eq("verified", 1), gte("age", 18)])
|
|
249
270
|
const predicates = [eq("status", "premium"), nestedAnd]
|
|
@@ -314,6 +335,28 @@ describe("Predicate Factory Functions", () => {
|
|
|
314
335
|
})
|
|
315
336
|
})
|
|
316
337
|
|
|
338
|
+
test("creates and predicate from varargs", () => {
|
|
339
|
+
const result = and(eq("status", "active"), gte("age", 18), not("deleted"))
|
|
340
|
+
|
|
341
|
+
expect(result).toEqual({
|
|
342
|
+
type: "and",
|
|
343
|
+
predicates: [
|
|
344
|
+
{ type: "eq", key: "status", value: "active" },
|
|
345
|
+
{ type: "gte", key: "age", value: 18 },
|
|
346
|
+
{ type: "not", key: "deleted", value: "" },
|
|
347
|
+
],
|
|
348
|
+
})
|
|
349
|
+
})
|
|
350
|
+
|
|
351
|
+
test("creates and predicate with no arguments", () => {
|
|
352
|
+
const result = and()
|
|
353
|
+
|
|
354
|
+
expect(result).toEqual({
|
|
355
|
+
type: "and",
|
|
356
|
+
predicates: [],
|
|
357
|
+
})
|
|
358
|
+
})
|
|
359
|
+
|
|
317
360
|
test("creates and predicate with nested or", () => {
|
|
318
361
|
const nestedOr = or([eq("type", "admin"), eq("type", "moderator")])
|
|
319
362
|
const predicates = [eq("active", 1), nestedOr]
|
package/src/query/predicate.ts
CHANGED
|
@@ -14,32 +14,36 @@ export type Predicate =
|
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* Creates an OR predicate
|
|
17
|
-
* @param predicates - The predicates to combine
|
|
17
|
+
* @param predicates - The predicates to combine, either as a single array or as separate arguments
|
|
18
18
|
* @returns The OR predicate
|
|
19
19
|
*
|
|
20
20
|
* @example
|
|
21
|
-
* const
|
|
22
|
-
*
|
|
21
|
+
* const result = or(eq("name", "John"), eq("age", 30))
|
|
22
|
+
* // or equivalently: or([eq("name", "John"), eq("age", 30)])
|
|
23
23
|
* // result = { type: "or", predicates: [eq("name", "John"), eq("age", 30)] }
|
|
24
24
|
*
|
|
25
25
|
*/
|
|
26
|
-
export function or(predicates: Predicate[]): Predicate
|
|
27
|
-
|
|
26
|
+
export function or(predicates: Predicate[]): Predicate
|
|
27
|
+
export function or(...predicates: Predicate[]): Predicate
|
|
28
|
+
export function or(...predicates: (Predicate | Predicate[])[]): Predicate {
|
|
29
|
+
return { type: "or", predicates: predicates.flat() }
|
|
28
30
|
}
|
|
29
31
|
|
|
30
32
|
/**
|
|
31
33
|
* Creates an AND predicate
|
|
32
|
-
* @param predicates - The predicates to combine
|
|
34
|
+
* @param predicates - The predicates to combine, either as a single array or as separate arguments
|
|
33
35
|
* @returns The AND predicate
|
|
34
36
|
*
|
|
35
37
|
* @example
|
|
36
|
-
* const
|
|
37
|
-
*
|
|
38
|
+
* const result = and(eq("name", "John"), eq("age", 30))
|
|
39
|
+
* // or equivalently: and([eq("name", "John"), eq("age", 30)])
|
|
38
40
|
* // result = { type: "and", predicates: [eq("name", "John"), eq("age", 30)] }
|
|
39
41
|
*
|
|
40
42
|
*/
|
|
41
|
-
export function and(predicates: Predicate[]): Predicate
|
|
42
|
-
|
|
43
|
+
export function and(predicates: Predicate[]): Predicate
|
|
44
|
+
export function and(...predicates: Predicate[]): Predicate
|
|
45
|
+
export function and(...predicates: (Predicate | Predicate[])[]): Predicate {
|
|
46
|
+
return { type: "and", predicates: predicates.flat() }
|
|
43
47
|
}
|
|
44
48
|
|
|
45
49
|
/**
|
|
@@ -78,6 +78,28 @@ describe("QueryBuilder", () => {
|
|
|
78
78
|
})
|
|
79
79
|
})
|
|
80
80
|
|
|
81
|
+
test("adds multiple predicates as varargs", async () => {
|
|
82
|
+
mockProcessQuery.mockResolvedValue({
|
|
83
|
+
data: [],
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
const builder = new QueryBuilder(mockClient)
|
|
87
|
+
await builder.where(eq("name", "test"), gt("age", 18), neq("status", "inactive")).fetch()
|
|
88
|
+
|
|
89
|
+
expect(mockProcessQuery).toHaveBeenCalledWith(mockClient, {
|
|
90
|
+
predicates: [eq("name", "test"), gt("age", 18), neq("status", "inactive")],
|
|
91
|
+
limit: undefined,
|
|
92
|
+
offset: undefined,
|
|
93
|
+
ownedBy: undefined,
|
|
94
|
+
createdBy: undefined,
|
|
95
|
+
orderBy: undefined,
|
|
96
|
+
validBeforeBlock: undefined,
|
|
97
|
+
withAttributes: undefined,
|
|
98
|
+
withMetadata: undefined,
|
|
99
|
+
withPayload: undefined,
|
|
100
|
+
})
|
|
101
|
+
})
|
|
102
|
+
|
|
81
103
|
test("chains multiple where() calls", async () => {
|
|
82
104
|
mockProcessQuery.mockResolvedValue({
|
|
83
105
|
data: [],
|