@arkiv-network/sdk 0.6.8-dev.1 → 0.6.8-dev.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 +52 -16
- package/dist/chains/index.d.cts +25 -25
- package/dist/{query-CO6f35AE.d.cts → entity-B3qWIdug.d.cts} +26 -26
- package/dist/entity-B3qWIdug.d.cts.map +1 -0
- package/dist/{query-BhdmAzYg.d.ts → entity-BOra9GVW.d.ts} +26 -26
- package/dist/entity-BOra9GVW.d.ts.map +1 -0
- package/dist/{queryBuilder-zTsKXctT.d.cts → index-Dushe05u.d.cts} +202 -74
- package/dist/index-Dushe05u.d.cts.map +1 -0
- package/dist/{queryBuilder-BvoQXa_u.d.ts → index-t3wvdqvC.d.ts} +202 -74
- package/dist/index-t3wvdqvC.d.ts.map +1 -0
- package/dist/index.cjs +3 -8
- package/dist/index.d.cts +65 -3
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +65 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -3
- package/dist/index.js.map +1 -1
- package/dist/query/index.cjs +5 -2
- package/dist/query/index.d.cts +3 -3
- package/dist/query/index.d.ts +3 -3
- package/dist/query/index.js +2 -2
- package/dist/{queryBuilder-DM-YO1Y7.cjs → queryBuilder-BlMPfusd.cjs} +204 -70
- package/dist/{queryBuilder-DonAg1aq.js → queryBuilder-D7iYS0vU.js} +188 -72
- package/dist/queryBuilder-D7iYS0vU.js.map +1 -0
- package/dist/types/index.d.cts +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/utils/index.cjs +1 -8
- package/dist/utils/index.d.cts +0 -1
- package/dist/utils/index.d.cts.map +1 -1
- package/dist/utils/index.d.ts +0 -1
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +0 -2
- package/dist/utils/index.js.map +1 -1
- package/package.json +11 -15
- package/src/actions/public/buildQuery.ts +4 -0
- package/src/clients/createPublicClient.test.ts +2 -0
- package/src/clients/decorators/arkivPublic.ts +67 -1
- package/src/index.ts +0 -3
- package/src/query/engine.ts +22 -13
- package/src/query/index.ts +1 -0
- package/src/query/queryBuilder.ts +189 -94
- package/src/query/queryResult.ts +13 -7
- package/src/query/selectQueryBuilder.test.ts +210 -0
- package/src/query/selection.test.ts +120 -0
- package/src/query/selection.ts +116 -0
- package/src/utils/index.ts +0 -1
- 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/query-BhdmAzYg.d.ts.map +0 -1
- package/dist/query-CO6f35AE.d.cts.map +0 -1
- package/dist/queryBuilder-BvoQXa_u.d.ts.map +0 -1
- package/dist/queryBuilder-DonAg1aq.js.map +0 -1
- package/dist/queryBuilder-zTsKXctT.d.cts.map +0 -1
- package/src/accounts/index.ts +0 -1
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import type { ArkivClient } from "../../clients/baseClient"
|
|
2
2
|
import { QueryBuilder } from "../../query/queryBuilder"
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* @deprecated Use `client.select()` (see SelectQueryBuilder) instead. `buildQuery()` returns
|
|
6
|
+
* only the entity `key` unless data is explicitly opted in to, which is an easy mistake.
|
|
7
|
+
*/
|
|
4
8
|
export async function buildQuery(client: ArkivClient) {
|
|
5
9
|
return new QueryBuilder(client)
|
|
6
10
|
}
|
|
@@ -20,6 +20,8 @@ test("creates", () => {
|
|
|
20
20
|
expect(typeof client.getEntity).toBe("function")
|
|
21
21
|
expect(client.buildQuery).toBeDefined()
|
|
22
22
|
expect(typeof client.buildQuery).toBe("function")
|
|
23
|
+
expect(client.select).toBeDefined()
|
|
24
|
+
expect(typeof client.select).toBe("function")
|
|
23
25
|
expect(client.query).toBeDefined()
|
|
24
26
|
expect(typeof client.query).toBe("function")
|
|
25
27
|
expect(client.getEntityCount).toBeDefined()
|
|
@@ -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
|
|
@@ -200,6 +265,7 @@ export function publicArkivActions<
|
|
|
200
265
|
getEntity: (key: Hex) => getEntity(client, key),
|
|
201
266
|
query: (rawQuery: string, queryOptions?: QueryOptions) => query(client, rawQuery, queryOptions),
|
|
202
267
|
buildQuery: () => new QueryBuilder(client),
|
|
268
|
+
select: (selection?: SelectArg) => new SelectQueryBuilder(client, selection),
|
|
203
269
|
getBlockTiming: () => getBlockTiming(client),
|
|
204
270
|
getEntityCount: () => getEntityCount(client),
|
|
205
271
|
subscribeEntityEvents: (
|
package/src/index.ts
CHANGED
package/src/query/engine.ts
CHANGED
|
@@ -55,6 +55,11 @@ export async function processQuery(
|
|
|
55
55
|
withAttributes?: boolean | undefined
|
|
56
56
|
withMetadata?: boolean | undefined
|
|
57
57
|
withPayload?: boolean | undefined
|
|
58
|
+
/**
|
|
59
|
+
* Fully-resolved include-data for fine-grained selection. When provided it takes
|
|
60
|
+
* precedence over the `withAttributes`/`withMetadata`/`withPayload` booleans.
|
|
61
|
+
*/
|
|
62
|
+
includeData?: RpcIncludeData | undefined
|
|
58
63
|
},
|
|
59
64
|
) {
|
|
60
65
|
const {
|
|
@@ -68,6 +73,7 @@ export async function processQuery(
|
|
|
68
73
|
withAttributes,
|
|
69
74
|
withMetadata,
|
|
70
75
|
withPayload,
|
|
76
|
+
includeData,
|
|
71
77
|
} = queryParams
|
|
72
78
|
|
|
73
79
|
logger("Processing query with params %o", {
|
|
@@ -81,6 +87,7 @@ export async function processQuery(
|
|
|
81
87
|
withAttributes,
|
|
82
88
|
withMetadata,
|
|
83
89
|
withPayload,
|
|
90
|
+
includeData,
|
|
84
91
|
})
|
|
85
92
|
|
|
86
93
|
let query = processPredicates(predicates)
|
|
@@ -98,19 +105,21 @@ export async function processQuery(
|
|
|
98
105
|
}
|
|
99
106
|
|
|
100
107
|
const queryOptions: RpcQueryOptions = {
|
|
101
|
-
includeData:
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
108
|
+
includeData:
|
|
109
|
+
includeData ??
|
|
110
|
+
({
|
|
111
|
+
key: true,
|
|
112
|
+
attributes: withAttributes ?? false,
|
|
113
|
+
payload: withPayload ?? false,
|
|
114
|
+
contentType: withMetadata ?? false,
|
|
115
|
+
expiration: withMetadata ?? false,
|
|
116
|
+
owner: withMetadata ?? false,
|
|
117
|
+
creator: withMetadata ?? false,
|
|
118
|
+
createdAtBlock: withMetadata ?? false,
|
|
119
|
+
lastModifiedAtBlock: withMetadata ?? false,
|
|
120
|
+
transactionIndexInBlock: withMetadata ?? false,
|
|
121
|
+
operationIndexInTransaction: withMetadata ?? false,
|
|
122
|
+
} as RpcIncludeData),
|
|
114
123
|
}
|
|
115
124
|
|
|
116
125
|
if (validAtBlock !== undefined) {
|
package/src/query/index.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import type { Hex } from "viem"
|
|
2
2
|
import type { ArkivClient } from "../clients/baseClient"
|
|
3
|
-
import type {
|
|
3
|
+
import type { Entity } from "../types/entity"
|
|
4
|
+
import type { RpcEntity, RpcIncludeData, RpcOrderByAttribute } from "../types/rpcSchema"
|
|
4
5
|
import { entityFromRpcResult } from "../utils/entities"
|
|
5
6
|
import { processQuery } from "./engine"
|
|
6
7
|
import type { Predicate } from "./predicate"
|
|
7
8
|
import { QueryResult } from "./queryResult"
|
|
9
|
+
import { type SelectArg, selectionToIncludeData } from "./selection"
|
|
8
10
|
|
|
9
11
|
export type OrderByAttribute = {
|
|
10
12
|
name: string
|
|
@@ -45,24 +47,37 @@ export function desc(attributeName: string, attributeType: "string" | "number"):
|
|
|
45
47
|
order: "desc",
|
|
46
48
|
}
|
|
47
49
|
}
|
|
50
|
+
|
|
48
51
|
/**
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
* @param client - The Arkiv client
|
|
52
|
-
* @returns The QueryBuilder instance {@link QueryBuilder}
|
|
52
|
+
* Data-selection parameters forwarded to the query engine on each fetch.
|
|
53
|
+
* Subclasses of {@link BaseQueryBuilder} decide which parts of an entity are fetched.
|
|
53
54
|
*/
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
55
|
+
type SelectionParams = {
|
|
56
|
+
withAttributes?: boolean | undefined
|
|
57
|
+
withMetadata?: boolean | undefined
|
|
58
|
+
withPayload?: boolean | undefined
|
|
59
|
+
includeData?: RpcIncludeData | undefined
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* BaseQueryBuilder holds the query-building logic shared by every query builder
|
|
64
|
+
* (filtering, ordering, pagination, execution). It follows the Builder pattern,
|
|
65
|
+
* allowing methods to be chained. Subclasses decide how data selection is expressed
|
|
66
|
+
* by implementing the protected `selectionParams` method.
|
|
67
|
+
*
|
|
68
|
+
* Use {@link SelectQueryBuilder} via `client.select()` to build and execute queries.
|
|
69
|
+
*
|
|
70
|
+
* @typeParam TEntity - The shape of each entity produced by {@link BaseQueryBuilder.fetch}.
|
|
71
|
+
*/
|
|
72
|
+
export abstract class BaseQueryBuilder<TEntity> {
|
|
73
|
+
protected _client: ArkivClient
|
|
74
|
+
protected _ownedBy: Hex | undefined
|
|
75
|
+
protected _createdBy: Hex | undefined
|
|
76
|
+
protected _orderBy: RpcOrderByAttribute[] | undefined
|
|
77
|
+
protected _validAtBlock: bigint | undefined
|
|
78
|
+
protected _limit: number | undefined
|
|
79
|
+
protected _cursor: string | undefined
|
|
80
|
+
protected _predicates: Predicate[]
|
|
66
81
|
|
|
67
82
|
constructor(client: ArkivClient) {
|
|
68
83
|
this._client = client
|
|
@@ -72,13 +87,13 @@ export class QueryBuilder {
|
|
|
72
87
|
/**
|
|
73
88
|
* Sets the ownedBy filter
|
|
74
89
|
* @param ownedBy - The address of the owner
|
|
75
|
-
* @returns The
|
|
90
|
+
* @returns The query builder instance
|
|
76
91
|
*
|
|
77
92
|
* @example
|
|
78
|
-
* const builder =
|
|
93
|
+
* const builder = client.select()
|
|
79
94
|
* builder.ownedBy("0x1234567890123456789012345678901234567890")
|
|
80
95
|
*/
|
|
81
|
-
ownedBy(ownedBy: Hex) {
|
|
96
|
+
ownedBy(ownedBy: Hex): this {
|
|
82
97
|
this._ownedBy = ownedBy
|
|
83
98
|
return this
|
|
84
99
|
}
|
|
@@ -86,13 +101,13 @@ export class QueryBuilder {
|
|
|
86
101
|
/**
|
|
87
102
|
* Sets the createdBy filter
|
|
88
103
|
* @param createdBy - The address of the creator
|
|
89
|
-
* @returns The
|
|
104
|
+
* @returns The query builder instance
|
|
90
105
|
*
|
|
91
106
|
* @example
|
|
92
|
-
* const builder =
|
|
107
|
+
* const builder = client.select()
|
|
93
108
|
* builder.createdBy("0x1234567890123456789012345678901234567890")
|
|
94
109
|
*/
|
|
95
|
-
createdBy(createdBy: Hex) {
|
|
110
|
+
createdBy(createdBy: Hex): this {
|
|
96
111
|
this._createdBy = createdBy
|
|
97
112
|
return this
|
|
98
113
|
}
|
|
@@ -105,10 +120,10 @@ export class QueryBuilder {
|
|
|
105
120
|
* @param attributeName - The name of the attribute to order by
|
|
106
121
|
* @param attributeType - The type of the attribute to order by (string or number)
|
|
107
122
|
* @param order - The order to set the order by (asc or desc)
|
|
108
|
-
* @returns The
|
|
123
|
+
* @returns The query builder instance
|
|
109
124
|
*
|
|
110
125
|
* @example
|
|
111
|
-
* const builder = client.
|
|
126
|
+
* const builder = client.select()
|
|
112
127
|
* builder.orderBy("name", "string", "desc")
|
|
113
128
|
* builder.orderBy(asc("name", "string"))
|
|
114
129
|
* builder.orderBy(desc("name", "string"))
|
|
@@ -119,10 +134,10 @@ export class QueryBuilder {
|
|
|
119
134
|
* This method takes the OrderByAttribute object as an argument and is mainly
|
|
120
135
|
* used to use the helper functions asc() and desc() to create the OrderByAttribute instances.
|
|
121
136
|
* @param orderByAttribute - The OrderByAttribute instance to set
|
|
122
|
-
* @returns The
|
|
137
|
+
* @returns The query builder instance
|
|
123
138
|
*
|
|
124
139
|
* @example
|
|
125
|
-
* const builder =
|
|
140
|
+
* const builder = client.select()
|
|
126
141
|
* builder.orderBy(asc("name", "string"))
|
|
127
142
|
* builder.orderBy(desc("name", "string"))
|
|
128
143
|
*/
|
|
@@ -131,7 +146,7 @@ export class QueryBuilder {
|
|
|
131
146
|
attributeNameOrOrderByAttribute: string | OrderByAttribute,
|
|
132
147
|
attributeType?: "string" | "number",
|
|
133
148
|
order: "asc" | "desc" = "asc",
|
|
134
|
-
) {
|
|
149
|
+
): this {
|
|
135
150
|
if (!this._orderBy) {
|
|
136
151
|
this._orderBy = []
|
|
137
152
|
}
|
|
@@ -160,58 +175,16 @@ export class QueryBuilder {
|
|
|
160
175
|
return this
|
|
161
176
|
}
|
|
162
177
|
|
|
163
|
-
/**
|
|
164
|
-
* Sets the withAttributes flag which will return the attributes for the entities if true
|
|
165
|
-
* @param withAttributes - The boolean value to set
|
|
166
|
-
* @returns The QueryBuilder instance
|
|
167
|
-
*
|
|
168
|
-
* @example
|
|
169
|
-
* const builder = new QueryBuilder(client)
|
|
170
|
-
* builder.withAttributes(true)
|
|
171
|
-
*/
|
|
172
|
-
withAttributes(withAttributes: boolean = true) {
|
|
173
|
-
this._withAttributes = withAttributes
|
|
174
|
-
return this
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
/**
|
|
178
|
-
* Sets the withMetadata flag which will return the metadata (like owner, expiredAt, etc.) for the entities if true
|
|
179
|
-
* @param withMetadata - The boolean value to set
|
|
180
|
-
* @returns The QueryBuilder instance
|
|
181
|
-
*
|
|
182
|
-
* @example
|
|
183
|
-
* const builder = new QueryBuilder(client)
|
|
184
|
-
* builder.withMetadata(true)
|
|
185
|
-
*/
|
|
186
|
-
withMetadata(withMetadata: boolean = true) {
|
|
187
|
-
this._withMetadata = withMetadata
|
|
188
|
-
return this
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
/**
|
|
192
|
-
* Sets the withPayload flag which will return the payload for the entities if true
|
|
193
|
-
* @param withPayload - The boolean value to set
|
|
194
|
-
* @returns The QueryBuilder instance
|
|
195
|
-
*
|
|
196
|
-
* @example
|
|
197
|
-
* const builder = new QueryBuilder(client)
|
|
198
|
-
* builder.withPayload(true)
|
|
199
|
-
*/
|
|
200
|
-
withPayload(withPayload: boolean = true) {
|
|
201
|
-
this._withPayload = withPayload
|
|
202
|
-
return this
|
|
203
|
-
}
|
|
204
|
-
|
|
205
178
|
/**
|
|
206
179
|
* Sets the limit for the query
|
|
207
180
|
* @param limit - The number of entities to return
|
|
208
|
-
* @returns The
|
|
181
|
+
* @returns The query builder instance
|
|
209
182
|
*
|
|
210
183
|
* @example
|
|
211
|
-
* const builder =
|
|
184
|
+
* const builder = client.select()
|
|
212
185
|
* builder.limit(10)
|
|
213
186
|
*/
|
|
214
|
-
limit(limit: number) {
|
|
187
|
+
limit(limit: number): this {
|
|
215
188
|
this._limit = limit
|
|
216
189
|
return this
|
|
217
190
|
}
|
|
@@ -219,13 +192,13 @@ export class QueryBuilder {
|
|
|
219
192
|
/**
|
|
220
193
|
* Sets the cursor for the query - it is advances setting which rather shouldn't be used manually but it is provided from query result if limit is used (pagination).
|
|
221
194
|
* @param cursor - The cursor to set which tells to RPC Query server where to start or continue the query.
|
|
222
|
-
* @returns The
|
|
195
|
+
* @returns The query builder instance
|
|
223
196
|
*
|
|
224
197
|
* @example
|
|
225
|
-
* const builder =
|
|
226
|
-
* builder.
|
|
198
|
+
* const builder = client.select()
|
|
199
|
+
* builder.cursor("0xABC123")
|
|
227
200
|
*/
|
|
228
|
-
cursor(cursor: string) {
|
|
201
|
+
cursor(cursor: string): this {
|
|
229
202
|
this._cursor = cursor
|
|
230
203
|
return this
|
|
231
204
|
}
|
|
@@ -234,13 +207,13 @@ export class QueryBuilder {
|
|
|
234
207
|
* Sets the validAtBlock for the query which tells at which block height the state we are intested.
|
|
235
208
|
* If not set, the latest block is used.
|
|
236
209
|
* @param validAtBlock - The block number to set
|
|
237
|
-
* @returns The
|
|
210
|
+
* @returns The query builder instance
|
|
238
211
|
*
|
|
239
212
|
* @example
|
|
240
|
-
* const builder =
|
|
213
|
+
* const builder = client.select()
|
|
241
214
|
* builder.validAtBlock(10000)
|
|
242
215
|
*/
|
|
243
|
-
validAtBlock(validAtBlock: bigint) {
|
|
216
|
+
validAtBlock(validAtBlock: bigint): this {
|
|
244
217
|
this._validAtBlock = validAtBlock
|
|
245
218
|
return this
|
|
246
219
|
}
|
|
@@ -249,10 +222,10 @@ export class QueryBuilder {
|
|
|
249
222
|
* Sets the predicates for the query limiting the results. It can be a single predicate or an array of predicates combined with 'and'.
|
|
250
223
|
* Predicates can be nested using 'or' and 'and' predicates.
|
|
251
224
|
* @param predicates - The predicates to set
|
|
252
|
-
* @returns The
|
|
225
|
+
* @returns The query builder instance
|
|
253
226
|
*
|
|
254
227
|
* @example
|
|
255
|
-
* const builder =
|
|
228
|
+
* const builder = client.select()
|
|
256
229
|
* builder.where(eq("name", "John"))
|
|
257
230
|
* builder.where([eq("name", "John"), eq("age", 30)])
|
|
258
231
|
* builder.where([eq("name", "John"), or([eq("age", 30), eq("age", 31)])])
|
|
@@ -261,7 +234,7 @@ export class QueryBuilder {
|
|
|
261
234
|
* builder.where([eq("name", "John"), and([eq("age", 30), or([eq("age", 31), eq("age", 32)])])])
|
|
262
235
|
* builder.where([eq("name", "John"), and([eq("age", 30), or([eq("age", 31), and([eq("age", 32), eq("age", 33)])])])])
|
|
263
236
|
*/
|
|
264
|
-
where(predicates: Predicate[] | Predicate) {
|
|
237
|
+
where(predicates: Predicate[] | Predicate): this {
|
|
265
238
|
if (Array.isArray(predicates)) {
|
|
266
239
|
this._predicates.push(...predicates)
|
|
267
240
|
} else {
|
|
@@ -271,16 +244,28 @@ export class QueryBuilder {
|
|
|
271
244
|
}
|
|
272
245
|
|
|
273
246
|
/**
|
|
274
|
-
*
|
|
247
|
+
* Returns the data-selection parameters forwarded to the query engine.
|
|
248
|
+
* Subclasses decide which parts of an entity are fetched.
|
|
249
|
+
*/
|
|
250
|
+
protected abstract selectionParams(): SelectionParams
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Builds a single result entity from a raw RPC entity. Subclasses decide the produced shape
|
|
254
|
+
* (a full {@link Entity}, or a projected object inferred from the selection).
|
|
255
|
+
*/
|
|
256
|
+
protected abstract projectEntity(rpcEntity: RpcEntity): TEntity | Promise<TEntity>
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Fetches the entities from the query.
|
|
275
260
|
* It will return a QueryResult instance which can be used to fetch the next and previous pages.
|
|
276
261
|
* @returns The QueryResult instance {@link QueryResult}
|
|
277
262
|
*
|
|
278
263
|
* @example
|
|
279
|
-
* const builder =
|
|
264
|
+
* const builder = client.select()
|
|
280
265
|
* const result = await builder.where(eq("name", "John")).fetch()
|
|
281
266
|
* // result = { entities: [Entity, Entity, Entity], next: async () => QueryResult, previous: async () => QueryResult }
|
|
282
267
|
*/
|
|
283
|
-
async fetch() {
|
|
268
|
+
async fetch(): Promise<QueryResult<TEntity>> {
|
|
284
269
|
const queryResult = await processQuery(this._client, {
|
|
285
270
|
predicates: this._predicates,
|
|
286
271
|
limit: this._limit,
|
|
@@ -289,19 +274,15 @@ export class QueryBuilder {
|
|
|
289
274
|
createdBy: this._createdBy,
|
|
290
275
|
orderBy: this._orderBy,
|
|
291
276
|
validAtBlock: this._validAtBlock,
|
|
292
|
-
|
|
293
|
-
withMetadata: this._withMetadata,
|
|
294
|
-
withPayload: this._withPayload,
|
|
277
|
+
...this.selectionParams(),
|
|
295
278
|
})
|
|
296
279
|
|
|
297
|
-
const entities = await Promise.all(
|
|
298
|
-
queryResult.data.map((entity) => entityFromRpcResult(entity)),
|
|
299
|
-
)
|
|
280
|
+
const entities = await Promise.all(queryResult.data.map((entity) => this.projectEntity(entity)))
|
|
300
281
|
|
|
301
282
|
this.cursor(queryResult.cursor)
|
|
302
283
|
this.validAtBlock(BigInt(queryResult.blockNumber ?? 0))
|
|
303
284
|
|
|
304
|
-
return new QueryResult(entities, this, this._cursor, this._limit, this._validAtBlock)
|
|
285
|
+
return new QueryResult<TEntity>(entities, this, this._cursor, this._limit, this._validAtBlock)
|
|
305
286
|
}
|
|
306
287
|
|
|
307
288
|
/**
|
|
@@ -309,7 +290,7 @@ export class QueryBuilder {
|
|
|
309
290
|
* @returns The number of entities
|
|
310
291
|
*
|
|
311
292
|
* @example
|
|
312
|
-
* const builder =
|
|
293
|
+
* const builder = client.select()
|
|
313
294
|
* const result = await builder.where(eq("name", "John")).count()
|
|
314
295
|
* // result = 10
|
|
315
296
|
*/
|
|
@@ -330,3 +311,117 @@ export class QueryBuilder {
|
|
|
330
311
|
return queryResult.data.length ?? 0
|
|
331
312
|
}
|
|
332
313
|
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* QueryBuilder is a helper class to build queries to the Arkiv DBChains.
|
|
317
|
+
* It can be used to fetch entities from the Arkiv DBChains. It follows the Builder pattern allowing chaining of methods.
|
|
318
|
+
*
|
|
319
|
+
* By default the result includes only the entity `key`. Additional data is opt-in through
|
|
320
|
+
* `withAttributes()`, `withMetadata()` and `withPayload()`.
|
|
321
|
+
*
|
|
322
|
+
* @deprecated Use {@link SelectQueryBuilder} via `client.select()` instead. Declaring the
|
|
323
|
+
* selection up front avoids the common mistake of forgetting to opt in to data and getting back
|
|
324
|
+
* entities with only their `key` populated. This class remains for backwards compatibility and
|
|
325
|
+
* will be removed in a future release.
|
|
326
|
+
*
|
|
327
|
+
* @param client - The Arkiv client
|
|
328
|
+
* @returns The QueryBuilder instance {@link QueryBuilder}
|
|
329
|
+
*/
|
|
330
|
+
export class QueryBuilder extends BaseQueryBuilder<Entity> {
|
|
331
|
+
private _withAttributes: boolean | undefined
|
|
332
|
+
private _withMetadata: boolean | undefined
|
|
333
|
+
private _withPayload: boolean | undefined
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Sets the withAttributes flag which will return the attributes for the entities if true
|
|
337
|
+
* @param withAttributes - The boolean value to set
|
|
338
|
+
* @returns The QueryBuilder instance
|
|
339
|
+
*
|
|
340
|
+
* @example
|
|
341
|
+
* const builder = client.buildQuery()
|
|
342
|
+
* builder.withAttributes(true)
|
|
343
|
+
*/
|
|
344
|
+
withAttributes(withAttributes: boolean = true): this {
|
|
345
|
+
this._withAttributes = withAttributes
|
|
346
|
+
return this
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Sets the withMetadata flag which will return the metadata (like owner, expiredAt, etc.) for the entities if true
|
|
351
|
+
* @param withMetadata - The boolean value to set
|
|
352
|
+
* @returns The QueryBuilder instance
|
|
353
|
+
*
|
|
354
|
+
* @example
|
|
355
|
+
* const builder = client.buildQuery()
|
|
356
|
+
* builder.withMetadata(true)
|
|
357
|
+
*/
|
|
358
|
+
withMetadata(withMetadata: boolean = true): this {
|
|
359
|
+
this._withMetadata = withMetadata
|
|
360
|
+
return this
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Sets the withPayload flag which will return the payload for the entities if true
|
|
365
|
+
* @param withPayload - The boolean value to set
|
|
366
|
+
* @returns The QueryBuilder instance
|
|
367
|
+
*
|
|
368
|
+
* @example
|
|
369
|
+
* const builder = client.buildQuery()
|
|
370
|
+
* builder.withPayload(true)
|
|
371
|
+
*/
|
|
372
|
+
withPayload(withPayload: boolean = true): this {
|
|
373
|
+
this._withPayload = withPayload
|
|
374
|
+
return this
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
protected selectionParams(): SelectionParams {
|
|
378
|
+
return {
|
|
379
|
+
withAttributes: this._withAttributes,
|
|
380
|
+
withMetadata: this._withMetadata,
|
|
381
|
+
withPayload: this._withPayload,
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
protected projectEntity(rpcEntity: RpcEntity): Promise<Entity> {
|
|
386
|
+
return entityFromRpcResult(rpcEntity)
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* SelectQueryBuilder is the recommended query builder. It requires the selection to be declared
|
|
392
|
+
* up front, so results always contain exactly the data you asked for — and the type of each
|
|
393
|
+
* returned entity is narrowed to exactly the selected fields.
|
|
394
|
+
*
|
|
395
|
+
* The selection is fixed at construction time and is flat — each field maps to an entity field.
|
|
396
|
+
* Create one via `client.select(...)` rather than constructing it directly, so the result type is
|
|
397
|
+
* inferred from the selection.
|
|
398
|
+
*
|
|
399
|
+
* @typeParam TEntity - The projected entity shape, inferred from the selection by `client.select()`.
|
|
400
|
+
*
|
|
401
|
+
* @example
|
|
402
|
+
* // everything
|
|
403
|
+
* await client.select().where(eq("name", "John")).fetch()
|
|
404
|
+
* await client.select("*").where(eq("name", "John")).fetch()
|
|
405
|
+
* // specific fields
|
|
406
|
+
* await client.select({ owner: true, attributes: true }).fetch()
|
|
407
|
+
* // a single field — result is typed (flat) { owner: Hex }
|
|
408
|
+
* await client.select({ owner: true }).fetch()
|
|
409
|
+
*/
|
|
410
|
+
export class SelectQueryBuilder<TEntity> extends BaseQueryBuilder<TEntity> {
|
|
411
|
+
private _includeData: RpcIncludeData
|
|
412
|
+
|
|
413
|
+
constructor(client: ArkivClient, selection?: SelectArg) {
|
|
414
|
+
super(client)
|
|
415
|
+
this._includeData = selectionToIncludeData(selection)
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
protected selectionParams(): SelectionParams {
|
|
419
|
+
return { includeData: this._includeData }
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
protected async projectEntity(rpcEntity: RpcEntity): Promise<TEntity> {
|
|
423
|
+
// The runtime value is a full Entity (with methods); the static type is narrowed to the
|
|
424
|
+
// selected fields via `client.select()`'s inference.
|
|
425
|
+
return (await entityFromRpcResult(rpcEntity)) as unknown as TEntity
|
|
426
|
+
}
|
|
427
|
+
}
|
package/src/query/queryResult.ts
CHANGED
|
@@ -1,20 +1,26 @@
|
|
|
1
1
|
import { NoCursorOrLimitError, NoMoreResultsError } from "../errors"
|
|
2
2
|
import type { Entity } from "../types/entity"
|
|
3
3
|
import { getLogger } from "../utils/logger"
|
|
4
|
-
import type {
|
|
4
|
+
import type { BaseQueryBuilder } from "./queryBuilder"
|
|
5
5
|
|
|
6
6
|
const logger = getLogger("query:result")
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
/**
|
|
9
|
+
* The result of a query. Holds the fetched entities and supports cursor-based pagination.
|
|
10
|
+
*
|
|
11
|
+
* @typeParam TEntity - The shape of each entity, inferred from the query builder
|
|
12
|
+
* (a full {@link Entity}, or a projected object inferred from a `select()` selection).
|
|
13
|
+
*/
|
|
14
|
+
export class QueryResult<TEntity = Entity> {
|
|
15
|
+
entities: TEntity[]
|
|
10
16
|
private _endOfIteration: boolean
|
|
11
17
|
private _cursor: string | undefined
|
|
12
18
|
private _limit: number | undefined
|
|
13
19
|
private _validAtBlock: bigint | undefined
|
|
14
|
-
private _queryBuilder:
|
|
20
|
+
private _queryBuilder: BaseQueryBuilder<TEntity>
|
|
15
21
|
|
|
16
22
|
// Public getters for internal state
|
|
17
|
-
get queryBuilder():
|
|
23
|
+
get queryBuilder(): BaseQueryBuilder<TEntity> {
|
|
18
24
|
return this._queryBuilder
|
|
19
25
|
}
|
|
20
26
|
|
|
@@ -23,8 +29,8 @@ export class QueryResult {
|
|
|
23
29
|
}
|
|
24
30
|
|
|
25
31
|
constructor(
|
|
26
|
-
entities:
|
|
27
|
-
queryBuilder:
|
|
32
|
+
entities: TEntity[],
|
|
33
|
+
queryBuilder: BaseQueryBuilder<TEntity>,
|
|
28
34
|
cursor: string | undefined,
|
|
29
35
|
limit: number | undefined,
|
|
30
36
|
validAtBlock: bigint | undefined,
|