@stonyx/orm 0.2.1-beta.90 → 0.2.1-beta.91

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/hooks.d.ts CHANGED
@@ -2,18 +2,31 @@
2
2
  * Middleware-based hooks registry for ORM operations.
3
3
  * Unlike event-based hooks, middleware hooks run sequentially and can halt operations.
4
4
  */
5
+ /** Context object passed to before/after hook handlers. */
5
6
  export interface HookContext {
7
+ /** Model name (e.g. 'user', 'animal'). */
6
8
  model: string;
9
+ /** Operation name: 'create', 'update', 'delete', 'get', or 'list'. */
7
10
  operation: string;
11
+ /** The incoming HTTP request object. */
8
12
  request?: unknown;
13
+ /** URL route parameters (e.g. { id: '42' }). */
9
14
  params?: Record<string, string>;
15
+ /** Parsed request body for create/update operations. */
10
16
  body?: Record<string, unknown>;
17
+ /** URL query string parameters. */
11
18
  query?: Record<string, string>;
19
+ /** Mutable state bag shared across hooks within a single request. */
12
20
  state?: Record<string, unknown>;
21
+ /** Previous record state (available in update hooks). */
13
22
  oldState?: unknown;
23
+ /** Target record ID for single-record operations. */
14
24
  recordId?: string | number;
25
+ /** Response data (available in after hooks). */
15
26
  response?: unknown;
27
+ /** The affected record (available in after hooks for create/update/delete). */
16
28
  record?: unknown;
29
+ /** The affected records (available in after hooks for list operations). */
17
30
  records?: unknown[];
18
31
  [key: string]: unknown;
19
32
  }
@@ -1,7 +1,7 @@
1
1
  import type { ForeignKeyDef, ModelSchema, ViewSchema, SnapshotEntry } from '../types/orm-types.js';
2
2
  export declare function introspectModels(): Record<string, ModelSchema>;
3
3
  export declare function buildTableDDL(name: string, schema: ModelSchema, allSchemas?: Record<string, ModelSchema>): string;
4
- export declare function getTopologicalOrder(schemas: Record<string, ModelSchema>): string[];
4
+ export { getTopologicalOrder } from '../schema-helpers.js';
5
5
  export declare function introspectViews(): Record<string, ViewSchema>;
6
6
  export declare function buildViewDDL(name: string, viewSchema: ViewSchema, modelSchemas?: Record<string, ModelSchema>): string;
7
7
  export declare function viewSchemasToSnapshot(viewSchemas: Record<string, ViewSchema>): Record<string, ViewSnapshotEntry>;
@@ -4,21 +4,8 @@ import { camelCaseToKebabCase } from '@stonyx/utils/string';
4
4
  import { getPluralName } from '../plural-registry.js';
5
5
  import { dbKey } from '../db.js';
6
6
  import { AggregateProperty } from '../aggregates.js';
7
+ import { getRelationshipInfo, sanitizeTableName } from '../schema-helpers.js';
7
8
  import ModelProperty from '../model-property.js';
8
- function getRelationshipInfo(property) {
9
- if (typeof property !== 'function')
10
- return null;
11
- const relType = property.__relationshipType;
12
- const modelName = property.__relatedModelName || null;
13
- if (relType === 'belongsTo')
14
- return { type: 'belongsTo', modelName };
15
- if (relType === 'hasMany')
16
- return { type: 'hasMany', modelName };
17
- return null;
18
- }
19
- function sanitizeTableName(name) {
20
- return name.replace(/[-/]/g, '_');
21
- }
22
9
  export function introspectModels() {
23
10
  const { models } = Orm.instance;
24
11
  const schemas = {};
@@ -112,28 +99,7 @@ function getReferencedIdType(tableName, allSchemas) {
112
99
  // Default to INT if referenced table not found in schemas
113
100
  return 'INT';
114
101
  }
115
- export function getTopologicalOrder(schemas) {
116
- const visited = new Set();
117
- const order = [];
118
- function visit(name) {
119
- if (visited.has(name))
120
- return;
121
- visited.add(name);
122
- const schema = schemas[name];
123
- if (!schema)
124
- return;
125
- // Visit dependencies (belongsTo targets) first
126
- for (const targetModelName of Object.values(schema.relationships.belongsTo)) {
127
- if (targetModelName)
128
- visit(targetModelName);
129
- }
130
- order.push(name);
131
- }
132
- for (const name of Object.keys(schemas)) {
133
- visit(name);
134
- }
135
- return order;
136
- }
102
+ export { getTopologicalOrder } from '../schema-helpers.js';
137
103
  export function introspectViews() {
138
104
  const orm = Orm.instance;
139
105
  if (!orm.views)
@@ -470,5 +470,6 @@ export default class OrmRequest extends Request {
470
470
  return 403;
471
471
  if (typeof access === 'function')
472
472
  state.filter = access;
473
+ return undefined;
473
474
  }
474
475
  }
@@ -21,9 +21,8 @@ export declare function buildTableDDL(name: string, schema: ModelSchema, allSche
21
21
  * Build HNSW index DDL for vector columns on a model.
22
22
  */
23
23
  export declare function buildVectorIndexDDL(name: string, schema: ModelSchema): string[];
24
- export declare function getTopologicalOrder(schemas: Record<string, ModelSchema>): string[];
24
+ export { getTopologicalOrder } from '../schema-helpers.js';
25
25
  export declare function introspectViews(): Record<string, ViewSchema>;
26
26
  export declare function buildViewDDL(name: string, viewSchema: ViewSchema, modelSchemas?: Record<string, ModelSchema>): string;
27
27
  export declare function viewSchemasToSnapshot(viewSchemas: Record<string, ViewSchema>): Record<string, ViewSnapshotEntry>;
28
28
  export declare function schemasToSnapshot(schemas: Record<string, ModelSchema>): Record<string, ModelSnapshotEntry>;
29
- export {};
@@ -4,21 +4,8 @@ import { camelCaseToKebabCase } from '@stonyx/utils/string';
4
4
  import { getPluralName } from '../plural-registry.js';
5
5
  import { dbKey } from '../db.js';
6
6
  import { AggregateProperty } from '../aggregates.js';
7
+ import { getRelationshipInfo, sanitizeTableName } from '../schema-helpers.js';
7
8
  import ModelProperty from '../model-property.js';
8
- function getRelationshipInfo(property) {
9
- if (typeof property !== 'function')
10
- return null;
11
- const relType = property.__relationshipType;
12
- const modelName = property.__relatedModelName || null;
13
- if (relType === 'belongsTo')
14
- return { type: 'belongsTo', modelName };
15
- if (relType === 'hasMany')
16
- return { type: 'hasMany', modelName };
17
- return null;
18
- }
19
- function sanitizeTableName(name) {
20
- return name.replace(/[-/]/g, '_');
21
- }
22
9
  export function introspectModels() {
23
10
  const { models } = Orm.instance;
24
11
  const schemas = {};
@@ -129,28 +116,7 @@ function getReferencedIdType(tableName, allSchemas) {
129
116
  }
130
117
  return 'INTEGER';
131
118
  }
132
- export function getTopologicalOrder(schemas) {
133
- const visited = new Set();
134
- const order = [];
135
- function visit(name) {
136
- if (visited.has(name))
137
- return;
138
- visited.add(name);
139
- const schema = schemas[name];
140
- if (!schema)
141
- return;
142
- // Visit dependencies (belongsTo targets) first
143
- for (const targetModelName of Object.values(schema.relationships.belongsTo)) {
144
- if (targetModelName)
145
- visit(targetModelName);
146
- }
147
- order.push(name);
148
- }
149
- for (const name of Object.keys(schemas)) {
150
- visit(name);
151
- }
152
- return order;
153
- }
119
+ export { getTopologicalOrder } from '../schema-helpers.js';
154
120
  export function introspectViews() {
155
121
  const orm = Orm.instance;
156
122
  if (!orm.views)
@@ -0,0 +1,20 @@
1
+ import type { ModelSchema } from './types/orm-types.js';
2
+ export interface SchemaRelationshipInfo {
3
+ type: 'belongsTo' | 'hasMany';
4
+ modelName: string | null;
5
+ }
6
+ /**
7
+ * Detect relationship type and target model from a model property function.
8
+ * Returns null if the property is not a relationship.
9
+ */
10
+ export declare function getRelationshipInfo(property: unknown): SchemaRelationshipInfo | null;
11
+ /**
12
+ * Sanitize a model/table name for use in SQL identifiers.
13
+ * Replaces hyphens and slashes with underscores.
14
+ */
15
+ export declare function sanitizeTableName(name: string): string;
16
+ /**
17
+ * Sort model schemas in dependency order (belongsTo targets before dependents).
18
+ * Uses depth-first traversal to ensure referenced tables are created first.
19
+ */
20
+ export declare function getTopologicalOrder(schemas: Record<string, ModelSchema>): string[];
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Detect relationship type and target model from a model property function.
3
+ * Returns null if the property is not a relationship.
4
+ */
5
+ export function getRelationshipInfo(property) {
6
+ if (typeof property !== 'function')
7
+ return null;
8
+ const relType = property.__relationshipType;
9
+ const modelName = property.__relatedModelName || null;
10
+ if (relType === 'belongsTo')
11
+ return { type: 'belongsTo', modelName };
12
+ if (relType === 'hasMany')
13
+ return { type: 'hasMany', modelName };
14
+ return null;
15
+ }
16
+ /**
17
+ * Sanitize a model/table name for use in SQL identifiers.
18
+ * Replaces hyphens and slashes with underscores.
19
+ */
20
+ export function sanitizeTableName(name) {
21
+ return name.replace(/[-/]/g, '_');
22
+ }
23
+ /**
24
+ * Sort model schemas in dependency order (belongsTo targets before dependents).
25
+ * Uses depth-first traversal to ensure referenced tables are created first.
26
+ */
27
+ export function getTopologicalOrder(schemas) {
28
+ const visited = new Set();
29
+ const order = [];
30
+ function visit(name) {
31
+ if (visited.has(name))
32
+ return;
33
+ visited.add(name);
34
+ const schema = schemas[name];
35
+ if (!schema)
36
+ return;
37
+ // Visit dependencies (belongsTo targets) first
38
+ for (const targetModelName of Object.values(schema.relationships.belongsTo)) {
39
+ if (targetModelName)
40
+ visit(targetModelName);
41
+ }
42
+ order.push(name);
43
+ }
44
+ for (const name of Object.keys(schemas)) {
45
+ visit(name);
46
+ }
47
+ return order;
48
+ }
package/dist/store.js CHANGED
@@ -119,14 +119,14 @@ export default class Store {
119
119
  unloadRecord(model, id, options = {}) {
120
120
  const modelStore = this.data.get(model);
121
121
  if (!modelStore) {
122
- console.warn(`[Store] Cannot unload record: model "${model}" not found in store`);
122
+ console.warn(`[Store] Cannot unload record: model "${model}" not found in store — ensure the model is registered before unloading`);
123
123
  return;
124
124
  }
125
125
  if (typeof id !== 'string' && typeof id !== 'number')
126
126
  return;
127
127
  const raw = modelStore.get(id);
128
128
  if (!raw || !isStoreRecord(raw)) {
129
- console.warn(`[Store] Cannot unload record: ${model}:${id} not found in store`);
129
+ console.warn(`[Store] Cannot unload record: ${model}:${id} not found in store — it may have already been unloaded`);
130
130
  return;
131
131
  }
132
132
  const record = raw;
@@ -145,7 +145,7 @@ export default class Store {
145
145
  unloadAllRecords(model, options = {}) {
146
146
  const modelStore = this.data.get(model);
147
147
  if (!modelStore) {
148
- console.warn(`[Store] Cannot unload all records: model "${model}" not found in store`);
148
+ console.warn(`[Store] Cannot unload all records: model "${model}" not found in store — ensure the model is registered before unloading`);
149
149
  return;
150
150
  }
151
151
  const recordIds = Array.from(modelStore.keys());
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "stonyx-async",
5
5
  "stonyx-module"
6
6
  ],
7
- "version": "0.2.1-beta.90",
7
+ "version": "0.2.1-beta.91",
8
8
  "description": "",
9
9
  "main": "dist/index.js",
10
10
  "type": "module",
package/src/hooks.ts CHANGED
@@ -19,18 +19,31 @@
19
19
  * Unlike event-based hooks, middleware hooks run sequentially and can halt operations.
20
20
  */
21
21
 
22
+ /** Context object passed to before/after hook handlers. */
22
23
  export interface HookContext {
24
+ /** Model name (e.g. 'user', 'animal'). */
23
25
  model: string;
26
+ /** Operation name: 'create', 'update', 'delete', 'get', or 'list'. */
24
27
  operation: string;
28
+ /** The incoming HTTP request object. */
25
29
  request?: unknown;
30
+ /** URL route parameters (e.g. { id: '42' }). */
26
31
  params?: Record<string, string>;
32
+ /** Parsed request body for create/update operations. */
27
33
  body?: Record<string, unknown>;
34
+ /** URL query string parameters. */
28
35
  query?: Record<string, string>;
36
+ /** Mutable state bag shared across hooks within a single request. */
29
37
  state?: Record<string, unknown>;
38
+ /** Previous record state (available in update hooks). */
30
39
  oldState?: unknown;
40
+ /** Target record ID for single-record operations. */
31
41
  recordId?: string | number;
42
+ /** Response data (available in after hooks). */
32
43
  response?: unknown;
44
+ /** The affected record (available in after hooks for create/update/delete). */
33
45
  record?: unknown;
46
+ /** The affected records (available in after hooks for list operations). */
34
47
  records?: unknown[];
35
48
  [key: string]: unknown;
36
49
  }
@@ -4,39 +4,15 @@ import { camelCaseToKebabCase } from '@stonyx/utils/string';
4
4
  import { getPluralName } from '../plural-registry.js';
5
5
  import { dbKey } from '../db.js';
6
6
  import { AggregateProperty } from '../aggregates.js';
7
+ import { getRelationshipInfo, sanitizeTableName } from '../schema-helpers.js';
7
8
  import type { ForeignKeyDef, ModelSchema, ViewSchema, SnapshotEntry } from '../types/orm-types.js';
8
9
  import ModelProperty from '../model-property.js';
9
10
 
10
- interface RelationshipInfo {
11
- type: 'belongsTo' | 'hasMany';
12
- modelName: string | null;
13
- }
14
-
15
11
  interface JoinClause {
16
12
  table: string;
17
13
  condition: string;
18
14
  }
19
15
 
20
- interface RelationshipProperty {
21
- __relatedModelName?: string | null;
22
- __relationshipType?: string;
23
- }
24
-
25
- function getRelationshipInfo(property: unknown): RelationshipInfo | null {
26
- if (typeof property !== 'function') return null;
27
- const relType = (property as RelationshipProperty).__relationshipType;
28
- const modelName = (property as RelationshipProperty).__relatedModelName || null;
29
-
30
- if (relType === 'belongsTo') return { type: 'belongsTo', modelName };
31
- if (relType === 'hasMany') return { type: 'hasMany', modelName };
32
-
33
- return null;
34
- }
35
-
36
- function sanitizeTableName(name: string): string {
37
- return name.replace(/[-/]/g, '_');
38
- }
39
-
40
16
  export function introspectModels(): Record<string, ModelSchema> {
41
17
  const { models } = (Orm as unknown as { instance: { models: Record<string, unknown>; transforms: Record<string, unknown> } }).instance;
42
18
  const schemas: Record<string, ModelSchema> = {};
@@ -143,31 +119,7 @@ function getReferencedIdType(tableName: string, allSchemas: Record<string, Model
143
119
  return 'INT';
144
120
  }
145
121
 
146
- export function getTopologicalOrder(schemas: Record<string, ModelSchema>): string[] {
147
- const visited = new Set<string>();
148
- const order: string[] = [];
149
-
150
- function visit(name: string): void {
151
- if (visited.has(name)) return;
152
- visited.add(name);
153
-
154
- const schema = schemas[name];
155
- if (!schema) return;
156
-
157
- // Visit dependencies (belongsTo targets) first
158
- for (const targetModelName of Object.values(schema.relationships.belongsTo)) {
159
- if (targetModelName) visit(targetModelName);
160
- }
161
-
162
- order.push(name);
163
- }
164
-
165
- for (const name of Object.keys(schemas)) {
166
- visit(name);
167
- }
168
-
169
- return order;
170
- }
122
+ export { getTopologicalOrder } from '../schema-helpers.js';
171
123
 
172
124
  export function introspectViews(): Record<string, ViewSchema> {
173
125
  const orm = (Orm as unknown as { instance: { views?: Record<string, unknown>; transforms: Record<string, unknown> } }).instance;
@@ -577,5 +577,6 @@ export default class OrmRequest extends Request {
577
577
  if (!access) return 403;
578
578
  if (Array.isArray(access) && !access.includes(methodAccessMap[request.method])) return 403;
579
579
  if (typeof access === 'function') state.filter = access;
580
+ return undefined;
580
581
  }
581
582
  }
@@ -4,14 +4,10 @@ import { camelCaseToKebabCase } from '@stonyx/utils/string';
4
4
  import { getPluralName } from '../plural-registry.js';
5
5
  import { dbKey } from '../db.js';
6
6
  import { AggregateProperty } from '../aggregates.js';
7
+ import { getRelationshipInfo, sanitizeTableName } from '../schema-helpers.js';
7
8
  import type { ForeignKeyDef, ModelSchema, ViewSchema } from '../types/orm-types.js';
8
9
  import ModelProperty from '../model-property.js';
9
10
 
10
- interface RelationshipInfo {
11
- type: 'belongsTo' | 'hasMany';
12
- modelName: string | null;
13
- }
14
-
15
11
  interface ViewSnapshotEntry {
16
12
  viewName: string;
17
13
  source: string;
@@ -35,21 +31,6 @@ interface JoinDef {
35
31
  condition: string;
36
32
  }
37
33
 
38
- function getRelationshipInfo(property: unknown): RelationshipInfo | null {
39
- if (typeof property !== 'function') return null;
40
- const relType = (property as { __relationshipType?: string }).__relationshipType;
41
- const modelName = (property as { __relatedModelName?: string }).__relatedModelName || null;
42
-
43
- if (relType === 'belongsTo') return { type: 'belongsTo', modelName };
44
- if (relType === 'hasMany') return { type: 'hasMany', modelName };
45
-
46
- return null;
47
- }
48
-
49
- function sanitizeTableName(name: string): string {
50
- return name.replace(/[-/]/g, '_');
51
- }
52
-
53
34
  export function introspectModels(): Record<string, ModelSchema> {
54
35
  const { models } = Orm.instance as { models: Record<string, unknown> };
55
36
  const schemas: Record<string, ModelSchema> = {};
@@ -177,31 +158,7 @@ function getReferencedIdType(tableName: string, allSchemas: Record<string, Model
177
158
  return 'INTEGER';
178
159
  }
179
160
 
180
- export function getTopologicalOrder(schemas: Record<string, ModelSchema>): string[] {
181
- const visited = new Set<string>();
182
- const order: string[] = [];
183
-
184
- function visit(name: string): void {
185
- if (visited.has(name)) return;
186
- visited.add(name);
187
-
188
- const schema = schemas[name];
189
- if (!schema) return;
190
-
191
- // Visit dependencies (belongsTo targets) first
192
- for (const targetModelName of Object.values(schema.relationships.belongsTo)) {
193
- if (targetModelName) visit(targetModelName);
194
- }
195
-
196
- order.push(name);
197
- }
198
-
199
- for (const name of Object.keys(schemas)) {
200
- visit(name);
201
- }
202
-
203
- return order;
204
- }
161
+ export { getTopologicalOrder } from '../schema-helpers.js';
205
162
 
206
163
  export function introspectViews(): Record<string, ViewSchema> {
207
164
  const orm = Orm.instance as { views?: Record<string, unknown> };
@@ -0,0 +1,59 @@
1
+ import type { ModelSchema } from './types/orm-types.js';
2
+
3
+ export interface SchemaRelationshipInfo {
4
+ type: 'belongsTo' | 'hasMany';
5
+ modelName: string | null;
6
+ }
7
+
8
+ /**
9
+ * Detect relationship type and target model from a model property function.
10
+ * Returns null if the property is not a relationship.
11
+ */
12
+ export function getRelationshipInfo(property: unknown): SchemaRelationshipInfo | null {
13
+ if (typeof property !== 'function') return null;
14
+ const relType = (property as { __relationshipType?: string }).__relationshipType;
15
+ const modelName = (property as { __relatedModelName?: string }).__relatedModelName || null;
16
+
17
+ if (relType === 'belongsTo') return { type: 'belongsTo', modelName };
18
+ if (relType === 'hasMany') return { type: 'hasMany', modelName };
19
+
20
+ return null;
21
+ }
22
+
23
+ /**
24
+ * Sanitize a model/table name for use in SQL identifiers.
25
+ * Replaces hyphens and slashes with underscores.
26
+ */
27
+ export function sanitizeTableName(name: string): string {
28
+ return name.replace(/[-/]/g, '_');
29
+ }
30
+
31
+ /**
32
+ * Sort model schemas in dependency order (belongsTo targets before dependents).
33
+ * Uses depth-first traversal to ensure referenced tables are created first.
34
+ */
35
+ export function getTopologicalOrder(schemas: Record<string, ModelSchema>): string[] {
36
+ const visited = new Set<string>();
37
+ const order: string[] = [];
38
+
39
+ function visit(name: string): void {
40
+ if (visited.has(name)) return;
41
+ visited.add(name);
42
+
43
+ const schema = schemas[name];
44
+ if (!schema) return;
45
+
46
+ // Visit dependencies (belongsTo targets) first
47
+ for (const targetModelName of Object.values(schema.relationships.belongsTo)) {
48
+ if (targetModelName) visit(targetModelName);
49
+ }
50
+
51
+ order.push(name);
52
+ }
53
+
54
+ for (const name of Object.keys(schemas)) {
55
+ visit(name);
56
+ }
57
+
58
+ return order;
59
+ }
package/src/store.ts CHANGED
@@ -185,14 +185,14 @@ export default class Store {
185
185
  const modelStore = this.data.get(model);
186
186
 
187
187
  if (!modelStore) {
188
- console.warn(`[Store] Cannot unload record: model "${model}" not found in store`);
188
+ console.warn(`[Store] Cannot unload record: model "${model}" not found in store — ensure the model is registered before unloading`);
189
189
  return;
190
190
  }
191
191
 
192
192
  if (typeof id !== 'string' && typeof id !== 'number') return;
193
193
  const raw = modelStore.get(id);
194
194
  if (!raw || !isStoreRecord(raw)) {
195
- console.warn(`[Store] Cannot unload record: ${model}:${id} not found in store`);
195
+ console.warn(`[Store] Cannot unload record: ${model}:${id} not found in store — it may have already been unloaded`);
196
196
  return;
197
197
  }
198
198
  const record = raw;
@@ -217,7 +217,7 @@ export default class Store {
217
217
  const modelStore = this.data.get(model);
218
218
 
219
219
  if (!modelStore) {
220
- console.warn(`[Store] Cannot unload all records: model "${model}" not found in store`);
220
+ console.warn(`[Store] Cannot unload all records: model "${model}" not found in store — ensure the model is registered before unloading`);
221
221
  return;
222
222
  }
223
223