@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,94 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import type { IReadonlyRepository } from "./IReadonlyRepository.ts";
|
|
3
|
+
import { MockConnection } from "./mocks/mock-connection.ts";
|
|
4
|
+
import { MockRepository } from "./mocks/mock-repository.ts";
|
|
5
|
+
import { RepositoryFactory } from "./RepositoryFactory.ts";
|
|
6
|
+
import { RepositoryNotRegisteredError } from "./RepositoryNotRegisteredError.ts";
|
|
7
|
+
|
|
8
|
+
class Item {}
|
|
9
|
+
|
|
10
|
+
const emptyReadonly = (): IReadonlyRepository<Item> => ({
|
|
11
|
+
type: Item,
|
|
12
|
+
readSingle: async () => undefined,
|
|
13
|
+
async *read() {},
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
describe("RepositoryFactory", () => {
|
|
17
|
+
it("registers and creates", () => {
|
|
18
|
+
const factory = new RepositoryFactory();
|
|
19
|
+
const repo = new MockRepository<Item>(new MockConnection(), Item);
|
|
20
|
+
factory.register(repo);
|
|
21
|
+
const created = factory.create(Item);
|
|
22
|
+
|
|
23
|
+
expect(created).toBe(repo);
|
|
24
|
+
expect(factory.asReadonly.create(Item)).toBe(repo);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("readonly registration is not crud", () => {
|
|
28
|
+
const factory = new RepositoryFactory();
|
|
29
|
+
const repo = emptyReadonly();
|
|
30
|
+
factory.asReadonly.register(repo);
|
|
31
|
+
|
|
32
|
+
expect(factory.asReadonly.create(Item)).toBe(repo);
|
|
33
|
+
expect(() => factory.create(Item)).toThrow(RepositoryNotRegisteredError);
|
|
34
|
+
try {
|
|
35
|
+
factory.create(Item);
|
|
36
|
+
} catch (error) {
|
|
37
|
+
expect(error).toBeInstanceOf(RepositoryNotRegisteredError);
|
|
38
|
+
expect((error as RepositoryNotRegisteredError).entityTypeName).toBe("Item");
|
|
39
|
+
expect((error as Error).message).toContain("Item");
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("registers and creates by name", () => {
|
|
44
|
+
const factory = new RepositoryFactory();
|
|
45
|
+
const connection = new MockConnection();
|
|
46
|
+
const crud = new MockRepository<Item>(connection, Item);
|
|
47
|
+
const read = emptyReadonly();
|
|
48
|
+
factory.register("Contact", "contacts", () => crud, () => read);
|
|
49
|
+
|
|
50
|
+
expect(factory.create("Contact", connection)).toBe(crud);
|
|
51
|
+
expect(factory.createReadonly("contacts", connection)).toBe(read);
|
|
52
|
+
expect(factory.access("contacts", connection).create()).toBe(crud);
|
|
53
|
+
expect(factory.access("Contact", connection).createReadonly()).toBe(read);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("binds register on construct", () => {
|
|
57
|
+
RepositoryFactory.resetBinders();
|
|
58
|
+
const connection = new MockConnection();
|
|
59
|
+
const crud = new MockRepository<Item>(connection, Item);
|
|
60
|
+
const read = emptyReadonly();
|
|
61
|
+
RepositoryFactory.bind((factory) =>
|
|
62
|
+
factory.register("Contact", "contacts", () => crud, () => read),
|
|
63
|
+
);
|
|
64
|
+
try {
|
|
65
|
+
const factory = new RepositoryFactory();
|
|
66
|
+
expect(factory.create("contacts", connection)).toBe(crud);
|
|
67
|
+
} finally {
|
|
68
|
+
RepositoryFactory.resetBinders();
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("binds register on access", () => {
|
|
73
|
+
RepositoryFactory.resetBinders();
|
|
74
|
+
const connection = new MockConnection();
|
|
75
|
+
const crud = new MockRepository<Item>(connection, Item);
|
|
76
|
+
const read = emptyReadonly();
|
|
77
|
+
RepositoryFactory.bind((factory) =>
|
|
78
|
+
factory.register("Contact", "contacts", () => crud, () => read),
|
|
79
|
+
);
|
|
80
|
+
try {
|
|
81
|
+
const factory = new RepositoryFactory();
|
|
82
|
+
expect(factory.access("contacts", connection).create()).toBe(crud);
|
|
83
|
+
} finally {
|
|
84
|
+
RepositoryFactory.resetBinders();
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it("throws when missing", () => {
|
|
89
|
+
const factory = new RepositoryFactory();
|
|
90
|
+
expect(() => factory.create(Item)).toThrow(RepositoryNotRegisteredError);
|
|
91
|
+
expect(() => factory.create("Contacts", new MockConnection())).toThrow(RepositoryNotRegisteredError);
|
|
92
|
+
expect(() => factory.createReadonly("Contacts", new MockConnection())).toThrow(RepositoryNotRegisteredError);
|
|
93
|
+
});
|
|
94
|
+
});
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { DictionaryMapper } from "./mappers/DictionaryMapper.ts";
|
|
3
|
+
import { StatementMappers } from "./StatementMappers.ts";
|
|
4
|
+
import { DefaultStatementTextRenderer } from "./statements/DefaultStatementTextRenderer.ts";
|
|
5
|
+
import { DeleteStatement } from "./statements/DeleteStatement.ts";
|
|
6
|
+
import { ExecuteStoredProcedureStatement } from "./statements/ExecuteStoredProcedureStatement.ts";
|
|
7
|
+
import { Identifier } from "./statements/Identifier.ts";
|
|
8
|
+
import { InsertStatement } from "./statements/InsertStatement.ts";
|
|
9
|
+
import { Join } from "./statements/Join.ts";
|
|
10
|
+
import { Operator } from "./statements/Operator.ts";
|
|
11
|
+
import { OrderBy } from "./statements/OrderBy.ts";
|
|
12
|
+
import { SelectInsertedIdStatement } from "./statements/SelectInsertedIdStatement.ts";
|
|
13
|
+
import { SelectStatement } from "./statements/SelectStatement.ts";
|
|
14
|
+
import { UpdateStatement } from "./statements/UpdateStatement.ts";
|
|
15
|
+
import { Where } from "./statements/Where.ts";
|
|
16
|
+
import { WhereText } from "./statements/WhereText.ts";
|
|
17
|
+
import type { Cell } from "./converters/Cell.ts";
|
|
18
|
+
|
|
19
|
+
const mapper = (name: string, value: Cell): Cell =>
|
|
20
|
+
typeof value === "string" ? `${name}:${value.toUpperCase()}` : value;
|
|
21
|
+
|
|
22
|
+
describe("StatementMappers", () => {
|
|
23
|
+
it("maps a select", () => {
|
|
24
|
+
const mappings = new StatementMappers(
|
|
25
|
+
new DictionaryMapper([
|
|
26
|
+
["User", "people"],
|
|
27
|
+
["Role", "roles"],
|
|
28
|
+
]),
|
|
29
|
+
new DictionaryMapper([
|
|
30
|
+
["Name", "full_name"],
|
|
31
|
+
["title", "book.title"],
|
|
32
|
+
["id", "user_id"],
|
|
33
|
+
]),
|
|
34
|
+
);
|
|
35
|
+
const select = new SelectStatement({
|
|
36
|
+
distinct: true,
|
|
37
|
+
table: "User",
|
|
38
|
+
fields: ["Name", "title", "scope.title", "alias.other", '"schema.name".id'],
|
|
39
|
+
where: new Where(new Operator.Equals("Name", "Bea")),
|
|
40
|
+
joins: [Join.inner("Role", "User.id", "Role.user_id"), Join.cross("logs")],
|
|
41
|
+
groupBy: ["Name"],
|
|
42
|
+
having: new Where(new Operator.Equals("Name", "Bea")),
|
|
43
|
+
orderBy: [OrderBy.desc("Name")],
|
|
44
|
+
limit: 5,
|
|
45
|
+
offset: 1,
|
|
46
|
+
});
|
|
47
|
+
const mapped = mappings.apply(select, mapper) as SelectStatement;
|
|
48
|
+
expect(mapped.table).toBe("people");
|
|
49
|
+
expect(mapped.fields).toEqual([
|
|
50
|
+
"full_name",
|
|
51
|
+
"book.title",
|
|
52
|
+
"book.title",
|
|
53
|
+
"alias.other",
|
|
54
|
+
'"schema.name".user_id',
|
|
55
|
+
]);
|
|
56
|
+
expect(mapped.distinct).toBe(true);
|
|
57
|
+
expect(mapped.limit).toBe(5);
|
|
58
|
+
expect(mapped.offset).toBe(1);
|
|
59
|
+
expect(mapped.groupBy).toEqual(["full_name"]);
|
|
60
|
+
expect(mapped.orderBy).toEqual([{ column: "full_name", direction: "desc" }]);
|
|
61
|
+
expect((mapped.joins[0]?.on as Operator.Equals).left).toBeInstanceOf(Identifier);
|
|
62
|
+
expect(((mapped.joins[0]?.on as Operator.Equals).left as Identifier).name).toBe("people.user_id");
|
|
63
|
+
expect(mapped.joins[1]).toEqual({ kind: "cross", table: "logs" });
|
|
64
|
+
expect(DefaultStatementTextRenderer.instance.render(mapped)).toContain("full_name = $full_name");
|
|
65
|
+
expect(mapped.where?.parameters?.[0]?.value).toBe("Name:BEA");
|
|
66
|
+
expect(mappings.property("full_name")).toBe("Name");
|
|
67
|
+
expect(mappings.property("people.full_name")).toBe("Name");
|
|
68
|
+
expect(mappings.property("other")).toBe("other");
|
|
69
|
+
expect(mappings.property("people.missing")).toBe("people.missing");
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("maps writes", () => {
|
|
73
|
+
const mappings = new StatementMappers(
|
|
74
|
+
new DictionaryMapper([
|
|
75
|
+
["User", "people"],
|
|
76
|
+
["Role", "roles"],
|
|
77
|
+
]),
|
|
78
|
+
new DictionaryMapper([
|
|
79
|
+
["Name", "full_name"],
|
|
80
|
+
["title", "book.title"],
|
|
81
|
+
["id", "user_id"],
|
|
82
|
+
]),
|
|
83
|
+
);
|
|
84
|
+
const insert = mappings.apply(new InsertStatement("User", [{ name: "Name", value: "Ada" }]), mapper) as InsertStatement;
|
|
85
|
+
expect(DefaultStatementTextRenderer.instance.render(insert)).toBe("INSERT INTO people (full_name) VALUES ($full_name)");
|
|
86
|
+
expect(insert.fields[0]?.value).toBe("Ada");
|
|
87
|
+
|
|
88
|
+
const update = mappings.apply(
|
|
89
|
+
new UpdateStatement("User", [{ name: "Name", value: "Ada" }], new Where(new Operator.Equals("Name", "Bea"))),
|
|
90
|
+
mapper,
|
|
91
|
+
) as UpdateStatement;
|
|
92
|
+
expect(DefaultStatementTextRenderer.instance.render(update)).toBe(
|
|
93
|
+
"UPDATE people SET full_name = $full_name WHERE full_name = $full_name__where",
|
|
94
|
+
);
|
|
95
|
+
expect(update.parameters[0]?.value).toBe("Ada");
|
|
96
|
+
expect(update.parameters[1]).toEqual({ name: "full_name__where", value: "Name:BEA" });
|
|
97
|
+
|
|
98
|
+
const deleted = mappings.apply(
|
|
99
|
+
new DeleteStatement("User", new Where(new Operator.Equals("Name", "Bea"))),
|
|
100
|
+
mapper,
|
|
101
|
+
) as DeleteStatement;
|
|
102
|
+
expect(DefaultStatementTextRenderer.instance.render(deleted)).toBe("DELETE FROM people WHERE full_name = $full_name");
|
|
103
|
+
|
|
104
|
+
const inserted = mappings.apply(new SelectInsertedIdStatement("User", "id"), mapper) as SelectInsertedIdStatement;
|
|
105
|
+
expect(inserted.table).toBe("people");
|
|
106
|
+
expect(inserted.column).toBe("user_id");
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it("leaves raw and other statements", () => {
|
|
110
|
+
const mappings = new StatementMappers(
|
|
111
|
+
new DictionaryMapper([
|
|
112
|
+
["User", "people"],
|
|
113
|
+
["Role", "roles"],
|
|
114
|
+
]),
|
|
115
|
+
new DictionaryMapper([
|
|
116
|
+
["Name", "full_name"],
|
|
117
|
+
["title", "book.title"],
|
|
118
|
+
["id", "user_id"],
|
|
119
|
+
]),
|
|
120
|
+
);
|
|
121
|
+
const call = new ExecuteStoredProcedureStatement("save_user", []);
|
|
122
|
+
expect(mappings.apply(call, mapper)).toBe(call);
|
|
123
|
+
expect(mappings.apply(undefined, mapper)).toBeUndefined();
|
|
124
|
+
const text = new WhereText("name = $name");
|
|
125
|
+
expect(mappings.applyWhere(text, mapper)).toBe(text);
|
|
126
|
+
const kept = mappings.apply(new DeleteStatement("User", text), mapper) as DeleteStatement;
|
|
127
|
+
expect(kept.where).toBe(text);
|
|
128
|
+
expect(DefaultStatementTextRenderer.instance.render(kept)).toBe("DELETE FROM people WHERE name = $name");
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it("maps a select field alias", () => {
|
|
132
|
+
const mappings = new StatementMappers(
|
|
133
|
+
new DictionaryMapper([
|
|
134
|
+
["Contact", "contacts_base"],
|
|
135
|
+
["Address", "address"],
|
|
136
|
+
]),
|
|
137
|
+
new DictionaryMapper([
|
|
138
|
+
["Id", "id"],
|
|
139
|
+
["FirstName", "first_name"],
|
|
140
|
+
["ContactId", "contact_id"],
|
|
141
|
+
]),
|
|
142
|
+
);
|
|
143
|
+
const select = new SelectStatement({
|
|
144
|
+
table: "Contact",
|
|
145
|
+
fields: ["Contact.Id AS Id", "Contact.FirstName AS FirstName", "Address.Id AS AddressId"],
|
|
146
|
+
joins: [Join.left("Address", "Contact.Id", "Address.ContactId")],
|
|
147
|
+
});
|
|
148
|
+
const mapped = mappings.apply(select, mapper) as SelectStatement;
|
|
149
|
+
expect(DefaultStatementTextRenderer.instance.render(mapped)).toBe(
|
|
150
|
+
"SELECT contacts_base.id AS Id, contacts_base.first_name AS FirstName, address.id AS AddressId FROM contacts_base LEFT JOIN address ON contacts_base.id = address.contact_id",
|
|
151
|
+
);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it("maps a contacts select", () => {
|
|
155
|
+
const mappings = new StatementMappers(
|
|
156
|
+
new DictionaryMapper([
|
|
157
|
+
["Contact", "contacts_base"],
|
|
158
|
+
["ContactSource", "contact_source"],
|
|
159
|
+
["ContactType", "contact_type"],
|
|
160
|
+
["Address", "address"],
|
|
161
|
+
["AddressType", "address_type"],
|
|
162
|
+
["Phone", "phone"],
|
|
163
|
+
["PhoneType", "phone_type"],
|
|
164
|
+
]),
|
|
165
|
+
new DictionaryMapper([
|
|
166
|
+
["Id", "id"],
|
|
167
|
+
["FirstName", "first_name"],
|
|
168
|
+
["LastName", "last_name"],
|
|
169
|
+
["Email", "email"],
|
|
170
|
+
["Name", "name"],
|
|
171
|
+
["Line1", "line1"],
|
|
172
|
+
["City", "city"],
|
|
173
|
+
["Number", "number"],
|
|
174
|
+
["ContactId", "contact_id"],
|
|
175
|
+
["ContactSourceId", "contact_source_id"],
|
|
176
|
+
["ContactTypeId", "contact_type_id"],
|
|
177
|
+
["AddressTypeId", "address_type_id"],
|
|
178
|
+
["PhoneTypeId", "phone_type_id"],
|
|
179
|
+
]),
|
|
180
|
+
);
|
|
181
|
+
const select = new SelectStatement({
|
|
182
|
+
distinct: true,
|
|
183
|
+
table: "Contact",
|
|
184
|
+
fields: [
|
|
185
|
+
"Contact.FirstName",
|
|
186
|
+
"Contact.LastName",
|
|
187
|
+
"Contact.Email",
|
|
188
|
+
"ContactSource.Name",
|
|
189
|
+
"Address.Line1",
|
|
190
|
+
"Address.City",
|
|
191
|
+
"Phone.Number",
|
|
192
|
+
],
|
|
193
|
+
where: new Where(
|
|
194
|
+
new Operator.And(
|
|
195
|
+
new Operator.Equals("Contact.Email", "ada@ex.com"),
|
|
196
|
+
new Operator.GreaterThan("Address.City", "A"),
|
|
197
|
+
),
|
|
198
|
+
),
|
|
199
|
+
joins: [
|
|
200
|
+
Join.inner("ContactSource", "Contact.ContactSourceId", "ContactSource.Id"),
|
|
201
|
+
Join.inner("ContactType", "Contact.ContactTypeId", "ContactType.Id"),
|
|
202
|
+
Join.left("Address", "Contact.Id", "Address.ContactId"),
|
|
203
|
+
Join.left("AddressType", "Address.AddressTypeId", "AddressType.Id"),
|
|
204
|
+
Join.left("Phone", "Contact.Id", "Phone.ContactId"),
|
|
205
|
+
Join.left("PhoneType", "Phone.PhoneTypeId", "PhoneType.Id"),
|
|
206
|
+
],
|
|
207
|
+
groupBy: ["Contact.LastName"],
|
|
208
|
+
having: new Where(new Operator.Equals("ContactType.Name", "person")),
|
|
209
|
+
orderBy: [OrderBy.asc("Contact.LastName"), OrderBy.desc("Contact.FirstName")],
|
|
210
|
+
limit: 20,
|
|
211
|
+
offset: 1,
|
|
212
|
+
});
|
|
213
|
+
const mapped = mappings.apply(select, mapper) as SelectStatement;
|
|
214
|
+
expect(DefaultStatementTextRenderer.instance.render(mapped)).toBe(
|
|
215
|
+
"SELECT DISTINCT contacts_base.first_name, contacts_base.last_name, contacts_base.email, contact_source.name, address.line1, address.city, phone.number FROM contacts_base INNER JOIN contact_source ON contacts_base.contact_source_id = contact_source.id INNER JOIN contact_type ON contacts_base.contact_type_id = contact_type.id LEFT JOIN address ON contacts_base.id = address.contact_id LEFT JOIN address_type ON address.address_type_id = address_type.id LEFT JOIN phone ON contacts_base.id = phone.contact_id LEFT JOIN phone_type ON phone.phone_type_id = phone_type.id WHERE contacts_base.email = $contacts_base.email AND address.city > $address.city GROUP BY contacts_base.last_name HAVING contact_type.name = $contact_type.name ORDER BY contacts_base.last_name ASC, contacts_base.first_name DESC LIMIT 20 OFFSET 1",
|
|
216
|
+
);
|
|
217
|
+
expect(mapped.where?.parameters[0]?.value).toBe("Contact.Email:ADA@EX.COM");
|
|
218
|
+
expect(mapped.where?.parameters[1]?.value).toBe("Address.City:A");
|
|
219
|
+
expect((mapped.having as Where).parameters[0]?.value).toBe("ContactType.Name:PERSON");
|
|
220
|
+
});
|
|
221
|
+
});
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { splitStatements, statementParameters, statementUsesParameter, statementReader, StreamingReader } from "./index.ts";
|
|
3
|
+
|
|
4
|
+
describe("splitStatements", () => {
|
|
5
|
+
it("splits on semicolons outside quotes and comments", () => {
|
|
6
|
+
expect(splitStatements("")).toEqual([]);
|
|
7
|
+
expect(splitStatements("SELECT 1")).toEqual(["SELECT 1"]);
|
|
8
|
+
expect(splitStatements("SELECT 1;")).toEqual(["SELECT 1"]);
|
|
9
|
+
expect(splitStatements("SELECT 1 AS id; SELECT 2 AS id")).toEqual([
|
|
10
|
+
"SELECT 1 AS id",
|
|
11
|
+
"SELECT 2 AS id",
|
|
12
|
+
]);
|
|
13
|
+
expect(splitStatements("SELECT ';' AS id; SELECT 2 AS id")).toEqual([
|
|
14
|
+
"SELECT ';' AS id",
|
|
15
|
+
"SELECT 2 AS id",
|
|
16
|
+
]);
|
|
17
|
+
expect(splitStatements('SELECT "a""b" AS id; SELECT `c`')).toEqual([
|
|
18
|
+
'SELECT "a""b" AS id',
|
|
19
|
+
"SELECT `c`",
|
|
20
|
+
]);
|
|
21
|
+
expect(splitStatements("SELECT 1; -- tail\nSELECT 2")).toEqual([
|
|
22
|
+
"SELECT 1",
|
|
23
|
+
"-- tail\nSELECT 2",
|
|
24
|
+
]);
|
|
25
|
+
expect(splitStatements("SELECT 1; /* ; */ SELECT 2")).toEqual([
|
|
26
|
+
"SELECT 1",
|
|
27
|
+
"/* ; */ SELECT 2",
|
|
28
|
+
]);
|
|
29
|
+
const mysql = "CREATE PROCEDURE add_user()\nBEGIN\n INSERT INTO t VALUES (1);\nEND";
|
|
30
|
+
expect(splitStatements(mysql)).toEqual([mysql]);
|
|
31
|
+
const postgres =
|
|
32
|
+
"CREATE PROCEDURE bulk_create_user(payload jsonb)\nLANGUAGE plpgsql\nAS $$\nBEGIN\n INSERT INTO t VALUES (1);\nEND;\n$$";
|
|
33
|
+
expect(splitStatements(postgres)).toEqual([postgres]);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("matches a parameter token at a word boundary", () => {
|
|
37
|
+
expect(statementUsesParameter("INSERT INTO t (is_enabled) VALUES ($is_enabled__10)", "is_enabled__10")).toBe(true);
|
|
38
|
+
expect(statementUsesParameter("INSERT INTO t (is_enabled) VALUES ($is_enabled__10)", "is_enabled__1")).toBe(false);
|
|
39
|
+
expect(statementUsesParameter("$is_enabled__1", "is_enabled__1")).toBe(true);
|
|
40
|
+
expect(statementParameters("INSERT INTO t (a, b) VALUES ($a__0, $b__0), ($a__1, $b__1)")).toEqual([
|
|
41
|
+
"a__0",
|
|
42
|
+
"b__0",
|
|
43
|
+
"a__1",
|
|
44
|
+
"b__1",
|
|
45
|
+
]);
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
describe("StreamingReader", () => {
|
|
50
|
+
it("reads the current set and advances on nextResult", async () => {
|
|
51
|
+
const sets = [[{ id: 1 }], [{ id: 2 }]];
|
|
52
|
+
let set = 0;
|
|
53
|
+
let row = 0;
|
|
54
|
+
let reads = 0;
|
|
55
|
+
let closed = false;
|
|
56
|
+
const reader = new StreamingReader(
|
|
57
|
+
async () => {
|
|
58
|
+
reads += 1;
|
|
59
|
+
const rows = sets[set] ?? [];
|
|
60
|
+
if (row >= rows.length) return undefined;
|
|
61
|
+
const current = rows[row];
|
|
62
|
+
row += 1;
|
|
63
|
+
return current;
|
|
64
|
+
},
|
|
65
|
+
async () => {
|
|
66
|
+
set += 1;
|
|
67
|
+
row = 0;
|
|
68
|
+
return set < sets.length;
|
|
69
|
+
},
|
|
70
|
+
async () => {
|
|
71
|
+
closed = true;
|
|
72
|
+
},
|
|
73
|
+
);
|
|
74
|
+
expect(reads).toBe(0);
|
|
75
|
+
expect(await reader.next()).toBe(true);
|
|
76
|
+
expect(reader.current).toEqual({ id: 1 });
|
|
77
|
+
expect(await reader.next()).toBe(false);
|
|
78
|
+
expect(reader.current).toBeUndefined();
|
|
79
|
+
expect(await reader.nextResult()).toBe(true);
|
|
80
|
+
expect(reader.current).toBeUndefined();
|
|
81
|
+
expect(await reader.next()).toBe(true);
|
|
82
|
+
expect(reader.current).toEqual({ id: 2 });
|
|
83
|
+
expect(await reader.nextResult()).toBe(false);
|
|
84
|
+
await reader.dispose();
|
|
85
|
+
await reader.dispose();
|
|
86
|
+
expect(closed).toBe(true);
|
|
87
|
+
const plain = new StreamingReader(
|
|
88
|
+
async () => undefined,
|
|
89
|
+
async () => false,
|
|
90
|
+
);
|
|
91
|
+
expect(await Array.fromAsync(plain.read())).toEqual([]);
|
|
92
|
+
await plain.dispose();
|
|
93
|
+
await plain[Symbol.asyncDispose]();
|
|
94
|
+
await plain.dispose();
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("loads one statement at a time", async () => {
|
|
98
|
+
const loaded: string[] = [];
|
|
99
|
+
const reader = statementReader(["SELECT 1 AS id", "SELECT 2 AS id"], async function* (sql) {
|
|
100
|
+
loaded.push(sql);
|
|
101
|
+
yield sql.endsWith("1 AS id") ? { id: 1 } : { id: 2 };
|
|
102
|
+
});
|
|
103
|
+
expect(loaded).toEqual([]);
|
|
104
|
+
expect(await reader.next()).toBe(true);
|
|
105
|
+
expect(reader.current).toEqual({ id: 1 });
|
|
106
|
+
expect(await reader.next()).toBe(false);
|
|
107
|
+
expect(loaded).toEqual(["SELECT 1 AS id"]);
|
|
108
|
+
expect(await reader.nextResult()).toBe(true);
|
|
109
|
+
expect(await reader.next()).toBe(true);
|
|
110
|
+
expect(reader.current).toEqual({ id: 2 });
|
|
111
|
+
expect(await reader.nextResult()).toBe(false);
|
|
112
|
+
expect(await reader.next()).toBe(false);
|
|
113
|
+
expect(loaded).toEqual(["SELECT 1 AS id", "SELECT 2 AS id"]);
|
|
114
|
+
await reader.dispose();
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it("returns false when a statement reader has no statements", async () => {
|
|
118
|
+
const reader = statementReader([], async function* () {});
|
|
119
|
+
expect(await reader.next()).toBe(false);
|
|
120
|
+
expect(await reader.nextResult()).toBe(false);
|
|
121
|
+
const blank = statementReader([""], async function* () {
|
|
122
|
+
yield { id: 1 };
|
|
123
|
+
});
|
|
124
|
+
expect(await blank.next()).toBe(false);
|
|
125
|
+
});
|
|
126
|
+
});
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { afterAll, afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
2
|
+
import { Datasource, DatasourceFactory, MockDriver } from "./index.ts";
|
|
3
|
+
|
|
4
|
+
const envKeys = [
|
|
5
|
+
"INSTALLED_PROVIDERS",
|
|
6
|
+
"DATASOURCE_URL",
|
|
7
|
+
"PROVIDER",
|
|
8
|
+
] as const;
|
|
9
|
+
const savedEnv = Object.fromEntries(envKeys.map((key) => [key, process.env[key]]));
|
|
10
|
+
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
for (const key of envKeys) delete process.env[key];
|
|
13
|
+
DatasourceFactory.register("sqlite", new MockDriver());
|
|
14
|
+
process.env.PROVIDER = "sqlite";
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
afterEach(() => DatasourceFactory.reset());
|
|
18
|
+
|
|
19
|
+
afterAll(() => {
|
|
20
|
+
for (const key of envKeys) {
|
|
21
|
+
if (savedEnv[key] === undefined) delete process.env[key];
|
|
22
|
+
else process.env[key] = savedEnv[key];
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
describe("Transaction", () => {
|
|
27
|
+
it("commit or abort", async () => {
|
|
28
|
+
const ds = Datasource.default;
|
|
29
|
+
const connection = await ds.createConnection();
|
|
30
|
+
await connection.open();
|
|
31
|
+
const txn = await connection.startTransaction();
|
|
32
|
+
const saved = true;
|
|
33
|
+
if (saved) await txn.commit();
|
|
34
|
+
else await txn.abort();
|
|
35
|
+
expect(txn.isolation).toBe("readCommitted");
|
|
36
|
+
});
|
|
37
|
+
});
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { DatasourceError, MockConnection, MockTransaction, UnitOfWorkTransaction } from "./index.ts";
|
|
3
|
+
|
|
4
|
+
describe("UnitOfWorkTransaction", () => {
|
|
5
|
+
it("joins a transaction already open on the connection", async () => {
|
|
6
|
+
const connection = new MockConnection();
|
|
7
|
+
const txn = connection.transaction as MockTransaction;
|
|
8
|
+
const outer = new UnitOfWorkTransaction(connection);
|
|
9
|
+
await outer.begin();
|
|
10
|
+
const inner = new UnitOfWorkTransaction(connection);
|
|
11
|
+
await inner.begin();
|
|
12
|
+
await inner.commit();
|
|
13
|
+
await outer.commit();
|
|
14
|
+
expect(connection.startTransactionCalls).toBe(1);
|
|
15
|
+
expect(txn.commitCalls).toBe(1);
|
|
16
|
+
expect(txn.abortCalls).toBe(0);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("aborts the outer transaction once when a joined unit aborts", async () => {
|
|
20
|
+
const connection = new MockConnection();
|
|
21
|
+
const txn = connection.transaction as MockTransaction;
|
|
22
|
+
const outer = new UnitOfWorkTransaction(connection);
|
|
23
|
+
await outer.begin();
|
|
24
|
+
const inner = new UnitOfWorkTransaction(connection);
|
|
25
|
+
await inner.begin();
|
|
26
|
+
await inner.abort();
|
|
27
|
+
await outer.abort();
|
|
28
|
+
expect(connection.startTransactionCalls).toBe(1);
|
|
29
|
+
expect(txn.commitCalls).toBe(0);
|
|
30
|
+
expect(txn.abortCalls).toBe(1);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("rejects a second begin and a unit that never began", async () => {
|
|
34
|
+
const failed = new MockConnection();
|
|
35
|
+
failed.startTransactionError = new Error("nope");
|
|
36
|
+
const broken = new UnitOfWorkTransaction(failed);
|
|
37
|
+
await expect(broken.begin()).rejects.toThrow("nope");
|
|
38
|
+
await expect(broken.begin()).rejects.toThrow(DatasourceError);
|
|
39
|
+
const idle = new UnitOfWorkTransaction(new MockConnection());
|
|
40
|
+
await expect(idle.commit()).rejects.toThrow("has not begun");
|
|
41
|
+
await expect(idle.abort()).rejects.toThrow("has not begun");
|
|
42
|
+
});
|
|
43
|
+
});
|