@sentzunhat/zacatl 0.0.20 → 0.0.22
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 +20 -2
- package/build/index.d.ts +1 -0
- package/build/index.d.ts.map +1 -1
- package/build/index.js +1 -0
- package/build/index.js.map +1 -1
- package/build/orm-exports.d.ts +5 -0
- package/build/orm-exports.d.ts.map +1 -0
- package/build/orm-exports.js +3 -0
- package/build/orm-exports.js.map +1 -0
- package/build/service/architecture/infrastructure/orm/adapter-loader.d.ts +5 -0
- package/build/service/architecture/infrastructure/orm/adapter-loader.d.ts.map +1 -0
- package/build/service/architecture/infrastructure/orm/adapter-loader.js +27 -0
- package/build/service/architecture/infrastructure/orm/adapter-loader.js.map +1 -0
- package/build/service/architecture/infrastructure/orm/adapters/mongoose-adapter.d.ts +1 -1
- package/build/service/architecture/infrastructure/orm/adapters/mongoose-adapter.d.ts.map +1 -1
- package/build/service/architecture/infrastructure/orm/adapters/mongoose-adapter.js +2 -4
- package/build/service/architecture/infrastructure/orm/adapters/mongoose-adapter.js.map +1 -1
- package/build/service/architecture/infrastructure/repositories/abstract.d.ts +8 -3
- package/build/service/architecture/infrastructure/repositories/abstract.d.ts.map +1 -1
- package/build/service/architecture/infrastructure/repositories/abstract.js +39 -9
- package/build/service/architecture/infrastructure/repositories/abstract.js.map +1 -1
- package/build/test/tsconfig.tsbuildinfo +1 -1
- package/package.json +53 -8
- package/src/index.ts +3 -0
- package/src/orm-exports.ts +23 -0
- package/src/service/architecture/infrastructure/orm/adapter-loader.ts +54 -0
- package/src/service/architecture/infrastructure/orm/adapters/mongoose-adapter.ts +10 -11
- package/src/service/architecture/infrastructure/repositories/abstract.ts +76 -22
package/package.json
CHANGED
|
@@ -4,8 +4,36 @@
|
|
|
4
4
|
"main": "build/index.js",
|
|
5
5
|
"module": "build/index.js",
|
|
6
6
|
"types": "build/index.d.ts",
|
|
7
|
-
"version": "0.0.
|
|
7
|
+
"version": "0.0.22",
|
|
8
8
|
"packageManager": "npm@10.9.0",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": "./build/index.js",
|
|
12
|
+
"types": "./build/index.d.ts"
|
|
13
|
+
},
|
|
14
|
+
"./infrastructure": {
|
|
15
|
+
"import": "./build/service/architecture/infrastructure/repositories/abstract.js",
|
|
16
|
+
"types": "./build/service/architecture/infrastructure/repositories/abstract.d.ts"
|
|
17
|
+
},
|
|
18
|
+
"./domain": {
|
|
19
|
+
"import": "./build/service/architecture/domain/index.js",
|
|
20
|
+
"types": "./build/service/architecture/domain/index.d.ts"
|
|
21
|
+
},
|
|
22
|
+
"./application": {
|
|
23
|
+
"import": "./build/service/architecture/application/index.js",
|
|
24
|
+
"types": "./build/service/architecture/application/index.d.ts"
|
|
25
|
+
},
|
|
26
|
+
"./errors": {
|
|
27
|
+
"import": "./build/error/index.js",
|
|
28
|
+
"types": "./build/error/index.d.ts"
|
|
29
|
+
},
|
|
30
|
+
"./config": {
|
|
31
|
+
"import": "./build/configuration/index.js",
|
|
32
|
+
"types": "./build/configuration/index.d.ts"
|
|
33
|
+
},
|
|
34
|
+
"./build/*": "./build/*",
|
|
35
|
+
"./package.json": "./package.json"
|
|
36
|
+
},
|
|
9
37
|
"repository": {
|
|
10
38
|
"type": "git",
|
|
11
39
|
"url": "https://github.com/sentzunhat/zacatl.git"
|
|
@@ -70,9 +98,16 @@
|
|
|
70
98
|
"test": "NODE_ENV=test ENV=test vitest run",
|
|
71
99
|
"test:watch": "NODE_ENV=test ENV=test vitest",
|
|
72
100
|
"test:coverage": "npm run test -- --coverage",
|
|
73
|
-
"
|
|
101
|
+
"test:bun": "bun test",
|
|
102
|
+
"test:node": "vitest run",
|
|
103
|
+
"type:check": "npm run type:check:src && npm run type:check:test",
|
|
104
|
+
"type:check:src": "tsc --noEmit -p ./tsconfig.json",
|
|
105
|
+
"type:check:test": "tsc --noEmit -p ./test/tsconfig.json",
|
|
74
106
|
"lint": "DEBUG=eslint:cli-engine bun eslint .",
|
|
75
|
-
"
|
|
107
|
+
"prepublish": "npm run test:coverage && npm run type:check && npm run lint && npm run build",
|
|
108
|
+
"publish:latest": "npm run prepublish && npm publish --access public --tag latest",
|
|
109
|
+
"publish:otp": "npm run prepublish && npm publish --access public --tag latest --otp",
|
|
110
|
+
"publish:dry": "npm run prepublish && npm publish --dry-run"
|
|
76
111
|
},
|
|
77
112
|
"devDependencies": {
|
|
78
113
|
"@eslint/eslintrc": "^3.3.3",
|
|
@@ -102,6 +137,21 @@
|
|
|
102
137
|
"README.md",
|
|
103
138
|
"LICENSE"
|
|
104
139
|
],
|
|
140
|
+
"peerDependencies": {
|
|
141
|
+
"mongoose": "^9.0.0",
|
|
142
|
+
"sequelize": "^6.0.0"
|
|
143
|
+
},
|
|
144
|
+
"peerDependenciesMeta": {
|
|
145
|
+
"mongoose": {
|
|
146
|
+
"optional": true
|
|
147
|
+
},
|
|
148
|
+
"sequelize": {
|
|
149
|
+
"optional": true
|
|
150
|
+
},
|
|
151
|
+
"better-sqlite3": {
|
|
152
|
+
"optional": true
|
|
153
|
+
}
|
|
154
|
+
},
|
|
105
155
|
"dependencies": {
|
|
106
156
|
"@fastify/http-proxy": "^11.4.1",
|
|
107
157
|
"@fastify/static": "^8.3.0",
|
|
@@ -124,11 +174,6 @@
|
|
|
124
174
|
"uuid": "^13.0.0",
|
|
125
175
|
"zod": "^4.3.6"
|
|
126
176
|
},
|
|
127
|
-
"peerDependenciesMeta": {
|
|
128
|
-
"better-sqlite3": {
|
|
129
|
-
"optional": true
|
|
130
|
-
}
|
|
131
|
-
},
|
|
132
177
|
"optionalDependencies": {
|
|
133
178
|
"@types/better-sqlite3": "^7.6.13",
|
|
134
179
|
"better-sqlite3": "^12.6.2"
|
package/src/index.ts
CHANGED
|
@@ -30,5 +30,8 @@ export type { DependencyContainer } from "tsyringe";
|
|
|
30
30
|
export { z } from "zod";
|
|
31
31
|
export type { ZodSchema, ZodType, ZodError } from "zod";
|
|
32
32
|
|
|
33
|
+
// Re-export ORMs (included as dependencies)
|
|
34
|
+
export * from "./orm-exports";
|
|
35
|
+
|
|
33
36
|
// Note: TypeScript utility types (Partial, Required, Readonly, etc.)
|
|
34
37
|
// are globally available and don't need re-export
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Re-export ORM dependencies for convenience
|
|
3
|
+
* These are included as dependencies (currently) for backward compatibility
|
|
4
|
+
* Future versions may move them to pure peer dependencies
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// Mongoose exports - full re-export
|
|
8
|
+
export {
|
|
9
|
+
default as mongoose,
|
|
10
|
+
Mongoose,
|
|
11
|
+
Schema,
|
|
12
|
+
Model,
|
|
13
|
+
Document,
|
|
14
|
+
connect,
|
|
15
|
+
connection,
|
|
16
|
+
} from "mongoose";
|
|
17
|
+
|
|
18
|
+
// Sequelize exports - full re-export
|
|
19
|
+
export { Sequelize, Model as SequelizeModel, DataTypes, Op } from "sequelize";
|
|
20
|
+
|
|
21
|
+
// Type-only exports for convenience
|
|
22
|
+
export type { Model as MongooseModel } from "mongoose";
|
|
23
|
+
export type { ModelStatic, Options as SequelizeOptions } from "sequelize";
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { Model } from "sequelize";
|
|
2
|
+
import type {
|
|
3
|
+
MongooseRepositoryConfig,
|
|
4
|
+
SequelizeRepositoryConfig,
|
|
5
|
+
ORMAdapter,
|
|
6
|
+
} from "../repositories/types";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Loads MongooseAdapter dynamically - only when needed
|
|
10
|
+
* Throws helpful error if mongoose is not installed
|
|
11
|
+
*/
|
|
12
|
+
export async function loadMongooseAdapter<D, I, O>(
|
|
13
|
+
config: MongooseRepositoryConfig<D>,
|
|
14
|
+
): Promise<ORMAdapter<D, I, O>> {
|
|
15
|
+
try {
|
|
16
|
+
// Dynamic import - only loads when Mongoose is actually used
|
|
17
|
+
const adapters = await import("./adapters/mongoose-adapter");
|
|
18
|
+
return new adapters.MongooseAdapter<D, I, O>(config);
|
|
19
|
+
} catch (error: any) {
|
|
20
|
+
if (
|
|
21
|
+
error.code === "ERR_MODULE_NOT_FOUND" ||
|
|
22
|
+
error.code === "MODULE_NOT_FOUND"
|
|
23
|
+
) {
|
|
24
|
+
throw new Error(
|
|
25
|
+
"Mongoose is not installed. Install it with: npm install mongoose",
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
throw error;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Loads SequelizeAdapter dynamically - only when needed
|
|
34
|
+
* Throws helpful error if sequelize is not installed
|
|
35
|
+
*/
|
|
36
|
+
export async function loadSequelizeAdapter<D extends Model, I, O>(
|
|
37
|
+
config: SequelizeRepositoryConfig<D>,
|
|
38
|
+
): Promise<ORMAdapter<D, I, O>> {
|
|
39
|
+
try {
|
|
40
|
+
// Dynamic import - only loads when Sequelize is actually used
|
|
41
|
+
const adapters = await import("./adapters/sequelize-adapter");
|
|
42
|
+
return new adapters.SequelizeAdapter<D, I, O>(config);
|
|
43
|
+
} catch (error: any) {
|
|
44
|
+
if (
|
|
45
|
+
error.code === "ERR_MODULE_NOT_FOUND" ||
|
|
46
|
+
error.code === "MODULE_NOT_FOUND"
|
|
47
|
+
) {
|
|
48
|
+
throw new Error(
|
|
49
|
+
"Sequelize is not installed. Install it with: npm install sequelize",
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
throw error;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -1,8 +1,5 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
Model as MongooseModel,
|
|
4
|
-
Mongoose,
|
|
5
|
-
} from "mongoose";
|
|
1
|
+
import type { Model as MongooseModel } from "mongoose";
|
|
2
|
+
import { Mongoose } from "mongoose";
|
|
6
3
|
import { v4 as uuidv4 } from "uuid";
|
|
7
4
|
import { container } from "tsyringe";
|
|
8
5
|
import type {
|
|
@@ -14,9 +11,11 @@ import type {
|
|
|
14
11
|
/**
|
|
15
12
|
* MongooseAdapter - Handles Mongoose-specific ORM operations
|
|
16
13
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* ID normalization
|
|
14
|
+
* Provides consistent interface for repository layer with:
|
|
15
|
+
* - Document transformation
|
|
16
|
+
* - ID normalization
|
|
17
|
+
* - Timestamp management
|
|
18
|
+
* - Connection instance from DI container
|
|
20
19
|
*/
|
|
21
20
|
export class MongooseAdapter<D, I, O> implements ORMAdapter<D, I, O> {
|
|
22
21
|
public readonly model: MongooseModel<D>;
|
|
@@ -24,9 +23,9 @@ export class MongooseAdapter<D, I, O> implements ORMAdapter<D, I, O> {
|
|
|
24
23
|
|
|
25
24
|
constructor(config: MongooseRepositoryConfig<D>) {
|
|
26
25
|
this.config = config;
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
26
|
+
// Get mongoose instance from DI container - allows per-database configuration
|
|
27
|
+
// Connection strings should be set up before repository instantiation
|
|
28
|
+
const mongoose = container.resolve<Mongoose>(Mongoose);
|
|
30
29
|
|
|
31
30
|
const { name, schema } = this.config;
|
|
32
31
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { Model as MongooseModel } from "mongoose";
|
|
2
|
-
import { Model, ModelStatic } from "sequelize";
|
|
1
|
+
import type { Model as MongooseModel } from "mongoose";
|
|
2
|
+
import type { Model, ModelStatic } from "sequelize";
|
|
3
3
|
import {
|
|
4
4
|
BaseRepositoryConfig,
|
|
5
5
|
Repository,
|
|
@@ -8,8 +8,10 @@ import {
|
|
|
8
8
|
ORMAdapter,
|
|
9
9
|
ORMType,
|
|
10
10
|
} from "./types";
|
|
11
|
-
import {
|
|
12
|
-
|
|
11
|
+
import {
|
|
12
|
+
loadMongooseAdapter,
|
|
13
|
+
loadSequelizeAdapter,
|
|
14
|
+
} from "../orm/adapter-loader";
|
|
13
15
|
|
|
14
16
|
export * from "./types";
|
|
15
17
|
|
|
@@ -34,23 +36,51 @@ const isSequelizeConfig = <D extends Model>(
|
|
|
34
36
|
/**
|
|
35
37
|
* BaseRepository - Abstract base class for all repositories
|
|
36
38
|
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
39
|
+
* Supports multiple ORMs (Mongoose, Sequelize) through adapter pattern.
|
|
40
|
+
* Adapters are lazy-loaded - only the ORM you use gets imported.
|
|
41
|
+
* This allows projects to install only one ORM without unused dependencies.
|
|
42
|
+
*
|
|
43
|
+
* Uses lazy initialization to support ESM dynamic imports while maintaining
|
|
44
|
+
* backward compatibility with synchronous constructors.
|
|
40
45
|
*/
|
|
41
46
|
export abstract class BaseRepository<D, I, O> implements Repository<D, I, O> {
|
|
42
|
-
private adapter
|
|
47
|
+
private adapter?: ORMAdapter<D, I, O>;
|
|
48
|
+
private readonly ormType: ORMType;
|
|
49
|
+
private readonly config: BaseRepositoryConfig<D>;
|
|
50
|
+
private initPromise?: Promise<void>;
|
|
43
51
|
|
|
44
52
|
constructor(config: BaseRepositoryConfig<D>) {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
53
|
+
this.ormType = config.type;
|
|
54
|
+
this.config = config;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Ensures the adapter is initialized before any operation
|
|
59
|
+
* Uses a promise to prevent multiple concurrent initializations
|
|
60
|
+
*/
|
|
61
|
+
private async ensureInitialized(): Promise<void> {
|
|
62
|
+
if (this.adapter) return;
|
|
63
|
+
|
|
64
|
+
if (!this.initPromise) {
|
|
65
|
+
this.initPromise = this.loadAdapter();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
await this.initPromise;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Loads the appropriate ORM adapter based on configuration
|
|
73
|
+
*/
|
|
74
|
+
private async loadAdapter(): Promise<void> {
|
|
75
|
+
if (isMongooseConfig<D>(this.config)) {
|
|
76
|
+
this.adapter = await loadMongooseAdapter<D, I, O>(this.config);
|
|
77
|
+
} else if (isSequelizeConfig(this.config)) {
|
|
78
|
+
// Type assertion needed here because D could be either Mongoose or Sequelize model
|
|
79
|
+
this.adapter = (await loadSequelizeAdapter<Model, I, O>(
|
|
80
|
+
this.config,
|
|
81
|
+
)) as ORMAdapter<D, I, O>;
|
|
51
82
|
} else {
|
|
52
|
-
|
|
53
|
-
const exhaustive: never = config;
|
|
83
|
+
const exhaustive: never = this.config;
|
|
54
84
|
throw new Error(
|
|
55
85
|
`Invalid repository configuration. Received: ${JSON.stringify(exhaustive)}`,
|
|
56
86
|
);
|
|
@@ -58,18 +88,28 @@ export abstract class BaseRepository<D, I, O> implements Repository<D, I, O> {
|
|
|
58
88
|
}
|
|
59
89
|
|
|
60
90
|
get model(): MongooseModel<D> | ModelStatic<any> {
|
|
91
|
+
if (!this.adapter) {
|
|
92
|
+
throw new Error(
|
|
93
|
+
"Repository not initialized. Call an async method first or await repository.ensureInitialized()",
|
|
94
|
+
);
|
|
95
|
+
}
|
|
61
96
|
return this.adapter.model;
|
|
62
97
|
}
|
|
63
98
|
|
|
64
99
|
public isMongoose(): boolean {
|
|
65
|
-
return this.
|
|
100
|
+
return this.ormType === ORMType.Mongoose;
|
|
66
101
|
}
|
|
67
102
|
|
|
68
103
|
public isSequelize(): boolean {
|
|
69
|
-
return this.
|
|
104
|
+
return this.ormType === ORMType.Sequelize;
|
|
70
105
|
}
|
|
71
106
|
|
|
72
107
|
public getMongooseModel(): MongooseModel<D> {
|
|
108
|
+
if (!this.adapter) {
|
|
109
|
+
throw new Error(
|
|
110
|
+
"Repository not initialized. Call an async method first or await repository.ensureInitialized()",
|
|
111
|
+
);
|
|
112
|
+
}
|
|
73
113
|
if (!this.isMongoose()) {
|
|
74
114
|
throw new Error("Repository is not using Mongoose");
|
|
75
115
|
}
|
|
@@ -77,6 +117,11 @@ export abstract class BaseRepository<D, I, O> implements Repository<D, I, O> {
|
|
|
77
117
|
}
|
|
78
118
|
|
|
79
119
|
public getSequelizeModel(): ModelStatic<any> {
|
|
120
|
+
if (!this.adapter) {
|
|
121
|
+
throw new Error(
|
|
122
|
+
"Repository not initialized. Call an async method first or await repository.ensureInitialized()",
|
|
123
|
+
);
|
|
124
|
+
}
|
|
80
125
|
if (!this.isSequelize()) {
|
|
81
126
|
throw new Error("Repository is not using Sequelize");
|
|
82
127
|
}
|
|
@@ -84,22 +129,31 @@ export abstract class BaseRepository<D, I, O> implements Repository<D, I, O> {
|
|
|
84
129
|
}
|
|
85
130
|
|
|
86
131
|
public toLean(input: unknown): O | null {
|
|
132
|
+
if (!this.adapter) {
|
|
133
|
+
throw new Error(
|
|
134
|
+
"Repository not initialized. Call an async method first or await repository.ensureInitialized()",
|
|
135
|
+
);
|
|
136
|
+
}
|
|
87
137
|
return this.adapter.toLean(input);
|
|
88
138
|
}
|
|
89
139
|
|
|
90
140
|
async findById(id: string): Promise<O | null> {
|
|
91
|
-
|
|
141
|
+
await this.ensureInitialized();
|
|
142
|
+
return this.adapter!.findById(id);
|
|
92
143
|
}
|
|
93
144
|
|
|
94
145
|
async create(entity: I): Promise<O> {
|
|
95
|
-
|
|
146
|
+
await this.ensureInitialized();
|
|
147
|
+
return this.adapter!.create(entity);
|
|
96
148
|
}
|
|
97
149
|
|
|
98
150
|
async update(id: string, update: Partial<I>): Promise<O | null> {
|
|
99
|
-
|
|
151
|
+
await this.ensureInitialized();
|
|
152
|
+
return this.adapter!.update(id, update);
|
|
100
153
|
}
|
|
101
154
|
|
|
102
155
|
async delete(id: string): Promise<O | null> {
|
|
103
|
-
|
|
156
|
+
await this.ensureInitialized();
|
|
157
|
+
return this.adapter!.delete(id);
|
|
104
158
|
}
|
|
105
159
|
}
|