joist-codegen 2.4.0-next.4 → 2.4.0-next.40

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 (176) hide show
  1. package/build/EntityDbMetadata.cjs +21 -3
  2. package/build/EntityDbMetadata.cjs.map +1 -1
  3. package/build/EntityDbMetadata.d.cts +28 -25
  4. package/build/EntityDbMetadata.d.cts.map +1 -1
  5. package/build/EntityDbMetadata.d.mts +28 -25
  6. package/build/EntityDbMetadata.d.mts.map +1 -1
  7. package/build/EntityDbMetadata.js +21 -3
  8. package/build/EntityDbMetadata.js.map +1 -1
  9. package/build/assignTags.d.cts +1 -2
  10. package/build/assignTags.d.cts.map +1 -1
  11. package/build/assignTags.d.mts +1 -2
  12. package/build/assignTags.d.mts.map +1 -1
  13. package/build/codemods/Codemod.d.cts +1 -2
  14. package/build/codemods/Codemod.d.cts.map +1 -1
  15. package/build/codemods/Codemod.d.mts +1 -2
  16. package/build/codemods/Codemod.d.mts.map +1 -1
  17. package/build/codemods/JscodeshiftMod.d.cts +1 -2
  18. package/build/codemods/JscodeshiftMod.d.cts.map +1 -1
  19. package/build/codemods/JscodeshiftMod.d.mts +1 -2
  20. package/build/codemods/JscodeshiftMod.d.mts.map +1 -1
  21. package/build/codemods/codemod_0001-rename_has_async_property.d.cts +2 -2
  22. package/build/codemods/codemod_0001-rename_has_async_property.d.cts.map +1 -1
  23. package/build/codemods/codemod_0001-rename_has_async_property.d.mts +2 -2
  24. package/build/codemods/codemod_0001-rename_has_async_property.d.mts.map +1 -1
  25. package/build/codemods/codemod_0002-rename_async_query_fields.d.cts +2 -2
  26. package/build/codemods/codemod_0002-rename_async_query_fields.d.cts.map +1 -1
  27. package/build/codemods/codemod_0002-rename_async_query_fields.d.mts +2 -2
  28. package/build/codemods/codemod_0002-rename_async_query_fields.d.mts.map +1 -1
  29. package/build/codemods/codemod_0003-rename_find_paginated.d.cts +2 -2
  30. package/build/codemods/codemod_0003-rename_find_paginated.d.cts.map +1 -1
  31. package/build/codemods/codemod_0003-rename_find_paginated.d.mts +2 -2
  32. package/build/codemods/codemod_0003-rename_find_paginated.d.mts.map +1 -1
  33. package/build/codemods/index.d.cts +3 -4
  34. package/build/codemods/index.d.cts.map +1 -1
  35. package/build/codemods/index.d.mts +3 -4
  36. package/build/codemods/index.d.mts.map +1 -1
  37. package/build/config.cjs +1 -1
  38. package/build/config.cjs.map +1 -1
  39. package/build/config.d.cts +27 -27
  40. package/build/config.d.cts.map +1 -1
  41. package/build/config.d.mts +27 -27
  42. package/build/config.d.mts.map +1 -1
  43. package/build/config.js +1 -1
  44. package/build/config.js.map +1 -1
  45. package/build/docs/cache.cjs.map +1 -1
  46. package/build/docs/cache.d.cts +2 -3
  47. package/build/docs/cache.d.cts.map +1 -1
  48. package/build/docs/cache.d.mts +2 -3
  49. package/build/docs/cache.d.mts.map +1 -1
  50. package/build/docs/cache.js.map +1 -1
  51. package/build/docs/generate-metadata-docs.d.cts +1 -2
  52. package/build/docs/generate-metadata-docs.d.cts.map +1 -1
  53. package/build/docs/generate-metadata-docs.d.mts +1 -2
  54. package/build/docs/generate-metadata-docs.d.mts.map +1 -1
  55. package/build/docs/index.d.cts +3 -3
  56. package/build/docs/index.d.cts.map +1 -1
  57. package/build/docs/index.d.mts +3 -3
  58. package/build/docs/index.d.mts.map +1 -1
  59. package/build/docs/markdown.d.cts +8 -8
  60. package/build/docs/markdown.d.cts.map +1 -1
  61. package/build/docs/markdown.d.mts +8 -8
  62. package/build/docs/markdown.d.mts.map +1 -1
  63. package/build/docs/parsing.d.cts +9 -9
  64. package/build/docs/parsing.d.cts.map +1 -1
  65. package/build/docs/parsing.d.mts +9 -9
  66. package/build/docs/parsing.d.mts.map +1 -1
  67. package/build/findEntityScopes.d.cts +3 -4
  68. package/build/findEntityScopes.d.cts.map +1 -1
  69. package/build/findEntityScopes.d.mts +3 -4
  70. package/build/findEntityScopes.d.mts.map +1 -1
  71. package/build/foreignKeyOrdering.d.cts +1 -2
  72. package/build/foreignKeyOrdering.d.cts.map +1 -1
  73. package/build/foreignKeyOrdering.d.mts +1 -2
  74. package/build/foreignKeyOrdering.d.mts.map +1 -1
  75. package/build/generate.d.cts +3 -4
  76. package/build/generate.d.cts.map +1 -1
  77. package/build/generate.d.mts +3 -4
  78. package/build/generate.d.mts.map +1 -1
  79. package/build/generateEntitiesFile.d.cts +1 -2
  80. package/build/generateEntitiesFile.d.cts.map +1 -1
  81. package/build/generateEntitiesFile.d.mts +1 -2
  82. package/build/generateEntitiesFile.d.mts.map +1 -1
  83. package/build/generateEntityCodegenFile.cjs +5 -3
  84. package/build/generateEntityCodegenFile.cjs.map +1 -1
  85. package/build/generateEntityCodegenFile.d.cts +3 -4
  86. package/build/generateEntityCodegenFile.d.cts.map +1 -1
  87. package/build/generateEntityCodegenFile.d.mts +3 -4
  88. package/build/generateEntityCodegenFile.d.mts.map +1 -1
  89. package/build/generateEntityCodegenFile.js +5 -3
  90. package/build/generateEntityCodegenFile.js.map +1 -1
  91. package/build/generateEntityFile.d.cts +1 -2
  92. package/build/generateEntityFile.d.cts.map +1 -1
  93. package/build/generateEntityFile.d.mts +1 -2
  94. package/build/generateEntityFile.d.mts.map +1 -1
  95. package/build/generateEntityTestFile.d.cts +1 -2
  96. package/build/generateEntityTestFile.d.cts.map +1 -1
  97. package/build/generateEntityTestFile.d.mts +1 -2
  98. package/build/generateEntityTestFile.d.mts.map +1 -1
  99. package/build/generateEnumFile.d.cts +1 -2
  100. package/build/generateEnumFile.d.cts.map +1 -1
  101. package/build/generateEnumFile.d.mts +1 -2
  102. package/build/generateEnumFile.d.mts.map +1 -1
  103. package/build/generateFactoriesFiles.d.cts +1 -2
  104. package/build/generateFactoriesFiles.d.cts.map +1 -1
  105. package/build/generateFactoriesFiles.d.mts +1 -2
  106. package/build/generateFactoriesFiles.d.mts.map +1 -1
  107. package/build/generateFlushFunction.d.cts +3 -4
  108. package/build/generateFlushFunction.d.cts.map +1 -1
  109. package/build/generateFlushFunction.d.mts +3 -4
  110. package/build/generateFlushFunction.d.mts.map +1 -1
  111. package/build/generateMetadataFile.cjs +2 -1
  112. package/build/generateMetadataFile.cjs.map +1 -1
  113. package/build/generateMetadataFile.d.cts +2 -3
  114. package/build/generateMetadataFile.d.cts.map +1 -1
  115. package/build/generateMetadataFile.d.mts +2 -3
  116. package/build/generateMetadataFile.d.mts.map +1 -1
  117. package/build/generateMetadataFile.js +2 -1
  118. package/build/generateMetadataFile.js.map +1 -1
  119. package/build/generatePgEnumFile.d.cts +1 -2
  120. package/build/generatePgEnumFile.d.cts.map +1 -1
  121. package/build/generatePgEnumFile.d.mts +1 -2
  122. package/build/generatePgEnumFile.d.mts.map +1 -1
  123. package/build/index.d.cts +4 -4
  124. package/build/index.d.cts.map +1 -1
  125. package/build/index.d.mts +4 -4
  126. package/build/index.d.mts.map +1 -1
  127. package/build/inheritance.d.cts +3 -3
  128. package/build/inheritance.d.cts.map +1 -1
  129. package/build/inheritance.d.mts +3 -3
  130. package/build/inheritance.d.mts.map +1 -1
  131. package/build/installSkills.d.cts +1 -2
  132. package/build/installSkills.d.cts.map +1 -1
  133. package/build/installSkills.d.mts +1 -2
  134. package/build/installSkills.d.mts.map +1 -1
  135. package/build/keywords.d.cts +1 -2
  136. package/build/keywords.d.cts.map +1 -1
  137. package/build/keywords.d.mts +1 -2
  138. package/build/keywords.d.mts.map +1 -1
  139. package/build/loadMetadata.d.cts +7 -8
  140. package/build/loadMetadata.d.cts.map +1 -1
  141. package/build/loadMetadata.d.mts +7 -8
  142. package/build/loadMetadata.d.mts.map +1 -1
  143. package/build/logger.d.cts +3 -4
  144. package/build/logger.d.cts.map +1 -1
  145. package/build/logger.d.mts +3 -4
  146. package/build/logger.d.mts.map +1 -1
  147. package/build/pgMetadata.d.cts +13 -14
  148. package/build/pgMetadata.d.cts.map +1 -1
  149. package/build/pgMetadata.d.mts +13 -14
  150. package/build/pgMetadata.d.mts.map +1 -1
  151. package/build/scanEntityFiles.cjs +6 -1
  152. package/build/scanEntityFiles.cjs.map +1 -1
  153. package/build/scanEntityFiles.d.cts +2 -3
  154. package/build/scanEntityFiles.d.cts.map +1 -1
  155. package/build/scanEntityFiles.d.mts +2 -3
  156. package/build/scanEntityFiles.d.mts.map +1 -1
  157. package/build/scanEntityFiles.js +6 -1
  158. package/build/scanEntityFiles.js.map +1 -1
  159. package/build/sortForeignKeys.d.cts +1 -2
  160. package/build/sortForeignKeys.d.cts.map +1 -1
  161. package/build/sortForeignKeys.d.mts +1 -2
  162. package/build/sortForeignKeys.d.mts.map +1 -1
  163. package/build/symbols.d.cts +116 -117
  164. package/build/symbols.d.cts.map +1 -1
  165. package/build/symbols.d.mts +116 -117
  166. package/build/symbols.d.mts.map +1 -1
  167. package/build/tsdoc.d.cts +1 -2
  168. package/build/tsdoc.d.cts.map +1 -1
  169. package/build/tsdoc.d.mts +1 -2
  170. package/build/tsdoc.d.mts.map +1 -1
  171. package/build/utils.d.cts +21 -22
  172. package/build/utils.d.cts.map +1 -1
  173. package/build/utils.d.mts +21 -22
  174. package/build/utils.d.mts.map +1 -1
  175. package/package.json +9 -9
  176. package/skills/joist-test-factories/SKILL.md +93 -6
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.cts","names":[],"sources":["../src/utils.ts"],"mappings":";;;;iBASgB,YAAY;iBAIZ,cAAc,QAAQ,QAAQ,GAAG;iBA8BjC,gBAAgB,GAAG;iBAKnB,YAAY,QAAQ,QAAQ,GAAG;iBAW/B,YAAY,QAAQ,QAAQ,GAAG;;iBAoB/B,eAAe,GAAG;;iBAelB,kBAAkB,QAAQ,QAAQ,OAAO;cAY5C,4BAAQ;cACR,iCAAa;cACb,iCAAa;cACb,qCAAiB;cACjB,qCAAiB;;iBAGd,gCAAgC,QAAQ,QAAQ,QAAQ,qBAAqB;iBA4C7E,MAAM,GAAG,GAAG,KAAK,GAAG,MAAM;;iBAKpB,eAAe,GAAG,mBAAmB;iBAO3C,KAAK;iBAIL,SAAS,kBAAkB,GAAG,IAAI;iBAclC,WAAW;EAA8B;EAAe;;iBAWxD,EAAE;iBAIF,aAAa;;iBASb,oBAAoB,QAAQ,QAAQ,GAAG"}
1
+ {"version":3,"file":"utils.d.cts","names":[],"sources":["../src/utils.ts"],"mappings":";;;;wBASgB,YAAY;wBAIZ,cAAc,QAAQ,QAAQ,GAAG;wBA8BjC,gBAAgB,GAAG;wBAKnB,YAAY,QAAQ,QAAQ,GAAG;wBAW/B,YAAY,QAAQ,QAAQ,GAAG;;wBAoB/B,eAAe,GAAG;;wBAelB,kBAAkB,QAAQ,QAAQ,OAAO;qBAY5C,4BAAQ;qBACR,iCAAa;qBACb,iCAAa;qBACb,qCAAiB;qBACjB,qCAAiB;;wBAGd,gCAAgC,QAAQ,QAAQ,QAAQ,qBAAqB;wBA4C7E,MAAM,GAAG,GAAG,KAAK,GAAG,MAAM;;wBAKpB,eAAe,GAAG,mBAAmB;wBAO3C,KAAK;wBAIL,SAAS,kBAAkB,GAAG,IAAI;wBAclC,WAAW;EAA8B;EAAe;;wBAWxD,EAAE;wBAIF,aAAa;;wBASb,oBAAoB,QAAQ,QAAQ,GAAG"}
package/build/utils.d.mts CHANGED
@@ -2,35 +2,34 @@ import { Config } from "./config.mjs";
2
2
  import { Table } from "./pgMetadata.mjs";
3
3
  import { DatabaseColumnType, PrimitiveTypescriptType } from "./EntityDbMetadata.mjs";
4
4
  //#region src/utils.d.ts
5
- declare function assertNever(x: never): never;
6
- declare function isEntityTable(config: Config, t: Table): boolean;
7
- declare function isSubClassTable(t: Table): boolean;
8
- declare function isEnumTable(config: Config, t: Table): boolean;
9
- declare function isJoinTable(config: Config, t: Table): boolean;
5
+ export declare function assertNever(x: never): never;
6
+ export declare function isEntityTable(config: Config, t: Table): boolean;
7
+ export declare function isSubClassTable(t: Table): boolean;
8
+ export declare function isEnumTable(config: Config, t: Table): boolean;
9
+ export declare function isJoinTable(config: Config, t: Table): boolean;
10
10
  /** Whether a join table has a surrogate `id` primary key (vs. an id-less FK-pair composite PK). */
11
- declare function joinTableHasId(t: Table): boolean;
11
+ export declare function joinTableHasId(t: Table): boolean;
12
12
  /** Converts `projects` to `Project`. */
13
- declare function tableToEntityName(config: Config, table: Table): string;
14
- declare const dateCode: import("ts-poet").Code;
15
- declare const plainDateCode: import("ts-poet").Code;
16
- declare const plainTimeCode: import("ts-poet").Code;
17
- declare const plainDateTimeCode: import("ts-poet").Code;
18
- declare const zonedDateTimeCode: import("ts-poet").Code;
13
+ export declare function tableToEntityName(config: Config, table: Table): string;
14
+ export declare const dateCode: import("ts-poet").Code;
15
+ export declare const plainDateCode: import("ts-poet").Code;
16
+ export declare const plainTimeCode: import("ts-poet").Code;
17
+ export declare const plainDateTimeCode: import("ts-poet").Code;
18
+ export declare const zonedDateTimeCode: import("ts-poet").Code;
19
19
  /** Maps db types, i.e. `int`, to JS types, i.e. `number`. */
20
- declare function mapSimpleDbTypeToTypescriptType(config: Config, dbType: DatabaseColumnType): PrimitiveTypescriptType;
21
- declare function merge<T>(a: T[], b: T[]): T[];
20
+ export declare function mapSimpleDbTypeToTypescriptType(config: Config, dbType: DatabaseColumnType): PrimitiveTypescriptType;
21
+ export declare function merge<T>(a: T[], b: T[]): T[];
22
22
  /** Returns true if `p` is resolved, otherwise false if it is rejected. */
23
- declare function trueIfResolved(p: Promise<unknown>): Promise<boolean>;
24
- declare function fail(message?: string): never;
25
- declare function sortKeys<T extends object>(o: T): T;
26
- declare function parseOrder(order: string | undefined): {
23
+ export declare function trueIfResolved(p: Promise<unknown>): Promise<boolean>;
24
+ export declare function fail(message?: string): never;
25
+ export declare function sortKeys<T extends object>(o: T): T;
26
+ export declare function parseOrder(order: string | undefined): {
27
27
  field: string;
28
28
  direction: "ASC" | "DESC";
29
29
  } | undefined;
30
- declare function q(s: string | undefined): string;
31
- declare function uncapitalize(s: string): string;
30
+ export declare function q(s: string | undefined): string;
31
+ export declare function uncapitalize(s: string): string;
32
32
  /** Uses the same schema selection for metadata and test database cleanup. */
33
- declare function shouldIncludeSchema(config: Config, t: Table): boolean;
33
+ export declare function shouldIncludeSchema(config: Config, t: Table): boolean;
34
34
  //#endregion
35
- export { assertNever, dateCode, fail, isEntityTable, isEnumTable, isJoinTable, isSubClassTable, joinTableHasId, mapSimpleDbTypeToTypescriptType, merge, parseOrder, plainDateCode, plainDateTimeCode, plainTimeCode, q, shouldIncludeSchema, sortKeys, tableToEntityName, trueIfResolved, uncapitalize, zonedDateTimeCode };
36
35
  //# sourceMappingURL=utils.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.mts","names":[],"sources":["../src/utils.ts"],"mappings":";;;;iBASgB,YAAY;iBAIZ,cAAc,QAAQ,QAAQ,GAAG;iBA8BjC,gBAAgB,GAAG;iBAKnB,YAAY,QAAQ,QAAQ,GAAG;iBAW/B,YAAY,QAAQ,QAAQ,GAAG;;iBAoB/B,eAAe,GAAG;;iBAelB,kBAAkB,QAAQ,QAAQ,OAAO;cAY5C,4BAAQ;cACR,iCAAa;cACb,iCAAa;cACb,qCAAiB;cACjB,qCAAiB;;iBAGd,gCAAgC,QAAQ,QAAQ,QAAQ,qBAAqB;iBA4C7E,MAAM,GAAG,GAAG,KAAK,GAAG,MAAM;;iBAKpB,eAAe,GAAG,mBAAmB;iBAO3C,KAAK;iBAIL,SAAS,kBAAkB,GAAG,IAAI;iBAclC,WAAW;EAA8B;EAAe;;iBAWxD,EAAE;iBAIF,aAAa;;iBASb,oBAAoB,QAAQ,QAAQ,GAAG"}
1
+ {"version":3,"file":"utils.d.mts","names":[],"sources":["../src/utils.ts"],"mappings":";;;;wBASgB,YAAY;wBAIZ,cAAc,QAAQ,QAAQ,GAAG;wBA8BjC,gBAAgB,GAAG;wBAKnB,YAAY,QAAQ,QAAQ,GAAG;wBAW/B,YAAY,QAAQ,QAAQ,GAAG;;wBAoB/B,eAAe,GAAG;;wBAelB,kBAAkB,QAAQ,QAAQ,OAAO;qBAY5C,4BAAQ;qBACR,iCAAa;qBACb,iCAAa;qBACb,qCAAiB;qBACjB,qCAAiB;;wBAGd,gCAAgC,QAAQ,QAAQ,QAAQ,qBAAqB;wBA4C7E,MAAM,GAAG,GAAG,KAAK,GAAG,MAAM;;wBAKpB,eAAe,GAAG,mBAAmB;wBAO3C,KAAK;wBAIL,SAAS,kBAAkB,GAAG,IAAI;wBAclC,WAAW;EAA8B;EAAe;;wBAWxD,EAAE;wBAIF,aAAa;;wBASb,oBAAoB,QAAQ,QAAQ,GAAG"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "joist-codegen",
3
- "version": "2.4.0-next.4",
3
+ "version": "2.4.0-next.40",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "repository": {
@@ -48,32 +48,32 @@
48
48
  "skills"
49
49
  ],
50
50
  "peerDependencies": {
51
- "joist-utils": "2.4.0-next.4",
51
+ "joist-utils": "2.4.0-next.40",
52
52
  "knex": "^3.1.0",
53
53
  "pg": "^8.23.0"
54
54
  },
55
55
  "dependencies": {
56
56
  "@dprint/formatter": "^0.5.1",
57
- "@dprint/json": "^0.21.3",
58
- "@inquirer/prompts": "^8.6.0",
57
+ "@dprint/json": "^0.25.2",
58
+ "@inquirer/prompts": "^8.7.3",
59
59
  "@types/jscodeshift": "^17.3.0",
60
60
  "@types/pluralize": "^0.0.33",
61
- "change-case": "^4.1.2",
61
+ "change-case": "^5.4.4",
62
62
  "fast-glob": "^3.3.3",
63
63
  "jscodeshift": "^17.4.0",
64
64
  "jsonc-parser": "^3.3.1",
65
65
  "pluralize": "^8.0.0",
66
- "prettier": "^3.9.6",
66
+ "prettier": "^3.9.10",
67
67
  "ts-poet": "^6.12.0",
68
- "zod": "^4.4.3"
68
+ "zod": "^4.6.5"
69
69
  },
70
70
  "devDependencies": {
71
71
  "@swc/jest": "^0.2.39",
72
72
  "@types/jest": "^30.0.0",
73
- "@types/node": "^26.2.0",
73
+ "@types/node": "^26.6.5",
74
74
  "jest": "30.4.2",
75
75
  "tsconfig-paths": "^4.2.0",
76
- "tsx": "^4.23.12",
76
+ "tsx": "^4.23.15",
77
77
  "typescript": "7.0.2"
78
78
  }
79
79
  }
@@ -1,14 +1,18 @@
1
1
  ---
2
2
  name: joist-test-factories
3
- description: Use when writing or refactoring Joist ORM tests with newTestInstance factories, DeepNew, run or makeRun, toMatchEntity, and nested Given graphs.
3
+ description: Use when writing or refactoring Joist ORM entity or API-layer tests with newTestInstance factories, DeepNew, toMatchEntity, and nested Given graphs, or deciding whether EntityManager isolation with run, makeRun, or RunPlugin is needed.
4
4
  ---
5
5
 
6
6
  # Joist Test Factories
7
7
 
8
- Write isolated, succinct tests that use Joist's factories for setup, retain
8
+ Write focused, succinct tests that use Joist's factories for setup, retain
9
9
  typed entity references through the action, and assert those same entities
10
10
  with `toMatchEntity`.
11
11
 
12
+ Entity tests normally use one `EntityManager` for Given, When, and Then.
13
+ Use `run` for API-layer tests and other production boundaries that need a
14
+ separate `EntityManager`, not as a default wrapper around every test action.
15
+
12
16
  Authoritative references:
13
17
 
14
18
  - <https://joist-orm.io/testing/test-factories/>
@@ -27,10 +31,16 @@ Authoritative references:
27
31
  4. Keep using the factory-created `DeepNew` graph after same-`EntityManager`
28
32
  actions or actions run through Joist's test `run` helper. Do not reload the
29
33
  same rows merely to assert against them.
30
- 5. Use `run` or the project's `makeRun` wrapper when production code needs an
34
+ 5. Keep entity tests in the original test `EntityManager`; they normally do not
35
+ need EM isolation or `RunPlugin`. Use `run` or the project's `makeRun` wrapper
36
+ for API/request-layer tests or another production boundary that needs an
31
37
  isolated `EntityManager`; it flushes Given state and mirrors the callback's
32
38
  flushed Joist writes into the original test graph.
33
- 6. Assert entity state and relationships with `toMatchEntity`.
39
+ Do not `await em.flush()` right before `run` or a `makeRun` helper: it
40
+ already flushes the Given graph, use as few `em.flush()` calls as possible.
41
+ 6. Assert entity state and relationships with `toMatchEntity`. Prefer checking
42
+ side effects through retained entities and their relations instead of using
43
+ `em.getEntities` to discover what the action created or changed.
34
44
  7. Prefer focused tests for one behavior over a single scenario that exercises
35
45
  unrelated updates at several graph levels.
36
46
 
@@ -41,6 +51,23 @@ happens, Then the state of the world is Z": the factory-created graph is the
41
51
  Given, the code under test is the When, and `toMatchEntity` proves the Then.
42
52
  Mark the three phases with `// Given`, `// When`, `// Then` comments:
43
53
 
54
+ For an entity test, act directly on the factory-created entity:
55
+
56
+ ```ts
57
+ it.withCtx("updates a book's title", async (ctx) => {
58
+ // Given a book with its original title
59
+ const book = newBook(ctx.em, { title: "Before" });
60
+
61
+ // When we change the book's title
62
+ book.title = "After";
63
+
64
+ // Then the book has the new title
65
+ expect(book).toMatchEntity({ title: "After" });
66
+ });
67
+ ```
68
+
69
+ For an API-layer test, use `run` to model its separate production unit of work:
70
+
44
71
  ```ts
45
72
  it.withCtx("updates a book", async (ctx) => {
46
73
  // Given an author with one book
@@ -241,6 +268,23 @@ test-only loads.
241
268
 
242
269
  ## Use run for Production Isolation
243
270
 
271
+ Choose isolation based on the behavior under test:
272
+
273
+ - **Entity tests:** Test entity methods, properties, relationships, and domain
274
+ logic directly with the test's `ctx.em` and factory-created entities. These
275
+ normally do not need `run`, a second EM, or a manually installed `RunPlugin`.
276
+ Flush only when the behavior requires it, such as validation, flush hooks,
277
+ persisted reactive fields, or database constraints. Needing a flush does not
278
+ by itself mean the test needs EM isolation.
279
+ - **API/request-layer tests:** Use `run` or an existing project wrapper such as
280
+ `runMutation` when testing a resolver, endpoint, or service entry point whose
281
+ production unit of work uses a fresh EM. This ensures the action loads its
282
+ own entities rather than inheriting the test's loaded factory graph.
283
+ - **Other boundaries:** Use `run` when a fresh production unit of work is part
284
+ of the behavior being tested. A service/helper that operates on existing
285
+ entities or the caller's EM does not need isolation merely because it is
286
+ production code.
287
+
244
288
  Joist's `run(ctx, fn)` executes `fn` with a fresh production-style context and
245
289
  `EntityManager`. Before the callback, it flushes the test factory graph. As the
246
290
  callback flushes Joist writes, `RunPlugin` mirrors those writes into the
@@ -248,6 +292,11 @@ original test `EntityManager`. It does not call `EntityManager.refresh`. The
248
292
  callback result is also mapped back to entities from the original test
249
293
  `EntityManager`.
250
294
 
295
+ `run` installs `RunPlugin` automatically on the isolated EM. The plugin bridges
296
+ that production unit of work back to the retained test entities; it is not
297
+ needed when Given, When, and Then already share one EM. Prefer `run` or the
298
+ project's wrapper over manually wiring the plugin.
299
+
251
300
  ```ts
252
301
  const author = newAuthor(ctx.em, { books: [{ title: "Before" }] });
253
302
  const [book] = author.books.get;
@@ -278,8 +327,9 @@ const books = await savedBook.author.load().then((author) => author.books.load()
278
327
  Keep using `author` and `book`. They are the stable test references.
279
328
 
280
329
  Call `em.flush()` directly only when the test intentionally needs a persistence
281
- boundary and its helper does not provide one. Pure entity tests should usually
282
- avoid unnecessary flushes.
330
+ boundary and its helper does not provide one, e.g. to persist one Given state
331
+ before changing it into the next one. Pure entity tests should usually avoid
332
+ unnecessary flushes.
283
333
 
284
334
  ## Register toMatchEntity
285
335
 
@@ -338,6 +388,39 @@ expect((await author.books.load()).map((book) => ({ id: book.id, title: book.tit
338
388
  ]);
339
389
  ```
340
390
 
391
+ ## Assert Side Effects Through the Domain Graph
392
+
393
+ Avoid `em.getEntities(Entity).filter(...)` or `.find(...)` to look up side
394
+ effects for assertions. `getEntities` inspects the `EntityManager`'s tracked
395
+ entities; finding a new child there does not prove that the action attached it
396
+ to the correct parent. Assert through the retained Given graph instead, so the
397
+ test describes the resulting domain state and checks relationship membership.
398
+
399
+ ```ts
400
+ // Less useful: finds a book without proving which author owns it.
401
+ const [createdBook] = ctx.em.getEntities(Book).filter((book) => book.title === "New book");
402
+ expect(createdBook).toMatchEntity({ title: "New book" });
403
+ ```
404
+
405
+ ```ts
406
+ it.withCtx("adds a book to the existing author", async (ctx) => {
407
+ // Given an author with an existing book
408
+ const author = newAuthor(ctx.em, { books: [{}] });
409
+ const [existingBook] = author.books.get;
410
+
411
+ // When we add another book to that author
412
+ await run(ctx, (ctx) => addBook(ctx, { authorId: author.id, title: "New book" }));
413
+
414
+ // Then the author keeps the existing book and owns the new book
415
+ expect(author).toMatchEntity({ books: [existingBook, { title: "New book" }] });
416
+ });
417
+ ```
418
+
419
+ Use the action result when a new entity has no natural path from the Given
420
+ graph. If neither a relation nor the result exposes it, use an intentional
421
+ domain query. Reserve `getEntities` assertions for tests whose behavior under
422
+ test is specifically which entities the `EntityManager` tracks.
423
+
341
424
  ## Keep Tests Focused
342
425
 
343
426
  One test should describe one coherent boundary. Split a large graph mutation
@@ -390,8 +473,12 @@ reuse decisions instead of replacing factories with manual setup.
390
473
  - Given state uses minimal factory opts and does not invoke the behavior under
391
474
  test.
392
475
  - Important entities have direct, role-based `const` names.
476
+ - Entity tests use the original test EM unless the behavior specifically needs
477
+ a separate production unit of work; a flush alone does not require `run`.
393
478
  - Separate production units of work use `run`/`makeRun` and flush normally.
394
479
  - Assertions reuse loaded factory entities with `toMatchEntity`.
480
+ - Side-effect assertions follow domain relations or action results instead of
481
+ scanning `em.getEntities` for created or changed entities.
395
482
  - No unnecessary reloads, `load()` calls, assertion awaits, or direct flushes
396
483
  remain.
397
484
  - Each test covers one coherent boundary.