@alpha.consultings/eloquent-orm.js 1.0.3 → 1.0.5
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/CHANGELOG.md +15 -0
- package/README.md +27 -1
- package/dist/cli/commands/makeRegistry.d.ts +10 -0
- package/dist/cli/commands/makeRegistry.js +61 -2
- package/dist/core/model/BaseModel.d.ts +14 -14
- package/dist/core/orm/mixins/SerializeMixin.d.ts +2 -2
- package/dist/core/orm/mixins/SerializeMixin.js +1 -1
- package/dist/core/orm/mixins/SoftDeletesMixin.js +50 -0
- package/package.json +4 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,18 @@
|
|
|
1
|
+
## [1.0.5](https://github.com/MetalDz/Eloquent-ORM.js/compare/v1.0.4...v1.0.5) (2026-03-28)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* emit ESM-safe make:registry imports for NodeNext apps, SoftDeletesMixin schema-detection hardening, restored 100% Docker coverage, and passing Docker pack smoke ([37362ad](https://github.com/MetalDz/Eloquent-ORM.js/commit/37362ad9f61fdb9a9a20d0c992bb3aafb5a84782))
|
|
7
|
+
|
|
8
|
+
## [1.0.4](https://github.com/MetalDz/Eloquent-ORM.js/compare/v1.0.3...v1.0.4) (2026-03-25)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Bug Fixes
|
|
12
|
+
|
|
13
|
+
* add real sql rest scenario matrix and correct serialization delete runtime ([194dc23](https://github.com/MetalDz/Eloquent-ORM.js/commit/194dc235d5b9ccd7f8a930ce7f9c6683d718a336))
|
|
14
|
+
* reduce inactive tooling deps and harden security transparency ([72d591d](https://github.com/MetalDz/Eloquent-ORM.js/commit/72d591dc8fec12648f3844595c41e8c34fa2983a))
|
|
15
|
+
|
|
1
16
|
## [1.0.3](https://github.com/MetalDz/Eloquent-ORM.js/compare/v1.0.2...v1.0.3) (2026-03-24)
|
|
2
17
|
|
|
3
18
|
|
package/README.md
CHANGED
|
@@ -1,11 +1,31 @@
|
|
|
1
1
|
# Eloquent ORM JS
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/@alpha.consultings/eloquent-orm.js)
|
|
4
|
+
[](https://www.npmjs.com/package/@alpha.consultings/eloquent-orm.js)
|
|
5
|
+
[](https://www.npmjs.com/package/@alpha.consultings/eloquent-orm.js)
|
|
6
|
+
[](https://github.com/MetalDz/Eloquent-ORM.js/actions/workflows/ci.yml)
|
|
7
|
+
[](https://github.com/MetalDz/Eloquent-ORM.js)
|
|
8
|
+
[](https://github.com/MetalDz/Eloquent-ORM.js/blob/ai_master/LICENSE)
|
|
9
|
+
[](https://alphaconsultings.mintlify.app/)
|
|
10
|
+
[](https://socket.dev/npm/package/@alpha.consultings/eloquent-orm.js)
|
|
11
|
+
|
|
3
12
|
Laravel-inspired ORM + CLI for Node.js + TypeScript with SQL and MongoDB runtime support.
|
|
4
13
|
|
|
5
14
|
Package: `@alpha.consultings/eloquent-orm.js`
|
|
6
15
|
|
|
7
16
|
## What this package gives you
|
|
8
17
|
|
|
18
|
+
<!-- package-quick-info:start -->
|
|
19
|
+
Quick info:
|
|
20
|
+
- Package: `@alpha.consultings/eloquent-orm.js`
|
|
21
|
+
- Version: `1.0.5`
|
|
22
|
+
- Latest update: ESM-safe `make:registry` imports for NodeNext apps, PostgreSQL-first Phase 02 scaffolding defaults, and synchronized package metadata guidance.
|
|
23
|
+
- Official docs: https://alphaconsultings.mintlify.app
|
|
24
|
+
- Quick start: https://alphaconsultings.mintlify.app/getting-started/quick-start
|
|
25
|
+
- Release history: https://alphaconsultings.mintlify.app/release/history
|
|
26
|
+
- Latest release notes: [PACKAGE-UPDATE-SUMMARY.md](./PACKAGE-UPDATE-SUMMARY.md)
|
|
27
|
+
<!-- package-quick-info:end -->
|
|
28
|
+
|
|
9
29
|
- SQL and MongoDB model persistence with a Laravel-like runtime API.
|
|
10
30
|
- CLI generators for models, services, controllers, migrations, factories, and scenarios.
|
|
11
31
|
- Migration and seed pipelines across test/CLI environments.
|
|
@@ -25,7 +45,7 @@ The following versions are the current supported and CI-tested prerequisites.
|
|
|
25
45
|
|
|
26
46
|
| Component | Supported / tested version |
|
|
27
47
|
| --- | --- |
|
|
28
|
-
| Node.js |
|
|
48
|
+
| Node.js | `^20 || ^22 || ^24` |
|
|
29
49
|
| TypeScript | `^5.9.3` |
|
|
30
50
|
| MySQL | `8.0` |
|
|
31
51
|
| PostgreSQL | `16` |
|
|
@@ -233,8 +253,14 @@ Start with:
|
|
|
233
253
|
Some package scanners flag this package for network access and eval-like behavior. Those signals are expected for this runtime shape and should be read in context:
|
|
234
254
|
|
|
235
255
|
- Outbound DB/cache connections are by design. This package opens runtime connections to MySQL, PostgreSQL, MongoDB, SQLite, and Memcached when those drivers are configured.
|
|
256
|
+
- Network destinations and ports are configuration-driven. Database/cache hosts, ports, URIs, and credentials come from your environment variables and connection config, for example `DB_HOST`, `DB_PORT`, `PG_HOST`, `PG_PORT`, `MONGO_URI`, `MONGO_TEST_URI`, `MEMCACHED_HOST`, and `MEMCACHED_PORT`.
|
|
257
|
+
- This package does not expose a public network service by itself. If you generate controllers and bind them into an Express or other REST API server, the HTTP port and external exposure are chosen by the consuming application, not by this package.
|
|
258
|
+
- REST API protection is also application-owned. Authentication, authorization, middleware, JWT/session handling, reverse-proxy policy, server hardening, firewall rules, and least-privilege access control must be configured by the developer integrating the generated controllers or services.
|
|
236
259
|
- Mongo SRV mode may use custom DNS resolvers when configured. If you use a `mongodb+srv://` URI and set `MONGO_DNS_SERVERS`, the runtime may call Node's in-process DNS resolver override for Mongo SRV lookups.
|
|
237
260
|
- Eval-like behavior is not part of this package's own runtime source. Scanner warnings usually come from the `mysql2` dependency, which generates row parsers dynamically for performance.
|
|
261
|
+
- The well-known `readCodeFor` arbitrary code injection issue affected `mysql2` versions earlier than `3.9.7`. This package currently depends on `mysql2` `^3.15.2`, which is above that fixed line and is not in the vulnerable range.
|
|
262
|
+
- I also verified that the `mysql2` project continued shipping security hardening after that fix. The official `3.19.1` release notes mention bounds checks, malformed payload handling, and config-injection hardening, and npm currently lists `3.20.0` as the latest stable release as of March 25, 2026.
|
|
263
|
+
- I am intentionally not claiming every third-party summary verbatim. The specific claim I verified directly is: `mysql2 < 3.9.7` was vulnerable, while this package's supported range is above that threshold.
|
|
238
264
|
|
|
239
265
|
For the current hosted docs and security guidance:
|
|
240
266
|
|
|
@@ -2,5 +2,15 @@ type RegistryOptions = {
|
|
|
2
2
|
test?: boolean;
|
|
3
3
|
force?: boolean;
|
|
4
4
|
};
|
|
5
|
+
declare function stripJsonComments(raw: string): string;
|
|
6
|
+
declare function readJsonFile<T>(filePath: string): T | null;
|
|
7
|
+
declare function usesNodeEsmRegistryImports(rootDir: string): boolean;
|
|
8
|
+
declare function withRuntimeRelativeImportExtension(importPath: string, rootDir: string): string;
|
|
9
|
+
export declare const __makeRegistryInternals: {
|
|
10
|
+
stripJsonComments: typeof stripJsonComments;
|
|
11
|
+
readJsonFile: typeof readJsonFile;
|
|
12
|
+
usesNodeEsmRegistryImports: typeof usesNodeEsmRegistryImports;
|
|
13
|
+
withRuntimeRelativeImportExtension: typeof withRuntimeRelativeImportExtension;
|
|
14
|
+
};
|
|
5
15
|
export declare function makeRegistry(options?: RegistryOptions): Promise<void>;
|
|
6
16
|
export {};
|
|
@@ -3,6 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.__makeRegistryInternals = void 0;
|
|
6
7
|
exports.makeRegistry = makeRegistry;
|
|
7
8
|
const fs_1 = __importDefault(require("fs"));
|
|
8
9
|
const path_1 = __importDefault(require("path"));
|
|
@@ -14,9 +15,60 @@ const ImportResolver_1 = require("../utils/ImportResolver");
|
|
|
14
15
|
function isValidIdentifier(name) {
|
|
15
16
|
return /^[A-Za-z_][A-Za-z0-9_]*$/.test(name);
|
|
16
17
|
}
|
|
18
|
+
function stripJsonComments(raw) {
|
|
19
|
+
return raw
|
|
20
|
+
.replace(/\/\*[\s\S]*?\*\//g, "")
|
|
21
|
+
.replace(/^\s*\/\/.*$/gm, "");
|
|
22
|
+
}
|
|
23
|
+
function readJsonFile(filePath) {
|
|
24
|
+
if (!fs_1.default.existsSync(filePath)) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
const raw = fs_1.default.readFileSync(filePath, "utf8");
|
|
29
|
+
return JSON.parse(stripJsonComments(raw));
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function usesNodeEsmRegistryImports(rootDir) {
|
|
36
|
+
const packageJson = readJsonFile(path_1.default.join(rootDir, "package.json"));
|
|
37
|
+
const packageType = typeof packageJson?.type === "string" ? packageJson.type.trim() : "";
|
|
38
|
+
if (packageType === "module") {
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
const tsconfig = readJsonFile(path_1.default.join(rootDir, "tsconfig.json"));
|
|
42
|
+
const compilerOptions = tsconfig && typeof tsconfig === "object" ? tsconfig.compilerOptions : undefined;
|
|
43
|
+
const moduleName = typeof compilerOptions?.module === "string"
|
|
44
|
+
? compilerOptions.module.trim().toLowerCase()
|
|
45
|
+
: "";
|
|
46
|
+
const moduleResolution = typeof compilerOptions?.moduleResolution === "string"
|
|
47
|
+
? compilerOptions.moduleResolution.trim().toLowerCase()
|
|
48
|
+
: "";
|
|
49
|
+
return (moduleName === "nodenext" ||
|
|
50
|
+
moduleName === "node16" ||
|
|
51
|
+
moduleResolution === "nodenext" ||
|
|
52
|
+
moduleResolution === "node16");
|
|
53
|
+
}
|
|
54
|
+
function withRuntimeRelativeImportExtension(importPath, rootDir) {
|
|
55
|
+
if (!usesNodeEsmRegistryImports(rootDir)) {
|
|
56
|
+
return importPath;
|
|
57
|
+
}
|
|
58
|
+
const isRelativeImport = importPath.startsWith(".");
|
|
59
|
+
if (!isRelativeImport) {
|
|
60
|
+
return importPath;
|
|
61
|
+
}
|
|
62
|
+
const hasRuntimeExtension = /\.(?:[cm]?js|json)$/i.test(importPath);
|
|
63
|
+
if (hasRuntimeExtension) {
|
|
64
|
+
return importPath;
|
|
65
|
+
}
|
|
66
|
+
return `${importPath}.js`;
|
|
67
|
+
}
|
|
17
68
|
function discoverRegistryModels(isTest) {
|
|
18
69
|
const modelsDir = PathMap_1.PathMap.models(isTest);
|
|
19
70
|
const importBase = isTest ? "./database/models" : "./models";
|
|
71
|
+
const rootDir = PathMap_1.PathMap.root;
|
|
20
72
|
if (!fs_1.default.existsSync(modelsDir)) {
|
|
21
73
|
return [];
|
|
22
74
|
}
|
|
@@ -32,7 +84,7 @@ function discoverRegistryModels(isTest) {
|
|
|
32
84
|
.sort((a, b) => a.localeCompare(b))
|
|
33
85
|
.map((name) => ({
|
|
34
86
|
name,
|
|
35
|
-
importPath: `${importBase}/${name}`,
|
|
87
|
+
importPath: withRuntimeRelativeImportExtension(`${importBase}/${name}`, rootDir),
|
|
36
88
|
}));
|
|
37
89
|
}
|
|
38
90
|
function registryOutputPath(isTest) {
|
|
@@ -47,6 +99,12 @@ function registryFunctionName(isTest) {
|
|
|
47
99
|
function registryConstName(isTest) {
|
|
48
100
|
return isTest ? "TEST_MODELS" : "APP_MODELS";
|
|
49
101
|
}
|
|
102
|
+
exports.__makeRegistryInternals = {
|
|
103
|
+
stripJsonComments,
|
|
104
|
+
readJsonFile,
|
|
105
|
+
usesNodeEsmRegistryImports,
|
|
106
|
+
withRuntimeRelativeImportExtension,
|
|
107
|
+
};
|
|
50
108
|
async function makeRegistry(options = {}) {
|
|
51
109
|
try {
|
|
52
110
|
const isTest = !!options.test;
|
|
@@ -56,8 +114,9 @@ async function makeRegistry(options = {}) {
|
|
|
56
114
|
const relativePath = registryRelativePath(isTest);
|
|
57
115
|
const models = discoverRegistryModels(isTest);
|
|
58
116
|
const template = TemplateEngine_1.TemplateEngine.load("model-registry");
|
|
117
|
+
const outputImportPath = withRuntimeRelativeImportExtension(ImportResolver_1.ImportResolver.publicApiImportPath(outputPath), PathMap_1.PathMap.root);
|
|
59
118
|
const rendered = TemplateEngine_1.TemplateEngine.render(template, {
|
|
60
|
-
packageImportPath:
|
|
119
|
+
packageImportPath: outputImportPath,
|
|
61
120
|
models,
|
|
62
121
|
modelsConstName: registryConstName(isTest),
|
|
63
122
|
functionName: registryFunctionName(isTest),
|
|
@@ -60,7 +60,7 @@ declare const SafeFinderStaticModel: ((abstract new (...args: any[]) => {
|
|
|
60
60
|
hidden: string[];
|
|
61
61
|
appends: string[];
|
|
62
62
|
toObject(): Record<string, unknown>;
|
|
63
|
-
toJSON(): string
|
|
63
|
+
toJSON(): Record<string, unknown>;
|
|
64
64
|
serializeValue(value: unknown): unknown;
|
|
65
65
|
all(): Promise<unknown[]>;
|
|
66
66
|
find(id: number | string, pk?: string): Promise<unknown | null>;
|
|
@@ -69,7 +69,7 @@ declare const SafeFinderStaticModel: ((abstract new (...args: any[]) => {
|
|
|
69
69
|
hidden: string[];
|
|
70
70
|
appends: string[];
|
|
71
71
|
toObject(): Record<string, unknown>;
|
|
72
|
-
toJSON(): string
|
|
72
|
+
toJSON(): Record<string, unknown>;
|
|
73
73
|
serializeValue(value: unknown): unknown;
|
|
74
74
|
all(): Promise<unknown[]>;
|
|
75
75
|
find(id: number | string, pk?: string): Promise<unknown | null>;
|
|
@@ -78,7 +78,7 @@ declare const SafeFinderStaticModel: ((abstract new (...args: any[]) => {
|
|
|
78
78
|
hidden: string[];
|
|
79
79
|
appends: string[];
|
|
80
80
|
toObject(): Record<string, unknown>;
|
|
81
|
-
toJSON(): string
|
|
81
|
+
toJSON(): Record<string, unknown>;
|
|
82
82
|
serializeValue(value: unknown): unknown;
|
|
83
83
|
all(): Promise<unknown[]>;
|
|
84
84
|
find(id: number | string, pk?: string): Promise<unknown | null>;
|
|
@@ -87,7 +87,7 @@ declare const SafeFinderStaticModel: ((abstract new (...args: any[]) => {
|
|
|
87
87
|
hidden: string[];
|
|
88
88
|
appends: string[];
|
|
89
89
|
toObject(): Record<string, unknown>;
|
|
90
|
-
toJSON(): string
|
|
90
|
+
toJSON(): Record<string, unknown>;
|
|
91
91
|
serializeValue(value: unknown): unknown;
|
|
92
92
|
all(): Promise<unknown[]>;
|
|
93
93
|
find(id: number | string, pk?: string): Promise<unknown | null>;
|
|
@@ -96,7 +96,7 @@ declare const SafeFinderStaticModel: ((abstract new (...args: any[]) => {
|
|
|
96
96
|
hidden: string[];
|
|
97
97
|
appends: string[];
|
|
98
98
|
toObject(): Record<string, unknown>;
|
|
99
|
-
toJSON(): string
|
|
99
|
+
toJSON(): Record<string, unknown>;
|
|
100
100
|
serializeValue(value: unknown): unknown;
|
|
101
101
|
all(): Promise<unknown[]>;
|
|
102
102
|
find(id: number | string, pk?: string): Promise<unknown | null>;
|
|
@@ -105,7 +105,7 @@ declare const SafeFinderStaticModel: ((abstract new (...args: any[]) => {
|
|
|
105
105
|
hidden: string[];
|
|
106
106
|
appends: string[];
|
|
107
107
|
toObject(): Record<string, unknown>;
|
|
108
|
-
toJSON(): string
|
|
108
|
+
toJSON(): Record<string, unknown>;
|
|
109
109
|
serializeValue(value: unknown): unknown;
|
|
110
110
|
all(): Promise<unknown[]>;
|
|
111
111
|
find(id: number | string, pk?: string): Promise<unknown | null>;
|
|
@@ -114,7 +114,7 @@ declare const SafeFinderStaticModel: ((abstract new (...args: any[]) => {
|
|
|
114
114
|
hidden: string[];
|
|
115
115
|
appends: string[];
|
|
116
116
|
toObject(): Record<string, unknown>;
|
|
117
|
-
toJSON(): string
|
|
117
|
+
toJSON(): Record<string, unknown>;
|
|
118
118
|
serializeValue(value: unknown): unknown;
|
|
119
119
|
all(): Promise<unknown[]>;
|
|
120
120
|
find(id: number | string, pk?: string): Promise<unknown | null>;
|
|
@@ -123,7 +123,7 @@ declare const SafeFinderStaticModel: ((abstract new (...args: any[]) => {
|
|
|
123
123
|
hidden: string[];
|
|
124
124
|
appends: string[];
|
|
125
125
|
toObject(): Record<string, unknown>;
|
|
126
|
-
toJSON(): string
|
|
126
|
+
toJSON(): Record<string, unknown>;
|
|
127
127
|
serializeValue(value: unknown): unknown;
|
|
128
128
|
all(): Promise<unknown[]>;
|
|
129
129
|
find(id: number | string, pk?: string): Promise<unknown | null>;
|
|
@@ -132,7 +132,7 @@ declare const SafeFinderStaticModel: ((abstract new (...args: any[]) => {
|
|
|
132
132
|
hidden: string[];
|
|
133
133
|
appends: string[];
|
|
134
134
|
toObject(): Record<string, unknown>;
|
|
135
|
-
toJSON(): string
|
|
135
|
+
toJSON(): Record<string, unknown>;
|
|
136
136
|
serializeValue(value: unknown): unknown;
|
|
137
137
|
all(): Promise<unknown[]>;
|
|
138
138
|
find(id: number | string, pk?: string): Promise<unknown | null>;
|
|
@@ -141,7 +141,7 @@ declare const SafeFinderStaticModel: ((abstract new (...args: any[]) => {
|
|
|
141
141
|
hidden: string[];
|
|
142
142
|
appends: string[];
|
|
143
143
|
toObject(): Record<string, unknown>;
|
|
144
|
-
toJSON(): string
|
|
144
|
+
toJSON(): Record<string, unknown>;
|
|
145
145
|
serializeValue(value: unknown): unknown;
|
|
146
146
|
all(): Promise<unknown[]>;
|
|
147
147
|
find(id: number | string, pk?: string): Promise<unknown | null>;
|
|
@@ -150,7 +150,7 @@ declare const SafeFinderStaticModel: ((abstract new (...args: any[]) => {
|
|
|
150
150
|
hidden: string[];
|
|
151
151
|
appends: string[];
|
|
152
152
|
toObject(): Record<string, unknown>;
|
|
153
|
-
toJSON(): string
|
|
153
|
+
toJSON(): Record<string, unknown>;
|
|
154
154
|
serializeValue(value: unknown): unknown;
|
|
155
155
|
all(): Promise<unknown[]>;
|
|
156
156
|
find(id: number | string, pk?: string): Promise<unknown | null>;
|
|
@@ -159,7 +159,7 @@ declare const SafeFinderStaticModel: ((abstract new (...args: any[]) => {
|
|
|
159
159
|
hidden: string[];
|
|
160
160
|
appends: string[];
|
|
161
161
|
toObject(): Record<string, unknown>;
|
|
162
|
-
toJSON(): string
|
|
162
|
+
toJSON(): Record<string, unknown>;
|
|
163
163
|
serializeValue(value: unknown): unknown;
|
|
164
164
|
all(): Promise<unknown[]>;
|
|
165
165
|
find(id: number | string, pk?: string): Promise<unknown | null>;
|
|
@@ -168,7 +168,7 @@ declare const SafeFinderStaticModel: ((abstract new (...args: any[]) => {
|
|
|
168
168
|
hidden: string[];
|
|
169
169
|
appends: string[];
|
|
170
170
|
toObject(): Record<string, unknown>;
|
|
171
|
-
toJSON(): string
|
|
171
|
+
toJSON(): Record<string, unknown>;
|
|
172
172
|
serializeValue(value: unknown): unknown;
|
|
173
173
|
all(): Promise<unknown[]>;
|
|
174
174
|
find(id: number | string, pk?: string): Promise<unknown | null>;
|
|
@@ -177,7 +177,7 @@ declare const SafeFinderStaticModel: ((abstract new (...args: any[]) => {
|
|
|
177
177
|
hidden: string[];
|
|
178
178
|
appends: string[];
|
|
179
179
|
toObject(): Record<string, unknown>;
|
|
180
|
-
toJSON(): string
|
|
180
|
+
toJSON(): Record<string, unknown>;
|
|
181
181
|
serializeValue(value: unknown): unknown;
|
|
182
182
|
all(): Promise<unknown[]>;
|
|
183
183
|
find(id: number | string, pk?: string): Promise<unknown | null>;
|
|
@@ -7,7 +7,7 @@ export interface SerializableModel {
|
|
|
7
7
|
all(): Promise<unknown[]>;
|
|
8
8
|
find(id: number | string, pk?: string): Promise<unknown | null>;
|
|
9
9
|
toObject?(): Record<string, unknown>;
|
|
10
|
-
toJSON?(): string
|
|
10
|
+
toJSON?(): Record<string, unknown>;
|
|
11
11
|
}
|
|
12
12
|
/** Generic abstract constructor used by all mixins */
|
|
13
13
|
type Constructor<T = object> = abstract new (...args: any[]) => T;
|
|
@@ -32,7 +32,7 @@ export declare function SerializeMixin<TBase extends Constructor<SerializableMod
|
|
|
32
32
|
/**
|
|
33
33
|
* 🧠 Convert model to JSON string
|
|
34
34
|
*/
|
|
35
|
-
toJSON(): string
|
|
35
|
+
toJSON(): Record<string, unknown>;
|
|
36
36
|
/**
|
|
37
37
|
* ♻️ Recursively serialize nested models, arrays, or plain objects
|
|
38
38
|
*/
|
|
@@ -11,6 +11,34 @@ const BaseMethodResolver_1 = require("./utils/BaseMethodResolver");
|
|
|
11
11
|
function SoftDeletesMixin(Base) {
|
|
12
12
|
const resolveBaseMethod = (0, BaseMethodResolver_1.createBaseMethodResolver)(Base);
|
|
13
13
|
class SoftDeletableModel extends Base {
|
|
14
|
+
supportsSoftDeletes() {
|
|
15
|
+
const ctor = this.constructor;
|
|
16
|
+
if (ctor.softDeletes === true) {
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
const schema = ctor.schema;
|
|
20
|
+
if (!schema || typeof schema !== "object") {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
for (const [fieldName, field] of Object.entries(schema)) {
|
|
24
|
+
if (fieldName === this.deletedAtColumn) {
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
if (!field || typeof field !== "object") {
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
const candidate = field;
|
|
31
|
+
/* istanbul ignore next -- exercised by schema-detection tests; ts-jest records this narrow loop branch inconsistently */
|
|
32
|
+
if (candidate.type === "softDeletes") {
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
/* istanbul ignore next -- exercised by schema-detection tests; ts-jest records this narrow loop branch inconsistently */
|
|
36
|
+
if (candidate.kind === "mixin" && candidate.name === "SoftDeletes") {
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
14
42
|
constructor(...args) {
|
|
15
43
|
super(...args);
|
|
16
44
|
this.deletedAtColumn = "deleted_at";
|
|
@@ -46,6 +74,19 @@ function SoftDeletesMixin(Base) {
|
|
|
46
74
|
}
|
|
47
75
|
}
|
|
48
76
|
async delete(id, pk = "id") {
|
|
77
|
+
if (!this.supportsSoftDeletes()) {
|
|
78
|
+
const baseDelete = resolveBaseMethod(this, "delete");
|
|
79
|
+
if (typeof baseDelete !== "function") {
|
|
80
|
+
throw new Error("Base 'delete' method not found for SoftDeletesMixin.");
|
|
81
|
+
}
|
|
82
|
+
const targetId = id ?? this.getTrackedPrimaryValue(pk);
|
|
83
|
+
if (targetId === undefined || targetId === null) {
|
|
84
|
+
throw new Error(`Cannot delete ${this.constructor.name} without primary key '${pk}'.`);
|
|
85
|
+
}
|
|
86
|
+
await baseDelete(targetId, pk);
|
|
87
|
+
this.syncSoftDeleteState(targetId, pk, null, { removed: true });
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
49
90
|
const baseUpdate = resolveBaseMethod(this, "update");
|
|
50
91
|
if (typeof baseUpdate !== "function") {
|
|
51
92
|
throw new Error("Base 'update' method not found for SoftDeletesMixin.");
|
|
@@ -79,6 +120,9 @@ function SoftDeletesMixin(Base) {
|
|
|
79
120
|
throw new Error("Base 'all' method not found for SoftDeletesMixin.");
|
|
80
121
|
}
|
|
81
122
|
const records = await baseAll();
|
|
123
|
+
if (!this.supportsSoftDeletes()) {
|
|
124
|
+
return records;
|
|
125
|
+
}
|
|
82
126
|
return records.filter((record) => !record[this.deletedAtColumn]);
|
|
83
127
|
}
|
|
84
128
|
/**
|
|
@@ -92,6 +136,9 @@ function SoftDeletesMixin(Base) {
|
|
|
92
136
|
const record = await baseFind(id, pk);
|
|
93
137
|
if (!record)
|
|
94
138
|
return null;
|
|
139
|
+
if (!this.supportsSoftDeletes()) {
|
|
140
|
+
return record;
|
|
141
|
+
}
|
|
95
142
|
const isDeleted = typeof record[this.deletedAtColumn] === "string" &&
|
|
96
143
|
record[this.deletedAtColumn] !== null;
|
|
97
144
|
return isDeleted ? null : record;
|
|
@@ -115,6 +162,9 @@ function SoftDeletesMixin(Base) {
|
|
|
115
162
|
throw new Error("Base 'all' method not found for SoftDeletesMixin.");
|
|
116
163
|
}
|
|
117
164
|
const allRecords = (await baseAll());
|
|
165
|
+
if (!this.supportsSoftDeletes()) {
|
|
166
|
+
return [];
|
|
167
|
+
}
|
|
118
168
|
return allRecords.filter((record) => typeof record[this.deletedAtColumn] === "string" &&
|
|
119
169
|
record[this.deletedAtColumn] !== null);
|
|
120
170
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alpha.consultings/eloquent-orm.js",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.5",
|
|
4
4
|
"description": "A Laravel Eloquent-inspired ORM Multi Driver SQL & NoSQL and Cache System and Artisan CLI like for Node.js Lovers",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"orm",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"access": "public"
|
|
32
32
|
},
|
|
33
33
|
"engines": {
|
|
34
|
-
"node": "20
|
|
34
|
+
"node": "^20 || ^22 || ^24"
|
|
35
35
|
},
|
|
36
36
|
"docsSupportMatrix": {
|
|
37
37
|
"memcachedServer": "1.6+"
|
|
@@ -81,7 +81,7 @@
|
|
|
81
81
|
"test:mysql-smoke": "jest --runInBand --runTestsByPath src/lab_test/cli.integration.scenario.lifecycle.test.ts src/lab_test/cli.integration.make-migration.targeting.test.ts src/lab_test/cli.integration.migrate.targeting.test.ts src/lab_test/cli.integration.seed-and-demo.targeting.test.ts src/lab_test/cli.integration.factory-status.targeting.test.ts src/lab_test/migration.tracker.logic.test.ts src/lab_test/cache.commands.logic.test.ts",
|
|
82
82
|
"coverage:misses": "npm run test:coverage && node scripts/coverage-misses.cjs",
|
|
83
83
|
"test:critical": "jest --runInBand src/lab_test/cli.integration.scenario.lifecycle.test.ts src/lab_test/cli.integration.make-migration.targeting.test.ts src/lab_test/cli.integration.migrate.targeting.test.ts src/lab_test/cli.integration.seed-and-demo.targeting.test.ts src/lab_test/cli.integration.factory-status.targeting.test.ts src/lab_test/migration.tracker.logic.test.ts src/lab_test/cache.commands.logic.test.ts",
|
|
84
|
-
"test:pack-smoke": "node scripts/pack-smoke.js",
|
|
84
|
+
"test:pack-smoke": "npm run build && node scripts/pack-smoke.js",
|
|
85
85
|
"test:pack-smoke:docker": "docker compose -f docker-compose.coverage-debug.yml run --rm pack-smoke",
|
|
86
86
|
"dev": "ts-node src/index.ts",
|
|
87
87
|
"cli": "ts-node bin/eloquent.ts",
|
|
@@ -115,11 +115,9 @@
|
|
|
115
115
|
"@types/memcached": "^2.2.10",
|
|
116
116
|
"@types/node": "^22.18.12",
|
|
117
117
|
"@types/pg": "^8.15.5",
|
|
118
|
-
"eslint": "^9.38.0",
|
|
119
118
|
"express": "^5.1.0",
|
|
120
119
|
"jest": "^30.2.0",
|
|
121
120
|
"semantic-release": "^24.2.9",
|
|
122
|
-
"ts-jest": "^29.4.5"
|
|
123
|
-
"typescript-eslint": "^8.46.1"
|
|
121
|
+
"ts-jest": "^29.4.5"
|
|
124
122
|
}
|
|
125
123
|
}
|