web3ql-client 1.2.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 (86) hide show
  1. package/README.md +66 -0
  2. package/contracts/PublicKeyRegistry.sol +87 -0
  3. package/dist/src/access.d.ts +176 -0
  4. package/dist/src/access.d.ts.map +1 -0
  5. package/dist/src/access.js +283 -0
  6. package/dist/src/access.js.map +1 -0
  7. package/dist/src/batch.d.ts +107 -0
  8. package/dist/src/batch.d.ts.map +1 -0
  9. package/dist/src/batch.js +188 -0
  10. package/dist/src/batch.js.map +1 -0
  11. package/dist/src/cli.d.ts +40 -0
  12. package/dist/src/cli.d.ts.map +1 -0
  13. package/dist/src/cli.js +361 -0
  14. package/dist/src/cli.js.map +1 -0
  15. package/dist/src/constraints.d.ts +126 -0
  16. package/dist/src/constraints.d.ts.map +1 -0
  17. package/dist/src/constraints.js +192 -0
  18. package/dist/src/constraints.js.map +1 -0
  19. package/dist/src/crypto.d.ts +118 -0
  20. package/dist/src/crypto.d.ts.map +1 -0
  21. package/dist/src/crypto.js +192 -0
  22. package/dist/src/crypto.js.map +1 -0
  23. package/dist/src/factory-client.d.ts +106 -0
  24. package/dist/src/factory-client.d.ts.map +1 -0
  25. package/dist/src/factory-client.js +202 -0
  26. package/dist/src/factory-client.js.map +1 -0
  27. package/dist/src/index-cache.d.ts +156 -0
  28. package/dist/src/index-cache.d.ts.map +1 -0
  29. package/dist/src/index-cache.js +265 -0
  30. package/dist/src/index-cache.js.map +1 -0
  31. package/dist/src/index.d.ts +60 -0
  32. package/dist/src/index.d.ts.map +1 -0
  33. package/dist/src/index.js +60 -0
  34. package/dist/src/index.js.map +1 -0
  35. package/dist/src/migrations.d.ts +114 -0
  36. package/dist/src/migrations.d.ts.map +1 -0
  37. package/dist/src/migrations.js +173 -0
  38. package/dist/src/migrations.js.map +1 -0
  39. package/dist/src/model.d.ts +198 -0
  40. package/dist/src/model.d.ts.map +1 -0
  41. package/dist/src/model.js +379 -0
  42. package/dist/src/model.js.map +1 -0
  43. package/dist/src/query.d.ts +155 -0
  44. package/dist/src/query.d.ts.map +1 -0
  45. package/dist/src/query.js +386 -0
  46. package/dist/src/query.js.map +1 -0
  47. package/dist/src/registry.d.ts +45 -0
  48. package/dist/src/registry.d.ts.map +1 -0
  49. package/dist/src/registry.js +80 -0
  50. package/dist/src/registry.js.map +1 -0
  51. package/dist/src/schema-manager.d.ts +109 -0
  52. package/dist/src/schema-manager.d.ts.map +1 -0
  53. package/dist/src/schema-manager.js +259 -0
  54. package/dist/src/schema-manager.js.map +1 -0
  55. package/dist/src/table-client.d.ts +156 -0
  56. package/dist/src/table-client.d.ts.map +1 -0
  57. package/dist/src/table-client.js +292 -0
  58. package/dist/src/table-client.js.map +1 -0
  59. package/dist/src/typed-table.d.ts +159 -0
  60. package/dist/src/typed-table.d.ts.map +1 -0
  61. package/dist/src/typed-table.js +246 -0
  62. package/dist/src/typed-table.js.map +1 -0
  63. package/dist/src/types.d.ts +48 -0
  64. package/dist/src/types.d.ts.map +1 -0
  65. package/dist/src/types.js +222 -0
  66. package/dist/src/types.js.map +1 -0
  67. package/keyManager.js +337 -0
  68. package/package.json +38 -0
  69. package/src/access.ts +421 -0
  70. package/src/batch.ts +259 -0
  71. package/src/cli.ts +349 -0
  72. package/src/constraints.ts +283 -0
  73. package/src/crypto.ts +239 -0
  74. package/src/factory-client.ts +237 -0
  75. package/src/index-cache.ts +351 -0
  76. package/src/index.ts +171 -0
  77. package/src/migrations.ts +215 -0
  78. package/src/model.ts +538 -0
  79. package/src/query.ts +508 -0
  80. package/src/registry.ts +100 -0
  81. package/src/schema-manager.ts +301 -0
  82. package/src/table-client.ts +393 -0
  83. package/src/typed-table.ts +340 -0
  84. package/src/types.ts +284 -0
  85. package/tsconfig.json +22 -0
  86. package/walletUtils.js +204 -0
@@ -0,0 +1,351 @@
1
+ /**
2
+ * @file index-cache.ts
3
+ * @notice Web3QL v1.2 — client-side event-sourced index for fast queries.
4
+ *
5
+ * Problem:
6
+ * The chain stores opaque ciphertext. Without decrypting every record,
7
+ * you cannot answer WHERE queries. For small tables this is fine (the
8
+ * query engine decrypts in batches). For large tables you need an index.
9
+ *
10
+ * Solution — two tiers:
11
+ *
12
+ * Tier 1: Client-side in-memory index (this file)
13
+ * • On first load, decrypt all records and build an in-memory lookup map
14
+ * • Subscribe to chain events to update the cache incrementally
15
+ * • Fast point-lookups and range scans via the query engine
16
+ * • Index survives page refreshes when serialised to localStorage/IndexedDB
17
+ *
18
+ * Tier 2: Relay-maintained SQLite index (v1.2 API)
19
+ * • The relay sees all writes and maintains a server-side SQLite DB
20
+ * • Exposes `/api/connector/query` endpoint for WHERE-based key lookup
21
+ * • SDK calls relay to get matching record keys, then reads+decrypts from chain
22
+ *
23
+ * Usage (client-side index):
24
+ * ─────────────────────────────────────────────────────────────
25
+ * const cache = new TableIndexCache(tableClient, ownerAddress, schema);
26
+ *
27
+ * // Build index from chain (decrypt all records)
28
+ * await cache.build();
29
+ *
30
+ * // Fast in-memory query (no chain reads)
31
+ * const results = cache.query()
32
+ * .where('email', 'eq', 'alice@example.com')
33
+ * .execute();
34
+ *
35
+ * // Subscribe to updates (incrementally maintains index)
36
+ * cache.subscribe(provider);
37
+ *
38
+ * // Persist to localStorage
39
+ * cache.save('users-index');
40
+ * cache.load('users-index');
41
+ * ─────────────────────────────────────────────────────────────
42
+ */
43
+
44
+ import { ethers } from 'ethers';
45
+ import type { EncryptedTableClient } from './table-client.js';
46
+ import type { SchemaDefinition } from './types.js';
47
+ import { decodeRow } from './types.js';
48
+ import { query, QueryBuilder } from './query.js';
49
+ import type { Row } from './query.js';
50
+
51
+ // ─────────────────────────────────────────────────────────────
52
+ // Types
53
+ // ─────────────────────────────────────────────────────────────
54
+
55
+ export interface IndexEntry {
56
+ key : string; // bytes32 on-chain record key
57
+ data : Row; // decrypted+decoded plaintext
58
+ version : bigint; // on-chain version counter
59
+ updatedAt: bigint; // on-chain timestamp
60
+ }
61
+
62
+ export interface IndexCacheOptions {
63
+ /** Batch size for initial build (parallel decrypt). Default: 20. */
64
+ buildBatchSize?: number;
65
+ /** Auto-subscribe to write events to keep index fresh. Default: true. */
66
+ autoSubscribe?: boolean;
67
+ /** Max records to index. Default: unlimited. */
68
+ maxRecords?: number;
69
+ }
70
+
71
+ // ─────────────────────────────────────────────────────────────
72
+ // TableIndexCache
73
+ // ─────────────────────────────────────────────────────────────
74
+
75
+ export class TableIndexCache {
76
+ private tableClient : EncryptedTableClient;
77
+ private ownerAddress : string;
78
+ private schema? : SchemaDefinition;
79
+ private opts : Required<IndexCacheOptions>;
80
+
81
+ /** In-memory index: bytes32 key → IndexEntry */
82
+ private _index = new Map<string, IndexEntry>();
83
+ private _built = false;
84
+ private _listener?: () => void;
85
+
86
+ constructor(
87
+ tableClient : EncryptedTableClient,
88
+ ownerAddress : string,
89
+ schema? : SchemaDefinition,
90
+ opts : IndexCacheOptions = {},
91
+ ) {
92
+ this.tableClient = tableClient;
93
+ this.ownerAddress = ownerAddress;
94
+ this.schema = schema;
95
+ this.opts = {
96
+ buildBatchSize : opts.buildBatchSize ?? 20,
97
+ autoSubscribe : opts.autoSubscribe ?? true,
98
+ maxRecords : opts.maxRecords ?? Infinity,
99
+ };
100
+ }
101
+
102
+ // ── Build ───────────────────────────────────────────────────
103
+
104
+ /**
105
+ * Decrypt all owner records from chain and populate the in-memory index.
106
+ * Returns the number of records indexed.
107
+ */
108
+ async build(): Promise<number> {
109
+ const total = Number(await this.tableClient.ownerRecordCount(this.ownerAddress));
110
+ const clampedTotal = Math.min(total, this.opts.maxRecords);
111
+
112
+ const BATCH = this.opts.buildBatchSize;
113
+ for (let i = 0; i < clampedTotal; i += BATCH) {
114
+ const keys = await this.tableClient.listOwnerRecords(
115
+ this.ownerAddress,
116
+ BigInt(i),
117
+ BigInt(Math.min(BATCH, clampedTotal - i)),
118
+ );
119
+ const settled = await Promise.allSettled(
120
+ keys.map(async (k) => {
121
+ const raw = await this.tableClient.readRaw(k);
122
+ const plain = await this.tableClient.readPlaintext(k);
123
+ const parsed = JSON.parse(plain) as Row;
124
+ const data = this.schema ? decodeRow(this.schema, parsed) as Row : parsed;
125
+ return {
126
+ key : k,
127
+ data,
128
+ version : raw.version,
129
+ updatedAt: raw.updatedAt,
130
+ } satisfies IndexEntry;
131
+ }),
132
+ );
133
+ for (const r of settled) {
134
+ if (r.status === 'fulfilled') {
135
+ this._index.set(r.value.key, r.value);
136
+ }
137
+ }
138
+ }
139
+
140
+ this._built = true;
141
+ return this._index.size;
142
+ }
143
+
144
+ // ── Live subscription ───────────────────────────────────────
145
+
146
+ /**
147
+ * Subscribe to on-chain write events. Automatically decrypts and updates
148
+ * the index when new records are written by the owner.
149
+ *
150
+ * @param provider An ethers Provider with WebSocket or polling support.
151
+ */
152
+ subscribe(provider: ethers.Provider): void {
153
+ // Listen to the raw Transfer-like events on the table contract.
154
+ // Web3QL contracts emit a Write(bytes32 key, address owner) event.
155
+ const iface = new ethers.Interface([
156
+ 'event RecordWritten(bytes32 indexed key, address indexed owner)',
157
+ 'event RecordUpdated(bytes32 indexed key, address indexed owner)',
158
+ 'event RecordDeleted(bytes32 indexed key, address indexed owner)',
159
+ ]);
160
+ const contract = new ethers.Contract(
161
+ this.tableClient.tableAddress,
162
+ iface,
163
+ provider,
164
+ );
165
+
166
+ const onWrite = async (key: string, owner: string) => {
167
+ if (owner.toLowerCase() !== this.ownerAddress.toLowerCase()) return;
168
+ try {
169
+ const raw = await this.tableClient.readRaw(key);
170
+ const plain = await this.tableClient.readPlaintext(key);
171
+ const parsed = JSON.parse(plain) as Row;
172
+ const data = this.schema ? decodeRow(this.schema, parsed) as Row : parsed;
173
+ this._index.set(key, { key, data, version: raw.version, updatedAt: raw.updatedAt });
174
+ } catch { /* record inaccessible — skip */ }
175
+ };
176
+
177
+ const onDelete = (key: string) => {
178
+ this._index.delete(key);
179
+ };
180
+
181
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
182
+ (contract as any).on('RecordWritten', onWrite);
183
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
184
+ (contract as any).on('RecordUpdated', onWrite);
185
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
186
+ (contract as any).on('RecordDeleted', onDelete);
187
+
188
+ this._listener = () => {
189
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
190
+ (contract as any).off('RecordWritten', onWrite);
191
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
192
+ (contract as any).off('RecordUpdated', onWrite);
193
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
194
+ (contract as any).off('RecordDeleted', onDelete);
195
+ };
196
+ }
197
+
198
+ /** Stop listening to chain events. */
199
+ unsubscribe(): void {
200
+ this._listener?.();
201
+ this._listener = undefined;
202
+ }
203
+
204
+ // ── Query ───────────────────────────────────────────────────
205
+
206
+ /** Return a QueryBuilder over all indexed records. No chain reads. */
207
+ query(): QueryBuilder<Row> {
208
+ return query(Array.from(this._index.values()).map((e) => e.data));
209
+ }
210
+
211
+ /** Look up a specific record by its bytes32 on-chain key. */
212
+ get(key: string): IndexEntry | undefined {
213
+ return this._index.get(key);
214
+ }
215
+
216
+ /** All indexed entries as an array. */
217
+ all(): IndexEntry[] {
218
+ return Array.from(this._index.values());
219
+ }
220
+
221
+ /** Number of records in the index. */
222
+ get size(): number { return this._index.size; }
223
+
224
+ /** True after build() has completed at least once. */
225
+ get built(): boolean { return this._built; }
226
+
227
+ // ── Invalidation ────────────────────────────────────────────
228
+
229
+ /** Manually add or update an entry (e.g. after a local write). */
230
+ upsert(entry: IndexEntry): void {
231
+ this._index.set(entry.key, entry);
232
+ }
233
+
234
+ /** Remove an entry from the cache (e.g. after a local delete). */
235
+ evict(key: string): void {
236
+ this._index.delete(key);
237
+ }
238
+
239
+ /** Clear the entire index. */
240
+ clear(): void {
241
+ this._index.clear();
242
+ this._built = false;
243
+ }
244
+
245
+ // ── Persistence ─────────────────────────────────────────────
246
+
247
+ /**
248
+ * Serialise the index to a JSON string.
249
+ * Store in localStorage or IndexedDB to survive page refreshes.
250
+ */
251
+ serialise(): string {
252
+ const entries = Array.from(this._index.values()).map((e) => ({
253
+ key : e.key,
254
+ data : e.data,
255
+ version : e.version.toString(),
256
+ updatedAt: e.updatedAt.toString(),
257
+ }));
258
+ return JSON.stringify({ version: 1, entries });
259
+ }
260
+
261
+ /** Restore an index from a previously serialised string. */
262
+ deserialise(json: string): void {
263
+ const { entries } = JSON.parse(json) as {
264
+ entries: { key: string; data: Row; version: string; updatedAt: string }[];
265
+ };
266
+ for (const e of entries) {
267
+ this._index.set(e.key, {
268
+ key : e.key,
269
+ data : e.data,
270
+ version : BigInt(e.version),
271
+ updatedAt: BigInt(e.updatedAt),
272
+ });
273
+ }
274
+ this._built = true;
275
+ }
276
+
277
+ /** Save to localStorage (browser only). */
278
+ save(storageKey: string): void {
279
+ const ls = (globalThis as Record<string, unknown>)['localStorage'] as { setItem(k:string, v:string): void } | undefined;
280
+ if (!ls) return;
281
+ ls.setItem(storageKey, this.serialise());
282
+ }
283
+
284
+ /** Load from localStorage (browser only). */
285
+ load(storageKey: string): boolean {
286
+ const ls = (globalThis as Record<string, unknown>)['localStorage'] as { getItem(k:string): string|null } | undefined;
287
+ if (!ls) return false;
288
+ const raw = ls.getItem(storageKey);
289
+ if (!raw) return false;
290
+ this.deserialise(raw);
291
+ return true;
292
+ }
293
+ }
294
+
295
+ // ─────────────────────────────────────────────────────────────
296
+ // Relay query client (v1.2 — queries relay-maintained SQLite index)
297
+ // ─────────────────────────────────────────────────────────────
298
+
299
+ export interface RelayQueryRequest {
300
+ tableAddress: string;
301
+ where? : { col: string; op: string; val: unknown }[];
302
+ orderBy? : { col: string; dir: 'asc' | 'desc' }[];
303
+ limit? : number;
304
+ offset? : number;
305
+ }
306
+
307
+ export interface RelayQueryResponse {
308
+ /** bytes32 record keys matching the query */
309
+ keys : string[];
310
+ total : number;
311
+ }
312
+
313
+ /**
314
+ * QueryRelayClient — sends WHERE queries to the relay's SQLite index endpoint
315
+ * and returns matching bytes32 record keys. The caller then reads+decrypts
316
+ * from chain using the returned keys.
317
+ *
318
+ * This is the v1.2 "fast query" path that avoids decrypting every record.
319
+ */
320
+ export class QueryRelayClient {
321
+ private baseUrl: string;
322
+
323
+ constructor(relayBaseUrl: string) {
324
+ this.baseUrl = relayBaseUrl.replace(/\/$/, '');
325
+ }
326
+
327
+ /**
328
+ * Query the relay's index and return matching bytes32 record keys.
329
+ *
330
+ * @example
331
+ * const { keys } = await relay.query({
332
+ * tableAddress: '0x...',
333
+ * where: [{ col: 'age', op: 'gt', val: 18 }],
334
+ * limit: 50,
335
+ * });
336
+ * const records = await Promise.all(keys.map(k => tableClient.readPlaintext(k)));
337
+ */
338
+ async query(req: RelayQueryRequest): Promise<RelayQueryResponse> {
339
+ const url = `${this.baseUrl}/api/connector/query`;
340
+ const resp = await fetch(url, {
341
+ method : 'POST',
342
+ headers: { 'Content-Type': 'application/json' },
343
+ body : JSON.stringify(req),
344
+ });
345
+ if (!resp.ok) {
346
+ const text = await resp.text();
347
+ throw new Error(`QueryRelayClient: relay returned ${resp.status}: ${text}`);
348
+ }
349
+ return resp.json() as Promise<RelayQueryResponse>;
350
+ }
351
+ }
package/src/index.ts ADDED
@@ -0,0 +1,171 @@
1
+ /**
2
+ * @web3ql/sdk — Public API (v1.2)
3
+ *
4
+ * ─────────────────────────────────────────────────────────────
5
+ * import {
6
+ * // Core clients
7
+ * Web3QLClient, DatabaseClient, EncryptedTableClient,
8
+ * // High-level typed API
9
+ * TypedTableClient,
10
+ * // Type system (v1.1+)
11
+ * validateAndEncode, decodeRow, encodeFieldValue, decodeFieldValue,
12
+ * // Query builder (v1.1+)
13
+ * query,
14
+ * // Migrations (v1.1+)
15
+ * MigrationRunner, addColumn, dropColumn, renameColumn, changeType,
16
+ * // Constraints (v1.2)
17
+ * ConstraintEngine, ConstraintViolation, AutoIncrementCounter,
18
+ * // Index cache (v1.2)
19
+ * TableIndexCache, QueryRelayClient,
20
+ * // Batch writes (v1.2)
21
+ * BatchWriter, buildCrossTableBatch,
22
+ * // Access control (v1.2)
23
+ * AccessManager, PublicTableClient,
24
+ * // Schema management (v1.2)
25
+ * SchemaManager, decodeSchemaBytes, diffSchema,
26
+ * // Crypto
27
+ * deriveKeypairFromWallet,
28
+ * Role,
29
+ * } from '@web3ql/sdk';
30
+ * ─────────────────────────────────────────────────────────────
31
+ */
32
+
33
+ // ── Core clients ──────────────────────────────────────────────
34
+ export { Web3QLClient, DatabaseClient } from './factory-client.js';
35
+ export { EncryptedTableClient, Role } from './table-client.js';
36
+ export type { RawRecord } from './table-client.js';
37
+ export { PublicKeyRegistryClient } from './registry.js';
38
+
39
+ // ── High-level typed API (Prisma-style) ───────────────────────
40
+ export { TypedTableClient } from './typed-table.js';
41
+ export type {
42
+ FindManyOptions,
43
+ RecordWithId,
44
+ WhereTuple,
45
+ SchemaDefinition,
46
+ } from './typed-table.js';
47
+
48
+ // ── v1.1 Type system (encode/decode/validate) ─────────────────
49
+ export {
50
+ NULL_SENTINEL,
51
+ validateAndEncode,
52
+ decodeRow,
53
+ encodeFieldValue,
54
+ decodeFieldValue,
55
+ } from './types.js';
56
+ export type { FieldType, FieldDescriptor } from './types.js';
57
+
58
+ // ── v1.1 Query builder ────────────────────────────────────────
59
+ export { query, QueryBuilder } from './query.js';
60
+ export type {
61
+ Row,
62
+ WhereOperator,
63
+ WhereClause,
64
+ OrderByClause,
65
+ SortDirection,
66
+ AggregateOptions,
67
+ AggregateResult,
68
+ JoinType,
69
+ JoinClause,
70
+ TimeBucketUnit,
71
+ HavingClause,
72
+ } from './query.js';
73
+
74
+ // ── v1.1 Migration framework ──────────────────────────────────
75
+ export {
76
+ MigrationRunner,
77
+ addColumn,
78
+ addColumnSchema,
79
+ dropColumn,
80
+ dropColumnSchema,
81
+ renameColumn,
82
+ renameColumnSchema,
83
+ changeType,
84
+ computeColumn,
85
+ } from './migrations.js';
86
+ export type { Migration, RowTransformer, SchemaTransformer } from './migrations.js';
87
+
88
+ // ── v1.2 Integrity constraints ────────────────────────────────
89
+ export {
90
+ ConstraintEngine,
91
+ ConstraintViolation,
92
+ AutoIncrementCounter,
93
+ } from './constraints.js';
94
+ export type {
95
+ Constraint,
96
+ UniqueConstraint,
97
+ CheckConstraint,
98
+ ForeignKeyConstraint,
99
+ NotNullConstraint,
100
+ } from './constraints.js';
101
+
102
+ // ── v1.2 Index cache + relay query client ─────────────────────
103
+ export {
104
+ TableIndexCache,
105
+ QueryRelayClient,
106
+ } from './index-cache.js';
107
+ export type {
108
+ IndexEntry,
109
+ IndexCacheOptions,
110
+ RelayQueryRequest,
111
+ RelayQueryResponse,
112
+ } from './index-cache.js';
113
+
114
+ // ── v1.2 Batch writes (Multicall3) ────────────────────────────
115
+ export {
116
+ BatchWriter,
117
+ buildCrossTableBatch,
118
+ MULTICALL3_ADDRESS,
119
+ } from './batch.js';
120
+ export type { CrossTableOp } from './batch.js';
121
+
122
+ // ── v1.2 Advanced access control ─────────────────────────────
123
+ export {
124
+ AccessManager,
125
+ PublicTableClient,
126
+ grantMetaKey,
127
+ generateColumnKeySet,
128
+ encryptWithColumnKeys,
129
+ decryptColumnBlobs,
130
+ } from './access.js';
131
+ export type {
132
+ TimedGrant,
133
+ CapabilityToken,
134
+ ColumnKeySet,
135
+ } from './access.js';
136
+
137
+ // ── v1.2 Schema management ────────────────────────────────────
138
+ export {
139
+ SchemaManager,
140
+ decodeSchemaBytes,
141
+ diffSchema,
142
+ } from './schema-manager.js';
143
+ export type {
144
+ SchemaChange,
145
+ SchemaChangeType,
146
+ } from './schema-manager.js';
147
+
148
+ // ── Crypto primitives ─────────────────────────────────────────
149
+ export {
150
+ KEY_DERIVATION_MESSAGE,
151
+ deriveKeypairFromWallet, // ✅ browser-compatible (recommended)
152
+ deriveKeypair, // ⚠️ deprecated: different keypair to browser
153
+ publicKeyFromPrivate,
154
+ generateSymmetricKey,
155
+ encryptData,
156
+ decryptData,
157
+ encryptKeyForSelf,
158
+ encryptKeyForRecipient,
159
+ decryptKeyForSelf,
160
+ decryptKeyFromSender,
161
+ publicKeyToHex,
162
+ hexToPublicKey,
163
+ } from './crypto.js';
164
+ export type { EncryptionKeypair } from './crypto.js';
165
+
166
+ // ── v1.3 ORM — Model + Relation wires ────────────────────────
167
+ export { Model } from './model.js';
168
+ export type {
169
+ ModelOptions,
170
+ RelatedCreateOptions,
171
+ } from './model.js';