@stonyx/orm 0.3.2-beta.7 → 0.3.2-beta.8
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/config/environment.js +12 -91
- package/config/environment.ts +91 -0
- package/dist/index.d.ts +1 -0
- package/dist/main.d.ts +16 -0
- package/dist/main.js +26 -0
- package/dist/manage-record.js +12 -3
- package/dist/store.js +6 -1
- package/package.json +6 -5
- package/src/index.ts +1 -0
- package/src/main.ts +35 -0
- package/src/manage-record.ts +12 -3
- package/src/store.ts +6 -1
package/config/environment.js
CHANGED
|
@@ -1,91 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
DB_SCHEMA_PATH,
|
|
14
|
-
DB_SAVE_INTERVAL,
|
|
15
|
-
MYSQL_HOST,
|
|
16
|
-
MYSQL_PORT,
|
|
17
|
-
MYSQL_USER,
|
|
18
|
-
MYSQL_PASSWORD,
|
|
19
|
-
MYSQL_DATABASE,
|
|
20
|
-
MYSQL_CONNECTION_LIMIT,
|
|
21
|
-
MYSQL_MIGRATIONS_DIR,
|
|
22
|
-
PG_HOST,
|
|
23
|
-
PG_PORT,
|
|
24
|
-
PG_USER,
|
|
25
|
-
PG_PASSWORD,
|
|
26
|
-
PG_DATABASE,
|
|
27
|
-
PG_CONNECTION_LIMIT,
|
|
28
|
-
PG_MIGRATIONS_DIR,
|
|
29
|
-
TIMESCALE_HOST,
|
|
30
|
-
TIMESCALE_PORT,
|
|
31
|
-
TIMESCALE_USER,
|
|
32
|
-
TIMESCALE_PASSWORD,
|
|
33
|
-
TIMESCALE_DATABASE,
|
|
34
|
-
TIMESCALE_CONNECTION_LIMIT,
|
|
35
|
-
TIMESCALE_MIGRATIONS_DIR,
|
|
36
|
-
} = process.env;
|
|
37
|
-
|
|
38
|
-
export default {
|
|
39
|
-
logColor: 'white',
|
|
40
|
-
logMethod: 'db',
|
|
41
|
-
|
|
42
|
-
db: {
|
|
43
|
-
autosave: DB_AUTO_SAVE ?? 'false', // 'true' (cron interval), 'false' (disabled), 'onUpdate' (save after each write op)
|
|
44
|
-
file: DB_FILE ?? 'db.json',
|
|
45
|
-
mode: DB_MODE ?? 'file', // 'file' (single db.json) or 'directory' (one file per collection)
|
|
46
|
-
directory: DB_DIRECTORY ?? 'db', // directory name for collection files when mode is 'directory'
|
|
47
|
-
saveInterval: DB_SAVE_INTERVAL ?? 60 * 60, // 1 hour
|
|
48
|
-
schema: DB_SCHEMA_PATH ?? './config/db-schema.js'
|
|
49
|
-
},
|
|
50
|
-
paths: {
|
|
51
|
-
access: ORM_ACCESS_PATH ?? './access', // Optional for restServer access hooks
|
|
52
|
-
model: ORM_MODEL_PATH ?? './models',
|
|
53
|
-
serializer: ORM_SERIALIZER_PATH ?? './serializers',
|
|
54
|
-
transform: ORM_TRANSFORM_PATH ?? './transforms',
|
|
55
|
-
view: ORM_VIEW_PATH ?? './views'
|
|
56
|
-
},
|
|
57
|
-
mysql: MYSQL_HOST ? {
|
|
58
|
-
host: MYSQL_HOST ?? 'localhost',
|
|
59
|
-
port: parseInt(MYSQL_PORT ?? '3306'),
|
|
60
|
-
user: MYSQL_USER ?? 'root',
|
|
61
|
-
password: MYSQL_PASSWORD ?? '',
|
|
62
|
-
database: MYSQL_DATABASE ?? 'stonyx',
|
|
63
|
-
connectionLimit: parseInt(MYSQL_CONNECTION_LIMIT ?? '10'),
|
|
64
|
-
migrationsDir: MYSQL_MIGRATIONS_DIR ?? 'migrations',
|
|
65
|
-
migrationsTable: '__migrations',
|
|
66
|
-
} : undefined,
|
|
67
|
-
postgres: PG_HOST ? {
|
|
68
|
-
host: PG_HOST ?? 'localhost',
|
|
69
|
-
port: parseInt(PG_PORT ?? '5432'),
|
|
70
|
-
user: PG_USER ?? 'postgres',
|
|
71
|
-
password: PG_PASSWORD ?? '',
|
|
72
|
-
database: PG_DATABASE ?? 'stonyx',
|
|
73
|
-
connectionLimit: parseInt(PG_CONNECTION_LIMIT ?? '10'),
|
|
74
|
-
migrationsDir: PG_MIGRATIONS_DIR ?? 'migrations',
|
|
75
|
-
migrationsTable: '__migrations',
|
|
76
|
-
} : undefined,
|
|
77
|
-
timescale: TIMESCALE_HOST ? {
|
|
78
|
-
host: TIMESCALE_HOST ?? 'localhost',
|
|
79
|
-
port: parseInt(TIMESCALE_PORT ?? '5432'),
|
|
80
|
-
user: TIMESCALE_USER ?? 'postgres',
|
|
81
|
-
password: TIMESCALE_PASSWORD ?? '',
|
|
82
|
-
database: TIMESCALE_DATABASE ?? 'stonyx',
|
|
83
|
-
connectionLimit: parseInt(TIMESCALE_CONNECTION_LIMIT ?? '10'),
|
|
84
|
-
migrationsDir: TIMESCALE_MIGRATIONS_DIR ?? 'migrations',
|
|
85
|
-
migrationsTable: '__migrations',
|
|
86
|
-
} : undefined,
|
|
87
|
-
restServer: {
|
|
88
|
-
enabled: ORM_USE_REST_SERVER ?? 'true', // Whether to load restServer for automatic route setup or
|
|
89
|
-
route: ORM_REST_ROUTE ?? '/',
|
|
90
|
-
}
|
|
91
|
-
}
|
|
1
|
+
// project configuration, override-able by listed environment variables
|
|
2
|
+
const {
|
|
3
|
+
DEBUG,
|
|
4
|
+
NODE_ENV,
|
|
5
|
+
} = process.env;
|
|
6
|
+
|
|
7
|
+
const environment = NODE_ENV ?? 'development';
|
|
8
|
+
|
|
9
|
+
export default {
|
|
10
|
+
environment,
|
|
11
|
+
debug: DEBUG ?? environment === 'development',
|
|
12
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
const {
|
|
2
|
+
ORM_ACCESS_PATH,
|
|
3
|
+
ORM_MODEL_PATH,
|
|
4
|
+
ORM_REST_ROUTE,
|
|
5
|
+
ORM_SERIALIZER_PATH,
|
|
6
|
+
ORM_TRANSFORM_PATH,
|
|
7
|
+
ORM_VIEW_PATH,
|
|
8
|
+
ORM_USE_REST_SERVER,
|
|
9
|
+
DB_AUTO_SAVE,
|
|
10
|
+
DB_FILE,
|
|
11
|
+
DB_MODE,
|
|
12
|
+
DB_DIRECTORY,
|
|
13
|
+
DB_SCHEMA_PATH,
|
|
14
|
+
DB_SAVE_INTERVAL,
|
|
15
|
+
MYSQL_HOST,
|
|
16
|
+
MYSQL_PORT,
|
|
17
|
+
MYSQL_USER,
|
|
18
|
+
MYSQL_PASSWORD,
|
|
19
|
+
MYSQL_DATABASE,
|
|
20
|
+
MYSQL_CONNECTION_LIMIT,
|
|
21
|
+
MYSQL_MIGRATIONS_DIR,
|
|
22
|
+
PG_HOST,
|
|
23
|
+
PG_PORT,
|
|
24
|
+
PG_USER,
|
|
25
|
+
PG_PASSWORD,
|
|
26
|
+
PG_DATABASE,
|
|
27
|
+
PG_CONNECTION_LIMIT,
|
|
28
|
+
PG_MIGRATIONS_DIR,
|
|
29
|
+
TIMESCALE_HOST,
|
|
30
|
+
TIMESCALE_PORT,
|
|
31
|
+
TIMESCALE_USER,
|
|
32
|
+
TIMESCALE_PASSWORD,
|
|
33
|
+
TIMESCALE_DATABASE,
|
|
34
|
+
TIMESCALE_CONNECTION_LIMIT,
|
|
35
|
+
TIMESCALE_MIGRATIONS_DIR,
|
|
36
|
+
} = process.env;
|
|
37
|
+
|
|
38
|
+
export default {
|
|
39
|
+
logColor: 'white',
|
|
40
|
+
logMethod: 'db',
|
|
41
|
+
|
|
42
|
+
db: {
|
|
43
|
+
autosave: DB_AUTO_SAVE ?? 'false', // 'true' (cron interval), 'false' (disabled), 'onUpdate' (save after each write op)
|
|
44
|
+
file: DB_FILE ?? 'db.json',
|
|
45
|
+
mode: DB_MODE ?? 'file', // 'file' (single db.json) or 'directory' (one file per collection)
|
|
46
|
+
directory: DB_DIRECTORY ?? 'db', // directory name for collection files when mode is 'directory'
|
|
47
|
+
saveInterval: DB_SAVE_INTERVAL ?? 60 * 60, // 1 hour
|
|
48
|
+
schema: DB_SCHEMA_PATH ?? './config/db-schema.js'
|
|
49
|
+
},
|
|
50
|
+
paths: {
|
|
51
|
+
access: ORM_ACCESS_PATH ?? './access', // Optional for restServer access hooks
|
|
52
|
+
model: ORM_MODEL_PATH ?? './models',
|
|
53
|
+
serializer: ORM_SERIALIZER_PATH ?? './serializers',
|
|
54
|
+
transform: ORM_TRANSFORM_PATH ?? './transforms',
|
|
55
|
+
view: ORM_VIEW_PATH ?? './views'
|
|
56
|
+
},
|
|
57
|
+
mysql: MYSQL_HOST ? {
|
|
58
|
+
host: MYSQL_HOST ?? 'localhost',
|
|
59
|
+
port: parseInt(MYSQL_PORT ?? '3306'),
|
|
60
|
+
user: MYSQL_USER ?? 'root',
|
|
61
|
+
password: MYSQL_PASSWORD ?? '',
|
|
62
|
+
database: MYSQL_DATABASE ?? 'stonyx',
|
|
63
|
+
connectionLimit: parseInt(MYSQL_CONNECTION_LIMIT ?? '10'),
|
|
64
|
+
migrationsDir: MYSQL_MIGRATIONS_DIR ?? 'migrations',
|
|
65
|
+
migrationsTable: '__migrations',
|
|
66
|
+
} : undefined,
|
|
67
|
+
postgres: PG_HOST ? {
|
|
68
|
+
host: PG_HOST ?? 'localhost',
|
|
69
|
+
port: parseInt(PG_PORT ?? '5432'),
|
|
70
|
+
user: PG_USER ?? 'postgres',
|
|
71
|
+
password: PG_PASSWORD ?? '',
|
|
72
|
+
database: PG_DATABASE ?? 'stonyx',
|
|
73
|
+
connectionLimit: parseInt(PG_CONNECTION_LIMIT ?? '10'),
|
|
74
|
+
migrationsDir: PG_MIGRATIONS_DIR ?? 'migrations',
|
|
75
|
+
migrationsTable: '__migrations',
|
|
76
|
+
} : undefined,
|
|
77
|
+
timescale: TIMESCALE_HOST ? {
|
|
78
|
+
host: TIMESCALE_HOST ?? 'localhost',
|
|
79
|
+
port: parseInt(TIMESCALE_PORT ?? '5432'),
|
|
80
|
+
user: TIMESCALE_USER ?? 'postgres',
|
|
81
|
+
password: TIMESCALE_PASSWORD ?? '',
|
|
82
|
+
database: TIMESCALE_DATABASE ?? 'stonyx',
|
|
83
|
+
connectionLimit: parseInt(TIMESCALE_CONNECTION_LIMIT ?? '10'),
|
|
84
|
+
migrationsDir: TIMESCALE_MIGRATIONS_DIR ?? 'migrations',
|
|
85
|
+
migrationsTable: '__migrations',
|
|
86
|
+
} : undefined,
|
|
87
|
+
restServer: {
|
|
88
|
+
enabled: ORM_USE_REST_SERVER ?? 'true', // Whether to load restServer for automatic route setup or
|
|
89
|
+
route: ORM_REST_ROUTE ?? '/',
|
|
90
|
+
}
|
|
91
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ import { createRecord, updateRecord } from './manage-record.js';
|
|
|
8
8
|
import { count, avg, sum, min, max } from './aggregates.js';
|
|
9
9
|
export { default } from './main.js';
|
|
10
10
|
export { store, relationships } from './main.js';
|
|
11
|
+
export type { PersistErrorDetail } from './main.js';
|
|
11
12
|
export { Model, View, Serializer };
|
|
12
13
|
export { attr, belongsTo, hasMany, createRecord, updateRecord };
|
|
13
14
|
export { count, avg, sum, min, max };
|
package/dist/main.d.ts
CHANGED
|
@@ -15,6 +15,12 @@ export interface OrmDB {
|
|
|
15
15
|
save(): Promise<void>;
|
|
16
16
|
init(): Promise<void>;
|
|
17
17
|
}
|
|
18
|
+
export interface PersistErrorDetail {
|
|
19
|
+
operation: 'create' | 'update' | 'delete';
|
|
20
|
+
modelName: string;
|
|
21
|
+
recordId: unknown;
|
|
22
|
+
error: Error;
|
|
23
|
+
}
|
|
18
24
|
export default class Orm {
|
|
19
25
|
static initialized: boolean;
|
|
20
26
|
static relationships: Map<string, Map<string, unknown>>;
|
|
@@ -29,6 +35,7 @@ export default class Orm {
|
|
|
29
35
|
options: OrmOptions;
|
|
30
36
|
sqlDb?: SqlDb;
|
|
31
37
|
db?: OrmDB | SqlDb;
|
|
38
|
+
private _persistErrorHandler;
|
|
32
39
|
constructor(options?: OrmOptions);
|
|
33
40
|
init(): Promise<void>;
|
|
34
41
|
startup(): Promise<void>;
|
|
@@ -39,6 +46,15 @@ export default class Orm {
|
|
|
39
46
|
serializerClass: unknown;
|
|
40
47
|
};
|
|
41
48
|
isView(modelName: string): boolean;
|
|
49
|
+
/**
|
|
50
|
+
* Register a callback to be invoked when a fire-and-forget SQL persist fails.
|
|
51
|
+
* Without a handler, persist errors are logged via log.error (backwards-compatible).
|
|
52
|
+
*/
|
|
53
|
+
onPersistError(handler: ((detail: PersistErrorDetail) => void) | null): void;
|
|
54
|
+
/**
|
|
55
|
+
* Emit a persist error to the registered handler, or fall back to log.error.
|
|
56
|
+
*/
|
|
57
|
+
emitPersistError(detail: PersistErrorDetail): void;
|
|
42
58
|
warn(message: string): void;
|
|
43
59
|
}
|
|
44
60
|
export declare const store: Store;
|
package/dist/main.js
CHANGED
|
@@ -41,6 +41,7 @@ export default class Orm {
|
|
|
41
41
|
options;
|
|
42
42
|
sqlDb;
|
|
43
43
|
db;
|
|
44
|
+
_persistErrorHandler = null;
|
|
44
45
|
constructor(options = {}) {
|
|
45
46
|
if (Orm.instance)
|
|
46
47
|
return Orm.instance;
|
|
@@ -168,6 +169,31 @@ export default class Orm {
|
|
|
168
169
|
const modelClassPrefix = kebabCaseToPascalCase(modelName);
|
|
169
170
|
return !!this.views[`${modelClassPrefix}View`];
|
|
170
171
|
}
|
|
172
|
+
/**
|
|
173
|
+
* Register a callback to be invoked when a fire-and-forget SQL persist fails.
|
|
174
|
+
* Without a handler, persist errors are logged via log.error (backwards-compatible).
|
|
175
|
+
*/
|
|
176
|
+
onPersistError(handler) {
|
|
177
|
+
this._persistErrorHandler = handler;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Emit a persist error to the registered handler, or fall back to log.error.
|
|
181
|
+
*/
|
|
182
|
+
emitPersistError(detail) {
|
|
183
|
+
const fallbackLog = () => log.error?.(`[ORM] Failed to persist ${detail.operation} for ${detail.modelName}:${String(detail.recordId)}: ${detail.error.message}`);
|
|
184
|
+
if (this._persistErrorHandler) {
|
|
185
|
+
try {
|
|
186
|
+
this._persistErrorHandler(detail);
|
|
187
|
+
}
|
|
188
|
+
catch (handlerError) {
|
|
189
|
+
fallbackLog();
|
|
190
|
+
log.error?.(`[ORM] onPersistError handler threw: ${handlerError instanceof Error ? handlerError.message : String(handlerError)}`);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
fallbackLog();
|
|
195
|
+
}
|
|
196
|
+
}
|
|
171
197
|
// Queue warnings to avoid the same error from being logged in the same iteration
|
|
172
198
|
warn(message) {
|
|
173
199
|
this.warnings.add(message);
|
package/dist/manage-record.js
CHANGED
|
@@ -2,7 +2,6 @@ import Orm, { store } from '@stonyx/orm';
|
|
|
2
2
|
import OrmRecord from './record.js';
|
|
3
3
|
import { getGlobalRegistry, getPendingRegistry, getPendingBelongsToRegistry, getBelongsToRegistry, getHasManyRegistry } from './relationships.js';
|
|
4
4
|
import { isOrmRecord } from './utils.js';
|
|
5
|
-
import log from 'stonyx/log';
|
|
6
5
|
const defaultOptions = {
|
|
7
6
|
isDbRecord: false,
|
|
8
7
|
serialize: true,
|
|
@@ -86,7 +85,12 @@ export function createRecord(modelName, rawData = {}, userOptions = {}) {
|
|
|
86
85
|
if (shouldPersist) {
|
|
87
86
|
const response = { data: { id: record.id } };
|
|
88
87
|
orm.sqlDb.persist('create', modelName, { rawData }, response).catch((err) => {
|
|
89
|
-
|
|
88
|
+
orm.emitPersistError({
|
|
89
|
+
operation: 'create',
|
|
90
|
+
modelName,
|
|
91
|
+
recordId: record.id,
|
|
92
|
+
error: err instanceof Error ? err : new Error(String(err)),
|
|
93
|
+
});
|
|
90
94
|
});
|
|
91
95
|
}
|
|
92
96
|
return record;
|
|
@@ -108,7 +112,12 @@ export function updateRecord(record, rawData, userOptions = {}) {
|
|
|
108
112
|
const shouldPersist = orm?.sqlDb && !options.isDbRecord && !userOptions._relationshipKey && !options._skipAutoPersist;
|
|
109
113
|
if (shouldPersist && modelName) {
|
|
110
114
|
orm.sqlDb.persist('update', modelName, { record, oldState }, {}).catch((err) => {
|
|
111
|
-
|
|
115
|
+
orm.emitPersistError({
|
|
116
|
+
operation: 'update',
|
|
117
|
+
modelName,
|
|
118
|
+
recordId: record.id,
|
|
119
|
+
error: err instanceof Error ? err : new Error(String(err)),
|
|
120
|
+
});
|
|
112
121
|
});
|
|
113
122
|
}
|
|
114
123
|
}
|
package/dist/store.js
CHANGED
|
@@ -115,7 +115,12 @@ export default class Store {
|
|
|
115
115
|
// Auto-persist delete to SQL
|
|
116
116
|
if (id && Orm.instance?.sqlDb) {
|
|
117
117
|
Orm.instance.sqlDb.persist('delete', key, { recordId: id }, {}).catch((err) => {
|
|
118
|
-
|
|
118
|
+
Orm.instance.emitPersistError({
|
|
119
|
+
operation: 'delete',
|
|
120
|
+
modelName: key,
|
|
121
|
+
recordId: id,
|
|
122
|
+
error: err instanceof Error ? err : new Error(String(err)),
|
|
123
|
+
});
|
|
119
124
|
});
|
|
120
125
|
}
|
|
121
126
|
if (id)
|
package/package.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"stonyx-async",
|
|
5
5
|
"stonyx-module"
|
|
6
6
|
],
|
|
7
|
-
"version": "0.3.2-beta.
|
|
7
|
+
"version": "0.3.2-beta.8",
|
|
8
8
|
"description": "",
|
|
9
9
|
"main": "dist/index.js",
|
|
10
10
|
"type": "module",
|
|
@@ -61,9 +61,9 @@
|
|
|
61
61
|
},
|
|
62
62
|
"homepage": "https://github.com/abofs/stonyx-orm#readme",
|
|
63
63
|
"dependencies": {
|
|
64
|
-
"@stonyx/cron": "0.2.1-beta.
|
|
64
|
+
"@stonyx/cron": "0.2.1-beta.38",
|
|
65
65
|
"@stonyx/events": "0.1.1-beta.9",
|
|
66
|
-
"stonyx": "0.2.3-beta.
|
|
66
|
+
"stonyx": "0.2.3-beta.53"
|
|
67
67
|
},
|
|
68
68
|
"peerDependencies": {
|
|
69
69
|
"@stonyx/rest-server": ">=0.2.1-beta.14",
|
|
@@ -83,17 +83,18 @@
|
|
|
83
83
|
},
|
|
84
84
|
"devDependencies": {
|
|
85
85
|
"@stonyx/rest-server": "0.2.1-beta.30",
|
|
86
|
-
"@stonyx/utils": "0.2.3-beta.
|
|
86
|
+
"@stonyx/utils": "0.2.3-beta.23",
|
|
87
87
|
"@types/node": "^25.6.0",
|
|
88
88
|
"mysql2": "^3.20.0",
|
|
89
89
|
"pg": "^8.20.0",
|
|
90
90
|
"qunit": "^2.24.1",
|
|
91
91
|
"sinon": "^21.0.0",
|
|
92
|
+
"tsx": "^4.21.0",
|
|
92
93
|
"typescript": "^5.8.3"
|
|
93
94
|
},
|
|
94
95
|
"scripts": {
|
|
95
96
|
"build": "tsc",
|
|
96
97
|
"build:test": "tsc -p tsconfig.test.json",
|
|
97
|
-
"test": "
|
|
98
|
+
"test": "pnpm build && NODE_ENV=test node --import tsx/esm --import ./test/setup.ts node_modules/qunit/bin/qunit.js 'test/**/*-test.ts'"
|
|
98
99
|
}
|
|
99
100
|
}
|
package/src/index.ts
CHANGED
|
@@ -26,6 +26,7 @@ import { count, avg, sum, min, max } from './aggregates.js';
|
|
|
26
26
|
|
|
27
27
|
export { default } from './main.js';
|
|
28
28
|
export { store, relationships } from './main.js';
|
|
29
|
+
export type { PersistErrorDetail } from './main.js';
|
|
29
30
|
export { Model, View, Serializer }; // base classes
|
|
30
31
|
export { attr, belongsTo, hasMany, createRecord, updateRecord }; // helpers
|
|
31
32
|
export { count, avg, sum, min, max }; // aggregate helpers
|
package/src/main.ts
CHANGED
|
@@ -49,6 +49,13 @@ const defaultOptions: OrmOptions = {
|
|
|
49
49
|
dbType: 'json'
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
export interface PersistErrorDetail {
|
|
53
|
+
operation: 'create' | 'update' | 'delete';
|
|
54
|
+
modelName: string;
|
|
55
|
+
recordId: unknown;
|
|
56
|
+
error: Error;
|
|
57
|
+
}
|
|
58
|
+
|
|
52
59
|
export default class Orm {
|
|
53
60
|
static initialized: boolean = false;
|
|
54
61
|
static relationships: Map<string, Map<string, unknown>> = new Map();
|
|
@@ -65,6 +72,8 @@ export default class Orm {
|
|
|
65
72
|
sqlDb?: SqlDb;
|
|
66
73
|
db?: OrmDB | SqlDb;
|
|
67
74
|
|
|
75
|
+
private _persistErrorHandler: ((detail: PersistErrorDetail) => void) | null = null;
|
|
76
|
+
|
|
68
77
|
constructor(options: OrmOptions = {}) {
|
|
69
78
|
if (Orm.instance) return Orm.instance;
|
|
70
79
|
|
|
@@ -214,6 +223,32 @@ export default class Orm {
|
|
|
214
223
|
return !!this.views[`${modelClassPrefix}View`];
|
|
215
224
|
}
|
|
216
225
|
|
|
226
|
+
/**
|
|
227
|
+
* Register a callback to be invoked when a fire-and-forget SQL persist fails.
|
|
228
|
+
* Without a handler, persist errors are logged via log.error (backwards-compatible).
|
|
229
|
+
*/
|
|
230
|
+
onPersistError(handler: ((detail: PersistErrorDetail) => void) | null): void {
|
|
231
|
+
this._persistErrorHandler = handler;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Emit a persist error to the registered handler, or fall back to log.error.
|
|
236
|
+
*/
|
|
237
|
+
emitPersistError(detail: PersistErrorDetail): void {
|
|
238
|
+
const fallbackLog = () => log.error?.(`[ORM] Failed to persist ${detail.operation} for ${detail.modelName}:${String(detail.recordId)}: ${detail.error.message}`);
|
|
239
|
+
|
|
240
|
+
if (this._persistErrorHandler) {
|
|
241
|
+
try {
|
|
242
|
+
this._persistErrorHandler(detail);
|
|
243
|
+
} catch (handlerError) {
|
|
244
|
+
fallbackLog();
|
|
245
|
+
log.error?.(`[ORM] onPersistError handler threw: ${handlerError instanceof Error ? handlerError.message : String(handlerError)}`);
|
|
246
|
+
}
|
|
247
|
+
} else {
|
|
248
|
+
fallbackLog();
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
217
252
|
// Queue warnings to avoid the same error from being logged in the same iteration
|
|
218
253
|
warn(message: string): void {
|
|
219
254
|
this.warnings.add(message);
|
package/src/manage-record.ts
CHANGED
|
@@ -3,7 +3,6 @@ import OrmRecord from './record.js';
|
|
|
3
3
|
import { getGlobalRegistry, getPendingRegistry, getPendingBelongsToRegistry, getBelongsToRegistry, getHasManyRegistry } from './relationships.js';
|
|
4
4
|
import type Serializer from './serializer.js';
|
|
5
5
|
import { isOrmRecord } from './utils.js';
|
|
6
|
-
import log from 'stonyx/log';
|
|
7
6
|
|
|
8
7
|
interface CreateRecordOptions {
|
|
9
8
|
isDbRecord?: boolean;
|
|
@@ -120,7 +119,12 @@ export function createRecord(modelName: string, rawData: { [key: string]: unknow
|
|
|
120
119
|
if (shouldPersist) {
|
|
121
120
|
const response = { data: { id: record.id } };
|
|
122
121
|
orm!.sqlDb!.persist('create', modelName, { rawData }, response).catch((err: unknown) => {
|
|
123
|
-
|
|
122
|
+
orm!.emitPersistError({
|
|
123
|
+
operation: 'create',
|
|
124
|
+
modelName,
|
|
125
|
+
recordId: record.id,
|
|
126
|
+
error: err instanceof Error ? err : new Error(String(err)),
|
|
127
|
+
});
|
|
124
128
|
});
|
|
125
129
|
}
|
|
126
130
|
|
|
@@ -148,7 +152,12 @@ export function updateRecord(record: OrmRecord, rawData: unknown, userOptions: C
|
|
|
148
152
|
const shouldPersist = orm?.sqlDb && !options.isDbRecord && !userOptions._relationshipKey && !options._skipAutoPersist;
|
|
149
153
|
if (shouldPersist && modelName) {
|
|
150
154
|
orm!.sqlDb!.persist('update', modelName, { record, oldState }, {}).catch((err: unknown) => {
|
|
151
|
-
|
|
155
|
+
orm!.emitPersistError({
|
|
156
|
+
operation: 'update',
|
|
157
|
+
modelName,
|
|
158
|
+
recordId: record.id,
|
|
159
|
+
error: err instanceof Error ? err : new Error(String(err)),
|
|
160
|
+
});
|
|
152
161
|
});
|
|
153
162
|
}
|
|
154
163
|
}
|
package/src/store.ts
CHANGED
|
@@ -179,7 +179,12 @@ export default class Store {
|
|
|
179
179
|
// Auto-persist delete to SQL
|
|
180
180
|
if (id && Orm.instance?.sqlDb) {
|
|
181
181
|
Orm.instance.sqlDb.persist('delete', key, { recordId: id }, {}).catch((err: unknown) => {
|
|
182
|
-
|
|
182
|
+
Orm.instance.emitPersistError({
|
|
183
|
+
operation: 'delete',
|
|
184
|
+
modelName: key,
|
|
185
|
+
recordId: id,
|
|
186
|
+
error: err instanceof Error ? err : new Error(String(err)),
|
|
187
|
+
});
|
|
183
188
|
});
|
|
184
189
|
}
|
|
185
190
|
|