@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.
Files changed (101) hide show
  1. package/README.md +52 -16
  2. package/dist/consts-Bz0LZQZY.js +7 -0
  3. package/dist/consts-Bz0LZQZY.js.map +1 -0
  4. package/dist/consts-CxT1-RXU.cjs +18 -0
  5. package/dist/entity-BhSXaB23.cjs +81 -0
  6. package/dist/{query-CO6f35AE.d.cts → entity-CN41IOrq.d.cts} +125 -59
  7. package/dist/entity-CN41IOrq.d.cts.map +1 -0
  8. package/dist/entity-DGfVyB-a.js +71 -0
  9. package/dist/entity-DGfVyB-a.js.map +1 -0
  10. package/dist/{query-BhdmAzYg.d.ts → entity-NnEVlePN.d.ts} +125 -59
  11. package/dist/entity-NnEVlePN.d.ts.map +1 -0
  12. package/dist/index-ClkpS6s_.d.ts +481 -0
  13. package/dist/index-ClkpS6s_.d.ts.map +1 -0
  14. package/dist/index-w4qop0Pe.d.cts +481 -0
  15. package/dist/index-w4qop0Pe.d.cts.map +1 -0
  16. package/dist/index.cjs +285 -233
  17. package/dist/index.d.cts +100 -6
  18. package/dist/index.d.cts.map +1 -1
  19. package/dist/index.d.ts +101 -7
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +280 -230
  22. package/dist/index.js.map +1 -1
  23. package/dist/payload-BpNUPOnY.js +13 -0
  24. package/dist/payload-BpNUPOnY.js.map +1 -0
  25. package/dist/{payload-Da9gJMG5.d.ts → payload-C2KHOUoj.d.ts} +1 -1
  26. package/dist/{payload-Da9gJMG5.d.ts.map → payload-C2KHOUoj.d.ts.map} +1 -1
  27. package/dist/{payload-vwU3Rc6P.cjs → payload-D8YLKcJj.cjs} +1 -18
  28. package/dist/query/index.cjs +11 -28
  29. package/dist/query/index.d.cts +3 -3
  30. package/dist/query/index.d.ts +3 -3
  31. package/dist/query/index.js +8 -28
  32. package/dist/query/index.js.map +1 -1
  33. package/dist/{queryBuilder-DonAg1aq.js → queryBuilder-BE_XhLLa.js} +303 -152
  34. package/dist/queryBuilder-BE_XhLLa.js.map +1 -0
  35. package/dist/{queryBuilder-DM-YO1Y7.cjs → queryBuilder-J37buGCi.cjs} +372 -149
  36. package/dist/types/index.cjs +4 -1
  37. package/dist/types/index.d.cts +2 -2
  38. package/dist/types/index.d.ts +2 -2
  39. package/dist/types/index.js +4 -1
  40. package/dist/utils/index.cjs +4 -10
  41. package/dist/utils/index.d.cts +0 -1
  42. package/dist/utils/index.d.cts.map +1 -1
  43. package/dist/utils/index.d.ts +1 -2
  44. package/dist/utils/index.d.ts.map +1 -1
  45. package/dist/utils/index.js +2 -3
  46. package/dist/utils/index.js.map +1 -1
  47. package/package.json +11 -15
  48. package/src/actions/public/buildQuery.ts +4 -0
  49. package/src/actions/public/query.ts +10 -0
  50. package/src/actions/public/subscribeEntityEvents.ts +25 -51
  51. package/src/actions/wallet/changeOwnership.ts +6 -6
  52. package/src/actions/wallet/createEntity.ts +19 -6
  53. package/src/actions/wallet/deleteEntity.ts +2 -3
  54. package/src/actions/wallet/extendEntity.ts +9 -5
  55. package/src/actions/wallet/mutateEntities.ts +11 -54
  56. package/src/actions/wallet/updateEntity.ts +15 -6
  57. package/src/clients/createPublicClient.test.ts +2 -0
  58. package/src/clients/decorators/arkivPublic.ts +67 -2
  59. package/src/clients/decorators/arkivWallet.ts +23 -1
  60. package/src/consts.ts +2 -1
  61. package/src/errors.ts +46 -0
  62. package/src/index.ts +0 -3
  63. package/src/query/engine.test.ts +45 -0
  64. package/src/query/engine.ts +28 -14
  65. package/src/query/index.ts +1 -0
  66. package/src/query/predicate.test.ts +43 -0
  67. package/src/query/predicate.ts +14 -10
  68. package/src/query/queryBuilder.test.ts +22 -0
  69. package/src/query/queryBuilder.ts +221 -106
  70. package/src/query/queryResult.ts +13 -7
  71. package/src/query/selectQueryBuilder.test.ts +213 -0
  72. package/src/query/selection.test.ts +120 -0
  73. package/src/query/selection.ts +116 -0
  74. package/src/types/attributes.ts +25 -1
  75. package/src/types/entity.ts +12 -2
  76. package/src/types/events.ts +14 -16
  77. package/src/types/index.ts +1 -0
  78. package/src/types/rpcSchema.ts +7 -2
  79. package/src/utils/arkivTransactions.test.ts +260 -0
  80. package/src/utils/arkivTransactions.ts +255 -92
  81. package/src/utils/entities.ts +17 -7
  82. package/src/utils/index.ts +0 -1
  83. package/src/utils/payload.ts +4 -2
  84. package/src/utils/validation.test.ts +106 -0
  85. package/src/utils/validation.ts +80 -0
  86. package/dist/accounts/index.cjs +0 -9
  87. package/dist/accounts/index.d.cts +0 -1
  88. package/dist/accounts/index.d.ts +0 -1
  89. package/dist/accounts/index.js +0 -1
  90. package/dist/payload-DKBYYjeA.js +0 -18
  91. package/dist/payload-DKBYYjeA.js.map +0 -1
  92. package/dist/query-BhdmAzYg.d.ts.map +0 -1
  93. package/dist/query-CO6f35AE.d.cts.map +0 -1
  94. package/dist/queryBuilder-BvoQXa_u.d.ts +0 -328
  95. package/dist/queryBuilder-BvoQXa_u.d.ts.map +0 -1
  96. package/dist/queryBuilder-DonAg1aq.js.map +0 -1
  97. package/dist/queryBuilder-zTsKXctT.d.cts +0 -328
  98. package/dist/queryBuilder-zTsKXctT.d.cts.map +0 -1
  99. package/src/accounts/index.ts +0 -1
  100. /package/dist/{types-H-TSmRpm.js → types-Ci4BRClw.js} +0 -0
  101. /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<PublicActions<transport, chain, account>, "waitForTransactionReceipt" | "call"> &
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
@@ -2,9 +2,6 @@
2
2
  * @module main
3
3
  */
4
4
 
5
- // Re-export all viem stuff
6
- export * from "viem"
7
-
8
5
  // export main arkiv stuff
9
6
  export type { ArkivClient } from "./clients/baseClient"
10
7
  export type { PublicArkivClient } from "./clients/createPublicClient"
@@ -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" },
@@ -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
- return `"${value}"`
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
- key: true,
103
- attributes: withAttributes ?? false,
104
- payload: withPayload ?? false,
105
- contentType: withMetadata ?? false,
106
- expiration: withMetadata ?? false,
107
- owner: withMetadata ?? false,
108
- creator: withMetadata ?? false,
109
- createdAtBlock: withMetadata ?? false,
110
- lastModifiedAtBlock: withMetadata ?? false,
111
- transactionIndexInBlock: withMetadata ?? false,
112
- operationIndexInTransaction: withMetadata ?? false,
113
- } as RpcIncludeData,
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) {
@@ -1,3 +1,4 @@
1
1
  export * from "./predicate"
2
2
  export * from "./queryBuilder"
3
3
  export * from "./queryResult"
4
+ export * from "./selection"
@@ -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]
@@ -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 predicates = [eq("name", "John"), eq("age", 30)]
22
- * const result = or(predicates)
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
- return { type: "or", predicates }
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 predicates = [eq("name", "John"), eq("age", 30)]
37
- * const result = and(predicates)
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
- return { type: "and", predicates }
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: [],