@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,269 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import type { Cells } from "../converters/Cell.ts";
3
+ import type { TypeMap } from "./TypeMap.ts";
4
+ import { FlatToNestedMapper } from "./FlatToNestedMapper.ts";
5
+
6
+ type Address = { id: number; line1: string; city: string; address_type_name: string };
7
+ type Phone = { id: number; number: string; phone_type_name: string };
8
+ type Contact = {
9
+ id: number;
10
+ first_name: string;
11
+ last_name: string;
12
+ email: string | null;
13
+ contact_source_name: string;
14
+ contact_type_name: string;
15
+ addresses: Address[];
16
+ phones: Phone[];
17
+ };
18
+
19
+ const contactsView: TypeMap<Contact> = {
20
+ key: "id",
21
+ fields: {
22
+ id: "id",
23
+ first_name: "first_name",
24
+ last_name: "last_name",
25
+ email: "email",
26
+ contact_source_name: "contact_source_name",
27
+ contact_type_name: "contact_type_name",
28
+ addresses: {
29
+ key: "address_id",
30
+ fields: {
31
+ id: "address_id",
32
+ line1: "address_line1",
33
+ city: "address_city",
34
+ address_type_name: "address_type_name",
35
+ },
36
+ },
37
+ phones: {
38
+ key: "phone_id",
39
+ fields: {
40
+ id: "phone_id",
41
+ number: "phone_number",
42
+ phone_type_name: "phone_type_name",
43
+ },
44
+ },
45
+ },
46
+ };
47
+
48
+ const adaHome: Cells = {
49
+ id: 7,
50
+ first_name: "Ada",
51
+ last_name: "Lovelace",
52
+ email: "ada@ex.com",
53
+ contact_source_name: "Web",
54
+ contact_type_name: "Person",
55
+ address_id: 101,
56
+ address_line1: "10 Downing St",
57
+ address_city: "London",
58
+ address_type_name: "Home",
59
+ phone_id: 201,
60
+ phone_number: "+44",
61
+ phone_type_name: "Mobile",
62
+ };
63
+
64
+ const adaWork: Cells = {
65
+ ...adaHome,
66
+ address_id: 102,
67
+ address_line1: "1 Parliament Sq",
68
+ address_city: "London",
69
+ };
70
+
71
+ const noAddress: Cells = {
72
+ id: 8,
73
+ first_name: "Charles",
74
+ last_name: "Babbage",
75
+ email: null,
76
+ contact_source_name: "Import",
77
+ contact_type_name: "Person",
78
+ address_id: null,
79
+ address_line1: null,
80
+ address_city: null,
81
+ address_type_name: null,
82
+ phone_id: 202,
83
+ phone_number: "555",
84
+ phone_type_name: "Work",
85
+ };
86
+
87
+ describe("FlatToNestedMapper", () => {
88
+ const mapper = new FlatToNestedMapper<Contact>(contactsView);
89
+
90
+ it("maps join rows to a nested contact", () => {
91
+ const mapper = new FlatToNestedMapper<Contact>({
92
+ key: "id",
93
+ fields: {
94
+ id: "id",
95
+ first_name: "first_name",
96
+ last_name: "last_name",
97
+ email: "email",
98
+ contact_source_name: "contact_source_name",
99
+ contact_type_name: "contact_type_name",
100
+ addresses: {
101
+ key: "address_id",
102
+ fields: {
103
+ id: "address_id",
104
+ line1: "address_line1",
105
+ city: "address_city",
106
+ address_type_name: "address_type_name",
107
+ },
108
+ },
109
+ phones: {
110
+ key: "phone_id",
111
+ fields: {
112
+ id: "phone_id",
113
+ number: "phone_number",
114
+ phone_type_name: "phone_type_name",
115
+ },
116
+ },
117
+ },
118
+ });
119
+ const adaHome: Cells = {
120
+ id: 7,
121
+ first_name: "Ada",
122
+ last_name: "Lovelace",
123
+ email: "ada@ex.com",
124
+ contact_source_name: "Web",
125
+ contact_type_name: "Person",
126
+ address_id: 101,
127
+ address_line1: "10 Downing St",
128
+ address_city: "London",
129
+ address_type_name: "Home",
130
+ phone_id: 201,
131
+ phone_number: "+44",
132
+ phone_type_name: "Mobile",
133
+ };
134
+ const adaWork = {
135
+ ...adaHome,
136
+ address_id: 102,
137
+ address_line1: "1 Parliament Sq",
138
+ };
139
+ const nested = mapper.map([adaHome, adaWork]);
140
+ const flat = mapper.reverseMap(nested);
141
+ expect(nested[0]).toEqual({
142
+ id: 7,
143
+ first_name: "Ada",
144
+ last_name: "Lovelace",
145
+ email: "ada@ex.com",
146
+ contact_source_name: "Web",
147
+ contact_type_name: "Person",
148
+ addresses: [
149
+ { id: 101, line1: "10 Downing St", city: "London", address_type_name: "Home" },
150
+ { id: 102, line1: "1 Parliament Sq", city: "London", address_type_name: "Home" },
151
+ ],
152
+ phones: [{ id: 201, number: "+44", phone_type_name: "Mobile" }],
153
+ });
154
+ expect(flat).toEqual([adaHome, adaWork]);
155
+ });
156
+
157
+ it("maps one denormalized row into a contact with 0-1 children", () => {
158
+ expect(mapper.map([adaHome])[0]).toEqual({
159
+ id: 7,
160
+ first_name: "Ada",
161
+ last_name: "Lovelace",
162
+ email: "ada@ex.com",
163
+ contact_source_name: "Web",
164
+ contact_type_name: "Person",
165
+ addresses: [{ id: 101, line1: "10 Downing St", city: "London", address_type_name: "Home" }],
166
+ phones: [{ id: 201, number: "+44", phone_type_name: "Mobile" }],
167
+ });
168
+ expect(mapper.map([noAddress])[0]?.addresses).toEqual([]);
169
+ expect(mapper.map([noAddress])[0]?.phones).toEqual([{ id: 202, number: "555", phone_type_name: "Work" }]);
170
+ });
171
+
172
+ it("reverseMaps the first child of each group", () => {
173
+ expect(mapper.reverseMap(mapper.map([adaHome]))).toEqual([adaHome]);
174
+ expect(mapper.reverseMap(mapper.map([noAddress]))).toEqual([noAddress]);
175
+ });
176
+
177
+ it("collapses duplicate join rows into one contact with unique children", () => {
178
+ const [ada, charles] = mapper.map([adaHome, adaWork, adaHome, noAddress]);
179
+ expect(ada).toEqual({
180
+ id: 7,
181
+ first_name: "Ada",
182
+ last_name: "Lovelace",
183
+ email: "ada@ex.com",
184
+ contact_source_name: "Web",
185
+ contact_type_name: "Person",
186
+ addresses: [
187
+ { id: 101, line1: "10 Downing St", city: "London", address_type_name: "Home" },
188
+ { id: 102, line1: "1 Parliament Sq", city: "London", address_type_name: "Home" },
189
+ ],
190
+ phones: [{ id: 201, number: "+44", phone_type_name: "Mobile" }],
191
+ });
192
+ expect(charles?.id).toBe(8);
193
+ expect(charles?.addresses).toEqual([]);
194
+ expect(charles?.phones).toHaveLength(1);
195
+ });
196
+
197
+ it("collapses already-mapped rows", () => {
198
+ const [ada] = mapper.map([mapper.map([adaHome])[0]!, mapper.map([adaWork])[0]!]);
199
+ expect(ada?.addresses).toHaveLength(2);
200
+ expect(ada?.phones).toHaveLength(1);
201
+ });
202
+
203
+ it("skips rows without a parent key and children without a group key", () => {
204
+ const orphan = { ...adaHome, id: null };
205
+ const mapped = mapper.map([adaHome])[0]!;
206
+ mapped.addresses = [
207
+ { id: 0, line1: "x", city: "y", address_type_name: "" },
208
+ { id: 101, line1: "dup", city: "London", address_type_name: "Home" },
209
+ ];
210
+ mapped.addresses[0] = {
211
+ id: undefined as unknown as number,
212
+ line1: "x",
213
+ city: "y",
214
+ address_type_name: "",
215
+ };
216
+ const extra = mapper.map([adaWork])[0]!;
217
+ extra.addresses = [
218
+ {
219
+ id: undefined as unknown as number,
220
+ line1: "ghost",
221
+ city: "None",
222
+ address_type_name: "",
223
+ },
224
+ ];
225
+ expect(mapper.map([orphan])).toEqual([]);
226
+ const [ada] = mapper.map([mapped, extra]);
227
+ expect(ada?.addresses.map((address) => address.line1)).toEqual(["x", "dup"]);
228
+ });
229
+
230
+ it("falls back to the spec key when it is not a mapped dest field", () => {
231
+ const names = new FlatToNestedMapper<{ id: number; name: string }>({
232
+ key: "id",
233
+ fields: { name: "name" },
234
+ });
235
+ expect(names.map([{ id: 1, name: "ada" }])).toEqual([{ id: 1, name: "ada" }]);
236
+ });
237
+
238
+ it("omits nested group fields when compiling a collection", () => {
239
+ type Row = { id: number; items: { id: number; title: string; kids: { n: number }[] }[] };
240
+ const nested = new FlatToNestedMapper<Row>({
241
+ key: "id",
242
+ fields: {
243
+ id: "id",
244
+ items: {
245
+ key: "item_id",
246
+ fields: {
247
+ id: "item_id",
248
+ title: "item_title",
249
+ kids: { key: "kid_id", fields: { n: "kid_n" } },
250
+ },
251
+ },
252
+ },
253
+ });
254
+ expect(nested.map([{ id: 1, item_id: 2 }])).toEqual([
255
+ {
256
+ id: 1,
257
+ items: [{ id: 2, title: null }],
258
+ },
259
+ ]);
260
+ expect(nested.map([{}])).toEqual([]);
261
+ expect(nested.reverseMap([{} as Row])).toEqual([
262
+ {
263
+ id: null,
264
+ item_id: null,
265
+ item_title: null,
266
+ },
267
+ ]);
268
+ });
269
+ });
@@ -0,0 +1,12 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { IdentityMapper } from "./IdentityMapper.ts";
3
+
4
+ describe("IdentityMapper", () => {
5
+ it("maps and reverses the same value", () => {
6
+ const mapper = new IdentityMapper<string>();
7
+ const mapped = mapper.map("contact");
8
+ const reversed = mapper.reverseMap("contact");
9
+ expect(mapped).toBe("contact");
10
+ expect(reversed).toBe("contact");
11
+ });
12
+ });
@@ -0,0 +1,40 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { ChangeCasingMapper } from "./ChangeCasingMapper.ts";
3
+ import { PluralizerMapper } from "./PluralizerMapper.ts";
4
+
5
+ describe("PluralizerMapper", () => {
6
+ it("pluralizes and lowercases the table name", () => {
7
+ const mapper = new PluralizerMapper();
8
+ expect(mapper.map("User")).toBe("Users");
9
+ expect(mapper.reverseMap("Users")).toBe("User");
10
+ });
11
+
12
+ it("maps contacts", () => {
13
+ const mapper = new PluralizerMapper();
14
+ const contact = mapper.map("Contact");
15
+ const address = mapper.map("Address");
16
+ expect(contact).toBe("Contacts");
17
+ expect(address).toBe("Addresses");
18
+ });
19
+
20
+ it("reverse maps contacts", () => {
21
+ const mapper = new PluralizerMapper();
22
+ const contact = mapper.reverseMap("Contacts");
23
+ const address = mapper.reverseMap("Addresses");
24
+ expect(contact).toBe("Contact");
25
+ expect(address).toBe("Address");
26
+ });
27
+
28
+ it("lowercases then pluralizes", () => {
29
+ const casing = new ChangeCasingMapper("pascalCase", "snakeCase");
30
+ const pluralizer = new PluralizerMapper();
31
+ const contact = pluralizer.map(casing.map("Contact"));
32
+ const address = pluralizer.map(casing.map("Address"));
33
+ const contactName = casing.reverseMap(pluralizer.reverseMap(contact));
34
+ const addressName = casing.reverseMap(pluralizer.reverseMap(address));
35
+ expect(contact).toBe("contacts");
36
+ expect(address).toBe("addresses");
37
+ expect(contactName).toBe("Contact");
38
+ expect(addressName).toBe("Address");
39
+ });
40
+ });
@@ -0,0 +1,12 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { MetaDataRowTypeConverter } from "../converters/MetaDataRowTypeConverter.ts";
3
+ import { RowTypeMapper } from "./RowTypeMapper.ts";
4
+
5
+ describe("RowTypeMapper", () => {
6
+ it("maps a flat row to T and back", () => {
7
+ const mapper = new RowTypeMapper<{ id: number; name: string }>(new MetaDataRowTypeConverter());
8
+ const row = { id: 1, name: "ada" };
9
+ expect(mapper.map(row)).toEqual(row);
10
+ expect(mapper.reverseMap(row)).toEqual(row);
11
+ });
12
+ });
@@ -0,0 +1,181 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { TypeConverter } from "../converters/TypeConverter.ts";
3
+ import { TransformConverterMapper } from "./TransformConverterMapper.ts";
4
+
5
+ class Wrap extends TypeConverter<string, string> {
6
+ convert = (from: string) => `[${from}]`;
7
+ reverseConvert = (to: string) => to.replace(/^\[|\]$/g, "");
8
+ }
9
+
10
+ describe("TransformConverterMapper", () => {
11
+ it("converts both ways without a filter", () => {
12
+ const mapper = new TransformConverterMapper(new Wrap());
13
+ expect(mapper.map("ab")).toBe("[ab]");
14
+ expect(mapper.reverseMap("[ab]")).toBe("ab");
15
+ });
16
+
17
+ it("converts when the filter is true", () => {
18
+ const mapper = new TransformConverterMapper(new Wrap(), (s) => s.includes("ab"));
19
+ expect(mapper.map("ab")).toBe("[ab]");
20
+ expect(mapper.reverseMap("[ab]")).toBe("ab");
21
+ });
22
+
23
+ it("is identity when the filter is false", () => {
24
+ const mapper = new TransformConverterMapper(new Wrap(), (s) => s.includes("ab"));
25
+ expect(mapper.map("a")).toBe("a");
26
+ expect(mapper.reverseMap("[a]")).toBe("[a]");
27
+ });
28
+
29
+ type Address = { street: string; city: string; country: string };
30
+ type Contact = { email: string; phone: string; address: Address };
31
+ type User = { username: string; displayName: string; contact: Contact };
32
+ type Payload = string[] | User;
33
+
34
+ class UserFromCells extends TypeConverter<Payload, Payload> {
35
+ convert = (from: Payload): Payload => {
36
+ if (!Array.isArray(from)) return from;
37
+ const [username, displayName, email, phone, street, city, country] = from;
38
+ if (from.length !== 7) throw new Error("expected 7 cells");
39
+ return {
40
+ username,
41
+ displayName,
42
+ contact: { email, phone, address: { street, city, country } },
43
+ };
44
+ };
45
+ reverseConvert = (to: Payload): Payload =>
46
+ Array.isArray(to)
47
+ ? to
48
+ : [
49
+ to.username,
50
+ to.displayName,
51
+ to.contact.email,
52
+ to.contact.phone,
53
+ to.contact.address.street,
54
+ to.contact.address.city,
55
+ to.contact.address.country,
56
+ ];
57
+ }
58
+
59
+ it("expands flat cells into a nested user", () => {
60
+ const cells = [
61
+ "ada",
62
+ "Ada Lovelace",
63
+ "ada@example.com",
64
+ "+44 20 7946 0958",
65
+ "10 Downing St",
66
+ "London",
67
+ "UK",
68
+ ];
69
+ const expected: User = {
70
+ username: "ada",
71
+ displayName: "Ada Lovelace",
72
+ contact: {
73
+ email: "ada@example.com",
74
+ phone: "+44 20 7946 0958",
75
+ address: { street: "10 Downing St", city: "London", country: "UK" },
76
+ },
77
+ };
78
+ const mapper = new TransformConverterMapper(new UserFromCells());
79
+ expect(mapper.map(cells)).toEqual(expected);
80
+ expect(mapper.reverseMap(expected)).toEqual(cells);
81
+ });
82
+
83
+ type Account = {
84
+ id: number;
85
+ username: string;
86
+ displayName: string;
87
+ isEnabled: boolean;
88
+ created: Date;
89
+ contact: Contact;
90
+ };
91
+ type AccountPayload = string[] | Account;
92
+
93
+ class AccountFromCells extends TypeConverter<AccountPayload, AccountPayload> {
94
+ convert = (from: AccountPayload): AccountPayload => {
95
+ if (!Array.isArray(from)) return from;
96
+ const [id, username, displayName, enabled, created, email, phone, street, city] = from;
97
+ if (from.length !== 10) throw new Error("expected 10 cells");
98
+ return {
99
+ id: Number(id),
100
+ username,
101
+ displayName,
102
+ isEnabled: enabled === "1",
103
+ created: new Date(created),
104
+ contact: { email, phone, address: { street, city, country: "" } },
105
+ };
106
+ };
107
+ reverseConvert = (to: AccountPayload): AccountPayload =>
108
+ Array.isArray(to)
109
+ ? to
110
+ : [
111
+ String(to.id),
112
+ to.username,
113
+ to.displayName,
114
+ to.isEnabled ? "1" : "0",
115
+ to.created.toISOString(),
116
+ to.contact.email,
117
+ to.contact.phone,
118
+ to.contact.address.street,
119
+ to.contact.address.city,
120
+ "",
121
+ ];
122
+ }
123
+
124
+ const isEnabled = (payload: AccountPayload) =>
125
+ Array.isArray(payload) ? payload[3] === "1" : payload.isEnabled;
126
+
127
+ it("converts types and filters rows", () => {
128
+ const ada = [
129
+ "7",
130
+ "ada",
131
+ "Ada Lovelace",
132
+ "1",
133
+ "2020-01-02T03:04:05.000Z",
134
+ "ada@example.com",
135
+ "+44",
136
+ "10 Downing St",
137
+ "London",
138
+ "secret-note",
139
+ ];
140
+ const guest = [
141
+ "0",
142
+ "guest",
143
+ "Guest",
144
+ "0",
145
+ "2020-01-01T00:00:00.000Z",
146
+ "guest@example.com",
147
+ "",
148
+ "1 Road",
149
+ "Paris",
150
+ "ignore-me",
151
+ ];
152
+ const expected: Account = {
153
+ id: 7,
154
+ username: "ada",
155
+ displayName: "Ada Lovelace",
156
+ isEnabled: true,
157
+ created: new Date("2020-01-02T03:04:05.000Z"),
158
+ contact: {
159
+ email: "ada@example.com",
160
+ phone: "+44",
161
+ address: { street: "10 Downing St", city: "London", country: "" },
162
+ },
163
+ };
164
+ const mapper = new TransformConverterMapper(new AccountFromCells(), isEnabled);
165
+ expect(mapper.map(ada)).toEqual(expected);
166
+ expect(mapper.reverseMap(expected)).toEqual([
167
+ "7",
168
+ "ada",
169
+ "Ada Lovelace",
170
+ "1",
171
+ "2020-01-02T03:04:05.000Z",
172
+ "ada@example.com",
173
+ "+44",
174
+ "10 Downing St",
175
+ "London",
176
+ "",
177
+ ]);
178
+ expect(mapper.map(guest)).toBe(guest);
179
+ expect(mapper.reverseMap(guest)).toBe(guest);
180
+ });
181
+ });
@@ -0,0 +1,66 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { TransformMapper } from "./TransformMapper.ts";
3
+
4
+ type User = { id: number; name: string };
5
+ type Contacts = { email: string; phone: string };
6
+ type Nested = { user: User; contacts: Contacts };
7
+ type Flat = {
8
+ userId: number;
9
+ userName: string;
10
+ contactEmail: string;
11
+ contactPhone: string;
12
+ };
13
+ type Contact = { name: string; email: string };
14
+ type ContactRow = { contactName: string; contactEmail: string };
15
+
16
+ describe("TransformMapper", () => {
17
+ it("flattens and unflattens user and contacts", () => {
18
+ const nested: Nested = {
19
+ user: { id: 1, name: "Ada" },
20
+ contacts: { email: "ada@x.dev", phone: "555" },
21
+ };
22
+ const mapper = new TransformMapper<Nested, Flat>(
23
+ (n) => ({
24
+ userId: n.user.id,
25
+ userName: n.user.name,
26
+ contactEmail: n.contacts.email,
27
+ contactPhone: n.contacts.phone,
28
+ }),
29
+ (f) => ({
30
+ user: { id: f.userId, name: f.userName },
31
+ contacts: { email: f.contactEmail, phone: f.contactPhone },
32
+ }),
33
+ );
34
+ const flat = mapper.map(nested);
35
+ expect(flat).toEqual({
36
+ userId: 1,
37
+ userName: "Ada",
38
+ contactEmail: "ada@x.dev",
39
+ contactPhone: "555",
40
+ });
41
+ expect(mapper.reverseMap(flat)).toEqual(nested);
42
+ });
43
+
44
+ it("prefixes a contact field", () => {
45
+ const mapper = new TransformMapper<string, string>(
46
+ (name) => `contact_${name}`,
47
+ (name) => name.slice("contact_".length),
48
+ );
49
+ const mapped = mapper.map("email");
50
+ const reversed = mapper.reverseMap(mapped);
51
+ expect(mapped).toBe("contact_email");
52
+ expect(reversed).toBe("email");
53
+ });
54
+
55
+ it("maps a contact name and email", () => {
56
+ const contact: Contact = { name: "Ada", email: "ada@x.dev" };
57
+ const mapper = new TransformMapper<Contact, ContactRow>(
58
+ (item) => ({ contactName: item.name, contactEmail: item.email }),
59
+ (row) => ({ name: row.contactName, email: row.contactEmail }),
60
+ );
61
+ const mapped = mapper.map(contact);
62
+ const reversed = mapper.reverseMap(mapped);
63
+ expect(mapped).toEqual({ contactName: "Ada", contactEmail: "ada@x.dev" });
64
+ expect(reversed).toEqual(contact);
65
+ });
66
+ });
@@ -0,0 +1,75 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import {
3
+ execute,
4
+ executeReader,
5
+ ExecuteStoredProcedureStatement,
6
+ Native,
7
+ MockCommand,
8
+ MockConnection,
9
+ MockReader,
10
+ } from "../index.ts";
11
+
12
+ describe("MockCommand", () => {
13
+ it("records command execute paths", async () => {
14
+ const connection = new MockConnection();
15
+ const command = new MockCommand(connection);
16
+ command.response = { result: 1, rowsAffected: 2 };
17
+ command.reader = new MockReader([{ id: 1 }]);
18
+ expect(await command.executeNative(new Native("insert"), [{ name: "id", value: 1 }])).toEqual({
19
+ result: 1,
20
+ rowsAffected: 2,
21
+ });
22
+ expect(command.executeCalls).toHaveLength(1);
23
+ const reader = await command.executeReaderNative(new Native(""));
24
+ expect(reader).toBe(command.reader);
25
+
26
+ command.reader = {
27
+ current: undefined,
28
+ next: async () => false,
29
+ nextResult: async () => false,
30
+ async *read() {
31
+ while (await this.next()) {
32
+ if (this.current !== undefined) yield this.current;
33
+ }
34
+ },
35
+ dispose: async () => {},
36
+ };
37
+ expect(await command.executeReaderNative(new Native("plain"))).toBe(command.reader);
38
+
39
+ command.executeError = new Error("exec");
40
+ await expect(command.executeNative(new Native(""))).rejects.toThrow("exec");
41
+ command.executeReaderError = new Error("read");
42
+ await expect(command.executeReaderNative(new Native("x"))).rejects.toThrow("read");
43
+
44
+ command.executeError = undefined;
45
+ command.executeReaderError = undefined;
46
+ const fields = [{ name: "id", value: 1 }];
47
+ const statement = new ExecuteStoredProcedureStatement("save", fields);
48
+ await execute(command, statement, fields);
49
+ expect(command.executeCalls.at(-1)).toEqual({
50
+ text: "CALL save($id)",
51
+ parameters: fields,
52
+ });
53
+ await executeReader(command, statement, fields);
54
+ expect(command.executeReaderCalls.at(-1)).toEqual({
55
+ text: "CALL save($id)",
56
+ parameters: fields,
57
+ });
58
+ });
59
+
60
+ it("runs command middleware on mock commands", async () => {
61
+ const connection = new MockConnection();
62
+ const command = new MockCommand(connection);
63
+ const seen: string[] = [];
64
+ command.addMiddleware({
65
+ beforeCommand: (text) => {
66
+ seen.push(`before:${text}`);
67
+ },
68
+ afterCommand: (text) => {
69
+ seen.push(`after:${text}`);
70
+ },
71
+ });
72
+ await command.executeNative(new Native("insert"));
73
+ expect(seen).toEqual(["before:insert", "after:insert"]);
74
+ });
75
+ });
@@ -0,0 +1,34 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { IsolationLevel, MockCommand, MockConnection, MockTransaction } from "../index.ts";
3
+
4
+ describe("MockConnection", () => {
5
+ it("records connection operations and errors", async () => {
6
+ const connection = new MockConnection();
7
+ await connection.open();
8
+ expect(connection.openCalls).toBe(1);
9
+ expect(connection.isOpen).toBe(true);
10
+ const txn = await connection.startTransaction(IsolationLevel.ReadUncommitted);
11
+ expect(txn).toBe(connection.transaction);
12
+ expect(txn.isolation).toBe(IsolationLevel.ReadUncommitted);
13
+ await connection.close();
14
+ expect(connection.isOpen).toBe(false);
15
+
16
+ connection.openError = new Error("open-fail");
17
+ await expect(connection.open()).rejects.toThrow("open-fail");
18
+ connection.closeError = new Error("close-fail");
19
+ await expect(connection.close()).rejects.toThrow("close-fail");
20
+ connection.startTransactionError = new Error("tx-fail");
21
+ await expect(connection.startTransaction()).rejects.toThrow("tx-fail");
22
+
23
+ const customTxn = new MockTransaction(connection);
24
+ const withTxn = new MockConnection(customTxn);
25
+ expect(withTxn.transaction).toBe(customTxn);
26
+ const opened = new MockConnection();
27
+ await opened.open();
28
+ await expect(opened.open()).rejects.toThrow("already open");
29
+ });
30
+
31
+ it("creates a command when none is assigned", () => {
32
+ expect(new MockConnection().createCommand()).toBeInstanceOf(MockCommand);
33
+ });
34
+ });