@sentzunhat/zacatl 0.0.21 → 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/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "main": "build/index.js",
5
5
  "module": "build/index.js",
6
6
  "types": "build/index.d.ts",
7
- "version": "0.0.21",
7
+ "version": "0.0.22",
8
8
  "packageManager": "npm@10.9.0",
9
9
  "exports": {
10
10
  ".": {
@@ -100,7 +100,9 @@
100
100
  "test:coverage": "npm run test -- --coverage",
101
101
  "test:bun": "bun test",
102
102
  "test:node": "vitest run",
103
- "type:check": "tsc -p ./tsconfig.json && tsc -p test/tsconfig.json",
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",
104
106
  "lint": "DEBUG=eslint:cli-engine bun eslint .",
105
107
  "prepublish": "npm run test:coverage && npm run type:check && npm run lint && npm run build",
106
108
  "publish:latest": "npm run prepublish && npm publish --access public --tag latest",
@@ -9,17 +9,18 @@ import type {
9
9
  * Loads MongooseAdapter dynamically - only when needed
10
10
  * Throws helpful error if mongoose is not installed
11
11
  */
12
- export function loadMongooseAdapter<D, I, O>(
12
+ export async function loadMongooseAdapter<D, I, O>(
13
13
  config: MongooseRepositoryConfig<D>,
14
- ): ORMAdapter<D, I, O> {
14
+ ): Promise<ORMAdapter<D, I, O>> {
15
15
  try {
16
16
  // Dynamic import - only loads when Mongoose is actually used
17
- // eslint-disable-next-line @typescript-eslint/no-var-requires
18
- const adapters =
19
- require("./adapters/mongoose-adapter") as typeof import("./adapters/mongoose-adapter");
17
+ const adapters = await import("./adapters/mongoose-adapter");
20
18
  return new adapters.MongooseAdapter<D, I, O>(config);
21
19
  } catch (error: any) {
22
- if (error.code === "MODULE_NOT_FOUND") {
20
+ if (
21
+ error.code === "ERR_MODULE_NOT_FOUND" ||
22
+ error.code === "MODULE_NOT_FOUND"
23
+ ) {
23
24
  throw new Error(
24
25
  "Mongoose is not installed. Install it with: npm install mongoose",
25
26
  );
@@ -32,17 +33,18 @@ export function loadMongooseAdapter<D, I, O>(
32
33
  * Loads SequelizeAdapter dynamically - only when needed
33
34
  * Throws helpful error if sequelize is not installed
34
35
  */
35
- export function loadSequelizeAdapter<D extends Model, I, O>(
36
+ export async function loadSequelizeAdapter<D extends Model, I, O>(
36
37
  config: SequelizeRepositoryConfig<D>,
37
- ): ORMAdapter<D, I, O> {
38
+ ): Promise<ORMAdapter<D, I, O>> {
38
39
  try {
39
40
  // Dynamic import - only loads when Sequelize is actually used
40
- // eslint-disable-next-line @typescript-eslint/no-var-requires
41
- const adapters =
42
- require("./adapters/sequelize-adapter") as typeof import("./adapters/sequelize-adapter");
41
+ const adapters = await import("./adapters/sequelize-adapter");
43
42
  return new adapters.SequelizeAdapter<D, I, O>(config);
44
43
  } catch (error: any) {
45
- if (error.code === "MODULE_NOT_FOUND") {
44
+ if (
45
+ error.code === "ERR_MODULE_NOT_FOUND" ||
46
+ error.code === "MODULE_NOT_FOUND"
47
+ ) {
46
48
  throw new Error(
47
49
  "Sequelize is not installed. Install it with: npm install sequelize",
48
50
  );
@@ -39,25 +39,48 @@ const isSequelizeConfig = <D extends Model>(
39
39
  * Supports multiple ORMs (Mongoose, Sequelize) through adapter pattern.
40
40
  * Adapters are lazy-loaded - only the ORM you use gets imported.
41
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.
42
45
  */
43
46
  export abstract class BaseRepository<D, I, O> implements Repository<D, I, O> {
44
- private adapter: ORMAdapter<D, I, O>;
47
+ private adapter?: ORMAdapter<D, I, O>;
45
48
  private readonly ormType: ORMType;
49
+ private readonly config: BaseRepositoryConfig<D>;
50
+ private initPromise?: Promise<void>;
46
51
 
47
52
  constructor(config: BaseRepositoryConfig<D>) {
48
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
+ }
49
67
 
50
- if (isMongooseConfig<D>(config)) {
51
- this.adapter = loadMongooseAdapter<D, I, O>(config);
52
- } else if (isSequelizeConfig(config)) {
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)) {
53
78
  // Type assertion needed here because D could be either Mongoose or Sequelize model
54
- this.adapter = loadSequelizeAdapter<Model, I, O>(config) as ORMAdapter<
55
- D,
56
- I,
57
- O
58
- >;
79
+ this.adapter = (await loadSequelizeAdapter<Model, I, O>(
80
+ this.config,
81
+ )) as ORMAdapter<D, I, O>;
59
82
  } else {
60
- const exhaustive: never = config;
83
+ const exhaustive: never = this.config;
61
84
  throw new Error(
62
85
  `Invalid repository configuration. Received: ${JSON.stringify(exhaustive)}`,
63
86
  );
@@ -65,6 +88,11 @@ export abstract class BaseRepository<D, I, O> implements Repository<D, I, O> {
65
88
  }
66
89
 
67
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
+ }
68
96
  return this.adapter.model;
69
97
  }
70
98
 
@@ -77,6 +105,11 @@ export abstract class BaseRepository<D, I, O> implements Repository<D, I, O> {
77
105
  }
78
106
 
79
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
+ }
80
113
  if (!this.isMongoose()) {
81
114
  throw new Error("Repository is not using Mongoose");
82
115
  }
@@ -84,6 +117,11 @@ export abstract class BaseRepository<D, I, O> implements Repository<D, I, O> {
84
117
  }
85
118
 
86
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
+ }
87
125
  if (!this.isSequelize()) {
88
126
  throw new Error("Repository is not using Sequelize");
89
127
  }
@@ -91,22 +129,31 @@ export abstract class BaseRepository<D, I, O> implements Repository<D, I, O> {
91
129
  }
92
130
 
93
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
+ }
94
137
  return this.adapter.toLean(input);
95
138
  }
96
139
 
97
140
  async findById(id: string): Promise<O | null> {
98
- return this.adapter.findById(id);
141
+ await this.ensureInitialized();
142
+ return this.adapter!.findById(id);
99
143
  }
100
144
 
101
145
  async create(entity: I): Promise<O> {
102
- return this.adapter.create(entity);
146
+ await this.ensureInitialized();
147
+ return this.adapter!.create(entity);
103
148
  }
104
149
 
105
150
  async update(id: string, update: Partial<I>): Promise<O | null> {
106
- return this.adapter.update(id, update);
151
+ await this.ensureInitialized();
152
+ return this.adapter!.update(id, update);
107
153
  }
108
154
 
109
155
  async delete(id: string): Promise<O | null> {
110
- return this.adapter.delete(id);
156
+ await this.ensureInitialized();
157
+ return this.adapter!.delete(id);
111
158
  }
112
159
  }