@deterministic-code/datasources 0.1.0 → 0.1.1

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 (302) hide show
  1. package/dist/ArrayReader.d.ts +12 -0
  2. package/dist/BatchCommandIBulkWriteStrategy.d.ts +33 -0
  3. package/dist/BuiltRepository.d.ts +16 -0
  4. package/dist/BulkCRUDRepository.d.ts +17 -0
  5. package/dist/BulkCommand.d.ts +5 -0
  6. package/dist/BulkWriteEnrichment.d.ts +4 -0
  7. package/dist/CRUDRepository.d.ts +40 -0
  8. package/dist/CRUDRepositoryBuilder.d.ts +19 -0
  9. package/dist/CRUDRepositoryOptions.d.ts +7 -0
  10. package/dist/CRUDRepositoryStatements.d.ts +17 -0
  11. package/dist/CascadeDeleteEnrichmentStrategy.d.ts +7 -0
  12. package/dist/CascadeDeleteUpdateEnrichmentStrategy.d.ts +8 -0
  13. package/dist/CommandMiddlewareHost.d.ts +22 -0
  14. package/dist/ConsoleCommandMiddleware.d.ts +14 -0
  15. package/dist/CreateEnrichmentStrategy.d.ts +14 -0
  16. package/dist/Datasource.d.ts +30 -0
  17. package/dist/DatasourceError.d.ts +3 -0
  18. package/dist/DatasourceFactory.d.ts +12 -0
  19. package/dist/DatasourceOptions.d.ts +1 -0
  20. package/dist/DatasourceUrl.d.ts +12 -0
  21. package/dist/DatasourceUrlParser.d.ts +4 -0
  22. package/dist/DefaultCreateEnrichmentStrategy.d.ts +7 -0
  23. package/dist/DefaultReadonlyEnrichmentStrategy.d.ts +19 -0
  24. package/dist/DefaultUpdateEnrichmentStrategy.d.ts +7 -0
  25. package/dist/DefaultWriteEnrichmentStrategy.d.ts +21 -0
  26. package/dist/DeleteEnrichmentStrategy.d.ts +11 -0
  27. package/dist/DriverLoader.d.ts +5 -0
  28. package/dist/Enrichment.d.ts +13 -0
  29. package/dist/EnrichmentPropertyType.d.ts +7 -0
  30. package/dist/ExpectConsoleMiddleware.d.ts +15 -0
  31. package/dist/FirstResultReader.d.ts +19 -0
  32. package/dist/HashsetOptimizedReadonlyEnrichmentStrategy.d.ts +9 -0
  33. package/dist/IBulkWriteStrategy.d.ts +12 -0
  34. package/dist/ICRUDRepository.d.ts +12 -0
  35. package/dist/ICRUDRepositoryFactory.d.ts +7 -0
  36. package/dist/ICommand.d.ts +20 -0
  37. package/dist/ICommandMiddleware.d.ts +12 -0
  38. package/dist/IConnection.d.ts +12 -0
  39. package/dist/IDatasource.d.ts +18 -0
  40. package/dist/IDatasourceFactory.d.ts +5 -0
  41. package/dist/IDriver.d.ts +19 -0
  42. package/dist/IEnriched.d.ts +6 -0
  43. package/dist/IReader.d.ts +8 -0
  44. package/dist/IReadonlyEnrichmentStrategy.d.ts +7 -0
  45. package/dist/IReadonlyRepository.d.ts +11 -0
  46. package/dist/IRepository.d.ts +4 -0
  47. package/dist/IRepositoryFactory.d.ts +7 -0
  48. package/dist/IRepositoryTree.d.ts +4 -0
  49. package/dist/IRepositoryTreeNode.d.ts +7 -0
  50. package/dist/ITransaction.d.ts +8 -0
  51. package/dist/IUnitOfWork.d.ts +5 -0
  52. package/dist/IWriteEnrichmentStrategy.d.ts +7 -0
  53. package/dist/Information.d.ts +5 -0
  54. package/dist/IsolationLevel.d.ts +7 -0
  55. package/dist/LazyWriteEnrichmentStrategy.d.ts +4 -0
  56. package/dist/NestedToObjectEnrichmentStrategy.d.ts +10 -0
  57. package/dist/Parameter.d.ts +7 -0
  58. package/dist/ParameterDirection.d.ts +7 -0
  59. package/dist/ProviderCatalog.d.ts +7 -0
  60. package/dist/ProviderNotImplementedError.d.ts +4 -0
  61. package/dist/ProviderNotInstalledError.d.ts +4 -0
  62. package/dist/ReadonlyRepository.d.ts +59 -0
  63. package/dist/ReadonlyRepositoryBuilder.d.ts +12 -0
  64. package/dist/ReadonlyRepositoryMappingOptions.d.ts +14 -0
  65. package/dist/ReadonlyRepositoryStatements.d.ts +8 -0
  66. package/dist/RepositoryAccess.d.ts +11 -0
  67. package/dist/RepositoryFactory.d.ts +23 -0
  68. package/dist/RepositoryNotRegisteredError.d.ts +4 -0
  69. package/dist/RepositoryTree.d.ts +6 -0
  70. package/dist/RepositoryTreeNode.d.ts +9 -0
  71. package/dist/Response.d.ts +7 -0
  72. package/dist/Row.d.ts +2 -0
  73. package/dist/SingleCommandIBulkWriteStrategy.d.ts +11 -0
  74. package/dist/StatementMappers.d.ts +16 -0
  75. package/dist/StoredEnrichment.d.ts +11 -0
  76. package/dist/StoredProcedureBulkWriteStrategy.d.ts +27 -0
  77. package/dist/StreamingReader.d.ts +16 -0
  78. package/dist/UnitOfWorkTransaction.d.ts +12 -0
  79. package/dist/UpdateEnrichmentStrategy.d.ts +18 -0
  80. package/dist/WriteMode.d.ts +1 -0
  81. package/dist/builders/Field.d.ts +5 -0
  82. package/dist/builders/Scalar.d.ts +1 -0
  83. package/dist/builders/index.d.ts +2 -0
  84. package/dist/collectAsync.d.ts +1 -0
  85. package/dist/converters/Cell.d.ts +2 -0
  86. package/dist/converters/ChangeCasingConverter.d.ts +7 -0
  87. package/dist/converters/Constructable.d.ts +1 -0
  88. package/dist/converters/MetaDataRowTypeConverter.d.ts +14 -0
  89. package/dist/converters/PluralizerConverter.d.ts +4 -0
  90. package/dist/converters/RegexConverter.d.ts +7 -0
  91. package/dist/converters/ReplaceConverter.d.ts +7 -0
  92. package/dist/converters/RowTypeConverter.d.ts +10 -0
  93. package/dist/converters/ToLowerCaseConverter.d.ts +4 -0
  94. package/dist/converters/ToUpperCaseConverter.d.ts +4 -0
  95. package/dist/converters/TypeConverter.d.ts +5 -0
  96. package/dist/createCRUDRepositoryOptions.d.ts +7 -0
  97. package/dist/createReadonlyRepositoryMappingOptions.d.ts +6 -0
  98. package/dist/dotEnv.d.ts +5 -0
  99. package/dist/index.d.ts +119 -0
  100. package/dist/mappers/ChangeCasingMapper.d.ts +9 -0
  101. package/dist/mappers/DictionaryMapper.d.ts +8 -0
  102. package/dist/mappers/FlatToNestedMapper.d.ts +20 -0
  103. package/dist/mappers/IFlatToNestedMapper.d.ts +6 -0
  104. package/dist/mappers/IMapper.d.ts +4 -0
  105. package/dist/mappers/IdentityMapper.d.ts +5 -0
  106. package/dist/mappers/PluralizerMapper.d.ts +6 -0
  107. package/dist/mappers/RowTypeMapper.d.ts +6 -0
  108. package/dist/mappers/TransformConverterMapper.d.ts +5 -0
  109. package/dist/mappers/TransformMapper.d.ts +6 -0
  110. package/dist/mappers/TypeMap.d.ts +8 -0
  111. package/dist/mappers/TypeMapGroup.d.ts +5 -0
  112. package/dist/mocks/index.d.ts +9 -0
  113. package/dist/mocks/mock-command.d.ts +28 -0
  114. package/dist/mocks/mock-connection.d.ts +22 -0
  115. package/dist/mocks/mock-datasource.d.ts +24 -0
  116. package/dist/mocks/mock-driver.d.ts +15 -0
  117. package/dist/mocks/mock-reader.d.ts +14 -0
  118. package/dist/mocks/mock-repository.d.ts +42 -0
  119. package/dist/mocks/mock-select-statement.d.ts +10 -0
  120. package/dist/mocks/mock-transaction.d.ts +16 -0
  121. package/dist/mocks/mock-where.d.ts +8 -0
  122. package/dist/parametersToRecord.d.ts +2 -0
  123. package/dist/readerToAsyncIterable.d.ts +2 -0
  124. package/dist/splitStatements.d.ts +1 -0
  125. package/dist/statementParameters.d.ts +1 -0
  126. package/dist/statementUsesParameter.d.ts +1 -0
  127. package/dist/statements/Aggregate.d.ts +6 -0
  128. package/dist/statements/ArrayExpression.d.ts +5 -0
  129. package/dist/statements/BulkDeleteStatement.d.ts +11 -0
  130. package/dist/statements/BulkDeleteStatementTextRenderer.d.ts +4 -0
  131. package/dist/statements/BulkInsertStatement.d.ts +12 -0
  132. package/dist/statements/BulkInsertStatementTextRenderer.d.ts +4 -0
  133. package/dist/statements/BulkUpdateStatement.d.ts +16 -0
  134. package/dist/statements/BulkUpdateStatementTextRenderer.d.ts +8 -0
  135. package/dist/statements/CheckConstraint.d.ts +7 -0
  136. package/dist/statements/Cmp.d.ts +9 -0
  137. package/dist/statements/Column.d.ts +9 -0
  138. package/dist/statements/ColumnDefault.d.ts +5 -0
  139. package/dist/statements/Condition.d.ts +3 -0
  140. package/dist/statements/ConditionRenderer.d.ts +12 -0
  141. package/dist/statements/CreateIndexStatement.d.ts +10 -0
  142. package/dist/statements/CreateIndexStatementTextRenderer.d.ts +5 -0
  143. package/dist/statements/CreateTableStatement.d.ts +11 -0
  144. package/dist/statements/CreateTableStatementTextRenderer.d.ts +18 -0
  145. package/dist/statements/DefaultStatementTextRenderer.d.ts +5 -0
  146. package/dist/statements/DeleteStatement.d.ts +9 -0
  147. package/dist/statements/DeleteStatementTextRenderer.d.ts +7 -0
  148. package/dist/statements/ExecuteStoredProcedureStatement.d.ts +9 -0
  149. package/dist/statements/ExecuteStoredProcedureStatementTextRenderer.d.ts +4 -0
  150. package/dist/statements/ExpressionEvaluator.d.ts +5 -0
  151. package/dist/statements/ExpressionRenderer.d.ts +8 -0
  152. package/dist/statements/ForeignKey.d.ts +19 -0
  153. package/dist/statements/IBulkDeleteStatement.d.ts +3 -0
  154. package/dist/statements/IBulkInsertStatement.d.ts +3 -0
  155. package/dist/statements/IBulkUpdateStatement.d.ts +3 -0
  156. package/dist/statements/ICondition.d.ts +4 -0
  157. package/dist/statements/IConditionVisitor.d.ts +6 -0
  158. package/dist/statements/ICreateIndexStatement.d.ts +3 -0
  159. package/dist/statements/ICreateTableStatement.d.ts +3 -0
  160. package/dist/statements/IDeleteStatement.d.ts +5 -0
  161. package/dist/statements/IExecuteStoredProcedureStatement.d.ts +3 -0
  162. package/dist/statements/IExpression.d.ts +2 -0
  163. package/dist/statements/IInsertStatement.d.ts +3 -0
  164. package/dist/statements/IReturnStatement.d.ts +3 -0
  165. package/dist/statements/ISelectStatement.d.ts +5 -0
  166. package/dist/statements/IStatement.d.ts +4 -0
  167. package/dist/statements/IStatementVisitor.d.ts +26 -0
  168. package/dist/statements/ITableConstraint.d.ts +5 -0
  169. package/dist/statements/IUpdateStatement.d.ts +5 -0
  170. package/dist/statements/IWhere.d.ts +6 -0
  171. package/dist/statements/Identifier.d.ts +5 -0
  172. package/dist/statements/InsertStatement.d.ts +9 -0
  173. package/dist/statements/InsertStatementTextRenderer.d.ts +4 -0
  174. package/dist/statements/Join.d.ts +14 -0
  175. package/dist/statements/Literal.d.ts +6 -0
  176. package/dist/statements/Native.d.ts +5 -0
  177. package/dist/statements/NotNull.d.ts +4 -0
  178. package/dist/statements/Operator.d.ts +107 -0
  179. package/dist/statements/OrderBy.d.ts +10 -0
  180. package/dist/statements/PrimaryKey.d.ts +7 -0
  181. package/dist/statements/ReturnStatement.d.ts +9 -0
  182. package/dist/statements/ReturnStatementTextRenderer.d.ts +4 -0
  183. package/dist/statements/SelectInsertedIdStatement.d.ts +8 -0
  184. package/dist/statements/SelectInsertedIdStatementTextRenderer.d.ts +4 -0
  185. package/dist/statements/SelectStatement.d.ts +20 -0
  186. package/dist/statements/SelectStatementOptions.d.ts +15 -0
  187. package/dist/statements/SelectStatementTextRenderer.d.ts +19 -0
  188. package/dist/statements/StatementEvaluator.d.ts +4 -0
  189. package/dist/statements/StatementParser.d.ts +4 -0
  190. package/dist/statements/StatementRenderer.d.ts +42 -0
  191. package/dist/statements/StatementTextRenderer.d.ts +14 -0
  192. package/dist/statements/Subquery.d.ts +6 -0
  193. package/dist/statements/UniqueConstraint.d.ts +7 -0
  194. package/dist/statements/UpdateStatement.d.ts +14 -0
  195. package/dist/statements/UpdateStatementTextRenderer.d.ts +14 -0
  196. package/dist/statements/Where.d.ts +16 -0
  197. package/dist/statements/WhereText.d.ts +8 -0
  198. package/dist/statements/index.d.ts +75 -0
  199. package/dist/stubs/StubCommand.d.ts +15 -0
  200. package/dist/stubs/StubConnection.d.ts +13 -0
  201. package/dist/stubs/StubDatasource.d.ts +20 -0
  202. package/dist/stubs/StubReader.d.ts +11 -0
  203. package/dist/stubs/StubRepository.d.ts +22 -0
  204. package/dist/stubs/StubTransaction.d.ts +10 -0
  205. package/dist/stubs/createStubDatasource.d.ts +2 -0
  206. package/dist/stubs/index.d.ts +7 -0
  207. package/dist/stubs/notImplemented.d.ts +1 -0
  208. package/package.json +14 -6
  209. package/src/ArrayReader.test.ts +23 -0
  210. package/src/BulkCRUDRepository.test.ts +183 -0
  211. package/src/BulkWriteStrategy.test.ts +293 -0
  212. package/src/CRUDRepository.test.ts +420 -0
  213. package/src/CRUDRepository.ts +1 -1
  214. package/src/CRUDRepositoryOptions.test.ts +76 -0
  215. package/src/Command.test.ts +105 -0
  216. package/src/CommandMiddlewareHost.test.ts +98 -0
  217. package/src/Connection.test.ts +34 -0
  218. package/src/ConsoleCommandMiddleware.test.ts +149 -0
  219. package/src/Datasource.test.ts +63 -0
  220. package/src/DatasourceError.test.ts +10 -0
  221. package/src/DatasourceFactory.test.ts +159 -0
  222. package/src/DatasourceUrlParser.test.ts +173 -0
  223. package/src/ExpectConsoleMiddleware.test.ts +79 -0
  224. package/src/FirstResultReader.test.ts +43 -0
  225. package/src/IsolationLevel.test.ts +13 -0
  226. package/src/ProviderCatalog.test.ts +110 -0
  227. package/src/ProviderNotImplementedError.test.ts +11 -0
  228. package/src/ProviderNotInstalledError.test.ts +11 -0
  229. package/src/ReadonlyRepository.test.ts +406 -0
  230. package/src/RepositoryFactory.test.ts +94 -0
  231. package/src/StatementMappers.test.ts +221 -0
  232. package/src/StreamingReader.test.ts +126 -0
  233. package/src/Transaction.test.ts +37 -0
  234. package/src/UnitOfWorkTransaction.test.ts +43 -0
  235. package/src/UserViewCRUDRepository.test.ts +323 -0
  236. package/src/builders/builders.test.ts +404 -0
  237. package/src/converters/ChangeCasingConverter.test.ts +34 -0
  238. package/src/converters/PluralizerConverter.test.ts +43 -0
  239. package/src/converters/RegexConverter.test.ts +11 -0
  240. package/src/converters/ReplaceConverter.test.ts +9 -0
  241. package/src/converters/RowTypeConverter.test.ts +79 -0
  242. package/src/converters/ToLowerCaseConverter.test.ts +9 -0
  243. package/src/converters/ToUpperCaseConverter.test.ts +9 -0
  244. package/src/dotEnv.test.ts +21 -0
  245. package/src/edgeCases.test.ts +485 -0
  246. package/src/mappers/ChangeCasingMapper.test.ts +32 -0
  247. package/src/mappers/DictionaryMapper.test.ts +69 -0
  248. package/src/mappers/FlatToNestedMapper.test.ts +269 -0
  249. package/src/mappers/IdentityMapper.test.ts +12 -0
  250. package/src/mappers/PluralizerMapper.test.ts +40 -0
  251. package/src/mappers/RowTypeMapper.test.ts +12 -0
  252. package/src/mappers/TransformConverterMapper.test.ts +181 -0
  253. package/src/mappers/transformMapper.test.ts +66 -0
  254. package/src/mocks/MockCommand.test.ts +75 -0
  255. package/src/mocks/MockConnection.test.ts +34 -0
  256. package/src/mocks/MockDatasource.test.ts +40 -0
  257. package/src/mocks/MockReader.test.ts +17 -0
  258. package/src/mocks/MockRepository.test.ts +32 -0
  259. package/src/mocks/MockSelectStatement.test.ts +11 -0
  260. package/src/mocks/MockTransaction.test.ts +17 -0
  261. package/src/mocks/MockWhere.test.ts +11 -0
  262. package/src/parametersToRecord.test.ts +19 -0
  263. package/src/public-surface.test.ts +23 -0
  264. package/src/readWithWhereClauseAndCustomFlatToNestedObjectMappingOptions.test.ts +126 -0
  265. package/src/readerToAsyncIterable.test.ts +39 -0
  266. package/src/repository.test.ts +410 -0
  267. package/src/repository.type.test.ts +29 -0
  268. package/src/statements/SelectInsertedIdStatement.test.ts +11 -0
  269. package/src/statements/StatementCompile.test.ts +231 -0
  270. package/src/statements/StatementParser.test.ts +304 -0
  271. package/src/statements/index.ts +1 -1
  272. package/src/stubs/StubDatasource.test.ts +99 -0
  273. package/src/tests/contact/Address.ts +14 -0
  274. package/src/tests/contact/AddressType.ts +6 -0
  275. package/src/tests/contact/Base.ts +7 -0
  276. package/src/tests/contact/Contact.ts +12 -0
  277. package/src/tests/contact/ContactBase.ts +8 -0
  278. package/src/tests/contact/ContactChangeLog.ts +8 -0
  279. package/src/tests/contact/ContactGroup.ts +6 -0
  280. package/src/tests/contact/ContactGroupBase.ts +6 -0
  281. package/src/tests/contact/ContactGroupMember.ts +6 -0
  282. package/src/tests/contact/ContactSource.ts +6 -0
  283. package/src/tests/contact/ContactType.ts +6 -0
  284. package/src/tests/contact/ContactsService.test.ts +118 -0
  285. package/src/tests/contact/ContactsService.ts +32 -0
  286. package/src/tests/contact/ImportContactRequest.ts +4 -0
  287. package/src/tests/contact/ImportContactResult.ts +5 -0
  288. package/src/tests/contact/LegacyContact.ts +7 -0
  289. package/src/tests/contact/LegacyMigrationResult.ts +5 -0
  290. package/src/tests/contact/Phone.ts +9 -0
  291. package/src/tests/contact/PhoneType.ts +6 -0
  292. package/src/tests/contact/asContactsView.ts +63 -0
  293. package/src/tests/contact/contactColumns.ts +40 -0
  294. package/src/tests/contact/contactLookupIds.ts +32 -0
  295. package/src/tests/contact/contactLookups.ts +6 -0
  296. package/src/tests/contact/contactsParity.ts +63 -0
  297. package/src/tests/contact/contactsServiceOf.ts +38 -0
  298. package/src/tests/contact/contactsView.ts +31 -0
  299. package/src/tests/contact/expectContactsView.ts +23 -0
  300. package/src/tests/contact/extraContact.ts +61 -0
  301. package/src/tests/contact/logMapped.ts +5 -0
  302. package/src/tests/contact/seedContacts.ts +31 -0
@@ -0,0 +1,485 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { MemoryDriver } from "@datasources/memory";
3
+ import { collectAsync } from "./collectAsync.ts";
4
+ import { BulkCRUDRepository } from "./BulkCRUDRepository.ts";
5
+ import { CascadeDeleteEnrichmentStrategy } from "./CascadeDeleteEnrichmentStrategy.ts";
6
+ import { CascadeDeleteUpdateEnrichmentStrategy } from "./CascadeDeleteUpdateEnrichmentStrategy.ts";
7
+ import { CRUDRepository } from "./CRUDRepository.ts";
8
+ import { DefaultReadonlyEnrichmentStrategy } from "./DefaultReadonlyEnrichmentStrategy.ts";
9
+ import { DefaultCreateEnrichmentStrategy } from "./DefaultCreateEnrichmentStrategy.ts";
10
+ import { DefaultUpdateEnrichmentStrategy } from "./DefaultUpdateEnrichmentStrategy.ts";
11
+ import { DefaultWriteEnrichmentStrategy } from "./DefaultWriteEnrichmentStrategy.ts";
12
+ import { LazyWriteEnrichmentStrategy } from "./LazyWriteEnrichmentStrategy.ts";
13
+ import { DeleteEnrichmentStrategy } from "./DeleteEnrichmentStrategy.ts";
14
+ import { HashsetOptimizedReadonlyEnrichmentStrategy } from "./HashsetOptimizedReadonlyEnrichmentStrategy.ts";
15
+ import { NestedToObjectEnrichmentStrategy } from "./NestedToObjectEnrichmentStrategy.ts";
16
+ import { ReadonlyRepository } from "./ReadonlyRepository.ts";
17
+ import { RepositoryTree } from "./RepositoryTree.ts";
18
+ import { RepositoryTreeNode } from "./RepositoryTreeNode.ts";
19
+ import { BatchCommandIBulkWriteStrategy } from "./BatchCommandIBulkWriteStrategy.ts";
20
+ import { SingleCommandIBulkWriteStrategy } from "./SingleCommandIBulkWriteStrategy.ts";
21
+ import { StoredProcedureBulkWriteStrategy } from "./StoredProcedureBulkWriteStrategy.ts";
22
+ import type { Cells } from "./converters/Cell.ts";
23
+ import type { Scalar } from "./builders/Scalar.ts";
24
+ import { TypeConverter } from "./converters/TypeConverter.ts";
25
+ import { MockCommand } from "./mocks/mock-command.ts";
26
+ import { MockConnection } from "./mocks/mock-connection.ts";
27
+ import { MockReader } from "./mocks/mock-reader.ts";
28
+ import { n } from "./statements/Native.ts";
29
+ import { ExecuteStoredProcedureStatement } from "./statements/ExecuteStoredProcedureStatement.ts";
30
+ import { WhereText } from "./statements/WhereText.ts";
31
+ import { RowTypeConverter } from "./converters/RowTypeConverter.ts";
32
+ import type { WriteMode } from "./WriteMode.ts";
33
+
34
+ class Item {
35
+ id = 0;
36
+ name = "";
37
+ }
38
+
39
+ type Bag = Record<string, Scalar | object>;
40
+
41
+ class Plain extends TypeConverter<Item, Cells> {
42
+ convert = (row: Cells): Item => Object.assign(new Item(), row);
43
+ reverseConvert = (row: Item): Cells => ({ id: row.id, name: row.name });
44
+ }
45
+
46
+ class OpenUpdate extends DefaultUpdateEnrichmentStrategy<Item> {
47
+ stamp = <TItem extends object>(related: TItem, by: string, key: Scalar | object | undefined): void =>
48
+ this.stampCell(related, by, key);
49
+
50
+ payload = <TParent extends object>(parent: TParent, into: string): object[] => this.payloadOf(parent, into);
51
+
52
+ key = <TItem extends object>(item: TItem, keys: readonly string[]): string => this.keyOf(item, keys);
53
+ }
54
+
55
+ class OpenCreate extends DefaultCreateEnrichmentStrategy<Item> {
56
+ payload = <TParent extends object>(parent: TParent, into: string): object[] => this.payloadOf(parent, into);
57
+ }
58
+
59
+ class DropId extends TypeConverter<Item, Cells> {
60
+ convert = (row: Cells): Item => Object.assign(new Item(), row);
61
+ reverseConvert = (): Cells => ({ name: "a" });
62
+ }
63
+
64
+ const mock = (rows: Cells[] = []) => {
65
+ const connection = new MockConnection();
66
+ const command = new MockCommand(connection);
67
+ command.reader = new MockReader(rows);
68
+ command.response = { result: 9, rowsAffected: 1 };
69
+ connection.command = command;
70
+ return { connection, command };
71
+ };
72
+
73
+ const enrichment = (into: string, extra: object = {}) =>
74
+ ({ destinationProperty: into, sourceProperty: "id", relatedKey: "parent_id", type: "list", ...extra });
75
+
76
+ describe("statement and repository edges", () => {
77
+ it("names a native fragment", () => {
78
+ expect(n("select 1").text).toBe("select 1");
79
+ });
80
+
81
+ it("reads without a connection and without statements", async () => {
82
+ const repo = new ReadonlyRepository();
83
+ await expect(collectAsync(repo.read(new WhereText("id = $id")))).rejects.toThrow();
84
+ await expect(collectAsync(repo.read())).rejects.toThrow();
85
+ });
86
+
87
+ it("converts with a plain object converter", async () => {
88
+ const { connection, command } = mock([{ generated: 9 }]);
89
+ const repo = new CRUDRepository<Item>(connection, {
90
+ type: Item,
91
+ entity: "items",
92
+ objectConverter: new Plain(),
93
+ });
94
+ expect(await collectAsync(repo.read())).toHaveLength(1);
95
+ const converted = new ReadonlyRepository<Item>(connection, {
96
+ type: Item,
97
+ entity: "items",
98
+ objectConverter: new Plain(),
99
+ rowConverter: new RowTypeConverter({}),
100
+ });
101
+ command.reader = new MockReader([{ id: 1, name: "a" }]);
102
+ expect(await collectAsync(converted.read())).toHaveLength(1);
103
+ command.reader = new MockReader([]);
104
+ await expect(
105
+ collectAsync(converted.read(new ExecuteStoredProcedureStatement("save_item", []))),
106
+ ).resolves.toEqual([]);
107
+ command.reader = new MockReader([{ generated: 9 }]);
108
+ const created = await repo.create(Object.assign(new Item(), { name: "a" }));
109
+ expect(created.id).toBe(9);
110
+ });
111
+
112
+ it("bulk write covers transaction, strategy position, and where text", async () => {
113
+ const { connection, command } = mock();
114
+ const strategy = new SingleCommandIBulkWriteStrategy<Item>();
115
+ const transactional = new BulkCRUDRepository<Item>(
116
+ connection,
117
+ { type: Item, entity: "items", write: { useTransaction: true, persist: async () => {} } },
118
+ strategy,
119
+ );
120
+ const created = await transactional.createBulk([Object.assign(new Item(), { name: "a" })]);
121
+ expect(created).toHaveLength(1);
122
+ expect(connection.startTransactionCalls).toBe(1);
123
+
124
+ expect(() => new BulkCRUDRepository<Item>(connection, strategy)).toThrow();
125
+
126
+ const open = new MemoryDriver().connection();
127
+ await open.open();
128
+ const repo = new BulkCRUDRepository<Item>(open, { type: Item, entity: "items", ids: [] });
129
+ await repo.create(Object.assign(new Item(), { name: "a" }));
130
+ await repo.deleteBulk([Object.assign(new Item(), { name: "a" })], new WhereText("id = $id"));
131
+ await repo.deleteBulk([new Item()], new WhereText("1 = 1"));
132
+
133
+ const batch = new BatchCommandIBulkWriteStrategy<Item>();
134
+ const listedRepo = new BulkCRUDRepository<Item>(connection, { type: Item, entity: "items", ids: [] }, batch);
135
+ command.executeCalls = [];
136
+ await listedRepo.deleteBulk([Object.assign(new Item(), { name: "a" })], new WhereText("id = $id"));
137
+ expect(command.executeCalls[0]?.text).toContain("$id");
138
+ command.executeCalls = [];
139
+ await listedRepo.deleteBulk([new Item()], new WhereText("1 = 1"));
140
+ expect(command.executeCalls[0]?.parameters).toEqual([]);
141
+ command.executeCalls = [];
142
+ await listedRepo.deleteBulk([new Item()]);
143
+ expect(command.executeCalls[0]?.text).toContain("1 = 1");
144
+ expect(await repo.createBulk([new Item()])).toEqual([new Item()]);
145
+ const both = new BulkCRUDRepository<Item>(
146
+ connection,
147
+ { type: Item, entity: "items", ids: ["id", "name"] },
148
+ new BatchCommandIBulkWriteStrategy<Item>(),
149
+ );
150
+ await expect(both.updateBulk([new Item()])).rejects.toThrow(/non-Id field/);
151
+ const stored = new StoredProcedureBulkWriteStrategy<Item>("create_item", "update_item", "delete_item");
152
+ const wide = new BulkCRUDRepository<Item>(connection, {
153
+ type: Item,
154
+ entity: "items",
155
+ ids: ["id", "name"],
156
+ rowConverter: new RowTypeConverter({}),
157
+ write: { persist: async () => {} },
158
+ }, stored);
159
+ command.executeCalls = [];
160
+ await wide.createBulk([new Item()]);
161
+ expect(String(command.executeCalls[0]?.parameters?.[0]?.value)).toContain('"id":0');
162
+ expect(String(command.executeCalls[0]?.parameters?.[0]?.value)).toContain('"name":""');
163
+ expect(await wide.createBulk([new Item()])).toEqual([new Item()]);
164
+
165
+ const dropped = new BulkCRUDRepository<Item>(
166
+ connection,
167
+ { type: Item, entity: "items", objectConverter: new DropId() },
168
+ new BatchCommandIBulkWriteStrategy<Item>(),
169
+ );
170
+ command.response = { result: undefined, rowsAffected: 1 };
171
+ await expect(dropped.deleteBulk([new Item()])).rejects.toThrow(/id/);
172
+
173
+ const plain = new BulkCRUDRepository<Item>(connection, { type: Item, entity: "items" });
174
+ command.reader = new MockReader([]);
175
+ expect(
176
+ await plain.createBulk([Object.assign(new Item(), { id: { nested: true } as unknown as number })]),
177
+ ).toEqual([Object.assign(new Item(), { id: { nested: true } as unknown as number })]);
178
+ });
179
+
180
+ it("create follows enrichment and inserted-id edges", async () => {
181
+ const { connection, command } = mock([]);
182
+ const writer = new CRUDRepository<Item>(connection, { type: Item, entity: "items" });
183
+ const host = new CRUDRepository<Item>(connection, { type: Item, entity: "parents" });
184
+ host.addEnriched({ destinationProperty: "Children", sourceProperty: "id", relatedKey: "parent_id", type: "list" }, writer);
185
+ const objectId = Object.assign(new Item(), { id: { nested: true } as unknown as number, name: "a" });
186
+ expect(await host.create(objectId)).toBe(objectId);
187
+ const numeric = Object.assign(new Item(), { name: "b" });
188
+ expect(await host.create(numeric)).toBe(numeric);
189
+ const wide = new CRUDRepository<Item>(connection, {
190
+ type: Item,
191
+ entity: "parents",
192
+ ids: ["id", "name"],
193
+ });
194
+ wide.addEnriched({ destinationProperty: "Children", sourceProperty: "id", relatedKey: "parent_id", type: "list" }, writer);
195
+ expect(await wide.create(new Item())).toBeInstanceOf(Item);
196
+
197
+ command.reader = new MockReader([undefined as unknown as Cells]);
198
+ await expect(host.create(new Item())).rejects.toThrow("Reader current must be a row");
199
+
200
+ const plainSide = mock([{ id: 9 }]);
201
+ const plainRepo = new CRUDRepository<Item>(plainSide.connection, { type: Item, entity: "items" });
202
+ expect((await plainRepo.create(Object.assign(new Item(), { name: "a" }))).id).toBe(9);
203
+ const convertedSide = mock([{ generated: 4 }]);
204
+ const convertedRepo = new CRUDRepository<Item>(convertedSide.connection, {
205
+ type: Item,
206
+ entity: "items",
207
+ objectConverter: new Plain(),
208
+ rowConverter: new RowTypeConverter({}),
209
+ });
210
+ expect((await convertedRepo.create(Object.assign(new Item(), { name: "a" }))).id).toBe(4);
211
+ });
212
+ });
213
+
214
+ describe("enrichment edges", () => {
215
+ it("requires a writer and an association", async () => {
216
+ const owned = enrichment("Children");
217
+ const cascade = new CascadeDeleteEnrichmentStrategy<Bag>();
218
+ await expect(cascade.deleteLinked(new CRUDRepository<Item>(mock([]).connection, { entity: "items" }), owned, {})).rejects.toThrow(
219
+ "Missing association",
220
+ );
221
+ const update = new CascadeDeleteUpdateEnrichmentStrategy<Bag>();
222
+ await expect(update.updateLinked(new CRUDRepository<Item>(mock([]).connection, { entity: "items" }), owned, {})).rejects.toThrow(
223
+ "Missing association",
224
+ );
225
+ const replacing = new DefaultUpdateEnrichmentStrategy<Bag>();
226
+ await expect(replacing.updateLinked(new CRUDRepository<Item>(mock([]).connection, { entity: "items" }), owned, {})).rejects.toThrow(
227
+ "Missing association",
228
+ );
229
+
230
+ const { connection } = mock([]);
231
+ const writer = new CRUDRepository<Item>(connection, { type: Item, entity: "items" });
232
+ const tags = {
233
+ destinationProperty: "Tags",
234
+ sourceProperty: "id",
235
+ relatedKey: "id",
236
+ association: { parent: "parent_id", related: "tag_id" },
237
+ };
238
+ await cascade.deleteLinked(writer, tags, { id: 0 });
239
+ await cascade.deleteLinked(writer, tags, { id: "" });
240
+ await expect(DeleteEnrichmentStrategy.load(writer, owned, { id: { nested: true } })).resolves.toEqual([]);
241
+
242
+ const host = new CRUDRepository<Item>(connection, { type: Item, entity: "items" });
243
+ host.addEnriched({ destinationProperty: "Children", sourceProperty: "id", relatedKey: "parent_id", type: "list" }, writer);
244
+ const tree = new RepositoryTree(new RepositoryTreeNode(host, ""));
245
+ await expect(
246
+ new DefaultWriteEnrichmentStrategy<Item>().persist(tree, [new Item()], "nope" as WriteMode),
247
+ ).rejects.toThrow("Unknown persist mode");
248
+ await new DefaultWriteEnrichmentStrategy<Item>().persist(
249
+ new RepositoryTree(new RepositoryTreeNode({}, "")),
250
+ [],
251
+ "create",
252
+ );
253
+
254
+ await new HashsetOptimizedReadonlyEnrichmentStrategy<Item>().apply(
255
+ writer,
256
+ { destinationProperty: "name", sourceProperty: "missing", relatedKey: "id", relatedProperty: "name" },
257
+ [{}],
258
+ );
259
+ await new NestedToObjectEnrichmentStrategy<Item>({ key: "id", fields: { id: "id" } }).apply(
260
+ writer,
261
+ { destinationProperty: "name", sourceProperty: "id", relatedKey: "id" },
262
+ [],
263
+ );
264
+ });
265
+
266
+ it("walks lookup, link, and replace edges", async () => {
267
+ const tags = { destinationProperty: "Tags", sourceProperty: "id", relatedKey: "id", association: { parent: "parent_id", related: "tag_id" } };
268
+ const empty = mock([]);
269
+ const via = new CRUDRepository<Item>(empty.connection, { type: Item, entity: "tags" });
270
+ const lookup = new DefaultReadonlyEnrichmentStrategy<Item>();
271
+ await lookup.apply(via, { destinationProperty: "name", sourceProperty: "id", relatedKey: "id" }, [{ id: null }, { id: 1 }]);
272
+ await lookup.apply(via, { destinationProperty: "Children", sourceProperty: "id", relatedKey: "parent_id", type: "list" }, [{ id: null }, {}]);
273
+ await expect(lookup.applyLinked(via, via, { destinationProperty: "Tags", sourceProperty: "id", relatedKey: "id" }, [{ id: 1 }])).rejects.toThrow(
274
+ "Missing association",
275
+ );
276
+ await lookup.applyLinked(via, via, tags, [{ id: null }]);
277
+ const linkSide = mock([{ tag_id: null }, { tag_id: { nested: true } as never }, { tag_id: 2 }]);
278
+ const linkRepo = new CRUDRepository<Item>(linkSide.connection, { entity: "links" });
279
+ await lookup.applyLinked(via, linkRepo, tags, [{ id: 1 }]);
280
+
281
+ const hash = new HashsetOptimizedReadonlyEnrichmentStrategy<Item>();
282
+ await hash.apply(via, { destinationProperty: "name", sourceProperty: "id", relatedKey: "id", relatedProperty: "name", type: "list" }, [{}]);
283
+ const named = mock([{ id: 1, name: "a" }]);
284
+ const namedRepo = new CRUDRepository<Item>(named.connection, { type: Item, entity: "tags" });
285
+ await hash.apply(namedRepo, { destinationProperty: "name", sourceProperty: "id", relatedKey: "id", relatedProperty: "name" }, [{ id: 1 }, {}]);
286
+ const many = mock([{ parent_id: 1, name: "a" }]);
287
+ const manyRepo = new CRUDRepository<Item>(many.connection, { type: Item, entity: "children" });
288
+ await hash.apply(manyRepo, { destinationProperty: "Children", sourceProperty: "id", relatedKey: "parent_id", type: "list" }, [{ id: 1 }, { id: null }]);
289
+
290
+ const children = { destinationProperty: "Children", sourceProperty: "id", relatedKey: "parent_id", type: "list" };
291
+ const quiet = mock([]);
292
+ const writer = new CRUDRepository<Item>(quiet.connection, { type: Item, entity: "children", ids: ["id"] });
293
+ const replacing = new DefaultUpdateEnrichmentStrategy<Bag>();
294
+ await replacing.updateChildren(writer, children, { id: 1 });
295
+ const existing = mock([
296
+ { id: 1, parent_id: 1, name: "old" },
297
+ { id: 9, parent_id: 1, name: "gone" },
298
+ ]);
299
+ const existingRepo = new CRUDRepository<Item>(existing.connection, { type: Item, entity: "children", ids: ["id"] });
300
+ await replacing.updateChildren(existingRepo, children, {
301
+ id: 1,
302
+ Children: [{ id: 1, name: "kept" }, { name: "fresh" }, null, "skip"],
303
+ });
304
+
305
+ const linkedRows = mock([{ tag_id: 0 }, { tag_id: 2 }, {}]);
306
+ const linkWriter = new CRUDRepository<Item>(linkedRows.connection, { entity: "links", ids: ["id"] });
307
+ await replacing.updateLinked(linkWriter, tags, { id: 1, Tags: [{ id: 2 }, { id: 2 }, { id: 3 }] });
308
+ await expect(new DefaultCreateEnrichmentStrategy<Bag>().createLinked(writer, { destinationProperty: "Tags", sourceProperty: "id", relatedKey: "id" }, { id: 1 })).rejects.toThrow(
309
+ "Missing association",
310
+ );
311
+ const noIds = new CRUDRepository<Item>(mock([]).connection, { entity: "children", ids: [] });
312
+ await replacing.updateChildren(noIds, children, {
313
+ id: 1,
314
+ Children: [{ name: "a" }],
315
+ });
316
+ const creating = new DefaultCreateEnrichmentStrategy<Bag>();
317
+ await expect(creating.createLinked(writer, tags, { id: 0, Tags: [{ id: 1 }] })).rejects.toThrow(/requires/);
318
+ await expect(creating.createLinked(writer, tags, { id: 1, Tags: [{ id: 0 }] })).rejects.toThrow(/requires/);
319
+
320
+ const open = new OpenUpdate();
321
+ expect(open.key({ name: "a" }, ["missing"])).toBe("");
322
+ expect(open.key({ id: 5 }, ["id"])).toBe("5");
323
+ expect(open.key({ id: null as unknown as number }, ["id"])).toBe("");
324
+ expect(open["requiredKey"]({ id: 1 }, {
325
+ destinationProperty: "Tags",
326
+ sourceProperty: "id",
327
+ relatedKey: "id",
328
+ })).toBe(1);
329
+ expect(() =>
330
+ open["requiredKey"]({ id: 0 }, {
331
+ destinationProperty: "Tags",
332
+ sourceProperty: "id",
333
+ relatedKey: "id",
334
+ }),
335
+ ).toThrow(/requires/);
336
+ open.stamp({ parent: null as unknown as object }, "parent", 8);
337
+ expect(open.payload({ Children: { not: "array" } }, "Children")).toEqual([]);
338
+ expect(open.payload({ Children: "text" }, "Children")).toEqual([]);
339
+ const creatingOpen = new OpenCreate();
340
+ expect(creatingOpen.payload({ Children: { not: "array" } }, "Children")).toEqual([]);
341
+ expect(creatingOpen.payload({ Children: [null, "skip", { id: 1n }] }, "Children")).toEqual([{ id: 1n }]);
342
+ const stamped = { parent: { Id: 0 } };
343
+ creatingOpen["stampCell"](stamped, "parent", 9);
344
+ expect(stamped.parent.Id).toBe(9);
345
+ const createSnake = { parent: { name: "a" } };
346
+ creatingOpen["stampCell"](createSnake, "parent", 5);
347
+ expect((createSnake.parent as { id?: number }).id).toBe(5);
348
+ const createFlat = { parent_id: 0 };
349
+ creatingOpen["stampCell"](createFlat, "parent_id", 6);
350
+ expect(createFlat.parent_id).toBe(6);
351
+ creatingOpen["stampCell"](createFlat, "parent_id", undefined);
352
+ creatingOpen["stampCell"](createFlat, "parent_id", null);
353
+ creatingOpen["stampCell"](createFlat, "parent_id", { nested: true });
354
+ creatingOpen["stampCell"](createFlat, "parent_id", []);
355
+ expect(open["namesOf"]({ ids: [] } as never, { id: 1, name: "a" })).toEqual(["id"]);
356
+ expect(open["namesOf"]({ ids: ["id"] } as never, { id: 1 })).toEqual(["id"]);
357
+ expect(() =>
358
+ creatingOpen["relatedIds"]({ Tags: [{ id: 0 }] }, {
359
+ destinationProperty: "Tags",
360
+ sourceProperty: "id",
361
+ relatedKey: "id",
362
+ }),
363
+ ).toThrow(/requires/);
364
+ expect(() =>
365
+ creatingOpen["relatedIds"]({ Tags: [{ id: 0n }] }, {
366
+ destinationProperty: "Tags",
367
+ sourceProperty: "id",
368
+ relatedKey: "id",
369
+ }),
370
+ ).toThrow(/requires/);
371
+ expect(() =>
372
+ creatingOpen["relatedIds"]({ Tags: [{ id: "" }] }, {
373
+ destinationProperty: "Tags",
374
+ sourceProperty: "id",
375
+ relatedKey: "id",
376
+ }),
377
+ ).toThrow(/requires/);
378
+ expect(() =>
379
+ creatingOpen["relatedIds"]({ Tags: [{ id: { nested: true } }] }, {
380
+ destinationProperty: "Tags",
381
+ sourceProperty: "id",
382
+ relatedKey: "id",
383
+ }),
384
+ ).toThrow(/requires/);
385
+ expect(
386
+ creatingOpen["relatedIds"]({ Tags: [{ id: 2 }, { id: 2 }] }, {
387
+ destinationProperty: "Tags",
388
+ sourceProperty: "id",
389
+ relatedKey: "id",
390
+ }),
391
+ ).toEqual([2]);
392
+ expect(open.payload({ Children: [null, "skip", { id: 1 }] }, "Children")).toEqual([{ id: 1 }]);
393
+ expect(() =>
394
+ open["relatedIds"]({ Tags: [{ id: 0 }] }, {
395
+ destinationProperty: "Tags",
396
+ sourceProperty: "id",
397
+ relatedKey: "id",
398
+ }),
399
+ ).toThrow(/requires/);
400
+ expect(() =>
401
+ open["relatedIds"]({ Tags: [{ id: 0n }] }, {
402
+ destinationProperty: "Tags",
403
+ sourceProperty: "id",
404
+ relatedKey: "id",
405
+ }),
406
+ ).toThrow(/requires/);
407
+ expect(() =>
408
+ open["relatedIds"]({ Tags: [{ id: "" }] }, {
409
+ destinationProperty: "Tags",
410
+ sourceProperty: "id",
411
+ relatedKey: "id",
412
+ }),
413
+ ).toThrow(/requires/);
414
+ expect(
415
+ open["relatedIds"]({ Tags: [{ id: 2 }, { id: 2 }] }, {
416
+ destinationProperty: "Tags",
417
+ sourceProperty: "id",
418
+ relatedKey: "id",
419
+ }),
420
+ ).toEqual([2]);
421
+ expect(open["hasKey"]({}, [])).toBe(false);
422
+ expect(open["hasKey"]({ id: 1 }, ["id"])).toBe(true);
423
+ expect(open["hasKey"]({ id: 0 }, ["id"])).toBe(false);
424
+ expect(open["hasKey"]({ id: 1 }, ["id", "missing"])).toBe(false);
425
+ const nested = { parent: { Id: 0 } };
426
+ open.stamp(nested, "parent", 4);
427
+ expect(nested.parent.Id).toBe(4);
428
+ const snake = { parent: { name: "a" } };
429
+ open.stamp(snake, "parent", 5);
430
+ expect((snake.parent as { id?: number }).id).toBe(5);
431
+ const flat = { parent_id: 0 };
432
+ open.stamp(flat, "parent_id", 6);
433
+ expect(flat.parent_id).toBe(6);
434
+ open.stamp(flat, "parent_id", { nested: true });
435
+ const withArray = { parent_id: [1, 2] as unknown as number };
436
+ open.stamp(withArray, "parent_id", 7);
437
+ expect(withArray.parent_id).toBe(7);
438
+
439
+ const cascade = new CascadeDeleteEnrichmentStrategy<Bag>();
440
+ const cascadeRows = mock([{ tag_id: 0 }, { tag_id: 2 }]);
441
+ const cascadeWriter = new CRUDRepository<Item>(cascadeRows.connection, { entity: "links", ids: ["id"] });
442
+ await cascade.deleteLinked(cascadeWriter, tags, { id: 1 });
443
+ const cascadeUpdate = new CascadeDeleteUpdateEnrichmentStrategy<Bag>();
444
+ const cascadeUpdateRows = mock([{ tag_id: 0 }, { tag_id: 3 }]);
445
+ const cascadeUpdateWriter = new CRUDRepository<Item>(cascadeUpdateRows.connection, { entity: "links", ids: ["id"] });
446
+ await cascadeUpdate.updateLinked(cascadeUpdateWriter, tags, { id: 1, Tags: [{ id: 4 }] });
447
+
448
+ const parents = mock([]);
449
+ const host = new CRUDRepository<Item>(parents.connection, { type: Item, entity: "parents" });
450
+ host.addEnriched(children, writer);
451
+ const tree = new RepositoryTree(new RepositoryTreeNode(host, ""));
452
+ await new LazyWriteEnrichmentStrategy<Item>().persist(tree, [new Item()], "create");
453
+ const associated = new CRUDRepository<Item>(parents.connection, { type: Item, entity: "parents" });
454
+ associated.addEnriched(tags, writer);
455
+ await expect(
456
+ new DefaultWriteEnrichmentStrategy<Item>().persist(
457
+ new RepositoryTree(new RepositoryTreeNode(associated, "")),
458
+ [{ id: 1, Tags: [{ id: 2 }] } as unknown as Item],
459
+ "create",
460
+ ),
461
+ ).rejects.toThrow(/Tags/);
462
+ });
463
+
464
+ it("rejects custom fills", async () => {
465
+ const { connection } = mock([]);
466
+ const via = new CRUDRepository<Item>(connection, { type: Item, entity: "items" });
467
+ const read = new DefaultReadonlyEnrichmentStrategy<Item>();
468
+ const fill = {
469
+ destinationProperty: "name",
470
+ sourceProperty: "id",
471
+ relatedKey: "id",
472
+ type: "custom" as const,
473
+ };
474
+ await expect(read.apply(via, fill, [])).rejects.toThrow('Unsupported enrichment type "custom"');
475
+ const custom = new CRUDRepository<Item>(connection, { type: Item, entity: "parents" });
476
+ custom.addEnriched(fill, via);
477
+ await expect(
478
+ new DefaultWriteEnrichmentStrategy<Item>().persist(
479
+ new RepositoryTree(new RepositoryTreeNode(custom, "")),
480
+ [new Item()],
481
+ "create",
482
+ ),
483
+ ).rejects.toThrow('Unsupported enrichment type "custom"');
484
+ });
485
+ });
@@ -0,0 +1,32 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { ChangeCasingMapper } from "./ChangeCasingMapper.ts";
3
+
4
+ describe("ChangeCasingMapper", () => {
5
+ it("maps pascal to snake and back", () => {
6
+ const mapper = new ChangeCasingMapper("pascalCase", "snakeCase");
7
+ expect(mapper.map("UserType")).toBe("user_type");
8
+ expect(mapper.map("Id")).toBe("id");
9
+ expect(mapper.reverseMap("user_type")).toBe("UserType");
10
+ expect(mapper.reverseMap("id")).toBe("Id");
11
+ });
12
+
13
+ it("maps contact names", () => {
14
+ const mapper = new ChangeCasingMapper("pascalCase", "snakeCase");
15
+ const contact = mapper.map("Contact");
16
+ const firstName = mapper.map("FirstName");
17
+ const emailAddress = mapper.map("EmailAddress");
18
+ expect(contact).toBe("contact");
19
+ expect(firstName).toBe("first_name");
20
+ expect(emailAddress).toBe("email_address");
21
+ });
22
+
23
+ it("reverse maps contact names", () => {
24
+ const mapper = new ChangeCasingMapper("pascalCase", "snakeCase");
25
+ const contact = mapper.reverseMap("contact");
26
+ const firstName = mapper.reverseMap("first_name");
27
+ const emailAddress = mapper.reverseMap("email_address");
28
+ expect(contact).toBe("Contact");
29
+ expect(firstName).toBe("FirstName");
30
+ expect(emailAddress).toBe("EmailAddress");
31
+ });
32
+ });
@@ -0,0 +1,69 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { DictionaryMapper } from "./DictionaryMapper.ts";
3
+ import { MockCommand } from "../mocks/mock-command.ts";
4
+ import { MockConnection } from "../mocks/mock-connection.ts";
5
+ import { MockReader } from "../mocks/mock-reader.ts";
6
+ import { ReadonlyRepository } from "../ReadonlyRepository.ts";
7
+
8
+ class Account {
9
+ Key = 0;
10
+ Login = "";
11
+ }
12
+
13
+ describe("DictionaryMapper", () => {
14
+ it("maps known names and passes through the rest", () => {
15
+ const mapper = new DictionaryMapper([
16
+ ["FirstName", "given_name"],
17
+ ["LastName", "family_name"],
18
+ ["Email", "email_address"],
19
+ ]);
20
+ const firstName = mapper.map("FirstName");
21
+ const lastName = mapper.map("LastName");
22
+ const email = mapper.map("Email");
23
+ const phone = mapper.map("Phone");
24
+ const fromFirst = mapper.reverseMap("given_name");
25
+ const fromLast = mapper.reverseMap("family_name");
26
+ const fromEmail = mapper.reverseMap("email_address");
27
+ const fromPhone = mapper.reverseMap("Phone");
28
+ expect(firstName).toBe("given_name");
29
+ expect(lastName).toBe("family_name");
30
+ expect(email).toBe("email_address");
31
+ expect(phone).toBe("Phone");
32
+ expect(fromFirst).toBe("FirstName");
33
+ expect(fromLast).toBe("LastName");
34
+ expect(fromEmail).toBe("Email");
35
+ expect(fromPhone).toBe("Phone");
36
+ });
37
+
38
+ it("first key wins on reverse", () => {
39
+ const mapper = new DictionaryMapper([
40
+ ["Home", "address"],
41
+ ["Work", "address"],
42
+ ]);
43
+ const home = mapper.map("Home");
44
+ const work = mapper.map("Work");
45
+ const address = mapper.reverseMap("address");
46
+ expect(home).toBe("address");
47
+ expect(work).toBe("address");
48
+ expect(address).toBe("Home");
49
+ });
50
+
51
+ it("read reverse-maps column names", async () => {
52
+ const connection = new MockConnection(undefined, "memory");
53
+ const command = new MockCommand(connection);
54
+ connection.command = command;
55
+ const repo = new ReadonlyRepository(connection, {
56
+ type: Account,
57
+ entity: "user",
58
+ propertyMappings: new DictionaryMapper([
59
+ ["Key", "id"],
60
+ ["Login", "username"],
61
+ ]),
62
+ });
63
+ command.reader = new MockReader([{ id: 7, username: "ada" }]);
64
+ const row = await repo.readSingle();
65
+ expect(row?.Key).toBe(7);
66
+ expect(row?.Login).toBe("ada");
67
+ expect(command.executeReaderCalls[0]?.text).toBe("SELECT id, username FROM user");
68
+ });
69
+ });