@deterministic-code/datasources 0.0.0-stage → 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 (508) 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 +24 -4
  209. package/src/ArrayReader.test.ts +23 -0
  210. package/src/ArrayReader.ts +32 -0
  211. package/src/BatchCommandIBulkWriteStrategy.ts +228 -0
  212. package/src/BuiltRepository.ts +17 -0
  213. package/src/BulkCRUDRepository.test.ts +183 -0
  214. package/src/BulkCRUDRepository.ts +84 -0
  215. package/src/BulkCommand.ts +6 -0
  216. package/src/BulkWriteEnrichment.ts +19 -0
  217. package/src/BulkWriteStrategy.test.ts +293 -0
  218. package/src/CRUDRepository.test.ts +420 -0
  219. package/src/CRUDRepository.ts +264 -0
  220. package/src/CRUDRepositoryBuilder.ts +53 -0
  221. package/src/CRUDRepositoryOptions.test.ts +76 -0
  222. package/src/CRUDRepositoryOptions.ts +8 -0
  223. package/src/CRUDRepositoryStatements.ts +43 -0
  224. package/src/CascadeDeleteEnrichmentStrategy.ts +51 -0
  225. package/src/CascadeDeleteUpdateEnrichmentStrategy.ts +28 -0
  226. package/src/Command.test.ts +105 -0
  227. package/src/CommandMiddlewareHost.test.ts +98 -0
  228. package/src/CommandMiddlewareHost.ts +113 -0
  229. package/src/Connection.test.ts +34 -0
  230. package/src/ConsoleCommandMiddleware.test.ts +149 -0
  231. package/src/ConsoleCommandMiddleware.ts +64 -0
  232. package/src/CreateEnrichmentStrategy.ts +82 -0
  233. package/src/Datasource.test.ts +63 -0
  234. package/src/Datasource.ts +152 -0
  235. package/src/DatasourceError.test.ts +10 -0
  236. package/src/DatasourceError.ts +6 -0
  237. package/src/DatasourceFactory.test.ts +159 -0
  238. package/src/DatasourceFactory.ts +86 -0
  239. package/src/DatasourceOptions.ts +1 -0
  240. package/src/DatasourceUrl.ts +37 -0
  241. package/src/DatasourceUrlParser.test.ts +173 -0
  242. package/src/DatasourceUrlParser.ts +188 -0
  243. package/src/DefaultCreateEnrichmentStrategy.ts +21 -0
  244. package/src/DefaultReadonlyEnrichmentStrategy.ts +164 -0
  245. package/src/DefaultUpdateEnrichmentStrategy.ts +40 -0
  246. package/src/DefaultWriteEnrichmentStrategy.ts +151 -0
  247. package/src/DeleteEnrichmentStrategy.ts +56 -0
  248. package/src/DriverLoader.ts +97 -0
  249. package/src/Enrichment.ts +15 -0
  250. package/src/EnrichmentPropertyType.ts +9 -0
  251. package/src/ExpectConsoleMiddleware.test.ts +79 -0
  252. package/src/ExpectConsoleMiddleware.ts +47 -0
  253. package/src/FirstResultReader.test.ts +43 -0
  254. package/src/FirstResultReader.ts +49 -0
  255. package/src/HashsetOptimizedReadonlyEnrichmentStrategy.ts +80 -0
  256. package/src/IBulkWriteStrategy.ts +13 -0
  257. package/src/ICRUDRepository.ts +13 -0
  258. package/src/ICRUDRepositoryFactory.ts +8 -0
  259. package/src/ICommand.ts +45 -0
  260. package/src/ICommandMiddleware.ts +20 -0
  261. package/src/IConnection.ts +13 -0
  262. package/src/IDatasource.ts +27 -0
  263. package/src/IDatasourceFactory.ts +6 -0
  264. package/src/IDriver.ts +24 -0
  265. package/src/IEnriched.ts +11 -0
  266. package/src/IReader.ts +8 -0
  267. package/src/IReadonlyEnrichmentStrategy.ts +19 -0
  268. package/src/IReadonlyRepository.ts +21 -0
  269. package/src/IRepository.ts +5 -0
  270. package/src/IRepositoryFactory.ts +8 -0
  271. package/src/IRepositoryTree.ts +5 -0
  272. package/src/IRepositoryTreeNode.ts +8 -0
  273. package/src/ITransaction.ts +9 -0
  274. package/src/IUnitOfWork.ts +5 -0
  275. package/src/IWriteEnrichmentStrategy.ts +8 -0
  276. package/src/Information.ts +5 -0
  277. package/src/IsolationLevel.test.ts +13 -0
  278. package/src/IsolationLevel.ts +8 -0
  279. package/src/LazyWriteEnrichmentStrategy.ts +5 -0
  280. package/src/NestedToObjectEnrichmentStrategy.ts +33 -0
  281. package/src/Parameter.ts +8 -0
  282. package/src/ParameterDirection.ts +9 -0
  283. package/src/ProviderCatalog.test.ts +110 -0
  284. package/src/ProviderCatalog.ts +21 -0
  285. package/src/ProviderNotImplementedError.test.ts +11 -0
  286. package/src/ProviderNotImplementedError.ts +9 -0
  287. package/src/ProviderNotInstalledError.test.ts +11 -0
  288. package/src/ProviderNotInstalledError.ts +9 -0
  289. package/src/ReadonlyRepository.test.ts +406 -0
  290. package/src/ReadonlyRepository.ts +276 -0
  291. package/src/ReadonlyRepositoryBuilder.ts +53 -0
  292. package/src/ReadonlyRepositoryMappingOptions.ts +15 -0
  293. package/src/ReadonlyRepositoryStatements.ts +13 -0
  294. package/src/RepositoryAccess.ts +14 -0
  295. package/src/RepositoryFactory.test.ts +94 -0
  296. package/src/RepositoryFactory.ts +115 -0
  297. package/src/RepositoryNotRegisteredError.ts +9 -0
  298. package/src/RepositoryTree.ts +6 -0
  299. package/src/RepositoryTreeNode.ts +11 -0
  300. package/src/Response.ts +15 -0
  301. package/src/Row.ts +3 -0
  302. package/src/SingleCommandIBulkWriteStrategy.ts +35 -0
  303. package/src/StatementMappers.test.ts +221 -0
  304. package/src/StatementMappers.ts +110 -0
  305. package/src/StoredEnrichment.ts +25 -0
  306. package/src/StoredProcedureBulkWriteStrategy.ts +164 -0
  307. package/src/StreamingReader.test.ts +126 -0
  308. package/src/StreamingReader.ts +64 -0
  309. package/src/Transaction.test.ts +37 -0
  310. package/src/UnitOfWorkTransaction.test.ts +43 -0
  311. package/src/UnitOfWorkTransaction.ts +50 -0
  312. package/src/UpdateEnrichmentStrategy.ts +97 -0
  313. package/src/UserViewCRUDRepository.test.ts +323 -0
  314. package/src/WriteMode.ts +1 -0
  315. package/src/builders/Field.ts +6 -0
  316. package/src/builders/Scalar.ts +1 -0
  317. package/src/builders/builders.test.ts +404 -0
  318. package/src/builders/index.ts +2 -0
  319. package/src/collectAsync.ts +7 -0
  320. package/src/converters/Cell.ts +10 -0
  321. package/src/converters/ChangeCasingConverter.test.ts +34 -0
  322. package/src/converters/ChangeCasingConverter.ts +59 -0
  323. package/src/converters/Constructable.ts +1 -0
  324. package/src/converters/MetaDataRowTypeConverter.ts +80 -0
  325. package/src/converters/PluralizerConverter.test.ts +43 -0
  326. package/src/converters/PluralizerConverter.ts +67 -0
  327. package/src/converters/RegexConverter.test.ts +11 -0
  328. package/src/converters/RegexConverter.ts +14 -0
  329. package/src/converters/ReplaceConverter.test.ts +9 -0
  330. package/src/converters/ReplaceConverter.ts +6 -0
  331. package/src/converters/RowTypeConverter.test.ts +79 -0
  332. package/src/converters/RowTypeConverter.ts +33 -0
  333. package/src/converters/ToLowerCaseConverter.test.ts +9 -0
  334. package/src/converters/ToLowerCaseConverter.ts +4 -0
  335. package/src/converters/ToUpperCaseConverter.test.ts +9 -0
  336. package/src/converters/ToUpperCaseConverter.ts +4 -0
  337. package/src/converters/TypeConverter.ts +6 -0
  338. package/src/createCRUDRepositoryOptions.ts +21 -0
  339. package/src/createReadonlyRepositoryMappingOptions.ts +26 -0
  340. package/src/dotEnv.test.ts +21 -0
  341. package/src/dotEnv.ts +16 -0
  342. package/src/edgeCases.test.ts +485 -0
  343. package/src/index.ts +222 -0
  344. package/src/mappers/ChangeCasingMapper.test.ts +32 -0
  345. package/src/mappers/ChangeCasingMapper.ts +15 -0
  346. package/src/mappers/DictionaryMapper.test.ts +69 -0
  347. package/src/mappers/DictionaryMapper.ts +16 -0
  348. package/src/mappers/FlatToNestedMapper.test.ts +269 -0
  349. package/src/mappers/FlatToNestedMapper.ts +127 -0
  350. package/src/mappers/IFlatToNestedMapper.ts +7 -0
  351. package/src/mappers/IMapper.ts +4 -0
  352. package/src/mappers/IdentityMapper.test.ts +12 -0
  353. package/src/mappers/IdentityMapper.ts +6 -0
  354. package/src/mappers/PluralizerMapper.test.ts +40 -0
  355. package/src/mappers/PluralizerMapper.ts +9 -0
  356. package/src/mappers/RowTypeMapper.test.ts +12 -0
  357. package/src/mappers/RowTypeMapper.ts +13 -0
  358. package/src/mappers/TransformConverterMapper.test.ts +181 -0
  359. package/src/mappers/TransformConverterMapper.ts +11 -0
  360. package/src/mappers/TransformMapper.ts +8 -0
  361. package/src/mappers/TypeMap.ts +10 -0
  362. package/src/mappers/TypeMapGroup.ts +6 -0
  363. package/src/mappers/transformMapper.test.ts +66 -0
  364. package/src/mocks/MockCommand.test.ts +75 -0
  365. package/src/mocks/MockConnection.test.ts +34 -0
  366. package/src/mocks/MockDatasource.test.ts +40 -0
  367. package/src/mocks/MockReader.test.ts +17 -0
  368. package/src/mocks/MockRepository.test.ts +32 -0
  369. package/src/mocks/MockSelectStatement.test.ts +11 -0
  370. package/src/mocks/MockTransaction.test.ts +17 -0
  371. package/src/mocks/MockWhere.test.ts +11 -0
  372. package/src/mocks/index.ts +9 -0
  373. package/src/mocks/mock-command.ts +66 -0
  374. package/src/mocks/mock-connection.ts +57 -0
  375. package/src/mocks/mock-datasource.ts +65 -0
  376. package/src/mocks/mock-driver.ts +27 -0
  377. package/src/mocks/mock-reader.ts +38 -0
  378. package/src/mocks/mock-repository.ts +91 -0
  379. package/src/mocks/mock-select-statement.ts +17 -0
  380. package/src/mocks/mock-transaction.ts +31 -0
  381. package/src/mocks/mock-where.ts +13 -0
  382. package/src/parametersToRecord.test.ts +19 -0
  383. package/src/parametersToRecord.ts +11 -0
  384. package/src/public-surface.test.ts +23 -0
  385. package/src/readWithWhereClauseAndCustomFlatToNestedObjectMappingOptions.test.ts +126 -0
  386. package/src/readerToAsyncIterable.test.ts +39 -0
  387. package/src/readerToAsyncIterable.ts +11 -0
  388. package/src/repository.test.ts +410 -0
  389. package/src/repository.type.test.ts +29 -0
  390. package/src/splitStatements.ts +99 -0
  391. package/src/statementParameters.ts +22 -0
  392. package/src/statementUsesParameter.ts +4 -0
  393. package/src/statements/Aggregate.ts +9 -0
  394. package/src/statements/ArrayExpression.ts +5 -0
  395. package/src/statements/BulkDeleteStatement.ts +18 -0
  396. package/src/statements/BulkDeleteStatementTextRenderer.ts +16 -0
  397. package/src/statements/BulkInsertStatement.ts +37 -0
  398. package/src/statements/BulkInsertStatementTextRenderer.ts +10 -0
  399. package/src/statements/BulkUpdateStatement.ts +24 -0
  400. package/src/statements/BulkUpdateStatementTextRenderer.ts +29 -0
  401. package/src/statements/CheckConstraint.ts +9 -0
  402. package/src/statements/Cmp.ts +12 -0
  403. package/src/statements/Column.ts +10 -0
  404. package/src/statements/ColumnDefault.ts +6 -0
  405. package/src/statements/Condition.ts +3 -0
  406. package/src/statements/ConditionRenderer.ts +17 -0
  407. package/src/statements/CreateIndexStatement.ts +18 -0
  408. package/src/statements/CreateIndexStatementTextRenderer.ts +11 -0
  409. package/src/statements/CreateTableStatement.ts +18 -0
  410. package/src/statements/CreateTableStatementTextRenderer.ts +92 -0
  411. package/src/statements/DefaultStatementTextRenderer.ts +9 -0
  412. package/src/statements/DeleteStatement.ts +15 -0
  413. package/src/statements/DeleteStatementTextRenderer.ts +12 -0
  414. package/src/statements/ExecuteStoredProcedureStatement.ts +15 -0
  415. package/src/statements/ExecuteStoredProcedureStatementTextRenderer.ts +15 -0
  416. package/src/statements/ExpressionEvaluator.ts +166 -0
  417. package/src/statements/ExpressionRenderer.ts +161 -0
  418. package/src/statements/ForeignKey.ts +23 -0
  419. package/src/statements/IBulkDeleteStatement.ts +3 -0
  420. package/src/statements/IBulkInsertStatement.ts +3 -0
  421. package/src/statements/IBulkUpdateStatement.ts +3 -0
  422. package/src/statements/ICondition.ts +5 -0
  423. package/src/statements/IConditionVisitor.ts +7 -0
  424. package/src/statements/ICreateIndexStatement.ts +3 -0
  425. package/src/statements/ICreateTableStatement.ts +3 -0
  426. package/src/statements/IDeleteStatement.ts +6 -0
  427. package/src/statements/IExecuteStoredProcedureStatement.ts +3 -0
  428. package/src/statements/IExpression.ts +1 -0
  429. package/src/statements/IInsertStatement.ts +3 -0
  430. package/src/statements/IReturnStatement.ts +3 -0
  431. package/src/statements/ISelectStatement.ts +6 -0
  432. package/src/statements/IStatement.ts +5 -0
  433. package/src/statements/IStatementVisitor.ts +27 -0
  434. package/src/statements/ITableConstraint.ts +6 -0
  435. package/src/statements/IUpdateStatement.ts +6 -0
  436. package/src/statements/IWhere.ts +7 -0
  437. package/src/statements/Identifier.ts +5 -0
  438. package/src/statements/InsertStatement.ts +15 -0
  439. package/src/statements/InsertStatementTextRenderer.ts +7 -0
  440. package/src/statements/Join.ts +26 -0
  441. package/src/statements/Literal.ts +6 -0
  442. package/src/statements/Native.ts +5 -0
  443. package/src/statements/NotNull.ts +3 -0
  444. package/src/statements/Operator.ts +234 -0
  445. package/src/statements/OrderBy.ts +19 -0
  446. package/src/statements/PrimaryKey.ts +9 -0
  447. package/src/statements/ReturnStatement.ts +15 -0
  448. package/src/statements/ReturnStatementTextRenderer.ts +15 -0
  449. package/src/statements/SelectInsertedIdStatement.test.ts +11 -0
  450. package/src/statements/SelectInsertedIdStatement.ts +11 -0
  451. package/src/statements/SelectInsertedIdStatementTextRenderer.ts +7 -0
  452. package/src/statements/SelectStatement.ts +34 -0
  453. package/src/statements/SelectStatementOptions.ts +16 -0
  454. package/src/statements/SelectStatementTextRenderer.ts +71 -0
  455. package/src/statements/StatementCompile.test.ts +231 -0
  456. package/src/statements/StatementEvaluator.ts +5 -0
  457. package/src/statements/StatementParser.test.ts +304 -0
  458. package/src/statements/StatementParser.ts +461 -0
  459. package/src/statements/StatementRenderer.ts +120 -0
  460. package/src/statements/StatementTextRenderer.ts +60 -0
  461. package/src/statements/Subquery.ts +6 -0
  462. package/src/statements/UniqueConstraint.ts +9 -0
  463. package/src/statements/UpdateStatement.ts +34 -0
  464. package/src/statements/UpdateStatementTextRenderer.ts +63 -0
  465. package/src/statements/Where.ts +250 -0
  466. package/src/statements/WhereText.ts +10 -0
  467. package/src/statements/index.ts +75 -0
  468. package/src/stubs/StubCommand.ts +30 -0
  469. package/src/stubs/StubConnection.ts +21 -0
  470. package/src/stubs/StubDatasource.test.ts +99 -0
  471. package/src/stubs/StubDatasource.ts +60 -0
  472. package/src/stubs/StubReader.ts +25 -0
  473. package/src/stubs/StubRepository.ts +42 -0
  474. package/src/stubs/StubTransaction.ts +17 -0
  475. package/src/stubs/createStubDatasource.ts +4 -0
  476. package/src/stubs/index.ts +7 -0
  477. package/src/stubs/notImplemented.ts +5 -0
  478. package/src/tests/contact/Address.ts +14 -0
  479. package/src/tests/contact/AddressType.ts +6 -0
  480. package/src/tests/contact/Base.ts +7 -0
  481. package/src/tests/contact/Contact.ts +12 -0
  482. package/src/tests/contact/ContactBase.ts +8 -0
  483. package/src/tests/contact/ContactChangeLog.ts +8 -0
  484. package/src/tests/contact/ContactGroup.ts +6 -0
  485. package/src/tests/contact/ContactGroupBase.ts +6 -0
  486. package/src/tests/contact/ContactGroupMember.ts +6 -0
  487. package/src/tests/contact/ContactSource.ts +6 -0
  488. package/src/tests/contact/ContactType.ts +6 -0
  489. package/src/tests/contact/ContactsService.test.ts +118 -0
  490. package/src/tests/contact/ContactsService.ts +32 -0
  491. package/src/tests/contact/ImportContactRequest.ts +4 -0
  492. package/src/tests/contact/ImportContactResult.ts +5 -0
  493. package/src/tests/contact/LegacyContact.ts +7 -0
  494. package/src/tests/contact/LegacyMigrationResult.ts +5 -0
  495. package/src/tests/contact/Phone.ts +9 -0
  496. package/src/tests/contact/PhoneType.ts +6 -0
  497. package/src/tests/contact/asContactsView.ts +63 -0
  498. package/src/tests/contact/contactColumns.ts +40 -0
  499. package/src/tests/contact/contactLookupIds.ts +32 -0
  500. package/src/tests/contact/contactLookups.ts +6 -0
  501. package/src/tests/contact/contactsParity.ts +63 -0
  502. package/src/tests/contact/contactsServiceOf.ts +38 -0
  503. package/src/tests/contact/contactsView.ts +31 -0
  504. package/src/tests/contact/expectContactsView.ts +23 -0
  505. package/src/tests/contact/extraContact.ts +61 -0
  506. package/src/tests/contact/logMapped.ts +5 -0
  507. package/src/tests/contact/seedContacts.ts +31 -0
  508. package/README.md +0 -3
@@ -0,0 +1,420 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { MemoryDriver } from "@datasources/memory";
3
+ import { collectAsync } from "./collectAsync.ts";
4
+ import { CRUDRepository } from "./CRUDRepository.ts";
5
+ import { CascadeDeleteUpdateEnrichmentStrategy } from "./CascadeDeleteUpdateEnrichmentStrategy.ts";
6
+ import { DefaultWriteEnrichmentStrategy } from "./DefaultWriteEnrichmentStrategy.ts";
7
+ import { ReadonlyRepository } from "./ReadonlyRepository.ts";
8
+ import type { IConnection } from "./IConnection.ts";
9
+ import type { IReadonlyRepository } from "./IReadonlyRepository.ts";
10
+ import type { IWriteEnrichmentStrategy } from "./IWriteEnrichmentStrategy.ts";
11
+ import type { Scalar } from "./builders/Scalar.ts";
12
+ import { DeleteEnrichmentStrategy } from "./DeleteEnrichmentStrategy.ts";
13
+ import { ChangeCasingMapper } from "./mappers/ChangeCasingMapper.ts";
14
+ import { DictionaryMapper } from "./mappers/DictionaryMapper.ts";
15
+ import type { IMapper } from "./mappers/IMapper.ts";
16
+ import { TransformMapper } from "./mappers/TransformMapper.ts";
17
+
18
+ class UserType {
19
+ Id = 0;
20
+ Name = "";
21
+ }
22
+
23
+ class Pref {
24
+ Locale = "";
25
+ Timezone = "";
26
+ }
27
+
28
+ class Setting {
29
+ Id = 0;
30
+ ParentId = 0;
31
+ Key = "";
32
+ Value = "";
33
+ }
34
+
35
+ class PrefRow {
36
+ Id = 0;
37
+ ParentId = 0;
38
+ Key = "";
39
+ Locale = "";
40
+ Timezone = "";
41
+ }
42
+
43
+ class Host {
44
+ Id = 0;
45
+ Label = "";
46
+ Settings: Record<string, string> = {};
47
+ Prefs: Record<string, Pref> = {};
48
+ }
49
+
50
+ class Parent {
51
+ Id = 0;
52
+ Type = new UserType();
53
+ TypeName = "";
54
+ Children: Child[] = [];
55
+ }
56
+
57
+ class Child {
58
+ Id = 0;
59
+ ParentId = 0;
60
+ Type = new UserType();
61
+ TypeName = "";
62
+ Label = "";
63
+ Notes: Note[] = [];
64
+ }
65
+
66
+ class Note {
67
+ Id = 0;
68
+ ChildId = 0;
69
+ Text = "";
70
+ }
71
+
72
+ class Tagged {
73
+ Id = 0;
74
+ Label = "";
75
+ Tags: Tag[] = [];
76
+ }
77
+
78
+ class Tag {
79
+ Id = 0;
80
+ Name = "";
81
+ }
82
+
83
+ class ParentTag {
84
+ Id = 0;
85
+ ParentId = 0;
86
+ TagId = 0;
87
+ }
88
+
89
+ class SkipDelete<T extends object> extends DeleteEnrichmentStrategy<T> {
90
+ deleteChildren(): Promise<void> {
91
+ return Promise.resolve();
92
+ }
93
+
94
+ deleteLinked(): Promise<void> {
95
+ return Promise.resolve();
96
+ }
97
+ }
98
+
99
+ type Seed = Record<string, Scalar>;
100
+
101
+ const snake = new ChangeCasingMapper("pascalCase", "snakeCase");
102
+ const references = new DictionaryMapper<string>([["Type", "type_id"]]);
103
+ const columns: IMapper<string, string> = new TransformMapper(
104
+ (name) => {
105
+ const mapped = references.map(name);
106
+ return mapped !== name ? mapped : snake.map(name);
107
+ },
108
+ (name) => {
109
+ const from = references.reverseMap(name);
110
+ return from !== name ? from : snake.reverseMap(name);
111
+ },
112
+ );
113
+
114
+ const repo = <T extends object>(
115
+ type: new () => T,
116
+ connection: IConnection,
117
+ write?: IWriteEnrichmentStrategy<T>,
118
+ ) => new CRUDRepository<T>(connection, { type, propertyMappings: columns, write });
119
+
120
+ const read = <T extends object>(type: new () => T, connection: IConnection) =>
121
+ new ReadonlyRepository(connection, { type, propertyMappings: columns });
122
+
123
+ const setup = async (rows: Seed[] = []): Promise<IConnection> => {
124
+ const connection = new MemoryDriver().connection();
125
+ await connection.open();
126
+ for (const row of rows) {
127
+ const response = await connection.createCommand().execute(
128
+ "insert",
129
+ Object.entries(row).map(([name, value]) => ({ name, value })),
130
+ );
131
+ if (typeof response.result === "number") row.id = response.result;
132
+ }
133
+ return connection;
134
+ };
135
+
136
+ const relate = (
137
+ parentsRepo: CRUDRepository<Parent>,
138
+ typesRepo: IReadonlyRepository<UserType>,
139
+ childrenRepo: IReadonlyRepository<Child>,
140
+ ) => {
141
+ parentsRepo.addEnriched({ destinationProperty: "TypeName", sourceProperty: "Type", relatedKey: "Id", relatedProperty: "Name" }, typesRepo);
142
+ parentsRepo.addEnriched({ destinationProperty: "Children", sourceProperty: "Id", relatedKey: "ParentId", type: "list" }, childrenRepo);
143
+ };
144
+
145
+ describe("CRUDRepository", () => {
146
+ it("round-trips string and object dictionaries", async () => {
147
+ const connection = await setup();
148
+ const hostsRepo = repo(Host, connection);
149
+ const settingsRepo = repo(Setting, connection);
150
+ const prefsRepo = repo(PrefRow, connection);
151
+ hostsRepo.addEnriched(
152
+ { destinationProperty: "Settings", sourceProperty: "Id", relatedKey: "ParentId", relatedProperty: "Value", type: "dictionary" },
153
+ settingsRepo,
154
+ );
155
+ hostsRepo.addEnriched(
156
+ { destinationProperty: "Prefs", sourceProperty: "Id", relatedKey: "ParentId", type: "dictionary" },
157
+ prefsRepo,
158
+ );
159
+ const created = await hostsRepo.create(
160
+ Object.assign(new Host(), {
161
+ Label: "ada",
162
+ Settings: { theme: "dark", density: "comfortable" },
163
+ Prefs: {
164
+ home: Object.assign(new Pref(), { Locale: "en-GB", Timezone: "Europe/London" }),
165
+ travel: Object.assign(new Pref(), { Locale: "en-US", Timezone: "America/New_York" }),
166
+ },
167
+ }),
168
+ );
169
+ expect(created.Id).toBeGreaterThan(0);
170
+ const loaded = await hostsRepo.readSingle();
171
+ expect(loaded?.Settings).toEqual({ theme: "dark", density: "comfortable" });
172
+ expect(loaded?.Prefs.home).toEqual({ Locale: "en-GB", Timezone: "Europe/London" });
173
+ expect(loaded?.Prefs.travel).toEqual({ Locale: "en-US", Timezone: "America/New_York" });
174
+ await hostsRepo.update(
175
+ Object.assign(new Host(), {
176
+ Id: created.Id,
177
+ Label: "ada",
178
+ Settings: { theme: "light", locale: "en" },
179
+ Prefs: {
180
+ home: Object.assign(new Pref(), { Locale: "en-GB", Timezone: "Europe/Paris" }),
181
+ work: Object.assign(new Pref(), { Locale: "en-US", Timezone: "America/New_York" }),
182
+ },
183
+ }),
184
+ );
185
+ const updated = await hostsRepo.readSingle();
186
+ expect(updated?.Settings).toEqual({ theme: "light", locale: "en" });
187
+ expect(updated?.Prefs.home).toEqual({ Locale: "en-GB", Timezone: "Europe/Paris" });
188
+ expect(updated?.Prefs.work).toEqual({ Locale: "en-US", Timezone: "America/New_York" });
189
+ expect(updated?.Prefs.travel).toBeUndefined();
190
+ });
191
+
192
+ it("creates children, stamps FK, and skips select", async () => {
193
+ const connection = await setup();
194
+ const parentsRepo = repo(Parent, connection);
195
+ const typesRepo = repo(UserType, connection);
196
+ const childrenRepo = repo(Child, connection);
197
+ const type = await typesRepo.create(Object.assign(new UserType(), { Name: "person" }));
198
+ expect(type.Id).toBeGreaterThan(0);
199
+ relate(parentsRepo, typesRepo, childrenRepo);
200
+ const created = await parentsRepo.create(Object.assign(new Parent(), {
201
+ Type: Object.assign(new UserType(), { Id: type.Id }),
202
+ Children: [
203
+ Object.assign(new Child(), { Label: "a" }),
204
+ Object.assign(new Child(), { Label: "b" }),
205
+ ],
206
+ }));
207
+ expect(created.Id).toBeGreaterThan(0);
208
+ const parent = (await collectAsync(parentsRepo.read()))[0];
209
+ const children = await collectAsync(childrenRepo.read());
210
+ expect(parent?.Id).toBe(created.Id);
211
+ expect(children).toHaveLength(2);
212
+ expect(children.every((child) => child.ParentId === parent?.Id)).toBe(true);
213
+ expect(children.map((child) => child.Label)).toEqual(["a", "b"]);
214
+ expect((await typesRepo.readSingle())?.Name).toBe("person");
215
+ });
216
+
217
+ it("applies generated parent id before child FKs", async () => {
218
+ const connection = await setup();
219
+ const parentsRepo = repo(Parent, connection);
220
+ const childrenRepo = repo(Child, connection);
221
+ parentsRepo.addEnriched({ destinationProperty: "Children", sourceProperty: "Id", relatedKey: "ParentId", type: "list" }, childrenRepo);
222
+ const created = await parentsRepo.create(Object.assign(new Parent(), {
223
+ Children: [Object.assign(new Child(), { Label: "n" })],
224
+ }));
225
+ expect(created.Id).toBeGreaterThan(0);
226
+ const parent = await parentsRepo.readSingle();
227
+ const child = await childrenRepo.readSingle();
228
+ expect(parent?.Id).toBe(created.Id);
229
+ expect(child?.ParentId).toBe(parent?.Id);
230
+ });
231
+
232
+ it("update replace-syncs children", async () => {
233
+ const connection = await setup();
234
+ const parentsRepo = repo(Parent, connection);
235
+ const childrenRepo = repo(Child, connection);
236
+ const parent = await parentsRepo.create(new Parent());
237
+ expect(parent.Id).toBeGreaterThan(0);
238
+ const kept = await childrenRepo.create(Object.assign(new Child(), { ParentId: parent.Id, Label: "a" }));
239
+ expect(kept.Id).toBeGreaterThan(0);
240
+ const dropped = await childrenRepo.create(Object.assign(new Child(), { ParentId: parent.Id, Label: "b" }));
241
+ expect(dropped.Id).toBeGreaterThan(0);
242
+ parentsRepo.addEnriched({ destinationProperty: "Children", sourceProperty: "Id", relatedKey: "ParentId", type: "list" }, childrenRepo);
243
+ const updated = await parentsRepo.update(Object.assign(new Parent(), {
244
+ Id: parent.Id,
245
+ Children: [
246
+ Object.assign(new Child(), { Id: kept.Id, Label: "aa" }),
247
+ Object.assign(new Child(), { Label: "c" }),
248
+ ],
249
+ }));
250
+ expect(updated.rowsAffected).toBe(1);
251
+ const children = await collectAsync(childrenRepo.read());
252
+ expect(children).toHaveLength(2);
253
+ expect(children.find((child) => child.Id === kept?.Id)?.Label).toBe("aa");
254
+ expect(children.find((child) => child.Id !== kept?.Id)?.Label).toBe("c");
255
+ expect(children.every((child) => child.ParentId === parent?.Id)).toBe(true);
256
+ });
257
+
258
+ it("cascade update deletes children and recreates", async () => {
259
+ const connection = await setup();
260
+ const parentsRepo = repo(
261
+ Parent,
262
+ connection,
263
+ new DefaultWriteEnrichmentStrategy(undefined, new CascadeDeleteUpdateEnrichmentStrategy()),
264
+ );
265
+ const childrenRepo = repo(Child, connection);
266
+ const notesRepo = repo(Note, connection);
267
+ const parent = await parentsRepo.create(new Parent());
268
+ expect(parent.Id).toBeGreaterThan(0);
269
+ const child = await childrenRepo.create(Object.assign(new Child(), { ParentId: parent.Id, Label: "a" }));
270
+ expect(child.Id).toBeGreaterThan(0);
271
+ const other = await childrenRepo.create(Object.assign(new Child(), { Label: "other" }));
272
+ expect(other.Id).toBeGreaterThan(0);
273
+ const note = await notesRepo.create(Object.assign(new Note(), { ChildId: child.Id, Text: "n" }));
274
+ expect(note.Id).toBeGreaterThan(0);
275
+ childrenRepo.addEnriched({ destinationProperty: "Notes", sourceProperty: "Id", relatedKey: "ChildId", type: "list" }, notesRepo);
276
+ parentsRepo.addEnriched({ destinationProperty: "Children", sourceProperty: "Id", relatedKey: "ParentId", type: "list" }, childrenRepo);
277
+ const updated = await parentsRepo.update(Object.assign(new Parent(), {
278
+ Id: parent.Id,
279
+ Children: [Object.assign(new Child(), { Id: child.Id, Label: "fresh" })],
280
+ }));
281
+ expect(updated.rowsAffected).toBe(1);
282
+ const created = (await collectAsync(childrenRepo.read())).find((row) => row.Label === "fresh");
283
+ expect(created?.Id).toBe(child?.Id);
284
+ expect(created?.Label).toBe("fresh");
285
+ expect(created?.ParentId).toBe(parent?.Id);
286
+ expect(await collectAsync(notesRepo.read())).toEqual([]);
287
+ });
288
+
289
+ it("cascade update replaces links and keeps related rows", async () => {
290
+ const connection = await setup();
291
+ const parentsRepo = repo(
292
+ Tagged,
293
+ connection,
294
+ new DefaultWriteEnrichmentStrategy(undefined, new CascadeDeleteUpdateEnrichmentStrategy()),
295
+ );
296
+ const tagsRepo = repo(Tag, connection);
297
+ const linksRepo = repo(ParentTag, connection);
298
+ const parent = await parentsRepo.create(new Tagged());
299
+ expect(parent.Id).toBeGreaterThan(0);
300
+ const keep = await tagsRepo.create(Object.assign(new Tag(), { Name: "keep" }));
301
+ expect(keep.Id).toBeGreaterThan(0);
302
+ const drop = await tagsRepo.create(Object.assign(new Tag(), { Name: "drop" }));
303
+ expect(drop.Id).toBeGreaterThan(0);
304
+ const oldLink = await linksRepo.create(Object.assign(new ParentTag(), { ParentId: parent.Id, TagId: keep.Id }));
305
+ expect(oldLink.Id).toBeGreaterThan(0);
306
+ const droppedLink = await linksRepo.create(Object.assign(new ParentTag(), { ParentId: parent.Id, TagId: drop.Id }));
307
+ expect(droppedLink.Id).toBeGreaterThan(0);
308
+ const leftover = await linksRepo.create(Object.assign(new ParentTag(), { TagId: keep.Id }));
309
+ expect(leftover.Id).toBeGreaterThan(0);
310
+ parentsRepo.addLinked(
311
+ { destinationProperty: "Tags", sourceProperty: "Id", relatedKey: "Id", association: { parent: "ParentId", related: "TagId" } },
312
+ tagsRepo,
313
+ linksRepo,
314
+ );
315
+ const updated = await parentsRepo.update(Object.assign(new Tagged(), {
316
+ Id: parent.Id,
317
+ Label: "p",
318
+ Tags: [Object.assign(new Tag(), { Id: keep.Id })],
319
+ }));
320
+ expect(updated.rowsAffected).toBe(1);
321
+ const links = (await collectAsync(linksRepo.read())).filter((link) => link.ParentId === parent.Id);
322
+ expect(links).toHaveLength(1);
323
+ expect(links[0]?.TagId).toBe(keep.Id);
324
+ expect(links[0]?.ParentId).toBe(parent.Id);
325
+ expect(links[0]?.Id).not.toBe(oldLink?.Id);
326
+ expect(await collectAsync(tagsRepo.read())).toHaveLength(2);
327
+ });
328
+
329
+ it("delete cascades children then parent", async () => {
330
+ const connection = await setup();
331
+ const parentsRepo = repo(Parent, connection);
332
+ const childrenRepo = repo(Child, connection);
333
+ const notesRepo = repo(Note, connection);
334
+ const parent = await parentsRepo.create(new Parent());
335
+ expect(parent.Id).toBeGreaterThan(0);
336
+ const child = await childrenRepo.create(Object.assign(new Child(), { ParentId: parent.Id, Label: "a" }));
337
+ expect(child.Id).toBeGreaterThan(0);
338
+ const note = await notesRepo.create(Object.assign(new Note(), { ChildId: child.Id, Text: "n" }));
339
+ expect(note.Id).toBeGreaterThan(0);
340
+ const other = await notesRepo.create(Object.assign(new Note(), { Text: "other" }));
341
+ expect(other.Id).toBeGreaterThan(0);
342
+ childrenRepo.addEnriched({ destinationProperty: "Notes", sourceProperty: "Id", relatedKey: "ChildId", type: "list" }, notesRepo);
343
+ parentsRepo.addEnriched({ destinationProperty: "Children", sourceProperty: "Id", relatedKey: "ParentId", type: "list" }, childrenRepo);
344
+ const removed = await parentsRepo.delete(Object.assign(new Parent(), { Id: parent.Id }));
345
+ expect(removed.rowsAffected).toBe(1);
346
+ expect(await collectAsync(parentsRepo.read())).toEqual([]);
347
+ expect(await collectAsync(childrenRepo.read())).toEqual([]);
348
+ expect((await notesRepo.readSingle())?.Text).toBe("other");
349
+ });
350
+
351
+ it("delete strategy can skip children", async () => {
352
+ const connection = await setup();
353
+ const childrenRepo = repo(Child, connection);
354
+ const parentsRepo = repo(Parent, connection, new DefaultWriteEnrichmentStrategy(new SkipDelete()));
355
+ const parent = await parentsRepo.create(new Parent());
356
+ expect(parent.Id).toBeGreaterThan(0);
357
+ const child = await childrenRepo.create(Object.assign(new Child(), { ParentId: parent.Id, Label: "a" }));
358
+ expect(child.Id).toBeGreaterThan(0);
359
+ parentsRepo.addEnriched({ destinationProperty: "Children", sourceProperty: "Id", relatedKey: "ParentId", type: "list" }, childrenRepo);
360
+ const removed = await parentsRepo.delete(Object.assign(new Parent(), { Id: parent.Id }));
361
+ expect(removed.rowsAffected).toBe(1);
362
+ expect(await collectAsync(parentsRepo.read())).toEqual([]);
363
+ expect((await childrenRepo.readSingle())?.Label).toBe("a");
364
+ });
365
+
366
+ it("writes nested many and skips nested select", async () => {
367
+ const connection = await setup();
368
+ const parentsRepo = repo(Parent, connection);
369
+ const childrenRepo = repo(Child, connection);
370
+ const notesRepo = repo(Note, connection);
371
+ const childTypesRepo = repo(UserType, connection);
372
+ const home = await childTypesRepo.create(Object.assign(new UserType(), { Name: "home" }));
373
+ expect(home.Id).toBeGreaterThan(0);
374
+ childrenRepo.addEnriched({ destinationProperty: "TypeName", sourceProperty: "Type", relatedKey: "Id", relatedProperty: "Name" }, childTypesRepo);
375
+ childrenRepo.addEnriched({ destinationProperty: "Notes", sourceProperty: "Id", relatedKey: "ChildId", type: "list" }, notesRepo);
376
+ parentsRepo.addEnriched({ destinationProperty: "Children", sourceProperty: "Id", relatedKey: "ParentId", type: "list" }, childrenRepo);
377
+ const created = await parentsRepo.create(Object.assign(new Parent(), {
378
+ Children: [Object.assign(new Child(), {
379
+ Type: Object.assign(new UserType(), { Id: home.Id }),
380
+ Label: "a",
381
+ Notes: [Object.assign(new Note(), { Text: "hi" })],
382
+ })],
383
+ }));
384
+ expect(created.Id).toBeGreaterThan(0);
385
+ const note = await notesRepo.readSingle();
386
+ const child = await childrenRepo.readSingle();
387
+ expect(note?.Text).toBe("hi");
388
+ expect(note?.ChildId).toBe(child?.Id);
389
+ expect((await childTypesRepo.readSingle())?.Name).toBe("home");
390
+ });
391
+
392
+ it("throws when many via is readonly", async () => {
393
+ const connection = await setup();
394
+ const parentsRepo = repo(Parent, connection);
395
+ parentsRepo.addEnriched(
396
+ { destinationProperty: "Children", sourceProperty: "Id", relatedKey: "ParentId", type: "list" },
397
+ read(Child, connection),
398
+ );
399
+ await expect(parentsRepo.create(Object.assign(new Parent(), {
400
+ Children: [Object.assign(new Child(), { Label: "a" })],
401
+ }))).rejects.toThrow(/Children/);
402
+ });
403
+
404
+ it("get still enriches", async () => {
405
+ const org: Seed = { name: "org" };
406
+ const typesConnection = await setup([org]);
407
+ const parent: Seed = { type_id: org.id };
408
+ const connection = await setup([parent]);
409
+ const parentsRepo = repo(Parent, connection);
410
+ const childrenConnection = await setup([{ parent_id: parent.id, label: "a" }]);
411
+ relate(
412
+ parentsRepo,
413
+ read(UserType, typesConnection),
414
+ read(Child, childrenConnection),
415
+ );
416
+ const item = await parentsRepo.readSingle();
417
+ expect(item?.TypeName).toBe("org");
418
+ expect(item?.Children.map((child) => child.Label)).toEqual(["a"]);
419
+ });
420
+ });
@@ -0,0 +1,264 @@
1
+ import type { IConnection } from "./IConnection.ts";
2
+ import type { ICRUDRepository } from "./ICRUDRepository.ts";
3
+ import type { Response } from "./Response.ts";
4
+ import type { IWhere } from "./statements/IWhere.ts";
5
+ import type { Field } from "./builders/Field.ts";
6
+ import type { Scalar } from "./builders/Scalar.ts";
7
+ import type { IExpression } from "./statements/IExpression.ts";
8
+ import { Literal } from "./statements/Literal.ts";
9
+ import { DefaultStatementTextRenderer, Operator, Where } from "./statements/index.ts";
10
+ import type { Cell, Cells } from "./converters/Cell.ts";
11
+ import type { Constructable } from "./converters/Constructable.ts";
12
+ import type { CRUDRepositoryOptions } from "./CRUDRepositoryOptions.ts";
13
+ import { repositoryParts } from "./CRUDRepositoryBuilder.ts";
14
+ import type { CRUDRepositoryStatements } from "./CRUDRepositoryStatements.ts";
15
+ import { MetaDataRowTypeConverter } from "./converters/MetaDataRowTypeConverter.ts";
16
+ import { enrichmentsOf, ReadonlyRepository } from "./ReadonlyRepository.ts";
17
+ import type { Enrichment } from "./Enrichment.ts";
18
+ import type { IReadonlyEnrichmentStrategy } from "./IReadonlyEnrichmentStrategy.ts";
19
+ import type { IReadonlyRepository } from "./IReadonlyRepository.ts";
20
+ import { DefaultReadonlyEnrichmentStrategy } from "./DefaultReadonlyEnrichmentStrategy.ts";
21
+ import { StoredEnrichment } from "./StoredEnrichment.ts";
22
+ import { execute, executeReader } from "./ICommand.ts";
23
+ import { DatasourceError } from "./DatasourceError.ts";
24
+ import { DefaultWriteEnrichmentStrategy } from "./DefaultWriteEnrichmentStrategy.ts";
25
+ import type { IWriteEnrichmentStrategy } from "./IWriteEnrichmentStrategy.ts";
26
+ import { UnitOfWorkTransaction } from "./UnitOfWorkTransaction.ts";
27
+ import type { IRepositoryTree } from "./IRepositoryTree.ts";
28
+ import { RepositoryTree } from "./RepositoryTree.ts";
29
+ import { RepositoryTreeNode } from "./RepositoryTreeNode.ts";
30
+
31
+ type Props = Record<string, Scalar | object>;
32
+
33
+ const assigned = (value: Cell): boolean =>
34
+ value !== null && value !== "" && value !== 0 && value !== 0n;
35
+
36
+ const fieldsOf = (
37
+ record: Cells,
38
+ include: (col: string) => boolean,
39
+ cell: (property: string, value: Cell, fieldsApplied: boolean) => Cell,
40
+ enrichmentInto: (name: string) => boolean,
41
+ ): Field[] =>
42
+ Object.entries(record)
43
+ .filter(([key, value]) => include(key) && !enrichmentInto(key) && value !== null && value !== undefined)
44
+ .sort(([left], [right]) => left.localeCompare(right))
45
+ .map(([name, value]) => ({ name, value: cell(name, value, true) as Scalar }));
46
+
47
+ const isCells = (id: Scalar | Cells): id is Cells => typeof id === "object" && id !== null;
48
+
49
+ const named = (row: Cells, property: string, sqlColumn: string): Cells => {
50
+ if (property === sqlColumn || !(sqlColumn in row)) return row;
51
+ const { [sqlColumn]: cell, ...rest } = row;
52
+ return { ...rest, [property]: cell };
53
+ };
54
+
55
+ const identified = (row: Cells, column: string): Cells => {
56
+ const values = Object.values(row);
57
+ return column in row || values.length !== 1 ? row : { [column]: values[0] };
58
+ };
59
+
60
+ const scalarOf = (value: Scalar | object | undefined): Scalar | undefined =>
61
+ value === undefined || value === null || typeof value === "object" ? undefined : value;
62
+
63
+
64
+ export class CRUDRepository<T = Cells>
65
+ extends ReadonlyRepository<T>
66
+ implements ICRUDRepository<T>
67
+ {
68
+ readonly ids: readonly string[];
69
+ private readonly crudStatements: CRUDRepositoryStatements;
70
+ private readonly tree: IRepositoryTree;
71
+ private readonly write: IWriteEnrichmentStrategy<T>;
72
+ private get useTransaction(): boolean {
73
+ return this.write.useTransaction;
74
+ }
75
+
76
+ constructor(type: Constructable<T>, connection: IConnection);
77
+ constructor(connection: IConnection, options: CRUDRepositoryOptions<T>);
78
+ constructor(
79
+ typeOrConnection: Constructable<T> | IConnection,
80
+ connectionOrOptions?: IConnection | CRUDRepositoryOptions<T>,
81
+ ) {
82
+ const options = connectionOrOptions as IConnection | CRUDRepositoryOptions<T>;
83
+ const opened = repositoryParts(typeOrConnection, options);
84
+ super(opened.built);
85
+ this.ids = opened.built.ids;
86
+ this.crudStatements = opened.statements;
87
+ this.tree = new RepositoryTree(new RepositoryTreeNode(this, ""));
88
+ this.write = (options as CRUDRepositoryOptions<T>).write ?? new DefaultWriteEnrichmentStrategy<T>();
89
+ }
90
+
91
+ override addEnriched<TRelated extends object>(
92
+ enrichment: Enrichment,
93
+ via: IReadonlyRepository<TRelated>,
94
+ strategy?: IReadonlyEnrichmentStrategy<T & object>,
95
+ ): this {
96
+ super.addEnriched(enrichment, via, strategy);
97
+ const writer = writerOf(via);
98
+ if (writer) lastEnrichment(this).persist = writer;
99
+ return this;
100
+ }
101
+
102
+ addLinked<TRelated extends object, TLink extends object>(
103
+ enrichment: Enrichment,
104
+ via: IReadonlyRepository<TRelated>,
105
+ link: ICRUDRepository<TLink>,
106
+ strategy?: IReadonlyEnrichmentStrategy<T & object>,
107
+ ): this {
108
+ const chosen = strategy ?? new DefaultReadonlyEnrichmentStrategy<T & object>();
109
+ super.addEnriched(enrichment, via, strategy);
110
+ const stored = lastEnrichment(this) as StoredEnrichment<T & object>;
111
+ stored.apply = (parents, sources) =>
112
+ chosen.applyLinked(via, link, enrichment, parents, sources);
113
+ stored.link = link as ICRUDRepository<object>;
114
+ return this;
115
+ }
116
+
117
+ create = (item: T): Promise<T> => this.inTransaction(() => this.createCore(item));
118
+
119
+ update = (item: T, byWhere?: IWhere): Promise<Response> =>
120
+ this.inTransaction(() => this.updateCore(item, byWhere));
121
+
122
+ delete = (item: T, byWhere?: IWhere): Promise<Response> =>
123
+ this.inTransaction(() => this.deleteCore(item, byWhere));
124
+
125
+ private inTransaction = async <TResult>(write: () => Promise<TResult>): Promise<TResult> => {
126
+ if (!this.useTransaction) return write();
127
+ const work = new UnitOfWorkTransaction(this.command().connection);
128
+ await work.begin();
129
+ try {
130
+ const result = await write();
131
+ await work.commit();
132
+ return result;
133
+ } catch (error) {
134
+ await work.abort();
135
+ throw error;
136
+ }
137
+ };
138
+
139
+ private createCore = async (item: T): Promise<T> => {
140
+ await this.insert(item);
141
+ if (!this.enriched) return item;
142
+ await this.write.persist(this.tree, [item], "create");
143
+ if (this.ids.length !== 1) return item;
144
+ const id = this.idValue(item);
145
+ return id === undefined ? item : (await this.readSingleById(id)) ?? item;
146
+ };
147
+
148
+ private updateCore = async (item: T, byWhere?: IWhere): Promise<Response> => {
149
+ const response = await this.save(item, byWhere);
150
+ if (this.enriched) await this.write.persist(this.tree, [item], "update");
151
+ return response;
152
+ };
153
+
154
+ private deleteCore = async (item: T, byWhere?: IWhere): Promise<Response> => {
155
+ if (this.enriched) await this.write.persist(this.tree, [item], "delete");
156
+ return this.remove(item, byWhere);
157
+ };
158
+
159
+ readSingleById = async (id: Scalar | Cells): Promise<T | undefined> =>
160
+ this.readSingle(this.byId(id));
161
+
162
+ private insert = async (item: T): Promise<Response> => {
163
+ const record = this.reverseConvert(item);
164
+ const fields = fieldsOf(
165
+ record,
166
+ (col) => !this.ids.includes(col) || assigned(record[col] ?? null),
167
+ this.cell,
168
+ this.enrichmentInto,
169
+ );
170
+ const statement = this.crudStatements.insert(fields);
171
+ const response = await execute(this.command(), statement, statement.fields);
172
+ if (this.ids.length === 1 && !fields.some((field) => field.name === this.ids[0]))
173
+ await this.copyInsertedId(item, fields);
174
+ return response;
175
+ };
176
+
177
+ private copyInsertedId = async (target: T, fields: Field[]): Promise<void> => {
178
+ void fields;
179
+ const column = this.ids[0]!;
180
+ const statement = this.crudStatements.selectInsertedId(column);
181
+ const sqlColumn = statement.column;
182
+ const reader = await executeReader(this.command(), statement);
183
+ try {
184
+ if (!(await reader.next())) return;
185
+ const current = reader.current;
186
+ if (!current) throw new DatasourceError("Reader current must be a row");
187
+ const row = identified(named(current, column, sqlColumn), column);
188
+ const cells = this.rowConverter ? this.rowConverter.convert(row) : row;
189
+ const converted =
190
+ this.objectConverter instanceof MetaDataRowTypeConverter
191
+ ? this.objectConverter.convert(cells, this.type)
192
+ : this.objectConverter!.convert(cells);
193
+ const value = scalarOf((converted as Props)[column]);
194
+ if (value !== undefined) (target as Props)[column] = value;
195
+ } finally {
196
+ await reader.dispose();
197
+ }
198
+ };
199
+
200
+ private idValue = (item: T): Scalar | undefined => scalarOf((item as Props)[this.ids[0]!]);
201
+
202
+ private save = async (item: T, byWhere?: IWhere): Promise<Response> => {
203
+ const record = this.reverseConvert(item);
204
+ const fields = fieldsOf(record, (col) => !this.ids.includes(col), this.cell, this.enrichmentInto);
205
+ if (fields.length === 0) throw new DatasourceError("Update requires at least one non-Id field");
206
+ const statement = this.crudStatements.update(
207
+ fields,
208
+ byWhere ?? this.idWhere(record),
209
+ (name, value) => this.cell(name, value, byWhere === undefined),
210
+ );
211
+ return execute(this.command(), statement, statement.parameters);
212
+ };
213
+
214
+ private remove = async (item: T, byWhere?: IWhere): Promise<Response> => {
215
+ const record = this.reverseConvert(item);
216
+ const statement = this.crudStatements.delete(
217
+ byWhere ?? this.idWhere(record),
218
+ (name, value) => this.cell(name, value, byWhere === undefined),
219
+ );
220
+ const listed = statement.where?.parameters ?? [];
221
+ const parameters = listed.length
222
+ ? listed
223
+ : DefaultStatementTextRenderer.instance.render(statement).includes("$")
224
+ ? fieldsOf(record, () => true, this.cell, this.enrichmentInto)
225
+ : [];
226
+ return execute(this.command(), statement, parameters);
227
+ };
228
+
229
+ private byId = (id: Scalar | Cells): IWhere => {
230
+ if (this.ids.length === 0) throw new DatasourceError("ReadSingleById requires an Id column");
231
+ if (isCells(id)) return this.idWhere(id);
232
+ if (this.ids.length !== 1)
233
+ throw new DatasourceError("Composite id requires a row keyed by each Id column");
234
+ return this.idWhere({ [this.ids[0]!]: id });
235
+ };
236
+
237
+ private reverseConvert = (item: T): Cells => {
238
+ const row = this.objectConverter!.reverseConvert(item);
239
+ return this.rowConverter ? this.rowConverter.reverseConvert(row) : row;
240
+ };
241
+
242
+ private idWhere = (record: Cells): IWhere => {
243
+ const parts: IExpression[] = this.ids.map((col) => {
244
+ if (!Object.prototype.hasOwnProperty.call(record, col))
245
+ throw new DatasourceError(`Missing Id value "${col}"`);
246
+ return new Operator.Equals(col, record[col] as Scalar);
247
+ });
248
+ return new Where(
249
+ parts.length === 0
250
+ ? new Operator.Equals(new Literal(1), new Literal(1))
251
+ : parts.reduce((left, right) => new Operator.And(left, right)),
252
+ );
253
+ };
254
+
255
+ protected enrichmentInto = (name: string): boolean =>
256
+ enrichmentsOf(this)!.some((entry) => entry.enrichment.destinationProperty === name);
257
+ }
258
+
259
+ const writerOf = <TRelated extends object>(
260
+ via: IReadonlyRepository<TRelated>,
261
+ ): ICRUDRepository<object> | undefined =>
262
+ "create" in via && typeof via.create === "function" ? (via as ICRUDRepository<object>) : undefined;
263
+
264
+ const lastEnrichment = (host: object): StoredEnrichment => enrichmentsOf(host)!.at(-1)!;