@stonyx/orm 0.2.5-alpha.0 → 0.3.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/README.md +482 -15
- package/config/environment.js +63 -6
- package/dist/aggregates.d.ts +21 -0
- package/dist/aggregates.js +93 -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 +180 -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 +75 -0
- package/dist/hooks.js +110 -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 +181 -0
- package/dist/manage-record.d.ts +13 -0
- package/dist/manage-record.js +123 -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 +254 -0
- package/dist/mysql/migration-runner.d.ts +12 -0
- package/dist/mysql/migration-runner.js +88 -0
- package/dist/mysql/mysql-db.d.ts +100 -0
- package/dist/mysql/mysql-db.js +425 -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 +257 -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 +475 -0
- package/dist/plural-registry.d.ts +4 -0
- package/dist/plural-registry.js +9 -0
- package/dist/postgres/connection.d.ts +15 -0
- package/dist/postgres/connection.js +32 -0
- package/dist/postgres/migration-generator.d.ts +45 -0
- package/dist/postgres/migration-generator.js +280 -0
- package/dist/postgres/migration-runner.d.ts +10 -0
- package/dist/postgres/migration-runner.js +87 -0
- package/dist/postgres/postgres-db.d.ts +119 -0
- package/dist/postgres/postgres-db.js +477 -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 +296 -0
- package/dist/postgres/type-map.d.ts +23 -0
- package/dist/postgres/type-map.js +56 -0
- package/dist/record.d.ts +75 -0
- package/dist/record.js +129 -0
- package/dist/relationships.d.ts +10 -0
- package/dist/relationships.js +41 -0
- package/dist/schema-helpers.d.ts +20 -0
- package/dist/schema-helpers.js +48 -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 +52 -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 +286 -0
- package/dist/timescale/query-builder.d.ts +43 -0
- package/dist/timescale/query-builder.js +115 -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 +153 -0
- package/dist/types/orm-types.js +1 -0
- package/dist/utils.d.ts +7 -0
- package/dist/utils.js +17 -0
- package/dist/view-resolver.d.ts +8 -0
- package/dist/view-resolver.js +171 -0
- package/dist/view.d.ts +11 -0
- package/dist/view.js +18 -0
- package/package.json +64 -11
- package/src/aggregates.ts +109 -0
- package/src/{attr.js → attr.ts} +2 -2
- package/src/belongs-to.ts +90 -0
- package/src/cli.ts +183 -0
- package/src/commands.ts +179 -0
- package/src/db.ts +232 -0
- package/src/exports/db.ts +7 -0
- package/src/has-many.ts +92 -0
- package/src/hooks.ts +151 -0
- package/src/{index.js → index.ts} +12 -2
- package/src/main.ts +229 -0
- package/src/manage-record.ts +161 -0
- package/src/{meta-request.js → meta-request.ts} +17 -14
- package/src/migrate.ts +72 -0
- package/src/model-property.ts +35 -0
- package/src/model.ts +21 -0
- package/src/mysql/connection.ts +43 -0
- package/src/mysql/migration-generator.ts +337 -0
- package/src/mysql/migration-runner.ts +121 -0
- package/src/mysql/mysql-db.ts +543 -0
- package/src/mysql/query-builder.ts +69 -0
- package/src/mysql/schema-introspector.ts +310 -0
- package/src/mysql/type-map.ts +42 -0
- package/src/orm-request.ts +582 -0
- package/src/plural-registry.ts +12 -0
- package/src/postgres/connection.ts +48 -0
- package/src/postgres/migration-generator.ts +370 -0
- package/src/postgres/migration-runner.ts +115 -0
- package/src/postgres/postgres-db.ts +616 -0
- package/src/postgres/query-builder.ts +148 -0
- package/src/postgres/schema-introspector.ts +360 -0
- package/src/postgres/type-map.ts +61 -0
- package/src/record.ts +186 -0
- package/src/relationships.ts +54 -0
- package/src/schema-helpers.ts +59 -0
- package/src/serializer.ts +161 -0
- package/src/setup-rest-server.ts +62 -0
- package/src/standalone-db.ts +185 -0
- package/src/store.ts +373 -0
- package/src/timescale/query-builder.ts +174 -0
- package/src/timescale/timescale-db.ts +119 -0
- package/src/transforms.ts +20 -0
- package/src/types/mysql2.d.ts +49 -0
- package/src/types/orm-types.ts +158 -0
- package/src/types/pg.d.ts +32 -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 +16 -0
- package/src/types/stonyx-utils.d.ts +33 -0
- package/src/types/stonyx.d.ts +21 -0
- package/src/utils.ts +22 -0
- package/src/view-resolver.ts +211 -0
- package/src/view.ts +22 -0
- package/.claude/project-structure.md +0 -578
- package/.github/workflows/ci.yml +0 -36
- package/.github/workflows/publish.yml +0 -143
- package/src/belongs-to.js +0 -63
- package/src/db.js +0 -80
- package/src/has-many.js +0 -61
- package/src/main.js +0 -119
- package/src/manage-record.js +0 -103
- package/src/model-property.js +0 -29
- package/src/model.js +0 -9
- package/src/orm-request.js +0 -249
- package/src/record.js +0 -100
- package/src/relationships.js +0 -43
- package/src/serializer.js +0 -138
- package/src/setup-rest-server.js +0 -57
- package/src/store.js +0 -211
- package/src/transforms.js +0 -20
- package/stonyx-bootstrap.cjs +0 -30
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
export class AggregateProperty {
|
|
2
|
+
__kind = 'AggregateProperty';
|
|
3
|
+
aggregateType;
|
|
4
|
+
relationship;
|
|
5
|
+
field;
|
|
6
|
+
mysqlFunction;
|
|
7
|
+
resultType;
|
|
8
|
+
constructor(aggregateType, relationship, field) {
|
|
9
|
+
this.aggregateType = aggregateType;
|
|
10
|
+
this.relationship = relationship;
|
|
11
|
+
this.field = field;
|
|
12
|
+
this.mysqlFunction = aggregateType.toUpperCase();
|
|
13
|
+
this.resultType = aggregateType === 'avg' ? 'float' : 'number';
|
|
14
|
+
}
|
|
15
|
+
compute(relatedRecords) {
|
|
16
|
+
if (!relatedRecords || !Array.isArray(relatedRecords) || relatedRecords.length === 0) {
|
|
17
|
+
if (this.aggregateType === 'min' || this.aggregateType === 'max')
|
|
18
|
+
return null;
|
|
19
|
+
return 0;
|
|
20
|
+
}
|
|
21
|
+
if (this.aggregateType === 'count')
|
|
22
|
+
return relatedRecords.length;
|
|
23
|
+
const field = this.field;
|
|
24
|
+
if (!field)
|
|
25
|
+
return null;
|
|
26
|
+
switch (this.aggregateType) {
|
|
27
|
+
case 'sum':
|
|
28
|
+
return relatedRecords.reduce((acc, record) => {
|
|
29
|
+
const val = parseFloat(record?.__data?.[field] ?? record?.[field]);
|
|
30
|
+
return acc + (isNaN(val) ? 0 : val);
|
|
31
|
+
}, 0);
|
|
32
|
+
case 'avg': {
|
|
33
|
+
let sum = 0;
|
|
34
|
+
let count = 0;
|
|
35
|
+
for (const record of relatedRecords) {
|
|
36
|
+
const val = parseFloat(record?.__data?.[field] ?? record?.[field]);
|
|
37
|
+
if (!isNaN(val)) {
|
|
38
|
+
sum += val;
|
|
39
|
+
count++;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return count === 0 ? 0 : sum / count;
|
|
43
|
+
}
|
|
44
|
+
case 'min': {
|
|
45
|
+
let min = null;
|
|
46
|
+
for (const record of relatedRecords) {
|
|
47
|
+
const val = parseFloat(record?.__data?.[field] ?? record?.[field]);
|
|
48
|
+
if (!isNaN(val) && (min === null || val < min))
|
|
49
|
+
min = val;
|
|
50
|
+
}
|
|
51
|
+
return min;
|
|
52
|
+
}
|
|
53
|
+
case 'max': {
|
|
54
|
+
let max = null;
|
|
55
|
+
for (const record of relatedRecords) {
|
|
56
|
+
const val = parseFloat(record?.__data?.[field] ?? record?.[field]);
|
|
57
|
+
if (!isNaN(val) && (max === null || val > max))
|
|
58
|
+
max = val;
|
|
59
|
+
}
|
|
60
|
+
return max;
|
|
61
|
+
}
|
|
62
|
+
default:
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
export function count(relationship) {
|
|
68
|
+
return new AggregateProperty('count', relationship);
|
|
69
|
+
}
|
|
70
|
+
export function avg(relationshipOrField, field) {
|
|
71
|
+
if (field !== undefined) {
|
|
72
|
+
return new AggregateProperty('avg', relationshipOrField, field);
|
|
73
|
+
}
|
|
74
|
+
return new AggregateProperty('avg', undefined, relationshipOrField);
|
|
75
|
+
}
|
|
76
|
+
export function sum(relationshipOrField, field) {
|
|
77
|
+
if (field !== undefined) {
|
|
78
|
+
return new AggregateProperty('sum', relationshipOrField, field);
|
|
79
|
+
}
|
|
80
|
+
return new AggregateProperty('sum', undefined, relationshipOrField);
|
|
81
|
+
}
|
|
82
|
+
export function min(relationshipOrField, field) {
|
|
83
|
+
if (field !== undefined) {
|
|
84
|
+
return new AggregateProperty('min', relationshipOrField, field);
|
|
85
|
+
}
|
|
86
|
+
return new AggregateProperty('min', undefined, relationshipOrField);
|
|
87
|
+
}
|
|
88
|
+
export function max(relationshipOrField, field) {
|
|
89
|
+
if (field !== undefined) {
|
|
90
|
+
return new AggregateProperty('max', relationshipOrField, field);
|
|
91
|
+
}
|
|
92
|
+
return new AggregateProperty('max', undefined, relationshipOrField);
|
|
93
|
+
}
|
package/dist/attr.d.ts
ADDED
package/dist/attr.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import ModelProperty from './model-property.js';
|
|
2
|
+
export default function attr(type, defaultValue) {
|
|
3
|
+
const modelProp = new ModelProperty(type, defaultValue);
|
|
4
|
+
return new Proxy(modelProp, {
|
|
5
|
+
get(target, prop, receiver) {
|
|
6
|
+
if (prop === 'valueOf' || prop === 'toString') {
|
|
7
|
+
return () => target.value;
|
|
8
|
+
}
|
|
9
|
+
if (prop in target) {
|
|
10
|
+
return Reflect.get(target, prop, receiver);
|
|
11
|
+
}
|
|
12
|
+
return target.value;
|
|
13
|
+
},
|
|
14
|
+
set(target, prop, value, receiver) {
|
|
15
|
+
if (prop === 'value') {
|
|
16
|
+
target.value = value;
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
return Reflect.set(target, prop, value, receiver);
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { SourceRecord } from './types/orm-types.js';
|
|
2
|
+
interface BelongsToOptions {
|
|
3
|
+
_relationshipKey?: string;
|
|
4
|
+
[key: string]: unknown;
|
|
5
|
+
}
|
|
6
|
+
type RelationshipHandler = ((sourceRecord: SourceRecord, rawData: unknown, options: BelongsToOptions) => unknown) & {
|
|
7
|
+
__relatedModelName: string;
|
|
8
|
+
__relationshipType: 'belongsTo';
|
|
9
|
+
};
|
|
10
|
+
export default function belongsTo(modelName: string): RelationshipHandler;
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { createRecord, store } from '@stonyx/orm';
|
|
2
|
+
import { getRelationships, getHasManyRegistry, getPendingRegistry, getPendingBelongsToRegistry } from './relationships.js';
|
|
3
|
+
function getOrSet(map, key, defaultValue) {
|
|
4
|
+
if (!map.has(key))
|
|
5
|
+
map.set(key, defaultValue);
|
|
6
|
+
return map.get(key);
|
|
7
|
+
}
|
|
8
|
+
export default function belongsTo(modelName) {
|
|
9
|
+
const hasManyRelationships = getHasManyRegistry();
|
|
10
|
+
const pendingHasManyQueue = getPendingRegistry();
|
|
11
|
+
const pendingBelongsToQueue = getPendingBelongsToRegistry();
|
|
12
|
+
const fn = (sourceRecord, rawData, options) => {
|
|
13
|
+
if (!rawData)
|
|
14
|
+
return null;
|
|
15
|
+
const { __name: sourceModelName } = sourceRecord.__model;
|
|
16
|
+
const relationshipId = sourceRecord.id;
|
|
17
|
+
const relationshipKey = options._relationshipKey;
|
|
18
|
+
const relationship = getRelationships('belongsTo', sourceModelName, modelName, relationshipId);
|
|
19
|
+
const modelStore = store.get(modelName);
|
|
20
|
+
// Try to get existing record
|
|
21
|
+
let output;
|
|
22
|
+
if (typeof rawData === 'object') {
|
|
23
|
+
output = createRecord(modelName, rawData, options);
|
|
24
|
+
}
|
|
25
|
+
else if (modelStore) {
|
|
26
|
+
output = modelStore.get(rawData);
|
|
27
|
+
}
|
|
28
|
+
// If not found and is a string ID, register as pending
|
|
29
|
+
if (!output && typeof rawData !== 'object') {
|
|
30
|
+
const targetId = rawData;
|
|
31
|
+
// Register pending belongsTo
|
|
32
|
+
const modelPendingMap = getOrSet(pendingBelongsToQueue, modelName, new Map());
|
|
33
|
+
const targetPendingArray = getOrSet(modelPendingMap, targetId, []);
|
|
34
|
+
targetPendingArray.push({
|
|
35
|
+
sourceRecord,
|
|
36
|
+
sourceModelName,
|
|
37
|
+
relationshipKey,
|
|
38
|
+
relationshipId
|
|
39
|
+
});
|
|
40
|
+
relationship.set(relationshipId, null);
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
relationship.set(relationshipId, output || {});
|
|
44
|
+
// Populate hasMany side if the relationship is defined
|
|
45
|
+
const outputRecord = typeof output === 'object' && output !== null && 'id' in output ? output : undefined;
|
|
46
|
+
const otherSide = outputRecord ? hasManyRelationships.get(modelName)?.get(sourceModelName)?.get(outputRecord.id) : undefined;
|
|
47
|
+
if (otherSide) {
|
|
48
|
+
otherSide.push(sourceRecord);
|
|
49
|
+
// Remove pending queue if it was just fulfilled
|
|
50
|
+
const pendingModelRelationships = pendingHasManyQueue.get(sourceModelName);
|
|
51
|
+
if (pendingModelRelationships)
|
|
52
|
+
pendingModelRelationships.delete(relationshipId);
|
|
53
|
+
}
|
|
54
|
+
return output;
|
|
55
|
+
};
|
|
56
|
+
Object.defineProperty(fn, '__relatedModelName', { value: modelName });
|
|
57
|
+
Object.defineProperty(fn, '__relationshipType', { value: 'belongsTo' });
|
|
58
|
+
return fn;
|
|
59
|
+
}
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Standalone CLI for ORM database operations.
|
|
4
|
+
*
|
|
5
|
+
* Performs CRUD operations on the JSON database without requiring
|
|
6
|
+
* the full Stonyx bootstrap. Supports both file and directory modes.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* stonyx-orm create <collection> <json-data>
|
|
10
|
+
* stonyx-orm list <collection>
|
|
11
|
+
* stonyx-orm get <collection> <id>
|
|
12
|
+
* stonyx-orm delete <collection> <id>
|
|
13
|
+
*
|
|
14
|
+
* Configuration (environment variables):
|
|
15
|
+
* DB_MODE -- 'file' or 'directory' (default: 'directory')
|
|
16
|
+
* DB_PATH -- Path to db.json (default: 'db.json')
|
|
17
|
+
* DB_DIRECTORY -- Directory name for collection files (default: 'db')
|
|
18
|
+
*
|
|
19
|
+
* Configuration (CLI flag):
|
|
20
|
+
* --config <path> -- Path to a JSON config file with { mode, dbPath, directory }
|
|
21
|
+
*/
|
|
22
|
+
export {};
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Standalone CLI for ORM database operations.
|
|
4
|
+
*
|
|
5
|
+
* Performs CRUD operations on the JSON database without requiring
|
|
6
|
+
* the full Stonyx bootstrap. Supports both file and directory modes.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* stonyx-orm create <collection> <json-data>
|
|
10
|
+
* stonyx-orm list <collection>
|
|
11
|
+
* stonyx-orm get <collection> <id>
|
|
12
|
+
* stonyx-orm delete <collection> <id>
|
|
13
|
+
*
|
|
14
|
+
* Configuration (environment variables):
|
|
15
|
+
* DB_MODE -- 'file' or 'directory' (default: 'directory')
|
|
16
|
+
* DB_PATH -- Path to db.json (default: 'db.json')
|
|
17
|
+
* DB_DIRECTORY -- Directory name for collection files (default: 'db')
|
|
18
|
+
*
|
|
19
|
+
* Configuration (CLI flag):
|
|
20
|
+
* --config <path> -- Path to a JSON config file with { mode, dbPath, directory }
|
|
21
|
+
*/
|
|
22
|
+
import StandaloneDB from './standalone-db.js';
|
|
23
|
+
import fs from 'fs/promises';
|
|
24
|
+
const USAGE = `Usage: stonyx-orm <command> [options]
|
|
25
|
+
|
|
26
|
+
Commands:
|
|
27
|
+
create <collection> <json-data> Create a record
|
|
28
|
+
list <collection> List all records
|
|
29
|
+
get <collection> <id> Get a record by ID
|
|
30
|
+
delete <collection> <id> Delete a record by ID
|
|
31
|
+
|
|
32
|
+
Options:
|
|
33
|
+
--config <path> Path to JSON config file
|
|
34
|
+
--help Show this help message
|
|
35
|
+
|
|
36
|
+
Environment variables:
|
|
37
|
+
DB_MODE 'file' or 'directory' (default: 'directory')
|
|
38
|
+
DB_PATH Path to db.json (default: 'db.json')
|
|
39
|
+
DB_DIRECTORY Directory name for collection files (default: 'db')`;
|
|
40
|
+
async function loadConfig(args) {
|
|
41
|
+
const config = {};
|
|
42
|
+
// Check for --config flag
|
|
43
|
+
const configIndex = args.indexOf('--config');
|
|
44
|
+
if (configIndex !== -1 && args[configIndex + 1]) {
|
|
45
|
+
const configPath = args[configIndex + 1];
|
|
46
|
+
try {
|
|
47
|
+
const content = await fs.readFile(configPath, 'utf-8');
|
|
48
|
+
Object.assign(config, JSON.parse(content));
|
|
49
|
+
}
|
|
50
|
+
catch (err) {
|
|
51
|
+
console.error(`Error reading config file '${configPath}': ${err instanceof Error ? err.message : String(err)}`);
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
54
|
+
// Remove --config and its value from args
|
|
55
|
+
args.splice(configIndex, 2);
|
|
56
|
+
}
|
|
57
|
+
// Environment variables override config file, config file overrides defaults
|
|
58
|
+
return {
|
|
59
|
+
mode: (process.env.DB_MODE || config.mode || 'directory'),
|
|
60
|
+
dbPath: process.env.DB_PATH || config.dbPath || 'db.json',
|
|
61
|
+
directory: process.env.DB_DIRECTORY || config.directory || 'db',
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
function parseArgs(argv) {
|
|
65
|
+
// Strip node binary and script path
|
|
66
|
+
const args = argv.slice(2);
|
|
67
|
+
if (args.includes('--help') || args.includes('-h') || args.length === 0) {
|
|
68
|
+
console.log(USAGE);
|
|
69
|
+
process.exit(0);
|
|
70
|
+
}
|
|
71
|
+
return args;
|
|
72
|
+
}
|
|
73
|
+
async function run() {
|
|
74
|
+
const args = parseArgs(process.argv);
|
|
75
|
+
const config = await loadConfig(args);
|
|
76
|
+
const db = new StandaloneDB(config);
|
|
77
|
+
const [command, collection, ...rest] = args;
|
|
78
|
+
if (!command) {
|
|
79
|
+
console.error('Error: No command specified.\n');
|
|
80
|
+
console.log(USAGE);
|
|
81
|
+
process.exit(1);
|
|
82
|
+
}
|
|
83
|
+
if (!collection && command !== '--help') {
|
|
84
|
+
console.error(`Error: No collection specified for '${command}' command.\n`);
|
|
85
|
+
console.log(USAGE);
|
|
86
|
+
process.exit(1);
|
|
87
|
+
}
|
|
88
|
+
try {
|
|
89
|
+
switch (command) {
|
|
90
|
+
case 'list': {
|
|
91
|
+
const records = await db.list(collection);
|
|
92
|
+
console.log(JSON.stringify(records, null, 2));
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
case 'get': {
|
|
96
|
+
const id = rest[0];
|
|
97
|
+
if (!id) {
|
|
98
|
+
console.error("Error: 'get' command requires an <id> argument.");
|
|
99
|
+
process.exit(1);
|
|
100
|
+
}
|
|
101
|
+
const record = await db.get(collection, id);
|
|
102
|
+
if (!record) {
|
|
103
|
+
console.error(`Record with id '${id}' not found in '${collection}'.`);
|
|
104
|
+
process.exit(1);
|
|
105
|
+
}
|
|
106
|
+
console.log(JSON.stringify(record, null, 2));
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
case 'create': {
|
|
110
|
+
const jsonStr = rest.join(' ');
|
|
111
|
+
if (!jsonStr) {
|
|
112
|
+
console.error("Error: 'create' command requires <json-data> argument.");
|
|
113
|
+
process.exit(1);
|
|
114
|
+
}
|
|
115
|
+
let data;
|
|
116
|
+
try {
|
|
117
|
+
data = JSON.parse(jsonStr);
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
console.error(`Error: Invalid JSON data: ${jsonStr}`);
|
|
121
|
+
process.exit(1);
|
|
122
|
+
}
|
|
123
|
+
const created = await db.create(collection, data);
|
|
124
|
+
console.log(JSON.stringify(created, null, 2));
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
case 'delete': {
|
|
128
|
+
const deleteId = rest[0];
|
|
129
|
+
if (!deleteId) {
|
|
130
|
+
console.error("Error: 'delete' command requires an <id> argument.");
|
|
131
|
+
process.exit(1);
|
|
132
|
+
}
|
|
133
|
+
const removed = await db.delete(collection, deleteId);
|
|
134
|
+
console.log(JSON.stringify(removed, null, 2));
|
|
135
|
+
break;
|
|
136
|
+
}
|
|
137
|
+
default:
|
|
138
|
+
console.error(`Error: Unknown command '${command}'.\n`);
|
|
139
|
+
console.log(USAGE);
|
|
140
|
+
process.exit(1);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
catch (err) {
|
|
144
|
+
console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
145
|
+
process.exit(1);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
run();
|
package/dist/commands.js
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { fileToDirectory, directoryToFile } from './migrate.js';
|
|
2
|
+
const commands = {
|
|
3
|
+
'db:migrate-to-directory': {
|
|
4
|
+
description: 'Migrate DB from single file to directory mode',
|
|
5
|
+
bootstrap: true,
|
|
6
|
+
run: async () => {
|
|
7
|
+
await fileToDirectory();
|
|
8
|
+
console.log('DB migration to directory mode complete.');
|
|
9
|
+
}
|
|
10
|
+
},
|
|
11
|
+
'db:migrate-to-file': {
|
|
12
|
+
description: 'Migrate DB from directory mode to single file',
|
|
13
|
+
bootstrap: true,
|
|
14
|
+
run: async () => {
|
|
15
|
+
await directoryToFile();
|
|
16
|
+
console.log('DB migration to file mode complete.');
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
'db:generate-migration': {
|
|
20
|
+
description: 'Generate a MySQL migration from current model schemas',
|
|
21
|
+
bootstrap: true,
|
|
22
|
+
run: async (args) => {
|
|
23
|
+
const description = args?.join(' ') || 'migration';
|
|
24
|
+
const { generateMigration } = await import('./mysql/migration-generator.js');
|
|
25
|
+
const result = await generateMigration(description);
|
|
26
|
+
if (result) {
|
|
27
|
+
console.log(`Migration created: ${result.filename}`);
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
console.log('No schema changes detected. No migration generated.');
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
'db:migrate': {
|
|
35
|
+
description: 'Apply pending MySQL migrations',
|
|
36
|
+
bootstrap: true,
|
|
37
|
+
run: async () => {
|
|
38
|
+
const config = (await import('stonyx/config')).default;
|
|
39
|
+
const mysqlConfig = config.orm.mysql;
|
|
40
|
+
if (!mysqlConfig) {
|
|
41
|
+
console.error('MySQL is not configured. Set MYSQL_HOST to enable MySQL mode.');
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
const { getPool, closePool } = await import('./mysql/connection.js');
|
|
45
|
+
const { ensureMigrationsTable, getAppliedMigrations, getMigrationFiles, applyMigration, parseMigrationFile } = await import('./mysql/migration-runner.js');
|
|
46
|
+
const { readFile } = await import('@stonyx/utils/file');
|
|
47
|
+
const path = await import('path');
|
|
48
|
+
const pool = await getPool(mysqlConfig);
|
|
49
|
+
const migrationsPath = path.resolve(config.rootPath, mysqlConfig.migrationsDir);
|
|
50
|
+
try {
|
|
51
|
+
await ensureMigrationsTable(pool, mysqlConfig.migrationsTable);
|
|
52
|
+
const applied = await getAppliedMigrations(pool, mysqlConfig.migrationsTable);
|
|
53
|
+
const files = await getMigrationFiles(migrationsPath);
|
|
54
|
+
const pending = files.filter((f) => !applied.includes(f));
|
|
55
|
+
if (pending.length === 0) {
|
|
56
|
+
console.log('No pending migrations.');
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
console.log(`Applying ${pending.length} migration(s)...`);
|
|
60
|
+
for (const filename of pending) {
|
|
61
|
+
const content = await readFile(path.join(migrationsPath, filename));
|
|
62
|
+
const { up } = parseMigrationFile(content);
|
|
63
|
+
await applyMigration(pool, filename, up, mysqlConfig.migrationsTable);
|
|
64
|
+
console.log(` Applied: ${filename}`);
|
|
65
|
+
}
|
|
66
|
+
console.log('All migrations applied.');
|
|
67
|
+
}
|
|
68
|
+
finally {
|
|
69
|
+
await closePool();
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
'db:migrate:rollback': {
|
|
74
|
+
description: 'Rollback the most recent MySQL migration',
|
|
75
|
+
bootstrap: true,
|
|
76
|
+
run: async () => {
|
|
77
|
+
const config = (await import('stonyx/config')).default;
|
|
78
|
+
const mysqlConfig = config.orm.mysql;
|
|
79
|
+
if (!mysqlConfig) {
|
|
80
|
+
console.error('MySQL is not configured. Set MYSQL_HOST to enable MySQL mode.');
|
|
81
|
+
process.exit(1);
|
|
82
|
+
}
|
|
83
|
+
const { getPool, closePool } = await import('./mysql/connection.js');
|
|
84
|
+
const { ensureMigrationsTable, getAppliedMigrations, rollbackMigration, parseMigrationFile } = await import('./mysql/migration-runner.js');
|
|
85
|
+
const { readFile } = await import('@stonyx/utils/file');
|
|
86
|
+
const path = await import('path');
|
|
87
|
+
const pool = await getPool(mysqlConfig);
|
|
88
|
+
const migrationsPath = path.resolve(config.rootPath, mysqlConfig.migrationsDir);
|
|
89
|
+
try {
|
|
90
|
+
await ensureMigrationsTable(pool, mysqlConfig.migrationsTable);
|
|
91
|
+
const applied = await getAppliedMigrations(pool, mysqlConfig.migrationsTable);
|
|
92
|
+
if (applied.length === 0) {
|
|
93
|
+
console.log('No migrations to rollback.');
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
const lastFilename = applied[applied.length - 1];
|
|
97
|
+
const content = await readFile(path.join(migrationsPath, lastFilename));
|
|
98
|
+
const { down } = parseMigrationFile(content);
|
|
99
|
+
if (!down) {
|
|
100
|
+
console.error(`No DOWN section found in ${lastFilename}. Cannot rollback.`);
|
|
101
|
+
process.exit(1);
|
|
102
|
+
}
|
|
103
|
+
await rollbackMigration(pool, lastFilename, down, mysqlConfig.migrationsTable);
|
|
104
|
+
console.log(`Rolled back: ${lastFilename}`);
|
|
105
|
+
}
|
|
106
|
+
finally {
|
|
107
|
+
await closePool();
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
'db:migrate:status': {
|
|
112
|
+
description: 'Show status of MySQL migrations',
|
|
113
|
+
bootstrap: true,
|
|
114
|
+
run: async () => {
|
|
115
|
+
const config = (await import('stonyx/config')).default;
|
|
116
|
+
const mysqlConfig = config.orm.mysql;
|
|
117
|
+
if (!mysqlConfig) {
|
|
118
|
+
console.error('MySQL is not configured. Set MYSQL_HOST to enable MySQL mode.');
|
|
119
|
+
process.exit(1);
|
|
120
|
+
}
|
|
121
|
+
const { getPool, closePool } = await import('./mysql/connection.js');
|
|
122
|
+
const { ensureMigrationsTable, getAppliedMigrations, getMigrationFiles } = await import('./mysql/migration-runner.js');
|
|
123
|
+
const path = await import('path');
|
|
124
|
+
const pool = await getPool(mysqlConfig);
|
|
125
|
+
const migrationsPath = path.resolve(config.rootPath, mysqlConfig.migrationsDir);
|
|
126
|
+
try {
|
|
127
|
+
await ensureMigrationsTable(pool, mysqlConfig.migrationsTable);
|
|
128
|
+
const applied = new Set(await getAppliedMigrations(pool, mysqlConfig.migrationsTable));
|
|
129
|
+
const files = await getMigrationFiles(migrationsPath);
|
|
130
|
+
if (files.length === 0) {
|
|
131
|
+
console.log('No migration files found.');
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
console.log('Migration status:');
|
|
135
|
+
for (const filename of files) {
|
|
136
|
+
const status = applied.has(filename) ? 'applied' : 'pending';
|
|
137
|
+
console.log(` [${status}] ${filename}`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
finally {
|
|
141
|
+
await closePool();
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
};
|
|
146
|
+
export default commands;
|
package/dist/db.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export declare const dbKey = "__db";
|
|
2
|
+
interface DBRecord {
|
|
3
|
+
format(): Record<string, unknown>;
|
|
4
|
+
[key: string]: unknown;
|
|
5
|
+
}
|
|
6
|
+
export default class DB {
|
|
7
|
+
static instance: DB;
|
|
8
|
+
record: DBRecord;
|
|
9
|
+
constructor();
|
|
10
|
+
getSchema(): Promise<unknown>;
|
|
11
|
+
getCollectionKeys(): string[];
|
|
12
|
+
getDirPath(): string;
|
|
13
|
+
validateMode(): Promise<void>;
|
|
14
|
+
init(): Promise<void>;
|
|
15
|
+
create(): Promise<Record<string, unknown>>;
|
|
16
|
+
save(): Promise<void>;
|
|
17
|
+
getRecord(): Promise<DBRecord>;
|
|
18
|
+
getRecordFromFile(): Promise<DBRecord>;
|
|
19
|
+
getRecordFromDirectory(): Promise<DBRecord>;
|
|
20
|
+
}
|
|
21
|
+
export {};
|