@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,323 @@
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 { LazyWriteEnrichmentStrategy } from "./LazyWriteEnrichmentStrategy.ts";
6
+ import type { IConnection } from "./IConnection.ts";
7
+
8
+ class UserView {
9
+ id = 0;
10
+ username = "";
11
+ is_enabled = false;
12
+ user_type_id = 0;
13
+ user_type?: UserType;
14
+ roles: Role[] | null = null;
15
+ groups: Group[] | null = null;
16
+ }
17
+
18
+ class UserType {
19
+ id = 0;
20
+ name = "";
21
+ }
22
+
23
+ class Role {
24
+ id = 0;
25
+ name = "";
26
+ }
27
+
28
+ class Group {
29
+ id = 0;
30
+ name = "";
31
+ }
32
+
33
+ class UserRole {
34
+ user_id = 0;
35
+ role_id = 0;
36
+ }
37
+
38
+ class UserGroup {
39
+ user_id = 0;
40
+ group_id = 0;
41
+ }
42
+
43
+ type Graph = {
44
+ usersRepo: CRUDRepository<UserView>;
45
+ userTypesRepo: CRUDRepository<UserType>;
46
+ rolesRepo: CRUDRepository<Role>;
47
+ groupsRepo: CRUDRepository<Group>;
48
+ userRolesRepo: CRUDRepository<UserRole>;
49
+ userGroupsRepo: CRUDRepository<UserGroup>;
50
+ adminType: UserType;
51
+ adminRole: Role;
52
+ editorRole: Role;
53
+ engineering: Group;
54
+ ops: Group;
55
+ };
56
+
57
+ const setup = async (): Promise<IConnection> => {
58
+ const connection = new MemoryDriver().connection();
59
+ await connection.open();
60
+ return connection;
61
+ };
62
+
63
+ const ids = (values: number[]) => [...values].sort((left, right) => left - right);
64
+
65
+ const graph = async (write?: LazyWriteEnrichmentStrategy<UserView>): Promise<Graph> => {
66
+ const connection = await setup();
67
+ const usersRepo = new CRUDRepository(connection, { type: UserView, write });
68
+ const userTypesRepo = new CRUDRepository(UserType, connection);
69
+ const rolesRepo = new CRUDRepository(Role, connection);
70
+ const groupsRepo = new CRUDRepository(Group, connection);
71
+ const userRolesRepo = new CRUDRepository(UserRole, connection);
72
+ const userGroupsRepo = new CRUDRepository(UserGroup, connection);
73
+ const adminType = await userTypesRepo.create(Object.assign(new UserType(), { name: "admin" }));
74
+ const adminRole = await rolesRepo.create(Object.assign(new Role(), { name: "admin" }));
75
+ const editorRole = await rolesRepo.create(Object.assign(new Role(), { name: "editor" }));
76
+ const engineering = await groupsRepo.create(Object.assign(new Group(), { name: "engineering" }));
77
+ const ops = await groupsRepo.create(Object.assign(new Group(), { name: "ops" }));
78
+ return { usersRepo, userTypesRepo, rolesRepo, groupsRepo, userRolesRepo, userGroupsRepo, adminType, adminRole, editorRole, engineering, ops };
79
+ };
80
+
81
+ const link = (usersRepo: CRUDRepository<UserView>, seeded: Graph): void => {
82
+ usersRepo.addEnriched({ destinationProperty: "user_type", sourceProperty: "user_type_id", relatedKey: "id" }, seeded.userTypesRepo);
83
+ usersRepo.addLinked(
84
+ { destinationProperty: "roles", sourceProperty: "id", relatedKey: "id", association: { parent: "user_id", related: "role_id" } },
85
+ seeded.rolesRepo,
86
+ seeded.userRolesRepo,
87
+ );
88
+ usersRepo.addLinked(
89
+ { destinationProperty: "groups", sourceProperty: "id", relatedKey: "id", association: { parent: "user_id", related: "group_id" } },
90
+ seeded.groupsRepo,
91
+ seeded.userGroupsRepo,
92
+ );
93
+ };
94
+
95
+ const createAda = async (seeded: Graph, roles: Role[] | null, groups: Group[] | null): Promise<UserView> => {
96
+ link(seeded.usersRepo, seeded);
97
+ return seeded.usersRepo.create(Object.assign(new UserView(), {
98
+ username: "ada",
99
+ is_enabled: true,
100
+ user_type_id: seeded.adminType.id,
101
+ roles,
102
+ groups,
103
+ }));
104
+ };
105
+
106
+ describe("CRUDRepository user view", () => {
107
+ it("creates, updates, and deletes through link rows", async () => {
108
+ const seeded = await graph();
109
+ const usersRepo = seeded.usersRepo;
110
+ usersRepo.addEnriched({ destinationProperty: "user_type", sourceProperty: "user_type_id", relatedKey: "id" }, seeded.userTypesRepo);
111
+ usersRepo.addLinked(
112
+ { destinationProperty: "roles", sourceProperty: "id", relatedKey: "id", association: { parent: "user_id", related: "role_id" } },
113
+ seeded.rolesRepo,
114
+ seeded.userRolesRepo,
115
+ );
116
+ usersRepo.addLinked(
117
+ { destinationProperty: "groups", sourceProperty: "id", relatedKey: "id", association: { parent: "user_id", related: "group_id" } },
118
+ seeded.groupsRepo,
119
+ seeded.userGroupsRepo,
120
+ );
121
+ const created = await usersRepo.create(Object.assign(new UserView(), {
122
+ username: "ada",
123
+ is_enabled: true,
124
+ user_type_id: seeded.adminType.id,
125
+ roles: [seeded.adminRole, seeded.editorRole],
126
+ groups: [seeded.engineering],
127
+ }));
128
+ const createdRoleLinks = await collectAsync(seeded.userRolesRepo.read());
129
+ const createdGroupLinks = await collectAsync(seeded.userGroupsRepo.read());
130
+ const saved = await usersRepo.update(Object.assign(new UserView(), {
131
+ id: created.id,
132
+ username: "ada",
133
+ is_enabled: true,
134
+ user_type_id: seeded.adminType.id,
135
+ roles: [seeded.editorRole],
136
+ groups: [seeded.engineering, seeded.ops],
137
+ }));
138
+ const updated = await usersRepo.readSingleById(created.id);
139
+ const updatedRoleLinks = await collectAsync(seeded.userRolesRepo.read());
140
+ const updatedGroupLinks = await collectAsync(seeded.userGroupsRepo.read());
141
+ const removed = await usersRepo.delete(Object.assign(new UserView(), { id: created.id }));
142
+
143
+ expect(created.id > 0).toEqual(true);
144
+ expect(created.username).toEqual("ada");
145
+ expect(created.is_enabled).toEqual(true);
146
+ expect(created.user_type_id).toEqual(seeded.adminType.id);
147
+ expect(created.user_type?.name).toEqual("admin");
148
+ expect(ids(created.roles?.map((role) => role.id) ?? [])).toEqual(ids([seeded.adminRole.id, seeded.editorRole.id]));
149
+ expect(created.groups?.map((group) => group.name)).toEqual(["engineering"]);
150
+ expect(createdRoleLinks.map((linkRow) => linkRow.user_id)).toEqual([created.id, created.id]);
151
+ expect(ids(createdRoleLinks.map((linkRow) => linkRow.role_id))).toEqual(ids([seeded.adminRole.id, seeded.editorRole.id]));
152
+ expect(createdGroupLinks.map((linkRow) => linkRow.user_id)).toEqual([created.id]);
153
+ expect(createdGroupLinks.map((linkRow) => linkRow.group_id)).toEqual([seeded.engineering.id]);
154
+ expect(saved.rowsAffected).toEqual(1);
155
+ expect(updated?.id).toEqual(created.id);
156
+ expect(updated?.username).toEqual("ada");
157
+ expect(updated?.is_enabled).toEqual(true);
158
+ expect(updated?.user_type_id).toEqual(seeded.adminType.id);
159
+ expect(updated?.roles?.map((role) => role.id)).toEqual([seeded.editorRole.id]);
160
+ expect(ids(updated?.groups?.map((group) => group.id) ?? [])).toEqual(ids([seeded.engineering.id, seeded.ops.id]));
161
+ expect(updatedRoleLinks.map((linkRow) => linkRow.user_id)).toEqual([created.id]);
162
+ expect(updatedRoleLinks.map((linkRow) => linkRow.role_id)).toEqual([seeded.editorRole.id]);
163
+ expect(updatedGroupLinks.map((linkRow) => linkRow.user_id)).toEqual([created.id, created.id]);
164
+ expect(ids(updatedGroupLinks.map((linkRow) => linkRow.group_id))).toEqual(ids([seeded.engineering.id, seeded.ops.id]));
165
+ expect(removed.rowsAffected).toEqual(1);
166
+ expect(await collectAsync(usersRepo.read())).toEqual([]);
167
+ expect(await collectAsync(seeded.userRolesRepo.read())).toEqual([]);
168
+ expect(await collectAsync(seeded.userGroupsRepo.read())).toEqual([]);
169
+ expect((await collectAsync(seeded.rolesRepo.read())).map((role) => role.name).sort()).toEqual(["admin", "editor"]);
170
+ expect((await collectAsync(seeded.groupsRepo.read())).map((group) => group.name).sort()).toEqual(["engineering", "ops"]);
171
+ expect((await collectAsync(seeded.userTypesRepo.read())).map((type) => type.name)).toEqual(["admin"]);
172
+ });
173
+
174
+ it("clears one association when the list is null", async () => {
175
+ const seeded = await graph();
176
+ const created = await createAda(seeded, [seeded.adminRole, seeded.editorRole], [seeded.engineering]);
177
+ const saved = await seeded.usersRepo.update(Object.assign(new UserView(), {
178
+ id: created.id,
179
+ username: "ada",
180
+ is_enabled: true,
181
+ user_type_id: seeded.adminType.id,
182
+ roles: null,
183
+ groups: [seeded.engineering],
184
+ }));
185
+ const loaded = await seeded.usersRepo.readSingleById(created.id);
186
+ const userRoles = await collectAsync(seeded.userRolesRepo.read());
187
+ const userGroups = await collectAsync(seeded.userGroupsRepo.read());
188
+
189
+ expect(created.id > 0).toEqual(true);
190
+ expect(saved.rowsAffected).toEqual(1);
191
+ expect(loaded?.id).toEqual(created.id);
192
+ expect(loaded?.username).toEqual("ada");
193
+ expect(loaded?.user_type_id).toEqual(seeded.adminType.id);
194
+ expect(loaded?.user_type?.name).toEqual("admin");
195
+ expect(loaded?.roles ?? []).toEqual([]);
196
+ expect(loaded?.groups?.map((group) => group.id)).toEqual([seeded.engineering.id]);
197
+ expect(userRoles).toEqual([]);
198
+ expect(userGroups.map((linkRow) => linkRow.user_id)).toEqual([created.id]);
199
+ expect(userGroups.map((linkRow) => linkRow.group_id)).toEqual([seeded.engineering.id]);
200
+ expect((await collectAsync(seeded.rolesRepo.read())).map((role) => role.name).sort()).toEqual(["admin", "editor"]);
201
+ expect((await collectAsync(seeded.groupsRepo.read())).map((group) => group.name).sort()).toEqual(["engineering", "ops"]);
202
+ });
203
+
204
+ it("clears one association when the list is empty", async () => {
205
+ const seeded = await graph();
206
+ const created = await createAda(seeded, [seeded.adminRole, seeded.editorRole], [seeded.engineering]);
207
+ const saved = await seeded.usersRepo.update(Object.assign(new UserView(), {
208
+ id: created.id,
209
+ username: "ada",
210
+ is_enabled: true,
211
+ user_type_id: seeded.adminType.id,
212
+ roles: [],
213
+ groups: [seeded.engineering],
214
+ }));
215
+ const loaded = await seeded.usersRepo.readSingleById(created.id);
216
+ const userRoles = await collectAsync(seeded.userRolesRepo.read());
217
+ const userGroups = await collectAsync(seeded.userGroupsRepo.read());
218
+
219
+ expect(created.id > 0).toEqual(true);
220
+ expect(saved.rowsAffected).toEqual(1);
221
+ expect(loaded?.id).toEqual(created.id);
222
+ expect(loaded?.username).toEqual("ada");
223
+ expect(loaded?.user_type_id).toEqual(seeded.adminType.id);
224
+ expect(loaded?.user_type?.name).toEqual("admin");
225
+ expect(loaded?.roles ?? []).toEqual([]);
226
+ expect(loaded?.groups?.map((group) => group.id)).toEqual([seeded.engineering.id]);
227
+ expect(userRoles).toEqual([]);
228
+ expect(userGroups.map((linkRow) => linkRow.user_id)).toEqual([created.id]);
229
+ expect(userGroups.map((linkRow) => linkRow.group_id)).toEqual([seeded.engineering.id]);
230
+ expect((await collectAsync(seeded.rolesRepo.read())).map((role) => role.name).sort()).toEqual(["admin", "editor"]);
231
+ expect((await collectAsync(seeded.groupsRepo.read())).map((group) => group.name).sort()).toEqual(["engineering", "ops"]);
232
+ });
233
+
234
+ it("inserts one junction row for a duplicate related id", async () => {
235
+ const seeded = await graph();
236
+ const created = await createAda(seeded, [seeded.adminRole, seeded.adminRole], [seeded.engineering]);
237
+ const roleLinks = await collectAsync(seeded.userRolesRepo.read());
238
+ const groupLinks = await collectAsync(seeded.userGroupsRepo.read());
239
+
240
+ expect(created.id > 0).toEqual(true);
241
+ expect(roleLinks.map((linkRow) => linkRow.user_id)).toEqual([created.id]);
242
+ expect(roleLinks.map((linkRow) => linkRow.role_id)).toEqual([seeded.adminRole.id]);
243
+ expect(groupLinks.map((linkRow) => linkRow.user_id)).toEqual([created.id]);
244
+ expect(groupLinks.map((linkRow) => linkRow.group_id)).toEqual([seeded.engineering.id]);
245
+ expect((await collectAsync(seeded.rolesRepo.read())).map((role) => role.name).sort()).toEqual(["admin", "editor"]);
246
+ });
247
+
248
+ it("lazy write skips unloaded links", async () => {
249
+ const seeded = await graph(new LazyWriteEnrichmentStrategy());
250
+ link(seeded.usersRepo, seeded);
251
+ const created = await seeded.usersRepo.create(Object.assign(new UserView(), {
252
+ username: "ada",
253
+ is_enabled: true,
254
+ user_type_id: seeded.adminType.id,
255
+ roles: null,
256
+ groups: [seeded.engineering],
257
+ }));
258
+ const createdRoleLinks = await collectAsync(seeded.userRolesRepo.read());
259
+ const createdGroupLinks = await collectAsync(seeded.userGroupsRepo.read());
260
+ const skipped = await seeded.usersRepo.update(Object.assign(new UserView(), {
261
+ id: created.id,
262
+ username: "ada",
263
+ is_enabled: false,
264
+ user_type_id: seeded.adminType.id,
265
+ }));
266
+ const afterSkip = await seeded.usersRepo.readSingleById(created.id);
267
+ const skippedRoleLinks = await collectAsync(seeded.userRolesRepo.read());
268
+ const skippedGroupLinks = await collectAsync(seeded.userGroupsRepo.read());
269
+ const linked = await seeded.usersRepo.update(Object.assign(new UserView(), {
270
+ id: created.id,
271
+ username: "ada",
272
+ is_enabled: false,
273
+ user_type_id: seeded.adminType.id,
274
+ roles: [seeded.editorRole],
275
+ }));
276
+ const linkedRoleLinks = await collectAsync(seeded.userRolesRepo.read());
277
+ const linkedGroupLinks = await collectAsync(seeded.userGroupsRepo.read());
278
+ const removed = await seeded.usersRepo.delete(Object.assign(new UserView(), { id: created.id, roles: null, groups: null }));
279
+
280
+ expect(created.id > 0).toEqual(true);
281
+ expect(createdRoleLinks).toEqual([]);
282
+ expect(createdGroupLinks.map((linkRow) => linkRow.user_id)).toEqual([created.id]);
283
+ expect(createdGroupLinks.map((linkRow) => linkRow.group_id)).toEqual([seeded.engineering.id]);
284
+ expect(skipped.rowsAffected).toEqual(1);
285
+ expect(afterSkip?.id).toEqual(created.id);
286
+ expect(afterSkip?.username).toEqual("ada");
287
+ expect(afterSkip?.is_enabled).toEqual(false);
288
+ expect(skippedRoleLinks).toEqual([]);
289
+ expect(skippedGroupLinks.map((linkRow) => linkRow.user_id)).toEqual([created.id]);
290
+ expect(skippedGroupLinks.map((linkRow) => linkRow.group_id)).toEqual([seeded.engineering.id]);
291
+ expect(linked.rowsAffected).toEqual(1);
292
+ expect(linkedRoleLinks.map((linkRow) => linkRow.user_id)).toEqual([created.id]);
293
+ expect(linkedRoleLinks.map((linkRow) => linkRow.role_id)).toEqual([seeded.editorRole.id]);
294
+ expect(linkedGroupLinks.map((linkRow) => linkRow.user_id)).toEqual([created.id]);
295
+ expect(linkedGroupLinks.map((linkRow) => linkRow.group_id)).toEqual([seeded.engineering.id]);
296
+ expect((await collectAsync(seeded.rolesRepo.read())).map((role) => role.name).sort()).toEqual(["admin", "editor"]);
297
+ expect((await collectAsync(seeded.groupsRepo.read())).map((group) => group.name).sort()).toEqual(["engineering", "ops"]);
298
+ expect(removed.rowsAffected).toEqual(1);
299
+ expect(await collectAsync(seeded.usersRepo.read())).toEqual([]);
300
+ expect(await collectAsync(seeded.userRolesRepo.read())).toEqual([]);
301
+ expect(await collectAsync(seeded.userGroupsRepo.read())).toEqual([]);
302
+ });
303
+
304
+ it("rejects a link without an id", async () => {
305
+ const seeded = await graph();
306
+ seeded.usersRepo.addLinked(
307
+ { destinationProperty: "roles", sourceProperty: "id", relatedKey: "id", association: { parent: "user_id", related: "role_id" } },
308
+ seeded.rolesRepo,
309
+ seeded.userRolesRepo,
310
+ );
311
+ await expect(seeded.usersRepo.create(Object.assign(new UserView(), {
312
+ username: "ada",
313
+ user_type_id: seeded.adminType.id,
314
+ is_enabled: true,
315
+ roles: [new Role()],
316
+ }))).rejects.toThrow(/requires "id"/);
317
+ const users = await collectAsync(seeded.usersRepo.read());
318
+ expect(users.length).toEqual(1);
319
+ expect(users[0]!.id > 0).toEqual(true);
320
+ expect(users[0]!.username).toEqual("ada");
321
+ expect(await collectAsync(seeded.userRolesRepo.read())).toEqual([]);
322
+ });
323
+ });