@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.
Files changed (28) hide show
  1. package/README.md +20 -2
  2. package/build/index.d.ts +1 -0
  3. package/build/index.d.ts.map +1 -1
  4. package/build/index.js +1 -0
  5. package/build/index.js.map +1 -1
  6. package/build/orm-exports.d.ts +5 -0
  7. package/build/orm-exports.d.ts.map +1 -0
  8. package/build/orm-exports.js +3 -0
  9. package/build/orm-exports.js.map +1 -0
  10. package/build/service/architecture/infrastructure/orm/adapter-loader.d.ts +5 -0
  11. package/build/service/architecture/infrastructure/orm/adapter-loader.d.ts.map +1 -0
  12. package/build/service/architecture/infrastructure/orm/adapter-loader.js +27 -0
  13. package/build/service/architecture/infrastructure/orm/adapter-loader.js.map +1 -0
  14. package/build/service/architecture/infrastructure/orm/adapters/mongoose-adapter.d.ts +1 -1
  15. package/build/service/architecture/infrastructure/orm/adapters/mongoose-adapter.d.ts.map +1 -1
  16. package/build/service/architecture/infrastructure/orm/adapters/mongoose-adapter.js +2 -4
  17. package/build/service/architecture/infrastructure/orm/adapters/mongoose-adapter.js.map +1 -1
  18. package/build/service/architecture/infrastructure/repositories/abstract.d.ts +8 -3
  19. package/build/service/architecture/infrastructure/repositories/abstract.d.ts.map +1 -1
  20. package/build/service/architecture/infrastructure/repositories/abstract.js +39 -9
  21. package/build/service/architecture/infrastructure/repositories/abstract.js.map +1 -1
  22. package/build/test/tsconfig.tsbuildinfo +1 -1
  23. package/package.json +53 -8
  24. package/src/index.ts +3 -0
  25. package/src/orm-exports.ts +23 -0
  26. package/src/service/architecture/infrastructure/orm/adapter-loader.ts +54 -0
  27. package/src/service/architecture/infrastructure/orm/adapters/mongoose-adapter.ts +10 -11
  28. 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.20",
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
- "type:check": "tsc -p ./tsconfig.json && tsc -p test/tsconfig.json",
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
- "publish:latest": "npm i && npm run test && npm run test:coverage && npm run type:check && npm run lint && npm run build && npm publish --access public --tag latest"
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 importedMongoose, {
2
- connection,
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
- * This adapter wraps Mongoose model operations and provides a consistent
18
- * interface for the repository layer. It handles document transformation,
19
- * ID normalization, and timestamp management.
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
- const mongoose = connection.db?.databaseName
28
- ? importedMongoose
29
- : container.resolve<Mongoose>(Mongoose);
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 { MongooseAdapter } from "../orm/adapters/mongoose-adapter";
12
- import { SequelizeAdapter } from "../orm/adapters/sequelize-adapter";
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
- * Uses the adapter pattern to support multiple ORMs (Mongoose, Sequelize, etc.)
38
- * without circular dependencies. Adapters are imported from separate files in
39
- * the orm/ folder.
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: ORMAdapter<D, I, O>;
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
- if (isMongooseConfig<D>(config)) {
46
- // TypeScript knows config is MongooseRepositoryConfig<D>
47
- this.adapter = new MongooseAdapter<D, I, O>(config);
48
- } else if (isSequelizeConfig(config)) {
49
- // TypeScript knows config is SequelizeRepositoryConfig
50
- this.adapter = new SequelizeAdapter<Model, I, O>(config);
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
- // Exhaustiveness check - should never reach here with proper types
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.adapter instanceof MongooseAdapter;
100
+ return this.ormType === ORMType.Mongoose;
66
101
  }
67
102
 
68
103
  public isSequelize(): boolean {
69
- return this.adapter instanceof SequelizeAdapter;
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
- return this.adapter.findById(id);
141
+ await this.ensureInitialized();
142
+ return this.adapter!.findById(id);
92
143
  }
93
144
 
94
145
  async create(entity: I): Promise<O> {
95
- return this.adapter.create(entity);
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
- return this.adapter.update(id, update);
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
- return this.adapter.delete(id);
156
+ await this.ensureInitialized();
157
+ return this.adapter!.delete(id);
104
158
  }
105
159
  }