@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.
- package/dist/aggregates.d.ts +21 -0
- package/dist/aggregates.js +90 -0
- package/dist/attr.d.ts +2 -0
- package/dist/attr.js +22 -0
- package/dist/belongs-to.d.ts +11 -0
- package/dist/belongs-to.js +59 -0
- package/dist/cli.d.ts +22 -0
- package/dist/cli.js +148 -0
- package/dist/commands.d.ts +7 -0
- package/dist/commands.js +146 -0
- package/dist/db.d.ts +21 -0
- package/dist/db.js +174 -0
- package/dist/exports/db.d.ts +7 -0
- package/{src → dist}/exports/db.js +2 -4
- package/dist/has-many.d.ts +11 -0
- package/dist/has-many.js +58 -0
- package/dist/hooks.d.ts +47 -0
- package/dist/hooks.js +106 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +34 -0
- package/dist/main.d.ts +46 -0
- package/dist/main.js +179 -0
- package/dist/manage-record.d.ts +13 -0
- package/dist/manage-record.js +114 -0
- package/dist/meta-request.d.ts +6 -0
- package/dist/meta-request.js +52 -0
- package/dist/migrate.d.ts +2 -0
- package/dist/migrate.js +57 -0
- package/dist/model-property.d.ts +9 -0
- package/dist/model-property.js +29 -0
- package/dist/model.d.ts +15 -0
- package/dist/model.js +18 -0
- package/dist/mysql/connection.d.ts +14 -0
- package/dist/mysql/connection.js +24 -0
- package/dist/mysql/migration-generator.d.ts +45 -0
- package/dist/mysql/migration-generator.js +245 -0
- package/dist/mysql/migration-runner.d.ts +12 -0
- package/dist/mysql/migration-runner.js +83 -0
- package/dist/mysql/mysql-db.d.ts +100 -0
- package/dist/mysql/mysql-db.js +415 -0
- package/dist/mysql/query-builder.d.ts +10 -0
- package/dist/mysql/query-builder.js +44 -0
- package/dist/mysql/schema-introspector.d.ts +19 -0
- package/dist/mysql/schema-introspector.js +286 -0
- package/dist/mysql/type-map.d.ts +21 -0
- package/dist/mysql/type-map.js +36 -0
- package/dist/orm-request.d.ts +38 -0
- package/dist/orm-request.js +455 -0
- package/dist/plural-registry.d.ts +4 -0
- package/{src → dist}/plural-registry.js +3 -6
- package/dist/postgres/connection.d.ts +15 -0
- package/dist/postgres/connection.js +30 -0
- package/dist/postgres/migration-generator.d.ts +45 -0
- package/dist/postgres/migration-generator.js +257 -0
- package/dist/postgres/migration-runner.d.ts +10 -0
- package/dist/postgres/migration-runner.js +82 -0
- package/dist/postgres/postgres-db.d.ts +119 -0
- package/dist/postgres/postgres-db.js +476 -0
- package/dist/postgres/query-builder.d.ts +27 -0
- package/dist/postgres/query-builder.js +98 -0
- package/dist/postgres/schema-introspector.d.ts +29 -0
- package/dist/postgres/schema-introspector.js +309 -0
- package/dist/postgres/type-map.d.ts +23 -0
- package/dist/postgres/type-map.js +53 -0
- package/dist/record.d.ts +75 -0
- package/dist/record.js +115 -0
- package/dist/relationships.d.ts +10 -0
- package/dist/relationships.js +39 -0
- package/dist/serializer.d.ts +17 -0
- package/dist/serializer.js +136 -0
- package/dist/setup-rest-server.d.ts +1 -0
- package/dist/setup-rest-server.js +54 -0
- package/dist/standalone-db.d.ts +58 -0
- package/dist/standalone-db.js +142 -0
- package/dist/store.d.ts +62 -0
- package/dist/store.js +271 -0
- package/dist/timescale/query-builder.d.ts +41 -0
- package/dist/timescale/query-builder.js +87 -0
- package/dist/timescale/timescale-db.d.ts +45 -0
- package/dist/timescale/timescale-db.js +84 -0
- package/dist/transforms.d.ts +2 -0
- package/dist/transforms.js +17 -0
- package/dist/types/orm-types.d.ts +142 -0
- package/dist/types/orm-types.js +1 -0
- package/dist/utils.d.ts +5 -0
- package/dist/utils.js +13 -0
- package/dist/view-resolver.d.ts +8 -0
- package/dist/view-resolver.js +169 -0
- package/dist/view.d.ts +11 -0
- package/dist/view.js +18 -0
- package/package.json +34 -11
- package/src/{aggregates.js → aggregates.ts} +27 -13
- package/src/{attr.js → attr.ts} +2 -2
- package/src/belongs-to.ts +90 -0
- package/src/{cli.js → cli.ts} +17 -11
- package/src/{commands.js → commands.ts} +179 -170
- package/src/{db.js → db.ts} +35 -26
- package/src/exports/db.ts +7 -0
- package/src/has-many.ts +92 -0
- package/src/{hooks.js → hooks.ts} +23 -27
- package/src/{index.js → index.ts} +4 -4
- package/src/{main.js → main.ts} +60 -34
- package/src/{manage-record.js → manage-record.ts} +42 -22
- package/src/{meta-request.js → meta-request.ts} +17 -14
- package/src/{migrate.js → migrate.ts} +9 -9
- package/src/{model-property.js → model-property.ts} +12 -6
- package/src/{model.js → model.ts} +5 -4
- package/src/mysql/{connection.js → connection.ts} +43 -28
- package/src/mysql/{migration-generator.js → migration-generator.ts} +332 -286
- package/src/mysql/{migration-runner.js → migration-runner.ts} +116 -110
- package/src/mysql/{mysql-db.js → mysql-db.ts} +537 -473
- package/src/mysql/{query-builder.js → query-builder.ts} +69 -64
- package/src/mysql/{schema-introspector.js → schema-introspector.ts} +355 -325
- package/src/mysql/{type-map.js → type-map.ts} +42 -37
- package/src/{orm-request.js → orm-request.ts} +169 -97
- package/src/plural-registry.ts +12 -0
- package/src/postgres/{connection.js → connection.ts} +14 -5
- package/src/postgres/{migration-generator.js → migration-generator.ts} +82 -38
- package/src/postgres/{migration-runner.js → migration-runner.ts} +11 -10
- package/src/postgres/{postgres-db.js → postgres-db.ts} +198 -114
- package/src/postgres/{query-builder.js → query-builder.ts} +27 -28
- package/src/postgres/{schema-introspector.js → schema-introspector.ts} +87 -58
- package/src/postgres/{type-map.js → type-map.ts} +10 -6
- package/src/{record.js → record.ts} +73 -34
- package/src/relationships.ts +53 -0
- package/src/{serializer.js → serializer.ts} +52 -36
- package/src/{setup-rest-server.js → setup-rest-server.ts} +18 -13
- package/src/{standalone-db.js → standalone-db.ts} +33 -24
- package/src/{store.js → store.ts} +90 -68
- package/src/timescale/{query-builder.js → query-builder.ts} +33 -38
- package/src/timescale/timescale-db.ts +119 -0
- package/src/transforms.ts +20 -0
- package/src/types/mysql2.d.ts +30 -0
- package/src/types/orm-types.ts +146 -0
- package/src/types/pg.d.ts +28 -0
- package/src/types/stonyx-cron.d.ts +5 -0
- package/src/types/stonyx-events.d.ts +4 -0
- package/src/types/stonyx-rest-server.d.ts +11 -0
- package/src/types/stonyx-utils.d.ts +33 -0
- package/src/types/stonyx.d.ts +21 -0
- package/src/utils.ts +16 -0
- package/src/{view-resolver.js → view-resolver.ts} +51 -24
- package/src/view.ts +22 -0
- package/src/belongs-to.js +0 -70
- package/src/has-many.js +0 -68
- package/src/relationships.js +0 -43
- package/src/timescale/timescale-db.js +0 -111
- package/src/transforms.js +0 -20
- package/src/utils.js +0 -12
- 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
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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 [
|
|
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
|
|
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
|
-
|
|
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
|
|
104
|
+
if (isAggregateProperty(handler)) {
|
|
91
105
|
parsedData[key] = data;
|
|
92
|
-
|
|
106
|
+
rec[key] = data;
|
|
93
107
|
continue;
|
|
94
108
|
}
|
|
95
109
|
|
|
96
110
|
// Direct assignment handling
|
|
97
|
-
if (handler
|
|
111
|
+
if (!isModelProperty(handler)) {
|
|
98
112
|
parsedData[key] = handler;
|
|
99
|
-
|
|
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: () =>
|
|
107
|
-
set(newValue) {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
parsedData[key] =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
12
|
-
|
|
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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
|
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
|
|
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
|
}
|