bazis 0.136.0 → 0.138.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/README.md +7 -4
- package/cli/generateProject.ts +11 -18
- package/cli/main.ts +9 -1
- package/cli/parseCli.ts +9 -0
- package/cli/templates/module.ts +16 -19
- package/cli/templates/passport.ts +2 -2
- package/cli/upgrade/markColumns.ts +151 -0
- package/cli/upgrade/runUpgrade.ts +57 -0
- package/core/access/Access.module.ts +1 -1
- package/core/access/catalog/moduleFields.ts +41 -4
- package/core/access/catalog/permissions.ts +10 -0
- package/core/access/check/Access.ts +5 -5
- package/core/access/check/can.ts +2 -1
- package/core/access/index.ts +11 -1
- package/core/access/roles/roles.ts +4 -2
- package/core/access/testing/index.ts +26 -4
- package/core/agent/ToolRunner.ts +7 -1
- package/core/agent/defineTool.ts +109 -0
- package/core/agent/index.ts +8 -3
- package/core/agent/moduleContributions-v1.ts +3 -10
- package/core/agent/session/entities.ts +45 -45
- package/core/app/index.ts +1 -1
- package/core/app/messages.ts +3 -3
- package/core/app/runApp.ts +90 -26
- package/core/cache/ICache.ts +1 -1
- package/core/cache/README.md +4 -2
- package/core/cache/SPEC.md +16 -9
- package/core/cache/http/missingCacheModuleCheck.ts +5 -4
- package/core/cache/internal/buildCacheProviders.ts +4 -5
- package/core/cache/providers/cachedProviders.ts +1 -1
- package/core/di/index.ts +0 -4
- package/core/di/lowLevel.ts +12 -0
- package/core/di/module/index.ts +0 -3
- package/core/di/module/shortcuts.ts +31 -86
- package/core/generatedRuntime.ts +11 -0
- package/core/grpc/Grpc.module.ts +2 -1
- package/core/grpc/grpcClients.ts +2 -1
- package/core/http/Authorization/metadata.ts +1 -1
- package/core/http/Decorators/metadata.ts +3 -0
- package/core/http/Decorators/routes.ts +12 -0
- package/core/http/Errors/HttpError.ts +16 -2
- package/core/http/Http.config.ts +22 -0
- package/core/http/HttpContext/HttpContext.ts +1 -1
- package/core/http/HttpServer.ts +29 -9
- package/core/http/Middleware/errorHandler.ts +16 -1
- package/core/http/OpenApi/openApiDocument.ts +44 -5
- package/core/http/Results/normalize.ts +38 -1
- package/core/http/Results/results.ts +5 -2
- package/core/http/Routing/RouterBuilder.ts +27 -2
- package/core/http/httpModule.ts +10 -8
- package/core/http/index.ts +4 -0
- package/core/http/middlewareTokens.ts +12 -0
- package/core/http/options.ts +5 -1
- package/core/http-client/httpClients.ts +2 -1
- package/core/infra/Infra.ts +3 -3
- package/core/infra/connectors/codex/CodexAppServerClient.ts +1 -1
- package/core/infra/connectors/codex/contracts.ts +3 -3
- package/core/infra/connectors/llm.ts +2 -1
- package/core/infra/connectors/postgres.ts +4 -2
- package/core/infra/connectors/redis.ts +2 -1
- package/core/infra/index.ts +1 -1
- package/core/kernel/KernelBuilder.ts +2 -1
- package/core/kernel/config/ConfigRegistry.ts +2 -1
- package/core/kernel/config/addConfigOptions.ts +2 -1
- package/core/kernel/config/defineConfig.ts +56 -20
- package/core/kernel/events/EventBus.ts +1 -0
- package/core/kernel/events/eventSubscribers.ts +1 -1
- package/core/kernel/events/onEvent.ts +2 -1
- package/core/kernel/index.ts +3 -0
- package/core/orm/buildOrmProviders.ts +4 -2
- package/core/orm/contextEntities.ts +36 -0
- package/core/orm/databaseConnector.ts +9 -3
- package/core/orm/databaseHttpError.ts +21 -0
- package/core/orm/encryptedWith.ts +1 -1
- package/core/orm/index.ts +12 -1
- package/core/orm/ormModule.ts +19 -8
- package/core/orm/ownedStoreContributions.ts +2 -1
- package/core/orm/registerRepositories.ts +2 -1
- package/core/scripts/di-generate.ts +113 -12
- package/core/testing/index.ts +51 -11
- package/core/tools/StandardTools.module.ts +1 -1
- package/core/tools/contracts/Tools.config.ts +13 -7
- package/core/tools/generated/deps.ts +3 -3
- package/core/tools/http/HttpRequest.tool.ts +1 -2
- package/core/tools/http/publicAddress.ts +2 -2
- package/core/tools/index.ts +1 -1
- package/core/tools/orm/OrmEntity.references.ts +7 -8
- package/core/tools/orm/ormEntityTools.ts +54 -28
- package/core/websocket/DESIGN.md +5 -3
- package/core/websocket/middleware.ts +1 -1
- package/core/websocket/websocketModule.ts +3 -3
- package/core/workflow/contracts/Workflow.config.ts +5 -5
- package/core/workflow/definitions_module/WorkflowDefinitions.module.ts +3 -4
- package/core/workflow/definitions_module/code/defineProcess.ts +213 -11
- package/core/workflow/definitions_module/code/inspectProcesses.ts +12 -2
- package/core/workflow/definitions_module/http/Processes.controller.ts +2 -2
- package/core/workflow/definitions_module/internal/PublishedVersions.ts +39 -0
- package/core/workflow/definitions_module/model/WfProcess.model.ts +24 -11
- package/core/workflow/definitions_module/model/WfVersion.model.ts +7 -1
- package/core/workflow/definitions_module/services/IWorkflowDefinitions.service.ts +4 -1
- package/core/workflow/definitions_module/services/WorkflowDefinitions.service.ts +17 -7
- package/core/workflow/generated/deps.ts +2 -1
- package/core/workflow/index.ts +23 -1
- package/core/workflow/internal/WorkflowLanguages.ts +1 -1
- package/core/workflow/internal/http.ts +2 -2
- package/core/workflow/runtime_module/background/WorkflowWorker.ts +6 -2
- package/core/workflow/runtime_module/http/Instances.controller.ts +1 -1
- package/core/workflow/runtime_module/internal/WorkflowJobs.ts +43 -1
- package/core/workflow/runtime_module/model/WfHistory.model.ts +13 -6
- package/core/workflow/runtime_module/model/WfInstance.model.ts +30 -10
- package/core/workflow/runtime_module/model/WfJob.model.ts +22 -10
- package/core/workflow/runtime_module/model/WfSubscription.model.ts +8 -3
- package/core/workflow/runtime_module/model/WfToken.model.ts +16 -7
- package/core/workflow/runtime_module/services/IWorkflowRuntime.service.ts +10 -6
- package/core/workflow/runtime_module/services/WorkflowRuntime.service.ts +5 -2
- package/core/workflow/tasks_module/model/WfTask.model.ts +25 -9
- package/core/workflow/testing/index.ts +46 -4
- package/index.ts +4 -3
- package/library/openapi/codegen.ts +2 -2
- package/library/orm/DbContext.ts +37 -1
- package/library/orm/Metadata/ModelBuilder.ts +23 -7
- package/library/orm/Metadata/decorators.ts +23 -10
- package/library/orm/Metadata/types.ts +2 -0
- package/library/orm/Transactions/TransactionScopeCoordinator.ts +3 -3
- package/library/orm/errors.ts +39 -1
- package/library/orm/index.ts +1 -0
- package/library/terminal/messages.ts +6 -6
- package/library/validation/README.md +1 -1
- package/library/validation/SPEC.md +1 -1
- package/library/workflow/index.ts +1 -0
- package/library/workflow/nodeConfigs.ts +130 -0
- package/package.json +2 -1
|
@@ -30,7 +30,9 @@ export interface PostgresConfigShape {
|
|
|
30
30
|
readonly maxLifetime?: number;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
/** The TLS modes of a PostgreSQL connection, for `configEnum(POSTGRES_SSL_MODES, "disable")`. */
|
|
34
|
+
export const POSTGRES_SSL_MODES = ["disable", "allow", "prefer", "require", "verify-ca", "verify-full"] as const;
|
|
35
|
+
export type PostgresSslMode = typeof POSTGRES_SSL_MODES[number];
|
|
34
36
|
export interface PostgresConnectorOptions {
|
|
35
37
|
readonly token?: InjectionToken<SQL>;
|
|
36
38
|
/** Maximum pool drain before closing connections. Default 1000ms; 0 closes immediately. */
|
|
@@ -47,7 +49,7 @@ export function postgresConnectionOptions(config: AppConfig<PostgresConfigShape>
|
|
|
47
49
|
return value;
|
|
48
50
|
};
|
|
49
51
|
const tls = c.has("tls") ? c.get("tls") : undefined;
|
|
50
|
-
if (tls !== undefined && !
|
|
52
|
+
if (tls !== undefined && !(POSTGRES_SSL_MODES as readonly string[]).includes(String(tls))) throw new InfraError("postgres tls is invalid");
|
|
51
53
|
return {
|
|
52
54
|
hostname: requireValue(c.get("host"), "host", "postgres"),
|
|
53
55
|
port: integer("port", 1, 65535),
|
|
@@ -7,7 +7,8 @@ import {
|
|
|
7
7
|
connectionCache,
|
|
8
8
|
type DistributedCacheStores,
|
|
9
9
|
} from "../../cache";
|
|
10
|
-
import { createToken,
|
|
10
|
+
import { createToken, type InjectionToken, type ProviderDefinition } from "../../di";
|
|
11
|
+
import { DI } from "../../di/lowLevel";
|
|
11
12
|
import type { AppConfig, Secret } from "../../kernel";
|
|
12
13
|
import { RedisDistributedCacheBackend, type RedisDistributedCacheTuning } from "../cache/RedisDistributedCacheBackend";
|
|
13
14
|
import { reader, requireValue, settingNames } from "../connectorConfig";
|
package/core/infra/index.ts
CHANGED
|
@@ -10,7 +10,7 @@ export { reader, requireValue, settingNames, type ConfigReader } from "./connect
|
|
|
10
10
|
export { codexAppServerConnect, ICodex } from "./connectors/codex";
|
|
11
11
|
export { CodexError, RU_CODEX_MESSAGES, type CodexErrorCode, CODEX_MODEL_ID, CODEX_REASONING_EFFORT, type CodexClient, type CodexConfigShape, type CodexLogin, type CodexLoginMethod, type CodexModel, type CodexStatus, type CodexRunInput, type CodexChatMessage, type CodexTool, type CodexToolCall, type CodexToolResult } from "./connectors/codex/contracts";
|
|
12
12
|
|
|
13
|
-
export { postgres, IPostgres, postgresConnectionOptions, type PostgresConfigShape, type PostgresSslMode, type PostgresConnectorOptions } from "./connectors/postgres";
|
|
13
|
+
export { postgres, IPostgres, POSTGRES_SSL_MODES, postgresConnectionOptions, type PostgresConfigShape, type PostgresSslMode, type PostgresConnectorOptions } from "./connectors/postgres";
|
|
14
14
|
export {
|
|
15
15
|
redisConnect,
|
|
16
16
|
IRedis,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Global, Module, collectModuleConfigs, createContainer, type BazisModule, type BazisModuleRef, type ProviderDefinition } from "../di";
|
|
2
|
+
import { DI } from "../di/lowLevel";
|
|
2
3
|
import { isBazisModuleClass } from "../di/module/Module";
|
|
3
4
|
import { ApplicationLifetime } from "./ApplicationLifetime";
|
|
4
5
|
import { Configuration, loadConfiguration } from "./config/Configuration";
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { type ProviderDefinition } from "../../di";
|
|
2
|
+
import { DI } from "../../di/lowLevel";
|
|
2
3
|
import { KernelError } from "../errors";
|
|
3
4
|
import type { EnvironmentName } from "../types";
|
|
4
5
|
import type { Configuration } from "./Configuration";
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { InjectionToken, Options, ProviderDefinition, ServiceCollection, ServiceResolver } from "../../di";
|
|
2
|
-
import {
|
|
2
|
+
import { OPTIONS_STARTUP_VALIDATOR, OptionsValidationError } from "../../di";
|
|
3
|
+
import { DI } from "../../di/lowLevel";
|
|
3
4
|
import { ConfigKeyMissingError } from "../errors";
|
|
4
5
|
import { Configuration } from "./Configuration";
|
|
5
6
|
|
|
@@ -15,7 +15,7 @@ registerNamedDependencyHint((name) => {
|
|
|
15
15
|
const match = /^Config:(.+)$/.exec(name);
|
|
16
16
|
if (!match) return undefined;
|
|
17
17
|
const declaration = match[1] === "default" ? "defineConfig({ … })" : `defineConfig("${match[1]}", …)`;
|
|
18
|
-
return `The config ${declaration} is not declared in the application: add it to \`config\` of the module that uses it, or to
|
|
18
|
+
return `The config ${declaration} is not declared in the application: add it to \`config\` of the module that uses it, or to @App({ config }).`;
|
|
19
19
|
});
|
|
20
20
|
|
|
21
21
|
type Stand = "development" | "test" | "production";
|
|
@@ -38,14 +38,23 @@ export function configEnum<const V extends readonly [string | number, ...(string
|
|
|
38
38
|
return declared;
|
|
39
39
|
}
|
|
40
40
|
const invalidEnums = new WeakMap<object, KernelError>();
|
|
41
|
-
|
|
41
|
+
export interface ConfigList { readonly __list: true; readonly default: readonly string[] }
|
|
42
|
+
/**
|
|
43
|
+
* A list of strings: `readonly string[]` in the code, comma-separated in the
|
|
44
|
+
* environment (`BAZIS_TOOLS__HTTP__ALLOW=api.example.com,*.example.org`). Items
|
|
45
|
+
* are trimmed, empty ones are dropped.
|
|
46
|
+
*/
|
|
47
|
+
export function configList(defaultValue: readonly string[] = []): ConfigList {
|
|
48
|
+
return Object.freeze({ __list: true, default: Object.freeze([...defaultValue]) });
|
|
49
|
+
}
|
|
50
|
+
type Cell = Primitive | SecretSpec | ConfigEnum | ConfigList;
|
|
42
51
|
type Defaults = Record<string, Cell>;
|
|
43
|
-
export type ConfigValueType = Primitive | Secret;
|
|
52
|
+
export type ConfigValueType = Primitive | Secret | readonly string[];
|
|
44
53
|
export type ConfigShape = Record<string, ConfigValueType>;
|
|
45
|
-
type Resolved<V> = V extends SecretSpec ? Secret : V extends ConfigEnum<infer E> ? E : V extends number ? number : V extends boolean ? boolean : string;
|
|
54
|
+
type Resolved<V> = V extends SecretSpec ? Secret : V extends ConfigList ? readonly string[] : V extends ConfigEnum<infer E> ? E : V extends number ? number : V extends boolean ? boolean : string;
|
|
46
55
|
type ResolveAll<D extends Defaults> = { [K in keyof D]: Resolved<D[K]> };
|
|
47
|
-
type WidenCell<V> = V extends SecretSpec ? SecretSpec : V extends ConfigEnum<infer E> ? E : V extends number ? number : V extends boolean ? boolean : string;
|
|
48
|
-
type CellFor<T> = [T] extends [Secret] ? SecretSpec : [T] extends [string] ? (string extends T ? string : ConfigEnum<T & string>) : [T] extends [number] ? (number extends T ? number : ConfigEnum<T & number>) : [T] extends [boolean] ? (boolean extends T ? boolean : never) : never;
|
|
56
|
+
type WidenCell<V> = V extends SecretSpec ? SecretSpec : V extends ConfigList ? readonly string[] : V extends ConfigEnum<infer E> ? E : V extends number ? number : V extends boolean ? boolean : string;
|
|
57
|
+
type CellFor<T> = [T] extends [Secret] ? SecretSpec : [T] extends [readonly string[]] ? ConfigList : [T] extends [string] ? (string extends T ? string : ConfigEnum<T & string>) : [T] extends [number] ? (number extends T ? number : ConfigEnum<T & number>) : [T] extends [boolean] ? (boolean extends T ? boolean : never) : never;
|
|
49
58
|
type Declaration<T extends object> = { [K in keyof T]: CellFor<T[K]> };
|
|
50
59
|
type OverrideFor<T> = T extends Secret ? SecretSpec : T;
|
|
51
60
|
interface Rules<T extends object> {
|
|
@@ -76,18 +85,28 @@ export interface AppConfig<T extends object> extends ValidatableConfig {
|
|
|
76
85
|
}
|
|
77
86
|
export interface ConfigInspection {
|
|
78
87
|
readonly key: string;
|
|
79
|
-
readonly type: "string" | "number" | "boolean" | "secret";
|
|
88
|
+
readonly type: "string" | "number" | "boolean" | "secret" | "list";
|
|
80
89
|
readonly env: readonly string[];
|
|
81
90
|
readonly source: string;
|
|
82
91
|
readonly value: Primitive;
|
|
83
92
|
}
|
|
84
|
-
|
|
93
|
+
/** The settings of one section as a service reads them. */
|
|
94
|
+
export interface ResolvedConfigView<T extends object> extends AppConfig<T> {
|
|
85
95
|
readonly environment: Stand;
|
|
86
96
|
inspect(): readonly ConfigInspection[];
|
|
87
97
|
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
98
|
+
/**
|
|
99
|
+
* The settings a service takes in its constructor. `T` is the declaration
|
|
100
|
+
* itself, `ConfigView<typeof mailConfig>`, so the settings are described once,
|
|
101
|
+
* by their defaults; or the shape of the settings, `ConfigView<MailConfig>`, for a
|
|
102
|
+
* declaration with an explicit type.
|
|
103
|
+
*/
|
|
104
|
+
export type ConfigView<T extends object> = ResolvedConfigView<T extends ConfigDefinition<infer Shape, string> ? Shape : T>;
|
|
105
|
+
export interface ConfigDefinition<T extends object, P extends string = string> extends AppConfig<T> {
|
|
106
|
+
/** The name of the section: `mail`; `default` for a declaration without one. */
|
|
107
|
+
readonly prefix: P;
|
|
108
|
+
readonly token: InjectionToken<ResolvedConfigView<T>>;
|
|
109
|
+
resolve(environment?: Stand, configuration?: Configuration): ResolvedConfigView<T>;
|
|
91
110
|
}
|
|
92
111
|
const definitions = new WeakSet<object>();
|
|
93
112
|
export function isConfigDefinition(config: ValidatableConfig): config is ConfigDefinition<object> {
|
|
@@ -118,8 +137,11 @@ function environmentNameProblem(name: string, claimed: ReadonlySet<string>): str
|
|
|
118
137
|
return undefined;
|
|
119
138
|
}
|
|
120
139
|
|
|
121
|
-
function isSecret(cell:
|
|
122
|
-
function isEnum(cell:
|
|
140
|
+
function isSecret(cell: unknown): cell is SecretSpec { return typeof cell === "object" && cell !== null && "__secret" in cell; }
|
|
141
|
+
function isEnum(cell: unknown): cell is ConfigEnum { return typeof cell === "object" && cell !== null && "__enum" in cell; }
|
|
142
|
+
function isList(cell: unknown): cell is ConfigList { return typeof cell === "object" && cell !== null && "__list" in cell; }
|
|
143
|
+
/** `a, b,,c` → `["a", "b", "c"]`. */
|
|
144
|
+
function listItems(raw: string): readonly string[] { return Object.freeze(raw.split(",").map((item) => item.trim()).filter((item) => item !== "")); }
|
|
123
145
|
function copyCell(cell: Cell): Cell {
|
|
124
146
|
if (isSecret(cell)) return secret(cell.default);
|
|
125
147
|
if (isEnum(cell)) {
|
|
@@ -127,20 +149,27 @@ function copyCell(cell: Cell): Cell {
|
|
|
127
149
|
if (problem !== undefined) throw problem;
|
|
128
150
|
return Object.freeze({ ...cell, values: Object.freeze([...cell.values]) });
|
|
129
151
|
}
|
|
152
|
+
if (isList(cell)) {
|
|
153
|
+
if (!Array.isArray(cell.default) || cell.default.some((item) => typeof item !== "string")) throw new KernelError("configList requires a list of strings.");
|
|
154
|
+
return configList(cell.default);
|
|
155
|
+
}
|
|
156
|
+
// A list in an environment section is written as it is read: `test: { hosts: ["a"] }`.
|
|
157
|
+
if (Array.isArray(cell)) return copyCell(configList(cell as readonly string[]));
|
|
130
158
|
if (["string", "number", "boolean"].includes(typeof cell)) return cell;
|
|
131
|
-
throw new KernelError("Configuration supports only primitive values, secret() and
|
|
159
|
+
throw new KernelError("Configuration supports only primitive values, secret(), configEnum() and configList().");
|
|
132
160
|
}
|
|
133
161
|
function safeValue(key: string, value: ConfigValueType): Primitive {
|
|
134
|
-
if (value instanceof Secret || (typeof value === "string" && isSensitiveKey(key))) return "***";
|
|
135
|
-
if (
|
|
162
|
+
if (value instanceof Secret || ((typeof value === "string" || Array.isArray(value)) && isSensitiveKey(key))) return "***";
|
|
163
|
+
if (Array.isArray(value)) return value.join(", ");
|
|
164
|
+
if (typeof value !== "string") return value as Primitive;
|
|
136
165
|
try {
|
|
137
166
|
const url = new URL(value);
|
|
138
167
|
if (url.username || url.password) { url.username = "***"; url.password = "***"; return redactSensitiveText(url.toString()); }
|
|
139
168
|
} catch { /* Most strings are not URLs. */ }
|
|
140
169
|
return redactSensitiveText(value);
|
|
141
170
|
}
|
|
142
|
-
export function defineConfig<const D extends Defaults>(schema: ConfigSchema<D>): ConfigDefinition<ResolveAll<D
|
|
143
|
-
export function defineConfig<const D extends Defaults>(prefix:
|
|
171
|
+
export function defineConfig<const D extends Defaults>(schema: ConfigSchema<D>): ConfigDefinition<ResolveAll<D>, "default">;
|
|
172
|
+
export function defineConfig<const P extends string, const D extends Defaults>(prefix: P, schema: ConfigSchema<D>): ConfigDefinition<ResolveAll<D>, P>;
|
|
144
173
|
export function defineConfig<T extends object>(schema: SchemaFor<T>): ConfigDefinition<T>;
|
|
145
174
|
export function defineConfig<T extends object>(prefix: string, schema: SchemaFor<T>): ConfigDefinition<T>;
|
|
146
175
|
export function defineConfig(prefixOrSchema: string | ConfigSchema<Defaults>, maybeSchema?: ConfigSchema<Defaults>): ConfigDefinition<ConfigShape> {
|
|
@@ -157,6 +186,7 @@ export function defineConfig(prefixOrSchema: string | ConfigSchema<Defaults>, ma
|
|
|
157
186
|
// errors, instead of crashing the import of the file that declares it.
|
|
158
187
|
const fail = (): never => { throw error; };
|
|
159
188
|
definition = Object.freeze({
|
|
189
|
+
prefix: prefix || "default",
|
|
160
190
|
token: Object.freeze(createToken<ConfigView<ConfigShape>>(`Config:${prefix || "default"}`)),
|
|
161
191
|
resolve: fail,
|
|
162
192
|
get: fail,
|
|
@@ -209,7 +239,7 @@ function createDefinition(prefix: string, input: ConfigSchema<Defaults>, declare
|
|
|
209
239
|
const base = schema.default[key]!;
|
|
210
240
|
const override = schema[environment]?.[key];
|
|
211
241
|
const cell = override ?? base;
|
|
212
|
-
const type = isSecret(base) ? "secret" : isEnum(base) ? typeof base.default : typeof base;
|
|
242
|
+
const type = isSecret(base) ? "secret" : isList(base) ? "list" : isEnum(base) ? typeof base.default : typeof base;
|
|
213
243
|
const candidates: { raw: string; source: string; priority: number; name: string }[] = [];
|
|
214
244
|
for (const name of names.get(key)!) {
|
|
215
245
|
const sourceKey = name.slice(ENV_PREFIX.length).toLowerCase().replaceAll("__", ".");
|
|
@@ -226,7 +256,7 @@ function createDefinition(prefix: string, input: ConfigSchema<Defaults>, declare
|
|
|
226
256
|
const variables = [...new Set(winners.map(candidate => candidate.name))];
|
|
227
257
|
issue(key, "conflict", `conflicting environment aliases: ${variables.join(" and ")} are set to different values; set only one`);
|
|
228
258
|
}
|
|
229
|
-
const raw = selected?.raw ?? (isSecret(cell) || isEnum(cell) ? cell.default : cell);
|
|
259
|
+
const raw: unknown = selected?.raw ?? (isSecret(cell) || isEnum(cell) || isList(cell) ? cell.default : cell);
|
|
230
260
|
let value: ConfigValueType | undefined;
|
|
231
261
|
if (type === "secret") {
|
|
232
262
|
// secret("") reads like an empty default but would mean "required":
|
|
@@ -238,6 +268,11 @@ function createDefinition(prefix: string, input: ConfigSchema<Defaults>, declare
|
|
|
238
268
|
// No "apiKey: …" in the hint: the console redaction would hide it.
|
|
239
269
|
else if (environment === "production" && selected === undefined && override === undefined) issue(key, "development-secret", `production would use the development default secret from the code; set ${names.get(key)!.join(" or ")}, or declare ${key} in the production section`);
|
|
240
270
|
else value = new Secret(raw);
|
|
271
|
+
} else if (type === "list") {
|
|
272
|
+
// From the environment a comma-separated string, from the code a list.
|
|
273
|
+
if (typeof raw === "string") value = listItems(raw);
|
|
274
|
+
else if (Array.isArray(raw) && (raw as unknown[]).every((item) => typeof item === "string")) value = Object.freeze([...(raw as string[])]);
|
|
275
|
+
else issue(key, "string", "expected a list of strings");
|
|
241
276
|
} else if (type === "number") {
|
|
242
277
|
const parsed = Number(raw);
|
|
243
278
|
if (raw === undefined || (typeof raw === "string" && !raw.trim()) || !Number.isFinite(parsed)) issue(key, "number", `expected a finite number${got(key, raw)}${source(key, selected)}`, from(selected));
|
|
@@ -273,6 +308,7 @@ function createDefinition(prefix: string, input: ConfigSchema<Defaults>, declare
|
|
|
273
308
|
});
|
|
274
309
|
};
|
|
275
310
|
return Object.freeze({
|
|
311
|
+
prefix: prefix || "default",
|
|
276
312
|
token: Object.freeze(createToken<ConfigView<ConfigShape>>(`Config:${prefix || "default"}`)),
|
|
277
313
|
resolve,
|
|
278
314
|
get: <K extends keyof ConfigShape>(key: K): ConfigShape[K] => resolve().get(key),
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import {
|
|
2
|
-
DI,
|
|
3
2
|
Module,
|
|
4
3
|
singleton,
|
|
5
4
|
type Class,
|
|
@@ -7,6 +6,7 @@ import {
|
|
|
7
6
|
type ProviderDefinition,
|
|
8
7
|
type ServiceResolver,
|
|
9
8
|
} from "../../di";
|
|
9
|
+
import { DI } from "../../di/lowLevel";
|
|
10
10
|
import { EVENT_HANDLER, type EventSubscription } from "./EventBus";
|
|
11
11
|
import type { EventHandler, EventToken } from "./EventToken";
|
|
12
12
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { type ProviderDefinition, type ServiceCollection } from "../../di";
|
|
2
|
+
import { DI } from "../../di/lowLevel";
|
|
2
3
|
import { EVENT_HANDLER, type EventSubscription } from "./EventBus";
|
|
3
4
|
import type { EventHandler, EventToken } from "./EventToken";
|
|
4
5
|
|
package/core/kernel/index.ts
CHANGED
|
@@ -16,12 +16,15 @@ export { Secret } from "./config/Secret";
|
|
|
16
16
|
export {
|
|
17
17
|
defineConfig,
|
|
18
18
|
configEnum,
|
|
19
|
+
configList,
|
|
19
20
|
secret,
|
|
20
21
|
type AppConfig,
|
|
21
22
|
type ConfigDefinition,
|
|
22
23
|
type ConfigView,
|
|
24
|
+
type ResolvedConfigView,
|
|
23
25
|
type ConfigInspection,
|
|
24
26
|
type ConfigEnum,
|
|
27
|
+
type ConfigList,
|
|
25
28
|
type ConfigSchema,
|
|
26
29
|
type ConfigShape,
|
|
27
30
|
type ConfigValueType,
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { HOSTED_SERVICE, type InjectionToken, type ModuleOwnedContributionSnapshot, type ProviderDefinition, type ProviderDependencyList, type ServiceResolver } from "../di";
|
|
2
|
+
import { DI } from "../di/lowLevel";
|
|
3
|
+
import { contextEntities } from "./contextEntities";
|
|
2
4
|
import { getGeneratedClassDeps } from "../di/module/autoDeps";
|
|
3
5
|
import { resolveDependencyList } from "../di/internal/resolveDependencyList";
|
|
4
6
|
import { ConfigRegistry, HEALTH_CHECK, StartupReport } from "../kernel";
|
|
@@ -27,7 +29,7 @@ function createContextOptions<TContext extends DbContext>(
|
|
|
27
29
|
): DbContextOptions {
|
|
28
30
|
return bindKeys(new DbContextOptions({
|
|
29
31
|
provider,
|
|
30
|
-
entities: config
|
|
32
|
+
entities: contextEntities(config),
|
|
31
33
|
validateOnSave: config.validateOnSave,
|
|
32
34
|
executionStrategy: config.executionStrategy,
|
|
33
35
|
}), serviceProvider);
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { DbContext } from "../../library/orm";
|
|
2
|
+
|
|
3
|
+
type EntityClass = new () => object;
|
|
4
|
+
type ContextClass = abstract new (...parameters: never[]) => DbContext;
|
|
5
|
+
|
|
6
|
+
/** The sets of a context class are read once: a class does not change. */
|
|
7
|
+
const sets = new WeakMap<ContextClass, readonly EntityClass[] | Error>();
|
|
8
|
+
|
|
9
|
+
function setsOf(context: ContextClass): readonly EntityClass[] | Error {
|
|
10
|
+
let known = sets.get(context);
|
|
11
|
+
if (known === undefined) {
|
|
12
|
+
try {
|
|
13
|
+
known = DbContext.entitiesOf(context as unknown as Parameters<typeof DbContext.entitiesOf>[0]);
|
|
14
|
+
} catch (error) {
|
|
15
|
+
known = error instanceof Error ? error : new Error(String(error));
|
|
16
|
+
}
|
|
17
|
+
sets.set(context, known);
|
|
18
|
+
}
|
|
19
|
+
return known;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* The entities of a context: the listed ones in their order, then those of its
|
|
24
|
+
* sets (`this.set(...)`). A context whose constructor cannot run without its
|
|
25
|
+
* dependencies has only the listed ones, and with none listed that is an error.
|
|
26
|
+
*/
|
|
27
|
+
export function contextEntities(config: { readonly context?: ContextClass; readonly entities?: readonly EntityClass[] }): readonly EntityClass[] {
|
|
28
|
+
const listed = config.entities ?? [];
|
|
29
|
+
if (!config.context) return listed;
|
|
30
|
+
const ofSets = setsOf(config.context);
|
|
31
|
+
if (ofSets instanceof Error) {
|
|
32
|
+
if (listed.length === 0) throw ofSets;
|
|
33
|
+
return listed;
|
|
34
|
+
}
|
|
35
|
+
return ofSets.length === 0 ? listed : [...new Set([...listed, ...ofSets])];
|
|
36
|
+
}
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { errorMessage, InfraError, reader, requireValue, postgresConnectionOptions, settingNames, type InfraConnector, type PostgresConfigShape } from "../infra";
|
|
2
|
-
import { postgresAddress, serverVersion } from "../infra/connectors/postgres";
|
|
2
|
+
import { POSTGRES_SSL_MODES, postgresAddress, serverVersion, type PostgresSslMode } from "../infra/connectors/postgres";
|
|
3
3
|
import type { AppConfig, ConfigRegistry } from "../kernel";
|
|
4
4
|
import { postgres, type DatabaseProvider, type PostgresServerTimeouts } from "../../library/orm";
|
|
5
|
+
import { singletonValue } from "../di";
|
|
6
|
+
import { HTTP_ERROR_MAPPER } from "../http/middlewareTokens";
|
|
7
|
+
import { databaseHttpError } from "./databaseHttpError";
|
|
5
8
|
import { DATABASE_PROVIDER } from "./DATABASE_PROVIDER";
|
|
6
9
|
|
|
7
10
|
/** Connect before the servers and ORM features: open the database before anyone asks for it. */
|
|
@@ -13,7 +16,7 @@ export interface PostgresOrmConfigShape extends PostgresConfigShape {
|
|
|
13
16
|
readonly connectionTimeout?: number;
|
|
14
17
|
readonly idleTimeout?: number;
|
|
15
18
|
readonly maxLifetime?: number;
|
|
16
|
-
readonly tls?:
|
|
19
|
+
readonly tls?: PostgresSslMode;
|
|
17
20
|
readonly tlsCa?: string;
|
|
18
21
|
readonly operationTimeoutMs?: number;
|
|
19
22
|
readonly cancellationTimeoutMs?: number;
|
|
@@ -43,7 +46,7 @@ function buildPostgresProvider<T extends PostgresOrmConfigShape>(config: AppConf
|
|
|
43
46
|
// defineConfig keys are always present, so an empty tlsCa means "no additional CA".
|
|
44
47
|
const tls = optional("tls"), configuredCa = optional("tlsCa"), ca = configuredCa === "" ? undefined : configuredCa;
|
|
45
48
|
if (tls !== undefined) {
|
|
46
|
-
if (typeof tls !== "string" || !
|
|
49
|
+
if (typeof tls !== "string" || !(POSTGRES_SSL_MODES as readonly string[]).includes(tls)) throw new InfraError("Invalid PostgreSQL configuration field: tls.");
|
|
47
50
|
options.tls = tls;
|
|
48
51
|
}
|
|
49
52
|
if (ca !== undefined) {
|
|
@@ -96,6 +99,9 @@ export function ormBazisConnect(
|
|
|
96
99
|
token: DATABASE_PROVIDER,
|
|
97
100
|
config,
|
|
98
101
|
phase: DATABASE_PHASE,
|
|
102
|
+
// Database errors answer by their meaning (409, 404, 422, 503) without a setting in the application.
|
|
103
|
+
providers: [singletonValue(HTTP_ERROR_MAPPER, databaseHttpError)],
|
|
104
|
+
exports: [HTTP_ERROR_MAPPER],
|
|
99
105
|
create(configs) {
|
|
100
106
|
const c = reader(config, configs);
|
|
101
107
|
target = `PostgreSQL at ${String(c.get("host"))}:${String(c.get("port"))} (database "${String(c.get("database"))}", user "${String(c.get("username"))}")`;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { ConflictError, HttpError } from "../http/Errors/HttpError";
|
|
2
|
+
import { englishHttpMessage, STATUS_MESSAGE_KEYS } from "../http/Errors/messages";
|
|
3
|
+
import { UniqueViolationError } from "../../library/orm";
|
|
4
|
+
import { databaseErrorStatus } from "./databaseErrorStatus";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A database error as the HTTP answer for it: the statuses of
|
|
8
|
+
* {@link databaseErrorStatus}, and for a unique violation the text of
|
|
9
|
+
* `@Index({ conflict })` with the properties of the index in the details:
|
|
10
|
+
* `{ error, details: { code: "unique", fields: ["name"] } }`. The ORM registers
|
|
11
|
+
* it for every application with HTTP; the message never carries values of the row.
|
|
12
|
+
*/
|
|
13
|
+
export function databaseHttpError(error: unknown): HttpError | undefined {
|
|
14
|
+
if (error instanceof UniqueViolationError) {
|
|
15
|
+
return new ConflictError(error.conflict, { code: "unique", fields: [...error.properties] });
|
|
16
|
+
}
|
|
17
|
+
const status = databaseErrorStatus(error);
|
|
18
|
+
if (status === undefined) return undefined;
|
|
19
|
+
const key = STATUS_MESSAGE_KEYS[status] ?? (status >= 500 ? "serverError" : "clientError");
|
|
20
|
+
return new HttpError(status, englishHttpMessage(key), undefined, { key });
|
|
21
|
+
}
|
|
@@ -60,7 +60,7 @@ export function bindEncryptedColumns(options: DbContextOptions, registry: Config
|
|
|
60
60
|
try {
|
|
61
61
|
view = registry.get(binding.config) as { get(key: string): unknown };
|
|
62
62
|
} catch {
|
|
63
|
-
throw new OrmError(`${where}: the config "${prefixOf(binding)}" is not declared in the application; add it to \`config\` of the module that owns ${entity.name} or to
|
|
63
|
+
throw new OrmError(`${where}: the config "${prefixOf(binding)}" is not declared in the application; add it to \`config\` of the module that owns ${entity.name} or to @App({ config }).`);
|
|
64
64
|
}
|
|
65
65
|
const value = view.get(binding.field);
|
|
66
66
|
const secret = value instanceof Secret ? value.reveal() : value;
|
package/core/orm/index.ts
CHANGED
|
@@ -11,12 +11,14 @@ export { IRepository, repositoryFor } from "./repository";
|
|
|
11
11
|
export { registerRepositories } from "./registerRepositories";
|
|
12
12
|
export { paginate, type PageResult } from "./listQuery";
|
|
13
13
|
export { databaseErrorStatus } from "./databaseErrorStatus";
|
|
14
|
+
export { databaseHttpError } from "./databaseHttpError";
|
|
14
15
|
export { encryptedWith } from "./encryptedWith";
|
|
15
16
|
|
|
16
17
|
import { registerModuleMetadataExpander, type BazisModuleRef } from "../di";
|
|
17
18
|
import { registerModuleDescriber } from "../kernel/report/moduleTree";
|
|
18
19
|
import { OrmOwnedStoreAdmissionError, type DbContext } from "../../library/orm";
|
|
19
20
|
import { registerRepositoryEncapsulationHook } from "./encapsulationHook";
|
|
21
|
+
import { contextEntities } from "./contextEntities";
|
|
20
22
|
import { ormModule, type OrmModuleConfig } from "./ormModule";
|
|
21
23
|
import { readOwnedStoreRegistration, revalidateOwnedStoreRegistration } from "./ownedStoreContributions";
|
|
22
24
|
|
|
@@ -62,13 +64,22 @@ function declaredOwnedStore(config: OrmModuleConfig<DbContext>): unknown {
|
|
|
62
64
|
return descriptor.value;
|
|
63
65
|
}
|
|
64
66
|
|
|
67
|
+
/** For the startup screen only: a context whose sets cannot be read shows what is listed. */
|
|
68
|
+
function describedEntities(config: OrmModuleConfig<DbContext>): readonly unknown[] {
|
|
69
|
+
try {
|
|
70
|
+
return contextEntities(config);
|
|
71
|
+
} catch {
|
|
72
|
+
return config.entities ?? [];
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
65
76
|
// The module tree of the startup screen: `Task, Comment · TaskDbContext`.
|
|
66
77
|
registerModuleDescriber((metadata) => {
|
|
67
78
|
const orm = (metadata as { ormBazis?: OrmModuleConfig<DbContext> | readonly OrmModuleConfig<DbContext>[] }).ormBazis;
|
|
68
79
|
if (orm === undefined) return [];
|
|
69
80
|
return (Array.isArray(orm) ? orm : [orm]).map((config: OrmModuleConfig<DbContext>) => ({
|
|
70
81
|
kind: "table" as const,
|
|
71
|
-
text: `${(config
|
|
82
|
+
text: `${describedEntities(config).map((entity) => (entity as { name?: string }).name ?? "entity").join(", ") || "no entities"} · ${(config.context as { name?: string } | undefined)?.name ?? "DbContext"}`,
|
|
72
83
|
}));
|
|
73
84
|
});
|
|
74
85
|
|
package/core/orm/ormModule.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { HOSTED_SERVICE, singletonValue, type BazisModuleRef, type ProviderDefinition } from "../di";
|
|
2
|
+
import { DI } from "../di/lowLevel";
|
|
2
3
|
import {
|
|
3
4
|
OrmError,
|
|
4
5
|
type DatabaseProvider,
|
|
@@ -7,6 +8,9 @@ import {
|
|
|
7
8
|
type DbContextOptionsConfig,
|
|
8
9
|
type Migration,
|
|
9
10
|
} from "../../library/orm";
|
|
11
|
+
import { HTTP_ERROR_MAPPER } from "../http/middlewareTokens";
|
|
12
|
+
import { databaseHttpError } from "./databaseHttpError";
|
|
13
|
+
import { contextEntities } from "./contextEntities";
|
|
10
14
|
import { DATABASE_PROVIDER } from "./DATABASE_PROVIDER";
|
|
11
15
|
import { IRepository } from "./repository";
|
|
12
16
|
import { registerRepositories } from "./registerRepositories";
|
|
@@ -40,7 +44,12 @@ type ContextClass<TContext extends DbContext> = new (options: DbContextOptions,
|
|
|
40
44
|
export interface OrmModuleConfig<TContext extends DbContext> {
|
|
41
45
|
/** Context class (a DbContext subclass). Omitted in the "connection" mode. */
|
|
42
46
|
readonly context?: ContextClass<TContext>;
|
|
43
|
-
/**
|
|
47
|
+
/**
|
|
48
|
+
* Entities of the context besides those of its sets: by default the context
|
|
49
|
+
* maps what it declares with `this.set(...)`. List here only an entity without
|
|
50
|
+
* a set, or every entity of a context whose constructor cannot run without its
|
|
51
|
+
* dependencies.
|
|
52
|
+
*/
|
|
44
53
|
readonly entities?: readonly EntityClass[];
|
|
45
54
|
/**
|
|
46
55
|
* Database provider value (`postgres(...)`). Without it the context connects
|
|
@@ -86,7 +95,7 @@ function defineFeatureOrmModule<TContext extends DbContext>(
|
|
|
86
95
|
providers,
|
|
87
96
|
exports: withRepositories ? [config.context, IRepository] : [config.context],
|
|
88
97
|
configure: withRepositories
|
|
89
|
-
? (di) => registerRepositories(di, config.context, config
|
|
98
|
+
? (di) => registerRepositories(di, config.context, contextEntities(config))
|
|
90
99
|
: undefined,
|
|
91
100
|
};
|
|
92
101
|
}
|
|
@@ -94,6 +103,8 @@ function defineFeatureOrmModule<TContext extends DbContext>(
|
|
|
94
103
|
function defineConnectionModule(provider: DatabaseProvider, healthCheck: boolean): BazisModuleRef {
|
|
95
104
|
const providers: ProviderDefinition[] = [
|
|
96
105
|
singletonValue(DATABASE_PROVIDER, provider),
|
|
106
|
+
// Database errors answer by their meaning (409, 404, 422, 503) without a setting in the application.
|
|
107
|
+
singletonValue(HTTP_ERROR_MAPPER, databaseHttpError),
|
|
97
108
|
DI.singleton(
|
|
98
109
|
DI.factoryProvider(
|
|
99
110
|
HOSTED_SERVICE,
|
|
@@ -112,7 +123,7 @@ function defineConnectionModule(provider: DatabaseProvider, healthCheck: boolean
|
|
|
112
123
|
/** Single ORM registration entry point (see {@link OrmModuleConfig}). */
|
|
113
124
|
export function ormModule<TContext extends DbContext>(config: OrmModuleConfig<TContext>): BazisModuleRef {
|
|
114
125
|
if (config.ownedStore !== undefined) {
|
|
115
|
-
if (!config.context ||
|
|
126
|
+
if (!config.context || contextEntities(config).length === 0) {
|
|
116
127
|
throw new OrmError("ORM owned store requires a context and entities.");
|
|
117
128
|
}
|
|
118
129
|
if (config.provider) {
|
|
@@ -124,7 +135,7 @@ export function ormModule<TContext extends DbContext>(config: OrmModuleConfig<TC
|
|
|
124
135
|
// Provider factories outlive this call. Keep their context/entity view
|
|
125
136
|
// registration-local so a caller cannot mutate the descriptor afterwards
|
|
126
137
|
// and silently change the runtime graph behind its owned receipt.
|
|
127
|
-
const buildConfig = Object.freeze({ ...config, context: config.context, entities: Object.freeze([...config
|
|
138
|
+
const buildConfig = Object.freeze({ ...config, context: config.context, entities: Object.freeze([...contextEntities(config)]) }) as OrmBuildConfig<TContext>;
|
|
128
139
|
const module = defineFeatureOrmModule(buildConfig, buildOrmModuleProviders(buildConfig, DATABASE_PROVIDER));
|
|
129
140
|
attachOwnedStoreRegistration(module, buildConfig.context, buildConfig.entities ?? [], config.ownedStore);
|
|
130
141
|
return module;
|
|
@@ -148,7 +159,7 @@ export function ormModule<TContext extends DbContext>(config: OrmModuleConfig<TC
|
|
|
148
159
|
// may replace them with a container-local snapshot.
|
|
149
160
|
const standaloneOptions = new DbContextOptions({
|
|
150
161
|
provider: config.provider,
|
|
151
|
-
entities: config
|
|
162
|
+
entities: contextEntities(config),
|
|
152
163
|
validateOnSave: config.validateOnSave,
|
|
153
164
|
executionStrategy: config.executionStrategy,
|
|
154
165
|
});
|
|
@@ -158,7 +169,7 @@ export function ormModule<TContext extends DbContext>(config: OrmModuleConfig<TC
|
|
|
158
169
|
providers.push(buildOrmHealthCheck(config.provider));
|
|
159
170
|
}
|
|
160
171
|
const module = defineFeatureOrmModule(buildConfig, providers);
|
|
161
|
-
attachOrmGraphContribution(module, buildConfig.context, buildConfig
|
|
172
|
+
attachOrmGraphContribution(module, buildConfig.context, contextEntities(buildConfig), config, registration);
|
|
162
173
|
return module;
|
|
163
174
|
}
|
|
164
175
|
|
|
@@ -174,7 +185,7 @@ export function ormModule<TContext extends DbContext>(config: OrmModuleConfig<TC
|
|
|
174
185
|
const module = defineFeatureOrmModule(buildConfig, providers);
|
|
175
186
|
// `registration` is deliberately per ormModule call, not a token or public
|
|
176
187
|
// identity. It links this factory to its container-local graph snapshot.
|
|
177
|
-
attachOrmGraphContribution(module, buildConfig.context, buildConfig
|
|
188
|
+
attachOrmGraphContribution(module, buildConfig.context, contextEntities(buildConfig), config, registration);
|
|
178
189
|
return module;
|
|
179
190
|
}
|
|
180
191
|
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
type BazisModuleMetadata,
|
|
7
7
|
} from "../di";
|
|
8
8
|
import { OrmModel, type DbContext, type OrmOwnedStoreDefinitionV1 } from "../../library/orm";
|
|
9
|
+
import { contextEntities } from "./contextEntities";
|
|
9
10
|
import { compileExpectedSchema, type OrmExpectedSchema } from "../../library/orm/Schema/ExpectedSchema";
|
|
10
11
|
import { isDefinedOrmOwnedStoreV1 } from "../../library/orm/Schema/OrmOwnedStore";
|
|
11
12
|
import { defineOrmOwnedStoreV1 } from "../../library/orm/Schema/OrmOwnedStore";
|
|
@@ -159,7 +160,7 @@ export function ownedStoreRegistrations(snapshot: ModuleOwnedContributionSnapsho
|
|
|
159
160
|
// view. Direct ordinary modules intentionally read their caller config at
|
|
160
161
|
// build time, then both graph preflight and runtime factories use this copy.
|
|
161
162
|
const source = entry.config as { readonly entities?: readonly EntityClass[]; readonly migrations?: readonly unknown[] } | undefined;
|
|
162
|
-
const entities = source === undefined ? entry.entities : Object.freeze([...(source.entities
|
|
163
|
+
const entities = source === undefined ? entry.entities : Object.freeze([...contextEntities({ context: entry.context, entities: source.entities })]);
|
|
163
164
|
// Migration instances remain caller-owned; only the sequence membership and
|
|
164
165
|
// order are part of this container's effective config snapshot.
|
|
165
166
|
const migrations = source?.migrations;
|