@stonyx/orm 0.2.1-beta.83 → 0.2.1-beta.85

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (150) hide show
  1. package/dist/aggregates.d.ts +21 -0
  2. package/dist/aggregates.js +90 -0
  3. package/dist/attr.d.ts +2 -0
  4. package/dist/attr.js +22 -0
  5. package/dist/belongs-to.d.ts +11 -0
  6. package/dist/belongs-to.js +59 -0
  7. package/dist/cli.d.ts +22 -0
  8. package/dist/cli.js +148 -0
  9. package/dist/commands.d.ts +7 -0
  10. package/dist/commands.js +146 -0
  11. package/dist/db.d.ts +21 -0
  12. package/dist/db.js +174 -0
  13. package/dist/exports/db.d.ts +7 -0
  14. package/{src → dist}/exports/db.js +2 -4
  15. package/dist/has-many.d.ts +11 -0
  16. package/dist/has-many.js +58 -0
  17. package/dist/hooks.d.ts +47 -0
  18. package/dist/hooks.js +106 -0
  19. package/dist/index.d.ts +14 -0
  20. package/dist/index.js +34 -0
  21. package/dist/main.d.ts +46 -0
  22. package/dist/main.js +179 -0
  23. package/dist/manage-record.d.ts +13 -0
  24. package/dist/manage-record.js +114 -0
  25. package/dist/meta-request.d.ts +6 -0
  26. package/dist/meta-request.js +52 -0
  27. package/dist/migrate.d.ts +2 -0
  28. package/dist/migrate.js +57 -0
  29. package/dist/model-property.d.ts +9 -0
  30. package/dist/model-property.js +29 -0
  31. package/dist/model.d.ts +15 -0
  32. package/dist/model.js +18 -0
  33. package/dist/mysql/connection.d.ts +14 -0
  34. package/dist/mysql/connection.js +24 -0
  35. package/dist/mysql/migration-generator.d.ts +45 -0
  36. package/dist/mysql/migration-generator.js +245 -0
  37. package/dist/mysql/migration-runner.d.ts +12 -0
  38. package/dist/mysql/migration-runner.js +83 -0
  39. package/dist/mysql/mysql-db.d.ts +100 -0
  40. package/dist/mysql/mysql-db.js +415 -0
  41. package/dist/mysql/query-builder.d.ts +10 -0
  42. package/dist/mysql/query-builder.js +44 -0
  43. package/dist/mysql/schema-introspector.d.ts +19 -0
  44. package/dist/mysql/schema-introspector.js +286 -0
  45. package/dist/mysql/type-map.d.ts +21 -0
  46. package/dist/mysql/type-map.js +36 -0
  47. package/dist/orm-request.d.ts +38 -0
  48. package/dist/orm-request.js +455 -0
  49. package/dist/plural-registry.d.ts +4 -0
  50. package/{src → dist}/plural-registry.js +3 -6
  51. package/dist/postgres/connection.d.ts +15 -0
  52. package/dist/postgres/connection.js +30 -0
  53. package/dist/postgres/migration-generator.d.ts +45 -0
  54. package/dist/postgres/migration-generator.js +257 -0
  55. package/dist/postgres/migration-runner.d.ts +10 -0
  56. package/dist/postgres/migration-runner.js +82 -0
  57. package/dist/postgres/postgres-db.d.ts +119 -0
  58. package/dist/postgres/postgres-db.js +476 -0
  59. package/dist/postgres/query-builder.d.ts +27 -0
  60. package/dist/postgres/query-builder.js +98 -0
  61. package/dist/postgres/schema-introspector.d.ts +29 -0
  62. package/dist/postgres/schema-introspector.js +309 -0
  63. package/dist/postgres/type-map.d.ts +23 -0
  64. package/dist/postgres/type-map.js +53 -0
  65. package/dist/record.d.ts +75 -0
  66. package/dist/record.js +115 -0
  67. package/dist/relationships.d.ts +10 -0
  68. package/dist/relationships.js +39 -0
  69. package/dist/serializer.d.ts +17 -0
  70. package/dist/serializer.js +136 -0
  71. package/dist/setup-rest-server.d.ts +1 -0
  72. package/dist/setup-rest-server.js +54 -0
  73. package/dist/standalone-db.d.ts +58 -0
  74. package/dist/standalone-db.js +142 -0
  75. package/dist/store.d.ts +62 -0
  76. package/dist/store.js +271 -0
  77. package/dist/timescale/query-builder.d.ts +41 -0
  78. package/dist/timescale/query-builder.js +87 -0
  79. package/dist/timescale/timescale-db.d.ts +45 -0
  80. package/dist/timescale/timescale-db.js +84 -0
  81. package/dist/transforms.d.ts +2 -0
  82. package/dist/transforms.js +17 -0
  83. package/dist/types/orm-types.d.ts +142 -0
  84. package/dist/types/orm-types.js +1 -0
  85. package/dist/utils.d.ts +5 -0
  86. package/dist/utils.js +13 -0
  87. package/dist/view-resolver.d.ts +8 -0
  88. package/dist/view-resolver.js +169 -0
  89. package/dist/view.d.ts +11 -0
  90. package/dist/view.js +18 -0
  91. package/package.json +34 -11
  92. package/src/{aggregates.js → aggregates.ts} +27 -13
  93. package/src/{attr.js → attr.ts} +2 -2
  94. package/src/belongs-to.ts +90 -0
  95. package/src/{cli.js → cli.ts} +17 -11
  96. package/src/{commands.js → commands.ts} +179 -170
  97. package/src/{db.js → db.ts} +35 -26
  98. package/src/exports/db.ts +7 -0
  99. package/src/has-many.ts +92 -0
  100. package/src/{hooks.js → hooks.ts} +23 -27
  101. package/src/{index.js → index.ts} +4 -4
  102. package/src/{main.js → main.ts} +60 -34
  103. package/src/{manage-record.js → manage-record.ts} +42 -22
  104. package/src/{meta-request.js → meta-request.ts} +17 -14
  105. package/src/{migrate.js → migrate.ts} +9 -9
  106. package/src/{model-property.js → model-property.ts} +12 -6
  107. package/src/{model.js → model.ts} +5 -4
  108. package/src/mysql/{connection.js → connection.ts} +43 -28
  109. package/src/mysql/{migration-generator.js → migration-generator.ts} +332 -286
  110. package/src/mysql/{migration-runner.js → migration-runner.ts} +116 -110
  111. package/src/mysql/{mysql-db.js → mysql-db.ts} +537 -473
  112. package/src/mysql/{query-builder.js → query-builder.ts} +69 -64
  113. package/src/mysql/{schema-introspector.js → schema-introspector.ts} +355 -325
  114. package/src/mysql/{type-map.js → type-map.ts} +42 -37
  115. package/src/{orm-request.js → orm-request.ts} +169 -97
  116. package/src/plural-registry.ts +12 -0
  117. package/src/postgres/{connection.js → connection.ts} +14 -5
  118. package/src/postgres/{migration-generator.js → migration-generator.ts} +82 -38
  119. package/src/postgres/{migration-runner.js → migration-runner.ts} +11 -10
  120. package/src/postgres/{postgres-db.js → postgres-db.ts} +198 -114
  121. package/src/postgres/{query-builder.js → query-builder.ts} +27 -28
  122. package/src/postgres/{schema-introspector.js → schema-introspector.ts} +87 -58
  123. package/src/postgres/{type-map.js → type-map.ts} +10 -6
  124. package/src/{record.js → record.ts} +73 -34
  125. package/src/relationships.ts +53 -0
  126. package/src/{serializer.js → serializer.ts} +52 -36
  127. package/src/{setup-rest-server.js → setup-rest-server.ts} +18 -13
  128. package/src/{standalone-db.js → standalone-db.ts} +33 -24
  129. package/src/{store.js → store.ts} +90 -68
  130. package/src/timescale/{query-builder.js → query-builder.ts} +33 -38
  131. package/src/timescale/timescale-db.ts +119 -0
  132. package/src/transforms.ts +20 -0
  133. package/src/types/mysql2.d.ts +30 -0
  134. package/src/types/orm-types.ts +146 -0
  135. package/src/types/pg.d.ts +28 -0
  136. package/src/types/stonyx-cron.d.ts +5 -0
  137. package/src/types/stonyx-events.d.ts +4 -0
  138. package/src/types/stonyx-rest-server.d.ts +11 -0
  139. package/src/types/stonyx-utils.d.ts +33 -0
  140. package/src/types/stonyx.d.ts +21 -0
  141. package/src/utils.ts +16 -0
  142. package/src/{view-resolver.js → view-resolver.ts} +51 -24
  143. package/src/view.ts +22 -0
  144. package/src/belongs-to.js +0 -70
  145. package/src/has-many.js +0 -68
  146. package/src/relationships.js +0 -43
  147. package/src/timescale/timescale-db.js +0 -111
  148. package/src/transforms.js +0 -20
  149. package/src/utils.js +0 -12
  150. package/src/view.js +0 -21
@@ -0,0 +1,53 @@
1
+ import { relationships } from '@stonyx/orm';
2
+ import type { HasManyMap, BelongsToMap, GlobalMap, PendingMap, PendingBelongsToMap } from './types/orm-types.js';
3
+
4
+ // TODO: Refactor mapping to remove a level of iteration
5
+ export function getRelationships(type: string, sourceModel: string, targetModel: string, relationshipId?: string): Map<unknown, unknown> | undefined {
6
+ let allRelationships = relationships.get(type) as Map<string, Map<string, Map<unknown, unknown>>> | undefined;
7
+
8
+ if (!allRelationships) {
9
+ allRelationships = new Map();
10
+ relationships.set(type, allRelationships);
11
+ }
12
+
13
+ // create relationship map for this type of it doesn't already exist
14
+ if (!allRelationships.has(sourceModel)) allRelationships.set(sourceModel, new Map());
15
+
16
+ const modelRelationship = allRelationships.get(sourceModel)!;
17
+
18
+ if (!modelRelationship.has(targetModel)) modelRelationship.set(targetModel, new Map());
19
+
20
+ const relationship = modelRelationship.get(targetModel)!;
21
+
22
+ // TODO: Determine whether already having id should be handled differently
23
+ //if (relationship.has(relationshipId)) return;
24
+
25
+ return relationship;
26
+ }
27
+
28
+ export function getHasManyRelationships(sourceModel: string, targetModel: string): Map<unknown, unknown> | undefined {
29
+ return (relationships.get('hasMany') as HasManyMap | undefined)?.get(sourceModel)?.get(targetModel);
30
+ }
31
+
32
+ /** Typed accessors for the relationship registry */
33
+ export function getHasManyRegistry(): HasManyMap {
34
+ return relationships.get('hasMany') as HasManyMap;
35
+ }
36
+
37
+ export function getBelongsToRegistry(): BelongsToMap {
38
+ return relationships.get('belongsTo') as BelongsToMap;
39
+ }
40
+
41
+ export function getGlobalRegistry(): GlobalMap {
42
+ return relationships.get('global') as GlobalMap;
43
+ }
44
+
45
+ export function getPendingRegistry(): PendingMap {
46
+ return relationships.get('pending') as PendingMap;
47
+ }
48
+
49
+ export function getPendingBelongsToRegistry(): PendingBelongsToMap {
50
+ return relationships.get('pendingBelongsTo') as PendingBelongsToMap;
51
+ }
52
+
53
+ export const TYPES: string[] = ['global', 'hasMany', 'belongsTo', 'pending'];
@@ -1,46 +1,57 @@
1
1
  import config from 'stonyx/config';
2
2
  import { get, makeArray } from '@stonyx/utils/object';
3
+ import type { AggregateProperty } from './aggregates.js';
4
+ import type ModelProperty from './model-property.js';
3
5
 
4
6
  const RESERVED_KEYS = ['__name'];
5
7
 
6
- function searchQuery(query, array, key) {
7
- const result = makeArray(array).find(item => {
8
- for (const [ prop, value ] of Object.entries(query)) {
9
- if (item[prop] !== value) return false;
8
+ function isAggregateProperty(v: unknown): v is AggregateProperty {
9
+ return typeof v === 'object' && v !== null && (v as { __kind?: string }).__kind === 'AggregateProperty';
10
+ }
11
+
12
+ function isModelProperty(v: unknown): v is ModelProperty {
13
+ return typeof v === 'object' && v !== null && (v as { __kind?: string }).__kind === 'ModelProperty';
14
+ }
15
+
16
+ function searchQuery(query: Record<string, unknown>, array: unknown, key?: string): unknown {
17
+ const result = makeArray(array).find((item: unknown) => {
18
+ for (const [prop, value] of Object.entries(query)) {
19
+ if ((item as Record<string, unknown>)[prop] !== value) return false;
10
20
 
11
21
  return true;
12
22
  }
13
23
  });
14
24
 
15
25
  if (!result) return null;
16
- if (key) return result[key];
26
+ if (key) return (result as Record<string, unknown>)[key];
17
27
 
18
28
  return result;
19
29
  }
20
30
 
21
- function query(rawData, pathPrefix, subPath) {
31
+ function query(rawData: unknown, pathPrefix: string, subPath: unknown): unknown {
22
32
  if (!rawData) return null;
23
33
 
24
- const [ path, getter, pointer ] = makeArray(subPath);
34
+ const [path, getter, pointer] = makeArray(subPath) as [string, unknown, string | undefined];
25
35
  const fullPath = `${pathPrefix}${path}`;
26
36
  const value = get(rawData, fullPath);
27
37
 
28
38
  if (getter === undefined || getter === null) return value;
29
39
 
30
40
  try {
31
- switch(typeof getter) {
41
+ switch (typeof getter) {
32
42
  case 'object':
33
- return searchQuery(getter, value, pointer);
43
+ return searchQuery(getter as Record<string, unknown>, value, pointer);
34
44
 
35
45
  case 'function':
36
- return getter(value);
46
+ return (getter as (v: unknown) => unknown)(value);
37
47
 
38
- case 'number':
39
- const element = value[getter];
40
- return pointer ? element[pointer] : element;
48
+ case 'number': {
49
+ const element = (value as unknown[])[getter];
50
+ return pointer ? (element as Record<string, unknown>)[pointer] : element;
51
+ }
41
52
 
42
- default:
43
- return value[getter];
53
+ default:
54
+ return (value as Record<string, unknown>)[getter as string];
44
55
  }
45
56
  } catch (error) {
46
57
  if (config.debug) console.error(`Cannot parse value for ${fullPath}.`, { getter, query }, error);
@@ -48,10 +59,11 @@ function query(rawData, pathPrefix, subPath) {
48
59
  }
49
60
 
50
61
  export default class Serializer {
51
- map = {};
62
+ map: Record<string, unknown> = {};
52
63
  path = '';
64
+ model: Record<string, unknown>;
53
65
 
54
- constructor(model) {
66
+ constructor(model: Record<string, unknown>) {
55
67
  this.model = model;
56
68
  }
57
69
 
@@ -60,14 +72,16 @@ export default class Serializer {
60
72
  * the ModelProperty object, while setting parsed values to the record's
61
73
  * __data property, which represents the serialized version of the data
62
74
  */
63
- setProperties(rawData, record, options) {
75
+ setProperties(rawData: unknown, record: unknown, options: Record<string, unknown>): void {
64
76
  const { path, model } = this;
65
77
  const keys = Object.keys(model).filter(key => !RESERVED_KEYS.includes(key));
66
78
  const pathPrefix = path ? `${path}.` : '';
67
- const { __data:parsedData, __relationships:relatedRecords } = record;
79
+ const rec = record as Record<string, unknown>;
80
+ const parsedData = rec.__data as Record<string, unknown>;
81
+ const relatedRecords = rec.__relationships as Record<string, unknown>;
68
82
 
69
83
  for (const key of keys) {
70
- const subPath = options.serialize ? (this.map[key] || key) : key;
84
+ const subPath = options.serialize ? ((this.map as Record<string, unknown>)[key] || key) : key;
71
85
  const handler = model[key];
72
86
  const data = query(rawData, pathPrefix, subPath);
73
87
 
@@ -80,38 +94,40 @@ export default class Serializer {
80
94
  const handlerOptions = { ...options, _relationshipKey: key };
81
95
  const childRecord = handler(record, data, handlerOptions);
82
96
 
83
- record[key] = childRecord
97
+ rec[key] = childRecord;
84
98
  relatedRecords[key] = childRecord;
85
99
 
86
100
  continue;
87
101
  }
88
102
 
89
103
  // Aggregate property handling — use the rawData value, not the aggregate descriptor
90
- if (handler?.constructor?.name === 'AggregateProperty') {
104
+ if (isAggregateProperty(handler)) {
91
105
  parsedData[key] = data;
92
- record[key] = data;
106
+ rec[key] = data;
93
107
  continue;
94
108
  }
95
109
 
96
110
  // Direct assignment handling
97
- if (handler?.constructor?.name !== 'ModelProperty') {
111
+ if (!isModelProperty(handler)) {
98
112
  parsedData[key] = handler;
99
- record[key] = handler;
113
+ rec[key] = handler;
100
114
  continue;
101
115
  }
102
116
 
117
+ const prop = handler as { value: unknown; ignoreFirstTransform: boolean };
118
+
103
119
  Object.defineProperty(record, key, {
104
120
  enumerable: true,
105
121
  configurable: true,
106
- get: () => handler.value,
107
- set(newValue) {
108
- handler.ignoreFirstTransform = !options.transform;
109
- handler.value = newValue;
110
- parsedData[key] = handler.value;
122
+ get: () => prop.value,
123
+ set(newValue: unknown) {
124
+ prop.ignoreFirstTransform = !options.transform;
125
+ prop.value = newValue;
126
+ parsedData[key] = prop.value;
111
127
  }
112
128
  });
113
129
 
114
- record[key] = data;
130
+ rec[key] = data;
115
131
  }
116
132
 
117
133
  if (options.update) return;
@@ -120,25 +136,25 @@ export default class Serializer {
120
136
  for (const [key, getter] of getComputedProperties(this.model)) {
121
137
  Object.defineProperty(record, key, {
122
138
  enumerable: true,
123
- get: () => getter.call(record)
139
+ get: () => (getter as () => unknown).call(record)
124
140
  });
125
141
  }
126
142
 
127
- record.__serialized = true;
143
+ rec.__serialized = true;
128
144
  }
129
145
 
130
146
  /**
131
147
  * OVERRIDE: This hook allows for data manipulation prior to serialization logic
132
148
  */
133
- normalize(data) {
149
+ normalize(data: unknown): unknown {
134
150
  return data;
135
151
  }
136
152
  }
137
153
 
138
- export function getComputedProperties(classInstance) {
154
+ export function getComputedProperties(classInstance: Record<string, unknown>): [string, PropertyDescriptor['get']][] {
139
155
  const proto = Object.getPrototypeOf(classInstance);
140
156
  if (!proto || proto === Object.prototype) return [];
141
-
157
+
142
158
  return Object.entries(Object.getOwnPropertyDescriptors(proto))
143
159
  .filter(([key, descriptor]) => key !== 'constructor' && descriptor.get)
144
160
  .map(([key, descriptor]) => [key, descriptor.get]);
@@ -8,32 +8,37 @@ import { dbKey } from './db.js';
8
8
  import { getPluralName } from './plural-registry.js';
9
9
  import log from 'stonyx/log';
10
10
 
11
- export default async function(route, accessPath, metaRoute) {
12
- let accessFiles = {};
13
-
11
+ interface AccessInstance {
12
+ models: string[] | '*';
13
+ access: (request: unknown) => unknown;
14
+ }
15
+
16
+ export default async function(route: string, accessPath: string, metaRoute: boolean): Promise<void> {
17
+ let accessFiles: Record<string, (request: unknown) => unknown> | null = {};
18
+
14
19
  try {
15
- await forEachFileImport(accessPath, accessClass => {
16
- const accessInstance = new accessClass();
20
+ await forEachFileImport(accessPath, (accessClass: unknown) => {
21
+ const accessInstance = new (accessClass as new () => AccessInstance)();
17
22
  const { models } = accessInstance;
18
23
 
19
- if (!models) throw new Error(`Access class "${accessClass.name}" must define a "models" list`);
24
+ if (!models) throw new Error(`Access class "${(accessClass as { name: string }).name}" must define a "models" list`);
20
25
 
21
26
  if (models.length === 0) return; // No models to assign access to
22
- if (typeof accessInstance.access !== 'function') throw new Error(`Access class "${accessClass.name}" must declare an "access" method`);
27
+ if (typeof accessInstance.access !== 'function') throw new Error(`Access class "${(accessClass as { name: string }).name}" must declare an "access" method`);
23
28
 
24
29
  const availableModels = Array.from(store.data.keys());
25
30
 
26
31
  for (const model of models === '*' ? availableModels : models) {
27
32
  if (model === dbKey) continue;
28
33
  if (!store.data.has(model)) throw new Error(`Unable to define access for Invalid Model "${model}". Model does not exist`);
29
- if (accessFiles[model]) throw new Error(`Access for model "${model}" has already been defined by another access class.`);
34
+ if (accessFiles![model]) throw new Error(`Access for model "${model}" has already been defined by another access class.`);
30
35
 
31
- accessFiles[model] = accessInstance.access;
36
+ accessFiles![model] = accessInstance.access;
32
37
  }
33
38
  });
34
39
  } catch (error) {
35
- log.error(error.message);
36
- log.warn('You must define a valid access configuration file in order to access ORM generated REST endpoints.');
40
+ log.error!(error instanceof Error ? error.message : String(error));
41
+ log.warn!('You must define a valid access configuration file in order to access ORM generated REST endpoints.');
37
42
  }
38
43
 
39
44
  await waitForModule('rest-server');
@@ -42,7 +47,7 @@ export default async function(route, accessPath, metaRoute) {
42
47
  const name = route === '/' ? 'index' : (route[0] === '/' ? route.slice(1) : route);
43
48
 
44
49
  // Configure endpoints for models and views with access configuration
45
- for (const [model, access] of Object.entries(accessFiles)) {
50
+ for (const [model, access] of Object.entries(accessFiles!)) {
46
51
  const pluralizedModel = getPluralName(model);
47
52
  const modelName = name === 'index' ? pluralizedModel : `${name}/${pluralizedModel}`;
48
53
  RestServer.instance.mountRoute(OrmRequest, { name: modelName, options: { model, access } });
@@ -50,7 +55,7 @@ export default async function(route, accessPath, metaRoute) {
50
55
 
51
56
  // Mount the meta route when metaRoute config is enabled
52
57
  if (metaRoute) {
53
- log.warn('SECURITY RISK! - Meta route is enabled via metaRoute config. This feature is intended for development purposes only!');
58
+ log.warn!('SECURITY RISK! - Meta route is enabled via metaRoute config. This feature is intended for development purposes only!');
54
59
 
55
60
  RestServer.instance.mountRoute(MetaRequest, { name });
56
61
  }
@@ -9,14 +9,23 @@
9
9
  import fs from 'fs/promises';
10
10
  import path from 'path';
11
11
 
12
+ interface StandaloneDBOptions {
13
+ dbPath?: string;
14
+ mode?: 'file' | 'directory';
15
+ directory?: string;
16
+ }
17
+
18
+ interface DBRecord {
19
+ id: string | number;
20
+ [key: string]: unknown;
21
+ }
22
+
12
23
  export default class StandaloneDB {
13
- /**
14
- * @param {Object} options
15
- * @param {string} options.dbPath - Path to db.json (file mode) or parent of db dir (directory mode)
16
- * @param {string} [options.mode='directory'] - 'file' or 'directory'
17
- * @param {string} [options.directory='db'] - Directory name when mode is 'directory'
18
- */
19
- constructor(options = {}) {
24
+ readonly mode: 'file' | 'directory';
25
+ readonly dbPath: string;
26
+ readonly directory: string;
27
+
28
+ constructor(options: StandaloneDBOptions = {}) {
20
29
  this.mode = options.mode || 'directory';
21
30
  this.dbPath = options.dbPath || 'db.json';
22
31
  this.directory = options.directory || 'db';
@@ -25,7 +34,7 @@ export default class StandaloneDB {
25
34
  /**
26
35
  * Resolve the directory path for directory mode.
27
36
  */
28
- getDirPath() {
37
+ getDirPath(): string {
29
38
  const dbDir = path.dirname(path.resolve(this.dbPath));
30
39
  return path.join(dbDir, this.directory);
31
40
  }
@@ -34,7 +43,7 @@ export default class StandaloneDB {
34
43
  * List available collections by inspecting either the db.json keys
35
44
  * or the files in the db directory.
36
45
  */
37
- async getCollections() {
46
+ async getCollections(): Promise<string[]> {
38
47
  if (this.mode === 'directory') {
39
48
  const dirPath = this.getDirPath();
40
49
 
@@ -48,9 +57,9 @@ export default class StandaloneDB {
48
57
  }
49
58
  }
50
59
 
51
- // File mode read db.json and return its top-level keys
60
+ // File mode -- read db.json and return its top-level keys
52
61
  try {
53
- const data = await this._readJSON(this.dbPath);
62
+ const data = await this._readJSON(this.dbPath) as Record<string, unknown>;
54
63
  return Object.keys(data).filter(key => Array.isArray(data[key]));
55
64
  } catch {
56
65
  return [];
@@ -60,20 +69,20 @@ export default class StandaloneDB {
60
69
  /**
61
70
  * Read all records for a collection.
62
71
  */
63
- async readCollection(collection) {
72
+ async readCollection(collection: string): Promise<DBRecord[]> {
64
73
  if (this.mode === 'directory') {
65
74
  const filePath = path.join(this.getDirPath(), `${collection}.json`);
66
- return this._readJSON(filePath);
75
+ return this._readJSON(filePath) as Promise<DBRecord[]>;
67
76
  }
68
77
 
69
- const data = await this._readJSON(this.dbPath);
78
+ const data = await this._readJSON(this.dbPath) as Record<string, DBRecord[]>;
70
79
  return data[collection] || [];
71
80
  }
72
81
 
73
82
  /**
74
83
  * Write all records for a collection.
75
84
  */
76
- async writeCollection(collection, records) {
85
+ async writeCollection(collection: string, records: DBRecord[]): Promise<void> {
77
86
  if (this.mode === 'directory') {
78
87
  const dirPath = this.getDirPath();
79
88
  await fs.mkdir(dirPath, { recursive: true });
@@ -83,11 +92,11 @@ export default class StandaloneDB {
83
92
  return;
84
93
  }
85
94
 
86
- // File mode read full db, update collection, write back
87
- let data;
95
+ // File mode -- read full db, update collection, write back
96
+ let data: Record<string, unknown>;
88
97
 
89
98
  try {
90
- data = await this._readJSON(this.dbPath);
99
+ data = await this._readJSON(this.dbPath) as Record<string, unknown>;
91
100
  } catch {
92
101
  data = {};
93
102
  }
@@ -99,7 +108,7 @@ export default class StandaloneDB {
99
108
  /**
100
109
  * Get a single record by id.
101
110
  */
102
- async get(collection, id) {
111
+ async get(collection: string, id: string | number): Promise<DBRecord | null> {
103
112
  const records = await this.readCollection(collection);
104
113
  const numericId = Number(id);
105
114
 
@@ -111,14 +120,14 @@ export default class StandaloneDB {
111
120
  /**
112
121
  * List all records in a collection.
113
122
  */
114
- async list(collection) {
123
+ async list(collection: string): Promise<DBRecord[]> {
115
124
  return this.readCollection(collection);
116
125
  }
117
126
 
118
127
  /**
119
128
  * Create a new record. Auto-assigns an integer id if none provided.
120
129
  */
121
- async create(collection, data) {
130
+ async create(collection: string, data: DBRecord): Promise<DBRecord> {
122
131
  const records = await this.readCollection(collection);
123
132
 
124
133
  if (!data.id) {
@@ -145,7 +154,7 @@ export default class StandaloneDB {
145
154
  /**
146
155
  * Delete a record by id.
147
156
  */
148
- async delete(collection, id) {
157
+ async delete(collection: string, id: string | number): Promise<DBRecord> {
149
158
  const records = await this.readCollection(collection);
150
159
  const numericId = Number(id);
151
160
 
@@ -165,12 +174,12 @@ export default class StandaloneDB {
165
174
 
166
175
  // -- Private helpers --
167
176
 
168
- async _readJSON(filePath) {
177
+ private async _readJSON(filePath: string): Promise<unknown> {
169
178
  const content = await fs.readFile(filePath, 'utf-8');
170
179
  return JSON.parse(content);
171
180
  }
172
181
 
173
- async _writeJSON(filePath, data) {
182
+ private async _writeJSON(filePath: string, data: unknown): Promise<void> {
174
183
  await fs.writeFile(filePath, JSON.stringify(data, null, 2) + '\n', 'utf-8');
175
184
  }
176
185
  }