@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
@@ -0,0 +1,213 @@
1
+ import { afterEach, beforeEach, describe, expect, jest, test } from "bun:test"
2
+ import type { ArkivClient } from "../clients/baseClient"
3
+ import type { Entity } from "../types/entity"
4
+ import { RpcAttributeValueType } from "../types/rpcSchema"
5
+ import * as entitiesUtils from "../utils/entities"
6
+ import * as engine from "./engine"
7
+ import { eq, gte } from "./predicate"
8
+ import { SelectQueryBuilder } from "./queryBuilder"
9
+ import { selectionToIncludeData } from "./selection"
10
+
11
+ describe("SelectQueryBuilder", () => {
12
+ let mockClient: ArkivClient
13
+ let mockProcessQuery: any
14
+
15
+ beforeEach(() => {
16
+ mockClient = {
17
+ request: jest.fn(),
18
+ } as unknown as ArkivClient
19
+
20
+ mockProcessQuery = jest.spyOn(engine, "processQuery")
21
+ jest.spyOn(entitiesUtils, "entityFromRpcResult")
22
+ })
23
+
24
+ afterEach(() => {
25
+ jest.restoreAllMocks()
26
+ })
27
+
28
+ test("select() with no argument forwards include-data for everything", async () => {
29
+ mockProcessQuery.mockResolvedValue({ data: [] })
30
+
31
+ await new SelectQueryBuilder(mockClient).where(eq("name", "test")).fetch()
32
+
33
+ expect(mockProcessQuery).toHaveBeenCalledWith(
34
+ mockClient,
35
+ expect.objectContaining({
36
+ predicates: [eq("name", "test")],
37
+ includeData: selectionToIncludeData(),
38
+ }),
39
+ )
40
+ })
41
+
42
+ test('select("*") forwards include-data for everything', async () => {
43
+ mockProcessQuery.mockResolvedValue({ data: [] })
44
+
45
+ await new SelectQueryBuilder(mockClient, "*").fetch()
46
+
47
+ expect(mockProcessQuery).toHaveBeenCalledWith(
48
+ mockClient,
49
+ expect.objectContaining({
50
+ includeData: selectionToIncludeData("*"),
51
+ }),
52
+ )
53
+ })
54
+
55
+ test("select({ owner: true, attributes: true }) forwards the resolved include-data", async () => {
56
+ mockProcessQuery.mockResolvedValue({ data: [] })
57
+
58
+ await new SelectQueryBuilder(mockClient, { owner: true, attributes: true }).fetch()
59
+
60
+ expect(mockProcessQuery).toHaveBeenCalledWith(
61
+ mockClient,
62
+ expect.objectContaining({
63
+ includeData: selectionToIncludeData({ owner: true, attributes: true }),
64
+ }),
65
+ )
66
+ })
67
+
68
+ test("select({ owner: true }) forwards narrowed include-data", async () => {
69
+ mockProcessQuery.mockResolvedValue({ data: [] })
70
+
71
+ await new SelectQueryBuilder(mockClient, { owner: true }).fetch()
72
+
73
+ const call = mockProcessQuery.mock.calls[0][1]
74
+ expect(call.includeData).toEqual(selectionToIncludeData({ owner: true }))
75
+ expect(call.includeData.owner).toBe(true)
76
+ expect(call.includeData.creator).toBe(false)
77
+ // key is opt-in: not selected here, so it is excluded
78
+ expect(call.includeData.key).toBe(false)
79
+ })
80
+
81
+ test("does not forward the with* boolean flags", async () => {
82
+ mockProcessQuery.mockResolvedValue({ data: [] })
83
+
84
+ await new SelectQueryBuilder(mockClient, { attributes: true }).fetch()
85
+
86
+ const call = mockProcessQuery.mock.calls[0][1]
87
+ expect(call.withAttributes).toBeUndefined()
88
+ expect(call.withMetadata).toBeUndefined()
89
+ expect(call.withPayload).toBeUndefined()
90
+ })
91
+
92
+ test("shares the filtering/ordering/pagination chain methods", async () => {
93
+ mockProcessQuery.mockResolvedValue({ data: [] })
94
+
95
+ const owner = "0x1234567890123456789012345678901234567890" as const
96
+ await new SelectQueryBuilder(mockClient, { attributes: true })
97
+ .where(eq("name", "test"))
98
+ .where(gte("age", 18))
99
+ .ownedBy(owner)
100
+ .createdBy(owner)
101
+ .orderBy("name", "string", "desc")
102
+ .limit(10)
103
+ .cursor("0xABC123")
104
+ .validAtBlock(123n)
105
+ .fetch()
106
+
107
+ expect(mockProcessQuery).toHaveBeenCalledWith(mockClient, {
108
+ predicates: [eq("name", "test"), gte("age", 18)],
109
+ limit: 10,
110
+ cursor: "0xABC123",
111
+ ownedBy: owner,
112
+ createdBy: owner,
113
+ orderBy: [{ name: "name", type: "string", desc: true }],
114
+ validAtBlock: 123n,
115
+ includeData: selectionToIncludeData({ attributes: true }),
116
+ })
117
+ })
118
+
119
+ test("count() ignores the selection and requests no data", async () => {
120
+ mockProcessQuery.mockResolvedValue({
121
+ data: [
122
+ { key: "0xabc", value: "data1" },
123
+ { key: "0xdef", value: "data2" },
124
+ ],
125
+ })
126
+
127
+ const count = await new SelectQueryBuilder(mockClient, "*")
128
+ .where(eq("status", "active"))
129
+ .count()
130
+
131
+ expect(count).toBe(2)
132
+ expect(mockProcessQuery).toHaveBeenCalledWith(mockClient, {
133
+ predicates: [eq("status", "active")],
134
+ limit: undefined,
135
+ cursor: undefined,
136
+ ownedBy: undefined,
137
+ createdBy: undefined,
138
+ orderBy: undefined,
139
+ validAtBlock: undefined,
140
+ withAttributes: false,
141
+ withMetadata: false,
142
+ withPayload: false,
143
+ })
144
+ })
145
+
146
+ describe("result entities", () => {
147
+ test("fetch returns flat Entity instances with the selected data and methods", async () => {
148
+ mockProcessQuery.mockResolvedValue({
149
+ data: [
150
+ {
151
+ key: "0xabc",
152
+ owner: "0xowner",
153
+ value: "0x68656c6c6f", // "hello"
154
+ attributes: [
155
+ { key: "category", value: "docs", valueType: RpcAttributeValueType.String },
156
+ { key: "score", value: "0x2a", valueType: RpcAttributeValueType.Uint },
157
+ ],
158
+ },
159
+ ],
160
+ cursor: "",
161
+ blockNumber: "0x1",
162
+ })
163
+
164
+ const result = await new SelectQueryBuilder(mockClient, {
165
+ key: true,
166
+ owner: true,
167
+ attributes: true,
168
+ payload: true,
169
+ }).fetch()
170
+
171
+ expect(result.entities).toHaveLength(1)
172
+ const entity = result.entities[0] as unknown as Entity
173
+ // flat, backwards-compatible field access
174
+ expect(entity.key).toBe("0xabc")
175
+ expect(entity.owner).toBe("0xowner")
176
+ expect(entity.attributes).toEqual([
177
+ { key: "category", value: "docs" },
178
+ { key: "score", value: 42 },
179
+ ])
180
+ // entity methods are preserved on the result
181
+ expect(typeof entity.toText).toBe("function")
182
+ expect(entity.toText()).toBe("hello")
183
+ })
184
+
185
+ test("toJson decodes a JSON payload", async () => {
186
+ mockProcessQuery.mockResolvedValue({
187
+ data: [{ key: "0xabc", value: "0x7b2261223a317d" }], // {"a":1}
188
+ cursor: "",
189
+ blockNumber: "0x1",
190
+ })
191
+
192
+ const result = await new SelectQueryBuilder(mockClient, { payload: true }).fetch()
193
+ const entity = result.entities[0] as unknown as Entity
194
+ expect(entity.toJson()).toEqual({ a: 1 })
195
+ })
196
+
197
+ test("fields the RPC omits (because they were not selected) are undefined at runtime", async () => {
198
+ // The RPC honours includeData and returns only the key.
199
+ mockProcessQuery.mockResolvedValue({
200
+ data: [{ key: "0xabc" }],
201
+ cursor: "",
202
+ blockNumber: "0x1",
203
+ })
204
+
205
+ const result = await new SelectQueryBuilder(mockClient, { key: true }).fetch()
206
+ const entity = result.entities[0] as unknown as Entity
207
+ expect(entity.key).toBe("0xabc")
208
+ expect(entity.owner).toBeUndefined()
209
+ expect(entity.payload).toBeUndefined()
210
+ expect(entity.attributes).toEqual([])
211
+ })
212
+ })
213
+ })
@@ -0,0 +1,120 @@
1
+ import { describe, expect, test } from "bun:test"
2
+ import { selectionToIncludeData } from "./selection"
3
+
4
+ const ALL = {
5
+ key: true,
6
+ attributes: true,
7
+ payload: true,
8
+ contentType: true,
9
+ expiration: true,
10
+ owner: true,
11
+ creator: true,
12
+ createdAtBlock: true,
13
+ lastModifiedAtBlock: true,
14
+ transactionIndexInBlock: true,
15
+ operationIndexInTransaction: true,
16
+ }
17
+
18
+ const EMPTY = {
19
+ key: false,
20
+ attributes: false,
21
+ payload: false,
22
+ contentType: false,
23
+ expiration: false,
24
+ owner: false,
25
+ creator: false,
26
+ createdAtBlock: false,
27
+ lastModifiedAtBlock: false,
28
+ transactionIndexInBlock: false,
29
+ operationIndexInTransaction: false,
30
+ }
31
+
32
+ describe("selectionToIncludeData", () => {
33
+ test("select() with no argument selects everything", () => {
34
+ expect(selectionToIncludeData()).toEqual(ALL)
35
+ })
36
+
37
+ test('select("*") selects everything', () => {
38
+ expect(selectionToIncludeData("*")).toEqual(ALL)
39
+ })
40
+
41
+ test("empty object throws", () => {
42
+ // @ts-expect-error – select requires at least one field
43
+ expect(() => selectionToIncludeData({})).toThrow()
44
+ })
45
+
46
+ test("an all-false selection throws (no field is actually selected)", () => {
47
+ expect(() => selectionToIncludeData({ key: false })).toThrow()
48
+ expect(() => selectionToIncludeData({ owner: false, payload: false })).toThrow()
49
+ })
50
+
51
+ test("key: true selects only the key", () => {
52
+ expect(selectionToIncludeData({ key: true })).toEqual({ ...EMPTY, key: true })
53
+ })
54
+
55
+ test("key is opt-in like every other field", () => {
56
+ expect(selectionToIncludeData({ key: true }).key).toBe(true)
57
+ expect(selectionToIncludeData({ attributes: true }).key).toBe(false)
58
+ expect(selectionToIncludeData({ owner: true }).key).toBe(false)
59
+ })
60
+
61
+ test("attributes only (no key)", () => {
62
+ expect(selectionToIncludeData({ attributes: true })).toEqual({
63
+ ...EMPTY,
64
+ attributes: true,
65
+ })
66
+ })
67
+
68
+ test("payload only (no key)", () => {
69
+ expect(selectionToIncludeData({ payload: true })).toEqual({
70
+ ...EMPTY,
71
+ payload: true,
72
+ })
73
+ })
74
+
75
+ test("a single metadata field, selected flat (no key)", () => {
76
+ expect(selectionToIncludeData({ owner: true })).toEqual({
77
+ ...EMPTY,
78
+ owner: true,
79
+ })
80
+ })
81
+
82
+ test("expiresAtBlock maps to the RPC `expiration` field", () => {
83
+ expect(selectionToIncludeData({ expiresAtBlock: true })).toEqual({
84
+ ...EMPTY,
85
+ expiration: true,
86
+ })
87
+ })
88
+
89
+ test("several fields selected together", () => {
90
+ expect(
91
+ selectionToIncludeData({
92
+ owner: true,
93
+ creator: true,
94
+ createdAtBlock: true,
95
+ attributes: true,
96
+ }),
97
+ ).toEqual({
98
+ ...EMPTY,
99
+ owner: true,
100
+ creator: true,
101
+ createdAtBlock: true,
102
+ attributes: true,
103
+ })
104
+ })
105
+
106
+ test("key can be combined with other fields", () => {
107
+ expect(selectionToIncludeData({ key: true, attributes: true })).toEqual({
108
+ ...EMPTY,
109
+ key: true,
110
+ attributes: true,
111
+ })
112
+ })
113
+
114
+ test("a field set to false is not selected", () => {
115
+ expect(selectionToIncludeData({ owner: true, creator: false })).toEqual({
116
+ ...EMPTY,
117
+ owner: true,
118
+ })
119
+ })
120
+ })
@@ -0,0 +1,116 @@
1
+ import type { Entity } from "../types/entity"
2
+ import type { RpcIncludeData } from "../types/rpcSchema"
3
+
4
+ /** The entity fields that can be selected. These match the fields of {@link Entity} one-to-one. */
5
+ type DataKey =
6
+ | "key"
7
+ | "contentType"
8
+ | "owner"
9
+ | "creator"
10
+ | "expiresAtBlock"
11
+ | "createdAtBlock"
12
+ | "lastModifiedAtBlock"
13
+ | "transactionIndexInBlock"
14
+ | "operationIndexInTransaction"
15
+ | "attributes"
16
+ | "payload"
17
+
18
+ /**
19
+ * The parts of an entity that can be selected. Every field is opt-in (including the `key`) and maps
20
+ * directly to a field of the returned {@link Entity} — the selection is flat.
21
+ */
22
+ export type SelectionFields = { [K in DataKey]?: boolean }
23
+
24
+ /**
25
+ * Requires at least one property of `T` to be present.
26
+ * `{}` is not assignable, which is how `select({})` is rejected at compile time.
27
+ */
28
+ type AtLeastOne<T, K extends keyof T = keyof T> = {
29
+ [P in K]: Required<Pick<T, P>> & Partial<Omit<T, P>>
30
+ }[K]
31
+
32
+ /**
33
+ * A non-empty entity selection — the object form accepted by `select()`. At least one field must be
34
+ * selected.
35
+ */
36
+ export type EntitySelection = AtLeastOne<SelectionFields>
37
+
38
+ /** Argument accepted by `select()` — a non-empty {@link EntitySelection}, or `"*"` for everything. */
39
+ export type SelectArg = EntitySelection | "*"
40
+
41
+ /**
42
+ * The result of a selection `S`: an {@link Entity} narrowed to exactly the selected fields (each
43
+ * non-`undefined`). The shape is flat and backwards compatible — e.g. `select({ owner: true })`
44
+ * yields `{ owner: Hex }` accessed as `entity.owner`. Accessing an unselected field is a compile
45
+ * error.
46
+ *
47
+ * The `toText()` / `toJson()` helpers operate on the payload, so they are only present when
48
+ * `payload` was selected — calling them otherwise would always throw.
49
+ */
50
+ export type ProjectedEntity<S> = {
51
+ readonly [K in keyof S as S[K] extends true ? K : never]: K extends keyof Entity
52
+ ? NonNullable<Entity[K]>
53
+ : never
54
+ } & (S extends { payload: true } ? Pick<Entity, "toText" | "toJson"> : unknown)
55
+
56
+ /** A selection of every field, used to type the result of `select()` / `select("*")`. */
57
+ type AllSelected = { [K in DataKey]: true }
58
+
59
+ /** The projected entity type when everything is selected — every field, plus the methods. */
60
+ export type FullEntity = ProjectedEntity<AllSelected>
61
+
62
+ /**
63
+ * Converts a {@link SelectArg} into the {@link RpcIncludeData} understood by the query engine.
64
+ * Every field is opt-in — anything not explicitly selected defaults to `false`, including the key.
65
+ * `undefined` or `"*"` selects everything.
66
+ *
67
+ * @param selection - The selection to convert
68
+ * @returns The fully-resolved include-data object for the RPC query
69
+ * @throws If an empty selection object is provided
70
+ *
71
+ * @example
72
+ * selectionToIncludeData() // everything
73
+ * selectionToIncludeData("*") // everything
74
+ * selectionToIncludeData({ key: true }) // only the key
75
+ * selectionToIncludeData({ owner: true }) // only the owner
76
+ */
77
+ export function selectionToIncludeData(selection?: SelectArg): RpcIncludeData {
78
+ if (selection === undefined || selection === "*") {
79
+ return {
80
+ key: true,
81
+ attributes: true,
82
+ payload: true,
83
+ contentType: true,
84
+ expiration: true,
85
+ owner: true,
86
+ creator: true,
87
+ createdAtBlock: true,
88
+ lastModifiedAtBlock: true,
89
+ transactionIndexInBlock: true,
90
+ operationIndexInTransaction: true,
91
+ }
92
+ }
93
+
94
+ // At least one field must be opted in. This rejects both `{}` and selections whose fields are
95
+ // all explicitly `false` (e.g. `select({ key: false })`), which the type system permits.
96
+ if (!Object.values(selection).some((selected) => selected === true)) {
97
+ throw new Error(
98
+ 'select() requires at least one field to be selected. Pass select() or select("*") to select everything.',
99
+ )
100
+ }
101
+
102
+ return {
103
+ key: selection.key ?? false,
104
+ attributes: selection.attributes ?? false,
105
+ payload: selection.payload ?? false,
106
+ contentType: selection.contentType ?? false,
107
+ // The selection uses the entity field name `expiresAtBlock`; the RPC calls it `expiration`.
108
+ expiration: selection.expiresAtBlock ?? false,
109
+ owner: selection.owner ?? false,
110
+ creator: selection.creator ?? false,
111
+ createdAtBlock: selection.createdAtBlock ?? false,
112
+ lastModifiedAtBlock: selection.lastModifiedAtBlock ?? false,
113
+ transactionIndexInBlock: selection.transactionIndexInBlock ?? false,
114
+ operationIndexInTransaction: selection.operationIndexInTransaction ?? false,
115
+ }
116
+ }
@@ -1,4 +1,28 @@
1
+ import type { Hex } from "viem"
2
+
3
+ /**
4
+ * An entity attribute (key/value pair).
5
+ *
6
+ * Arkiv supports string, Hex and number values.
7
+ *
8
+ * Hex values are stored as the on-chain `EntityKey` type **only when they are
9
+ * exactly 32 bytes** (66 characters, e.g. an entity key). Hex-looking strings
10
+ * of any other length are stored as plain strings: they round-trip unchanged
11
+ * and compare lexicographically, not as hex.
12
+ *
13
+ * Numeric values **must be non-negative integers** (they are stored on-chain
14
+ * as unsigned 256-bit) — passing a non-integer (e.g. `1.5`) or negative number
15
+ * throws an {@link InvalidAttributeError} at mutation time. To store a
16
+ * non-integer value:
17
+ * - Scale it to an integer (e.g. `1.5` -> `1500`, dividing by the same factor on
18
+ * read) to keep numeric ordering and range queries working.
19
+ * - Or pass it as a string (e.g. `"1.5"`), which sorts lexicographically and so
20
+ * does not support numeric comparisons.
21
+ *
22
+ * To store a negative value, offset it into the non-negative range (subtracting
23
+ * the offset on read) or store it as a string.
24
+ */
1
25
  export type Attribute = {
2
26
  key: string
3
- value: string | number
27
+ value: number | string | Hex
4
28
  }
@@ -1,10 +1,20 @@
1
1
  import { bytesToString, type Hex } from "viem"
2
+
3
+ export enum EntityOperationType {
4
+ Create = 1,
5
+ Update = 2,
6
+ Extend = 3,
7
+ Transfer = 4,
8
+ Delete = 5,
9
+ Expire = 6,
10
+ }
11
+
2
12
  import type { MimeType } from "../types"
3
13
  import type { Attribute } from "./attributes"
4
14
 
5
15
  export class Entity {
6
16
  key: Hex
7
- contentType: MimeType | undefined
17
+ contentType: MimeType | `${string}/${string}` | undefined
8
18
  owner: Hex | undefined
9
19
  creator: Hex | undefined
10
20
  expiresAtBlock: bigint | undefined
@@ -17,7 +27,7 @@ export class Entity {
17
27
 
18
28
  constructor(
19
29
  key: Hex,
20
- contentType: MimeType | undefined = undefined,
30
+ contentType: MimeType | `${string}/${string}` | undefined = undefined,
21
31
  owner: Hex | undefined = undefined,
22
32
  creator: Hex | undefined = undefined,
23
33
  expiresAtBlock: bigint | undefined = undefined,
@@ -1,40 +1,38 @@
1
- import type { Hex } from "viem"
1
+ import type { Address, Hex } from "viem"
2
2
 
3
3
  export type OnEntityCreatedEvent = {
4
4
  entityKey: Hex
5
- owner: Hex
6
- expirationBlock: number
7
- cost: bigint
5
+ owner: Address
6
+ expiresAt: number
7
+ entityHash: Hex
8
8
  }
9
9
 
10
10
  export type OnEntityUpdatedEvent = {
11
11
  entityKey: Hex
12
- owner: Hex
13
- oldExpirationBlock: number
14
- newExpirationBlock: number
15
- cost: bigint
12
+ owner: Address
13
+ expiresAt: number
14
+ entityHash: Hex
16
15
  }
17
16
 
18
17
  export type OnEntityDeletedEvent = {
19
18
  entityKey: Hex
20
- owner: Hex
19
+ owner: Address
21
20
  }
22
21
 
23
22
  export type OnEntityExpiredEvent = {
24
23
  entityKey: Hex
25
- owner: Hex
24
+ owner: Address
26
25
  }
27
26
 
28
27
  export type OnEntityExpiresInExtendedEvent = {
29
28
  entityKey: Hex
30
- owner: Hex
31
- oldExpirationBlock: number
32
- newExpirationBlock: number
33
- cost: bigint
29
+ owner: Address
30
+ expiresAt: number
31
+ entityHash: Hex
34
32
  }
35
33
 
36
34
  export type OnEntityOwnerChangedEvent = {
37
35
  entityKey: Hex
38
- oldOwner: Hex
39
- newOwner: Hex
36
+ owner: Address
37
+ entityHash: Hex
40
38
  }
@@ -19,6 +19,7 @@ export type {
19
19
  export type { UpdateEntityParameters, UpdateEntityReturnType } from "../actions/wallet/updateEntity"
20
20
  export type { Attribute } from "./attributes"
21
21
  export type { Entity } from "./entity"
22
+ export { EntityOperationType } from "./entity"
22
23
  export type {
23
24
  OnEntityCreatedEvent,
24
25
  OnEntityDeletedEvent,
@@ -12,8 +12,13 @@ export type RpcEntity = {
12
12
  operationIndexInTransaction: Hex
13
13
  owner: Hex
14
14
  creator: Hex
15
- stringAttributes?: [{ key: string; value: string }]
16
- numericAttributes?: [{ key: string; value: Hex }]
15
+ attributes?: { key: string; value: string; valueType: RpcAttributeValueType }[]
16
+ }
17
+
18
+ export enum RpcAttributeValueType {
19
+ Uint = 1,
20
+ String = 2,
21
+ EntityKey = 3,
17
22
  }
18
23
 
19
24
  export type RpcOrderByAttribute = {