@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.
- package/dist/ArrayReader.d.ts +12 -0
- package/dist/BatchCommandIBulkWriteStrategy.d.ts +33 -0
- package/dist/BuiltRepository.d.ts +16 -0
- package/dist/BulkCRUDRepository.d.ts +17 -0
- package/dist/BulkCommand.d.ts +5 -0
- package/dist/BulkWriteEnrichment.d.ts +4 -0
- package/dist/CRUDRepository.d.ts +40 -0
- package/dist/CRUDRepositoryBuilder.d.ts +19 -0
- package/dist/CRUDRepositoryOptions.d.ts +7 -0
- package/dist/CRUDRepositoryStatements.d.ts +17 -0
- package/dist/CascadeDeleteEnrichmentStrategy.d.ts +7 -0
- package/dist/CascadeDeleteUpdateEnrichmentStrategy.d.ts +8 -0
- package/dist/CommandMiddlewareHost.d.ts +22 -0
- package/dist/ConsoleCommandMiddleware.d.ts +14 -0
- package/dist/CreateEnrichmentStrategy.d.ts +14 -0
- package/dist/Datasource.d.ts +30 -0
- package/dist/DatasourceError.d.ts +3 -0
- package/dist/DatasourceFactory.d.ts +12 -0
- package/dist/DatasourceOptions.d.ts +1 -0
- package/dist/DatasourceUrl.d.ts +12 -0
- package/dist/DatasourceUrlParser.d.ts +4 -0
- package/dist/DefaultCreateEnrichmentStrategy.d.ts +7 -0
- package/dist/DefaultReadonlyEnrichmentStrategy.d.ts +19 -0
- package/dist/DefaultUpdateEnrichmentStrategy.d.ts +7 -0
- package/dist/DefaultWriteEnrichmentStrategy.d.ts +21 -0
- package/dist/DeleteEnrichmentStrategy.d.ts +11 -0
- package/dist/DriverLoader.d.ts +5 -0
- package/dist/Enrichment.d.ts +13 -0
- package/dist/EnrichmentPropertyType.d.ts +7 -0
- package/dist/ExpectConsoleMiddleware.d.ts +15 -0
- package/dist/FirstResultReader.d.ts +19 -0
- package/dist/HashsetOptimizedReadonlyEnrichmentStrategy.d.ts +9 -0
- package/dist/IBulkWriteStrategy.d.ts +12 -0
- package/dist/ICRUDRepository.d.ts +12 -0
- package/dist/ICRUDRepositoryFactory.d.ts +7 -0
- package/dist/ICommand.d.ts +20 -0
- package/dist/ICommandMiddleware.d.ts +12 -0
- package/dist/IConnection.d.ts +12 -0
- package/dist/IDatasource.d.ts +18 -0
- package/dist/IDatasourceFactory.d.ts +5 -0
- package/dist/IDriver.d.ts +19 -0
- package/dist/IEnriched.d.ts +6 -0
- package/dist/IReader.d.ts +8 -0
- package/dist/IReadonlyEnrichmentStrategy.d.ts +7 -0
- package/dist/IReadonlyRepository.d.ts +11 -0
- package/dist/IRepository.d.ts +4 -0
- package/dist/IRepositoryFactory.d.ts +7 -0
- package/dist/IRepositoryTree.d.ts +4 -0
- package/dist/IRepositoryTreeNode.d.ts +7 -0
- package/dist/ITransaction.d.ts +8 -0
- package/dist/IUnitOfWork.d.ts +5 -0
- package/dist/IWriteEnrichmentStrategy.d.ts +7 -0
- package/dist/Information.d.ts +5 -0
- package/dist/IsolationLevel.d.ts +7 -0
- package/dist/LazyWriteEnrichmentStrategy.d.ts +4 -0
- package/dist/NestedToObjectEnrichmentStrategy.d.ts +10 -0
- package/dist/Parameter.d.ts +7 -0
- package/dist/ParameterDirection.d.ts +7 -0
- package/dist/ProviderCatalog.d.ts +7 -0
- package/dist/ProviderNotImplementedError.d.ts +4 -0
- package/dist/ProviderNotInstalledError.d.ts +4 -0
- package/dist/ReadonlyRepository.d.ts +59 -0
- package/dist/ReadonlyRepositoryBuilder.d.ts +12 -0
- package/dist/ReadonlyRepositoryMappingOptions.d.ts +14 -0
- package/dist/ReadonlyRepositoryStatements.d.ts +8 -0
- package/dist/RepositoryAccess.d.ts +11 -0
- package/dist/RepositoryFactory.d.ts +23 -0
- package/dist/RepositoryNotRegisteredError.d.ts +4 -0
- package/dist/RepositoryTree.d.ts +6 -0
- package/dist/RepositoryTreeNode.d.ts +9 -0
- package/dist/Response.d.ts +7 -0
- package/dist/Row.d.ts +2 -0
- package/dist/SingleCommandIBulkWriteStrategy.d.ts +11 -0
- package/dist/StatementMappers.d.ts +16 -0
- package/dist/StoredEnrichment.d.ts +11 -0
- package/dist/StoredProcedureBulkWriteStrategy.d.ts +27 -0
- package/dist/StreamingReader.d.ts +16 -0
- package/dist/UnitOfWorkTransaction.d.ts +12 -0
- package/dist/UpdateEnrichmentStrategy.d.ts +18 -0
- package/dist/WriteMode.d.ts +1 -0
- package/dist/builders/Field.d.ts +5 -0
- package/dist/builders/Scalar.d.ts +1 -0
- package/dist/builders/index.d.ts +2 -0
- package/dist/collectAsync.d.ts +1 -0
- package/dist/converters/Cell.d.ts +2 -0
- package/dist/converters/ChangeCasingConverter.d.ts +7 -0
- package/dist/converters/Constructable.d.ts +1 -0
- package/dist/converters/MetaDataRowTypeConverter.d.ts +14 -0
- package/dist/converters/PluralizerConverter.d.ts +4 -0
- package/dist/converters/RegexConverter.d.ts +7 -0
- package/dist/converters/ReplaceConverter.d.ts +7 -0
- package/dist/converters/RowTypeConverter.d.ts +10 -0
- package/dist/converters/ToLowerCaseConverter.d.ts +4 -0
- package/dist/converters/ToUpperCaseConverter.d.ts +4 -0
- package/dist/converters/TypeConverter.d.ts +5 -0
- package/dist/createCRUDRepositoryOptions.d.ts +7 -0
- package/dist/createReadonlyRepositoryMappingOptions.d.ts +6 -0
- package/dist/dotEnv.d.ts +5 -0
- package/dist/index.d.ts +119 -0
- package/dist/mappers/ChangeCasingMapper.d.ts +9 -0
- package/dist/mappers/DictionaryMapper.d.ts +8 -0
- package/dist/mappers/FlatToNestedMapper.d.ts +20 -0
- package/dist/mappers/IFlatToNestedMapper.d.ts +6 -0
- package/dist/mappers/IMapper.d.ts +4 -0
- package/dist/mappers/IdentityMapper.d.ts +5 -0
- package/dist/mappers/PluralizerMapper.d.ts +6 -0
- package/dist/mappers/RowTypeMapper.d.ts +6 -0
- package/dist/mappers/TransformConverterMapper.d.ts +5 -0
- package/dist/mappers/TransformMapper.d.ts +6 -0
- package/dist/mappers/TypeMap.d.ts +8 -0
- package/dist/mappers/TypeMapGroup.d.ts +5 -0
- package/dist/mocks/index.d.ts +9 -0
- package/dist/mocks/mock-command.d.ts +28 -0
- package/dist/mocks/mock-connection.d.ts +22 -0
- package/dist/mocks/mock-datasource.d.ts +24 -0
- package/dist/mocks/mock-driver.d.ts +15 -0
- package/dist/mocks/mock-reader.d.ts +14 -0
- package/dist/mocks/mock-repository.d.ts +42 -0
- package/dist/mocks/mock-select-statement.d.ts +10 -0
- package/dist/mocks/mock-transaction.d.ts +16 -0
- package/dist/mocks/mock-where.d.ts +8 -0
- package/dist/parametersToRecord.d.ts +2 -0
- package/dist/readerToAsyncIterable.d.ts +2 -0
- package/dist/splitStatements.d.ts +1 -0
- package/dist/statementParameters.d.ts +1 -0
- package/dist/statementUsesParameter.d.ts +1 -0
- package/dist/statements/Aggregate.d.ts +6 -0
- package/dist/statements/ArrayExpression.d.ts +5 -0
- package/dist/statements/BulkDeleteStatement.d.ts +11 -0
- package/dist/statements/BulkDeleteStatementTextRenderer.d.ts +4 -0
- package/dist/statements/BulkInsertStatement.d.ts +12 -0
- package/dist/statements/BulkInsertStatementTextRenderer.d.ts +4 -0
- package/dist/statements/BulkUpdateStatement.d.ts +16 -0
- package/dist/statements/BulkUpdateStatementTextRenderer.d.ts +8 -0
- package/dist/statements/CheckConstraint.d.ts +7 -0
- package/dist/statements/Cmp.d.ts +9 -0
- package/dist/statements/Column.d.ts +9 -0
- package/dist/statements/ColumnDefault.d.ts +5 -0
- package/dist/statements/Condition.d.ts +3 -0
- package/dist/statements/ConditionRenderer.d.ts +12 -0
- package/dist/statements/CreateIndexStatement.d.ts +10 -0
- package/dist/statements/CreateIndexStatementTextRenderer.d.ts +5 -0
- package/dist/statements/CreateTableStatement.d.ts +11 -0
- package/dist/statements/CreateTableStatementTextRenderer.d.ts +18 -0
- package/dist/statements/DefaultStatementTextRenderer.d.ts +5 -0
- package/dist/statements/DeleteStatement.d.ts +9 -0
- package/dist/statements/DeleteStatementTextRenderer.d.ts +7 -0
- package/dist/statements/ExecuteStoredProcedureStatement.d.ts +9 -0
- package/dist/statements/ExecuteStoredProcedureStatementTextRenderer.d.ts +4 -0
- package/dist/statements/ExpressionEvaluator.d.ts +5 -0
- package/dist/statements/ExpressionRenderer.d.ts +8 -0
- package/dist/statements/ForeignKey.d.ts +19 -0
- package/dist/statements/IBulkDeleteStatement.d.ts +3 -0
- package/dist/statements/IBulkInsertStatement.d.ts +3 -0
- package/dist/statements/IBulkUpdateStatement.d.ts +3 -0
- package/dist/statements/ICondition.d.ts +4 -0
- package/dist/statements/IConditionVisitor.d.ts +6 -0
- package/dist/statements/ICreateIndexStatement.d.ts +3 -0
- package/dist/statements/ICreateTableStatement.d.ts +3 -0
- package/dist/statements/IDeleteStatement.d.ts +5 -0
- package/dist/statements/IExecuteStoredProcedureStatement.d.ts +3 -0
- package/dist/statements/IExpression.d.ts +2 -0
- package/dist/statements/IInsertStatement.d.ts +3 -0
- package/dist/statements/IReturnStatement.d.ts +3 -0
- package/dist/statements/ISelectStatement.d.ts +5 -0
- package/dist/statements/IStatement.d.ts +4 -0
- package/dist/statements/IStatementVisitor.d.ts +26 -0
- package/dist/statements/ITableConstraint.d.ts +5 -0
- package/dist/statements/IUpdateStatement.d.ts +5 -0
- package/dist/statements/IWhere.d.ts +6 -0
- package/dist/statements/Identifier.d.ts +5 -0
- package/dist/statements/InsertStatement.d.ts +9 -0
- package/dist/statements/InsertStatementTextRenderer.d.ts +4 -0
- package/dist/statements/Join.d.ts +14 -0
- package/dist/statements/Literal.d.ts +6 -0
- package/dist/statements/Native.d.ts +5 -0
- package/dist/statements/NotNull.d.ts +4 -0
- package/dist/statements/Operator.d.ts +107 -0
- package/dist/statements/OrderBy.d.ts +10 -0
- package/dist/statements/PrimaryKey.d.ts +7 -0
- package/dist/statements/ReturnStatement.d.ts +9 -0
- package/dist/statements/ReturnStatementTextRenderer.d.ts +4 -0
- package/dist/statements/SelectInsertedIdStatement.d.ts +8 -0
- package/dist/statements/SelectInsertedIdStatementTextRenderer.d.ts +4 -0
- package/dist/statements/SelectStatement.d.ts +20 -0
- package/dist/statements/SelectStatementOptions.d.ts +15 -0
- package/dist/statements/SelectStatementTextRenderer.d.ts +19 -0
- package/dist/statements/StatementEvaluator.d.ts +4 -0
- package/dist/statements/StatementParser.d.ts +4 -0
- package/dist/statements/StatementRenderer.d.ts +42 -0
- package/dist/statements/StatementTextRenderer.d.ts +14 -0
- package/dist/statements/Subquery.d.ts +6 -0
- package/dist/statements/UniqueConstraint.d.ts +7 -0
- package/dist/statements/UpdateStatement.d.ts +14 -0
- package/dist/statements/UpdateStatementTextRenderer.d.ts +14 -0
- package/dist/statements/Where.d.ts +16 -0
- package/dist/statements/WhereText.d.ts +8 -0
- package/dist/statements/index.d.ts +75 -0
- package/dist/stubs/StubCommand.d.ts +15 -0
- package/dist/stubs/StubConnection.d.ts +13 -0
- package/dist/stubs/StubDatasource.d.ts +20 -0
- package/dist/stubs/StubReader.d.ts +11 -0
- package/dist/stubs/StubRepository.d.ts +22 -0
- package/dist/stubs/StubTransaction.d.ts +10 -0
- package/dist/stubs/createStubDatasource.d.ts +2 -0
- package/dist/stubs/index.d.ts +7 -0
- package/dist/stubs/notImplemented.d.ts +1 -0
- package/package.json +14 -6
- package/src/ArrayReader.test.ts +23 -0
- package/src/BulkCRUDRepository.test.ts +183 -0
- package/src/BulkWriteStrategy.test.ts +293 -0
- package/src/CRUDRepository.test.ts +420 -0
- package/src/CRUDRepository.ts +1 -1
- package/src/CRUDRepositoryOptions.test.ts +76 -0
- package/src/Command.test.ts +105 -0
- package/src/CommandMiddlewareHost.test.ts +98 -0
- package/src/Connection.test.ts +34 -0
- package/src/ConsoleCommandMiddleware.test.ts +149 -0
- package/src/Datasource.test.ts +63 -0
- package/src/DatasourceError.test.ts +10 -0
- package/src/DatasourceFactory.test.ts +159 -0
- package/src/DatasourceUrlParser.test.ts +173 -0
- package/src/ExpectConsoleMiddleware.test.ts +79 -0
- package/src/FirstResultReader.test.ts +43 -0
- package/src/IsolationLevel.test.ts +13 -0
- package/src/ProviderCatalog.test.ts +110 -0
- package/src/ProviderNotImplementedError.test.ts +11 -0
- package/src/ProviderNotInstalledError.test.ts +11 -0
- package/src/ReadonlyRepository.test.ts +406 -0
- package/src/RepositoryFactory.test.ts +94 -0
- package/src/StatementMappers.test.ts +221 -0
- package/src/StreamingReader.test.ts +126 -0
- package/src/Transaction.test.ts +37 -0
- package/src/UnitOfWorkTransaction.test.ts +43 -0
- package/src/UserViewCRUDRepository.test.ts +323 -0
- package/src/builders/builders.test.ts +404 -0
- package/src/converters/ChangeCasingConverter.test.ts +34 -0
- package/src/converters/PluralizerConverter.test.ts +43 -0
- package/src/converters/RegexConverter.test.ts +11 -0
- package/src/converters/ReplaceConverter.test.ts +9 -0
- package/src/converters/RowTypeConverter.test.ts +79 -0
- package/src/converters/ToLowerCaseConverter.test.ts +9 -0
- package/src/converters/ToUpperCaseConverter.test.ts +9 -0
- package/src/dotEnv.test.ts +21 -0
- package/src/edgeCases.test.ts +485 -0
- package/src/mappers/ChangeCasingMapper.test.ts +32 -0
- package/src/mappers/DictionaryMapper.test.ts +69 -0
- package/src/mappers/FlatToNestedMapper.test.ts +269 -0
- package/src/mappers/IdentityMapper.test.ts +12 -0
- package/src/mappers/PluralizerMapper.test.ts +40 -0
- package/src/mappers/RowTypeMapper.test.ts +12 -0
- package/src/mappers/TransformConverterMapper.test.ts +181 -0
- package/src/mappers/transformMapper.test.ts +66 -0
- package/src/mocks/MockCommand.test.ts +75 -0
- package/src/mocks/MockConnection.test.ts +34 -0
- package/src/mocks/MockDatasource.test.ts +40 -0
- package/src/mocks/MockReader.test.ts +17 -0
- package/src/mocks/MockRepository.test.ts +32 -0
- package/src/mocks/MockSelectStatement.test.ts +11 -0
- package/src/mocks/MockTransaction.test.ts +17 -0
- package/src/mocks/MockWhere.test.ts +11 -0
- package/src/parametersToRecord.test.ts +19 -0
- package/src/public-surface.test.ts +23 -0
- package/src/readWithWhereClauseAndCustomFlatToNestedObjectMappingOptions.test.ts +126 -0
- package/src/readerToAsyncIterable.test.ts +39 -0
- package/src/repository.test.ts +410 -0
- package/src/repository.type.test.ts +29 -0
- package/src/statements/SelectInsertedIdStatement.test.ts +11 -0
- package/src/statements/StatementCompile.test.ts +231 -0
- package/src/statements/StatementParser.test.ts +304 -0
- package/src/statements/index.ts +1 -1
- package/src/stubs/StubDatasource.test.ts +99 -0
- package/src/tests/contact/Address.ts +14 -0
- package/src/tests/contact/AddressType.ts +6 -0
- package/src/tests/contact/Base.ts +7 -0
- package/src/tests/contact/Contact.ts +12 -0
- package/src/tests/contact/ContactBase.ts +8 -0
- package/src/tests/contact/ContactChangeLog.ts +8 -0
- package/src/tests/contact/ContactGroup.ts +6 -0
- package/src/tests/contact/ContactGroupBase.ts +6 -0
- package/src/tests/contact/ContactGroupMember.ts +6 -0
- package/src/tests/contact/ContactSource.ts +6 -0
- package/src/tests/contact/ContactType.ts +6 -0
- package/src/tests/contact/ContactsService.test.ts +118 -0
- package/src/tests/contact/ContactsService.ts +32 -0
- package/src/tests/contact/ImportContactRequest.ts +4 -0
- package/src/tests/contact/ImportContactResult.ts +5 -0
- package/src/tests/contact/LegacyContact.ts +7 -0
- package/src/tests/contact/LegacyMigrationResult.ts +5 -0
- package/src/tests/contact/Phone.ts +9 -0
- package/src/tests/contact/PhoneType.ts +6 -0
- package/src/tests/contact/asContactsView.ts +63 -0
- package/src/tests/contact/contactColumns.ts +40 -0
- package/src/tests/contact/contactLookupIds.ts +32 -0
- package/src/tests/contact/contactLookups.ts +6 -0
- package/src/tests/contact/contactsParity.ts +63 -0
- package/src/tests/contact/contactsServiceOf.ts +38 -0
- package/src/tests/contact/contactsView.ts +31 -0
- package/src/tests/contact/expectContactsView.ts +23 -0
- package/src/tests/contact/extraContact.ts +61 -0
- package/src/tests/contact/logMapped.ts +5 -0
- package/src/tests/contact/seedContacts.ts +31 -0
|
@@ -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
|
+
});
|
package/src/CRUDRepository.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { IConnection } from "./IConnection.ts";
|
|
2
2
|
import type { ICRUDRepository } from "./ICRUDRepository.ts";
|
|
3
3
|
import type { Response } from "./Response.ts";
|
|
4
|
-
import type { IWhere } from "./IWhere.ts";
|
|
4
|
+
import type { IWhere } from "./statements/IWhere.ts";
|
|
5
5
|
import type { Field } from "./builders/Field.ts";
|
|
6
6
|
import type { Scalar } from "./builders/Scalar.ts";
|
|
7
7
|
import type { IExpression } from "./statements/IExpression.ts";
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { DefaultStatementTextRenderer, Operator, UpdateStatement, Where } from "./statements/index.ts";
|
|
3
|
+
import { CRUDRepositoryStatements } from "./CRUDRepositoryStatements.ts";
|
|
4
|
+
import { CRUDRepository } from "./CRUDRepository.ts";
|
|
5
|
+
import { DefaultWriteEnrichmentStrategy } from "./DefaultWriteEnrichmentStrategy.ts";
|
|
6
|
+
import { CRUDRepositoryBuilder } from "./CRUDRepositoryBuilder.ts";
|
|
7
|
+
import { ReadonlyRepositoryBuilder } from "./ReadonlyRepositoryBuilder.ts";
|
|
8
|
+
import { MockConnection } from "./mocks/mock-connection.ts";
|
|
9
|
+
import type { CRUDRepositoryOptions } from "./CRUDRepositoryOptions.ts";
|
|
10
|
+
|
|
11
|
+
describe("CRUDRepositoryOptions", () => {
|
|
12
|
+
it("defaults are identity", () => {
|
|
13
|
+
const read: CRUDRepositoryOptions = {
|
|
14
|
+
ids: ["id"],
|
|
15
|
+
entity: "users",
|
|
16
|
+
readProperties: ["id"],
|
|
17
|
+
};
|
|
18
|
+
const built = new CRUDRepositoryBuilder().parts(new MockConnection(), read);
|
|
19
|
+
expect(DefaultStatementTextRenderer.instance.render(built.statements.select())).toBe("SELECT id FROM users");
|
|
20
|
+
expect(built.propertyMappings.map("x")).toBe("x");
|
|
21
|
+
expect(built.propertyMappings.reverseMap("x")).toBe("x");
|
|
22
|
+
const withId: CRUDRepositoryOptions = { ids: ["id"] };
|
|
23
|
+
expect(new CRUDRepositoryBuilder().parts(new MockConnection(), withId).ids).toEqual(["id"]);
|
|
24
|
+
const fromType: CRUDRepositoryOptions<{ id: number }> = {
|
|
25
|
+
type: class {
|
|
26
|
+
id = 0;
|
|
27
|
+
},
|
|
28
|
+
ids: ["id"],
|
|
29
|
+
};
|
|
30
|
+
expect(new CRUDRepositoryBuilder<{ id: number }>().parts(new MockConnection(), fromType).ids).toEqual(["id"]);
|
|
31
|
+
expect(new ReadonlyRepositoryBuilder().parts(new MockConnection(), {}).ids).toEqual([]);
|
|
32
|
+
expect(new CRUDRepositoryBuilder().parts(new MockConnection(), { entity: "users" }).ids).toEqual([]);
|
|
33
|
+
expect(
|
|
34
|
+
new CRUDRepositoryBuilder().parts(new MockConnection(), {
|
|
35
|
+
write: new DefaultWriteEnrichmentStrategy(),
|
|
36
|
+
}).ids,
|
|
37
|
+
).toEqual(["id"]);
|
|
38
|
+
class Named {
|
|
39
|
+
id = 0;
|
|
40
|
+
}
|
|
41
|
+
expect(
|
|
42
|
+
new CRUDRepositoryBuilder<Named>().parts(new MockConnection(), {
|
|
43
|
+
type: Named,
|
|
44
|
+
write: new DefaultWriteEnrichmentStrategy<Named>(),
|
|
45
|
+
}).ids,
|
|
46
|
+
).toEqual(["id"]);
|
|
47
|
+
expect(
|
|
48
|
+
new CRUDRepositoryBuilder().build(new MockConnection(), { ids: ["id"], entity: "users" }),
|
|
49
|
+
).toBeInstanceOf(CRUDRepository);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("uses the mapped table and columns", () => {
|
|
53
|
+
const statements = new CRUDRepositoryStatements("users", ["id", "name"]);
|
|
54
|
+
expect(DefaultStatementTextRenderer.instance.render(statements.select())).toBe("SELECT id, name FROM users");
|
|
55
|
+
expect(DefaultStatementTextRenderer.instance.render(statements.insert([{ name: "id", value: 1 }]))).toBe(
|
|
56
|
+
"INSERT INTO users (id) VALUES ($id)",
|
|
57
|
+
);
|
|
58
|
+
expect(DefaultStatementTextRenderer.instance.render(statements.update([{ name: "name", value: "a" }]))).toBe(
|
|
59
|
+
"UPDATE users SET name = $name",
|
|
60
|
+
);
|
|
61
|
+
expect(
|
|
62
|
+
DefaultStatementTextRenderer.instance.render(statements.update([{ name: "name", value: "a" }], new Where(new Operator.Equals("id", 1)))),
|
|
63
|
+
).toBe("UPDATE users SET name = $name WHERE id = $id");
|
|
64
|
+
expect(DefaultStatementTextRenderer.instance.render(statements.delete(new Where(new Operator.Equals("id", 1))))).toBe(
|
|
65
|
+
"DELETE FROM users WHERE id = $id",
|
|
66
|
+
);
|
|
67
|
+
expect(UpdateStatement.parameterList([{ name: "name", value: "a" }])).toEqual([
|
|
68
|
+
{ name: "name", value: "a" },
|
|
69
|
+
]);
|
|
70
|
+
expect(DefaultStatementTextRenderer.instance.render(statements.delete())).toBe("DELETE FROM users");
|
|
71
|
+
const inserted = statements.selectInsertedId("id");
|
|
72
|
+
expect(inserted.table).toBe("users");
|
|
73
|
+
expect(inserted.column).toBe("id");
|
|
74
|
+
expect(() => DefaultStatementTextRenderer.instance.render(inserted)).toThrow("provider must implement SelectInsertIdStatement");
|
|
75
|
+
});
|
|
76
|
+
});
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
MockCommand,
|
|
4
|
+
Native,
|
|
5
|
+
MockConnection,
|
|
6
|
+
MockReader,
|
|
7
|
+
MetaDataRowTypeConverter,
|
|
8
|
+
TransformMapper,
|
|
9
|
+
} from "./index.ts";
|
|
10
|
+
|
|
11
|
+
class NameValue {
|
|
12
|
+
name = "";
|
|
13
|
+
value = "";
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
class Contact {
|
|
17
|
+
name = "";
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
class User {
|
|
21
|
+
id = 0;
|
|
22
|
+
username = "";
|
|
23
|
+
email = "";
|
|
24
|
+
contact_name1 = "";
|
|
25
|
+
contact_name2 = "";
|
|
26
|
+
contacts: Contact[] = [];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const userContactsMapper = () =>
|
|
30
|
+
new TransformMapper<User, User>(
|
|
31
|
+
(row) => {
|
|
32
|
+
row.contacts = [
|
|
33
|
+
Object.assign(new Contact(), { name: row.contact_name1 }),
|
|
34
|
+
Object.assign(new Contact(), { name: row.contact_name2 }),
|
|
35
|
+
];
|
|
36
|
+
return row;
|
|
37
|
+
},
|
|
38
|
+
(user) => {
|
|
39
|
+
user.contact_name1 = user.contacts[0]?.name ?? "";
|
|
40
|
+
user.contact_name2 = user.contacts[1]?.name ?? "";
|
|
41
|
+
return user;
|
|
42
|
+
},
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
describe("Command", () => {
|
|
46
|
+
it("executeReader returns NameValue objects", async () => {
|
|
47
|
+
const command = new MockCommand(new MockConnection());
|
|
48
|
+
command.reader = new MockReader([{ name: "theme", value: "dark" }]);
|
|
49
|
+
const reader = await command.executeReader("SELECT name, value FROM settings");
|
|
50
|
+
const converter = new MetaDataRowTypeConverter<NameValue>();
|
|
51
|
+
const items: NameValue[] = [];
|
|
52
|
+
while (await reader.next()) {
|
|
53
|
+
items.push(converter.convert(reader.current!, NameValue));
|
|
54
|
+
}
|
|
55
|
+
expect(items[0]?.name).toBe("theme");
|
|
56
|
+
expect(items[0]?.value).toBe("dark");
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("execute insert update and delete", async () => {
|
|
60
|
+
const command = new MockCommand(new MockConnection());
|
|
61
|
+
await command.executeNative(new Native("insert"), [
|
|
62
|
+
{ name: "id", value: 1 },
|
|
63
|
+
{ name: "name", value: "a" },
|
|
64
|
+
]);
|
|
65
|
+
await command.executeNative(new Native("update"), [
|
|
66
|
+
{ name: "id", value: 1 },
|
|
67
|
+
{ name: "name", value: "A" },
|
|
68
|
+
]);
|
|
69
|
+
await command.executeNative(new Native("delete"), [{ name: "id", value: 1 }]);
|
|
70
|
+
expect(command.executeCalls.map((call) => call.text)).toEqual([
|
|
71
|
+
"insert",
|
|
72
|
+
"update",
|
|
73
|
+
"delete",
|
|
74
|
+
]);
|
|
75
|
+
expect(command.executeCalls[0]?.parameters?.map((parameter) => parameter.name)).toEqual([
|
|
76
|
+
"id",
|
|
77
|
+
"name",
|
|
78
|
+
]);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("executeReader maps flat contacts", async () => {
|
|
82
|
+
const command = new MockCommand(new MockConnection());
|
|
83
|
+
command.reader = new MockReader([
|
|
84
|
+
{
|
|
85
|
+
id: 1,
|
|
86
|
+
username: "bobby",
|
|
87
|
+
email: "bob@asdf.com",
|
|
88
|
+
contact_name1: "bob",
|
|
89
|
+
contact_name2: "roberta",
|
|
90
|
+
},
|
|
91
|
+
]);
|
|
92
|
+
const reader = await command.executeReader(
|
|
93
|
+
"SELECT contact_name1, contact_name2, email, id, username FROM users",
|
|
94
|
+
);
|
|
95
|
+
const converter = new MetaDataRowTypeConverter<User>();
|
|
96
|
+
const mapper = userContactsMapper();
|
|
97
|
+
let user: User | undefined;
|
|
98
|
+
while (await reader.next()) {
|
|
99
|
+
user = mapper.map(converter.convert(reader.current!, User));
|
|
100
|
+
}
|
|
101
|
+
expect(user?.username).toBe("bobby");
|
|
102
|
+
expect(user?.email).toBe("bob@asdf.com");
|
|
103
|
+
expect(user?.contacts.map((contact) => contact.name)).toEqual(["bob", "roberta"]);
|
|
104
|
+
});
|
|
105
|
+
});
|