@rljson/io 0.0.21 → 0.0.23

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/index.d.ts CHANGED
@@ -1,2 +1,4 @@
1
1
  export * from './io-mem.ts';
2
+ export * from './io-tools.ts';
2
3
  export * from './io.ts';
4
+ export * from './reverse-ref.ts';
package/dist/io-mem.d.ts CHANGED
@@ -6,7 +6,7 @@ import { Io } from './io.ts';
6
6
  */
7
7
  export declare class IoMem implements Io {
8
8
  constructor();
9
- static example: () => IoMem;
9
+ static example: () => Promise<IoMem>;
10
10
  isReady(): Promise<void>;
11
11
  dump(): Promise<Rljson>;
12
12
  dumpTable(request: {
@@ -18,20 +18,20 @@ export declare class IoMem implements Io {
18
18
  [column: string]: JsonValue;
19
19
  };
20
20
  }): Promise<Rljson>;
21
+ rowCount(table: string): Promise<number>;
21
22
  write(request: {
22
23
  data: Rljson;
23
24
  }): Promise<void>;
24
- createTable(request: {
25
+ createOrExtendTable(request: {
25
26
  tableCfg: TableCfg;
26
27
  }): Promise<void>;
27
28
  tableCfgs(): Promise<Rljson>;
28
- allTableNames(): Promise<string[]>;
29
+ private _ioTools;
29
30
  private _isReady;
30
31
  private _mem;
31
- private _ioInit;
32
32
  private _init;
33
33
  private _initTableCfgs;
34
- private _createTable;
34
+ private _createOrExtendTable;
35
35
  private _dump;
36
36
  private _dumpTable;
37
37
  private _write;
@@ -0,0 +1,48 @@
1
+ import { TableCfg, TableKey } from '@rljson/rljson';
2
+ import { Io } from './io.ts';
3
+ /**
4
+ * Provides utility functions for the Io interface.
5
+ */
6
+ export declare class IoTools {
7
+ readonly io: Io;
8
+ /**
9
+ * Constructor
10
+ * @param io The Io interface to use
11
+ */
12
+ constructor(io: Io);
13
+ /**
14
+ * Initializes the revisions table.
15
+ */
16
+ initRevisionsTable: () => Promise<void>;
17
+ /**
18
+ * Returns the table configuration of the tableCfgs table.
19
+ */
20
+ get tableCfgsTableCfg(): TableCfg;
21
+ /**
22
+ * Example object for test purposes
23
+ * @returns An instance of io tools
24
+ */
25
+ static example: () => Promise<IoTools>;
26
+ /**
27
+ * Returns a list with all table names
28
+ */
29
+ allTableKeys(): Promise<string[]>;
30
+ /**
31
+ * Returns the configuration of a given table
32
+ */
33
+ tableCfg(table: TableKey): Promise<TableCfg>;
34
+ /**
35
+ * Returns the configuration of a given table or null if it does not exist.
36
+
37
+ */
38
+ tableCfgOrNull(table: TableKey): Promise<TableCfg | null>;
39
+ /**
40
+ * Returns a list of all column names of a given table
41
+ */
42
+ allColumnKeys(table: TableKey): Promise<string[]>;
43
+ /**
44
+ * Throws when a table update is not compatible with the current table
45
+ * configuration.
46
+ */
47
+ throwWhenTableIsNotCompatible(update: TableCfg): Promise<void>;
48
+ }
package/dist/io.d.ts CHANGED
@@ -13,21 +13,19 @@ export interface Io {
13
13
  table: string;
14
14
  }): Promise<Rljson>;
15
15
  /**
16
- * Creates a table with a given config
17
- *
18
- * The config must be already in the database
16
+ * Creates a table with a given config.
17
+ * If the table already exists, new columns are added to the existing table.
18
+ * If the table does not exist, it is created with the given config.
19
+ * If the table exists and columns are removed, an error is thrown.
20
+ * If the table exists and the column type is changed, an error is thrown.
19
21
  */
20
- createTable(request: {
22
+ createOrExtendTable(request: {
21
23
  tableCfg: TableCfg;
22
24
  }): Promise<void>;
23
25
  /**
24
26
  * Returns a json structure returning current table configurations
25
27
  */
26
28
  tableCfgs(): Promise<Rljson>;
27
- /**
28
- * Returns an rljson with all available tables without data
29
- */
30
- allTableNames(): Promise<string[]>;
31
29
  /** Writes Rljson data into the database */
32
30
  write(request: {
33
31
  data: Rljson;
@@ -39,5 +37,7 @@ export interface Io {
39
37
  [column: string]: JsonValue;
40
38
  };
41
39
  }): Promise<Rljson>;
40
+ /** Returns the number of rows in the given table */
41
+ rowCount(table: string): Promise<number>;
42
42
  }
43
43
  export declare const exampleIo = "Checkout @rljson/io-mem for an example implementation";
package/dist/io.js CHANGED
@@ -3,46 +3,156 @@ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { en
3
3
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
4
  import { hip, hsh } from "@rljson/hash";
5
5
  import { IsReady } from "@rljson/is-ready";
6
- import { copy, equals } from "@rljson/json";
6
+ import { jsonValueTypes, copy, equals } from "@rljson/json";
7
+ import { iterateTablesSync } from "@rljson/rljson";
7
8
  // @license
8
- class IoInit {
9
+ const _IoTools = class _IoTools {
10
+ /**
11
+ * Constructor
12
+ * @param io The Io interface to use
13
+ */
9
14
  constructor(io) {
15
+ /**
16
+ * Initializes the revisions table.
17
+ */
10
18
  __publicField(this, "initRevisionsTable", async () => {
11
19
  const tableCfg = {
12
20
  version: 1,
13
21
  key: "revisions",
14
22
  type: "ingredients",
15
- isHead: false,
16
- isRoot: false,
17
- isShared: true,
18
- columns: {
19
- table: { type: "string" },
20
- predecessor: { type: "string" },
21
- successor: { type: "string" },
22
- timestamp: { type: "number" },
23
- id: { type: "string" }
24
- }
23
+ isHead: true,
24
+ isRoot: true,
25
+ isShared: false,
26
+ columns: [
27
+ { key: "table", type: "string" },
28
+ { key: "predecessor", type: "string" },
29
+ { key: "successor", type: "string" },
30
+ { key: "timestamp", type: "number" },
31
+ { key: "id", type: "string" }
32
+ ]
25
33
  };
26
- await this.io.createTable({ tableCfg });
34
+ await this.io.createOrExtendTable({ tableCfg });
27
35
  });
28
36
  this.io = io;
29
37
  }
30
- get tableCfg() {
38
+ /**
39
+ * Returns the table configuration of the tableCfgs table.
40
+ */
41
+ get tableCfgsTableCfg() {
31
42
  const tableCfg = hip({
32
- version: 1,
43
+ _hash: "",
33
44
  key: "tableCfgs",
34
45
  type: "ingredients",
35
46
  isHead: false,
36
47
  isRoot: false,
37
48
  isShared: true,
38
- columns: {
39
- key: { type: "string" },
40
- type: { type: "string" }
41
- }
49
+ version: 1,
50
+ columns: [
51
+ { key: "_hash", type: "string" },
52
+ { key: "key", type: "string" },
53
+ { key: "type", type: "string" },
54
+ { key: "isHead", type: "boolean" },
55
+ { key: "isRoot", type: "boolean" },
56
+ { key: "isShared", type: "boolean" },
57
+ { key: "version", type: "number" },
58
+ { key: "columns", type: "jsonArray" }
59
+ ]
42
60
  });
43
61
  return tableCfg;
44
62
  }
45
- }
63
+ /**
64
+ * Returns a list with all table names
65
+ */
66
+ async allTableKeys() {
67
+ const result = (await this.io.tableCfgs()).tableCfgs._data.map(
68
+ (e) => e.key
69
+ );
70
+ return result;
71
+ }
72
+ /**
73
+ * Returns the configuration of a given table
74
+ */
75
+ async tableCfg(table) {
76
+ const tableCfg = await this.tableCfgOrNull(table);
77
+ if (!tableCfg) {
78
+ throw new Error(`Table "${table}" not found`);
79
+ }
80
+ return tableCfg;
81
+ }
82
+ /**
83
+ * Returns the configuration of a given table or null if it does not exist.
84
+
85
+ */
86
+ async tableCfgOrNull(table) {
87
+ const tableCfgs = await this.io.tableCfgs();
88
+ const tableCfg = tableCfgs.tableCfgs._data.find((e) => e.key === table);
89
+ return tableCfg ?? null;
90
+ }
91
+ /**
92
+ * Returns a list of all column names of a given table
93
+ */
94
+ async allColumnKeys(table) {
95
+ const tableCfg = await this.tableCfg(table);
96
+ const result = tableCfg.columns.map((column) => column.key);
97
+ return result;
98
+ }
99
+ /**
100
+ * Throws when a table update is not compatible with the current table
101
+ * configuration.
102
+ */
103
+ async throwWhenTableIsNotCompatible(update) {
104
+ const prefix = `Invalid update of table able "${update.key}"`;
105
+ for (const column of update.columns) {
106
+ if (!jsonValueTypes.includes(column.type)) {
107
+ throw new Error(
108
+ `${prefix}: Column "${column.key}" has an unsupported type "${column.type}"`
109
+ );
110
+ }
111
+ }
112
+ const existing = await this.tableCfgOrNull(update.key);
113
+ if (existing) {
114
+ if (existing.columns.length > update.columns.length) {
115
+ const deletedColumnKeys = existing.columns.map((column) => column.key).filter(
116
+ (key) => !update.columns.some((column) => column.key === key)
117
+ );
118
+ if (deletedColumnKeys.length > 0) {
119
+ const deletedColumns = deletedColumnKeys.join(", ");
120
+ throw new Error(
121
+ `${prefix}: Columns must not be deleted. Deleted columns: ${deletedColumns}}`
122
+ );
123
+ }
124
+ }
125
+ for (let i = 0; i < existing.columns.length; i++) {
126
+ const before = existing.columns[i].key;
127
+ const after = update.columns[i].key;
128
+ if (before !== after) {
129
+ throw new Error(
130
+ `${prefix}: Column keys must not change! Column "${before}" was renamed into "${after}".`
131
+ );
132
+ }
133
+ }
134
+ for (let i = 0; i < existing.columns.length; i++) {
135
+ const column = existing.columns[i].key;
136
+ const before = existing.columns[i].type;
137
+ const after = update.columns[i].type;
138
+ if (before !== after) {
139
+ throw new Error(
140
+ `${prefix}: Column types must not change! Type of column "${column}" was changed from "${before}" to ${after}.`
141
+ );
142
+ }
143
+ }
144
+ }
145
+ }
146
+ };
147
+ /**
148
+ * Example object for test purposes
149
+ * @returns An instance of io tools
150
+ */
151
+ __publicField(_IoTools, "example", async () => {
152
+ const io = await IoMem.example();
153
+ return new _IoTools(io);
154
+ });
155
+ let IoTools = _IoTools;
46
156
  // @license
47
157
  const _IoMem = class _IoMem {
48
158
  // ...........................................................................
@@ -51,12 +161,12 @@ const _IoMem = class _IoMem {
51
161
  // ######################
52
162
  // Private
53
163
  // ######################
164
+ __publicField(this, "_ioTools");
54
165
  __publicField(this, "_isReady", new IsReady());
55
166
  __publicField(this, "_mem", hip({}));
56
- __publicField(this, "_ioInit");
57
167
  // ...........................................................................
58
168
  __publicField(this, "_initTableCfgs", () => {
59
- const tableCfg = this._ioInit.tableCfg;
169
+ const tableCfg = this._ioTools.tableCfgsTableCfg;
60
170
  this._mem.tableCfgs = hip({
61
171
  _data: [tableCfg],
62
172
  _type: "ingredients",
@@ -84,6 +194,13 @@ const _IoMem = class _IoMem {
84
194
  readRows(request) {
85
195
  return this._readRows(request);
86
196
  }
197
+ async rowCount(table) {
198
+ const tableData = this._mem[table];
199
+ if (!tableData) {
200
+ throw new Error(`Table "${table}" not found`);
201
+ }
202
+ return Promise.resolve(tableData._data.length);
203
+ }
87
204
  // ...........................................................................
88
205
  // Write
89
206
  write(request) {
@@ -91,8 +208,8 @@ const _IoMem = class _IoMem {
91
208
  }
92
209
  // ...........................................................................
93
210
  // Table management
94
- createTable(request) {
95
- return this._createTable(request);
211
+ createOrExtendTable(request) {
212
+ return this._createOrExtendTable(request);
96
213
  }
97
214
  async tableCfgs() {
98
215
  const tables = this._mem.tableCfgs._data;
@@ -113,25 +230,18 @@ const _IoMem = class _IoMem {
113
230
  }
114
231
  });
115
232
  }
116
- async allTableNames() {
117
- const tables = Object.keys(this._mem).filter((key) => !key.startsWith("_"));
118
- return tables;
119
- }
120
233
  // ...........................................................................
121
234
  async _init() {
122
- this._ioInit = new IoInit(this);
235
+ this._ioTools = new IoTools(this);
123
236
  this._initTableCfgs();
124
- await this._ioInit.initRevisionsTable();
237
+ await this._ioTools.initRevisionsTable();
125
238
  this._isReady.resolve();
126
239
  }
127
240
  // ...........................................................................
128
- async _createTable(request) {
241
+ async _createOrExtendTable(request) {
129
242
  var _a;
130
- const { key, type } = request.tableCfg;
131
- const existing = this._mem[key];
132
- if (existing) {
133
- throw new Error(`Table ${key} already exists`);
134
- }
243
+ await this._ioTools.throwWhenTableIsNotCompatible(request.tableCfg);
244
+ const { type, key } = request.tableCfg;
135
245
  const newConfig = hsh(request.tableCfg);
136
246
  const existingConfig = this._mem.tableCfgs._data.find(
137
247
  (cfg) => cfg._hash === newConfig._hash
@@ -139,9 +249,10 @@ const _IoMem = class _IoMem {
139
249
  if (!existingConfig) {
140
250
  this._mem.tableCfgs._data.push(newConfig);
141
251
  this._mem.tableCfgs._hash = "";
142
- const updateExistingHashes = false;
143
- const throwOnWrongHashes = false;
144
- hip(this._mem.tableCfgs, { updateExistingHashes, throwOnWrongHashes });
252
+ hip(this._mem.tableCfgs, {
253
+ updateExistingHashes: false,
254
+ throwOnWrongHashes: false
255
+ });
145
256
  }
146
257
  const table = {
147
258
  _data: [],
@@ -217,13 +328,117 @@ const _IoMem = class _IoMem {
217
328
  return result;
218
329
  }
219
330
  };
220
- __publicField(_IoMem, "example", () => {
331
+ __publicField(_IoMem, "example", async () => {
221
332
  return new _IoMem();
222
333
  });
223
334
  let IoMem = _IoMem;
224
335
  // @license
225
336
  const exampleIo = "Checkout @rljson/io-mem for an example implementation";
337
+ // @license
338
+ const calcReverseRefs = (rljson) => {
339
+ const result = {};
340
+ iterateTablesSync(rljson, (childTableKey, table) => {
341
+ const childTable = {};
342
+ result[childTableKey] = childTable;
343
+ for (const childRow of table._data) {
344
+ childTable[childRow._hash] = {};
345
+ }
346
+ });
347
+ iterateTablesSync(rljson, (parentTableKey, parentTable) => {
348
+ for (const parentTableRow of parentTable._data) {
349
+ switch (parentTable._type) {
350
+ case "ingredients":
351
+ _writeIngredientRefs(parentTableKey, parentTableRow, result);
352
+ break;
353
+ case "layers": {
354
+ _writeLayerRefs(parentTableKey, parentTableRow, result);
355
+ break;
356
+ }
357
+ case "sliceIds": {
358
+ break;
359
+ }
360
+ case "cakes": {
361
+ _writeCakeRefs(parentTableKey, parentTableRow, result);
362
+ break;
363
+ }
364
+ case "buffets": {
365
+ _writeBuffetRefs(parentTableKey, parentTableRow, result);
366
+ break;
367
+ }
368
+ }
369
+ }
370
+ });
371
+ return result;
372
+ };
373
+ const _writeIngredientRefs = (parentTableName, parentRow, result) => {
374
+ const parentRowHash = parentRow._hash;
375
+ for (const parentColumnName in parentRow) {
376
+ if (parentColumnName.startsWith("_")) {
377
+ continue;
378
+ }
379
+ if (!parentColumnName.endsWith("Ref")) {
380
+ continue;
381
+ }
382
+ const childTableName = parentColumnName.slice(0, -3);
383
+ const childRowHash = parentRow[parentColumnName];
384
+ _write(
385
+ result,
386
+ childTableName,
387
+ childRowHash,
388
+ parentTableName,
389
+ parentRowHash
390
+ );
391
+ }
392
+ };
393
+ const _writeLayerRefs = (parentTableName, parentRow, result) => {
394
+ const childTableName = parentRow.ingredientsTable;
395
+ const parentRowHash = parentRow._hash;
396
+ for (const sliceId in parentRow.assign) {
397
+ if (sliceId.startsWith("_")) {
398
+ continue;
399
+ }
400
+ const sliceHash = parentRow.assign[sliceId];
401
+ _write(result, childTableName, sliceHash, parentTableName, parentRowHash);
402
+ }
403
+ };
404
+ const _writeCakeRefs = (parentTableName, parentRow, result) => {
405
+ const parentRowHash = parentRow._hash;
406
+ for (const layer in parentRow.layers) {
407
+ const childTableName = parentRow.layersTable;
408
+ const childRowHash = parentRow.layers[layer];
409
+ _write(
410
+ result,
411
+ childTableName,
412
+ childRowHash,
413
+ parentTableName,
414
+ parentRowHash
415
+ );
416
+ }
417
+ };
418
+ const _writeBuffetRefs = (parentTableName, parentRow, result) => {
419
+ const parentRowHash = parentRow._hash;
420
+ for (const item of parentRow.items) {
421
+ const childTableName = item.table;
422
+ const childRowHash = item.ref;
423
+ _write(
424
+ result,
425
+ childTableName,
426
+ childRowHash,
427
+ parentTableName,
428
+ parentRowHash
429
+ );
430
+ }
431
+ };
432
+ const _write = (result, childTableName, childRowHash, parentTableName, parentRowHash) => {
433
+ var _a;
434
+ const referencesForChildTable = result[childTableName] ?? (result[childTableName] = {});
435
+ const referencesForChildTableRow = referencesForChildTable[childRowHash] ?? (referencesForChildTable[childRowHash] = {});
436
+ referencesForChildTableRow[parentTableName] ?? (referencesForChildTableRow[parentTableName] = {});
437
+ (_a = referencesForChildTableRow[parentTableName])[parentRowHash] ?? (_a[parentRowHash] = {});
438
+ };
226
439
  export {
227
440
  IoMem,
441
+ IoTools,
442
+ calcReverseRefs,
228
443
  exampleIo
229
444
  };
@@ -17,11 +17,15 @@ export const example = async () => {
17
17
  const rowWithHash = hsh(row);
18
18
 
19
19
  // Create a table config first
20
- const tableCfg = hip({
20
+ const tableCfg = hip<TableCfg>({
21
21
  key: 'tableA',
22
22
  type: 'ingredients',
23
- columns: {},
24
- } as TableCfg);
23
+ columns: [],
24
+ version: 1,
25
+ isHead: true,
26
+ isRoot: true,
27
+ isShared: false,
28
+ });
25
29
 
26
30
  await ioMem.write({
27
31
  data: {
@@ -33,7 +37,7 @@ export const example = async () => {
33
37
  });
34
38
 
35
39
  // Create a table first
36
- await ioMem.createTable({ tableCfg: tableCfg });
40
+ await ioMem.createOrExtendTable({ tableCfg: tableCfg });
37
41
 
38
42
  // Write data into the table
39
43
  await ioMem.write({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rljson/io",
3
- "version": "0.0.21",
3
+ "version": "0.0.23",
4
4
  "packageManager": "pnpm@10.6.2",
5
5
  "description": "Low level interface for reading and writing RLJSON data",
6
6
  "homepage": "https://github.com/rljson/io",
@@ -29,24 +29,24 @@
29
29
  "updateGoldens": "cross-env UPDATE_GOLDENS=true pnpm test"
30
30
  },
31
31
  "devDependencies": {
32
- "@types/node": "^22.14.1",
33
- "@typescript-eslint/eslint-plugin": "^8.30.1",
34
- "@typescript-eslint/parser": "^8.30.1",
35
- "@vitest/coverage-v8": "^3.1.1",
32
+ "@types/node": "^22.15.2",
33
+ "@typescript-eslint/eslint-plugin": "^8.31.0",
34
+ "@typescript-eslint/parser": "^8.31.0",
35
+ "@vitest/coverage-v8": "^3.1.2",
36
36
  "cross-env": "^7.0.3",
37
- "eslint": "^9.24.0",
38
- "eslint-plugin-jsdoc": "^50.6.9",
37
+ "eslint": "^9.25.1",
38
+ "eslint-plugin-jsdoc": "^50.6.11",
39
39
  "eslint-plugin-tsdoc": "^0.4.0",
40
40
  "globals": "^16.0.0",
41
41
  "jsdoc": "^4.0.4",
42
42
  "read-pkg": "^9.0.1",
43
43
  "typescript": "~5.8.3",
44
- "typescript-eslint": "^8.30.1",
45
- "vite": "^6.2.6",
46
- "vite-node": "^3.1.1",
44
+ "typescript-eslint": "^8.31.0",
45
+ "vite": "^6.3.3",
46
+ "vite-node": "^3.1.2",
47
47
  "vite-plugin-dts": "^4.5.3",
48
48
  "vite-tsconfig-paths": "^5.1.4",
49
- "vitest": "^3.1.1",
49
+ "vitest": "^3.1.2",
50
50
  "vitest-dom": "^0.1.1"
51
51
  },
52
52
  "pnpm": {
@@ -56,10 +56,10 @@
56
56
  "overrides": {}
57
57
  },
58
58
  "dependencies": {
59
- "@rljson/hash": "^0.0.13",
59
+ "@rljson/hash": "^0.0.15",
60
60
  "@rljson/is-ready": "^0.0.17",
61
- "@rljson/json": "^0.0.18",
62
- "@rljson/rljson": "^0.0.38",
63
- "@rljson/validate": "^0.0.9"
61
+ "@rljson/json": "^0.0.21",
62
+ "@rljson/rljson": "^0.0.42",
63
+ "@rljson/validate": "^0.0.10"
64
64
  }
65
65
  }
package/dist/io-init.d.ts DELETED
@@ -1,11 +0,0 @@
1
- import { TableCfg } from '@rljson/rljson';
2
- import { Io } from './io.ts';
3
- /**
4
- * Initialization tools for Io
5
- */
6
- export declare class IoInit {
7
- readonly io: Io;
8
- constructor(io: Io);
9
- get tableCfg(): TableCfg;
10
- initRevisionsTable: () => Promise<void>;
11
- }