@peers-app/peers-sdk 0.8.23 → 0.9.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.
- package/dist/context/user-context-singleton.d.ts +1 -0
- package/dist/context/user-context-singleton.js +8 -0
- package/dist/context/user-context.js +3 -0
- package/dist/data/assistants.d.ts +1 -1
- package/dist/data/assistants.js +1 -1
- package/dist/data/change-tracking.d.ts +8 -0
- package/dist/data/change-tracking.js +18 -0
- package/dist/data/channels.d.ts +2 -2
- package/dist/data/channels.js +3 -2
- package/dist/data/data-locks.test.js +8 -4
- package/dist/data/devices.d.ts +2 -2
- package/dist/data/devices.js +3 -2
- package/dist/data/embeddings.d.ts +1 -1
- package/dist/data/embeddings.js +2 -1
- package/dist/data/files/file.types.d.ts +2 -2
- package/dist/data/files/file.types.js +3 -2
- package/dist/data/groups.d.ts +1 -1
- package/dist/data/groups.js +1 -1
- package/dist/data/index.d.ts +1 -0
- package/dist/data/index.js +1 -0
- package/dist/data/messages.d.ts +5 -5
- package/dist/data/messages.js +5 -5
- package/dist/data/orm/dependency-injection.test.js +5 -2
- package/dist/data/orm/sql-db.d.ts +3 -1
- package/dist/data/orm/sql.data-source.test.js +5 -3
- package/dist/data/orm/table-container.d.ts +33 -0
- package/dist/data/orm/table-container.js +202 -8
- package/dist/data/orm/table-container.test.d.ts +1 -0
- package/dist/data/orm/table-container.test.js +163 -0
- package/dist/data/orm/table.d.ts +1 -0
- package/dist/data/orm/table.js +14 -2
- package/dist/data/orm/types.d.ts +2 -1
- package/dist/data/orm/types.js +14 -5
- package/dist/data/orm/types.test.js +136 -1
- package/dist/data/packages.d.ts +5 -5
- package/dist/data/packages.js +6 -5
- package/dist/data/peer-events/peer-event-handlers.d.ts +3 -3
- package/dist/data/peer-events/peer-event-handlers.js +4 -3
- package/dist/data/peer-events/peer-event-types.d.ts +1 -1
- package/dist/data/peer-events/peer-event-types.js +2 -1
- package/dist/data/persistent-vars.d.ts +1 -1
- package/dist/data/persistent-vars.js +2 -1
- package/dist/data/table-definitions-table.d.ts +31 -0
- package/dist/data/table-definitions-table.js +28 -0
- package/dist/data/tool-tests.d.ts +2 -2
- package/dist/data/tool-tests.js +3 -2
- package/dist/data/tools.d.ts +1 -1
- package/dist/data/tools.js +2 -1
- package/dist/data/user-trust-levels.d.ts +1 -1
- package/dist/data/user-trust-levels.js +2 -1
- package/dist/data/users.d.ts +1 -2
- package/dist/data/users.js +3 -4
- package/dist/data/workflow-logs.d.ts +2 -2
- package/dist/data/workflow-runs.d.ts +2 -2
- package/dist/keys.js +7 -1
- package/dist/types/workflow.d.ts +7 -7
- package/dist/types/workflow.js +6 -5
- package/dist/types/zod-types.js +2 -0
- package/package.json +1 -1
|
@@ -1,15 +1,56 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.TableContainer = void 0;
|
|
4
|
+
exports.checkVersionedUpdate = checkVersionedUpdate;
|
|
4
5
|
const types_1 = require("./types");
|
|
5
6
|
const table_1 = require("./table");
|
|
6
7
|
const table_definitions_system_1 = require("./table-definitions.system");
|
|
7
8
|
const utils_1 = require("../../utils");
|
|
9
|
+
const rpc_types_1 = require("../../rpc-types");
|
|
10
|
+
/**
|
|
11
|
+
* Compare two table definition versions and decide whether to update.
|
|
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
|
+
*/
|
|
15
|
+
function checkVersionedUpdate(existingVersion, incomingVersion, existingMetaData, incomingMetaData) {
|
|
16
|
+
if (existingVersion < incomingVersion) {
|
|
17
|
+
return 'update';
|
|
18
|
+
}
|
|
19
|
+
if (existingVersion === incomingVersion) {
|
|
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.`);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
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
|
|
42
|
+
}
|
|
8
43
|
class TableContainer {
|
|
9
44
|
tableFactory;
|
|
10
45
|
groupId;
|
|
11
46
|
tableDefinitions = {};
|
|
12
47
|
tableInstances = {};
|
|
48
|
+
savingDefinitions = false;
|
|
49
|
+
// Fallback tier: bare-bones definitions from synced TableDefinitionRecords (no custom constructors).
|
|
50
|
+
// These live in a separate map so they never block full instances from being created.
|
|
51
|
+
fallbackDefinitions = {};
|
|
52
|
+
fallbackInstances = {};
|
|
53
|
+
fallbackSubscribed = false;
|
|
13
54
|
constructor(tableFactory, groupId) {
|
|
14
55
|
this.tableFactory = tableFactory;
|
|
15
56
|
this.groupId = groupId;
|
|
@@ -26,6 +67,10 @@ class TableContainer {
|
|
|
26
67
|
return;
|
|
27
68
|
}
|
|
28
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
|
+
}
|
|
29
74
|
console.warn(`Overwriting table definition for ${tableName}.`);
|
|
30
75
|
delete this.tableInstances[tableName];
|
|
31
76
|
}
|
|
@@ -63,34 +108,183 @@ class TableContainer {
|
|
|
63
108
|
const TableClass = tableConstructor || registeredDef?.tableConstructor || (table_1.Table);
|
|
64
109
|
const table = this.tableFactory(metaData, schema, TableClass, this.groupId);
|
|
65
110
|
this.tableInstances[tableName] = table;
|
|
111
|
+
this.saveTableDefinition(metaData);
|
|
66
112
|
}
|
|
67
113
|
return this.tableInstances[tableName];
|
|
68
114
|
}
|
|
115
|
+
/**
|
|
116
|
+
* Auto-save table definition to the TableDefinitions system table.
|
|
117
|
+
* Only runs on server side, only for tables with a tableId (skips system tables),
|
|
118
|
+
* and skips saving the TableDefinitions table itself to avoid recursion.
|
|
119
|
+
*/
|
|
120
|
+
saveTableDefinition(metaData) {
|
|
121
|
+
if (rpc_types_1.isClient)
|
|
122
|
+
return;
|
|
123
|
+
if (!metaData.tableId)
|
|
124
|
+
return; // system tables don't need to be saved
|
|
125
|
+
if (metaData.name === 'TableDefinitions')
|
|
126
|
+
return; // avoid recursion
|
|
127
|
+
if (this.savingDefinitions)
|
|
128
|
+
return; // guard against re-entrant calls
|
|
129
|
+
this.savingDefinitions = true;
|
|
130
|
+
const tableName = (0, utils_1.getFullTableName)(metaData);
|
|
131
|
+
try {
|
|
132
|
+
// Lazy import to avoid circular dependency at module load time
|
|
133
|
+
const { TableDefinitions } = require('../table-definitions-table');
|
|
134
|
+
const tableDefsTable = TableDefinitions();
|
|
135
|
+
const record = {
|
|
136
|
+
tableId: metaData.tableId,
|
|
137
|
+
name: tableName,
|
|
138
|
+
metaData: metaData,
|
|
139
|
+
versionNumber: metaData.versionNumber ?? 0,
|
|
140
|
+
};
|
|
141
|
+
// Fire-and-forget: check if existing record has a newer versionNumber
|
|
142
|
+
tableDefsTable.get(metaData.tableId).then(existing => {
|
|
143
|
+
if (existing) {
|
|
144
|
+
const result = checkVersionedUpdate(existing.versionNumber, record.versionNumber, existing.metaData, record.metaData);
|
|
145
|
+
if (result === 'skip') {
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return tableDefsTable.save(record, { restoreIfDeleted: true, saveAsSnapshot: true });
|
|
150
|
+
}).catch(err => {
|
|
151
|
+
console.warn(`Failed to save table definition for ${tableName}:`, err);
|
|
152
|
+
}).finally(() => {
|
|
153
|
+
this.savingDefinitions = false;
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
catch (err) {
|
|
157
|
+
console.warn(`Failed to save table definition for ${tableName}:`, err);
|
|
158
|
+
this.savingDefinitions = false;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
69
161
|
getTableByName(tableName) {
|
|
162
|
+
// 1. Check for a full instance (from code/packages)
|
|
70
163
|
if (this.tableInstances[tableName]) {
|
|
71
|
-
// If the table is already instantiated, return it
|
|
72
164
|
return this.tableInstances[tableName];
|
|
73
165
|
}
|
|
166
|
+
// 2. Check for a full definition (code-registered or system) and instantiate it
|
|
74
167
|
const tableDefinition = this.tableDefinitions[tableName] || table_definitions_system_1.systemTableDefinitions[tableName];
|
|
75
|
-
if (
|
|
76
|
-
|
|
168
|
+
if (tableDefinition) {
|
|
169
|
+
const { metaData, schema, tableConstructor } = tableDefinition;
|
|
170
|
+
return this.getTable(metaData, schema, tableConstructor);
|
|
171
|
+
}
|
|
172
|
+
// 3. Check for a fallback instance (from synced TableDefinitionRecords)
|
|
173
|
+
if (this.fallbackInstances[tableName]) {
|
|
174
|
+
return this.fallbackInstances[tableName];
|
|
175
|
+
}
|
|
176
|
+
// 4. Check for a fallback definition and create a fallback instance
|
|
177
|
+
const fallbackDef = this.fallbackDefinitions[tableName];
|
|
178
|
+
if (fallbackDef) {
|
|
179
|
+
const { metaData, schema } = fallbackDef;
|
|
180
|
+
const table = this.tableFactory(metaData, schema ?? (0, types_1.fieldsToSchema)(metaData.fields), table_1.Table, this.groupId);
|
|
181
|
+
this.fallbackInstances[tableName] = table;
|
|
182
|
+
return table;
|
|
183
|
+
}
|
|
184
|
+
throw new Error(`There is no instantiated table or registered definition for ${tableName}`);
|
|
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
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Register a table definition from a synced TableDefinitions record.
|
|
231
|
+
* These go into the fallback tier (no custom constructor) so they never
|
|
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.
|
|
236
|
+
*/
|
|
237
|
+
registerFromTableDefinitionRecord(record) {
|
|
238
|
+
const metaData = record.metaData;
|
|
239
|
+
if (!metaData?.tableId || !metaData?.name || !metaData?.fields) {
|
|
240
|
+
console.warn(`Invalid table definition record, skipping:`, record);
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
const tableName = (0, utils_1.getFullTableName)(metaData);
|
|
244
|
+
// Don't overwrite a full (code-registered) definition -- that always takes precedence
|
|
245
|
+
const fullDef = this.tableDefinitions[tableName] || table_definitions_system_1.systemTableDefinitions[tableName];
|
|
246
|
+
if (fullDef) {
|
|
247
|
+
return; // full definition exists, no need for a fallback
|
|
77
248
|
}
|
|
78
|
-
const
|
|
79
|
-
|
|
249
|
+
const existing = this.fallbackDefinitions[tableName];
|
|
250
|
+
if (existing) {
|
|
251
|
+
const existingVersion = existing.metaData.versionNumber ?? 0;
|
|
252
|
+
const incomingVersion = record.versionNumber ?? 0;
|
|
253
|
+
const result = checkVersionedUpdate(existingVersion, incomingVersion, existing.metaData, metaData);
|
|
254
|
+
if (result === 'skip') {
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
// Remove old fallback instance so it gets re-created with the new schema
|
|
258
|
+
delete this.fallbackInstances[tableName];
|
|
259
|
+
}
|
|
260
|
+
const schema = (0, types_1.fieldsToSchema)(metaData.fields);
|
|
261
|
+
this.fallbackDefinitions[tableName] = { metaData, schema };
|
|
80
262
|
}
|
|
81
263
|
/**
|
|
82
264
|
* This ensures all tables have been new'ed up and then returns the entire list of them.
|
|
265
|
+
* Full (code-defined) instances take precedence over fallback (synced-definition) instances.
|
|
83
266
|
*
|
|
84
267
|
* This is primarily intended to be used by the peer-device sync operation which needs all
|
|
85
268
|
* tables to be able to apply changes correctly.
|
|
86
269
|
*/
|
|
87
270
|
getAllTables() {
|
|
88
|
-
|
|
89
|
-
|
|
271
|
+
// 1. Instantiate all full definitions (system + code-registered)
|
|
272
|
+
const fullDefinitions = [...Object.values(table_definitions_system_1.systemTableDefinitions), ...Object.values(this.tableDefinitions)];
|
|
273
|
+
for (const tableDefinition of fullDefinitions) {
|
|
90
274
|
const { metaData, schema, tableConstructor } = tableDefinition;
|
|
91
275
|
this.getTable(metaData, schema, tableConstructor);
|
|
92
276
|
}
|
|
93
|
-
|
|
277
|
+
// 2. Instantiate fallback definitions that don't already have a full instance
|
|
278
|
+
for (const [tableName, fallbackDef] of Object.entries(this.fallbackDefinitions)) {
|
|
279
|
+
if (!this.tableInstances[tableName] && !this.fallbackInstances[tableName]) {
|
|
280
|
+
const { metaData, schema } = fallbackDef;
|
|
281
|
+
const table = this.tableFactory(metaData, schema ?? (0, types_1.fieldsToSchema)(metaData.fields), table_1.Table, this.groupId);
|
|
282
|
+
this.fallbackInstances[tableName] = table;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
// 3. Merge: full instances take precedence, then add fallbacks for tables not in full
|
|
286
|
+
const result = { ...this.fallbackInstances, ...this.tableInstances };
|
|
287
|
+
return Object.values(result);
|
|
94
288
|
}
|
|
95
289
|
}
|
|
96
290
|
exports.TableContainer = TableContainer;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const zod_1 = require("zod");
|
|
4
|
+
const table_container_1 = require("./table-container");
|
|
5
|
+
const event_registry_1 = require("./event-registry");
|
|
6
|
+
const client_proxy_data_source_1 = require("./client-proxy.data-source");
|
|
7
|
+
const table_1 = require("./table");
|
|
8
|
+
const field_type_1 = require("../../types/field-type");
|
|
9
|
+
const utils_1 = require("../../utils");
|
|
10
|
+
describe('checkVersionedUpdate', () => {
|
|
11
|
+
const metaA = { name: 'TestTable', fields: [{ name: 'id', type: 'string' }] };
|
|
12
|
+
const metaAReordered = { fields: [{ name: 'id', type: 'string' }], name: 'TestTable' };
|
|
13
|
+
const metaB = { name: 'TestTable', fields: [{ name: 'id', type: 'number' }] };
|
|
14
|
+
it('should skip when existing version is strictly newer', () => {
|
|
15
|
+
expect((0, table_container_1.checkVersionedUpdate)(200, 100, metaA, metaB)).toBe('skip');
|
|
16
|
+
});
|
|
17
|
+
it('should update when incoming version is strictly newer', () => {
|
|
18
|
+
expect((0, table_container_1.checkVersionedUpdate)(100, 200, metaA, metaB)).toBe('update');
|
|
19
|
+
});
|
|
20
|
+
it('should skip when same non-zero version and same definition', () => {
|
|
21
|
+
expect((0, table_container_1.checkVersionedUpdate)(100, 100, metaA, metaA)).toBe('skip');
|
|
22
|
+
});
|
|
23
|
+
it('should skip when same non-zero version and same definition with different key order', () => {
|
|
24
|
+
expect((0, table_container_1.checkVersionedUpdate)(100, 100, metaA, metaAReordered)).toBe('skip');
|
|
25
|
+
});
|
|
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
|
+
});
|
|
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
|
+
});
|
|
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');
|
|
37
|
+
});
|
|
38
|
+
it('should update when incoming is newer regardless of definition content', () => {
|
|
39
|
+
expect((0, table_container_1.checkVersionedUpdate)(50, 100, metaA, metaA)).toBe('update');
|
|
40
|
+
expect((0, table_container_1.checkVersionedUpdate)(50, 100, metaA, metaB)).toBe('update');
|
|
41
|
+
});
|
|
42
|
+
it('should skip when existing is newer regardless of definition content', () => {
|
|
43
|
+
expect((0, table_container_1.checkVersionedUpdate)(100, 50, metaA, metaA)).toBe('skip');
|
|
44
|
+
expect((0, table_container_1.checkVersionedUpdate)(100, 50, metaA, metaB)).toBe('skip');
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
// --- Tiered table instance tests ---
|
|
48
|
+
// Custom Table subclass for testing
|
|
49
|
+
class CustomTestTable extends table_1.Table {
|
|
50
|
+
customMethod() {
|
|
51
|
+
return 'custom';
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function createTestHarness() {
|
|
55
|
+
const mockDataContext = { dataContextId: 'test-context' };
|
|
56
|
+
const eventRegistry = new event_registry_1.EventRegistry(mockDataContext);
|
|
57
|
+
const tableFactory = jest.fn().mockImplementation((metaData, schema, TableClass) => {
|
|
58
|
+
const deps = {
|
|
59
|
+
dataSource: new client_proxy_data_source_1.ClientProxyDataSource(metaData, schema, 'test-context'),
|
|
60
|
+
eventRegistry,
|
|
61
|
+
};
|
|
62
|
+
return new (TableClass || table_1.Table)(metaData, deps);
|
|
63
|
+
});
|
|
64
|
+
const container = new table_container_1.TableContainer(tableFactory);
|
|
65
|
+
const tableId = (0, utils_1.newid)();
|
|
66
|
+
const testMetaData = {
|
|
67
|
+
tableId,
|
|
68
|
+
name: 'TestPkgTable',
|
|
69
|
+
description: 'A package-defined table',
|
|
70
|
+
primaryKeyName: 'id',
|
|
71
|
+
fields: [
|
|
72
|
+
{ name: 'id', type: field_type_1.FieldType.id },
|
|
73
|
+
{ name: 'title', type: field_type_1.FieldType.string },
|
|
74
|
+
],
|
|
75
|
+
};
|
|
76
|
+
const testSchema = zod_1.z.object({
|
|
77
|
+
id: zod_1.z.string(),
|
|
78
|
+
title: zod_1.z.string(),
|
|
79
|
+
});
|
|
80
|
+
const defRecord = {
|
|
81
|
+
tableId,
|
|
82
|
+
name: `TestPkgTable_${tableId}`,
|
|
83
|
+
metaData: testMetaData,
|
|
84
|
+
versionNumber: 0,
|
|
85
|
+
};
|
|
86
|
+
return { container, testMetaData, testSchema, defRecord, tableFactory };
|
|
87
|
+
}
|
|
88
|
+
describe('Tiered table instances', () => {
|
|
89
|
+
it('bare-bones fallback instance should NOT block full instance with custom constructor', () => {
|
|
90
|
+
const { container, testMetaData, testSchema, defRecord } = createTestHarness();
|
|
91
|
+
// Simulate sync: register from a TableDefinitionRecord (no custom constructor)
|
|
92
|
+
// (method is private — normally auto-called via dataChanged subscription)
|
|
93
|
+
container.registerFromTableDefinitionRecord(defRecord);
|
|
94
|
+
// Sync code calls getTableByName to get a working instance
|
|
95
|
+
const fallbackTable = container.getTableByName(defRecord.name);
|
|
96
|
+
expect(fallbackTable).toBeInstanceOf(table_1.Table);
|
|
97
|
+
expect(fallbackTable).not.toBeInstanceOf(CustomTestTable);
|
|
98
|
+
// Later, package code calls getTable with the full custom constructor
|
|
99
|
+
const fullTable = container.getTable(testMetaData, testSchema, CustomTestTable);
|
|
100
|
+
// The full instance should be a CustomTestTable, NOT the bare-bones Table
|
|
101
|
+
expect(fullTable).toBeInstanceOf(CustomTestTable);
|
|
102
|
+
expect(fullTable.customMethod()).toBe('custom');
|
|
103
|
+
});
|
|
104
|
+
it('getTableByName should return full instance when one exists', () => {
|
|
105
|
+
const { container, testMetaData, testSchema, defRecord } = createTestHarness();
|
|
106
|
+
// Register fallback definition (private — normally auto-called)
|
|
107
|
+
container.registerFromTableDefinitionRecord(defRecord);
|
|
108
|
+
// Package code creates the full instance first
|
|
109
|
+
const fullTable = container.getTable(testMetaData, testSchema, CustomTestTable);
|
|
110
|
+
expect(fullTable).toBeInstanceOf(CustomTestTable);
|
|
111
|
+
// getTableByName should return the full instance, not create a new bare one
|
|
112
|
+
const byName = container.getTableByName(defRecord.name);
|
|
113
|
+
expect(byName).toBe(fullTable);
|
|
114
|
+
expect(byName).toBeInstanceOf(CustomTestTable);
|
|
115
|
+
});
|
|
116
|
+
it('getTableByName should return fallback instance when no full instance exists', () => {
|
|
117
|
+
const { container, defRecord } = createTestHarness();
|
|
118
|
+
// Only register from definition record -- no full definition available (private — normally auto-called)
|
|
119
|
+
container.registerFromTableDefinitionRecord(defRecord);
|
|
120
|
+
// Should return a bare-bones Table instance
|
|
121
|
+
const table = container.getTableByName(defRecord.name);
|
|
122
|
+
expect(table).toBeInstanceOf(table_1.Table);
|
|
123
|
+
expect(table).not.toBeInstanceOf(CustomTestTable);
|
|
124
|
+
});
|
|
125
|
+
it('getAllTables should prefer full instances over fallback instances', () => {
|
|
126
|
+
const { container, testMetaData, testSchema, defRecord, tableFactory } = createTestHarness();
|
|
127
|
+
// Create a second table that will only have a fallback definition
|
|
128
|
+
const tableId2 = (0, utils_1.newid)();
|
|
129
|
+
const metaData2 = {
|
|
130
|
+
tableId: tableId2,
|
|
131
|
+
name: 'OtherTable',
|
|
132
|
+
description: 'Another table',
|
|
133
|
+
primaryKeyName: 'id',
|
|
134
|
+
fields: [
|
|
135
|
+
{ name: 'id', type: field_type_1.FieldType.id },
|
|
136
|
+
{ name: 'value', type: field_type_1.FieldType.string },
|
|
137
|
+
],
|
|
138
|
+
};
|
|
139
|
+
const defRecord2 = {
|
|
140
|
+
tableId: tableId2,
|
|
141
|
+
name: `OtherTable_${tableId2}`,
|
|
142
|
+
metaData: metaData2,
|
|
143
|
+
versionNumber: 0,
|
|
144
|
+
};
|
|
145
|
+
// Register both as fallback definitions (private — normally auto-called)
|
|
146
|
+
container.registerFromTableDefinitionRecord(defRecord);
|
|
147
|
+
container.registerFromTableDefinitionRecord(defRecord2);
|
|
148
|
+
// Create full instance for only the first table
|
|
149
|
+
container.getTable(testMetaData, testSchema, CustomTestTable);
|
|
150
|
+
// getAllTables should include both tables
|
|
151
|
+
const allTables = container.getAllTables();
|
|
152
|
+
const tableNames = allTables.map(t => t.tableName);
|
|
153
|
+
expect(tableNames).toContain(defRecord.name);
|
|
154
|
+
expect(tableNames).toContain(defRecord2.name);
|
|
155
|
+
// The first table should be the full CustomTestTable instance
|
|
156
|
+
const firstTable = allTables.find(t => t.tableName === defRecord.name);
|
|
157
|
+
expect(firstTable).toBeInstanceOf(CustomTestTable);
|
|
158
|
+
// The second table should be a bare-bones Table (fallback)
|
|
159
|
+
const secondTable = allTables.find(t => t.tableName === defRecord2.name);
|
|
160
|
+
expect(secondTable).toBeInstanceOf(table_1.Table);
|
|
161
|
+
expect(secondTable).not.toBeInstanceOf(CustomTestTable);
|
|
162
|
+
});
|
|
163
|
+
});
|
package/dist/data/orm/table.d.ts
CHANGED
|
@@ -10,6 +10,7 @@ export declare class Table<T extends {
|
|
|
10
10
|
readonly tableName: string;
|
|
11
11
|
readonly primaryKeyName: string;
|
|
12
12
|
readonly schema: z.AnyZodObject;
|
|
13
|
+
private readonly codeSchema?;
|
|
13
14
|
protected readonly dataChangedEmitter: Emitter<IDataChangedEvent<T>>;
|
|
14
15
|
readonly dataChanged: Event<IDataChangedEvent<T>>;
|
|
15
16
|
private readonly localOperationCache;
|
package/dist/data/orm/table.js
CHANGED
|
@@ -14,6 +14,7 @@ class Table {
|
|
|
14
14
|
tableName;
|
|
15
15
|
primaryKeyName;
|
|
16
16
|
schema;
|
|
17
|
+
codeSchema;
|
|
17
18
|
dataChangedEmitter;
|
|
18
19
|
dataChanged;
|
|
19
20
|
localOperationCache = new Map();
|
|
@@ -23,7 +24,8 @@ class Table {
|
|
|
23
24
|
constructor(metaData, deps) {
|
|
24
25
|
this.metaData = metaData;
|
|
25
26
|
this.dataSource = deps.dataSource;
|
|
26
|
-
this.
|
|
27
|
+
this.codeSchema = deps.schema;
|
|
28
|
+
this.schema = (0, types_1.fieldsToSchema)(metaData.fields, deps.schema);
|
|
27
29
|
this.groupId = deps.groupId;
|
|
28
30
|
this.tableName = (0, utils_1.getFullTableName)(metaData);
|
|
29
31
|
this.primaryKeyName = metaData.primaryKeyName;
|
|
@@ -43,7 +45,7 @@ class Table {
|
|
|
43
45
|
// Find any underlying SubscribableDataSource to intercept its events
|
|
44
46
|
let subscribableDataSource = undefined;
|
|
45
47
|
let _dataSource = this.dataSource;
|
|
46
|
-
while (
|
|
48
|
+
while (_dataSource) {
|
|
47
49
|
if (!subscribableDataSource && _dataSource instanceof subscribable_data_source_1.SubscribableDataSource) {
|
|
48
50
|
subscribableDataSource = _dataSource;
|
|
49
51
|
}
|
|
@@ -219,6 +221,16 @@ class Table {
|
|
|
219
221
|
initRecord(data = {}) {
|
|
220
222
|
const defaults = {};
|
|
221
223
|
for (const field of this.metaData.fields) {
|
|
224
|
+
// Apply code schema first (has original defaults like () => new Date())
|
|
225
|
+
if (this.codeSchema?.shape?.[field.name]) {
|
|
226
|
+
const codeValue = this.codeSchema.shape[field.name].safeParse(data[field.name]);
|
|
227
|
+
if (codeValue.success) {
|
|
228
|
+
// @ts-ignore
|
|
229
|
+
defaults[field.name] = codeValue.data;
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
// Fall back to merged schema (fields-derived + code schema intersection)
|
|
222
234
|
const zodField = this.schema.shape[field.name];
|
|
223
235
|
const value = zodField.safeParse(data[field.name]);
|
|
224
236
|
if (value.success) {
|
package/dist/data/orm/types.d.ts
CHANGED
|
@@ -15,6 +15,7 @@ export interface ITableMetaData {
|
|
|
15
15
|
}[];
|
|
16
16
|
iconClassName?: string;
|
|
17
17
|
localOnly?: boolean;
|
|
18
|
+
versionNumber?: number;
|
|
18
19
|
}
|
|
19
|
-
export declare function fieldsToSchema(fields: IField[]): z.AnyZodObject;
|
|
20
|
+
export declare function fieldsToSchema(fields: IField[], codeSchema?: z.AnyZodObject): z.AnyZodObject;
|
|
20
21
|
export declare function schemaToFields(schema: z.AnyZodObject): IField[];
|
package/dist/data/orm/types.js
CHANGED
|
@@ -5,7 +5,7 @@ exports.schemaToFields = schemaToFields;
|
|
|
5
5
|
const zod_1 = require("zod");
|
|
6
6
|
const field_type_1 = require("../../types/field-type");
|
|
7
7
|
const utils_1 = require("../../utils");
|
|
8
|
-
function fieldsToSchema(fields) {
|
|
8
|
+
function fieldsToSchema(fields, codeSchema) {
|
|
9
9
|
let argsSchema = zod_1.z.object({});
|
|
10
10
|
for (const argSchema of fields) {
|
|
11
11
|
const { name, description, type, isArray, defaultValue, optional } = argSchema;
|
|
@@ -13,6 +13,7 @@ function fieldsToSchema(fields) {
|
|
|
13
13
|
switch (type) {
|
|
14
14
|
case 'id':
|
|
15
15
|
propertyType = zod_1.z.string().refine((id => (0, utils_1.isid)(id)), { message: 'Invalid id' });
|
|
16
|
+
propertyType._def._fieldType = field_type_1.FieldType.id;
|
|
16
17
|
break;
|
|
17
18
|
case 'string':
|
|
18
19
|
propertyType = zod_1.z.string();
|
|
@@ -43,6 +44,9 @@ function fieldsToSchema(fields) {
|
|
|
43
44
|
propertyType = propertyType.default(defaultValue);
|
|
44
45
|
}
|
|
45
46
|
propertyType = propertyType.describe(description);
|
|
47
|
+
if (codeSchema?.shape?.[name]) {
|
|
48
|
+
propertyType = zod_1.z.intersection(propertyType, codeSchema.shape[name]);
|
|
49
|
+
}
|
|
46
50
|
const newProperty = { [name]: propertyType };
|
|
47
51
|
argsSchema = argsSchema.extend(newProperty);
|
|
48
52
|
}
|
|
@@ -55,14 +59,22 @@ function schemaToFields(schema) {
|
|
|
55
59
|
let type = field_type_1.FieldType.any;
|
|
56
60
|
let _def = field._def;
|
|
57
61
|
let isArray = undefined;
|
|
62
|
+
let brandedFieldType;
|
|
58
63
|
while (true) {
|
|
59
64
|
if (_def.typeName === 'ZodArray') {
|
|
60
65
|
isArray = true;
|
|
61
66
|
}
|
|
67
|
+
if (_def._fieldType) {
|
|
68
|
+
brandedFieldType = _def._fieldType;
|
|
69
|
+
}
|
|
62
70
|
if (_def.innerType) {
|
|
63
71
|
_def = _def.innerType._def;
|
|
64
72
|
continue;
|
|
65
73
|
}
|
|
74
|
+
if (_def.schema) {
|
|
75
|
+
_def = _def.schema._def;
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
66
78
|
if (_def.type) {
|
|
67
79
|
_def = _def.type._def;
|
|
68
80
|
continue;
|
|
@@ -71,7 +83,7 @@ function schemaToFields(schema) {
|
|
|
71
83
|
}
|
|
72
84
|
switch (_def.typeName) {
|
|
73
85
|
case 'ZodString':
|
|
74
|
-
type = field_type_1.FieldType.string;
|
|
86
|
+
type = brandedFieldType || field_type_1.FieldType.string;
|
|
75
87
|
break;
|
|
76
88
|
case 'ZodNumber':
|
|
77
89
|
type = field_type_1.FieldType.number;
|
|
@@ -93,9 +105,6 @@ function schemaToFields(schema) {
|
|
|
93
105
|
default:
|
|
94
106
|
type = field_type_1.FieldType.any;
|
|
95
107
|
}
|
|
96
|
-
if ((name === 'id' || name.endsWith('Id')) && type === 'string') {
|
|
97
|
-
type = field_type_1.FieldType.id;
|
|
98
|
-
}
|
|
99
108
|
if (!isArray && field._def.typeName === 'ZodArray') {
|
|
100
109
|
isArray = true;
|
|
101
110
|
// isArray = field._def.typeName === 'ZodArray' || undefined;
|