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.
- package/README.md +66 -0
- package/contracts/PublicKeyRegistry.sol +87 -0
- package/dist/src/access.d.ts +176 -0
- package/dist/src/access.d.ts.map +1 -0
- package/dist/src/access.js +283 -0
- package/dist/src/access.js.map +1 -0
- package/dist/src/batch.d.ts +107 -0
- package/dist/src/batch.d.ts.map +1 -0
- package/dist/src/batch.js +188 -0
- package/dist/src/batch.js.map +1 -0
- package/dist/src/cli.d.ts +40 -0
- package/dist/src/cli.d.ts.map +1 -0
- package/dist/src/cli.js +361 -0
- package/dist/src/cli.js.map +1 -0
- package/dist/src/constraints.d.ts +126 -0
- package/dist/src/constraints.d.ts.map +1 -0
- package/dist/src/constraints.js +192 -0
- package/dist/src/constraints.js.map +1 -0
- package/dist/src/crypto.d.ts +118 -0
- package/dist/src/crypto.d.ts.map +1 -0
- package/dist/src/crypto.js +192 -0
- package/dist/src/crypto.js.map +1 -0
- package/dist/src/factory-client.d.ts +106 -0
- package/dist/src/factory-client.d.ts.map +1 -0
- package/dist/src/factory-client.js +202 -0
- package/dist/src/factory-client.js.map +1 -0
- package/dist/src/index-cache.d.ts +156 -0
- package/dist/src/index-cache.d.ts.map +1 -0
- package/dist/src/index-cache.js +265 -0
- package/dist/src/index-cache.js.map +1 -0
- package/dist/src/index.d.ts +60 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +60 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/migrations.d.ts +114 -0
- package/dist/src/migrations.d.ts.map +1 -0
- package/dist/src/migrations.js +173 -0
- package/dist/src/migrations.js.map +1 -0
- package/dist/src/model.d.ts +198 -0
- package/dist/src/model.d.ts.map +1 -0
- package/dist/src/model.js +379 -0
- package/dist/src/model.js.map +1 -0
- package/dist/src/query.d.ts +155 -0
- package/dist/src/query.d.ts.map +1 -0
- package/dist/src/query.js +386 -0
- package/dist/src/query.js.map +1 -0
- package/dist/src/registry.d.ts +45 -0
- package/dist/src/registry.d.ts.map +1 -0
- package/dist/src/registry.js +80 -0
- package/dist/src/registry.js.map +1 -0
- package/dist/src/schema-manager.d.ts +109 -0
- package/dist/src/schema-manager.d.ts.map +1 -0
- package/dist/src/schema-manager.js +259 -0
- package/dist/src/schema-manager.js.map +1 -0
- package/dist/src/table-client.d.ts +156 -0
- package/dist/src/table-client.d.ts.map +1 -0
- package/dist/src/table-client.js +292 -0
- package/dist/src/table-client.js.map +1 -0
- package/dist/src/typed-table.d.ts +159 -0
- package/dist/src/typed-table.d.ts.map +1 -0
- package/dist/src/typed-table.js +246 -0
- package/dist/src/typed-table.js.map +1 -0
- package/dist/src/types.d.ts +48 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +222 -0
- package/dist/src/types.js.map +1 -0
- package/keyManager.js +337 -0
- package/package.json +38 -0
- package/src/access.ts +421 -0
- package/src/batch.ts +259 -0
- package/src/cli.ts +349 -0
- package/src/constraints.ts +283 -0
- package/src/crypto.ts +239 -0
- package/src/factory-client.ts +237 -0
- package/src/index-cache.ts +351 -0
- package/src/index.ts +171 -0
- package/src/migrations.ts +215 -0
- package/src/model.ts +538 -0
- package/src/query.ts +508 -0
- package/src/registry.ts +100 -0
- package/src/schema-manager.ts +301 -0
- package/src/table-client.ts +393 -0
- package/src/typed-table.ts +340 -0
- package/src/types.ts +284 -0
- package/tsconfig.json +22 -0
- package/walletUtils.js +204 -0
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file schema-manager.ts
|
|
3
|
+
* @notice Web3QL v1.2 — schema management: drop tables, rename, introspection.
|
|
4
|
+
*
|
|
5
|
+
* Schema is stored as ABI-encoded bytes in the database contract.
|
|
6
|
+
* This module provides:
|
|
7
|
+
*
|
|
8
|
+
* 1. SCHEMA INTROSPECTION — decode raw schema bytes → FieldDescriptor[]
|
|
9
|
+
* 2. DROP TABLE — bulk delete all owner records, then rename table to __dropped__
|
|
10
|
+
* 3. RENAME TABLE — soft-rename via a meta record (contract doesn't support rename natively)
|
|
11
|
+
* 4. SCHEMA DIFF — compare two schemas, produce a list of changes
|
|
12
|
+
* 5. SCHEMA VERSION — read + write the schema version stored in a meta record
|
|
13
|
+
*
|
|
14
|
+
* Usage:
|
|
15
|
+
* ─────────────────────────────────────────────────────────────
|
|
16
|
+
* const mgr = new SchemaManager(db, tableAddress, tableClient);
|
|
17
|
+
*
|
|
18
|
+
* // Inspect a deployed table's schema
|
|
19
|
+
* const fields = await mgr.introspect();
|
|
20
|
+
*
|
|
21
|
+
* // Diff two versions
|
|
22
|
+
* const changes = diffSchema(oldFields, newFields);
|
|
23
|
+
*
|
|
24
|
+
* // Soft-drop: mark table as dropped + purge all owner records
|
|
25
|
+
* await mgr.dropTable(ownerAddress);
|
|
26
|
+
*
|
|
27
|
+
* // Soft-rename: store alias mapping in meta record
|
|
28
|
+
* await mgr.renameTable('users', 'app_users');
|
|
29
|
+
* ─────────────────────────────────────────────────────────────
|
|
30
|
+
*/
|
|
31
|
+
import { ethers } from 'ethers';
|
|
32
|
+
// ─────────────────────────────────────────────────────────────
|
|
33
|
+
// Schema introspection
|
|
34
|
+
// ─────────────────────────────────────────────────────────────
|
|
35
|
+
/**
|
|
36
|
+
* Minimal ABI type tags used in Web3QL schema encoding.
|
|
37
|
+
* Must stay in sync with protocol/compiler/generator.ts.
|
|
38
|
+
*/
|
|
39
|
+
const SOLIDITY_TO_FIELD_TYPE = {
|
|
40
|
+
'uint256': 'INT',
|
|
41
|
+
'int256': 'INT',
|
|
42
|
+
'int64': 'INT',
|
|
43
|
+
'uint64': 'UINT64',
|
|
44
|
+
'uint32': 'UINT32',
|
|
45
|
+
'uint16': 'UINT16',
|
|
46
|
+
'uint8': 'UINT8',
|
|
47
|
+
'string': 'TEXT',
|
|
48
|
+
'bool': 'BOOL',
|
|
49
|
+
'address': 'ADDRESS',
|
|
50
|
+
'bytes32': 'BYTES32',
|
|
51
|
+
'bytes': 'BYTES32',
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* Decode raw schema bytes from the contract into an array of FieldDescriptors.
|
|
55
|
+
*
|
|
56
|
+
* Web3QL encodes schema as ABI-encoded:
|
|
57
|
+
* tuple(string name, string solidityType, bool primaryKey, bool notNull)[]
|
|
58
|
+
*
|
|
59
|
+
* This mirrors protocol/compiler/generator.ts compileSchema().
|
|
60
|
+
*/
|
|
61
|
+
export function decodeSchemaBytes(schemaBytes) {
|
|
62
|
+
try {
|
|
63
|
+
const bytes = typeof schemaBytes === 'string' ? ethers.getBytes(schemaBytes) : schemaBytes;
|
|
64
|
+
if (bytes.length === 0)
|
|
65
|
+
return [];
|
|
66
|
+
const abiCoder = ethers.AbiCoder.defaultAbiCoder();
|
|
67
|
+
const decoded = abiCoder.decode(['tuple(string name, string solidityType, bool primaryKey, bool notNull)[]'], bytes);
|
|
68
|
+
const fields = decoded[0];
|
|
69
|
+
return fields.map((f) => ({
|
|
70
|
+
name: f.name,
|
|
71
|
+
type: (SOLIDITY_TO_FIELD_TYPE[f.solidityType] ?? 'TEXT'),
|
|
72
|
+
primaryKey: f.primaryKey || undefined,
|
|
73
|
+
notNull: f.notNull || undefined,
|
|
74
|
+
}));
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
return [];
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Compute the diff between two schema versions.
|
|
82
|
+
* Returns an ordered list of changes from `from` → `to`.
|
|
83
|
+
*/
|
|
84
|
+
export function diffSchema(from, to) {
|
|
85
|
+
const changes = [];
|
|
86
|
+
const fromMap = new Map(from.map((f) => [f.name, f]));
|
|
87
|
+
const toMap = new Map(to.map((f) => [f.name, f]));
|
|
88
|
+
// Added or changed
|
|
89
|
+
for (const [name, toField] of toMap) {
|
|
90
|
+
const fromField = fromMap.get(name);
|
|
91
|
+
if (!fromField) {
|
|
92
|
+
changes.push({ type: 'added', column: name, newValue: toField.type });
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
if (fromField.type !== toField.type) {
|
|
96
|
+
changes.push({ type: 'typeChanged', column: name, oldValue: fromField.type, newValue: toField.type });
|
|
97
|
+
}
|
|
98
|
+
if (Boolean(fromField.notNull) !== Boolean(toField.notNull)) {
|
|
99
|
+
changes.push({
|
|
100
|
+
type: 'notNullChanged',
|
|
101
|
+
column: name,
|
|
102
|
+
oldValue: fromField.notNull ? 'NOT NULL' : 'NULLABLE',
|
|
103
|
+
newValue: toField.notNull ? 'NOT NULL' : 'NULLABLE',
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
// Dropped
|
|
109
|
+
for (const name of fromMap.keys()) {
|
|
110
|
+
if (!toMap.has(name)) {
|
|
111
|
+
changes.push({ type: 'dropped', column: name });
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return changes;
|
|
115
|
+
}
|
|
116
|
+
// ─────────────────────────────────────────────────────────────
|
|
117
|
+
// SchemaManager
|
|
118
|
+
// ─────────────────────────────────────────────────────────────
|
|
119
|
+
const DATABASE_INTROSPECT_ABI = [
|
|
120
|
+
'function getTableSchema(string calldata name) external view returns (bytes memory)',
|
|
121
|
+
'function getTable(string calldata name) external view returns (address)',
|
|
122
|
+
'function listTables() external view returns (string[] memory)',
|
|
123
|
+
];
|
|
124
|
+
export class SchemaManager {
|
|
125
|
+
db;
|
|
126
|
+
tableAddress;
|
|
127
|
+
tableClient;
|
|
128
|
+
dbContract;
|
|
129
|
+
constructor(db, tableAddress, tableClient, signer) {
|
|
130
|
+
this.db = db;
|
|
131
|
+
this.tableAddress = tableAddress;
|
|
132
|
+
this.tableClient = tableClient;
|
|
133
|
+
this.dbContract = new ethers.Contract(db.address, DATABASE_INTROSPECT_ABI, signer);
|
|
134
|
+
}
|
|
135
|
+
// ── Introspection ───────────────────────────────────────────
|
|
136
|
+
/**
|
|
137
|
+
* Read the schema bytes from the database contract and decode them
|
|
138
|
+
* into a usable FieldDescriptor array.
|
|
139
|
+
*
|
|
140
|
+
* @param tableName The name used when the table was created.
|
|
141
|
+
*/
|
|
142
|
+
async introspect(tableName) {
|
|
143
|
+
try {
|
|
144
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
145
|
+
const schemaBytes = await this.dbContract.getTableSchema(tableName);
|
|
146
|
+
return decodeSchemaBytes(schemaBytes);
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
return [];
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* List all table names in this database.
|
|
154
|
+
*/
|
|
155
|
+
async listTables() {
|
|
156
|
+
return this.db.listTables();
|
|
157
|
+
}
|
|
158
|
+
// ── Schema version tracking ─────────────────────────────────
|
|
159
|
+
/**
|
|
160
|
+
* Read the schema version stored in a meta record on-chain.
|
|
161
|
+
* Returns 0 if no version record has been written yet.
|
|
162
|
+
*/
|
|
163
|
+
async getSchemaVersion(tableName) {
|
|
164
|
+
const versionKey = this.tableClient.deriveKey(`__schema_version__${tableName}`, 0n);
|
|
165
|
+
try {
|
|
166
|
+
const exists = await this.tableClient.exists(versionKey);
|
|
167
|
+
if (!exists)
|
|
168
|
+
return 0;
|
|
169
|
+
const json = await this.tableClient.readPlaintext(versionKey);
|
|
170
|
+
const { version } = JSON.parse(json);
|
|
171
|
+
return version;
|
|
172
|
+
}
|
|
173
|
+
catch {
|
|
174
|
+
return 0;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Write (or update) the schema version meta record.
|
|
179
|
+
*/
|
|
180
|
+
async setSchemaVersion(tableName, version) {
|
|
181
|
+
const versionKey = this.tableClient.deriveKey(`__schema_version__${tableName}`, 0n);
|
|
182
|
+
const payload = JSON.stringify({ version, updatedAt: Date.now() });
|
|
183
|
+
const exists = await this.tableClient.exists(versionKey);
|
|
184
|
+
if (exists) {
|
|
185
|
+
await this.tableClient.updateRaw(versionKey, payload);
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
188
|
+
await this.tableClient.writeRaw(versionKey, payload);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
// ── Soft-rename ─────────────────────────────────────────────
|
|
192
|
+
/**
|
|
193
|
+
* Store a name alias mapping in a meta record.
|
|
194
|
+
* Future `introspect()` calls should use the new name.
|
|
195
|
+
*
|
|
196
|
+
* ⚠ The contract still uses the old name internally. This is a
|
|
197
|
+
* client-side alias only. To hard-rename, re-create the table.
|
|
198
|
+
*/
|
|
199
|
+
async renameTable(oldName, newName) {
|
|
200
|
+
const renameKey = this.tableClient.deriveKey(`__rename__${oldName}`, 0n);
|
|
201
|
+
const payload = JSON.stringify({ from: oldName, to: newName, renamedAt: Date.now() });
|
|
202
|
+
const exists = await this.tableClient.exists(renameKey);
|
|
203
|
+
if (exists) {
|
|
204
|
+
await this.tableClient.updateRaw(renameKey, payload);
|
|
205
|
+
}
|
|
206
|
+
else {
|
|
207
|
+
await this.tableClient.writeRaw(renameKey, payload);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
/** Check if a table has been soft-renamed. Returns the new name or null. */
|
|
211
|
+
async getRenamedTo(tableName) {
|
|
212
|
+
const renameKey = this.tableClient.deriveKey(`__rename__${tableName}`, 0n);
|
|
213
|
+
try {
|
|
214
|
+
const exists = await this.tableClient.exists(renameKey);
|
|
215
|
+
if (!exists)
|
|
216
|
+
return null;
|
|
217
|
+
const json = await this.tableClient.readPlaintext(renameKey);
|
|
218
|
+
const { to } = JSON.parse(json);
|
|
219
|
+
return to;
|
|
220
|
+
}
|
|
221
|
+
catch {
|
|
222
|
+
return null;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
// ── Soft-drop ───────────────────────────────────────────────
|
|
226
|
+
/**
|
|
227
|
+
* "Drop" a table by:
|
|
228
|
+
* 1. Deleting all owner records (batch, up to `maxRecords`).
|
|
229
|
+
* 2. Writing a __dropped__ meta record so the SDK knows to ignore it.
|
|
230
|
+
*
|
|
231
|
+
* ⚠ This is irreversible. The on-chain key->ciphertext mapping for
|
|
232
|
+
* deleted records is permanently unreadable (symmetric key scrubbed).
|
|
233
|
+
*
|
|
234
|
+
* @param ownerAddress Address whose records to delete.
|
|
235
|
+
* @param maxRecords Safety cap. Default: 500. Increase for large tables.
|
|
236
|
+
*/
|
|
237
|
+
async dropTable(ownerAddress, maxRecords = 500) {
|
|
238
|
+
const keys = await this.tableClient.listOwnerRecords(ownerAddress, 0n, BigInt(maxRecords));
|
|
239
|
+
let deleted = 0;
|
|
240
|
+
for (const key of keys) {
|
|
241
|
+
try {
|
|
242
|
+
await this.tableClient.deleteRecord(key);
|
|
243
|
+
deleted++;
|
|
244
|
+
}
|
|
245
|
+
catch { /* already deleted or no access — skip */ }
|
|
246
|
+
}
|
|
247
|
+
// Write tombstone
|
|
248
|
+
const tombstoneKey = this.tableClient.deriveKey(`__dropped__${this.tableAddress}`, 0n);
|
|
249
|
+
const payload = JSON.stringify({ droppedAt: Date.now(), ownerAddress });
|
|
250
|
+
await this.tableClient.writeRaw(tombstoneKey, payload);
|
|
251
|
+
return { deleted };
|
|
252
|
+
}
|
|
253
|
+
/** Check if a table has been soft-dropped. */
|
|
254
|
+
async isDropped() {
|
|
255
|
+
const tombstoneKey = this.tableClient.deriveKey(`__dropped__${this.tableAddress}`, 0n);
|
|
256
|
+
return this.tableClient.exists(tombstoneKey);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
//# sourceMappingURL=schema-manager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema-manager.js","sourceRoot":"","sources":["../../src/schema-manager.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAyB,QAAQ,CAAC;AAKnD,gEAAgE;AAChE,wBAAwB;AACxB,gEAAgE;AAEhE;;;GAGG;AACH,MAAM,sBAAsB,GAA2B;IACrD,SAAS,EAAE,KAAK;IAChB,QAAQ,EAAG,KAAK;IAChB,OAAO,EAAI,KAAK;IAChB,QAAQ,EAAG,QAAQ;IACnB,QAAQ,EAAG,QAAQ;IACnB,QAAQ,EAAG,QAAQ;IACnB,OAAO,EAAI,OAAO;IAClB,QAAQ,EAAG,MAAM;IACjB,MAAM,EAAK,MAAM;IACjB,SAAS,EAAE,SAAS;IACpB,SAAS,EAAE,SAAS;IACpB,OAAO,EAAI,SAAS;CACrB,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,UAAU,iBAAiB,CAAC,WAAgC;IAChE,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,OAAO,WAAW,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC;QAC3F,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,EAAE,CAAC;QAElC,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC,eAAe,EAAE,CAAC;QACnD,MAAM,OAAO,GAAI,QAAQ,CAAC,MAAM,CAC9B,CAAC,0EAA0E,CAAC,EAC5E,KAAK,CACN,CAAC;QACF,MAAM,MAAM,GAAG,OAAO,CAAC,CAAC,CAAoF,CAAC;QAC7G,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YACxB,IAAI,EAAQ,CAAC,CAAC,IAAI;YAClB,IAAI,EAAQ,CAAC,sBAAsB,CAAC,CAAC,CAAC,YAAY,CAAC,IAAI,MAAM,CAA4B;YACzF,UAAU,EAAE,CAAC,CAAC,UAAU,IAAI,SAAS;YACrC,OAAO,EAAK,CAAC,CAAC,OAAO,IAAO,SAAS;SACtC,CAAC,CAAC,CAAC;IACN,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAeD;;;GAGG;AACH,MAAM,UAAU,UAAU,CACxB,IAAuB,EACvB,EAAuB;IAEvB,MAAM,OAAO,GAAmB,EAAE,CAAC;IACnC,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IACtD,MAAM,KAAK,GAAK,IAAI,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IAEpD,mBAAmB;IACnB,KAAK,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,IAAI,KAAK,EAAE,CAAC;QACpC,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACpC,IAAI,CAAC,SAAS,EAAE,CAAC;YACf,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC;QACxE,CAAC;aAAM,CAAC;YACN,IAAI,SAAS,CAAC,IAAI,KAAK,OAAO,CAAC,IAAI,EAAE,CAAC;gBACpC,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,SAAS,CAAC,IAAI,EAAE,QAAQ,EAAE,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC;YACxG,CAAC;YACD,IAAI,OAAO,CAAC,SAAS,CAAC,OAAO,CAAC,KAAK,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;gBAC5D,OAAO,CAAC,IAAI,CAAC;oBACX,IAAI,EAAM,gBAAgB;oBAC1B,MAAM,EAAI,IAAI;oBACd,QAAQ,EAAE,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,UAAU;oBACrD,QAAQ,EAAE,OAAO,CAAC,OAAO,CAAG,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,UAAU;iBACtD,CAAC,CAAC;YACL,CAAC;QACH,CAAC;IACH,CAAC;IAED,UAAU;IACV,KAAK,MAAM,IAAI,IAAI,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC;QAClC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;YACrB,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;QAClD,CAAC;IACH,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,gEAAgE;AAChE,iBAAiB;AACjB,gEAAgE;AAEhE,MAAM,uBAAuB,GAAG;IAC9B,oFAAoF;IACpF,yEAAyE;IACzE,+DAA+D;CACvD,CAAC;AAEX,MAAM,OAAO,aAAa;IAChB,EAAE,CAA2B;IAC7B,YAAY,CAAS;IACrB,WAAW,CAAwB;IACnC,UAAU,CAAoB;IAEtC,YACE,EAA4B,EAC5B,YAAoB,EACpB,WAAkC,EAClC,MAA2B;QAE3B,IAAI,CAAC,EAAE,GAAa,EAAE,CAAC;QACvB,IAAI,CAAC,YAAY,GAAG,YAAY,CAAC;QACjC,IAAI,CAAC,WAAW,GAAI,WAAW,CAAC;QAChC,IAAI,CAAC,UAAU,GAAK,IAAI,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,uBAAuB,EAAE,MAAM,CAAC,CAAC;IACvF,CAAC;IAED,+DAA+D;IAE/D;;;;;OAKG;IACH,KAAK,CAAC,UAAU,CAAC,SAAiB;QAChC,IAAI,CAAC;YACH,8DAA8D;YAC9D,MAAM,WAAW,GAAG,MAAO,IAAI,CAAC,UAAkB,CAAC,cAAc,CAAC,SAAS,CAAW,CAAC;YACvF,OAAO,iBAAiB,CAAC,WAAW,CAAC,CAAC;QACxC,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,EAAE,CAAC;QACZ,CAAC;IACH,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,UAAU;QACd,OAAO,IAAI,CAAC,EAAE,CAAC,UAAU,EAAE,CAAC;IAC9B,CAAC;IAED,+DAA+D;IAE/D;;;OAGG;IACH,KAAK,CAAC,gBAAgB,CAAC,SAAiB;QACtC,MAAM,UAAU,GAAG,IAAI,CAAC,WAAW,CAAC,SAAS,CAAC,qBAAqB,SAAS,EAAE,EAAE,EAAE,CAAC,CAAC;QACpF,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;YACzD,IAAI,CAAC,MAAM;gBAAE,OAAO,CAAC,CAAC;YACtB,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,aAAa,CAAC,UAAU,CAAC,CAAC;YAC9D,MAAM,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAwB,CAAC;YAC5D,OAAO,OAAO,CAAC;QACjB,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,CAAC,CAAC;QACX,CAAC;IACH,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,gBAAgB,CAAC,SAAiB,EAAE,OAAe;QACvD,MAAM,UAAU,GAAG,IAAI,CAAC,WAAW,CAAC,SAAS,CAAC,qBAAqB,SAAS,EAAE,EAAE,EAAE,CAAC,CAAC;QACpF,MAAM,OAAO,GAAM,IAAI,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;QACtE,MAAM,MAAM,GAAO,MAAM,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;QAC7D,IAAI,MAAM,EAAE,CAAC;YACX,MAAM,IAAI,CAAC,WAAW,CAAC,SAAS,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;QACxD,CAAC;aAAM,CAAC;YACN,MAAM,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;QACvD,CAAC;IACH,CAAC;IAED,+DAA+D;IAE/D;;;;;;OAMG;IACH,KAAK,CAAC,WAAW,CAAC,OAAe,EAAE,OAAe;QAChD,MAAM,SAAS,GAAG,IAAI,CAAC,WAAW,CAAC,SAAS,CAAC,aAAa,OAAO,EAAE,EAAE,EAAE,CAAC,CAAC;QACzE,MAAM,OAAO,GAAK,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,OAAO,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;QACxF,MAAM,MAAM,GAAM,MAAM,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;QAC3D,IAAI,MAAM,EAAE,CAAC;YACX,MAAM,IAAI,CAAC,WAAW,CAAC,SAAS,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;QACvD,CAAC;aAAM,CAAC;YACN,MAAM,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;QACtD,CAAC;IACH,CAAC;IAED,4EAA4E;IAC5E,KAAK,CAAC,YAAY,CAAC,SAAiB;QAClC,MAAM,SAAS,GAAG,IAAI,CAAC,WAAW,CAAC,SAAS,CAAC,aAAa,SAAS,EAAE,EAAE,EAAE,CAAC,CAAC;QAC3E,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;YACxD,IAAI,CAAC,MAAM;gBAAE,OAAO,IAAI,CAAC;YACzB,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC;YAC7D,MAAM,EAAE,EAAE,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAmB,CAAC;YAClD,OAAO,EAAE,CAAC;QACZ,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IAED,+DAA+D;IAE/D;;;;;;;;;;OAUG;IACH,KAAK,CAAC,SAAS,CAAC,YAAoB,EAAE,UAAU,GAAG,GAAG;QACpD,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,gBAAgB,CAAC,YAAY,EAAE,EAAE,EAAE,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC;QAC3F,IAAI,OAAO,GAAG,CAAC,CAAC;QAChB,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;YACvB,IAAI,CAAC;gBACH,MAAM,IAAI,CAAC,WAAW,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC;gBACzC,OAAO,EAAE,CAAC;YACZ,CAAC;YAAC,MAAM,CAAC,CAAC,yCAAyC,CAAC,CAAC;QACvD,CAAC;QAED,kBAAkB;QAClB,MAAM,YAAY,GAAG,IAAI,CAAC,WAAW,CAAC,SAAS,CAAC,cAAc,IAAI,CAAC,YAAY,EAAE,EAAE,EAAE,CAAC,CAAC;QACvF,MAAM,OAAO,GAAQ,IAAI,CAAC,SAAS,CAAC,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,YAAY,EAAE,CAAC,CAAC;QAC7E,MAAM,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;QAEvD,OAAO,EAAE,OAAO,EAAE,CAAC;IACrB,CAAC;IAED,8CAA8C;IAC9C,KAAK,CAAC,SAAS;QACb,MAAM,YAAY,GAAG,IAAI,CAAC,WAAW,CAAC,SAAS,CAAC,cAAc,IAAI,CAAC,YAAY,EAAE,EAAE,EAAE,CAAC,CAAC;QACvF,OAAO,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC;IAC/C,CAAC;CACF"}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file table-client.ts
|
|
3
|
+
* @notice Base encrypted table client.
|
|
4
|
+
*
|
|
5
|
+
* This class wraps a Web3QL table contract proxy and handles ALL
|
|
6
|
+
* encryption/decryption client-side. The chain only ever sees:
|
|
7
|
+
* • ciphertext blobs (AES-equivalent via NaCl secretbox)
|
|
8
|
+
* • per-user encrypted key blobs (NaCl box)
|
|
9
|
+
*
|
|
10
|
+
* Plaintext and symmetric keys never leave this class.
|
|
11
|
+
*
|
|
12
|
+
* Usage pattern:
|
|
13
|
+
* ─────────────────────────────────────────────────────────────
|
|
14
|
+
* const client = new EncryptedTableClient(tableAddress, signer, keypair);
|
|
15
|
+
*
|
|
16
|
+
* // Write — encrypts automatically
|
|
17
|
+
* await client.write(1n, JSON.stringify({ name: 'Alice' }));
|
|
18
|
+
*
|
|
19
|
+
* // Read — decrypts automatically
|
|
20
|
+
* const data = await client.read(1n); // '{"name":"Alice"}'
|
|
21
|
+
*
|
|
22
|
+
* // Share with Bob (needs Bob registered in registry)
|
|
23
|
+
* await client.share(1n, bobAddress, Role.VIEWER, registry);
|
|
24
|
+
*
|
|
25
|
+
* // Revoke
|
|
26
|
+
* await client.revoke(1n, bobAddress);
|
|
27
|
+
*/
|
|
28
|
+
import { ethers } from 'ethers';
|
|
29
|
+
import { EncryptionKeypair } from './crypto.js';
|
|
30
|
+
import type { PublicKeyRegistryClient } from './registry.js';
|
|
31
|
+
export declare enum Role {
|
|
32
|
+
VIEWER = 1,
|
|
33
|
+
EDITOR = 2
|
|
34
|
+
}
|
|
35
|
+
export interface RawRecord {
|
|
36
|
+
ciphertext: Uint8Array;
|
|
37
|
+
deleted: boolean;
|
|
38
|
+
version: bigint;
|
|
39
|
+
updatedAt: bigint;
|
|
40
|
+
owner: string;
|
|
41
|
+
}
|
|
42
|
+
export declare class EncryptedTableClient {
|
|
43
|
+
readonly tableAddress: string;
|
|
44
|
+
protected contract: ethers.Contract;
|
|
45
|
+
protected signer: ethers.Signer;
|
|
46
|
+
/** The caller's X25519 keypair — private key STAYS in memory only. */
|
|
47
|
+
private keypair;
|
|
48
|
+
/** Shorthand for casting contract to any so strict index checks don't block calls. */
|
|
49
|
+
private get c();
|
|
50
|
+
constructor(tableAddress: string, signer: ethers.Signer, keypair: EncryptionKeypair, abi?: readonly string[]);
|
|
51
|
+
/**
|
|
52
|
+
* Derive the bytes32 on-chain record key from a table name + primary key.
|
|
53
|
+
* Canonical scheme: keccak256(abi.encodePacked(tableName, id))
|
|
54
|
+
* Matches the Solidity generator and connector — all three layers are aligned.
|
|
55
|
+
*/
|
|
56
|
+
deriveKey(tableName: string, id: bigint): string;
|
|
57
|
+
/**
|
|
58
|
+
* Encrypt `plaintext` and store it as a new record.
|
|
59
|
+
* The symmetric key is encrypted for the caller (owner).
|
|
60
|
+
*
|
|
61
|
+
* @param key bytes32 record key (use deriveKey or pass directly).
|
|
62
|
+
* @param plaintext Any data you want to store — string or raw bytes.
|
|
63
|
+
*/
|
|
64
|
+
writeRaw(key: string, plaintext: string | Uint8Array): Promise<ethers.TransactionReceipt>;
|
|
65
|
+
/**
|
|
66
|
+
* Encrypt `plaintext` for self — returns the raw ciphertext and
|
|
67
|
+
* encrypted symmetric key bytes WITHOUT submitting any transaction.
|
|
68
|
+
*
|
|
69
|
+
* Used by Model.relatedCreate() to hand the encrypted bytes to a
|
|
70
|
+
* RelationWire contract that will call table.write() on behalf of
|
|
71
|
+
* the user within an atomic transaction.
|
|
72
|
+
*/
|
|
73
|
+
encryptForSelf(plaintext: string | Uint8Array): Promise<{
|
|
74
|
+
ciphertext: Uint8Array;
|
|
75
|
+
encryptedKey: Uint8Array;
|
|
76
|
+
}>;
|
|
77
|
+
/**
|
|
78
|
+
* Read and decrypt a record.
|
|
79
|
+
* Returns the plaintext as a UTF-8 string.
|
|
80
|
+
* Throws if the record is deleted, doesn't exist, or you lack access.
|
|
81
|
+
*/
|
|
82
|
+
readPlaintext(key: string): Promise<string>;
|
|
83
|
+
/**
|
|
84
|
+
* Read and decrypt a record — returns raw bytes.
|
|
85
|
+
*/
|
|
86
|
+
readBytes(key: string): Promise<Uint8Array>;
|
|
87
|
+
/**
|
|
88
|
+
* Get the raw (still-encrypted) record from chain.
|
|
89
|
+
*/
|
|
90
|
+
readRaw(key: string): Promise<RawRecord>;
|
|
91
|
+
/**
|
|
92
|
+
* Update an existing record with new plaintext.
|
|
93
|
+
* Re-encrypts with a FRESH symmetric key — old key copies are NOT
|
|
94
|
+
* automatically re-shared. Call reshareAfterUpdate() afterwards
|
|
95
|
+
* if collaborators need access to the updated version.
|
|
96
|
+
*/
|
|
97
|
+
updateRaw(key: string, plaintext: string | Uint8Array): Promise<ethers.TransactionReceipt>;
|
|
98
|
+
/**
|
|
99
|
+
* Delete a record. The contract scrubs ALL collaborator key copies
|
|
100
|
+
* on-chain. Ciphertext remains but is permanently unreadable by
|
|
101
|
+
* anyone (the symmetric key is gone).
|
|
102
|
+
*/
|
|
103
|
+
deleteRecord(key: string): Promise<ethers.TransactionReceipt>;
|
|
104
|
+
/**
|
|
105
|
+
* Share a record with another user.
|
|
106
|
+
*
|
|
107
|
+
* Flow:
|
|
108
|
+
* 1. Fetch caller's encrypted key from chain
|
|
109
|
+
* 2. Decrypt to recover the symmetric key (requires caller's privkey)
|
|
110
|
+
* 3. Fetch recipient's X25519 public key from the registry
|
|
111
|
+
* 4. Re-encrypt the symmetric key for the recipient
|
|
112
|
+
* 5. Call grantAccess on-chain with the new encrypted key copy
|
|
113
|
+
*
|
|
114
|
+
* @param key bytes32 record key
|
|
115
|
+
* @param recipient Address to share with
|
|
116
|
+
* @param role Role.VIEWER or Role.EDITOR
|
|
117
|
+
* @param registry PublicKeyRegistryClient to look up recipient's pubkey
|
|
118
|
+
*/
|
|
119
|
+
share(key: string, recipient: string, role: Role, registry: PublicKeyRegistryClient): Promise<ethers.TransactionReceipt>;
|
|
120
|
+
/**
|
|
121
|
+
* Revoke a user's access. Their encrypted key copy is scrubbed
|
|
122
|
+
* on-chain — they can no longer decrypt the record.
|
|
123
|
+
* (They may have decrypted and cached it locally — that is a
|
|
124
|
+
* client-side concern, not something the chain can prevent.)
|
|
125
|
+
*/
|
|
126
|
+
revoke(key: string, user: string): Promise<ethers.TransactionReceipt>;
|
|
127
|
+
/**
|
|
128
|
+
* After updating a record (which rotates the key), re-share with
|
|
129
|
+
* all current collaborators so they can decrypt the new ciphertext.
|
|
130
|
+
*
|
|
131
|
+
* @param key bytes32 record key
|
|
132
|
+
* @param collaborators List of addresses to re-share with
|
|
133
|
+
* @param roles Role for each address (parallel array)
|
|
134
|
+
* @param registry PublicKeyRegistryClient
|
|
135
|
+
*/
|
|
136
|
+
reshareAfterUpdate(key: string, collaborators: string[], roles: Role[], registry: PublicKeyRegistryClient): Promise<void>;
|
|
137
|
+
/** Fetch this caller's encrypted key blob from chain (still encrypted). */
|
|
138
|
+
getMyEncryptedKey(key: string): Promise<Uint8Array>;
|
|
139
|
+
/**
|
|
140
|
+
* Decrypt a symmetric key blob that was encrypted by a known sender
|
|
141
|
+
* (use when you are a collaborator, not the owner — the sender's
|
|
142
|
+
* public key must be passed explicitly).
|
|
143
|
+
*/
|
|
144
|
+
decryptSharedKey(encryptedKey: Uint8Array, senderPublicKey: Uint8Array): Uint8Array;
|
|
145
|
+
exists(key: string): Promise<boolean>;
|
|
146
|
+
owner(key: string): Promise<string>;
|
|
147
|
+
collaboratorCount(key: string): Promise<number>;
|
|
148
|
+
/** List bytes32 record keys owned by `addr` (paginated).
|
|
149
|
+
* Prefers getActiveOwnerRecords (skips deleted) when available;
|
|
150
|
+
* falls back to getOwnerRecords for older contract versions.
|
|
151
|
+
*/
|
|
152
|
+
listOwnerRecords(addr: string, start?: bigint, limit?: bigint): Promise<string[]>;
|
|
153
|
+
/** Total number of records written by `addr` (including deleted). */
|
|
154
|
+
ownerRecordCount(addr: string): Promise<bigint>;
|
|
155
|
+
}
|
|
156
|
+
//# sourceMappingURL=table-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"table-client.d.ts","sourceRoot":"","sources":["../../src/table-client.ts"],"names":[],"mappings":"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"}
|