@stonyx/orm 0.2.1-beta.9 → 0.2.1-beta.90
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 +64 -6
- package/config/environment.js +37 -1
- 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 +62 -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 +291 -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 +474 -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 +261 -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 +314 -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/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 +142 -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 +57 -15
- 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.js → commands.ts} +179 -170
- package/src/{db.js → db.ts} +55 -29
- package/src/exports/db.ts +7 -0
- package/src/has-many.ts +92 -0
- package/src/{hooks.js → hooks.ts} +41 -27
- package/src/{index.js → index.ts} +11 -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.js → migrate.ts} +9 -9
- package/src/model-property.ts +35 -0
- package/src/model.ts +21 -0
- package/src/mysql/{connection.js → connection.ts} +43 -28
- package/src/mysql/migration-generator.ts +337 -0
- package/src/mysql/{migration-runner.js → migration-runner.ts} +121 -110
- package/src/mysql/mysql-db.ts +543 -0
- package/src/mysql/{query-builder.js → query-builder.ts} +69 -64
- package/src/mysql/schema-introspector.ts +358 -0
- package/src/mysql/{type-map.js → type-map.ts} +42 -37
- package/src/{orm-request.js → orm-request.ts} +186 -108
- package/src/plural-registry.ts +12 -0
- package/src/postgres/connection.ts +48 -0
- package/src/postgres/migration-generator.ts +348 -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 +386 -0
- package/src/postgres/type-map.ts +61 -0
- package/src/record.ts +186 -0
- package/src/relationships.ts +54 -0
- package/src/serializer.ts +161 -0
- package/src/{setup-rest-server.js → setup-rest-server.ts} +18 -16
- 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 +146 -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/code-style-rules.md +0 -44
- package/.claude/hooks.md +0 -250
- package/.claude/index.md +0 -279
- package/.claude/usage-patterns.md +0 -217
- package/.github/workflows/ci.yml +0 -16
- package/.github/workflows/publish.yml +0 -51
- package/improvements.md +0 -139
- package/project-structure.md +0 -343
- package/src/belongs-to.js +0 -63
- package/src/has-many.js +0 -61
- package/src/main.js +0 -148
- package/src/manage-record.js +0 -118
- package/src/model-property.js +0 -29
- package/src/model.js +0 -9
- package/src/mysql/migration-generator.js +0 -188
- package/src/mysql/mysql-db.js +0 -320
- package/src/mysql/schema-introspector.js +0 -158
- package/src/record.js +0 -127
- package/src/relationships.js +0 -43
- package/src/serializer.js +0 -138
- package/src/store.js +0 -211
- package/src/transforms.js +0 -20
- package/src/utils.js +0 -12
- package/test-events-setup.js +0 -41
- package/test-hooks-manual.js +0 -54
- package/test-hooks-with-logging.js +0 -52
|
@@ -1,158 +0,0 @@
|
|
|
1
|
-
import Orm from '@stonyx/orm';
|
|
2
|
-
import { getMysqlType } from './type-map.js';
|
|
3
|
-
import { camelCaseToKebabCase } from '@stonyx/utils/string';
|
|
4
|
-
import { pluralize } from '../utils.js';
|
|
5
|
-
import { dbKey } from '../db.js';
|
|
6
|
-
|
|
7
|
-
function getRelationshipInfo(property) {
|
|
8
|
-
if (typeof property !== 'function') return null;
|
|
9
|
-
const fnStr = property.toString();
|
|
10
|
-
|
|
11
|
-
if (fnStr.includes(`getRelationships('belongsTo',`)) return 'belongsTo';
|
|
12
|
-
if (fnStr.includes(`getRelationships('hasMany',`)) return 'hasMany';
|
|
13
|
-
|
|
14
|
-
return null;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export function introspectModels() {
|
|
18
|
-
const { models } = Orm.instance;
|
|
19
|
-
const schemas = {};
|
|
20
|
-
|
|
21
|
-
for (const [modelKey, modelClass] of Object.entries(models)) {
|
|
22
|
-
const name = camelCaseToKebabCase(modelKey.slice(0, -5));
|
|
23
|
-
|
|
24
|
-
if (name === dbKey) continue;
|
|
25
|
-
|
|
26
|
-
const model = new modelClass(modelKey);
|
|
27
|
-
const columns = {};
|
|
28
|
-
const foreignKeys = {};
|
|
29
|
-
const relationships = { belongsTo: {}, hasMany: {} };
|
|
30
|
-
let idType = 'number';
|
|
31
|
-
|
|
32
|
-
const transforms = Orm.instance.transforms;
|
|
33
|
-
|
|
34
|
-
for (const [key, property] of Object.entries(model)) {
|
|
35
|
-
if (key.startsWith('__')) continue;
|
|
36
|
-
|
|
37
|
-
const relType = getRelationshipInfo(property);
|
|
38
|
-
|
|
39
|
-
if (relType === 'belongsTo') {
|
|
40
|
-
relationships.belongsTo[key] = true;
|
|
41
|
-
} else if (relType === 'hasMany') {
|
|
42
|
-
relationships.hasMany[key] = true;
|
|
43
|
-
} else if (property?.constructor?.name === 'ModelProperty') {
|
|
44
|
-
if (key === 'id') {
|
|
45
|
-
idType = property.type;
|
|
46
|
-
} else {
|
|
47
|
-
columns[key] = getMysqlType(property.type, transforms[property.type]);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// Build foreign keys from belongsTo relationships
|
|
53
|
-
for (const relName of Object.keys(relationships.belongsTo)) {
|
|
54
|
-
const fkColumn = `${relName}_id`;
|
|
55
|
-
foreignKeys[fkColumn] = {
|
|
56
|
-
references: pluralize(relName),
|
|
57
|
-
column: 'id',
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
schemas[name] = {
|
|
62
|
-
table: pluralize(name),
|
|
63
|
-
idType,
|
|
64
|
-
columns,
|
|
65
|
-
foreignKeys,
|
|
66
|
-
relationships,
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
return schemas;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
export function buildTableDDL(name, schema, allSchemas = {}) {
|
|
74
|
-
const { table, idType, columns, foreignKeys } = schema;
|
|
75
|
-
const lines = [];
|
|
76
|
-
|
|
77
|
-
// Primary key
|
|
78
|
-
if (idType === 'string') {
|
|
79
|
-
lines.push(' `id` VARCHAR(255) PRIMARY KEY');
|
|
80
|
-
} else {
|
|
81
|
-
lines.push(' `id` INT AUTO_INCREMENT PRIMARY KEY');
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// Attribute columns
|
|
85
|
-
for (const [col, mysqlType] of Object.entries(columns)) {
|
|
86
|
-
lines.push(` \`${col}\` ${mysqlType}`);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// Foreign key columns
|
|
90
|
-
for (const [fkCol, fkDef] of Object.entries(foreignKeys)) {
|
|
91
|
-
const refIdType = getReferencedIdType(fkDef.references, allSchemas);
|
|
92
|
-
lines.push(` \`${fkCol}\` ${refIdType}`);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// Timestamps
|
|
96
|
-
lines.push(' `created_at` DATETIME DEFAULT CURRENT_TIMESTAMP');
|
|
97
|
-
lines.push(' `updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP');
|
|
98
|
-
|
|
99
|
-
// Foreign key constraints
|
|
100
|
-
for (const [fkCol, fkDef] of Object.entries(foreignKeys)) {
|
|
101
|
-
lines.push(` FOREIGN KEY (\`${fkCol}\`) REFERENCES \`${fkDef.references}\`(\`${fkDef.column}\`) ON DELETE SET NULL`);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
return `CREATE TABLE IF NOT EXISTS \`${table}\` (\n${lines.join(',\n')}\n)`;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
function getReferencedIdType(tableName, allSchemas) {
|
|
108
|
-
// Look up the referenced table's PK type from schemas
|
|
109
|
-
for (const schema of Object.values(allSchemas)) {
|
|
110
|
-
if (schema.table === tableName) {
|
|
111
|
-
return schema.idType === 'string' ? 'VARCHAR(255)' : 'INT';
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
// Default to INT if referenced table not found in schemas
|
|
116
|
-
return 'INT';
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
export function getTopologicalOrder(schemas) {
|
|
120
|
-
const visited = new Set();
|
|
121
|
-
const order = [];
|
|
122
|
-
|
|
123
|
-
function visit(name) {
|
|
124
|
-
if (visited.has(name)) return;
|
|
125
|
-
visited.add(name);
|
|
126
|
-
|
|
127
|
-
const schema = schemas[name];
|
|
128
|
-
if (!schema) return;
|
|
129
|
-
|
|
130
|
-
// Visit dependencies (belongsTo targets) first
|
|
131
|
-
for (const relName of Object.keys(schema.relationships.belongsTo)) {
|
|
132
|
-
visit(relName);
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
order.push(name);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
for (const name of Object.keys(schemas)) {
|
|
139
|
-
visit(name);
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
return order;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
export function schemasToSnapshot(schemas) {
|
|
146
|
-
const snapshot = {};
|
|
147
|
-
|
|
148
|
-
for (const [name, schema] of Object.entries(schemas)) {
|
|
149
|
-
snapshot[name] = {
|
|
150
|
-
table: schema.table,
|
|
151
|
-
idType: schema.idType,
|
|
152
|
-
columns: { ...schema.columns },
|
|
153
|
-
foreignKeys: { ...schema.foreignKeys },
|
|
154
|
-
};
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
return snapshot;
|
|
158
|
-
}
|
package/src/record.js
DELETED
|
@@ -1,127 +0,0 @@
|
|
|
1
|
-
import { store } from './index.js';
|
|
2
|
-
import { getComputedProperties } from "./serializer.js";
|
|
3
|
-
import { camelCaseToKebabCase } from '@stonyx/utils/string';
|
|
4
|
-
import { pluralize } from './utils.js';
|
|
5
|
-
export default class Record {
|
|
6
|
-
__data = {};
|
|
7
|
-
__relationships = {};
|
|
8
|
-
__serialized = false;
|
|
9
|
-
|
|
10
|
-
constructor(model, serializer) {
|
|
11
|
-
this.__model = model;
|
|
12
|
-
this.__serializer = serializer;
|
|
13
|
-
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
serialize(rawData, options={}) {
|
|
17
|
-
const { __data:data } = this;
|
|
18
|
-
|
|
19
|
-
if (this.__serialized && !options.update) {
|
|
20
|
-
const relatedIds = {};
|
|
21
|
-
|
|
22
|
-
for (const [ key, childRecord ] of Object.entries(this.__relationships)) {
|
|
23
|
-
relatedIds[key] = Array.isArray(childRecord)
|
|
24
|
-
? childRecord.map(r => r.id)
|
|
25
|
-
: childRecord?.id ?? null;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
return { ...data, ...relatedIds };
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const normalizedData = this.__serializer.normalize(rawData);
|
|
32
|
-
this.__serializer.setProperties(normalizedData, this, options);
|
|
33
|
-
|
|
34
|
-
return data;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// Similar to serialize, but preserves top level relationship records
|
|
38
|
-
format() {
|
|
39
|
-
if (!this.__serialized) throw new Error('Record must be serialized before being converted to JSON');
|
|
40
|
-
|
|
41
|
-
const { __data:data } = this;
|
|
42
|
-
const records = {};
|
|
43
|
-
|
|
44
|
-
for (const [ key, childRecord ] of Object.entries(this.__relationships)) {
|
|
45
|
-
records[key] = Array.isArray(childRecord)
|
|
46
|
-
? childRecord.map(r => r.serialize())
|
|
47
|
-
: childRecord?.serialize() ?? null;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
return { ...data, ...records };
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// Formats record for JSON API output
|
|
54
|
-
toJSON(options = {}) {
|
|
55
|
-
if (!this.__serialized) throw new Error('Record must be serialized before being converted to JSON');
|
|
56
|
-
|
|
57
|
-
const { fields, baseUrl } = options;
|
|
58
|
-
const { __data:data } = this;
|
|
59
|
-
const modelName = this.__model.__name;
|
|
60
|
-
const pluralizedModelName = pluralize(modelName);
|
|
61
|
-
const recordId = data.id;
|
|
62
|
-
const relationships = {};
|
|
63
|
-
const attributes = {};
|
|
64
|
-
|
|
65
|
-
for (const [key, value] of Object.entries(data)) {
|
|
66
|
-
if (key === 'id') continue;
|
|
67
|
-
if (fields && !fields.has(key)) continue;
|
|
68
|
-
attributes[key] = value;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
for (const [key, getter] of getComputedProperties(this.__model)) {
|
|
72
|
-
if (fields && !fields.has(key)) continue;
|
|
73
|
-
attributes[key] = getter.call(this);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
for (const [key, childRecord] of Object.entries(this.__relationships)) {
|
|
77
|
-
if (fields && !fields.has(key)) continue;
|
|
78
|
-
|
|
79
|
-
const relationshipData = Array.isArray(childRecord)
|
|
80
|
-
? childRecord.map(r => ({ type: r.__model.__name, id: r.id }))
|
|
81
|
-
: childRecord ? { type: childRecord.__model.__name, id: childRecord.id } : null;
|
|
82
|
-
|
|
83
|
-
// Dasherize the key for URL paths (e.g., accessLinks -> access-links)
|
|
84
|
-
const dasherizedKey = camelCaseToKebabCase(key);
|
|
85
|
-
|
|
86
|
-
relationships[dasherizedKey] = { data: relationshipData };
|
|
87
|
-
|
|
88
|
-
// Add links to relationship if baseUrl provided
|
|
89
|
-
if (baseUrl) {
|
|
90
|
-
relationships[dasherizedKey].links = {
|
|
91
|
-
self: `${baseUrl}/${pluralizedModelName}/${recordId}/relationships/${dasherizedKey}`,
|
|
92
|
-
related: `${baseUrl}/${pluralizedModelName}/${recordId}/${dasherizedKey}`
|
|
93
|
-
};
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
const result = {
|
|
98
|
-
attributes,
|
|
99
|
-
relationships,
|
|
100
|
-
id: recordId,
|
|
101
|
-
type: modelName,
|
|
102
|
-
};
|
|
103
|
-
|
|
104
|
-
// Add resource links if baseUrl provided
|
|
105
|
-
if (baseUrl) {
|
|
106
|
-
result.links = {
|
|
107
|
-
self: `${baseUrl}/${pluralizedModelName}/${recordId}`
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
return result;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
unload(options={}) {
|
|
115
|
-
store.unloadRecord(this.__model.__name, this.id, options);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
clean() {
|
|
119
|
-
try {
|
|
120
|
-
for (const key of Object.keys(this)) {
|
|
121
|
-
delete this[key];
|
|
122
|
-
}
|
|
123
|
-
} catch {
|
|
124
|
-
// Ignore errors during cleanup, as some keys may not be deletable
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
}
|
package/src/relationships.js
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import { relationships } from "@stonyx/orm";
|
|
2
|
-
|
|
3
|
-
export default class Relationships {
|
|
4
|
-
constructor() {
|
|
5
|
-
if (Relationships.instance) return Relationships.instance;
|
|
6
|
-
Relationships.instance = this;
|
|
7
|
-
|
|
8
|
-
this.data = new Map();
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
get(key) {
|
|
12
|
-
return this.data.get(key);
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
set(key, value) {
|
|
16
|
-
this.data.set(key, value);
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
// TODO: Refactor mapping to remove a level of iteration
|
|
21
|
-
export function getRelationships(type, sourceModel, targetModel, relationshipId) {
|
|
22
|
-
const allRelationships = relationships.get(type);
|
|
23
|
-
|
|
24
|
-
// create relationship map for this type of it doesn't already exist
|
|
25
|
-
if (!allRelationships.has(sourceModel)) allRelationships.set(sourceModel, new Map());
|
|
26
|
-
|
|
27
|
-
const modelRelationship = allRelationships.get(sourceModel);
|
|
28
|
-
|
|
29
|
-
if (!modelRelationship.has(targetModel)) modelRelationship.set(targetModel, new Map());
|
|
30
|
-
|
|
31
|
-
const relationship = modelRelationship.get(targetModel);
|
|
32
|
-
|
|
33
|
-
// TODO: Determine whether already having id should be handled differently
|
|
34
|
-
//if (relationship.has(relationshipId)) return;
|
|
35
|
-
|
|
36
|
-
return relationship;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export function getHasManyRelationships(sourceModel, targetModel) {
|
|
40
|
-
return relationships.get('hasMany').get(sourceModel)?.get(targetModel);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export const TYPES = ['global', 'hasMany', 'belongsTo', 'pending'];
|
package/src/serializer.js
DELETED
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
import config from 'stonyx/config';
|
|
2
|
-
import { get, makeArray } from '@stonyx/utils/object';
|
|
3
|
-
|
|
4
|
-
const RESERVED_KEYS = ['__name'];
|
|
5
|
-
|
|
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;
|
|
10
|
-
|
|
11
|
-
return true;
|
|
12
|
-
}
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
if (!result) return null;
|
|
16
|
-
if (key) return result[key];
|
|
17
|
-
|
|
18
|
-
return result;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function query(rawData, pathPrefix, subPath) {
|
|
22
|
-
if (!rawData) return null;
|
|
23
|
-
|
|
24
|
-
const [ path, getter, pointer ] = makeArray(subPath);
|
|
25
|
-
const fullPath = `${pathPrefix}${path}`;
|
|
26
|
-
const value = get(rawData, fullPath);
|
|
27
|
-
|
|
28
|
-
if (getter === undefined || getter === null) return value;
|
|
29
|
-
|
|
30
|
-
try {
|
|
31
|
-
switch(typeof getter) {
|
|
32
|
-
case 'object':
|
|
33
|
-
return searchQuery(getter, value, pointer);
|
|
34
|
-
|
|
35
|
-
case 'function':
|
|
36
|
-
return getter(value);
|
|
37
|
-
|
|
38
|
-
case 'number':
|
|
39
|
-
const element = value[getter];
|
|
40
|
-
return pointer ? element[pointer] : element;
|
|
41
|
-
|
|
42
|
-
default:
|
|
43
|
-
return value[getter];
|
|
44
|
-
}
|
|
45
|
-
} catch (error) {
|
|
46
|
-
if (config.debug) console.error(`Cannot parse value for ${fullPath}.`, { getter, query }, error);
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export default class Serializer {
|
|
51
|
-
map = {};
|
|
52
|
-
path = '';
|
|
53
|
-
|
|
54
|
-
constructor(model) {
|
|
55
|
-
this.model = model;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* This method populates the record's instance with instances of
|
|
60
|
-
* the ModelProperty object, while setting parsed values to the record's
|
|
61
|
-
* __data property, which represents the serialized version of the data
|
|
62
|
-
*/
|
|
63
|
-
setProperties(rawData, record, options) {
|
|
64
|
-
const { path, model } = this;
|
|
65
|
-
const keys = Object.keys(model).filter(key => !RESERVED_KEYS.includes(key));
|
|
66
|
-
const pathPrefix = path ? `${path}.` : '';
|
|
67
|
-
const { __data:parsedData, __relationships:relatedRecords } = record;
|
|
68
|
-
|
|
69
|
-
for (const key of keys) {
|
|
70
|
-
const subPath = options.serialize ? (this.map[key] || key) : key;
|
|
71
|
-
const handler = model[key];
|
|
72
|
-
const data = query(rawData, pathPrefix, subPath);
|
|
73
|
-
|
|
74
|
-
// Ignore null/undefined values on updates (TODO: What if we want it set to null?)
|
|
75
|
-
if ((data === null || data === undefined) && options.update) continue;
|
|
76
|
-
|
|
77
|
-
// Relationship handling
|
|
78
|
-
if (typeof handler === 'function') {
|
|
79
|
-
// Pass relationship key name to handler for pending fulfillment
|
|
80
|
-
const handlerOptions = { ...options, _relationshipKey: key };
|
|
81
|
-
const childRecord = handler(record, data, handlerOptions);
|
|
82
|
-
|
|
83
|
-
record[key] = childRecord
|
|
84
|
-
relatedRecords[key] = childRecord;
|
|
85
|
-
|
|
86
|
-
continue;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// Direct assignment handling
|
|
90
|
-
if (handler?.constructor?.name !== 'ModelProperty') {
|
|
91
|
-
parsedData[key] = handler;
|
|
92
|
-
record[key] = handler;
|
|
93
|
-
continue;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
Object.defineProperty(record, key, {
|
|
97
|
-
enumerable: true,
|
|
98
|
-
configurable: true,
|
|
99
|
-
get: () => handler.value,
|
|
100
|
-
set(newValue) {
|
|
101
|
-
handler.ignoreFirstTransform = !options.transform;
|
|
102
|
-
handler.value = newValue;
|
|
103
|
-
parsedData[key] = handler.value;
|
|
104
|
-
}
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
record[key] = data;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
if (options.update) return;
|
|
111
|
-
|
|
112
|
-
// Serialize computed properties
|
|
113
|
-
for (const [key, getter] of getComputedProperties(this.model)) {
|
|
114
|
-
Object.defineProperty(record, key, {
|
|
115
|
-
enumerable: true,
|
|
116
|
-
get: () => getter.call(record)
|
|
117
|
-
});
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
record.__serialized = true;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
* OVERRIDE: This hook allows for data manipulation prior to serialization logic
|
|
125
|
-
*/
|
|
126
|
-
normalize(data) {
|
|
127
|
-
return data;
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
export function getComputedProperties(classInstance) {
|
|
132
|
-
const proto = Object.getPrototypeOf(classInstance);
|
|
133
|
-
if (!proto || proto === Object.prototype) return [];
|
|
134
|
-
|
|
135
|
-
return Object.entries(Object.getOwnPropertyDescriptors(proto))
|
|
136
|
-
.filter(([key, descriptor]) => key !== 'constructor' && descriptor.get)
|
|
137
|
-
.map(([key, descriptor]) => [key, descriptor.get]);
|
|
138
|
-
}
|
package/src/store.js
DELETED
|
@@ -1,211 +0,0 @@
|
|
|
1
|
-
import { relationships } from '@stonyx/orm';
|
|
2
|
-
import { TYPES } from './relationships.js';
|
|
3
|
-
|
|
4
|
-
export default class Store {
|
|
5
|
-
constructor() {
|
|
6
|
-
if (Store.instance) return Store.instance;
|
|
7
|
-
Store.instance = this;
|
|
8
|
-
|
|
9
|
-
this.data = new Map();
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
get(key, id) {
|
|
13
|
-
if (!id) return this.data.get(key);
|
|
14
|
-
|
|
15
|
-
return this.data.get(key)?.get(id);
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
set(key, value) {
|
|
19
|
-
this.data.set(key, value);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
remove(key, id) {
|
|
23
|
-
if (id) return this.unloadRecord(key, id);
|
|
24
|
-
|
|
25
|
-
this.unloadAllRecords(key);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
unloadRecord(model, id, options={}) {
|
|
29
|
-
const modelStore = this.data.get(model);
|
|
30
|
-
|
|
31
|
-
if (!modelStore) {
|
|
32
|
-
console.warn(`[Store] Cannot unload record: model "${model}" not found in store`);
|
|
33
|
-
return;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const record = modelStore.get(id);
|
|
37
|
-
|
|
38
|
-
if (!record) {
|
|
39
|
-
console.warn(`[Store] Cannot unload record: ${model}:${id} not found in store`);
|
|
40
|
-
return;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
const { toUnload, visited } = options.includeChildren
|
|
44
|
-
? this._buildUnloadQueue(record, options)
|
|
45
|
-
: { toUnload: [{ record, modelName: model, recordId: id }], visited: new Set([`${model}:${id}`]) };
|
|
46
|
-
|
|
47
|
-
for (const item of toUnload.reverse()) {
|
|
48
|
-
const { record: recordToUnload, modelName, recordId } = item;
|
|
49
|
-
|
|
50
|
-
this._removeFromHasManyArrays(modelName, recordId, visited);
|
|
51
|
-
this._nullifyBelongsToReferences(modelName, recordId, visited);
|
|
52
|
-
this._cleanupRelationshipRegistries(modelName, recordId);
|
|
53
|
-
recordToUnload.clean();
|
|
54
|
-
|
|
55
|
-
this.data.get(modelName).delete(recordId);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
unloadAllRecords(model, options={}) {
|
|
60
|
-
const modelStore = this.data.get(model);
|
|
61
|
-
|
|
62
|
-
if (!modelStore) {
|
|
63
|
-
console.warn(`[Store] Cannot unload all records: model "${model}" not found in store`);
|
|
64
|
-
return;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
const recordIds = Array.from(modelStore.keys());
|
|
68
|
-
|
|
69
|
-
for (const id of recordIds) {
|
|
70
|
-
if (modelStore.has(id)) {
|
|
71
|
-
this.unloadRecord(model, id, options);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
for (const relationshipType of TYPES) relationships.get(relationshipType).delete(model);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
_removeFromHasManyArrays(modelName, recordId, visited) {
|
|
79
|
-
const hasManyRegistry = relationships.get('hasMany');
|
|
80
|
-
|
|
81
|
-
for (const [sourceModel, targetModels] of hasManyRegistry) {
|
|
82
|
-
const targetModelMap = targetModels.get(modelName);
|
|
83
|
-
if (!targetModelMap) continue;
|
|
84
|
-
|
|
85
|
-
for (const [sourceRecordId, hasManyArray] of targetModelMap) {
|
|
86
|
-
const sourceKey = `${sourceModel}:${sourceRecordId}`;
|
|
87
|
-
|
|
88
|
-
// Don't modify arrays of records being deleted
|
|
89
|
-
if (visited.has(sourceKey)) continue;
|
|
90
|
-
|
|
91
|
-
const index = hasManyArray.findIndex(r => r && r.id === recordId);
|
|
92
|
-
if (index !== -1) hasManyArray.splice(index, 1);
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
_nullifyBelongsToReferences(modelName, recordId, visited) {
|
|
98
|
-
const belongsToRegistry = relationships.get('belongsTo');
|
|
99
|
-
|
|
100
|
-
for (const [sourceModel, targetModels] of belongsToRegistry) {
|
|
101
|
-
const targetModelMap = targetModels.get(modelName);
|
|
102
|
-
if (!targetModelMap) continue;
|
|
103
|
-
|
|
104
|
-
for (const [sourceRecordId, belongsToRecord] of targetModelMap) {
|
|
105
|
-
if (belongsToRecord && belongsToRecord.id === recordId) {
|
|
106
|
-
const sourceKey = `${sourceModel}:${sourceRecordId}`;
|
|
107
|
-
|
|
108
|
-
if (visited.has(sourceKey)) continue;
|
|
109
|
-
targetModelMap.set(sourceRecordId, null);
|
|
110
|
-
|
|
111
|
-
const sourceRecord = this.get(sourceModel, sourceRecordId);
|
|
112
|
-
if (sourceRecord && sourceRecord.__relationships) {
|
|
113
|
-
for (const [key, value] of Object.entries(sourceRecord.__relationships)) {
|
|
114
|
-
if (value && value.id === recordId) {
|
|
115
|
-
sourceRecord.__relationships[key] = null;
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
_cleanupRelationshipRegistries(modelName, recordId) {
|
|
125
|
-
const hasManyMap = relationships.get('hasMany').get(modelName);
|
|
126
|
-
if (hasManyMap) {
|
|
127
|
-
for (const [, recordMap] of hasManyMap) recordMap.delete(recordId);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
const belongsToMap = relationships.get('belongsTo').get(modelName);
|
|
131
|
-
if (belongsToMap) {
|
|
132
|
-
for (const [, recordMap] of belongsToMap) recordMap.delete(recordId);
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
const pendingMap = relationships.get('pending').get(modelName);
|
|
136
|
-
if (pendingMap) pendingMap.delete(recordId);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
/**
|
|
140
|
-
* Extracts hasMany and non-bidirectional belongsTo children from a record
|
|
141
|
-
* @private
|
|
142
|
-
*/
|
|
143
|
-
_getChildren(record) {
|
|
144
|
-
const children = [];
|
|
145
|
-
|
|
146
|
-
if (!record.__relationships) return children;
|
|
147
|
-
|
|
148
|
-
for (const [key, value] of Object.entries(record.__relationships)) {
|
|
149
|
-
// hasMany children - always include
|
|
150
|
-
if (Array.isArray(value)) {
|
|
151
|
-
for (const childRecord of value) {
|
|
152
|
-
if (childRecord) children.push({ childRecord, relationshipKey: key, type: 'hasMany' });
|
|
153
|
-
}
|
|
154
|
-
} else if (value && !this._isBidirectionalRelationship(
|
|
155
|
-
record.__model.__name,
|
|
156
|
-
value.__model.__name
|
|
157
|
-
)) {
|
|
158
|
-
children.push({ childRecord: value, relationshipKey: key, type: 'belongsTo' });
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
return children;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
_isBidirectionalRelationship(sourceModel, targetModel) {
|
|
166
|
-
const hasManyRegistry = relationships.get('hasMany');
|
|
167
|
-
const inverseMap = hasManyRegistry.get(targetModel)?.get(sourceModel);
|
|
168
|
-
|
|
169
|
-
return inverseMap && inverseMap.size > 0;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
_buildUnloadQueue(record, options) {
|
|
173
|
-
const visited = new Set();
|
|
174
|
-
const toUnload = [];
|
|
175
|
-
const queue = [{
|
|
176
|
-
record,
|
|
177
|
-
modelName: record.__model.__name,
|
|
178
|
-
recordId: record.id,
|
|
179
|
-
isRoot: true,
|
|
180
|
-
depth: 0
|
|
181
|
-
}];
|
|
182
|
-
|
|
183
|
-
while (queue.length > 0) {
|
|
184
|
-
const item = queue.shift();
|
|
185
|
-
const key = `${item.modelName}:${item.recordId}`;
|
|
186
|
-
|
|
187
|
-
if (visited.has(key)) continue;
|
|
188
|
-
visited.add(key);
|
|
189
|
-
|
|
190
|
-
toUnload.push(item);
|
|
191
|
-
|
|
192
|
-
// Add children to queue if includeChildren is enabled
|
|
193
|
-
if (options.includeChildren) {
|
|
194
|
-
const children = this._getChildren(item.record);
|
|
195
|
-
for (const { childRecord } of children) {
|
|
196
|
-
if (childRecord) {
|
|
197
|
-
queue.push({
|
|
198
|
-
record: childRecord,
|
|
199
|
-
modelName: childRecord.__model.__name,
|
|
200
|
-
recordId: childRecord.id,
|
|
201
|
-
isRoot: false,
|
|
202
|
-
depth: item.depth + 1
|
|
203
|
-
});
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
return { toUnload, visited };
|
|
210
|
-
}
|
|
211
|
-
}
|
package/src/transforms.js
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { getTimestamp } from "@stonyx/utils/date";
|
|
2
|
-
|
|
3
|
-
const transforms = {
|
|
4
|
-
boolean: value => typeof value === 'string' ? value.trim().toLowerCase() === 'true' : !!value,
|
|
5
|
-
date: value => value ? new Date(value) : null,
|
|
6
|
-
float: value => parseFloat(value),
|
|
7
|
-
number: value => parseInt(value),
|
|
8
|
-
passthrough: value => value,
|
|
9
|
-
string: value => String(value),
|
|
10
|
-
timestamp: value => getTimestamp(value),
|
|
11
|
-
trim: value => value?.trim(),
|
|
12
|
-
uppercase: value => value?.toUpperCase(),
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
// Math Proxies
|
|
16
|
-
['ceil', 'floor', 'round'].forEach(method => {
|
|
17
|
-
transforms[method] = value => Math[method](value);
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
export default transforms;
|
package/src/utils.js
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { pluralize as basePluralize } from '@stonyx/utils/string';
|
|
2
|
-
|
|
3
|
-
// Wrapper to handle dasherized model names (e.g., "access-link" → "access-links")
|
|
4
|
-
export function pluralize(word) {
|
|
5
|
-
if (word.includes('-')) {
|
|
6
|
-
const parts = word.split('-');
|
|
7
|
-
const pluralizedLast = basePluralize(parts.pop());
|
|
8
|
-
return [...parts, pluralizedLast].join('-');
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
return basePluralize(word);
|
|
12
|
-
}
|