@c9up/atlas 0.1.9 → 0.1.10

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 (70) hide show
  1. package/db.win32-x64-msvc.node +0 -0
  2. package/dist/AtlasProvider.d.ts.map +1 -1
  3. package/dist/AtlasProvider.js +9 -1
  4. package/dist/AtlasProvider.js.map +1 -1
  5. package/dist/BaseEntity.d.ts +3 -12
  6. package/dist/BaseEntity.d.ts.map +1 -1
  7. package/dist/BaseEntity.js +20 -4
  8. package/dist/BaseEntity.js.map +1 -1
  9. package/dist/BaseRepository.d.ts.map +1 -1
  10. package/dist/BaseRepository.js +37 -89
  11. package/dist/BaseRepository.js.map +1 -1
  12. package/dist/ModelQuery.d.ts +2 -2
  13. package/dist/ModelQuery.d.ts.map +1 -1
  14. package/dist/ModelQuery.js +62 -17
  15. package/dist/ModelQuery.js.map +1 -1
  16. package/dist/adapters/NapiDbAdapter.d.ts.map +1 -1
  17. package/dist/adapters/NapiDbAdapter.js +7 -5
  18. package/dist/adapters/NapiDbAdapter.js.map +1 -1
  19. package/dist/configure.d.ts.map +1 -1
  20. package/dist/configure.js +5 -6
  21. package/dist/configure.js.map +1 -1
  22. package/dist/decorators/entity.js +1 -1
  23. package/dist/decorators/entity.js.map +1 -1
  24. package/dist/metadata-keys.d.ts +20 -0
  25. package/dist/metadata-keys.d.ts.map +1 -0
  26. package/dist/metadata-keys.js +13 -0
  27. package/dist/metadata-keys.js.map +1 -0
  28. package/dist/query/native.d.ts +7 -0
  29. package/dist/query/native.d.ts.map +1 -1
  30. package/dist/query/native.js +10 -1
  31. package/dist/query/native.js.map +1 -1
  32. package/dist/schema/MigrationRunner.d.ts.map +1 -1
  33. package/dist/schema/MigrationRunner.js +5 -2
  34. package/dist/schema/MigrationRunner.js.map +1 -1
  35. package/dist/schema/Seeder.d.ts +13 -8
  36. package/dist/schema/Seeder.d.ts.map +1 -1
  37. package/dist/schema/Seeder.js +13 -8
  38. package/dist/schema/Seeder.js.map +1 -1
  39. package/dist/services/db.d.ts +7 -0
  40. package/dist/services/db.d.ts.map +1 -1
  41. package/dist/services/db.js +10 -0
  42. package/dist/services/db.js.map +1 -1
  43. package/dist/testing/Factory.d.ts +6 -7
  44. package/dist/testing/Factory.d.ts.map +1 -1
  45. package/dist/testing/Factory.js +3 -5
  46. package/dist/testing/Factory.js.map +1 -1
  47. package/dist/utils/safePath.d.ts +7 -1
  48. package/dist/utils/safePath.d.ts.map +1 -1
  49. package/dist/utils/safePath.js +24 -1
  50. package/dist/utils/safePath.js.map +1 -1
  51. package/index.darwin-arm64.node +0 -0
  52. package/index.darwin-x64.node +0 -0
  53. package/index.linux-arm64-gnu.node +0 -0
  54. package/index.linux-x64-gnu.node +0 -0
  55. package/index.win32-x64-msvc.node +0 -0
  56. package/package.json +1 -1
  57. package/src/AtlasProvider.ts +9 -1
  58. package/src/BaseEntity.ts +27 -12
  59. package/src/BaseRepository.ts +38 -98
  60. package/src/ModelQuery.ts +66 -17
  61. package/src/adapters/NapiDbAdapter.ts +11 -8
  62. package/src/configure.ts +5 -6
  63. package/src/decorators/entity.ts +1 -1
  64. package/src/metadata-keys.ts +22 -0
  65. package/src/query/native.ts +12 -1
  66. package/src/schema/MigrationRunner.ts +5 -2
  67. package/src/schema/Seeder.ts +14 -12
  68. package/src/services/db.ts +10 -0
  69. package/src/testing/Factory.ts +9 -12
  70. package/src/utils/safePath.ts +27 -2
@@ -1,15 +1,20 @@
1
1
  /**
2
2
  * Seeder — populate the database with default / reference / test data.
3
3
  *
4
- * The canonical pattern:
4
+ * The canonical pattern (idempotent via `upsert`, keyed on a unique column):
5
5
  *
6
6
  * export default class CountrySeeder extends BaseSeeder {
7
7
  * async run() {
8
- * // Idempotent via updateOrCreateMany safe to re-run.
9
- * await Country.updateOrCreateMany('isoCode', [
10
- * { isoCode: 'FR', name: 'France' },
11
- * { isoCode: 'IN', name: 'India' },
12
- * ])
8
+ * const countries = new BaseRepository(Country, this.db)
9
+ * // Conflict on isoCode → update name. Safe to re-run.
10
+ * await countries.upsert(
11
+ * [
12
+ * { isoCode: 'FR', name: 'France' },
13
+ * { isoCode: 'IN', name: 'India' },
14
+ * ],
15
+ * ['isoCode'],
16
+ * ['name'],
17
+ * )
13
18
  * }
14
19
  * }
15
20
  *
@@ -25,7 +30,7 @@ import type { DatabaseConnection } from "../BaseRepository.js";
25
30
  export declare abstract class BaseSeeder {
26
31
  protected db: DatabaseConnection;
27
32
  constructor(db: DatabaseConnection);
28
- /** The seeder body. Should be idempotent — `updateOrCreateMany` is the recommended pattern. */
33
+ /** The seeder body. Should be idempotent — `repo.upsert(...)` is the recommended pattern. */
29
34
  abstract run(): Promise<void> | void;
30
35
  }
31
36
  /** Legacy alias kept for code using the previous API. */
@@ -35,7 +40,7 @@ export declare const Seeder: typeof BaseSeeder;
35
40
  * sequentially so ordering is deterministic and side effects are visible to
36
41
  * subsequent seeders.
37
42
  */
38
- export declare function runSeeders(seeders: BaseSeeder[], _db?: DatabaseConnection): Promise<void>;
43
+ export declare function runSeeders(seeders: BaseSeeder[]): Promise<void>;
39
44
  /**
40
45
  * Discover seeder files under `dir`, import them in alphabetical order, build
41
46
  * instances with the given DB connection, and run them sequentially.
@@ -1 +1 @@
1
- {"version":3,"file":"Seeder.d.ts","sourceRoot":"","sources":["../../src/schema/Seeder.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAIH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAQ/D;;;;;GAKG;AACH,8BAAsB,UAAU;IAC/B,SAAS,CAAC,EAAE,EAAE,kBAAkB,CAAC;gBAErB,EAAE,EAAE,kBAAkB;IAIlC,+FAA+F;IAC/F,QAAQ,CAAC,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI;CACpC;AAED,yDAAyD;AACzD,eAAO,MAAM,MAAM,mBAAa,CAAC;AAEjC;;;;GAIG;AACH,wBAAsB,UAAU,CAC/B,OAAO,EAAE,UAAU,EAAE,EACrB,GAAG,CAAC,EAAE,kBAAkB,GACtB,OAAO,CAAC,IAAI,CAAC,CAIf;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,kBAAkB,CACvC,GAAG,EAAE,MAAM,EACX,EAAE,EAAE,kBAAkB,EACtB,OAAO,CAAC,EAAE;IAAE,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,GACrC,OAAO,CAAC,MAAM,EAAE,CAAC,CAqDnB"}
1
+ {"version":3,"file":"Seeder.d.ts","sourceRoot":"","sources":["../../src/schema/Seeder.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAIH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAQ/D;;;;;GAKG;AACH,8BAAsB,UAAU;IAC/B,SAAS,CAAC,EAAE,EAAE,kBAAkB,CAAC;gBAErB,EAAE,EAAE,kBAAkB;IAIlC,6FAA6F;IAC7F,QAAQ,CAAC,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI;CACpC;AAED,yDAAyD;AACzD,eAAO,MAAM,MAAM,mBAAa,CAAC;AAEjC;;;;GAIG;AACH,wBAAsB,UAAU,CAAC,OAAO,EAAE,UAAU,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAIrE;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,kBAAkB,CACvC,GAAG,EAAE,MAAM,EACX,EAAE,EAAE,kBAAkB,EACtB,OAAO,CAAC,EAAE;IAAE,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,GACrC,OAAO,CAAC,MAAM,EAAE,CAAC,CAqDnB"}
@@ -1,15 +1,20 @@
1
1
  /**
2
2
  * Seeder — populate the database with default / reference / test data.
3
3
  *
4
- * The canonical pattern:
4
+ * The canonical pattern (idempotent via `upsert`, keyed on a unique column):
5
5
  *
6
6
  * export default class CountrySeeder extends BaseSeeder {
7
7
  * async run() {
8
- * // Idempotent via updateOrCreateMany safe to re-run.
9
- * await Country.updateOrCreateMany('isoCode', [
10
- * { isoCode: 'FR', name: 'France' },
11
- * { isoCode: 'IN', name: 'India' },
12
- * ])
8
+ * const countries = new BaseRepository(Country, this.db)
9
+ * // Conflict on isoCode → update name. Safe to re-run.
10
+ * await countries.upsert(
11
+ * [
12
+ * { isoCode: 'FR', name: 'France' },
13
+ * { isoCode: 'IN', name: 'India' },
14
+ * ],
15
+ * ['isoCode'],
16
+ * ['name'],
17
+ * )
13
18
  * }
14
19
  * }
15
20
  *
@@ -38,7 +43,7 @@ export const Seeder = BaseSeeder;
38
43
  * sequentially so ordering is deterministic and side effects are visible to
39
44
  * subsequent seeders.
40
45
  */
41
- export async function runSeeders(seeders, _db) {
46
+ export async function runSeeders(seeders) {
42
47
  for (const seeder of seeders) {
43
48
  await seeder.run();
44
49
  }
@@ -70,7 +75,7 @@ export async function runSeederDirectory(dir, db, options) {
70
75
  const executed = [];
71
76
  for (const file of selected) {
72
77
  assertSafeName(file, "E_SEEDER_INVALID", "seeder");
73
- const resolved = assertPathInsideBase(dir, file, "E_SEEDER_INVALID_PATH", "Seeder");
78
+ const resolved = await assertPathInsideBase(dir, file, "E_SEEDER_INVALID_PATH", "Seeder");
74
79
  // `pathToFileURL` is required on Windows where bare `C:\…` paths
75
80
  // trip ESM's ERR_UNSUPPORTED_ESM_URL_SCHEME on dynamic import.
76
81
  const mod = await import(pathToFileURL(resolved).href);
@@ -1 +1 @@
1
- {"version":3,"file":"Seeder.js","sourceRoot":"","sources":["../../src/schema/Seeder.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,GAAG,MAAM,kBAAkB,CAAC;AACxC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EACN,oBAAoB,EACpB,cAAc,EACd,UAAU,GACV,MAAM,sBAAsB,CAAC;AAE9B;;;;;GAKG;AACH,MAAM,OAAgB,UAAU;IACrB,EAAE,CAAqB;IAEjC,YAAY,EAAsB;QACjC,IAAI,CAAC,EAAE,GAAG,EAAE,CAAC;IACd,CAAC;CAID;AAED,yDAAyD;AACzD,MAAM,CAAC,MAAM,MAAM,GAAG,UAAU,CAAC;AAEjC;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAC/B,OAAqB,EACrB,GAAwB;IAExB,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC9B,MAAM,MAAM,CAAC,GAAG,EAAE,CAAC;IACpB,CAAC;AACF,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACvC,GAAW,EACX,EAAsB,EACtB,OAAuC;IAEvC,IAAI,CAAC,CAAC,MAAM,UAAU,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;QAC9B,MAAM,IAAI,UAAU,CACnB,wBAAwB,EACxB,+BAA+B,GAAG,EAAE,EACpC;YACC,IAAI,EAAE,uEAAuE;SAC7E,CACD,CAAC;IACH,CAAC;IAED,MAAM,QAAQ,GAAG,CAAC,MAAM,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;SACvC,MAAM,CACN,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CACvE;SACA,IAAI,EAAE,CAAC;IAET,MAAM,QAAQ,GAAG,OAAO,EAAE,KAAK;QAC9B,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CACtB,OAAO,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC,CACpD;QACF,CAAC,CAAC,QAAQ,CAAC;IAEZ,MAAM,QAAQ,GAAa,EAAE,CAAC;IAC9B,KAAK,MAAM,IAAI,IAAI,QAAQ,EAAE,CAAC;QAC7B,cAAc,CAAC,IAAI,EAAE,kBAAkB,EAAE,QAAQ,CAAC,CAAC;QACnD,MAAM,QAAQ,GAAG,oBAAoB,CACpC,GAAG,EACH,IAAI,EACJ,uBAAuB,EACvB,QAAQ,CACR,CAAC;QAEF,iEAAiE;QACjE,+DAA+D;QAC/D,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,CAAC;QACvD,MAAM,WAAW,GAAG,GAAG,CAAC,OAAO,CAAC;QAChC,IAAI,CAAC,WAAW,IAAI,OAAO,WAAW,KAAK,UAAU,EAAE,CAAC;YACvD,MAAM,IAAI,UAAU,CACnB,kBAAkB,EAClB,UAAU,IAAI,mDAAmD,EACjE;gBACC,IAAI,EAAE,uFAAuF;aAC7F,CACD,CAAC;QACH,CAAC;QAED,MAAM,QAAQ,GAAe,IAAI,WAAW,CAAC,EAAE,CAAC,CAAC;QACjD,MAAM,QAAQ,CAAC,GAAG,EAAE,CAAC;QACrB,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC,CAAC;IAC/C,CAAC;IAED,OAAO,QAAQ,CAAC;AACjB,CAAC"}
1
+ {"version":3,"file":"Seeder.js","sourceRoot":"","sources":["../../src/schema/Seeder.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,GAAG,MAAM,kBAAkB,CAAC;AACxC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EACN,oBAAoB,EACpB,cAAc,EACd,UAAU,GACV,MAAM,sBAAsB,CAAC;AAE9B;;;;;GAKG;AACH,MAAM,OAAgB,UAAU;IACrB,EAAE,CAAqB;IAEjC,YAAY,EAAsB;QACjC,IAAI,CAAC,EAAE,GAAG,EAAE,CAAC;IACd,CAAC;CAID;AAED,yDAAyD;AACzD,MAAM,CAAC,MAAM,MAAM,GAAG,UAAU,CAAC;AAEjC;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,OAAqB;IACrD,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC9B,MAAM,MAAM,CAAC,GAAG,EAAE,CAAC;IACpB,CAAC;AACF,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACvC,GAAW,EACX,EAAsB,EACtB,OAAuC;IAEvC,IAAI,CAAC,CAAC,MAAM,UAAU,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;QAC9B,MAAM,IAAI,UAAU,CACnB,wBAAwB,EACxB,+BAA+B,GAAG,EAAE,EACpC;YACC,IAAI,EAAE,uEAAuE;SAC7E,CACD,CAAC;IACH,CAAC;IAED,MAAM,QAAQ,GAAG,CAAC,MAAM,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;SACvC,MAAM,CACN,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CACvE;SACA,IAAI,EAAE,CAAC;IAET,MAAM,QAAQ,GAAG,OAAO,EAAE,KAAK;QAC9B,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CACtB,OAAO,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC,CACpD;QACF,CAAC,CAAC,QAAQ,CAAC;IAEZ,MAAM,QAAQ,GAAa,EAAE,CAAC;IAC9B,KAAK,MAAM,IAAI,IAAI,QAAQ,EAAE,CAAC;QAC7B,cAAc,CAAC,IAAI,EAAE,kBAAkB,EAAE,QAAQ,CAAC,CAAC;QACnD,MAAM,QAAQ,GAAG,MAAM,oBAAoB,CAC1C,GAAG,EACH,IAAI,EACJ,uBAAuB,EACvB,QAAQ,CACR,CAAC;QAEF,iEAAiE;QACjE,+DAA+D;QAC/D,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,CAAC;QACvD,MAAM,WAAW,GAAG,GAAG,CAAC,OAAO,CAAC;QAChC,IAAI,CAAC,WAAW,IAAI,OAAO,WAAW,KAAK,UAAU,EAAE,CAAC;YACvD,MAAM,IAAI,UAAU,CACnB,kBAAkB,EAClB,UAAU,IAAI,mDAAmD,EACjE;gBACC,IAAI,EAAE,uFAAuF;aAC7F,CACD,CAAC;QACH,CAAC;QAED,MAAM,QAAQ,GAAe,IAAI,WAAW,CAAC,EAAE,CAAC,CAAC;QACjD,MAAM,QAAQ,CAAC,GAAG,EAAE,CAAC;QACrB,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC,CAAC;IAC/C,CAAC;IAED,OAAO,QAAQ,CAAC;AACjB,CAAC"}
@@ -15,6 +15,13 @@
15
15
  import type { AsyncDatabaseConnection } from "../adapters/NapiDbAdapter.js";
16
16
  /** @internal Bind the singleton (called by AtlasProvider). */
17
17
  export declare function setDb(connection: AsyncDatabaseConnection): void;
18
+ /**
19
+ * @internal Unbind the singleton IF it still points at `connection` (called by
20
+ * `AtlasProvider.shutdown()`). Ownership-guarded: when a second provider rebound
21
+ * the singleton, the older provider's shutdown must not clear the newer binding.
22
+ * Without this, `db.*` after shutdown would dereference a closed connection.
23
+ */
24
+ export declare function clearDb(connection: AsyncDatabaseConnection): void;
18
25
  /** @internal Read the singleton (or `undefined` pre-boot). */
19
26
  export declare function getDb(): AsyncDatabaseConnection | undefined;
20
27
  declare const db: AsyncDatabaseConnection;
@@ -1 +1 @@
1
- {"version":3,"file":"db.d.ts","sourceRoot":"","sources":["../../src/services/db.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AAI5E,8DAA8D;AAC9D,wBAAgB,KAAK,CAAC,UAAU,EAAE,uBAAuB,GAAG,IAAI,CAE/D;AAED,8DAA8D;AAC9D,wBAAgB,KAAK,IAAI,uBAAuB,GAAG,SAAS,CAE3D;AAED,QAAA,MAAM,EAAE,EAAE,uBAaR,CAAC;AAEH,eAAe,EAAE,CAAC"}
1
+ {"version":3,"file":"db.d.ts","sourceRoot":"","sources":["../../src/services/db.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AAI5E,8DAA8D;AAC9D,wBAAgB,KAAK,CAAC,UAAU,EAAE,uBAAuB,GAAG,IAAI,CAE/D;AAED;;;;;GAKG;AACH,wBAAgB,OAAO,CAAC,UAAU,EAAE,uBAAuB,GAAG,IAAI,CAEjE;AAED,8DAA8D;AAC9D,wBAAgB,KAAK,IAAI,uBAAuB,GAAG,SAAS,CAE3D;AAED,QAAA,MAAM,EAAE,EAAE,uBAaR,CAAC;AAEH,eAAe,EAAE,CAAC"}
@@ -17,6 +17,16 @@ let instance;
17
17
  export function setDb(connection) {
18
18
  instance = connection;
19
19
  }
20
+ /**
21
+ * @internal Unbind the singleton IF it still points at `connection` (called by
22
+ * `AtlasProvider.shutdown()`). Ownership-guarded: when a second provider rebound
23
+ * the singleton, the older provider's shutdown must not clear the newer binding.
24
+ * Without this, `db.*` after shutdown would dereference a closed connection.
25
+ */
26
+ export function clearDb(connection) {
27
+ if (instance === connection)
28
+ instance = undefined;
29
+ }
20
30
  /** @internal Read the singleton (or `undefined` pre-boot). */
21
31
  export function getDb() {
22
32
  return instance;
@@ -1 +1 @@
1
- {"version":3,"file":"db.js","sourceRoot":"","sources":["../../src/services/db.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAIH,IAAI,QAA6C,CAAC;AAElD,8DAA8D;AAC9D,MAAM,UAAU,KAAK,CAAC,UAAmC;IACxD,QAAQ,GAAG,UAAU,CAAC;AACvB,CAAC;AAED,8DAA8D;AAC9D,MAAM,UAAU,KAAK;IACpB,OAAO,QAAQ,CAAC;AACjB,CAAC;AAED,MAAM,EAAE,GAA4B,IAAI,KAAK,CAAC,EAA6B,EAAE;IAC5E,GAAG,CAAC,OAAO,EAAE,IAAI;QAChB,IAAI,CAAC,QAAQ,EAAE,CAAC;YACf,MAAM,IAAI,KAAK,CACd,iEAAiE;gBAChE,gEAAgE;gBAChE,+DAA+D;gBAC/D,aAAa,CACd,CAAC;QACH,CAAC;QACD,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC;QACpD,OAAO,OAAO,KAAK,KAAK,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;IACnE,CAAC;CACD,CAAC,CAAC;AAEH,eAAe,EAAE,CAAC"}
1
+ {"version":3,"file":"db.js","sourceRoot":"","sources":["../../src/services/db.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAIH,IAAI,QAA6C,CAAC;AAElD,8DAA8D;AAC9D,MAAM,UAAU,KAAK,CAAC,UAAmC;IACxD,QAAQ,GAAG,UAAU,CAAC;AACvB,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,OAAO,CAAC,UAAmC;IAC1D,IAAI,QAAQ,KAAK,UAAU;QAAE,QAAQ,GAAG,SAAS,CAAC;AACnD,CAAC;AAED,8DAA8D;AAC9D,MAAM,UAAU,KAAK;IACpB,OAAO,QAAQ,CAAC;AACjB,CAAC;AAED,MAAM,EAAE,GAA4B,IAAI,KAAK,CAAC,EAA6B,EAAE;IAC5E,GAAG,CAAC,OAAO,EAAE,IAAI;QAChB,IAAI,CAAC,QAAQ,EAAE,CAAC;YACf,MAAM,IAAI,KAAK,CACd,iEAAiE;gBAChE,gEAAgE;gBAChE,+DAA+D;gBAC/D,aAAa,CACd,CAAC;QACH,CAAC;QACD,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC;QACpD,OAAO,OAAO,KAAK,KAAK,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;IACnE,CAAC;CACD,CAAC,CAAC;AAEH,eAAe,EAAE,CAAC"}
@@ -21,15 +21,14 @@ type StateFn<D> = (data: D) => void;
21
21
  export interface FactoryBuilder<T extends BaseEntity> {
22
22
  /** Override specific fields for the next call (reset after consumption). */
23
23
  merge(overrides: Partial<Record<string, unknown>>): FactoryBuilder<T>;
24
- /**
25
- * Declare a named variation of this factory. States are stored on the
26
- * factory itself and don't mutate the caller — `apply()` returns a child
27
- * builder with the state active.
28
- */
24
+ /** Declare a named variation of this factory, stored on the factory's state map. */
29
25
  state(name: string, fn: StateFn<Record<string, unknown>>): FactoryBuilder<T>;
30
26
  /**
31
- * Activate one or more declared states for the next call. Multiple applies
32
- * compose (all applied states fire, in order).
27
+ * Activate one or more declared states for the NEXT build. Multiple applies
28
+ * compose (all fire, in order). NOTE: this mutates the builder's shared
29
+ * pending state and returns the SAME builder for chaining — there is no
30
+ * isolated child builder. The pending set is reset after each make/create
31
+ * (audit 2026-06-13). `merge()` behaves the same way.
33
32
  */
34
33
  apply(...stateNames: string[]): FactoryBuilder<T>;
35
34
  /** Create and persist a single entity (fires lifecycle hooks via `repo.create`). */
@@ -1 +1 @@
1
- {"version":3,"file":"Factory.d.ts","sourceRoot":"","sources":["../../src/testing/Factory.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,EAAkB,KAAK,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAE/E,KAAK,iBAAiB,CAAC,CAAC,SAAS,UAAU,IAAI,UAAU,CAAC,CAAC;AAE3D,mEAAmE;AACnE,KAAK,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,KAAK,IAAI,CAAC;AAEpC,MAAM,WAAW,cAAc,CAAC,CAAC,SAAS,UAAU;IACnD,4EAA4E;IAC5E,KAAK,CAAC,SAAS,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG,cAAc,CAAC,CAAC,CAAC,CAAC;IAEtE;;;;OAIG;IACH,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG,cAAc,CAAC,CAAC,CAAC,CAAC;IAE7E;;;OAGG;IACH,KAAK,CAAC,GAAG,UAAU,EAAE,MAAM,EAAE,GAAG,cAAc,CAAC,CAAC,CAAC,CAAC;IAElD,oFAAoF;IACpF,MAAM,CAAC,EAAE,EAAE,kBAAkB,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IAE3C,kEAAkE;IAClE,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,kBAAkB,GAAG,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC;IAEhE,oFAAoF;IACpF,IAAI,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAEhC,sDAAsD;IACtD,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;IAEnD;;;OAGG;IACH,WAAW,IAAI,CAAC,CAAC;CACjB;AAED;;;;;;;GAOG;AACH,wBAAgB,OAAO,CAAC,CAAC,SAAS,UAAU,EAC3C,WAAW,EAAE,iBAAiB,CAAC,CAAC,CAAC,EACjC,QAAQ,EAAE,MAAM,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACrC,cAAc,CAAC,CAAC,CAAC,CA4FnB"}
1
+ {"version":3,"file":"Factory.d.ts","sourceRoot":"","sources":["../../src/testing/Factory.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,EAAkB,KAAK,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAE/E,KAAK,iBAAiB,CAAC,CAAC,SAAS,UAAU,IAAI,UAAU,CAAC,CAAC;AAE3D,mEAAmE;AACnE,KAAK,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,KAAK,IAAI,CAAC;AAEpC,MAAM,WAAW,cAAc,CAAC,CAAC,SAAS,UAAU;IACnD,4EAA4E;IAC5E,KAAK,CAAC,SAAS,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG,cAAc,CAAC,CAAC,CAAC,CAAC;IAEtE,oFAAoF;IACpF,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG,cAAc,CAAC,CAAC,CAAC,CAAC;IAE7E;;;;;;OAMG;IACH,KAAK,CAAC,GAAG,UAAU,EAAE,MAAM,EAAE,GAAG,cAAc,CAAC,CAAC,CAAC,CAAC;IAElD,oFAAoF;IACpF,MAAM,CAAC,EAAE,EAAE,kBAAkB,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IAE3C,kEAAkE;IAClE,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,kBAAkB,GAAG,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC;IAEhE,oFAAoF;IACpF,IAAI,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAEhC,sDAAsD;IACtD,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;IAEnD;;;OAGG;IACH,WAAW,IAAI,CAAC,CAAC;CACjB;AAED;;;;;;;GAOG;AACH,wBAAgB,OAAO,CAAC,CAAC,SAAS,UAAU,EAC3C,WAAW,EAAE,iBAAiB,CAAC,CAAC,CAAC,EACjC,QAAQ,EAAE,MAAM,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACrC,cAAc,CAAC,CAAC,CAAC,CA0FnB"}
@@ -64,13 +64,11 @@ export function factory(entityClass, defaults) {
64
64
  return data;
65
65
  },
66
66
  makeMany(count) {
67
- // Re-evaluate defaults for each row so `Date.now()` / faker generate distinct values.
67
+ // Re-evaluate defaults for each row so `Date.now()` / faker generate
68
+ // distinct values. `buildData()` reads (never mutates) the pending
69
+ // overrides/states, so they stay stable across iterations on their own.
68
70
  const rows = [];
69
- const capturedOverrides = pendingOverrides;
70
- const capturedStates = pendingStates;
71
71
  for (let i = 0; i < count; i++) {
72
- pendingOverrides = capturedOverrides;
73
- pendingStates = capturedStates;
74
72
  rows.push(buildData());
75
73
  }
76
74
  resetPending();
@@ -1 +1 @@
1
- {"version":3,"file":"Factory.js","sourceRoot":"","sources":["../../src/testing/Factory.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"Factory.js","sourceRoot":"","sources":["../../src/testing/Factory.ts"],"names":[],"mappings":"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"}
@@ -17,6 +17,12 @@ export declare function assertSafeName(name: string, errorCode: string, kind: st
17
17
  * Resolve `fileName` inside `baseDir` and throw if the resulting path escapes
18
18
  * the base — guards against symlink / `../` traversal attacks when loading
19
19
  * migration or seeder files dynamically.
20
+ *
21
+ * Two layers: (1) a logical `path.resolve` check that catches `../` walks, and
22
+ * (2) a `realpath` check that follows symlinks — a symlink FILE sitting inside
23
+ * the base but pointing at `/etc/passwd` passes the logical check yet is caught
24
+ * here. When the file doesn't exist yet (ENOENT), only the logical check
25
+ * applies and the caller's own existence check produces the not-found error.
20
26
  */
21
- export declare function assertPathInsideBase(baseDir: string, fileName: string, errorCode: string, kind: string): string;
27
+ export declare function assertPathInsideBase(baseDir: string, fileName: string, errorCode: string, kind: string): Promise<string>;
22
28
  //# sourceMappingURL=safePath.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"safePath.d.ts","sourceRoot":"","sources":["../../src/utils/safePath.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAMH,uGAAuG;AACvG,wBAAsB,UAAU,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAQ5D;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAC7B,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,GACV,IAAI,CAIN;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CACnC,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,GACV,MAAM,CAUR"}
1
+ {"version":3,"file":"safePath.d.ts","sourceRoot":"","sources":["../../src/utils/safePath.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAMH,uGAAuG;AACvG,wBAAsB,UAAU,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAQ5D;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAC7B,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,GACV,IAAI,CAIN;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,oBAAoB,CACzC,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,GACV,OAAO,CAAC,MAAM,CAAC,CA6BjB"}
@@ -34,13 +34,36 @@ export function assertSafeName(name, errorCode, kind) {
34
34
  * Resolve `fileName` inside `baseDir` and throw if the resulting path escapes
35
35
  * the base — guards against symlink / `../` traversal attacks when loading
36
36
  * migration or seeder files dynamically.
37
+ *
38
+ * Two layers: (1) a logical `path.resolve` check that catches `../` walks, and
39
+ * (2) a `realpath` check that follows symlinks — a symlink FILE sitting inside
40
+ * the base but pointing at `/etc/passwd` passes the logical check yet is caught
41
+ * here. When the file doesn't exist yet (ENOENT), only the logical check
42
+ * applies and the caller's own existence check produces the not-found error.
37
43
  */
38
- export function assertPathInsideBase(baseDir, fileName, errorCode, kind) {
44
+ export async function assertPathInsideBase(baseDir, fileName, errorCode, kind) {
39
45
  const resolved = path.resolve(baseDir, fileName);
40
46
  const base = path.resolve(baseDir);
41
47
  if (!resolved.startsWith(base + path.sep) && resolved !== base) {
42
48
  throw new AtlasError(errorCode, `${kind} path escapes directory: ${fileName}`);
43
49
  }
50
+ // Symlink-aware check: resolve real targets and re-verify containment.
51
+ try {
52
+ const realBase = await fsp.realpath(base);
53
+ const realResolved = await fsp.realpath(resolved);
54
+ if (!realResolved.startsWith(realBase + path.sep) &&
55
+ realResolved !== realBase) {
56
+ throw new AtlasError(errorCode, `${kind} path escapes directory via symlink: ${fileName}`);
57
+ }
58
+ }
59
+ catch (err) {
60
+ // File not yet created — the logical check above stands; let the caller's
61
+ // existence check report the missing file. Re-throw real traversal errors.
62
+ if (err instanceof AtlasError)
63
+ throw err;
64
+ if (err.code !== "ENOENT")
65
+ throw err;
66
+ }
44
67
  return resolved;
45
68
  }
46
69
  //# sourceMappingURL=safePath.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"safePath.js","sourceRoot":"","sources":["../../src/utils/safePath.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,GAAG,MAAM,kBAAkB,CAAC;AACxC,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE1C,uGAAuG;AACvG,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,CAAS;IACzC,IAAI,CAAC;QACJ,MAAM,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;QACpB,OAAO,IAAI,CAAC;IACb,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACd,IAAK,GAA6B,CAAC,IAAI,KAAK,QAAQ;YAAE,OAAO,KAAK,CAAC;QACnE,MAAM,GAAG,CAAC;IACX,CAAC;AACF,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAC7B,IAAY,EACZ,SAAiB,EACjB,IAAY;IAEZ,IAAI,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QACnD,MAAM,IAAI,UAAU,CAAC,SAAS,EAAE,WAAW,IAAI,UAAU,IAAI,EAAE,CAAC,CAAC;IAClE,CAAC;AACF,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,oBAAoB,CACnC,OAAe,EACf,QAAgB,EAChB,SAAiB,EACjB,IAAY;IAEZ,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;IACjD,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IACnC,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,QAAQ,KAAK,IAAI,EAAE,CAAC;QAChE,MAAM,IAAI,UAAU,CACnB,SAAS,EACT,GAAG,IAAI,4BAA4B,QAAQ,EAAE,CAC7C,CAAC;IACH,CAAC;IACD,OAAO,QAAQ,CAAC;AACjB,CAAC"}
1
+ {"version":3,"file":"safePath.js","sourceRoot":"","sources":["../../src/utils/safePath.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,GAAG,MAAM,kBAAkB,CAAC;AACxC,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE1C,uGAAuG;AACvG,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,CAAS;IACzC,IAAI,CAAC;QACJ,MAAM,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;QACpB,OAAO,IAAI,CAAC;IACb,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACd,IAAK,GAA6B,CAAC,IAAI,KAAK,QAAQ;YAAE,OAAO,KAAK,CAAC;QACnE,MAAM,GAAG,CAAC;IACX,CAAC;AACF,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAC7B,IAAY,EACZ,SAAiB,EACjB,IAAY;IAEZ,IAAI,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QACnD,MAAM,IAAI,UAAU,CAAC,SAAS,EAAE,WAAW,IAAI,UAAU,IAAI,EAAE,CAAC,CAAC;IAClE,CAAC;AACF,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACzC,OAAe,EACf,QAAgB,EAChB,SAAiB,EACjB,IAAY;IAEZ,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;IACjD,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IACnC,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,QAAQ,KAAK,IAAI,EAAE,CAAC;QAChE,MAAM,IAAI,UAAU,CACnB,SAAS,EACT,GAAG,IAAI,4BAA4B,QAAQ,EAAE,CAC7C,CAAC;IACH,CAAC;IACD,uEAAuE;IACvE,IAAI,CAAC;QACJ,MAAM,QAAQ,GAAG,MAAM,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;QAC1C,MAAM,YAAY,GAAG,MAAM,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;QAClD,IACC,CAAC,YAAY,CAAC,UAAU,CAAC,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC;YAC7C,YAAY,KAAK,QAAQ,EACxB,CAAC;YACF,MAAM,IAAI,UAAU,CACnB,SAAS,EACT,GAAG,IAAI,wCAAwC,QAAQ,EAAE,CACzD,CAAC;QACH,CAAC;IACF,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACd,0EAA0E;QAC1E,2EAA2E;QAC3E,IAAI,GAAG,YAAY,UAAU;YAAE,MAAM,GAAG,CAAC;QACzC,IAAK,GAA6B,CAAC,IAAI,KAAK,QAAQ;YAAE,MAAM,GAAG,CAAC;IACjE,CAAC;IACD,OAAO,QAAQ,CAAC;AACjB,CAAC"}
Binary file
Binary file
Binary file
Binary file
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@c9up/atlas",
3
- "version": "0.1.9",
3
+ "version": "0.1.10",
4
4
  "description": "Atlas — Data Mapper ORM for the Ream framework",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -25,7 +25,7 @@ import {
25
25
  type AsyncDatabaseConnection,
26
26
  createNapiConnection,
27
27
  } from "./adapters/NapiDbAdapter.js";
28
- import { setAtlasDialect } from "./query/native.js";
28
+ import { clearCastRegistry, setAtlasDialect } from "./query/native.js";
29
29
  import {
30
30
  type DatabaseAdapter,
31
31
  MigrationRunner,
@@ -221,6 +221,14 @@ export default class AtlasProvider {
221
221
  const named = [...this.#connections.entries()];
222
222
  const results = await Promise.allSettled(named.map(([, c]) => c.close()));
223
223
  this.#connections.clear();
224
+
225
+ // Release the module-level singletons this provider populated at boot so a
226
+ // re-boot starts clean and `db.*` can't dereference a now-closed handle.
227
+ // `clearDb` is ownership-guarded, so clearing every connection only unbinds
228
+ // the one still owning the singleton.
229
+ const { clearDb } = await import("./services/db.js");
230
+ for (const [, conn] of named) clearDb(conn);
231
+ clearCastRegistry();
224
232
  const errors = results
225
233
  .map((r, i) =>
226
234
  r.status === "rejected" ? { name: named[i][0], error: r.reason } : null,
package/src/BaseEntity.ts CHANGED
@@ -11,18 +11,23 @@
11
11
  * @implements FR29, FR35, stories 32.1 through 32.5
12
12
  */
13
13
 
14
+ import { getRelationMetadata } from "./decorators/entity.js";
14
15
  import { MassAssignmentError } from "./errors.js";
16
+ import {
17
+ COLUMN_SERIALIZE_KEY,
18
+ COMPUTED_KEY,
19
+ type ColumnSerializeConfig,
20
+ } from "./metadata-keys.js";
15
21
 
16
22
  export interface DomainEvent {
17
23
  name: string;
18
24
  data: Record<string, unknown>;
19
25
  }
20
26
 
21
- /** Symbol metadata key for the computed-property registry on an entity class. */
22
- export const COMPUTED_KEY = Symbol.for("atlas:computed");
23
-
24
- /** Symbol metadata key for the serialize-as / serializer overrides on columns. */
25
- export const COLUMN_SERIALIZE_KEY = Symbol.for("atlas:columnSerialize");
27
+ // COMPUTED_KEY / COLUMN_SERIALIZE_KEY / ColumnSerializeConfig moved to
28
+ // ./metadata-keys.js to break the BaseEntity ↔ entity-decorator runtime cycle
29
+ // (fallow 2026-06-14). Re-exported here for backward compatibility.
30
+ export { COLUMN_SERIALIZE_KEY, COMPUTED_KEY };
26
31
 
27
32
  /** Symbol property key used by entities to back-reference their hydrating repo. */
28
33
  export const REPO_REF = Symbol.for("atlas:repoRef");
@@ -115,13 +120,7 @@ export type RelationProxy =
115
120
  | BelongsToRelationProxy
116
121
  | ManyToManyRelationProxy;
117
122
 
118
- /** Per-column serialization config (populated by @Column options). */
119
- export interface ColumnSerializeConfig {
120
- /** Rename this column at toJSON time (e.g. `password` → `passwordHash`). Null = hidden. */
121
- serializeAs?: string | null;
122
- /** Transform function applied to the value at toJSON time. */
123
- serialize?: (value: unknown) => unknown;
124
- }
123
+ export type { ColumnSerializeConfig };
125
124
 
126
125
  /**
127
126
  * Internal reserved keys on BaseEntity that must never be treated as database
@@ -495,6 +494,13 @@ export class BaseEntity {
495
494
  ctor.visible && ctor.visible.length > 0 ? new Set(ctor.visible) : null;
496
495
 
497
496
  const serializeConfig = getColumnSerializeConfig(ctor);
497
+ // Relation `serializeAs` overrides — a preloaded relation is a plain
498
+ // property on the instance, so it shows up in `Object.keys` below. Without
499
+ // this map the `serializeAs` declared on @HasOne/@HasMany/etc. was silently
500
+ // ignored (the relation always serialized under its property name).
501
+ const relByKey = new Map(
502
+ getRelationMetadata(ctor).map((r) => [r.propertyKey, r]),
503
+ );
498
504
  const result: Record<string, unknown> = {};
499
505
 
500
506
  // Regular columns (respecting hidden/visible + serialize overrides)
@@ -503,6 +509,15 @@ export class BaseEntity {
503
509
  if (visible && !visible.has(key)) continue;
504
510
  if (hidden.has(key)) continue;
505
511
 
512
+ // Preloaded relation: honour its `serializeAs` (rename, or `null` hides
513
+ // it from the JSON). The nested entity serializes via its own toJSON.
514
+ const rel = relByKey.get(key);
515
+ if (rel) {
516
+ if (rel.serializeAs === null) continue;
517
+ result[rel.serializeAs ?? key] = this[key];
518
+ continue;
519
+ }
520
+
506
521
  const cfg = serializeConfig[key];
507
522
  if (cfg?.serializeAs === null) continue; // explicit hide
508
523
 
@@ -351,15 +351,10 @@ export class BaseRepository<T extends BaseEntity> {
351
351
  // ─── Finders ──────────────────────────────────────────────
352
352
 
353
353
  async find(id: string | number | bigint): Promise<T | null> {
354
- const wheres: Array<Record<string, unknown>> = [
355
- { column: this.#primaryKey, operator: "=", value: id, type: "and" },
356
- ];
357
- this.#appendSoftScope(wheres);
358
- const { sql, params } = this.#compileSelect({ wheres, limit: 1 });
359
- const rows = await this.#db.query<Row>(sql, params);
360
- const row = rows[0];
361
- if (!row) return null;
362
- return this.#hydrate(row);
354
+ // Route through the query builder so the read hooks (beforeFind/afterFind)
355
+ // fire and a `beforeFind` hook can mutate the query — the previous direct
356
+ // `#compileSelect` fast path bypassed every read hook silently.
357
+ return this.query().where(this.#primaryKey, id).first();
363
358
  }
364
359
 
365
360
  async findOrFail(id: string | number): Promise<T> {
@@ -373,60 +368,34 @@ export class BaseRepository<T extends BaseEntity> {
373
368
  }
374
369
 
375
370
  async findBy(column: string, value: unknown): Promise<T | null> {
376
- const col = this.#resolveColumn(column);
377
- const wheres: Array<Record<string, unknown>> = [
378
- { column: col, operator: "=", value, type: "and" },
379
- ];
380
- this.#appendSoftScope(wheres);
381
- const { sql, params } = this.#compileSelect({ wheres, limit: 1 });
382
- const rows = await this.#db.query<Row>(sql, params);
383
- const row = rows[0];
384
- if (!row) return null;
385
- return this.#hydrate(row);
371
+ // Through the builder for read-hook parity (see `find`).
372
+ return this.query().where(column, value).first();
386
373
  }
387
374
 
388
375
  async all(): Promise<T[]> {
389
- const wheres: Array<Record<string, unknown>> = [];
390
- this.#appendSoftScope(wheres);
391
- return this.#runSelect({ wheres });
376
+ // Through the builder so beforeFetch/afterFetch fire. The builder applies
377
+ // the soft-delete scope by default, exactly like the old fast path.
378
+ return this.query().exec();
392
379
  }
393
380
 
394
381
  async allWithTrashed(): Promise<T[]> {
395
- return this.#runSelect({});
382
+ return this.query().withTrashed().exec();
396
383
  }
397
384
 
398
385
  async onlyTrashed(): Promise<T[]> {
399
386
  if (!this.#softDeletes) return [];
400
- return this.#runSelect({
401
- wheres: [
402
- {
403
- column: "deleted_at",
404
- operator: "IS NOT NULL",
405
- value: null,
406
- type: "and",
407
- },
408
- ],
409
- });
387
+ return this.query().onlyTrashed().exec();
410
388
  }
411
389
 
412
390
  async where(column: string, value: unknown): Promise<T[]> {
413
- const col = this.#resolveColumn(column);
414
- const wheres: Array<Record<string, unknown>> = [
415
- { column: col, operator: "=", value, type: "and" },
416
- ];
417
- this.#appendSoftScope(wheres);
418
- // Order by the resolved primary key (DESC = most recent insert first when
419
- // the PK is an auto-increment integer or a monotonic UUID). Previously
420
- // this hard-coded `rowid DESC`, which is a SQLite-only pseudo-column and
421
- // blew up on Postgres/MySQL the moment the app ran against a real driver.
422
- // Using the PK works on every dialect and matches the user's actual
423
- // schema — the ordering contract is "most recent first by PK" for
424
- // `repo.where(col, val)` as a convenience finder.
425
- const pkCol = camelToSnake(this.#primaryKey);
426
- return this.#runSelect({
427
- wheres,
428
- orderBy: [{ column: pkCol, direction: "desc" }],
429
- });
391
+ // Order by the primary key (DESC = most recent insert first when the PK is
392
+ // an auto-increment integer or a monotonic UUID). The ordering contract is
393
+ // "most recent first by PK" for `repo.where(col, val)` as a convenience
394
+ // finder. Through the builder for read-hook parity (see `find`).
395
+ return this.query()
396
+ .where(column, value)
397
+ .orderBy(this.#primaryKey, "desc")
398
+ .exec();
430
399
  }
431
400
 
432
401
  // ─── Create / Save / Delete ───────────────────────────────
@@ -450,6 +419,7 @@ export class BaseRepository<T extends BaseEntity> {
450
419
  await this.#insert(entity);
451
420
  await fireHooks(this.#entityClass, "afterCreate", entity);
452
421
  await fireHooks(this.#entityClass, "afterSave", entity);
422
+ await this.#dispatchDomainEvents(entity);
453
423
  return entity;
454
424
  }
455
425
 
@@ -494,6 +464,17 @@ export class BaseRepository<T extends BaseEntity> {
494
464
  }
495
465
  await fireHooks(this.#entityClass, "afterSave", entity);
496
466
 
467
+ await this.#dispatchDomainEvents(entity);
468
+ }
469
+
470
+ /**
471
+ * Flush the entity's accumulated domain events through `onDomainEvents`.
472
+ * On dispatch failure the events are re-queued on the entity and the error
473
+ * propagates, so a caller can retry without losing them. Shared by `save`,
474
+ * `create` and `createMany` — every persistence path that produces a live
475
+ * entity must dispatch, otherwise events silently vanish on batch inserts.
476
+ */
477
+ async #dispatchDomainEvents(entity: BaseEntity): Promise<void> {
497
478
  const events = entity.flushDomainEvents();
498
479
  if (events.length > 0 && this.onDomainEvents) {
499
480
  try {
@@ -576,6 +557,9 @@ export class BaseRepository<T extends BaseEntity> {
576
557
  await fireHooks(this.#entityClass, "afterCreate", e);
577
558
  await fireHooks(this.#entityClass, "afterSave", e);
578
559
  }
560
+ for (const e of entities) {
561
+ await this.#dispatchDomainEvents(e);
562
+ }
579
563
  return entities;
580
564
  }
581
565
 
@@ -908,43 +892,6 @@ export class BaseRepository<T extends BaseEntity> {
908
892
 
909
893
  // ─── Private helpers ──────────────────────────────────────
910
894
 
911
- #compileSelect(opts: {
912
- wheres?: Array<Record<string, unknown>>;
913
- orderBy?: Array<Record<string, unknown>>;
914
- limit?: number;
915
- }): { sql: string; params: unknown[] } {
916
- const spec = {
917
- kind: "select",
918
- table: this.#tableName,
919
- select: ["*"],
920
- wheres: opts.wheres ?? [],
921
- orderBy: opts.orderBy ?? [],
922
- groupBy: [],
923
- having: [],
924
- limit: opts.limit ?? null,
925
- offset: null,
926
- distinct: false,
927
- ctes: [],
928
- unions: [],
929
- // Cast WHERE params on native-typed columns (uuid/timestamp/…) so a
930
- // `WHERE id = $1` on a uuid PK emits `$1::uuid` — otherwise Postgres
931
- // rejects it with `operator does not exist: uuid = text`.
932
- casts: this.#castTypes,
933
- };
934
- const compiled = compileStatementNative(spec, this.#dialect);
935
- return { sql: compiled.statements[0], params: compiled.params };
936
- }
937
-
938
- async #runSelect(opts: {
939
- wheres?: Array<Record<string, unknown>>;
940
- orderBy?: Array<Record<string, unknown>>;
941
- limit?: number;
942
- }): Promise<T[]> {
943
- const { sql, params } = this.#compileSelect(opts);
944
- const rows = await this.#db.query<Row>(sql, params);
945
- return rows.map((r) => this.#hydrate(r));
946
- }
947
-
948
895
  async #runDelete(wheres: Array<Record<string, unknown>>): Promise<void> {
949
896
  const compiled = compileStatementNative(
950
897
  { kind: "delete", table: this.#tableName, wheres },
@@ -1023,17 +970,6 @@ export class BaseRepository<T extends BaseEntity> {
1023
970
  return {};
1024
971
  }
1025
972
 
1026
- #appendSoftScope(wheres: Array<Record<string, unknown>>): void {
1027
- if (this.#softDeletes) {
1028
- wheres.push({
1029
- column: "deleted_at",
1030
- operator: "IS NULL",
1031
- value: null,
1032
- type: "and",
1033
- });
1034
- }
1035
- }
1036
-
1037
973
  async #insert(entity: T): Promise<void> {
1038
974
  // Auto-generate the PK when declared via `@PrimaryKey({ generated })`.
1039
975
  this.#applyPrimaryKeyGenerator(entity);
@@ -1314,6 +1250,10 @@ export class BaseRepository<T extends BaseEntity> {
1314
1250
  const relatedRepo = new BaseRepository<BaseEntity>(relatedClass, this.#db, {
1315
1251
  dialect: this.#dialect,
1316
1252
  });
1253
+ // Propagate the domain-event sink so entities persisted through a relation
1254
+ // proxy (user.related('posts').create(...)) dispatch their events too —
1255
+ // otherwise the related entity's events silently vanish.
1256
+ relatedRepo.onDomainEvents = this.onDomainEvents;
1317
1257
  const db = this.#db;
1318
1258
 
1319
1259
  // FK column naming: belongsTo stores the FK on THIS side; has* / m2m on the OTHER side.