@deterministic-code/datasources 0.1.11 → 0.1.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/dist/BulkCRUDRepository.js +2 -0
  2. package/dist/CRUDRepository.js +20 -9
  3. package/dist/CRUDRepositoryBuilder.d.ts +4 -0
  4. package/dist/CRUDRepositoryBuilder.js +9 -0
  5. package/dist/CreateEnrichmentStrategy.d.ts +8 -1
  6. package/dist/CreateEnrichmentStrategy.js +48 -3
  7. package/dist/DefaultWriteEnrichmentStrategy.d.ts +1 -0
  8. package/dist/DefaultWriteEnrichmentStrategy.js +8 -2
  9. package/dist/DriverLoader.d.ts +1 -1
  10. package/dist/DriverLoader.js +7 -110
  11. package/dist/IWriteEnrichmentStrategy.d.ts +1 -0
  12. package/dist/NestedSelectBuilder.d.ts +6 -0
  13. package/dist/NestedSelectBuilder.js +63 -0
  14. package/dist/NestedSelectBuilderOptions.d.ts +5 -0
  15. package/dist/NestedSelectBuilderOptions.js +1 -0
  16. package/dist/NestedSelectRelation.d.ts +9 -0
  17. package/dist/NestedSelectRelation.js +1 -0
  18. package/dist/NestedSelectRelationKind.d.ts +1 -0
  19. package/dist/NestedSelectRelationKind.js +1 -0
  20. package/dist/NestedSelectStatement.d.ts +10 -0
  21. package/dist/NestedSelectStatement.js +1 -0
  22. package/dist/NestedSelectType.d.ts +7 -0
  23. package/dist/NestedSelectType.js +1 -0
  24. package/dist/ReadonlyRepository.d.ts +2 -1
  25. package/dist/ReadonlyRepository.js +9 -1
  26. package/dist/RepositoryBuilderOptionsFactory.d.ts +8 -0
  27. package/dist/RepositoryBuilderOptionsFactory.js +12 -0
  28. package/dist/SelectFieldAliases.d.ts +14 -0
  29. package/dist/SelectFieldAliases.js +62 -0
  30. package/dist/StatementMappers.d.ts +1 -0
  31. package/dist/StatementMappers.js +21 -28
  32. package/dist/StoredEnrichment.d.ts +7 -5
  33. package/dist/StoredEnrichment.js +3 -1
  34. package/dist/converters/DefaultValueConverter.js +7 -5
  35. package/dist/converters/DefaultValueOptions.d.ts +2 -0
  36. package/dist/index.d.ts +9 -2
  37. package/dist/index.js +3 -1
  38. package/dist/statements/Aggregate.d.ts +4 -4
  39. package/dist/statements/Aggregate.js +5 -5
  40. package/dist/statements/CreateNestedReadStoredProcedureStatement.d.ts +14 -0
  41. package/dist/statements/CreateNestedReadStoredProcedureStatement.js +37 -0
  42. package/dist/statements/CreateNestedReadStoredProcedureStatementTextRenderer.d.ts +4 -0
  43. package/dist/statements/CreateNestedReadStoredProcedureStatementTextRenderer.js +5 -0
  44. package/dist/statements/CreateTableStatementTextRenderer.d.ts +2 -1
  45. package/dist/statements/ICreateNestedReadStoredProcedureStatement.d.ts +8 -0
  46. package/dist/statements/ICreateNestedReadStoredProcedureStatement.js +1 -0
  47. package/dist/statements/IStatementVisitor.d.ts +2 -0
  48. package/dist/statements/NestedWhereArgument.d.ts +5 -0
  49. package/dist/statements/NestedWhereArgument.js +1 -0
  50. package/dist/statements/SelectStatement.d.ts +1 -0
  51. package/dist/statements/SelectStatement.js +2 -0
  52. package/dist/statements/SelectStatementOptions.d.ts +1 -0
  53. package/dist/statements/SelectStatementTextRenderer.js +6 -1
  54. package/dist/statements/StatementRenderer.d.ts +3 -0
  55. package/dist/statements/StatementRenderer.js +5 -0
  56. package/dist/statements/Where.js +1 -0
  57. package/dist/statements/index.d.ts +4 -0
  58. package/dist/statements/index.js +2 -0
  59. package/package.json +1 -1
@@ -10,6 +10,8 @@ const isBulkStrategy = (value) => "create" in value &&
10
10
  !("statements" in value);
11
11
  const transactionOf = (options) => options !== undefined && "write" in options ? options.write?.useTransaction ?? false : false;
12
12
  const inTransaction = async (transactional, connection, write) => {
13
+ if (!connection.isOpen)
14
+ await connection.open();
13
15
  if (!transactional)
14
16
  return write();
15
17
  const work = new UnitOfWorkTransaction(connection);
@@ -6,6 +6,7 @@ import { enrichmentsOf, ReadonlyRepository } from "./ReadonlyRepository.js";
6
6
  import { DefaultReadonlyEnrichmentStrategy } from "./DefaultReadonlyEnrichmentStrategy.js";
7
7
  import { execute, executeReader } from "./ICommand.js";
8
8
  import { DatasourceError } from "./DatasourceError.js";
9
+ import { lookupKeys, mergeLookups } from "./CreateEnrichmentStrategy.js";
9
10
  import { DefaultWriteEnrichmentStrategy } from "./DefaultWriteEnrichmentStrategy.js";
10
11
  import { UnitOfWorkTransaction } from "./UnitOfWorkTransaction.js";
11
12
  import { RepositoryTree } from "./RepositoryTree.js";
@@ -13,10 +14,9 @@ import { RepositoryTreeNode } from "./RepositoryTreeNode.js";
13
14
  const assigned = (value) => value !== null && value !== "" && value !== 0 && value !== 0n;
14
15
  const listed = (list, col, fallback) => list === undefined ? fallback : list.includes(col);
15
16
  const writable = (key, columns, column) => columns.length === 0 || columns.includes(key) || columns.some((name) => column(name) === key);
16
- const fieldsOf = (record, include, cell, enrichmentInto, columns, column) => Object.entries(record)
17
+ const fieldsOf = (record, include, extras, cell, enrichmentInto, columns, column) => Object.entries(record)
17
18
  .filter(([key, value]) => include(key) &&
18
- writable(key, columns, column) &&
19
- !enrichmentInto(key) &&
19
+ (extras.has(key) || (writable(key, columns, column) && !enrichmentInto(key))) &&
20
20
  value !== null &&
21
21
  value !== undefined)
22
22
  .sort(([left], [right]) => left.localeCompare(right))
@@ -56,9 +56,11 @@ export class CRUDRepository extends ReadonlyRepository {
56
56
  }
57
57
  addEnriched(enrichment, via, strategy) {
58
58
  super.addEnriched(enrichment, via, strategy);
59
+ const stored = lastEnrichment(this);
60
+ stored.via = via;
59
61
  const writer = writerOf(via);
60
62
  if (writer)
61
- lastEnrichment(this).persist = writer;
63
+ stored.persist = writer;
62
64
  return this;
63
65
  }
64
66
  addLinked(enrichment, via, link, strategy) {
@@ -73,9 +75,12 @@ export class CRUDRepository extends ReadonlyRepository {
73
75
  update = (item, byWhere) => this.inTransaction(() => this.updateCore(item, byWhere));
74
76
  delete = (item, byWhere) => this.inTransaction(() => this.deleteCore(item, byWhere));
75
77
  inTransaction = async (write) => {
78
+ const connection = this.command().connection;
79
+ if (!connection.isOpen)
80
+ await connection.open();
76
81
  if (!this.useTransaction)
77
82
  return write();
78
- const work = new UnitOfWorkTransaction(this.command().connection);
83
+ const work = new UnitOfWorkTransaction(connection);
79
84
  await work.begin();
80
85
  try {
81
86
  const result = await write();
@@ -88,6 +93,7 @@ export class CRUDRepository extends ReadonlyRepository {
88
93
  }
89
94
  };
90
95
  createCore = async (item) => {
96
+ await this.write.resolveLookups?.(this.tree, [item]);
91
97
  await this.insert(item);
92
98
  if (!this.enriched)
93
99
  return item;
@@ -98,6 +104,7 @@ export class CRUDRepository extends ReadonlyRepository {
98
104
  return id === undefined ? item : (await this.readSingleById(id)) ?? item;
99
105
  };
100
106
  updateCore = async (item, byWhere) => {
107
+ await this.write.resolveLookups?.(this.tree, [item]);
101
108
  const response = await this.save(item, byWhere);
102
109
  if (this.enriched)
103
110
  await this.write.persist(this.tree, [item], "update");
@@ -110,8 +117,9 @@ export class CRUDRepository extends ReadonlyRepository {
110
117
  };
111
118
  readSingleById = async (id) => this.readSingle(this.byId(id));
112
119
  insert = async (item) => {
120
+ const extras = lookupKeys(item);
113
121
  const record = this.reverseConvert(item);
114
- const fields = fieldsOf(record, (col) => listed(this.createProperties, col, !this.ids.includes(col) || assigned(record[col] ?? null)), this.cell, this.enrichmentInto, this.crudStatements.readColumns, this.column);
122
+ const fields = fieldsOf(record, (col) => extras.has(col) || listed(this.createProperties, col, !this.ids.includes(col) || assigned(record[col] ?? null)), extras, this.cell, this.enrichmentInto, this.crudStatements.readColumns, this.column);
115
123
  const statement = this.crudStatements.insert(fields);
116
124
  const response = await execute(this.command(), statement, statement.fields);
117
125
  if (this.ids.length === 1 && !fields.some((field) => field.name === this.ids[0]))
@@ -145,8 +153,9 @@ export class CRUDRepository extends ReadonlyRepository {
145
153
  };
146
154
  idValue = (item) => scalarOf(item[this.ids[0]]);
147
155
  save = async (item, byWhere) => {
156
+ const extras = lookupKeys(item);
148
157
  const record = this.reverseConvert(item);
149
- const fields = fieldsOf(record, (col) => listed(this.updateProperties, col, !this.ids.includes(col)), this.cell, this.enrichmentInto, this.crudStatements.readColumns, this.column);
158
+ const fields = fieldsOf(record, (col) => extras.has(col) || listed(this.updateProperties, col, !this.ids.includes(col)), extras, this.cell, this.enrichmentInto, this.crudStatements.readColumns, this.column);
150
159
  if (fields.length === 0)
151
160
  throw new DatasourceError("Update requires at least one non-Id field");
152
161
  const statement = this.crudStatements.update(fields, byWhere ?? this.idWhere(record), (name, value) => this.cell(name, value, byWhere === undefined));
@@ -159,7 +168,7 @@ export class CRUDRepository extends ReadonlyRepository {
159
168
  const parameters = listed.length
160
169
  ? listed
161
170
  : DefaultStatementTextRenderer.instance.render(statement).includes("$")
162
- ? fieldsOf(record, () => true, this.cell, this.enrichmentInto, this.crudStatements.readColumns, this.column)
171
+ ? fieldsOf(record, () => true, new Set(), this.cell, this.enrichmentInto, this.crudStatements.readColumns, this.column)
163
172
  : [];
164
173
  return execute(this.command(), statement, parameters);
165
174
  };
@@ -174,7 +183,9 @@ export class CRUDRepository extends ReadonlyRepository {
174
183
  };
175
184
  reverseConvert = (item) => {
176
185
  const row = this.objectConverter.reverseConvert(item);
177
- return this.rowConverter ? this.rowConverter.reverseConvert(row) : row;
186
+ const converted = this.rowConverter ? this.rowConverter.reverseConvert(row) : row;
187
+ mergeLookups(item, converted);
188
+ return converted;
178
189
  };
179
190
  idWhere = (record) => {
180
191
  const parts = this.ids.map((col) => {
@@ -12,7 +12,11 @@ export declare const repositoryParts: <T>(typeOrConnection: Constructable<T> | I
12
12
  statements: CRUDRepositoryStatements;
13
13
  };
14
14
  export declare class CRUDRepositoryBuilder<T = object> extends ReadonlyRepositoryBuilder<T> {
15
+ private readonly factory;
15
16
  crudStatements?: CRUDRepositoryStatements;
17
+ add(options: CRUDRepositoryOptions<T>): this;
18
+ add(dialect: string, options: CRUDRepositoryOptions<T>): this;
19
+ for: (connection: IConnection) => CRUDRepositoryOptions<T>;
16
20
  protected ids: (options: ReadonlyRepositoryMappingOptions<T>) => string[];
17
21
  protected statements: (table: string, columns: string[]) => ReadonlyRepositoryStatements;
18
22
  build: (connection: IConnection, options: CRUDRepositoryOptions<T>) => CRUDRepository<T>;
@@ -3,6 +3,7 @@ import { CRUDRepository } from "./CRUDRepository.js";
3
3
  import { CRUDRepositoryStatements } from "./CRUDRepositoryStatements.js";
4
4
  import { ReadonlyRepositoryBuilder } from "./ReadonlyRepositoryBuilder.js";
5
5
  import { ReadonlyRepositoryStatements } from "./ReadonlyRepositoryStatements.js";
6
+ import { RepositoryBuilderOptionsFactory } from "./RepositoryBuilderOptionsFactory.js";
6
7
  const isCrud = (options) => "ids" in options || "write" in options;
7
8
  const withIds = (options) => "ids" in options
8
9
  ? options
@@ -18,7 +19,15 @@ export const repositoryParts = (typeOrConnection, connectionOrOptions) => {
18
19
  return { built, statements: builder.crudStatements };
19
20
  };
20
21
  export class CRUDRepositoryBuilder extends ReadonlyRepositoryBuilder {
22
+ factory = new RepositoryBuilderOptionsFactory();
21
23
  crudStatements;
24
+ add(dialectOrOptions, options) {
25
+ typeof dialectOrOptions === "string"
26
+ ? this.factory.add(dialectOrOptions, options)
27
+ : this.factory.add(dialectOrOptions);
28
+ return this;
29
+ }
30
+ for = (connection) => this.factory.for(connection.provider);
22
31
  ids = (options) => isCrud(options)
23
32
  ? (options.ids ?? (options.type ? defaultId(fieldNamesOf(options.type)) : ["id"]))
24
33
  : [];
@@ -1,13 +1,20 @@
1
1
  import type { Enrichment } from "./Enrichment.js";
2
2
  import type { ICRUDRepository } from "./ICRUDRepository.js";
3
3
  import type { Scalar } from "./builders/Scalar.js";
4
+ import type { Cells } from "./converters/Cell.js";
5
+ import type { StoredEnrichment } from "./StoredEnrichment.js";
6
+ export declare const mergeLookups: (item: object, record: Cells) => void;
7
+ export declare const lookupKeys: (item: object) => Set<string>;
4
8
  export declare abstract class CreateEnrichmentStrategy<T> {
5
9
  abstract createChildren<TChild extends object>(writer: ICRUDRepository<TChild>, enrichment: Enrichment, parent: T): Promise<void>;
6
10
  abstract createLinked<TChild extends object>(writer: ICRUDRepository<TChild>, enrichment: Enrichment, parent: T): Promise<void>;
7
- collectChildren<TChild extends object>(writer: ICRUDRepository<TChild>, enrichment: Enrichment, parent: T): TChild[];
11
+ resolveLookups(entries: readonly StoredEnrichment<T>[], items: T[]): Promise<void>;
12
+ collectChildren<TChild extends object>(_writer: ICRUDRepository<TChild>, enrichment: Enrichment, parent: T): TChild[];
13
+ protected keyOf: (value: Scalar | object | undefined) => Scalar | object | undefined;
8
14
  protected getCell: <TItem>(item: TItem, name: string) => Scalar | object | undefined;
9
15
  protected presentCell: (value: Scalar | object | undefined) => value is Scalar;
10
16
  protected payloadOf: <TChild extends object, TParent>(parent: TParent, into: string, enrichment?: Enrichment) => TChild[];
17
+ protected stamp: <TItem extends object>(related: TItem, by: string, key: Scalar | object | undefined) => void;
11
18
  protected stampCell: <TItem extends object>(related: TItem, by: string, key: Scalar | object | undefined) => void;
12
19
  protected relatedIds: <TParent>(parent: TParent, enrichment: Enrichment) => Scalar[];
13
20
  protected requiredKey: <TParent>(parent: TParent, enrichment: Enrichment) => Scalar;
@@ -1,6 +1,38 @@
1
1
  import { DatasourceError } from "./DatasourceError.js";
2
+ import { Operator, Where } from "./statements/index.js";
3
+ const lookups = new WeakMap();
4
+ export const mergeLookups = (item, record) => {
5
+ const extras = lookups.get(item);
6
+ if (extras)
7
+ Object.assign(record, extras);
8
+ };
9
+ export const lookupKeys = (item) => new Set(Object.keys(lookups.get(item) ?? {}));
2
10
  export class CreateEnrichmentStrategy {
3
- collectChildren(writer, enrichment, parent) {
11
+ async resolveLookups(entries, items) {
12
+ for (const item of items) {
13
+ for (const entry of entries) {
14
+ const { enrichment } = entry;
15
+ if ((enrichment.type ?? "object") !== "object" || enrichment.association || !enrichment.relatedProperty)
16
+ continue;
17
+ const name = this.getCell(item, enrichment.destinationProperty);
18
+ if (!this.presentCell(name))
19
+ continue;
20
+ if (this.presentCell(this.keyOf(this.getCell(item, enrichment.sourceProperty))))
21
+ continue;
22
+ const via = entry.via ?? entry.persist;
23
+ if (!via)
24
+ continue;
25
+ const found = await via.readSingle(new Where(new Operator.Equals(enrichment.relatedProperty, name)));
26
+ if (!found)
27
+ throw new DatasourceError(`Unknown ${enrichment.destinationProperty} "${String(name)}"`);
28
+ const id = this.getCell(found, enrichment.relatedKey);
29
+ if (!this.presentCell(id))
30
+ throw new DatasourceError(`Unknown ${enrichment.destinationProperty} "${String(name)}"`);
31
+ this.stamp(item, enrichment.sourceProperty, id);
32
+ }
33
+ }
34
+ }
35
+ collectChildren(_writer, enrichment, parent) {
4
36
  const children = this.payloadOf(parent, enrichment.destinationProperty, enrichment);
5
37
  if (children.length === 0)
6
38
  return children;
@@ -9,10 +41,16 @@ export class CreateEnrichmentStrategy {
9
41
  this.stampCell(child, enrichment.relatedKey, key);
10
42
  return children;
11
43
  }
44
+ keyOf = (value) => {
45
+ if (value === undefined || value === null || typeof value !== "object")
46
+ return value;
47
+ const id = value.Id ?? value.id;
48
+ return id === undefined || typeof id === "object" ? undefined : id;
49
+ };
12
50
  getCell = (item, name) => item[name];
13
51
  presentCell = (value) => value !== undefined && value !== null && typeof value !== "object" && value !== 0 && value !== 0n && value !== "";
14
52
  payloadOf = (parent, into, enrichment) => mapPayload(this.getCell(parent, into), enrichment);
15
- stampCell = (related, by, key) => {
53
+ stamp = (related, by, key) => {
16
54
  if (key === undefined || key === null || typeof key === "object")
17
55
  return;
18
56
  const current = this.getCell(related, by);
@@ -22,8 +60,15 @@ export class CreateEnrichmentStrategy {
22
60
  target[id] = key;
23
61
  return;
24
62
  }
25
- Object.assign(related, { [by]: key });
63
+ if (by in related) {
64
+ Object.assign(related, { [by]: key });
65
+ return;
66
+ }
67
+ const extras = lookups.get(related) ?? {};
68
+ extras[by] = key;
69
+ lookups.set(related, extras);
26
70
  };
71
+ stampCell = (related, by, key) => this.stamp(related, by, key);
27
72
  relatedIds = (parent, enrichment) => {
28
73
  const ids = [];
29
74
  const seen = new Set();
@@ -11,6 +11,7 @@ export declare class DefaultWriteEnrichmentStrategy<T> implements IWriteEnrichme
11
11
  private readonly deleteStrategy;
12
12
  private readonly updateStrategy;
13
13
  constructor(deleteStrategy?: DeleteEnrichmentStrategy<T>, updateStrategy?: UpdateEnrichmentStrategy<T>, createStrategy?: CreateEnrichmentStrategy<T>, useTransaction?: boolean);
14
+ resolveLookups: (tree: IRepositoryTree, items: T[]) => Promise<void>;
14
15
  persist: (tree: IRepositoryTree, items: T[], mode: WriteMode) => Promise<void>;
15
16
  persistBulk: (tree: IRepositoryTree, items: T[], mode: WriteMode) => Promise<void>;
16
17
  private walk;
@@ -8,14 +8,15 @@ const unloaded = (parent, into) => {
8
8
  const value = parent[into];
9
9
  return value === undefined || value === null;
10
10
  };
11
+ const asRelated = (writer) => writer;
11
12
  const childWriter = (entry, into) => {
12
13
  if (entry.persist)
13
- return entry.persist;
14
+ return asRelated(entry.persist);
14
15
  throw new DatasourceError(`Deep write requires a CRUD repository for "${into}"`);
15
16
  };
16
17
  const linkWriter = (entry, into) => {
17
18
  if (entry.link)
18
- return entry.link;
19
+ return asRelated(entry.link);
19
20
  throw new DatasourceError(`Deep write requires a CRUD repository for "${into}"`);
20
21
  };
21
22
  const dispatch = (mode, runs) => (runs[mode] ??
@@ -34,6 +35,11 @@ export class DefaultWriteEnrichmentStrategy {
34
35
  this.createStrategy = createStrategy;
35
36
  this.useTransaction = useTransaction;
36
37
  }
38
+ resolveLookups = async (tree, items) => {
39
+ const entries = enrichmentsOf(tree.parent.value);
40
+ if (entries)
41
+ await this.createStrategy.resolveLookups(entries, items);
42
+ };
37
43
  persist = async (tree, items, mode) => {
38
44
  await this.walk(tree.parent.value, items, mode);
39
45
  };
@@ -1,5 +1,5 @@
1
1
  import type { IDriver } from "./IDriver.js";
2
2
  import type { ProviderCatalog } from "./ProviderCatalog.js";
3
3
  export declare class DriverLoader {
4
- static load: (catalog: ProviderCatalog) => IDriver;
4
+ static load(catalog: ProviderCatalog): IDriver;
5
5
  }
@@ -1,118 +1,15 @@
1
- import { readFileSync } from "node:fs";
2
- import { createRequire, registerHooks } from "node:module";
3
- import { tmpdir } from "node:os";
4
- import { dirname, join } from "node:path";
5
- import { fileURLToPath, pathToFileURL } from "node:url";
1
+ import { createRequire } from "node:module";
2
+ import { join } from "node:path";
6
3
  import { ProviderNotInstalledError } from "./ProviderNotInstalledError.js";
7
- const require = createRequire(import.meta.url);
8
- const requirers = () => [
9
- createRequire(join(process.cwd(), "package.json")),
10
- ...(process.argv[1] ? [createRequire(process.argv[1])] : []),
11
- require,
12
- ];
13
- const missingModule = (error) => error instanceof Error &&
14
- "code" in error &&
15
- (error.code === "MODULE_NOT_FOUND" ||
16
- error.code === "ERR_MODULE_NOT_FOUND" ||
17
- error.code === "ERR_PACKAGE_PATH_NOT_EXPORTED");
18
- const virtual = new Map();
19
- const virtualBySource = new Map();
20
- let typeScriptHook = false;
21
- let compiling = false;
22
- let compilingRoot = "";
23
- let nextVirtual = 0;
24
- /* v8 ignore start */
25
- const underRoot = (url) => {
26
- if (!url.startsWith("file:"))
27
- return false;
28
- const path = fileURLToPath(url);
29
- return path === compilingRoot || path.startsWith(`${compilingRoot}/`);
30
- };
31
- /* v8 ignore stop */
32
- const installTypeScriptHook = () => {
33
- if (typeScriptHook)
34
- return;
35
- const typescript = require("typescript");
36
- /* v8 ignore start */
37
- registerHooks({
38
- resolve: (specifier, context, nextResolve) => {
39
- const parentURL = context.parentURL ? virtual.get(context.parentURL) ?? context.parentURL : undefined;
40
- const resolved = nextResolve(specifier, parentURL ? { ...context, parentURL } : context);
41
- /* v8 ignore next */
42
- if (!compiling || !resolved.url.endsWith(".ts") || !underRoot(resolved.url))
43
- return resolved;
44
- const existing = virtualBySource.get(resolved.url);
45
- /* v8 ignore next */
46
- if (existing)
47
- return { url: existing, shortCircuit: true };
48
- const url = pathToFileURL(`${tmpdir()}/datasources-plugin-${nextVirtual++}.mjs`).href;
49
- virtual.set(url, resolved.url);
50
- virtualBySource.set(resolved.url, url);
51
- return { url, shortCircuit: true };
52
- },
53
- load: (url, context, nextLoad) => {
54
- const original = virtual.get(url) ?? (compiling && url.endsWith(".ts") && underRoot(url) ? url : undefined);
55
- /* v8 ignore next */
56
- if (!original)
57
- return nextLoad(url, context);
58
- const filename = fileURLToPath(original);
59
- const source = typescript.transpileModule(readFileSync(filename, "utf8"), {
60
- compilerOptions: {
61
- module: typescript.ModuleKind.ESNext,
62
- target: typescript.ScriptTarget.ES2022,
63
- },
64
- fileName: filename,
65
- }).outputText;
66
- return { format: "module", source, shortCircuit: true };
67
- },
68
- });
69
- /* v8 ignore stop */
70
- typeScriptHook = true;
71
- };
72
- const instantiate = (catalog) => {
73
- compiling = true;
74
- try {
75
- return instantiateLoaded(catalog);
76
- }
77
- finally {
78
- compiling = false;
79
- compilingRoot = "";
80
- }
81
- };
82
- const instantiateLoaded = (catalog) => {
83
- let last;
84
- for (const req of requirers()) {
4
+ export class DriverLoader {
5
+ static load(catalog) {
85
6
  try {
86
- compilingRoot = dirname(req.resolve(catalog.location));
87
- const loaded = req(catalog.location);
7
+ const loaded = createRequire(join(process.cwd(), "package.json"))(catalog.location);
88
8
  const Type = loaded[catalog.module];
89
- if (typeof Type !== "function")
90
- throw new ProviderNotInstalledError(catalog.name);
91
- return new Type();
9
+ return typeof Type === "function" ? new Type() : (() => { throw new ProviderNotInstalledError(catalog.name); })();
92
10
  }
93
11
  catch (error) {
94
- last = error;
95
- if (error instanceof ProviderNotInstalledError || !missingModule(error))
96
- throw error;
12
+ throw error instanceof ProviderNotInstalledError ? error : new ProviderNotInstalledError(catalog.name);
97
13
  }
98
14
  }
99
- throw last instanceof ProviderNotInstalledError ? last : new ProviderNotInstalledError(catalog.name);
100
- };
101
- export class DriverLoader {
102
- static load = (catalog) => {
103
- try {
104
- return instantiate(catalog);
105
- }
106
- catch (error) {
107
- if (error instanceof ProviderNotInstalledError)
108
- throw error;
109
- try {
110
- installTypeScriptHook();
111
- return instantiate(catalog);
112
- }
113
- catch {
114
- throw new ProviderNotInstalledError(catalog.name);
115
- }
116
- }
117
- };
118
15
  }
@@ -4,4 +4,5 @@ export interface IWriteEnrichmentStrategy<T> {
4
4
  readonly useTransaction: boolean;
5
5
  persist(tree: IRepositoryTree, items: T[], mode: WriteMode): Promise<void>;
6
6
  persistBulk?(tree: IRepositoryTree, items: T[], mode: WriteMode): Promise<void>;
7
+ resolveLookups?(tree: IRepositoryTree, items: T[]): Promise<void>;
7
8
  }
@@ -0,0 +1,6 @@
1
+ import type { NestedSelectBuilderOptions } from "./NestedSelectBuilderOptions.js";
2
+ import type { NestedSelectStatement } from "./NestedSelectStatement.js";
3
+ import type { NestedSelectType } from "./NestedSelectType.js";
4
+ export declare class NestedSelectBuilder {
5
+ build(root: NestedSelectType, options?: NestedSelectBuilderOptions): NestedSelectStatement;
6
+ }
@@ -0,0 +1,63 @@
1
+ import { selectFieldAliases } from "./SelectFieldAliases.js";
2
+ import { Join } from "./statements/Join.js";
3
+ import { SelectStatement } from "./statements/SelectStatement.js";
4
+ export class NestedSelectBuilder {
5
+ build(root, options) {
6
+ const stopAt = options?.stopAt ?? [];
7
+ const fields = [];
8
+ const joins = [];
9
+ walk(root, root.name, root.name, "", stopAt, false, fields, joins);
10
+ const aliases = selectFieldAliases(root.name, fields);
11
+ const byPath = Object.fromEntries(fields.map((field, i) => [field, aliases[i]]));
12
+ return {
13
+ select: new SelectStatement({
14
+ table: root.name,
15
+ fields,
16
+ joins,
17
+ where: options?.where,
18
+ }),
19
+ map: mapOf(root, root.name, "", stopAt, byPath),
20
+ };
21
+ }
22
+ }
23
+ const walk = (type, fieldPrefix, joinEntity, stopPrefix, stopAt, outer, fields, joins) => {
24
+ for (const property of type.properties)
25
+ fields.push(`${fieldPrefix}.${property}`);
26
+ for (const relation of type.relations) {
27
+ const path = stopPrefix.length === 0 ? relation.name : `${stopPrefix}.${relation.name}`;
28
+ if (stopped(stopAt, path))
29
+ continue;
30
+ const list = relation.kind === "list";
31
+ joins.push(list || outer
32
+ ? Join.left(relation.target.name, `${joinEntity}.${relation.from}`, `${relation.target.name}.${relation.to}`)
33
+ : Join.inner(relation.target.name, `${joinEntity}.${relation.from}`, `${relation.target.name}.${relation.to}`));
34
+ walk(relation.target, `${fieldPrefix}.${relation.target.name}`, relation.target.name, path, stopAt, outer || list, fields, joins);
35
+ }
36
+ };
37
+ const mapOf = (type, fieldPrefix, stopPrefix, stopAt, aliases) => ({
38
+ key: aliases[`${fieldPrefix}.${type.key}`],
39
+ fields: fieldsOf(type, fieldPrefix, stopPrefix, stopAt, aliases),
40
+ });
41
+ const fieldsOf = (type, fieldPrefix, stopPrefix, stopAt, aliases) => {
42
+ const fields = {};
43
+ for (const property of type.properties) {
44
+ const alias = aliases[`${fieldPrefix}.${property}`];
45
+ fields[alias] = alias;
46
+ }
47
+ for (const relation of type.relations) {
48
+ const path = stopPrefix.length === 0 ? relation.name : `${stopPrefix}.${relation.name}`;
49
+ if (stopped(stopAt, path))
50
+ continue;
51
+ const childPrefix = `${fieldPrefix}.${relation.target.name}`;
52
+ if (relation.kind === "list") {
53
+ fields[relation.name] = {
54
+ key: aliases[`${childPrefix}.${relation.target.key}`],
55
+ fields: fieldsOf(relation.target, childPrefix, path, stopAt, aliases),
56
+ };
57
+ continue;
58
+ }
59
+ Object.assign(fields, fieldsOf(relation.target, childPrefix, path, stopAt, aliases));
60
+ }
61
+ return fields;
62
+ };
63
+ const stopped = (stopAt, path) => stopAt.some((item) => item === path || path.startsWith(`${item}.`));
@@ -0,0 +1,5 @@
1
+ import type { IWhere } from "./statements/IWhere.js";
2
+ export type NestedSelectBuilderOptions = {
3
+ where?: IWhere;
4
+ stopAt?: readonly string[];
5
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,9 @@
1
+ import type { NestedSelectRelationKind } from "./NestedSelectRelationKind.js";
2
+ import type { NestedSelectType } from "./NestedSelectType.js";
3
+ export type NestedSelectRelation = {
4
+ name: string;
5
+ kind: NestedSelectRelationKind;
6
+ from: string;
7
+ to: string;
8
+ target: NestedSelectType;
9
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export type NestedSelectRelationKind = "object" | "list";
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,10 @@
1
+ import type { TypeMapGroup } from "./mappers/TypeMapGroup.js";
2
+ import type { SelectStatement } from "./statements/SelectStatement.js";
3
+ export type NestedSelectMapField = string | TypeMapGroup<Record<string, string>>;
4
+ export type NestedSelectStatement = {
5
+ select: SelectStatement;
6
+ map: {
7
+ key: string;
8
+ fields: Record<string, NestedSelectMapField>;
9
+ };
10
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,7 @@
1
+ import type { NestedSelectRelation } from "./NestedSelectRelation.js";
2
+ export type NestedSelectType = {
3
+ name: string;
4
+ key: string;
5
+ properties: readonly string[];
6
+ relations: readonly NestedSelectRelation[];
7
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -18,7 +18,7 @@ export type Sourced<T> = {
18
18
  item: T;
19
19
  source?: Row;
20
20
  };
21
- export declare const enrichmentsOf: (host: object) => readonly StoredEnrichment[] | undefined;
21
+ export declare const enrichmentsOf: <T>(host: object) => readonly StoredEnrichment<T>[] | undefined;
22
22
  export declare const isBuilt: <T>(value: IConnection | BuiltRepository<T>) => value is BuiltRepository<T>;
23
23
  export declare const builtOf: <T>(typeOrConnectionOrBuilt: Constructable<T> | IConnection | BuiltRepository<T>, connectionOrOptions?: IConnection | ReadonlyRepositoryMappingOptions<T>) => BuiltRepository<T> | undefined;
24
24
  export declare class ReadonlyRepository<T> implements IReadonlyRepository<T>, IEnriched<T> {
@@ -42,6 +42,7 @@ export declare class ReadonlyRepository<T> implements IReadonlyRepository<T>, IE
42
42
  readSingle(query?: ReadQuery, parameters?: Parameter[], options?: ReadonlyRepositoryMappingOptions<T>): Promise<T | undefined>;
43
43
  protected get enriched(): boolean;
44
44
  protected command: () => ICommand;
45
+ private ensureOpen;
45
46
  protected column: (property: string) => string;
46
47
  protected cell: (property: string, value: Cell, fieldsApplied: boolean, options?: ReadonlyRepositoryMappingOptions<T>) => Cell;
47
48
  private entries;
@@ -56,7 +56,7 @@ export class ReadonlyRepository {
56
56
  };
57
57
  addEnriched(enrichment, via, strategy) {
58
58
  const chosen = strategy ?? new DefaultReadonlyEnrichmentStrategy();
59
- this.entries().push(new StoredEnrichment(enrichment, (parents, sources) => chosen.apply(via, enrichment, parents, sources)));
59
+ this.entries().push(new StoredEnrichment(enrichment, (parents, sources) => chosen.apply(via, enrichment, parents, sources), undefined, undefined, via));
60
60
  return this;
61
61
  }
62
62
  async *read(query, parameters, options) {
@@ -72,6 +72,13 @@ export class ReadonlyRepository {
72
72
  return this.entries().length > 0;
73
73
  }
74
74
  command = () => this.middlewares.reduce((command, middleware) => command.addMiddleware(middleware), this.connection.createCommand());
75
+ ensureOpen = async () => {
76
+ const connection = this.connection;
77
+ if (!connection)
78
+ throw new DatasourceError("Connection required");
79
+ if (!connection.isOpen)
80
+ await connection.open();
81
+ };
75
82
  column = (property) => this.statementMappers.column(property);
76
83
  cell = (property, value, fieldsApplied, options) => {
77
84
  const objects = options?.objectConverter ?? this.objectConverter;
@@ -107,6 +114,7 @@ export class ReadonlyRepository {
107
114
  : objects.convert(row);
108
115
  async *readItems(query, parameters, mappers, rows, objects) {
109
116
  const statement = query ?? this.readonlyStatements.select();
117
+ await this.ensureOpen();
110
118
  const reader = await executeReader(this.command(), statement, parameters);
111
119
  try {
112
120
  while (await reader.next()) {
@@ -0,0 +1,8 @@
1
+ import type { CRUDRepositoryOptions } from "./CRUDRepositoryOptions.js";
2
+ export declare class RepositoryBuilderOptionsFactory<T = object> {
3
+ private fallback?;
4
+ private readonly items;
5
+ add(options: CRUDRepositoryOptions<T>): this;
6
+ add(dialect: string, options: CRUDRepositoryOptions<T>): this;
7
+ for: (provider: string) => CRUDRepositoryOptions<T>;
8
+ }
@@ -0,0 +1,12 @@
1
+ import { DatasourceError } from "./DatasourceError.js";
2
+ export class RepositoryBuilderOptionsFactory {
3
+ fallback;
4
+ items = new Map();
5
+ add(dialectOrOptions, options) {
6
+ typeof dialectOrOptions === "string"
7
+ ? this.items.set(dialectOrOptions, options)
8
+ : (this.fallback = dialectOrOptions);
9
+ return this;
10
+ }
11
+ for = (provider) => this.items.get(provider) ?? this.fallback ?? (() => { throw new DatasourceError("repository options required"); })();
12
+ }
@@ -0,0 +1,14 @@
1
+ export type PathParts = {
2
+ owner: string;
3
+ last: string;
4
+ root: boolean;
5
+ };
6
+ export declare const selectFieldAliases: (table: string, fields: readonly string[], aliases?: readonly (string | undefined)[]) => (string | undefined)[];
7
+ export declare const applySelectFieldAlias: (field: string, alias?: string) => string;
8
+ export declare const pathOf: (field: string, table: string) => PathParts | undefined;
9
+ export declare const functionOf: (field: string) => {
10
+ name: string;
11
+ inner: string;
12
+ } | undefined;
13
+ export declare const asIndexOf: (field: string) => number;
14
+ export declare const segments: (name: string) => string[];