@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,53 @@
|
|
|
1
|
+
import type { IConnection } from "./IConnection.ts";
|
|
2
|
+
import { defaultId, fieldNamesOf, createCRUDRepositoryOptions } from "./createCRUDRepositoryOptions.ts";
|
|
3
|
+
import type { CRUDRepositoryOptions } from "./CRUDRepositoryOptions.ts";
|
|
4
|
+
import { CRUDRepository } from "./CRUDRepository.ts";
|
|
5
|
+
import { CRUDRepositoryStatements } from "./CRUDRepositoryStatements.ts";
|
|
6
|
+
import type { BuiltRepository } from "./BuiltRepository.ts";
|
|
7
|
+
import type { Constructable } from "./converters/Constructable.ts";
|
|
8
|
+
import { ReadonlyRepositoryBuilder } from "./ReadonlyRepositoryBuilder.ts";
|
|
9
|
+
import { ReadonlyRepositoryStatements } from "./ReadonlyRepositoryStatements.ts";
|
|
10
|
+
import type { ReadonlyRepositoryMappingOptions } from "./ReadonlyRepositoryMappingOptions.ts";
|
|
11
|
+
|
|
12
|
+
const isCrud = <T>(options: ReadonlyRepositoryMappingOptions<T>): options is CRUDRepositoryOptions<T> =>
|
|
13
|
+
"ids" in options || "write" in options;
|
|
14
|
+
|
|
15
|
+
const withIds = <T>(options: CRUDRepositoryOptions<T>): CRUDRepositoryOptions<T> =>
|
|
16
|
+
"ids" in options
|
|
17
|
+
? options
|
|
18
|
+
: {
|
|
19
|
+
...options,
|
|
20
|
+
ids: options.type ? defaultId(fieldNamesOf(options.type)) : ["id"],
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const repositoryParts = <T>(
|
|
24
|
+
typeOrConnection: Constructable<T> | IConnection,
|
|
25
|
+
connectionOrOptions?: IConnection | CRUDRepositoryOptions<T>,
|
|
26
|
+
): { built: BuiltRepository<T>; statements: CRUDRepositoryStatements } => {
|
|
27
|
+
const builder = new CRUDRepositoryBuilder<T>();
|
|
28
|
+
const built =
|
|
29
|
+
typeof typeOrConnection === "function"
|
|
30
|
+
? builder.parts(
|
|
31
|
+
connectionOrOptions as IConnection,
|
|
32
|
+
createCRUDRepositoryOptions(typeOrConnection),
|
|
33
|
+
)
|
|
34
|
+
: builder.parts(typeOrConnection, withIds(connectionOrOptions as CRUDRepositoryOptions<T>));
|
|
35
|
+
return { built, statements: builder.crudStatements! };
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export class CRUDRepositoryBuilder<T = object> extends ReadonlyRepositoryBuilder<T> {
|
|
39
|
+
crudStatements?: CRUDRepositoryStatements;
|
|
40
|
+
|
|
41
|
+
protected override ids = (options: ReadonlyRepositoryMappingOptions<T>): string[] =>
|
|
42
|
+
isCrud(options)
|
|
43
|
+
? (options.ids ?? (options.type ? defaultId(fieldNamesOf(options.type)) : ["id"]))
|
|
44
|
+
: [];
|
|
45
|
+
|
|
46
|
+
protected override statements = (table: string, columns: string[]): ReadonlyRepositoryStatements => {
|
|
47
|
+
this.crudStatements = new CRUDRepositoryStatements(table, columns, this.entityMapper, this.propertyMapper);
|
|
48
|
+
return new ReadonlyRepositoryStatements(table, columns);
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
build = (connection: IConnection, options: CRUDRepositoryOptions<T>): CRUDRepository<T> =>
|
|
52
|
+
new CRUDRepository(connection, options);
|
|
53
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ReadonlyRepositoryMappingOptions } from "./ReadonlyRepositoryMappingOptions.ts";
|
|
2
|
+
import type { Cells } from "./converters/Cell.ts";
|
|
3
|
+
import type { IWriteEnrichmentStrategy } from "./IWriteEnrichmentStrategy.ts";
|
|
4
|
+
|
|
5
|
+
export type CRUDRepositoryOptions<T = Cells> = ReadonlyRepositoryMappingOptions<T> & {
|
|
6
|
+
ids?: string[];
|
|
7
|
+
write?: IWriteEnrichmentStrategy<T>;
|
|
8
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { Cell } from "./converters/Cell.ts";
|
|
2
|
+
import type { Field } from "./builders/Field.ts";
|
|
3
|
+
import type { IMapper } from "./mappers/IMapper.ts";
|
|
4
|
+
import { IdentityMapper } from "./mappers/IdentityMapper.ts";
|
|
5
|
+
import { StatementMappers } from "./StatementMappers.ts";
|
|
6
|
+
import { DeleteStatement } from "./statements/DeleteStatement.ts";
|
|
7
|
+
import { InsertStatement } from "./statements/InsertStatement.ts";
|
|
8
|
+
import { SelectInsertedIdStatement } from "./statements/SelectInsertedIdStatement.ts";
|
|
9
|
+
import { UpdateStatement } from "./statements/UpdateStatement.ts";
|
|
10
|
+
import type { IWhere } from "./statements/IWhere.ts";
|
|
11
|
+
import { ReadonlyRepositoryStatements } from "./ReadonlyRepositoryStatements.ts";
|
|
12
|
+
|
|
13
|
+
const same = (_name: string, value: Cell): Cell => value;
|
|
14
|
+
|
|
15
|
+
export class CRUDRepositoryStatements extends ReadonlyRepositoryStatements {
|
|
16
|
+
private readonly mappings: StatementMappers;
|
|
17
|
+
|
|
18
|
+
constructor(
|
|
19
|
+
table: string,
|
|
20
|
+
columns: readonly string[],
|
|
21
|
+
entityMappers: IMapper<string, string> = new IdentityMapper(),
|
|
22
|
+
propertyMappers: IMapper<string, string> = new IdentityMapper(),
|
|
23
|
+
) {
|
|
24
|
+
super(table, columns);
|
|
25
|
+
this.mappings = new StatementMappers(entityMappers, propertyMappers);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
insert = (fields: Field[]): InsertStatement =>
|
|
29
|
+
this.mappings.apply(new InsertStatement(this.table, fields), same) as InsertStatement;
|
|
30
|
+
|
|
31
|
+
update = (
|
|
32
|
+
fields: Field[],
|
|
33
|
+
where?: IWhere,
|
|
34
|
+
cell: (name: string, value: Cell) => Cell = same,
|
|
35
|
+
): UpdateStatement =>
|
|
36
|
+
this.mappings.apply(new UpdateStatement(this.table, fields, where), cell) as UpdateStatement;
|
|
37
|
+
|
|
38
|
+
delete = (where?: IWhere, cell: (name: string, value: Cell) => Cell = same): DeleteStatement =>
|
|
39
|
+
this.mappings.apply(new DeleteStatement(this.table, where), cell) as DeleteStatement;
|
|
40
|
+
|
|
41
|
+
selectInsertedId = (column: string): SelectInsertedIdStatement =>
|
|
42
|
+
this.mappings.apply(new SelectInsertedIdStatement(this.table, column), same) as SelectInsertedIdStatement;
|
|
43
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { DatasourceError } from "./DatasourceError.ts";
|
|
2
|
+
import { DeleteEnrichmentStrategy } from "./DeleteEnrichmentStrategy.ts";
|
|
3
|
+
import type { Enrichment } from "./Enrichment.ts";
|
|
4
|
+
import type { ICRUDRepository } from "./ICRUDRepository.ts";
|
|
5
|
+
import type { Scalar } from "./builders/Scalar.ts";
|
|
6
|
+
import { Operator, Where } from "./statements/index.ts";
|
|
7
|
+
|
|
8
|
+
type Props = Record<string, Scalar | object>;
|
|
9
|
+
|
|
10
|
+
const get = <TItem>(item: TItem, name: string): Scalar | object | undefined =>
|
|
11
|
+
(item as never as Props)[name];
|
|
12
|
+
|
|
13
|
+
const present = (value: Scalar | object | undefined): value is Scalar =>
|
|
14
|
+
value !== undefined && value !== null && typeof value !== "object" && value !== 0 && value !== 0n && value !== "";
|
|
15
|
+
|
|
16
|
+
const linkWhere = (
|
|
17
|
+
association: NonNullable<Enrichment["association"]>,
|
|
18
|
+
parentKey: Scalar,
|
|
19
|
+
relatedKey: Scalar,
|
|
20
|
+
): Where =>
|
|
21
|
+
new Where(
|
|
22
|
+
new Operator.And(
|
|
23
|
+
new Operator.Equals(association.parent, parentKey),
|
|
24
|
+
new Operator.Equals(association.related, relatedKey),
|
|
25
|
+
),
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
export class CascadeDeleteEnrichmentStrategy<T> extends DeleteEnrichmentStrategy<T> {
|
|
29
|
+
async deleteChildren<TChild extends object>(
|
|
30
|
+
writer: ICRUDRepository<TChild>,
|
|
31
|
+
enrichment: Enrichment,
|
|
32
|
+
parent: T,
|
|
33
|
+
deleteNested: (rows: TChild[]) => Promise<void>,
|
|
34
|
+
): Promise<void> {
|
|
35
|
+
const existing = await DeleteEnrichmentStrategy.load(writer, enrichment, parent);
|
|
36
|
+
await deleteNested(existing);
|
|
37
|
+
for (const row of existing) await writer.delete(row, DeleteEnrichmentStrategy.childrenWhere(writer, enrichment, parent));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async deleteLinked<TChild extends object>(writer: ICRUDRepository<TChild>, enrichment: Enrichment, parent: T): Promise<void> {
|
|
41
|
+
const association = enrichment.association;
|
|
42
|
+
if (!association) throw new DatasourceError("Missing association");
|
|
43
|
+
const key = get(parent, enrichment.sourceProperty);
|
|
44
|
+
if (!present(key)) return;
|
|
45
|
+
for (const row of await DeleteEnrichmentStrategy.loadLinks(writer, association.parent, key)) {
|
|
46
|
+
const id = get(row, association.related);
|
|
47
|
+
if (!present(id)) continue;
|
|
48
|
+
await writer.delete(row, linkWhere(association, key, id));
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { DefaultCreateEnrichmentStrategy } from "./DefaultCreateEnrichmentStrategy.ts";
|
|
2
|
+
import { DatasourceError } from "./DatasourceError.ts";
|
|
3
|
+
import { DeleteEnrichmentStrategy } from "./DeleteEnrichmentStrategy.ts";
|
|
4
|
+
import type { Enrichment } from "./Enrichment.ts";
|
|
5
|
+
import type { ICRUDRepository } from "./ICRUDRepository.ts";
|
|
6
|
+
import { UpdateEnrichmentStrategy } from "./UpdateEnrichmentStrategy.ts";
|
|
7
|
+
|
|
8
|
+
export class CascadeDeleteUpdateEnrichmentStrategy<T> extends UpdateEnrichmentStrategy<T> {
|
|
9
|
+
private readonly create = new DefaultCreateEnrichmentStrategy<T>();
|
|
10
|
+
|
|
11
|
+
async updateChildren<TChild extends object>(writer: ICRUDRepository<TChild>, enrichment: Enrichment, parent: T): Promise<void> {
|
|
12
|
+
for (const row of await DeleteEnrichmentStrategy.load(writer, enrichment, parent))
|
|
13
|
+
await writer.delete(row, DeleteEnrichmentStrategy.childrenWhere(writer, enrichment, parent));
|
|
14
|
+
await this.create.createChildren(writer, enrichment, parent);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async updateLinked<TChild extends object>(writer: ICRUDRepository<TChild>, enrichment: Enrichment, parent: T): Promise<void> {
|
|
18
|
+
const association = enrichment.association;
|
|
19
|
+
if (!association) throw new DatasourceError("Missing association");
|
|
20
|
+
const parentKey = this.requiredKey(parent, enrichment);
|
|
21
|
+
for (const row of await DeleteEnrichmentStrategy.loadLinks(writer, association.parent, parentKey)) {
|
|
22
|
+
const id = this.getCell(row, association.related);
|
|
23
|
+
if (!this.presentCell(id)) continue;
|
|
24
|
+
await writer.delete(row, this.linkWhere(association, parentKey, id));
|
|
25
|
+
}
|
|
26
|
+
await this.create.createLinked(writer, enrichment, parent);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import type { ICommandMiddleware } from "./ICommandMiddleware.ts";
|
|
2
|
+
import type { IReader } from "./IReader.ts";
|
|
3
|
+
import type { Parameter } from "./Parameter.ts";
|
|
4
|
+
import type { Response } from "./Response.ts";
|
|
5
|
+
import type { Row } from "./Row.ts";
|
|
6
|
+
import type { Cell, Cells } from "./converters/Cell.ts";
|
|
7
|
+
import type { IStatement } from "./statements/IStatement.ts";
|
|
8
|
+
import { DefaultStatementTextRenderer } from "./statements/DefaultStatementTextRenderer.ts";
|
|
9
|
+
import { Native } from "./statements/Native.ts";
|
|
10
|
+
import { StatementParser } from "./statements/StatementParser.ts";
|
|
11
|
+
|
|
12
|
+
const asError = (error: unknown): Error =>
|
|
13
|
+
error instanceof Error ? error : new Error(String(error));
|
|
14
|
+
|
|
15
|
+
type ExecuteBody = { result: Cell; rowsAffected: number };
|
|
16
|
+
|
|
17
|
+
const resultOf = (value: Cell | object | null): Cell =>
|
|
18
|
+
value instanceof Date ||
|
|
19
|
+
value instanceof Uint8Array ||
|
|
20
|
+
value === null ||
|
|
21
|
+
typeof value === "string" ||
|
|
22
|
+
typeof value === "number" ||
|
|
23
|
+
typeof value === "boolean" ||
|
|
24
|
+
typeof value === "bigint"
|
|
25
|
+
? value
|
|
26
|
+
: String(value);
|
|
27
|
+
|
|
28
|
+
const isResponse = (value: object): value is ExecuteBody =>
|
|
29
|
+
"rowsAffected" in value && typeof (value as ExecuteBody).rowsAffected === "number";
|
|
30
|
+
|
|
31
|
+
const logged = <T>(value: T, rows: readonly Row[]): readonly Row[] =>
|
|
32
|
+
rows.length > 0 || value === null || typeof value !== "object" || !isResponse(value)
|
|
33
|
+
? rows
|
|
34
|
+
: [{ result: resultOf(value.result), rowsAffected: value.rowsAffected }];
|
|
35
|
+
|
|
36
|
+
export class CommandMiddlewareHost {
|
|
37
|
+
private readonly middlewares: ICommandMiddleware[] = [];
|
|
38
|
+
|
|
39
|
+
addMiddleware(middleware: ICommandMiddleware): this {
|
|
40
|
+
this.middlewares.push(middleware);
|
|
41
|
+
return this;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
execute(text: string, parameters?: Parameter[]): Promise<Response> {
|
|
45
|
+
if (!text.trim()) throw new Error("empty command");
|
|
46
|
+
return this.executeStatement(StatementParser.parse(text), parameters);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
executeReader(text: string, parameters?: Parameter[]): Promise<IReader<Cells>> {
|
|
50
|
+
if (!text.trim()) throw new Error("empty command");
|
|
51
|
+
return this.executeReaderStatement(StatementParser.parse(text), parameters);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
executeStatement(statement: IStatement, parameters?: Parameter[]): Promise<Response> {
|
|
55
|
+
return this.executeNative(
|
|
56
|
+
new Native(DefaultStatementTextRenderer.instance.render(statement)),
|
|
57
|
+
parameters,
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
executeReaderStatement(
|
|
62
|
+
statement: IStatement,
|
|
63
|
+
parameters?: Parameter[],
|
|
64
|
+
): Promise<IReader<Cells>> {
|
|
65
|
+
return this.executeReaderNative(
|
|
66
|
+
new Native(DefaultStatementTextRenderer.instance.render(statement)),
|
|
67
|
+
parameters,
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
executeNative(_text: Native, _parameters?: Parameter[]): Promise<Response> {
|
|
72
|
+
throw new Error("executeNative");
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
executeReaderNative(_text: Native, _parameters?: Parameter[]): Promise<IReader<Cells>> {
|
|
76
|
+
throw new Error("executeReaderNative");
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async run<T>(
|
|
80
|
+
text: string,
|
|
81
|
+
params: Parameter[] | undefined,
|
|
82
|
+
fn: (
|
|
83
|
+
text: string,
|
|
84
|
+
params?: Parameter[],
|
|
85
|
+
) => Promise<{ value: T; rows: readonly Row[] }>,
|
|
86
|
+
): Promise<T> {
|
|
87
|
+
let q = text.trim();
|
|
88
|
+
let p = params;
|
|
89
|
+
for (const middleware of this.middlewares) {
|
|
90
|
+
const rewritten = await middleware.beforeCommand(q, p);
|
|
91
|
+
if (rewritten) {
|
|
92
|
+
q = rewritten.text;
|
|
93
|
+
p = rewritten.params as Parameter[] | undefined;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const start = performance.now();
|
|
98
|
+
try {
|
|
99
|
+
const { value, rows } = await fn(q, p);
|
|
100
|
+
const elapsedMs = performance.now() - start;
|
|
101
|
+
for (const middleware of this.middlewares) {
|
|
102
|
+
await middleware.afterCommand(q, p, logged(value, rows), elapsedMs);
|
|
103
|
+
}
|
|
104
|
+
return value;
|
|
105
|
+
} catch (error) {
|
|
106
|
+
const elapsedMs = performance.now() - start;
|
|
107
|
+
for (const middleware of this.middlewares) {
|
|
108
|
+
await middleware.afterCommand(q, p, [], elapsedMs, asError(error));
|
|
109
|
+
}
|
|
110
|
+
throw error;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { ICommandMiddleware } from "./ICommandMiddleware.ts";
|
|
2
|
+
import type { Parameter } from "./Parameter.ts";
|
|
3
|
+
import type { Row } from "./Row.ts";
|
|
4
|
+
import type { Cell } from "./converters/Cell.ts";
|
|
5
|
+
|
|
6
|
+
export const formatElapsed = (elapsedMs: number): string =>
|
|
7
|
+
elapsedMs < 1
|
|
8
|
+
? `${Math.round(elapsedMs * 1000)}μs`
|
|
9
|
+
: `${Math.round(elapsedMs)}ms`;
|
|
10
|
+
|
|
11
|
+
const cellOf = (value: Cell): string =>
|
|
12
|
+
value === null
|
|
13
|
+
? "null"
|
|
14
|
+
: value instanceof Date
|
|
15
|
+
? value.toISOString()
|
|
16
|
+
: value instanceof Uint8Array
|
|
17
|
+
? Buffer.from(value).toString("base64")
|
|
18
|
+
: typeof value === "bigint"
|
|
19
|
+
? String(value)
|
|
20
|
+
: typeof value === "boolean"
|
|
21
|
+
? value ? "True" : "False"
|
|
22
|
+
: typeof value === "string"
|
|
23
|
+
? value
|
|
24
|
+
: String(value);
|
|
25
|
+
|
|
26
|
+
const paramsOf = (params?: ReadonlyArray<Parameter>) =>
|
|
27
|
+
`{${(params ?? []).map((param) => `${param.name}: ${cellOf(param.value as Cell)}`).join(", ")}}`;
|
|
28
|
+
|
|
29
|
+
const rowsOf = (rows: ReadonlyArray<Row>) =>
|
|
30
|
+
rows
|
|
31
|
+
.map((row) => `{${Object.entries(row).sort(([left], [right]) => left < right ? -1 : 1).map(([key, value]) => `${key}: ${cellOf(value)}`).join(", ")}}`)
|
|
32
|
+
.join(", ");
|
|
33
|
+
|
|
34
|
+
export class ConsoleCommandMiddleware implements ICommandMiddleware {
|
|
35
|
+
constructor(
|
|
36
|
+
private readonly write: (line: string) => void = (line) => {
|
|
37
|
+
console.log(line);
|
|
38
|
+
},
|
|
39
|
+
private readonly now: () => Date = () => new Date(),
|
|
40
|
+
private readonly options: Record<string, string> = {},
|
|
41
|
+
) {}
|
|
42
|
+
|
|
43
|
+
beforeCommand = (text: string, params?: ReadonlyArray<Parameter>): void => {
|
|
44
|
+
this.write(this.line("Start", text));
|
|
45
|
+
if (this.verbose()) this.write(this.line("Request", paramsOf(params)));
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
afterCommand = (
|
|
49
|
+
text: string,
|
|
50
|
+
_params: ReadonlyArray<Parameter> | undefined,
|
|
51
|
+
results: ReadonlyArray<Row>,
|
|
52
|
+
elapsedMs: number,
|
|
53
|
+
): void => {
|
|
54
|
+
const options = { ...this.options, elapsedMs: formatElapsed(elapsedMs) };
|
|
55
|
+
this.write(`${this.line("Finish", text)} ${options.elapsedMs}`);
|
|
56
|
+
if (this.verbose()) this.write(this.line("AfterCommand", rowsOf(results)));
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
private verbose = (): boolean =>
|
|
60
|
+
this.options.isVerbose === "true" || this.options.isVerbose === "1";
|
|
61
|
+
|
|
62
|
+
private line = (phase: string, text: string): string =>
|
|
63
|
+
`[${this.now().toISOString()}]-[datasource][${phase}]-[${text}]`;
|
|
64
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import type { Enrichment } from "./Enrichment.ts";
|
|
2
|
+
import type { ICRUDRepository } from "./ICRUDRepository.ts";
|
|
3
|
+
import type { Scalar } from "./builders/Scalar.ts";
|
|
4
|
+
import { DatasourceError } from "./DatasourceError.ts";
|
|
5
|
+
|
|
6
|
+
type Props = Record<string, Scalar | object>;
|
|
7
|
+
|
|
8
|
+
export abstract class CreateEnrichmentStrategy<T> {
|
|
9
|
+
abstract createChildren<TChild extends object>(writer: ICRUDRepository<TChild>, enrichment: Enrichment, parent: T): Promise<void>;
|
|
10
|
+
|
|
11
|
+
abstract createLinked<TChild extends object>(writer: ICRUDRepository<TChild>, enrichment: Enrichment, parent: T): Promise<void>;
|
|
12
|
+
|
|
13
|
+
collectChildren<TChild extends object>(writer: ICRUDRepository<TChild>, enrichment: Enrichment, parent: T): TChild[] {
|
|
14
|
+
const children = this.payloadOf<TChild, T>(parent, enrichment.destinationProperty, enrichment);
|
|
15
|
+
if (children.length === 0) return children;
|
|
16
|
+
const key = this.requiredKey(parent, enrichment);
|
|
17
|
+
for (const child of children) this.stampCell(child, enrichment.relatedKey, key);
|
|
18
|
+
return children;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
protected getCell = <TItem>(item: TItem, name: string): Scalar | object | undefined =>
|
|
22
|
+
(item as never as Props)[name];
|
|
23
|
+
|
|
24
|
+
protected presentCell = (value: Scalar | object | undefined): value is Scalar =>
|
|
25
|
+
value !== undefined && value !== null && typeof value !== "object" && value !== 0 && value !== 0n && value !== "";
|
|
26
|
+
|
|
27
|
+
protected payloadOf = <TChild extends object, TParent>(
|
|
28
|
+
parent: TParent,
|
|
29
|
+
into: string,
|
|
30
|
+
enrichment?: Enrichment,
|
|
31
|
+
): TChild[] => mapPayload(this.getCell(parent, into), enrichment) as TChild[];
|
|
32
|
+
|
|
33
|
+
protected stampCell = <TItem extends object>(related: TItem, by: string, key: Scalar | object | undefined): void => {
|
|
34
|
+
if (key === undefined || key === null || typeof key === "object") return;
|
|
35
|
+
const current = this.getCell(related, by);
|
|
36
|
+
if (current !== null && typeof current === "object" && !Array.isArray(current)) {
|
|
37
|
+
const target = current as Props;
|
|
38
|
+
const id = "Id" in target ? "Id" : "id";
|
|
39
|
+
target[id] = key;
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
Object.assign(related, { [by]: key });
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
protected relatedIds = <TParent>(parent: TParent, enrichment: Enrichment): Scalar[] => {
|
|
46
|
+
const ids: Scalar[] = [];
|
|
47
|
+
const seen = new Set<string>();
|
|
48
|
+
for (const related of this.payloadOf<object, TParent>(parent, enrichment.destinationProperty)) {
|
|
49
|
+
const id = this.getCell(related, enrichment.relatedKey);
|
|
50
|
+
if (!this.presentCell(id)) throw new DatasourceError(`${enrichment.destinationProperty} requires "${enrichment.relatedKey}"`);
|
|
51
|
+
const key = String(id);
|
|
52
|
+
if (seen.has(key)) continue;
|
|
53
|
+
seen.add(key);
|
|
54
|
+
ids.push(id);
|
|
55
|
+
}
|
|
56
|
+
return ids;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
protected requiredKey = <TParent>(parent: TParent, enrichment: Enrichment): Scalar => {
|
|
60
|
+
const key = this.getCell(parent, enrichment.sourceProperty);
|
|
61
|
+
if (!this.presentCell(key)) throw new DatasourceError(`${enrichment.destinationProperty} requires "${enrichment.sourceProperty}"`);
|
|
62
|
+
return key;
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const mapPayload = (value: Scalar | object | undefined, enrichment?: Enrichment): object[] => {
|
|
67
|
+
if (value === undefined || value === null || typeof value === "string") return [];
|
|
68
|
+
if (Array.isArray(value)) return value.filter((item) => item !== null && typeof item === "object");
|
|
69
|
+
if (enrichment?.type !== "dictionary" || typeof value !== "object") return [];
|
|
70
|
+
const keyName = mapKeyName(enrichment.relatedKey);
|
|
71
|
+
const valueName = enrichment.relatedProperty ?? (keyName === "Key" ? "Value" : "value");
|
|
72
|
+
return Object.entries(value as Record<string, Scalar | object>)
|
|
73
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
74
|
+
.map(([key, cell]) =>
|
|
75
|
+
cell !== null && typeof cell === "object" && !Array.isArray(cell)
|
|
76
|
+
? { [keyName]: key, ...(cell as object) }
|
|
77
|
+
: { [keyName]: key, [valueName]: cell },
|
|
78
|
+
);
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const mapKeyName = (relatedKey: string): string =>
|
|
82
|
+
relatedKey.includes("_") || relatedKey[0] !== relatedKey[0]?.toUpperCase() ? "key" : "Key";
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import type { Cell } from "./converters/Cell.ts";
|
|
2
|
+
import type { Constructable } from "./converters/Constructable.ts";
|
|
3
|
+
import type { CRUDRepositoryOptions } from "./CRUDRepositoryOptions.ts";
|
|
4
|
+
import type { ICommand } from "./ICommand.ts";
|
|
5
|
+
import type { IConnection } from "./IConnection.ts";
|
|
6
|
+
import type { IDatasource } from "./IDatasource.ts";
|
|
7
|
+
import type { IDriver } from "./IDriver.ts";
|
|
8
|
+
import type { IReadonlyRepository } from "./IReadonlyRepository.ts";
|
|
9
|
+
import type { ICRUDRepository } from "./ICRUDRepository.ts";
|
|
10
|
+
import type { DatasourceOptions } from "./DatasourceOptions.ts";
|
|
11
|
+
import type { ReadonlyRepositoryMappingOptions } from "./ReadonlyRepositoryMappingOptions.ts";
|
|
12
|
+
import { DatasourceError } from "./DatasourceError.ts";
|
|
13
|
+
import { CRUDRepository } from "./CRUDRepository.ts";
|
|
14
|
+
import { ReadonlyRepository } from "./ReadonlyRepository.ts";
|
|
15
|
+
import { RepositoryFactory } from "./RepositoryFactory.ts";
|
|
16
|
+
import { RepositoryNotRegisteredError } from "./RepositoryNotRegisteredError.ts";
|
|
17
|
+
import { RowTypeConverter } from "./converters/RowTypeConverter.ts";
|
|
18
|
+
import { createCRUDRepositoryOptions } from "./createCRUDRepositoryOptions.ts";
|
|
19
|
+
import { createReadonlyRepositoryMappingOptions } from "./createReadonlyRepositoryMappingOptions.ts";
|
|
20
|
+
|
|
21
|
+
export class Datasource implements IDatasource {
|
|
22
|
+
private connection?: IConnection;
|
|
23
|
+
readonly repositories = new RepositoryFactory();
|
|
24
|
+
|
|
25
|
+
constructor(
|
|
26
|
+
readonly provider: string,
|
|
27
|
+
private readonly driver: IDriver,
|
|
28
|
+
private readonly options?: DatasourceOptions,
|
|
29
|
+
) {
|
|
30
|
+
const self = this;
|
|
31
|
+
return new Proxy(this, {
|
|
32
|
+
get(target, prop, receiver) {
|
|
33
|
+
if (typeof prop !== "string" || Reflect.has(target, prop))
|
|
34
|
+
return Reflect.get(target, prop, receiver);
|
|
35
|
+
if (self.repositories.has(prop))
|
|
36
|
+
return self.repositories.create(prop, self.defaultConnection());
|
|
37
|
+
throw new RepositoryNotRegisteredError(prop);
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
private defaultConnection = (): IConnection =>
|
|
43
|
+
(this.connection ??= this.driver.connection(this.options));
|
|
44
|
+
|
|
45
|
+
createConnection = (): Promise<IConnection> =>
|
|
46
|
+
Promise.resolve(this.defaultConnection());
|
|
47
|
+
|
|
48
|
+
createCommand = async (connection?: IConnection): Promise<ICommand> => {
|
|
49
|
+
if (!connection) {
|
|
50
|
+
connection = this.defaultConnection();
|
|
51
|
+
if (connection.isOpen) throw new DatasourceError("Connection is already open");
|
|
52
|
+
await connection.open();
|
|
53
|
+
}
|
|
54
|
+
return this.driver.command(connection);
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
private applyCrud = <T>(
|
|
58
|
+
type: Constructable<T>,
|
|
59
|
+
options?: CRUDRepositoryOptions<T>,
|
|
60
|
+
): CRUDRepositoryOptions<T> => {
|
|
61
|
+
const built = createCRUDRepositoryOptions(type, this.driver.rowTypeConverters);
|
|
62
|
+
const types = this.driver.rowTypeConverters;
|
|
63
|
+
const rows = options?.rowConverter ?? RowTypeConverter.from(this.driver.rowTypeConverters, columnsOf(type));
|
|
64
|
+
return {
|
|
65
|
+
...(options ?? built),
|
|
66
|
+
entity: options?.entity ?? built.entity,
|
|
67
|
+
rowConverter: rows,
|
|
68
|
+
rowTypeConverters: types,
|
|
69
|
+
type,
|
|
70
|
+
};
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
private applyReadonly = <T>(
|
|
74
|
+
type: Constructable<T>,
|
|
75
|
+
options?: ReadonlyRepositoryMappingOptions<T>,
|
|
76
|
+
): ReadonlyRepositoryMappingOptions<T> => {
|
|
77
|
+
const built = createReadonlyRepositoryMappingOptions(type, this.driver.rowTypeConverters);
|
|
78
|
+
const types = this.driver.rowTypeConverters;
|
|
79
|
+
const rows = options?.rowConverter ?? RowTypeConverter.from(this.driver.rowTypeConverters, columnsOf(type));
|
|
80
|
+
return {
|
|
81
|
+
...(options ?? built),
|
|
82
|
+
entity: options?.entity ?? built.entity,
|
|
83
|
+
rowConverter: rows,
|
|
84
|
+
rowTypeConverters: types,
|
|
85
|
+
type,
|
|
86
|
+
};
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
createRepository<T>(
|
|
90
|
+
type: Constructable<T>,
|
|
91
|
+
connection?: IConnection,
|
|
92
|
+
options?: CRUDRepositoryOptions<T>,
|
|
93
|
+
): ICRUDRepository<T>;
|
|
94
|
+
createRepository(typeName: string, connection?: IConnection): ICRUDRepository;
|
|
95
|
+
createRepository<T>(
|
|
96
|
+
typeOrName: Constructable<T> | string,
|
|
97
|
+
connection?: IConnection,
|
|
98
|
+
options?: CRUDRepositoryOptions<T>,
|
|
99
|
+
): ICRUDRepository<T> {
|
|
100
|
+
if (typeof typeOrName === "string")
|
|
101
|
+
return this.repositories.create(typeOrName, connection ?? this.defaultConnection()) as ICRUDRepository<T>;
|
|
102
|
+
return new CRUDRepository(
|
|
103
|
+
connection ?? this.defaultConnection(),
|
|
104
|
+
this.applyCrud(typeOrName, options),
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
createReadonlyRepository<T>(
|
|
109
|
+
type: Constructable<T>,
|
|
110
|
+
connection?: IConnection,
|
|
111
|
+
options?: ReadonlyRepositoryMappingOptions<T>,
|
|
112
|
+
): IReadonlyRepository<T>;
|
|
113
|
+
createReadonlyRepository(typeName: string, connection?: IConnection): IReadonlyRepository;
|
|
114
|
+
createReadonlyRepository<T>(
|
|
115
|
+
typeOrName: Constructable<T> | string,
|
|
116
|
+
connection?: IConnection,
|
|
117
|
+
options?: ReadonlyRepositoryMappingOptions<T>,
|
|
118
|
+
): IReadonlyRepository<T> {
|
|
119
|
+
if (typeof typeOrName === "string")
|
|
120
|
+
return this.repositories.createReadonly(typeOrName, connection ?? this.defaultConnection()) as IReadonlyRepository<T>;
|
|
121
|
+
return new ReadonlyRepository(
|
|
122
|
+
connection ?? this.defaultConnection(),
|
|
123
|
+
this.applyReadonly(typeOrName, options),
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
static create: (options?: DatasourceOptions) => Datasource;
|
|
128
|
+
|
|
129
|
+
static get default(): Datasource {
|
|
130
|
+
return Datasource.create();
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const columnsOf = <T>(type: Constructable<T>): Record<string, string> => {
|
|
135
|
+
const instance = new type() as Record<string, Cell | object>;
|
|
136
|
+
const columns: Record<string, string> = {};
|
|
137
|
+
for (const [name, value] of Object.entries(instance)) {
|
|
138
|
+
if (Array.isArray(value)) continue;
|
|
139
|
+
const canonical = canonicalOf(value);
|
|
140
|
+
if (canonical) columns[name] = canonical;
|
|
141
|
+
}
|
|
142
|
+
return columns;
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
const canonicalOf = (value: Cell | object): string | undefined =>
|
|
146
|
+
typeof value === "boolean"
|
|
147
|
+
? "bool"
|
|
148
|
+
: value instanceof Date
|
|
149
|
+
? "datetime"
|
|
150
|
+
: value instanceof Uint8Array
|
|
151
|
+
? "binary"
|
|
152
|
+
: undefined;
|