@peers-app/peers-sdk 0.9.0 → 0.9.2
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.
|
@@ -2,13 +2,12 @@ import { z } from 'zod';
|
|
|
2
2
|
import { ITableMetaData } from './types';
|
|
3
3
|
import { Table } from './table';
|
|
4
4
|
import { ITableDefinition, TableFactory, TableConstructor } from './table-definitions.type';
|
|
5
|
-
import type { ITableDefinitionRecord } from '../table-definitions-table';
|
|
6
5
|
/**
|
|
7
6
|
* Compare two table definition versions and decide whether to update.
|
|
8
|
-
* @returns 'update'
|
|
9
|
-
*
|
|
7
|
+
* @returns 'update' when incoming version is strictly newer, 'skip' otherwise.
|
|
8
|
+
* Logs an error if same version but different definitions (caller should bump versionNumber).
|
|
10
9
|
*/
|
|
11
|
-
export declare function checkVersionedUpdate(existingVersion: number, incomingVersion: number, existingMetaData:
|
|
10
|
+
export declare function checkVersionedUpdate(existingVersion: number, incomingVersion: number, existingMetaData: ITableMetaData, incomingMetaData: ITableMetaData): 'update' | 'skip';
|
|
12
11
|
export declare class TableContainer {
|
|
13
12
|
readonly tableFactory: TableFactory;
|
|
14
13
|
readonly groupId?: string | undefined;
|
|
@@ -17,6 +16,7 @@ export declare class TableContainer {
|
|
|
17
16
|
private savingDefinitions;
|
|
18
17
|
private readonly fallbackDefinitions;
|
|
19
18
|
private fallbackInstances;
|
|
19
|
+
private fallbackSubscribed;
|
|
20
20
|
constructor(tableFactory: TableFactory, groupId?: string | undefined);
|
|
21
21
|
registerTableDefinition<T extends {
|
|
22
22
|
[key: string]: any;
|
|
@@ -45,12 +45,22 @@ export declare class TableContainer {
|
|
|
45
45
|
getTableByName<T extends {
|
|
46
46
|
[key: string]: any;
|
|
47
47
|
}>(tableName: string): Table<T>;
|
|
48
|
+
/**
|
|
49
|
+
* Lazily look up a fallback table by name from the TableDefinitions system table.
|
|
50
|
+
* On first call, subscribes to TableDefinitions.dataChanged so future synced
|
|
51
|
+
* records are auto-registered (and removed on delete).
|
|
52
|
+
* Returns the table instance if found, or null if no definition exists.
|
|
53
|
+
*/
|
|
54
|
+
getFallbackTable(tableName: string): Promise<Table<any> | null>;
|
|
48
55
|
/**
|
|
49
56
|
* Register a table definition from a synced TableDefinitions record.
|
|
50
57
|
* These go into the fallback tier (no custom constructor) so they never
|
|
51
58
|
* block full code-defined instances from being created later.
|
|
59
|
+
*
|
|
60
|
+
* Called automatically by the getFallbackTable dataChanged subscription.
|
|
61
|
+
* External code should not need to call this directly.
|
|
52
62
|
*/
|
|
53
|
-
registerFromTableDefinitionRecord
|
|
63
|
+
private registerFromTableDefinitionRecord;
|
|
54
64
|
/**
|
|
55
65
|
* This ensures all tables have been new'ed up and then returns the entire list of them.
|
|
56
66
|
* Full (code-defined) instances take precedence over fallback (synced-definition) instances.
|
|
@@ -9,23 +9,36 @@ const utils_1 = require("../../utils");
|
|
|
9
9
|
const rpc_types_1 = require("../../rpc-types");
|
|
10
10
|
/**
|
|
11
11
|
* Compare two table definition versions and decide whether to update.
|
|
12
|
-
* @returns 'update'
|
|
13
|
-
*
|
|
12
|
+
* @returns 'update' when incoming version is strictly newer, 'skip' otherwise.
|
|
13
|
+
* Logs an error if same version but different definitions (caller should bump versionNumber).
|
|
14
14
|
*/
|
|
15
15
|
function checkVersionedUpdate(existingVersion, incomingVersion, existingMetaData, incomingMetaData) {
|
|
16
|
-
if (existingVersion
|
|
17
|
-
return '
|
|
16
|
+
if (existingVersion < incomingVersion) {
|
|
17
|
+
return 'update';
|
|
18
18
|
}
|
|
19
19
|
if (existingVersion === incomingVersion) {
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
const sameDefinition = (0, utils_1.simpleObjectHash)(existingMetaData) === (0, utils_1.simpleObjectHash)(incomingMetaData);
|
|
21
|
+
if (!sameDefinition) {
|
|
22
|
+
const tableName = (0, utils_1.getFullTableName)(existingMetaData);
|
|
23
|
+
console.error(`Table "${tableName}" has two different definitions with the same versionNumber (${existingMetaData.versionNumber}). Skipping update. Increment versionNumber when changing table definitions.`);
|
|
22
24
|
}
|
|
23
|
-
if ((0, utils_1.simpleObjectHash)(existingMetaData) !== (0, utils_1.simpleObjectHash)(incomingMetaData)) {
|
|
24
|
-
return 'skip-warn-conflict';
|
|
25
|
-
}
|
|
26
|
-
return 'skip'; // same version, same definition
|
|
27
25
|
}
|
|
28
|
-
return '
|
|
26
|
+
return 'skip';
|
|
27
|
+
// commenting this out for now, it's too likely to end up in an infinite loop
|
|
28
|
+
// if (existingVersion > incomingVersion) {
|
|
29
|
+
// return 'skip';
|
|
30
|
+
// }
|
|
31
|
+
// if (existingVersion === incomingVersion) {
|
|
32
|
+
// const sameDefinition = simpleObjectHash(existingMetaData) === simpleObjectHash(incomingMetaData);
|
|
33
|
+
// if (sameDefinition) {
|
|
34
|
+
// return 'skip'; // same version, same definition — nothing to do
|
|
35
|
+
// }
|
|
36
|
+
// if (!incomingVersion) {
|
|
37
|
+
// return 'update-warn-unversioned';
|
|
38
|
+
// }
|
|
39
|
+
// return 'skip-warn-conflict';
|
|
40
|
+
// }
|
|
41
|
+
// return 'update'; // incoming is strictly newer
|
|
29
42
|
}
|
|
30
43
|
class TableContainer {
|
|
31
44
|
tableFactory;
|
|
@@ -37,6 +50,7 @@ class TableContainer {
|
|
|
37
50
|
// These live in a separate map so they never block full instances from being created.
|
|
38
51
|
fallbackDefinitions = {};
|
|
39
52
|
fallbackInstances = {};
|
|
53
|
+
fallbackSubscribed = false;
|
|
40
54
|
constructor(tableFactory, groupId) {
|
|
41
55
|
this.tableFactory = tableFactory;
|
|
42
56
|
this.groupId = groupId;
|
|
@@ -53,6 +67,10 @@ class TableContainer {
|
|
|
53
67
|
return;
|
|
54
68
|
}
|
|
55
69
|
if (existing && opts?.overwrite) {
|
|
70
|
+
const existingDef = this.tableDefinitions[tableName];
|
|
71
|
+
if (existingDef && (0, utils_1.simpleObjectHash)(existingDef.metaData) === (0, utils_1.simpleObjectHash)(tableDefinition.metaData)) {
|
|
72
|
+
return; // definition hasn't changed, no need to overwrite
|
|
73
|
+
}
|
|
56
74
|
console.warn(`Overwriting table definition for ${tableName}.`);
|
|
57
75
|
delete this.tableInstances[tableName];
|
|
58
76
|
}
|
|
@@ -124,14 +142,7 @@ class TableContainer {
|
|
|
124
142
|
tableDefsTable.get(metaData.tableId).then(existing => {
|
|
125
143
|
if (existing) {
|
|
126
144
|
const result = checkVersionedUpdate(existing.versionNumber, record.versionNumber, existing.metaData, record.metaData);
|
|
127
|
-
if (result === '
|
|
128
|
-
console.error(`Table "${tableName}" has no versionNumber. Saving anyway but this could lose data. Set metaData.versionNumber to fix this.`);
|
|
129
|
-
}
|
|
130
|
-
else if (result === 'skip-warn-conflict') {
|
|
131
|
-
console.error(`Table "${tableName}" has two different definitions with the same versionNumber (${record.versionNumber}). Skipping update. Increment versionNumber when changing table definitions.`);
|
|
132
|
-
return;
|
|
133
|
-
}
|
|
134
|
-
else if (result === 'skip') {
|
|
145
|
+
if (result === 'skip') {
|
|
135
146
|
return;
|
|
136
147
|
}
|
|
137
148
|
}
|
|
@@ -172,10 +183,56 @@ class TableContainer {
|
|
|
172
183
|
}
|
|
173
184
|
throw new Error(`There is no instantiated table or registered definition for ${tableName}`);
|
|
174
185
|
}
|
|
186
|
+
/**
|
|
187
|
+
* Lazily look up a fallback table by name from the TableDefinitions system table.
|
|
188
|
+
* On first call, subscribes to TableDefinitions.dataChanged so future synced
|
|
189
|
+
* records are auto-registered (and removed on delete).
|
|
190
|
+
* Returns the table instance if found, or null if no definition exists.
|
|
191
|
+
*/
|
|
192
|
+
async getFallbackTable(tableName) {
|
|
193
|
+
// Already have an instance or definition from a previous lookup
|
|
194
|
+
if (this.fallbackInstances[tableName])
|
|
195
|
+
return this.fallbackInstances[tableName];
|
|
196
|
+
if (this.fallbackDefinitions[tableName]) {
|
|
197
|
+
const { metaData, schema } = this.fallbackDefinitions[tableName];
|
|
198
|
+
const table = this.tableFactory(metaData, schema ?? (0, types_1.fieldsToSchema)(metaData.fields), table_1.Table, this.groupId);
|
|
199
|
+
this.fallbackInstances[tableName] = table;
|
|
200
|
+
return table;
|
|
201
|
+
}
|
|
202
|
+
const tableDefsTable = this.tableInstances['TableDefinitions'];
|
|
203
|
+
if (!tableDefsTable)
|
|
204
|
+
return null;
|
|
205
|
+
// Subscribe once so future changes are picked up automatically
|
|
206
|
+
if (!this.fallbackSubscribed) {
|
|
207
|
+
this.fallbackSubscribed = true;
|
|
208
|
+
tableDefsTable.dataChanged.subscribe((event) => {
|
|
209
|
+
if (event.op === 'delete') {
|
|
210
|
+
const md = event.dataObject?.metaData;
|
|
211
|
+
if (md) {
|
|
212
|
+
const name = (0, utils_1.getFullTableName)(md);
|
|
213
|
+
delete this.fallbackDefinitions[name];
|
|
214
|
+
delete this.fallbackInstances[name];
|
|
215
|
+
}
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
this.registerFromTableDefinitionRecord(event.dataObject);
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
// Query for this specific table definition
|
|
222
|
+
const record = await tableDefsTable.findOne({ name: tableName });
|
|
223
|
+
if (!record)
|
|
224
|
+
return null;
|
|
225
|
+
this.registerFromTableDefinitionRecord(record);
|
|
226
|
+
// Now instantiate via getTableByName which checks fallbackDefinitions
|
|
227
|
+
return this.getTableByName(tableName);
|
|
228
|
+
}
|
|
175
229
|
/**
|
|
176
230
|
* Register a table definition from a synced TableDefinitions record.
|
|
177
231
|
* These go into the fallback tier (no custom constructor) so they never
|
|
178
232
|
* block full code-defined instances from being created later.
|
|
233
|
+
*
|
|
234
|
+
* Called automatically by the getFallbackTable dataChanged subscription.
|
|
235
|
+
* External code should not need to call this directly.
|
|
179
236
|
*/
|
|
180
237
|
registerFromTableDefinitionRecord(record) {
|
|
181
238
|
const metaData = record.metaData;
|
|
@@ -194,14 +251,7 @@ class TableContainer {
|
|
|
194
251
|
const existingVersion = existing.metaData.versionNumber ?? 0;
|
|
195
252
|
const incomingVersion = record.versionNumber ?? 0;
|
|
196
253
|
const result = checkVersionedUpdate(existingVersion, incomingVersion, existing.metaData, metaData);
|
|
197
|
-
if (result === '
|
|
198
|
-
console.error(`Table "${tableName}" has no versionNumber. Saving anyway but this could lose data. Set metaData.versionNumber to fix this.`);
|
|
199
|
-
}
|
|
200
|
-
else if (result === 'skip-warn-conflict') {
|
|
201
|
-
console.error(`Table "${tableName}" has two different definitions with the same versionNumber (${incomingVersion}). Skipping update. Increment versionNumber when changing table definitions.`);
|
|
202
|
-
return;
|
|
203
|
-
}
|
|
204
|
-
else if (result === 'skip') {
|
|
254
|
+
if (result === 'skip') {
|
|
205
255
|
return;
|
|
206
256
|
}
|
|
207
257
|
// Remove old fallback instance so it gets re-created with the new schema
|
|
@@ -23,14 +23,17 @@ describe('checkVersionedUpdate', () => {
|
|
|
23
23
|
it('should skip when same non-zero version and same definition with different key order', () => {
|
|
24
24
|
expect((0, table_container_1.checkVersionedUpdate)(100, 100, metaA, metaAReordered)).toBe('skip');
|
|
25
25
|
});
|
|
26
|
-
it('should
|
|
27
|
-
expect((0, table_container_1.checkVersionedUpdate)(100, 100, metaA, metaB)).toBe('skip
|
|
26
|
+
it('should skip when same non-zero version but different definitions', () => {
|
|
27
|
+
expect((0, table_container_1.checkVersionedUpdate)(100, 100, metaA, metaB)).toBe('skip');
|
|
28
28
|
});
|
|
29
|
-
it('should
|
|
30
|
-
expect((0, table_container_1.checkVersionedUpdate)(0, 0, metaA, metaA)).toBe('
|
|
29
|
+
it('should skip when both versions are 0 and definitions are the same', () => {
|
|
30
|
+
expect((0, table_container_1.checkVersionedUpdate)(0, 0, metaA, metaA)).toBe('skip');
|
|
31
31
|
});
|
|
32
|
-
it('should
|
|
33
|
-
expect((0, table_container_1.checkVersionedUpdate)(0, 0, metaA,
|
|
32
|
+
it('should skip when both versions are 0 and definitions are the same with different key order', () => {
|
|
33
|
+
expect((0, table_container_1.checkVersionedUpdate)(0, 0, metaA, metaAReordered)).toBe('skip');
|
|
34
|
+
});
|
|
35
|
+
it('should skip when both versions are 0 and definitions differ', () => {
|
|
36
|
+
expect((0, table_container_1.checkVersionedUpdate)(0, 0, metaA, metaB)).toBe('skip');
|
|
34
37
|
});
|
|
35
38
|
it('should update when incoming is newer regardless of definition content', () => {
|
|
36
39
|
expect((0, table_container_1.checkVersionedUpdate)(50, 100, metaA, metaA)).toBe('update');
|
|
@@ -86,6 +89,7 @@ describe('Tiered table instances', () => {
|
|
|
86
89
|
it('bare-bones fallback instance should NOT block full instance with custom constructor', () => {
|
|
87
90
|
const { container, testMetaData, testSchema, defRecord } = createTestHarness();
|
|
88
91
|
// Simulate sync: register from a TableDefinitionRecord (no custom constructor)
|
|
92
|
+
// (method is private — normally auto-called via dataChanged subscription)
|
|
89
93
|
container.registerFromTableDefinitionRecord(defRecord);
|
|
90
94
|
// Sync code calls getTableByName to get a working instance
|
|
91
95
|
const fallbackTable = container.getTableByName(defRecord.name);
|
|
@@ -99,7 +103,7 @@ describe('Tiered table instances', () => {
|
|
|
99
103
|
});
|
|
100
104
|
it('getTableByName should return full instance when one exists', () => {
|
|
101
105
|
const { container, testMetaData, testSchema, defRecord } = createTestHarness();
|
|
102
|
-
// Register fallback definition
|
|
106
|
+
// Register fallback definition (private — normally auto-called)
|
|
103
107
|
container.registerFromTableDefinitionRecord(defRecord);
|
|
104
108
|
// Package code creates the full instance first
|
|
105
109
|
const fullTable = container.getTable(testMetaData, testSchema, CustomTestTable);
|
|
@@ -111,7 +115,7 @@ describe('Tiered table instances', () => {
|
|
|
111
115
|
});
|
|
112
116
|
it('getTableByName should return fallback instance when no full instance exists', () => {
|
|
113
117
|
const { container, defRecord } = createTestHarness();
|
|
114
|
-
// Only register from definition record -- no full definition available
|
|
118
|
+
// Only register from definition record -- no full definition available (private — normally auto-called)
|
|
115
119
|
container.registerFromTableDefinitionRecord(defRecord);
|
|
116
120
|
// Should return a bare-bones Table instance
|
|
117
121
|
const table = container.getTableByName(defRecord.name);
|
|
@@ -138,7 +142,7 @@ describe('Tiered table instances', () => {
|
|
|
138
142
|
metaData: metaData2,
|
|
139
143
|
versionNumber: 0,
|
|
140
144
|
};
|
|
141
|
-
// Register both as fallback definitions
|
|
145
|
+
// Register both as fallback definitions (private — normally auto-called)
|
|
142
146
|
container.registerFromTableDefinitionRecord(defRecord);
|
|
143
147
|
container.registerFromTableDefinitionRecord(defRecord2);
|
|
144
148
|
// Create full instance for only the first table
|
package/dist/data/orm/table.js
CHANGED
|
@@ -45,7 +45,7 @@ class Table {
|
|
|
45
45
|
// Find any underlying SubscribableDataSource to intercept its events
|
|
46
46
|
let subscribableDataSource = undefined;
|
|
47
47
|
let _dataSource = this.dataSource;
|
|
48
|
-
while (
|
|
48
|
+
while (_dataSource) {
|
|
49
49
|
if (!subscribableDataSource && _dataSource instanceof subscribable_data_source_1.SubscribableDataSource) {
|
|
50
50
|
subscribableDataSource = _dataSource;
|
|
51
51
|
}
|
package/dist/keys.js
CHANGED
|
@@ -63,6 +63,9 @@ function encodeBase64(data) {
|
|
|
63
63
|
return str.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
|
|
64
64
|
}
|
|
65
65
|
function decodeBase64(data) {
|
|
66
|
+
if (typeof data !== 'string') {
|
|
67
|
+
throw new TypeError(`decodeBase64 expected a string but received ${typeof data}`);
|
|
68
|
+
}
|
|
66
69
|
data = data.replace(/-/g, '+').replace(/_/g, '/');
|
|
67
70
|
while (data.length % 4 !== 0) {
|
|
68
71
|
data += '=';
|
|
@@ -211,9 +214,12 @@ function addSignatureToObject(obj, secretKey) {
|
|
|
211
214
|
* Will throw if the signature is invalid
|
|
212
215
|
*/
|
|
213
216
|
function verifyObjectSignature(objectWithSignature) {
|
|
217
|
+
if (!objectWithSignature.signature) {
|
|
218
|
+
throw new Error('Object has no signature to verify');
|
|
219
|
+
}
|
|
214
220
|
const objWithoutSignature = { ...objectWithSignature };
|
|
215
221
|
delete objWithoutSignature.signature;
|
|
216
|
-
const [objPublicKey, objSignature] = objectWithSignature.signature
|
|
222
|
+
const [objPublicKey, objSignature] = objectWithSignature.signature.split(':');
|
|
217
223
|
const signedObject = {
|
|
218
224
|
contents: objWithoutSignature,
|
|
219
225
|
signature: objSignature,
|