@arkiv-network/sdk 0.1.1-dev.0 → 0.1.2-dev.0

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 (63) hide show
  1. package/README.md +22 -10
  2. package/dist/chains/index.cjs +1 -0
  3. package/dist/chains/index.d.ts +25 -25
  4. package/dist/chains/index.d.ts.map +1 -1
  5. package/dist/index.cjs +40 -10
  6. package/dist/index.js +13 -8
  7. package/dist/index.js.map +1 -1
  8. package/dist/query/index.cjs +1 -1
  9. package/dist/{queryBuilder-n9ocnT4P.cjs → queryBuilder-okYHE7jd.cjs} +1 -0
  10. package/dist/utils/index.cjs +1 -0
  11. package/dist/utils/index.d.cts.map +1 -1
  12. package/dist/utils/index.d.ts.map +1 -1
  13. package/package.json +22 -8
  14. package/src/accounts/index.ts +1 -0
  15. package/src/actions/public/buildQuery.ts +6 -0
  16. package/src/actions/public/getBlockTiming.ts +20 -0
  17. package/src/actions/public/getEntity.ts +18 -0
  18. package/src/actions/public/getEntityCount.ts +10 -0
  19. package/src/actions/public/query.ts +13 -0
  20. package/src/actions/public/subscribeEntityEvents.ts +105 -0
  21. package/src/actions/wallet/changeOwnership.ts +40 -0
  22. package/src/actions/wallet/createEntity.ts +45 -0
  23. package/src/actions/wallet/deleteEntity.ts +39 -0
  24. package/src/actions/wallet/extendEntity.ts +41 -0
  25. package/src/actions/wallet/mutateEntities.ts +100 -0
  26. package/src/actions/wallet/updateEntity.ts +47 -0
  27. package/src/chains/index.ts +3 -0
  28. package/src/chains/kaolin.ts +26 -0
  29. package/src/chains/localhost.ts +14 -0
  30. package/src/chains/marketplace.ts +26 -0
  31. package/src/clients/baseClient.ts +9 -0
  32. package/src/clients/createPublicClient.test.ts +22 -0
  33. package/src/clients/createPublicClient.ts +65 -0
  34. package/src/clients/createWalletClient.test.ts +23 -0
  35. package/src/clients/createWalletClient.ts +70 -0
  36. package/src/clients/decorators/arkivPublic.ts +203 -0
  37. package/src/clients/decorators/arkivWallet.ts +250 -0
  38. package/src/consts.ts +2 -0
  39. package/src/index.ts +21 -0
  40. package/src/query/engine.test.ts +288 -0
  41. package/src/query/engine.ts +112 -0
  42. package/src/query/errors.ts +23 -0
  43. package/src/query/index.ts +4 -0
  44. package/src/query/predicate.test.ts +418 -0
  45. package/src/query/predicate.ts +140 -0
  46. package/src/query/queryBuilder.test.ts +537 -0
  47. package/src/query/queryBuilder.ts +209 -0
  48. package/src/query/queryResult.test.ts +270 -0
  49. package/src/query/queryResult.ts +61 -0
  50. package/src/types/attributes.ts +4 -0
  51. package/src/types/entity.ts +36 -0
  52. package/src/types/events.ts +34 -0
  53. package/src/types/index.ts +4 -0
  54. package/src/types/mimeTypes.ts +27 -0
  55. package/src/types/rpcSchema.ts +61 -0
  56. package/src/types/txParams.ts +15 -0
  57. package/src/utils/arkivTransactions.ts +100 -0
  58. package/src/utils/compression.test.ts +32 -0
  59. package/src/utils/compression.ts +48 -0
  60. package/src/utils/entities.ts +23 -0
  61. package/src/utils/expirationTime.ts +13 -0
  62. package/src/utils/index.ts +3 -0
  63. package/src/utils/payload.ts +9 -0
@@ -0,0 +1,537 @@
1
+ import { afterEach, beforeEach, describe, expect, jest, test } from "bun:test"
2
+ import type { ArkivClient } from "../clients/baseClient"
3
+ import { Entity } from "../types/entity"
4
+ import * as entitiesUtils from "../utils/entities"
5
+ import * as engine from "./engine"
6
+ import { and, eq, gt, gte, neq, or } from "./predicate"
7
+ import { QueryBuilder } from "./queryBuilder"
8
+
9
+ describe("QueryBuilder", () => {
10
+ let mockClient: ArkivClient
11
+ let mockProcessQuery: any
12
+ let mockEntityFromRpcResult: any
13
+
14
+ beforeEach(() => {
15
+ // Create a mock client
16
+ mockClient = {
17
+ request: jest.fn(),
18
+ } as unknown as ArkivClient
19
+
20
+ // Spy on the functions instead of mocking the module
21
+ mockProcessQuery = jest.spyOn(engine, "processQuery")
22
+ mockEntityFromRpcResult = jest.spyOn(entitiesUtils, "entityFromRpcResult")
23
+ })
24
+
25
+ afterEach(() => {
26
+ // Restore all spies to prevent test interference
27
+ jest.restoreAllMocks()
28
+ })
29
+
30
+ test("creates QueryBuilder with empty predicates", () => {
31
+ const builder = new QueryBuilder(mockClient)
32
+ expect(builder).toBeDefined()
33
+ })
34
+
35
+ describe("where() method", () => {
36
+ test("adds single predicate", async () => {
37
+ mockProcessQuery.mockResolvedValue({
38
+ data: [],
39
+ })
40
+
41
+ const builder = new QueryBuilder(mockClient)
42
+ await builder.where(eq("name", "test")).fetch()
43
+
44
+ expect(mockProcessQuery).toHaveBeenCalledWith(mockClient, {
45
+ predicates: [eq("name", "test")],
46
+ limit: undefined,
47
+ offset: undefined,
48
+ ownedBy: undefined,
49
+ validBeforeBlock: undefined,
50
+ withAttributes: undefined,
51
+ withMetadata: undefined,
52
+ withPayload: undefined,
53
+ })
54
+ })
55
+
56
+ test("adds multiple predicates as array", async () => {
57
+ mockProcessQuery.mockResolvedValue({
58
+ data: [],
59
+ })
60
+
61
+ const builder = new QueryBuilder(mockClient)
62
+ const predicates = [eq("name", "test"), gt("age", 18)]
63
+ await builder.where(predicates).fetch()
64
+
65
+ expect(mockProcessQuery).toHaveBeenCalledWith(mockClient, {
66
+ predicates: predicates,
67
+ limit: undefined,
68
+ offset: undefined,
69
+ ownedBy: undefined,
70
+ validBeforeBlock: undefined,
71
+ withAttributes: undefined,
72
+ withMetadata: undefined,
73
+ withPayload: undefined,
74
+ })
75
+ })
76
+
77
+ test("chains multiple where() calls", async () => {
78
+ mockProcessQuery.mockResolvedValue({
79
+ data: [],
80
+ })
81
+
82
+ const builder = new QueryBuilder(mockClient)
83
+ await builder
84
+ .where(eq("name", "test"))
85
+ .where(gt("age", 18))
86
+ .where([neq("status", "inactive"), eq("verified", 1)])
87
+ .fetch()
88
+
89
+ expect(mockProcessQuery).toHaveBeenCalledWith(mockClient, {
90
+ predicates: [
91
+ eq("name", "test"),
92
+ gt("age", 18),
93
+ neq("status", "inactive"),
94
+ eq("verified", 1),
95
+ ],
96
+ limit: undefined,
97
+ offset: undefined,
98
+ ownedBy: undefined,
99
+ validBeforeBlock: undefined,
100
+ withAttributes: undefined,
101
+ withMetadata: undefined,
102
+ withPayload: undefined,
103
+ })
104
+ })
105
+
106
+ test("handles complex predicates with or/and", async () => {
107
+ mockProcessQuery.mockResolvedValue({
108
+ data: [],
109
+ })
110
+
111
+ const builder = new QueryBuilder(mockClient)
112
+ const complexPredicate = or([eq("type", "admin"), eq("type", "moderator")])
113
+ await builder.where(complexPredicate).fetch()
114
+
115
+ expect(mockProcessQuery).toHaveBeenCalledWith(mockClient, {
116
+ predicates: [complexPredicate],
117
+ limit: undefined,
118
+ offset: undefined,
119
+ ownedBy: undefined,
120
+ validBeforeBlock: undefined,
121
+ withAttributes: undefined,
122
+ withMetadata: undefined,
123
+ withPayload: undefined,
124
+ })
125
+ })
126
+
127
+ test("handles nested or/and predicates", async () => {
128
+ mockProcessQuery.mockResolvedValue({
129
+ data: [],
130
+ })
131
+
132
+ const builder = new QueryBuilder(mockClient)
133
+ const nestedPredicate = and([
134
+ eq("active", 1),
135
+ or([eq("role", "admin"), eq("role", "moderator")]),
136
+ ])
137
+ await builder.where(nestedPredicate).fetch()
138
+
139
+ expect(mockProcessQuery).toHaveBeenCalledWith(mockClient, {
140
+ predicates: [nestedPredicate],
141
+ limit: undefined,
142
+ offset: undefined,
143
+ ownedBy: undefined,
144
+ validBeforeBlock: undefined,
145
+ withAttributes: undefined,
146
+ withMetadata: undefined,
147
+ withPayload: undefined,
148
+ })
149
+ })
150
+ })
151
+
152
+ describe("builder methods chain correctly", () => {
153
+ test("ownedBy() sets owner", async () => {
154
+ mockProcessQuery.mockResolvedValue({
155
+ data: [],
156
+ })
157
+
158
+ const builder = new QueryBuilder(mockClient)
159
+ const owner = "0x1234567890123456789012345678901234567890" as const
160
+ await builder.ownedBy(owner).fetch()
161
+
162
+ expect(mockProcessQuery).toHaveBeenCalledWith(mockClient, {
163
+ predicates: [],
164
+ limit: undefined,
165
+ offset: undefined,
166
+ ownedBy: owner,
167
+ validBeforeBlock: undefined,
168
+ withAttributes: undefined,
169
+ withMetadata: undefined,
170
+ withPayload: undefined,
171
+ })
172
+ })
173
+
174
+ test("limit() sets limit", async () => {
175
+ mockProcessQuery.mockResolvedValue({
176
+ data: [],
177
+ })
178
+
179
+ const builder = new QueryBuilder(mockClient)
180
+ await builder.limit(10).fetch()
181
+
182
+ expect(mockProcessQuery).toHaveBeenCalledWith(mockClient, {
183
+ predicates: [],
184
+ limit: 10,
185
+ offset: undefined,
186
+ ownedBy: undefined,
187
+ validBeforeBlock: undefined,
188
+ withAttributes: undefined,
189
+ withMetadata: undefined,
190
+ withPayload: undefined,
191
+ })
192
+ })
193
+
194
+ test("cursor() sets cursor", async () => {
195
+ mockProcessQuery.mockResolvedValue({
196
+ data: [],
197
+ })
198
+
199
+ const builder = new QueryBuilder(mockClient)
200
+ await builder.cursor("0xABC123").fetch()
201
+
202
+ expect(mockProcessQuery).toHaveBeenCalledWith(mockClient, {
203
+ predicates: [],
204
+ limit: undefined,
205
+ cursor: "0xABC123",
206
+ ownedBy: undefined,
207
+ validAtBlock: undefined,
208
+ withAttributes: undefined,
209
+ withMetadata: undefined,
210
+ withPayload: undefined,
211
+ })
212
+ })
213
+
214
+ test("withAttributes() sets withAttributes to true by default", async () => {
215
+ mockProcessQuery.mockResolvedValue({
216
+ data: [],
217
+ })
218
+
219
+ const builder = new QueryBuilder(mockClient)
220
+ await builder.withAttributes().fetch()
221
+
222
+ expect(mockProcessQuery).toHaveBeenCalledWith(mockClient, {
223
+ predicates: [],
224
+ limit: undefined,
225
+ offset: undefined,
226
+ ownedBy: undefined,
227
+ validBeforeBlock: undefined,
228
+ withAttributes: true,
229
+ withMetadata: undefined,
230
+ withPayload: undefined,
231
+ })
232
+ })
233
+
234
+ test("withAttributes(false) sets withAttributes to false", async () => {
235
+ mockProcessQuery.mockResolvedValue({
236
+ data: [],
237
+ })
238
+
239
+ const builder = new QueryBuilder(mockClient)
240
+ await builder.withAttributes(false).fetch()
241
+
242
+ expect(mockProcessQuery).toHaveBeenCalledWith(mockClient, {
243
+ predicates: [],
244
+ limit: undefined,
245
+ cursor: undefined,
246
+ ownedBy: undefined,
247
+ validBeforeBlock: undefined,
248
+ withAttributes: false,
249
+ withMetadata: undefined,
250
+ withPayload: undefined,
251
+ })
252
+ })
253
+
254
+ test("withMetadata() sets withMetadata to true by default", async () => {
255
+ mockProcessQuery.mockResolvedValue({
256
+ data: [],
257
+ })
258
+
259
+ const builder = new QueryBuilder(mockClient)
260
+ await builder.withMetadata().fetch()
261
+
262
+ expect(mockProcessQuery).toHaveBeenCalledWith(mockClient, {
263
+ predicates: [],
264
+ limit: undefined,
265
+ cursor: undefined,
266
+ ownedBy: undefined,
267
+ validAtBlock: undefined,
268
+ withAttributes: undefined,
269
+ withMetadata: true,
270
+ withPayload: undefined,
271
+ })
272
+ })
273
+
274
+ test("withMetadata(false) sets withMetadata to false", async () => {
275
+ mockProcessQuery.mockResolvedValue({
276
+ data: [],
277
+ })
278
+
279
+ const builder = new QueryBuilder(mockClient)
280
+ await builder.withMetadata(false).fetch()
281
+
282
+ expect(mockProcessQuery).toHaveBeenCalledWith(mockClient, {
283
+ predicates: [],
284
+ limit: undefined,
285
+ cursor: undefined,
286
+ ownedBy: undefined,
287
+ validAtBlock: undefined,
288
+ withAttributes: undefined,
289
+ withMetadata: false,
290
+ withPayload: undefined,
291
+ })
292
+ })
293
+
294
+ test("withPayload() sets withPayload to true by default", async () => {
295
+ mockProcessQuery.mockResolvedValue({
296
+ data: [],
297
+ })
298
+
299
+ const builder = new QueryBuilder(mockClient)
300
+ await builder.withPayload().fetch()
301
+
302
+ expect(mockProcessQuery).toHaveBeenCalledWith(mockClient, {
303
+ predicates: [],
304
+ limit: undefined,
305
+ cursor: undefined,
306
+ ownedBy: undefined,
307
+ validAtBlock: undefined,
308
+ withAttributes: undefined,
309
+ withMetadata: undefined,
310
+ withPayload: true,
311
+ })
312
+ })
313
+
314
+ test("withPayload(false) sets withPayload to false", async () => {
315
+ mockProcessQuery.mockResolvedValue({
316
+ data: [],
317
+ })
318
+
319
+ const builder = new QueryBuilder(mockClient)
320
+ await builder.withPayload(false).fetch()
321
+
322
+ expect(mockProcessQuery).toHaveBeenCalledWith(mockClient, {
323
+ predicates: [],
324
+ limit: undefined,
325
+ cursor: undefined,
326
+ ownedBy: undefined,
327
+ validAtBlock: undefined,
328
+ withAttributes: undefined,
329
+ withMetadata: undefined,
330
+ withPayload: false,
331
+ })
332
+ })
333
+
334
+ test("all methods chain together correctly", async () => {
335
+ mockProcessQuery.mockResolvedValue({
336
+ data: [],
337
+ })
338
+
339
+ const builder = new QueryBuilder(mockClient)
340
+ const owner = "0x1234567890123456789012345678901234567890" as const
341
+ await builder
342
+ .where(eq("name", "test"))
343
+ .where(gte("age", 18))
344
+ .ownedBy(owner)
345
+ .limit(10)
346
+ .cursor("0xABC123")
347
+ .withAttributes(true)
348
+ .withMetadata(true)
349
+ .withPayload(true)
350
+ .fetch()
351
+
352
+ expect(mockProcessQuery).toHaveBeenCalledWith(mockClient, {
353
+ predicates: [eq("name", "test"), gte("age", 18)],
354
+ limit: 10,
355
+ cursor: "0xABC123",
356
+ ownedBy: owner,
357
+ validAtBlock: undefined,
358
+ withAttributes: true,
359
+ withMetadata: true,
360
+ withPayload: true,
361
+ })
362
+ })
363
+ })
364
+
365
+ describe("fetch() method", () => {
366
+ test("calls processQuery and entityFromRpcResult", async () => {
367
+ const mockQueryResult = {
368
+ data: [
369
+ { key: "0xabc" as const, value: "encodedData1" },
370
+ { key: "0xdef" as const, value: "encodedData2" },
371
+ ],
372
+ }
373
+ const mockEntity1 = new Entity(
374
+ "0xabc" as const,
375
+ "application/json" as const,
376
+ "0x123" as const,
377
+ 1000n,
378
+ new Uint8Array(),
379
+ [],
380
+ )
381
+ const mockEntity2 = new Entity(
382
+ "0xdef" as const,
383
+ "application/json" as const,
384
+ "0x456" as const,
385
+ 2000n,
386
+ new Uint8Array(),
387
+ [],
388
+ )
389
+
390
+ mockProcessQuery.mockResolvedValue(mockQueryResult)
391
+ mockEntityFromRpcResult.mockResolvedValueOnce(mockEntity1).mockResolvedValueOnce(mockEntity2)
392
+
393
+ const builder = new QueryBuilder(mockClient)
394
+ const result = await builder.where(eq("name", "test")).fetch()
395
+
396
+ expect(mockProcessQuery).toHaveBeenCalled()
397
+ expect(mockEntityFromRpcResult).toHaveBeenCalled()
398
+ expect(mockEntityFromRpcResult).toHaveBeenCalledWith({ key: "0xabc", value: "encodedData1" })
399
+ expect(mockEntityFromRpcResult).toHaveBeenCalledWith({ key: "0xdef", value: "encodedData2" })
400
+ expect(result.entities).toHaveLength(2)
401
+ expect(result.entities[0]).toBe(mockEntity1)
402
+ expect(result.entities[1]).toBe(mockEntity2)
403
+ })
404
+
405
+ test("returns QueryResult with correct properties", async () => {
406
+ mockProcessQuery.mockResolvedValue({
407
+ data: [],
408
+ })
409
+
410
+ const builder = new QueryBuilder(mockClient)
411
+ const result = await builder.limit(10).cursor("0xABC123").fetch()
412
+
413
+ expect(result).toBeDefined()
414
+ expect(result.entities).toEqual([])
415
+ })
416
+ })
417
+
418
+ describe("count() method", () => {
419
+ test("calls processQuery and returns count", async () => {
420
+ const mockQueryResult = {
421
+ data: [
422
+ { key: "0xabc", value: "data1" },
423
+ { key: "0xdef", value: "data2" },
424
+ { key: "0xghi", value: "data3" },
425
+ ],
426
+ }
427
+ mockProcessQuery.mockResolvedValue(mockQueryResult)
428
+
429
+ const builder = new QueryBuilder(mockClient)
430
+ const count = await builder.where(eq("status", "active")).count()
431
+
432
+ expect(mockProcessQuery).toHaveBeenCalled()
433
+ expect(mockProcessQuery).toHaveBeenCalledWith(mockClient, {
434
+ predicates: [eq("status", "active")],
435
+ limit: undefined,
436
+ cursor: undefined,
437
+ ownedBy: undefined,
438
+ validAtBlock: undefined,
439
+ withAttributes: false,
440
+ withMetadata: false,
441
+ withPayload: false,
442
+ })
443
+ expect(count as number).toEqual(3 as number)
444
+ // Verify entityFromRpcResult is NOT called for count()
445
+ expect(mockEntityFromRpcResult).not.toHaveBeenCalled()
446
+ })
447
+
448
+ test("returns 0 for empty results", async () => {
449
+ mockProcessQuery.mockResolvedValue({
450
+ data: [],
451
+ })
452
+
453
+ const builder = new QueryBuilder(mockClient)
454
+ const count = await builder.count()
455
+
456
+ expect(count as number).toEqual(0 as number)
457
+ })
458
+
459
+ test("count() uses all builder parameters", async () => {
460
+ mockProcessQuery.mockResolvedValue({
461
+ data: [],
462
+ })
463
+
464
+ const builder = new QueryBuilder(mockClient)
465
+ const owner = "0x1234567890123456789012345678901234567890" as const
466
+ await builder
467
+ .where([eq("active", 1), gte("score", 100)])
468
+ .ownedBy(owner)
469
+ .limit(50)
470
+ .cursor("0xABC123")
471
+ .validAtBlock(123n)
472
+ .count()
473
+
474
+ expect(mockProcessQuery).toHaveBeenCalledWith(mockClient, {
475
+ predicates: [eq("active", 1), gte("score", 100)],
476
+ limit: 50,
477
+ cursor: "0xABC123",
478
+ ownedBy: owner,
479
+ validAtBlock: 123n,
480
+ withAttributes: false,
481
+ withMetadata: false,
482
+ withPayload: false,
483
+ })
484
+ })
485
+ })
486
+
487
+ describe("predicate types", () => {
488
+ test("passes string values correctly", async () => {
489
+ mockProcessQuery.mockResolvedValue({
490
+ data: [],
491
+ })
492
+
493
+ const builder = new QueryBuilder(mockClient)
494
+ await builder.where(eq("name", "john")).fetch()
495
+
496
+ expect(mockProcessQuery).toHaveBeenCalledWith(
497
+ mockClient,
498
+ expect.objectContaining({
499
+ predicates: [eq("name", "john")],
500
+ }),
501
+ )
502
+ })
503
+
504
+ test("passes numeric values correctly", async () => {
505
+ mockProcessQuery.mockResolvedValue({
506
+ data: [],
507
+ })
508
+
509
+ const builder = new QueryBuilder(mockClient)
510
+ await builder.where(gt("age", 25)).fetch()
511
+
512
+ expect(mockProcessQuery).toHaveBeenCalledWith(
513
+ mockClient,
514
+ expect.objectContaining({
515
+ predicates: [gt("age", 25)],
516
+ }),
517
+ )
518
+ })
519
+
520
+ test("passes mixed predicates correctly", async () => {
521
+ mockProcessQuery.mockResolvedValue({
522
+ data: [],
523
+ })
524
+
525
+ const builder = new QueryBuilder(mockClient)
526
+ const predicates = [eq("name", "john"), gt("age", 25), neq("status", "inactive")]
527
+ await builder.where(predicates).fetch()
528
+
529
+ expect(mockProcessQuery).toHaveBeenCalledWith(
530
+ mockClient,
531
+ expect.objectContaining({
532
+ predicates: predicates,
533
+ }),
534
+ )
535
+ })
536
+ })
537
+ })