@deterministic-code/datasources 0.0.0-stage → 0.1.0
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/package.json +16 -4
- package/src/ArrayReader.ts +32 -0
- package/src/BatchCommandIBulkWriteStrategy.ts +228 -0
- package/src/BuiltRepository.ts +17 -0
- package/src/BulkCRUDRepository.ts +84 -0
- package/src/BulkCommand.ts +6 -0
- package/src/BulkWriteEnrichment.ts +19 -0
- package/src/CRUDRepository.ts +264 -0
- package/src/CRUDRepositoryBuilder.ts +53 -0
- package/src/CRUDRepositoryOptions.ts +8 -0
- package/src/CRUDRepositoryStatements.ts +43 -0
- package/src/CascadeDeleteEnrichmentStrategy.ts +51 -0
- package/src/CascadeDeleteUpdateEnrichmentStrategy.ts +28 -0
- package/src/CommandMiddlewareHost.ts +113 -0
- package/src/ConsoleCommandMiddleware.ts +64 -0
- package/src/CreateEnrichmentStrategy.ts +82 -0
- package/src/Datasource.ts +152 -0
- package/src/DatasourceError.ts +6 -0
- package/src/DatasourceFactory.ts +86 -0
- package/src/DatasourceOptions.ts +1 -0
- package/src/DatasourceUrl.ts +37 -0
- package/src/DatasourceUrlParser.ts +188 -0
- package/src/DefaultCreateEnrichmentStrategy.ts +21 -0
- package/src/DefaultReadonlyEnrichmentStrategy.ts +164 -0
- package/src/DefaultUpdateEnrichmentStrategy.ts +40 -0
- package/src/DefaultWriteEnrichmentStrategy.ts +151 -0
- package/src/DeleteEnrichmentStrategy.ts +56 -0
- package/src/DriverLoader.ts +97 -0
- package/src/Enrichment.ts +15 -0
- package/src/EnrichmentPropertyType.ts +9 -0
- package/src/ExpectConsoleMiddleware.ts +47 -0
- package/src/FirstResultReader.ts +49 -0
- package/src/HashsetOptimizedReadonlyEnrichmentStrategy.ts +80 -0
- package/src/IBulkWriteStrategy.ts +13 -0
- package/src/ICRUDRepository.ts +13 -0
- package/src/ICRUDRepositoryFactory.ts +8 -0
- package/src/ICommand.ts +45 -0
- package/src/ICommandMiddleware.ts +20 -0
- package/src/IConnection.ts +13 -0
- package/src/IDatasource.ts +27 -0
- package/src/IDatasourceFactory.ts +6 -0
- package/src/IDriver.ts +24 -0
- package/src/IEnriched.ts +11 -0
- package/src/IReader.ts +8 -0
- package/src/IReadonlyEnrichmentStrategy.ts +19 -0
- package/src/IReadonlyRepository.ts +21 -0
- package/src/IRepository.ts +5 -0
- package/src/IRepositoryFactory.ts +8 -0
- package/src/IRepositoryTree.ts +5 -0
- package/src/IRepositoryTreeNode.ts +8 -0
- package/src/ITransaction.ts +9 -0
- package/src/IUnitOfWork.ts +5 -0
- package/src/IWriteEnrichmentStrategy.ts +8 -0
- package/src/Information.ts +5 -0
- package/src/IsolationLevel.ts +8 -0
- package/src/LazyWriteEnrichmentStrategy.ts +5 -0
- package/src/NestedToObjectEnrichmentStrategy.ts +33 -0
- package/src/Parameter.ts +8 -0
- package/src/ParameterDirection.ts +9 -0
- package/src/ProviderCatalog.ts +21 -0
- package/src/ProviderNotImplementedError.ts +9 -0
- package/src/ProviderNotInstalledError.ts +9 -0
- package/src/ReadonlyRepository.ts +276 -0
- package/src/ReadonlyRepositoryBuilder.ts +53 -0
- package/src/ReadonlyRepositoryMappingOptions.ts +15 -0
- package/src/ReadonlyRepositoryStatements.ts +13 -0
- package/src/RepositoryAccess.ts +14 -0
- package/src/RepositoryFactory.ts +115 -0
- package/src/RepositoryNotRegisteredError.ts +9 -0
- package/src/RepositoryTree.ts +6 -0
- package/src/RepositoryTreeNode.ts +11 -0
- package/src/Response.ts +15 -0
- package/src/Row.ts +3 -0
- package/src/SingleCommandIBulkWriteStrategy.ts +35 -0
- package/src/StatementMappers.ts +110 -0
- package/src/StoredEnrichment.ts +25 -0
- package/src/StoredProcedureBulkWriteStrategy.ts +164 -0
- package/src/StreamingReader.ts +64 -0
- package/src/UnitOfWorkTransaction.ts +50 -0
- package/src/UpdateEnrichmentStrategy.ts +97 -0
- package/src/WriteMode.ts +1 -0
- package/src/builders/Field.ts +6 -0
- package/src/builders/Scalar.ts +1 -0
- package/src/builders/index.ts +2 -0
- package/src/collectAsync.ts +7 -0
- package/src/converters/Cell.ts +10 -0
- package/src/converters/ChangeCasingConverter.ts +59 -0
- package/src/converters/Constructable.ts +1 -0
- package/src/converters/MetaDataRowTypeConverter.ts +80 -0
- package/src/converters/PluralizerConverter.ts +67 -0
- package/src/converters/RegexConverter.ts +14 -0
- package/src/converters/ReplaceConverter.ts +6 -0
- package/src/converters/RowTypeConverter.ts +33 -0
- package/src/converters/ToLowerCaseConverter.ts +4 -0
- package/src/converters/ToUpperCaseConverter.ts +4 -0
- package/src/converters/TypeConverter.ts +6 -0
- package/src/createCRUDRepositoryOptions.ts +21 -0
- package/src/createReadonlyRepositoryMappingOptions.ts +26 -0
- package/src/dotEnv.ts +16 -0
- package/src/index.ts +222 -0
- package/src/mappers/ChangeCasingMapper.ts +15 -0
- package/src/mappers/DictionaryMapper.ts +16 -0
- package/src/mappers/FlatToNestedMapper.ts +127 -0
- package/src/mappers/IFlatToNestedMapper.ts +7 -0
- package/src/mappers/IMapper.ts +4 -0
- package/src/mappers/IdentityMapper.ts +6 -0
- package/src/mappers/PluralizerMapper.ts +9 -0
- package/src/mappers/RowTypeMapper.ts +13 -0
- package/src/mappers/TransformConverterMapper.ts +11 -0
- package/src/mappers/TransformMapper.ts +8 -0
- package/src/mappers/TypeMap.ts +10 -0
- package/src/mappers/TypeMapGroup.ts +6 -0
- package/src/mocks/index.ts +9 -0
- package/src/mocks/mock-command.ts +66 -0
- package/src/mocks/mock-connection.ts +57 -0
- package/src/mocks/mock-datasource.ts +65 -0
- package/src/mocks/mock-driver.ts +27 -0
- package/src/mocks/mock-reader.ts +38 -0
- package/src/mocks/mock-repository.ts +91 -0
- package/src/mocks/mock-select-statement.ts +17 -0
- package/src/mocks/mock-transaction.ts +31 -0
- package/src/mocks/mock-where.ts +13 -0
- package/src/parametersToRecord.ts +11 -0
- package/src/readerToAsyncIterable.ts +11 -0
- package/src/splitStatements.ts +99 -0
- package/src/statementParameters.ts +22 -0
- package/src/statementUsesParameter.ts +4 -0
- package/src/statements/Aggregate.ts +9 -0
- package/src/statements/ArrayExpression.ts +5 -0
- package/src/statements/BulkDeleteStatement.ts +18 -0
- package/src/statements/BulkDeleteStatementTextRenderer.ts +16 -0
- package/src/statements/BulkInsertStatement.ts +37 -0
- package/src/statements/BulkInsertStatementTextRenderer.ts +10 -0
- package/src/statements/BulkUpdateStatement.ts +24 -0
- package/src/statements/BulkUpdateStatementTextRenderer.ts +29 -0
- package/src/statements/CheckConstraint.ts +9 -0
- package/src/statements/Cmp.ts +12 -0
- package/src/statements/Column.ts +10 -0
- package/src/statements/ColumnDefault.ts +6 -0
- package/src/statements/Condition.ts +3 -0
- package/src/statements/ConditionRenderer.ts +17 -0
- package/src/statements/CreateIndexStatement.ts +18 -0
- package/src/statements/CreateIndexStatementTextRenderer.ts +11 -0
- package/src/statements/CreateTableStatement.ts +18 -0
- package/src/statements/CreateTableStatementTextRenderer.ts +92 -0
- package/src/statements/DefaultStatementTextRenderer.ts +9 -0
- package/src/statements/DeleteStatement.ts +15 -0
- package/src/statements/DeleteStatementTextRenderer.ts +12 -0
- package/src/statements/ExecuteStoredProcedureStatement.ts +15 -0
- package/src/statements/ExecuteStoredProcedureStatementTextRenderer.ts +15 -0
- package/src/statements/ExpressionEvaluator.ts +166 -0
- package/src/statements/ExpressionRenderer.ts +161 -0
- package/src/statements/ForeignKey.ts +23 -0
- package/src/statements/IBulkDeleteStatement.ts +3 -0
- package/src/statements/IBulkInsertStatement.ts +3 -0
- package/src/statements/IBulkUpdateStatement.ts +3 -0
- package/src/statements/ICondition.ts +5 -0
- package/src/statements/IConditionVisitor.ts +7 -0
- package/src/statements/ICreateIndexStatement.ts +3 -0
- package/src/statements/ICreateTableStatement.ts +3 -0
- package/src/statements/IDeleteStatement.ts +6 -0
- package/src/statements/IExecuteStoredProcedureStatement.ts +3 -0
- package/src/statements/IExpression.ts +1 -0
- package/src/statements/IInsertStatement.ts +3 -0
- package/src/statements/IReturnStatement.ts +3 -0
- package/src/statements/ISelectStatement.ts +6 -0
- package/src/statements/IStatement.ts +5 -0
- package/src/statements/IStatementVisitor.ts +27 -0
- package/src/statements/ITableConstraint.ts +6 -0
- package/src/statements/IUpdateStatement.ts +6 -0
- package/src/statements/IWhere.ts +7 -0
- package/src/statements/Identifier.ts +5 -0
- package/src/statements/InsertStatement.ts +15 -0
- package/src/statements/InsertStatementTextRenderer.ts +7 -0
- package/src/statements/Join.ts +26 -0
- package/src/statements/Literal.ts +6 -0
- package/src/statements/Native.ts +5 -0
- package/src/statements/NotNull.ts +3 -0
- package/src/statements/Operator.ts +234 -0
- package/src/statements/OrderBy.ts +19 -0
- package/src/statements/PrimaryKey.ts +9 -0
- package/src/statements/ReturnStatement.ts +15 -0
- package/src/statements/ReturnStatementTextRenderer.ts +15 -0
- package/src/statements/SelectInsertedIdStatement.ts +11 -0
- package/src/statements/SelectInsertedIdStatementTextRenderer.ts +7 -0
- package/src/statements/SelectStatement.ts +34 -0
- package/src/statements/SelectStatementOptions.ts +16 -0
- package/src/statements/SelectStatementTextRenderer.ts +71 -0
- package/src/statements/StatementEvaluator.ts +5 -0
- package/src/statements/StatementParser.ts +461 -0
- package/src/statements/StatementRenderer.ts +120 -0
- package/src/statements/StatementTextRenderer.ts +60 -0
- package/src/statements/Subquery.ts +6 -0
- package/src/statements/UniqueConstraint.ts +9 -0
- package/src/statements/UpdateStatement.ts +34 -0
- package/src/statements/UpdateStatementTextRenderer.ts +63 -0
- package/src/statements/Where.ts +250 -0
- package/src/statements/WhereText.ts +10 -0
- package/src/statements/index.ts +75 -0
- package/src/stubs/StubCommand.ts +30 -0
- package/src/stubs/StubConnection.ts +21 -0
- package/src/stubs/StubDatasource.ts +60 -0
- package/src/stubs/StubReader.ts +25 -0
- package/src/stubs/StubRepository.ts +42 -0
- package/src/stubs/StubTransaction.ts +17 -0
- package/src/stubs/createStubDatasource.ts +4 -0
- package/src/stubs/index.ts +7 -0
- package/src/stubs/notImplemented.ts +5 -0
- package/README.md +0 -3
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import type { Constructable } from "./converters/Constructable.ts";
|
|
2
|
+
import type { IConnection } from "./IConnection.ts";
|
|
3
|
+
import type { IReadonlyRepository } from "./IReadonlyRepository.ts";
|
|
4
|
+
import type { IRepository } from "./IRepository.ts";
|
|
5
|
+
import type { IRepositoryFactory } from "./IRepositoryFactory.ts";
|
|
6
|
+
import type { ICRUDRepository } from "./ICRUDRepository.ts";
|
|
7
|
+
import type { ICRUDRepositoryFactory } from "./ICRUDRepositoryFactory.ts";
|
|
8
|
+
import { RepositoryAccess } from "./RepositoryAccess.ts";
|
|
9
|
+
import { RepositoryNotRegisteredError } from "./RepositoryNotRegisteredError.ts";
|
|
10
|
+
|
|
11
|
+
type Named = {
|
|
12
|
+
create: (connection: IConnection) => ICRUDRepository;
|
|
13
|
+
createReadonly: (connection: IConnection) => IReadonlyRepository;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const binders: Array<(factory: RepositoryFactory) => void> = [];
|
|
17
|
+
|
|
18
|
+
export class RepositoryFactory implements ICRUDRepositoryFactory {
|
|
19
|
+
readonly #readonlyByType = new Map<Constructable<unknown>, IRepository>();
|
|
20
|
+
readonly #crudByType = new Map<Constructable<unknown>, IRepository>();
|
|
21
|
+
readonly #byName = new Map<string, Named>();
|
|
22
|
+
|
|
23
|
+
constructor() {
|
|
24
|
+
this.applyBound();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
static bind = (register: (factory: RepositoryFactory) => void): void => {
|
|
28
|
+
binders.push(register);
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
static resetBinders = (): void => {
|
|
32
|
+
binders.length = 0;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
private applyBound(): void {
|
|
36
|
+
for (const register of binders) register(this);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
register(repository: IRepository): void;
|
|
40
|
+
register(
|
|
41
|
+
typeName: string,
|
|
42
|
+
pluralName: string,
|
|
43
|
+
create: (connection: IConnection) => ICRUDRepository,
|
|
44
|
+
createReadonly: (connection: IConnection) => IReadonlyRepository,
|
|
45
|
+
): void;
|
|
46
|
+
register(
|
|
47
|
+
repositoryOrType: IRepository | string,
|
|
48
|
+
pluralName?: string,
|
|
49
|
+
create?: (connection: IConnection) => ICRUDRepository,
|
|
50
|
+
createReadonly?: (connection: IConnection) => IReadonlyRepository,
|
|
51
|
+
): void {
|
|
52
|
+
if (typeof repositoryOrType !== "string") {
|
|
53
|
+
this.#put(repositoryOrType, true);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
const named = { create: create as Named["create"], createReadonly: createReadonly as Named["createReadonly"] };
|
|
57
|
+
this.#byName.set(repositoryOrType, named);
|
|
58
|
+
this.#byName.set(pluralName as string, named);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
create<T>(type: Constructable<T>): ICRUDRepository<T>;
|
|
62
|
+
create(name: string, connection: IConnection): ICRUDRepository;
|
|
63
|
+
create(
|
|
64
|
+
typeOrName: Constructable<unknown> | string,
|
|
65
|
+
connection?: IConnection,
|
|
66
|
+
): ICRUDRepository {
|
|
67
|
+
if (typeof typeOrName !== "string")
|
|
68
|
+
return this.#get(this.#crudByType, typeOrName) as ICRUDRepository;
|
|
69
|
+
this.applyBound();
|
|
70
|
+
return this.#lookup(typeOrName).create(connection as IConnection);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
createReadonly = (name: string, connection: IConnection): IReadonlyRepository => {
|
|
74
|
+
this.applyBound();
|
|
75
|
+
return this.#lookup(name).createReadonly(connection);
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
access = (name: string, connection: IConnection): RepositoryAccess => {
|
|
79
|
+
this.applyBound();
|
|
80
|
+
const named = this.#lookup(name);
|
|
81
|
+
return new RepositoryAccess(named.create, named.createReadonly, connection);
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
has = (name: string): boolean => {
|
|
85
|
+
this.applyBound();
|
|
86
|
+
return this.#byName.has(name);
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
readonly asReadonly: IRepositoryFactory = {
|
|
90
|
+
register: (repository) => this.#put(repository, false),
|
|
91
|
+
create: <T>(type: Constructable<T>) =>
|
|
92
|
+
this.#get(this.#readonlyByType, type) as IReadonlyRepository<T>,
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
#put = (repository: IRepository, crud: boolean): void => {
|
|
96
|
+
this.#readonlyByType.set(repository.type, repository);
|
|
97
|
+
if (!crud) return;
|
|
98
|
+
this.#crudByType.set(repository.type, repository);
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
#get = (
|
|
102
|
+
map: Map<Constructable<unknown>, IRepository>,
|
|
103
|
+
type: Constructable<unknown>,
|
|
104
|
+
): IRepository => {
|
|
105
|
+
const repository = map.get(type);
|
|
106
|
+
if (!repository) throw new RepositoryNotRegisteredError(type.name);
|
|
107
|
+
return repository;
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
#lookup = (name: string): Named => {
|
|
111
|
+
const named = this.#byName.get(name);
|
|
112
|
+
if (!named) throw new RepositoryNotRegisteredError(name);
|
|
113
|
+
return named;
|
|
114
|
+
};
|
|
115
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export class RepositoryNotRegisteredError extends Error {
|
|
2
|
+
readonly entityTypeName: string;
|
|
3
|
+
|
|
4
|
+
constructor(entityTypeName: string) {
|
|
5
|
+
super(`Repository "${entityTypeName}" is not registered`);
|
|
6
|
+
this.name = "RepositoryNotRegisteredError";
|
|
7
|
+
this.entityTypeName = entityTypeName;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Enrichment } from "./Enrichment.ts";
|
|
2
|
+
import type { IRepositoryTreeNode } from "./IRepositoryTreeNode.ts";
|
|
3
|
+
|
|
4
|
+
export class RepositoryTreeNode implements IRepositoryTreeNode {
|
|
5
|
+
constructor(
|
|
6
|
+
readonly value: object,
|
|
7
|
+
readonly propertyName: string,
|
|
8
|
+
readonly parent?: IRepositoryTreeNode,
|
|
9
|
+
readonly enrichment?: Enrichment,
|
|
10
|
+
) {}
|
|
11
|
+
}
|
package/src/Response.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { ParameterDirection } from "./ParameterDirection.ts";
|
|
2
|
+
import type { Parameter } from "./Parameter.ts";
|
|
3
|
+
|
|
4
|
+
export class Response {
|
|
5
|
+
readonly result: unknown;
|
|
6
|
+
readonly rowsAffected: number;
|
|
7
|
+
readonly parameters?: Parameter[];
|
|
8
|
+
|
|
9
|
+
constructor(result: unknown, rowsAffected: number, parameters?: Parameter[]) {
|
|
10
|
+
const returned = parameters?.find((parameter) => parameter.direction === ParameterDirection.ReturnValue)?.value;
|
|
11
|
+
this.result = result == null && returned != null ? returned : result;
|
|
12
|
+
this.rowsAffected = rowsAffected;
|
|
13
|
+
if (parameters !== undefined) this.parameters = parameters;
|
|
14
|
+
}
|
|
15
|
+
}
|
package/src/Row.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { CRUDRepository } from "./CRUDRepository.ts";
|
|
2
|
+
import type { Response } from "./Response.ts";
|
|
3
|
+
import type { IWhere } from "./statements/IWhere.ts";
|
|
4
|
+
import type { IBulkWriteStrategy } from "./IBulkWriteStrategy.ts";
|
|
5
|
+
|
|
6
|
+
const fold = async <T>(
|
|
7
|
+
items: T[],
|
|
8
|
+
write: (item: T) => Promise<Response>,
|
|
9
|
+
): Promise<Response> => {
|
|
10
|
+
let result: Response["result"] = null;
|
|
11
|
+
let rowsAffected = 0;
|
|
12
|
+
for (const item of items) {
|
|
13
|
+
const response = await write(item);
|
|
14
|
+
result = response.result;
|
|
15
|
+
rowsAffected += response.rowsAffected;
|
|
16
|
+
}
|
|
17
|
+
return { result, rowsAffected };
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export class SingleCommandIBulkWriteStrategy<T extends object> implements IBulkWriteStrategy<T> {
|
|
21
|
+
repo!: CRUDRepository<T>;
|
|
22
|
+
|
|
23
|
+
attach = (): void => {};
|
|
24
|
+
|
|
25
|
+
create = async (items: T[]): Promise<Response> => {
|
|
26
|
+
for (const item of items) await this.repo.create(item);
|
|
27
|
+
return { result: null, rowsAffected: items.length };
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
update = (items: T[], byWhere: IWhere | undefined): Promise<Response> =>
|
|
31
|
+
fold(items, (item) => this.repo.update(item, byWhere));
|
|
32
|
+
|
|
33
|
+
delete = (items: T[], byWhere: IWhere | undefined): Promise<Response> =>
|
|
34
|
+
fold(items, (item) => this.repo.delete(item, byWhere));
|
|
35
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import type { Cell } from "./converters/Cell.ts";
|
|
2
|
+
import type { IMapper } from "./mappers/IMapper.ts";
|
|
3
|
+
import { DeleteStatement } from "./statements/DeleteStatement.ts";
|
|
4
|
+
import type { IStatement } from "./statements/IStatement.ts";
|
|
5
|
+
import type { IWhere } from "./statements/IWhere.ts";
|
|
6
|
+
import { InsertStatement } from "./statements/InsertStatement.ts";
|
|
7
|
+
import { SelectInsertedIdStatement } from "./statements/SelectInsertedIdStatement.ts";
|
|
8
|
+
import { SelectStatement } from "./statements/SelectStatement.ts";
|
|
9
|
+
import { UpdateStatement } from "./statements/UpdateStatement.ts";
|
|
10
|
+
import { Where } from "./statements/Where.ts";
|
|
11
|
+
|
|
12
|
+
export class StatementMappers implements IMapper<string, string> {
|
|
13
|
+
constructor(
|
|
14
|
+
private readonly entityMappers: IMapper<string, string>,
|
|
15
|
+
private readonly propertyMappers: IMapper<string, string>,
|
|
16
|
+
) {}
|
|
17
|
+
|
|
18
|
+
map = (property: string): string => {
|
|
19
|
+
const dot = lastDot(property);
|
|
20
|
+
if (dot < 0) return this.propertyMappers.map(property);
|
|
21
|
+
const qualifier = property.slice(0, dot);
|
|
22
|
+
const name = property.slice(dot + 1);
|
|
23
|
+
const mapped = this.propertyMappers.map(name);
|
|
24
|
+
if (mapped !== name && mapped.includes(".")) return mapped;
|
|
25
|
+
const table = this.entityMappers.map(qualifier);
|
|
26
|
+
return table === qualifier && mapped === name ? property : `${table}.${mapped}`;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
reverseMap = (key: string): string => {
|
|
30
|
+
const reversed = this.propertyMappers.reverseMap(key);
|
|
31
|
+
if (reversed !== key && this.propertyMappers.map(reversed) === key) return reversed;
|
|
32
|
+
const dot = lastDot(key);
|
|
33
|
+
if (dot < 0) return reversed;
|
|
34
|
+
const column = key.slice(dot + 1);
|
|
35
|
+
const name = this.propertyMappers.reverseMap(column);
|
|
36
|
+
return name !== column && this.propertyMappers.map(name) === column ? name : reversed;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
column = (property: string): string => this.map(property);
|
|
40
|
+
|
|
41
|
+
property = (key: string): string => this.reverseMap(key);
|
|
42
|
+
|
|
43
|
+
apply = (
|
|
44
|
+
query: IStatement | undefined,
|
|
45
|
+
cell: (property: string, value: Cell) => Cell,
|
|
46
|
+
): IStatement | undefined => {
|
|
47
|
+
if (query instanceof SelectStatement && query.table)
|
|
48
|
+
return new SelectStatement({
|
|
49
|
+
distinct: query.distinct,
|
|
50
|
+
table: this.entityMappers.map(query.table),
|
|
51
|
+
fields: query.fields.map(this.mapSelectField),
|
|
52
|
+
where: query.where instanceof Where ? this.applyWhere(query.where, cell) : query.where,
|
|
53
|
+
joins: query.joins.map((join) => ({
|
|
54
|
+
kind: join.kind,
|
|
55
|
+
table: this.entityMappers.map(join.table),
|
|
56
|
+
on: join.on ? new Where(join.on).map(this.column, cell).condition : undefined,
|
|
57
|
+
})),
|
|
58
|
+
groupBy: query.groupBy.map(this.column),
|
|
59
|
+
having: query.having instanceof Where ? this.applyWhere(query.having, cell) : query.having,
|
|
60
|
+
orderBy: query.orderBy.map((order) => ({ column: this.column(order.column), direction: order.direction })),
|
|
61
|
+
limit: query.limit,
|
|
62
|
+
offset: query.offset,
|
|
63
|
+
});
|
|
64
|
+
if (query instanceof InsertStatement && query.table !== undefined)
|
|
65
|
+
return new InsertStatement(
|
|
66
|
+
this.entityMappers.map(query.table),
|
|
67
|
+
query.fields.map((field) => ({ name: this.column(field.name), value: field.value })),
|
|
68
|
+
);
|
|
69
|
+
if (query instanceof UpdateStatement && query.table !== undefined)
|
|
70
|
+
return new UpdateStatement(
|
|
71
|
+
this.entityMappers.map(query.table),
|
|
72
|
+
query.fields.map((field) => ({ name: this.column(field.name), value: field.value })),
|
|
73
|
+
query.where instanceof Where ? this.applyWhere(query.where, cell) : query.where,
|
|
74
|
+
);
|
|
75
|
+
if (query instanceof DeleteStatement && query.table !== undefined)
|
|
76
|
+
return new DeleteStatement(
|
|
77
|
+
this.entityMappers.map(query.table),
|
|
78
|
+
query.where instanceof Where ? this.applyWhere(query.where, cell) : query.where,
|
|
79
|
+
);
|
|
80
|
+
if (query instanceof SelectInsertedIdStatement)
|
|
81
|
+
return new SelectInsertedIdStatement(this.entityMappers.map(query.table), this.column(query.column));
|
|
82
|
+
return query;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
applyWhere = (
|
|
86
|
+
where: IWhere | undefined,
|
|
87
|
+
cell: (property: string, value: Cell) => Cell,
|
|
88
|
+
): IWhere | undefined =>
|
|
89
|
+
where instanceof Where ? where.map(this.column, cell) : where;
|
|
90
|
+
|
|
91
|
+
private mapSelectField = (field: string): string => {
|
|
92
|
+
const asIndex = field.toUpperCase().indexOf(" AS ");
|
|
93
|
+
if (asIndex >= 0) {
|
|
94
|
+
const expression = field.slice(0, asIndex).trim();
|
|
95
|
+
const alias = field.slice(asIndex + 4).trim();
|
|
96
|
+
return `${this.map(expression)} AS ${alias}`;
|
|
97
|
+
}
|
|
98
|
+
return this.map(field);
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const lastDot = (name: string): number => {
|
|
103
|
+
let quoted = false;
|
|
104
|
+
let dot = -1;
|
|
105
|
+
for (let i = 0; i < name.length; i++) {
|
|
106
|
+
if (name[i] === '"') quoted = !quoted;
|
|
107
|
+
if (name[i] === "." && !quoted) dot = i;
|
|
108
|
+
}
|
|
109
|
+
return dot;
|
|
110
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { Enrichment } from "./Enrichment.ts";
|
|
2
|
+
import type { ICRUDRepository } from "./ICRUDRepository.ts";
|
|
3
|
+
import type { Row } from "./Row.ts";
|
|
4
|
+
|
|
5
|
+
export type ApplyEnrichment<T extends object = object> = (
|
|
6
|
+
parents: T[],
|
|
7
|
+
sources?: (Row | undefined)[],
|
|
8
|
+
) => Promise<void>;
|
|
9
|
+
|
|
10
|
+
export class StoredEnrichment<T extends object = object> {
|
|
11
|
+
apply: ApplyEnrichment<T>;
|
|
12
|
+
persist?: ICRUDRepository<object>;
|
|
13
|
+
link?: ICRUDRepository<object>;
|
|
14
|
+
|
|
15
|
+
constructor(
|
|
16
|
+
readonly enrichment: Enrichment,
|
|
17
|
+
apply: ApplyEnrichment<T>,
|
|
18
|
+
persist?: ICRUDRepository<object>,
|
|
19
|
+
link?: ICRUDRepository<object>,
|
|
20
|
+
) {
|
|
21
|
+
this.apply = apply;
|
|
22
|
+
this.persist = persist;
|
|
23
|
+
this.link = link;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import type { Cell, Cells } from "./converters/Cell.ts";
|
|
2
|
+
import { DatasourceError } from "./DatasourceError.ts";
|
|
3
|
+
import type { Response } from "./Response.ts";
|
|
4
|
+
import type { IWhere } from "./statements/IWhere.ts";
|
|
5
|
+
import { ExecuteStoredProcedureStatement } from "./statements/ExecuteStoredProcedureStatement.ts";
|
|
6
|
+
import type { CRUDRepository } from "./CRUDRepository.ts";
|
|
7
|
+
import type { CRUDRepositoryOptions } from "./CRUDRepositoryOptions.ts";
|
|
8
|
+
import type { ICommand } from "./ICommand.ts";
|
|
9
|
+
import { execute } from "./ICommand.ts";
|
|
10
|
+
import type { IBulkWriteStrategy } from "./IBulkWriteStrategy.ts";
|
|
11
|
+
import { repositoryParts } from "./CRUDRepositoryBuilder.ts";
|
|
12
|
+
import { MetaDataRowTypeConverter } from "./converters/MetaDataRowTypeConverter.ts";
|
|
13
|
+
import { afterBulkWrite } from "./BulkWriteEnrichment.ts";
|
|
14
|
+
import { ParameterDirection } from "./ParameterDirection.ts";
|
|
15
|
+
import type { Parameter } from "./Parameter.ts";
|
|
16
|
+
|
|
17
|
+
const empty = (): Response => ({ result: null, rowsAffected: 0 });
|
|
18
|
+
|
|
19
|
+
const json = (rows: Cells[]): string => `[${rows.map(object).join(",")}]`;
|
|
20
|
+
|
|
21
|
+
const object = (row: Cells): string =>
|
|
22
|
+
`{${Object.entries(row)
|
|
23
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
24
|
+
.map(([key, value]) => `${quote(key)}:${scalar(value)}`)
|
|
25
|
+
.join(",")}}`;
|
|
26
|
+
|
|
27
|
+
const scalar = (value: Cell): string => {
|
|
28
|
+
if (value === null || value === undefined) return "null";
|
|
29
|
+
if (typeof value === "boolean") return value ? "true" : "false";
|
|
30
|
+
if (typeof value === "number") return Number.isFinite(value) ? JSON.stringify(value) : fail();
|
|
31
|
+
if (typeof value === "bigint") return value.toString();
|
|
32
|
+
if (typeof value === "string") return quote(value);
|
|
33
|
+
if (value instanceof Date) return quote(roundTrip(value));
|
|
34
|
+
if (value instanceof Uint8Array) return quote(base64(value));
|
|
35
|
+
return fail();
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const fail = (): string => {
|
|
39
|
+
throw new DatasourceError("Unsupported bulk payload value");
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const roundTrip = (date: Date): string =>
|
|
43
|
+
date.toISOString().replace(/\.(\d{3})Z$/, (_, fraction: string) => `.${fraction}0000Z`);
|
|
44
|
+
|
|
45
|
+
const base64 = (bytes: Uint8Array): string =>
|
|
46
|
+
btoa(Array.from(bytes, (byte) => String.fromCharCode(byte)).join(""));
|
|
47
|
+
|
|
48
|
+
const quote = (text: string): string => {
|
|
49
|
+
let result = "\"";
|
|
50
|
+
for (const ch of text) {
|
|
51
|
+
result += ({
|
|
52
|
+
"\\": "\\\\",
|
|
53
|
+
"\"": "\\\"",
|
|
54
|
+
"\b": "\\b",
|
|
55
|
+
"\f": "\\f",
|
|
56
|
+
"\n": "\\n",
|
|
57
|
+
"\r": "\\r",
|
|
58
|
+
"\t": "\\t",
|
|
59
|
+
}[ch] ?? (ch < " " ? `\\u${ch.charCodeAt(0).toString(16).padStart(4, "0")}` : ch));
|
|
60
|
+
}
|
|
61
|
+
return `${result}"`;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export class StoredProcedureBulkWriteStrategy<T extends object> implements IBulkWriteStrategy<T> {
|
|
65
|
+
repo!: CRUDRepository<T>;
|
|
66
|
+
command!: ICommand;
|
|
67
|
+
options!: CRUDRepositoryOptions<T>;
|
|
68
|
+
|
|
69
|
+
attach = (command: ICommand, options: CRUDRepositoryOptions<T>): void => {
|
|
70
|
+
this.command = command;
|
|
71
|
+
this.options = options;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
constructor(
|
|
75
|
+
private readonly createName: string,
|
|
76
|
+
private readonly updateName: string,
|
|
77
|
+
private readonly deleteName: string,
|
|
78
|
+
) {}
|
|
79
|
+
|
|
80
|
+
create = (items: T[]): Promise<Response> => this.then(this.call(this.createName, items, true), items, "create");
|
|
81
|
+
|
|
82
|
+
update = (items: T[], _byWhere: IWhere | undefined): Promise<Response> =>
|
|
83
|
+
this.then(this.call(this.updateName, items), items, "update");
|
|
84
|
+
|
|
85
|
+
delete = (items: T[], _byWhere: IWhere | undefined): Promise<Response> =>
|
|
86
|
+
this.then(this.call(this.deleteName, items), items, "delete");
|
|
87
|
+
|
|
88
|
+
private then = async (write: Promise<Response>, items: T[], mode: "create" | "update" | "delete"): Promise<Response> => {
|
|
89
|
+
const response = await write;
|
|
90
|
+
await afterBulkWrite(this.repo, this.options, items, mode);
|
|
91
|
+
return response;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
private call = async (name: string, items: T[], generated = false): Promise<Response> => {
|
|
95
|
+
if (items.length === 0) return empty();
|
|
96
|
+
const payload = { name: "payload", value: json(items.map((item) => this.rowOf(item, generated))) };
|
|
97
|
+
const parameters: Parameter[] = generated && this.ids().length === 1
|
|
98
|
+
? [payload, { name: "ids", value: null, direction: ParameterDirection.InputOutput }]
|
|
99
|
+
: [payload];
|
|
100
|
+
const response = await execute(this.command, new ExecuteStoredProcedureStatement(name, parameters), parameters);
|
|
101
|
+
if (generated) this.applyIds(items, response.parameters);
|
|
102
|
+
return response;
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
private ids = (): readonly string[] => this.options.ids ?? this.repo.ids ?? ["id"];
|
|
106
|
+
|
|
107
|
+
private rowOf = (item: T, create = false): Cells => {
|
|
108
|
+
const stored = this.reverse(item);
|
|
109
|
+
const statements = repositoryParts(this.command.connection, this.options).statements;
|
|
110
|
+
const row: Cells = {};
|
|
111
|
+
for (const name of statements.readColumns) {
|
|
112
|
+
if (create && this.identity(name) && !assigned(stored[name] ?? stored[this.ids()[0]!])) continue;
|
|
113
|
+
const match = Object.entries(stored).find(([key]) => this.column(key) === name);
|
|
114
|
+
row[name] = match?.[1] ?? null;
|
|
115
|
+
}
|
|
116
|
+
return row;
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
private identity = (column: string): boolean => this.ids().length === 1 && this.ids()[0] === column;
|
|
120
|
+
|
|
121
|
+
private applyIds = (items: T[], parameters?: Parameter[]): void => {
|
|
122
|
+
if (this.ids().length !== 1) return;
|
|
123
|
+
const found = parameters?.find((parameter) => parameter.name === "ids");
|
|
124
|
+
const list = parseIds(found?.value);
|
|
125
|
+
const column = this.ids()[0]!;
|
|
126
|
+
for (let index = 0; index < items.length && index < list.length; index++)
|
|
127
|
+
stamp(items[index], column, list[index]!);
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
private column = (property: string): string => this.options.propertyMappings?.map(property) ?? property;
|
|
131
|
+
|
|
132
|
+
private reverse = (item: T): Cells => {
|
|
133
|
+
const objects = this.options.objectConverter ?? new MetaDataRowTypeConverter<T>();
|
|
134
|
+
const row = objects.reverseConvert(item);
|
|
135
|
+
return this.options.rowConverter ? this.options.rowConverter.reverseConvert(row) : row;
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const assigned = (value: Cell | undefined): boolean => {
|
|
140
|
+
if (value === null || value === undefined) return false;
|
|
141
|
+
if (value === 0 || value === 0n) return false;
|
|
142
|
+
if (typeof value === "string") return value.length > 0;
|
|
143
|
+
return true;
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
const parseIds = (value: Cell | undefined): number[] => {
|
|
147
|
+
const text =
|
|
148
|
+
typeof value === "string" ? value.trim()
|
|
149
|
+
: typeof value === "number" || typeof value === "bigint" ? String(value)
|
|
150
|
+
: "";
|
|
151
|
+
if (text === "" || text === "[]" || text === "null") return [];
|
|
152
|
+
return text
|
|
153
|
+
.replace(/^\[/, "")
|
|
154
|
+
.replace(/\]$/, "")
|
|
155
|
+
.split(",")
|
|
156
|
+
.map((part) => part.trim())
|
|
157
|
+
.filter(Boolean)
|
|
158
|
+
.map((part) => Number(part));
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
const stamp = <T extends object>(item: T, column: string, id: number): void => {
|
|
162
|
+
const key = column in item ? column : "id" in item ? "id" : "Id" in item ? "Id" : column;
|
|
163
|
+
(item as Record<string, Cell>)[key] = id;
|
|
164
|
+
};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { Cells } from "./converters/Cell.ts";
|
|
2
|
+
import type { IReader } from "./IReader.ts";
|
|
3
|
+
import { readerToAsyncIterable } from "./readerToAsyncIterable.ts";
|
|
4
|
+
|
|
5
|
+
export class StreamingReader implements IReader<Cells> {
|
|
6
|
+
current: Cells | undefined = undefined;
|
|
7
|
+
private closed = false;
|
|
8
|
+
|
|
9
|
+
constructor(
|
|
10
|
+
private readonly pull: () => Promise<Cells | undefined>,
|
|
11
|
+
private readonly advance: () => Promise<boolean>,
|
|
12
|
+
private readonly close: () => Promise<void> = async () => {},
|
|
13
|
+
) {}
|
|
14
|
+
|
|
15
|
+
async next(): Promise<boolean> {
|
|
16
|
+
this.current = await this.pull();
|
|
17
|
+
return this.current !== undefined;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
read(): AsyncIterable<Cells> {
|
|
21
|
+
return readerToAsyncIterable(this);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async nextResult(): Promise<boolean> {
|
|
25
|
+
this.current = undefined;
|
|
26
|
+
return this.advance();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async dispose(): Promise<void> {
|
|
30
|
+
if (this.closed) return;
|
|
31
|
+
this.closed = true;
|
|
32
|
+
await this.close();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
[Symbol.asyncDispose] = (): Promise<void> => this.dispose();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export const statementReader = (
|
|
39
|
+
statements: readonly string[],
|
|
40
|
+
load: (sql: string) => AsyncIterable<Cells>,
|
|
41
|
+
): StreamingReader => {
|
|
42
|
+
let index = 0;
|
|
43
|
+
let rows: AsyncIterator<Cells> | undefined;
|
|
44
|
+
const closeRows = async () => {
|
|
45
|
+
await rows?.return?.();
|
|
46
|
+
rows = undefined;
|
|
47
|
+
};
|
|
48
|
+
return new StreamingReader(
|
|
49
|
+
async () => {
|
|
50
|
+
if (index >= statements.length) return undefined;
|
|
51
|
+
const sql = statements[index];
|
|
52
|
+
if (!sql) return undefined;
|
|
53
|
+
rows ??= load(sql)[Symbol.asyncIterator]();
|
|
54
|
+
const step = await rows.next();
|
|
55
|
+
return step.done ? undefined : step.value;
|
|
56
|
+
},
|
|
57
|
+
async () => {
|
|
58
|
+
await closeRows();
|
|
59
|
+
index += 1;
|
|
60
|
+
return index < statements.length;
|
|
61
|
+
},
|
|
62
|
+
closeRows,
|
|
63
|
+
);
|
|
64
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { DatasourceError } from "./DatasourceError.ts";
|
|
2
|
+
import type { IConnection } from "./IConnection.ts";
|
|
3
|
+
import type { ITransaction } from "./ITransaction.ts";
|
|
4
|
+
import type { IUnitOfWork } from "./IUnitOfWork.ts";
|
|
5
|
+
|
|
6
|
+
const open = new WeakSet<IConnection>();
|
|
7
|
+
|
|
8
|
+
export class UnitOfWorkTransaction implements IUnitOfWork {
|
|
9
|
+
private begun = false;
|
|
10
|
+
private transaction: ITransaction | undefined;
|
|
11
|
+
|
|
12
|
+
constructor(private readonly connection: IConnection) {}
|
|
13
|
+
|
|
14
|
+
begin = async (): Promise<void> => {
|
|
15
|
+
if (this.begun) throw new DatasourceError("Unit of work already begun");
|
|
16
|
+
this.begun = true;
|
|
17
|
+
if (open.has(this.connection)) return;
|
|
18
|
+
open.add(this.connection);
|
|
19
|
+
try {
|
|
20
|
+
this.transaction = await this.connection.startTransaction();
|
|
21
|
+
} catch (error) {
|
|
22
|
+
open.delete(this.connection);
|
|
23
|
+
throw error;
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
commit = async (): Promise<void> => {
|
|
28
|
+
if (!this.begun) throw new DatasourceError("Unit of work has not begun");
|
|
29
|
+
const txn = this.transaction;
|
|
30
|
+
if (!txn) return;
|
|
31
|
+
await txn.commit();
|
|
32
|
+
this.release();
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
abort = async (): Promise<void> => {
|
|
36
|
+
if (!this.begun) throw new DatasourceError("Unit of work has not begun");
|
|
37
|
+
const txn = this.transaction;
|
|
38
|
+
if (!txn) return;
|
|
39
|
+
try {
|
|
40
|
+
await txn.abort();
|
|
41
|
+
} finally {
|
|
42
|
+
this.release();
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
private release(): void {
|
|
47
|
+
open.delete(this.connection);
|
|
48
|
+
this.transaction = undefined;
|
|
49
|
+
}
|
|
50
|
+
}
|