@decohere/data-typeorm 0.0.1-dev.125 → 0.0.1-dev.5

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 (75) hide show
  1. package/package.json +8 -24
  2. package/src/bootstrap-policy.ts +84 -0
  3. package/src/bootstrap.ts +76 -0
  4. package/src/data-source.factory.ts +51 -0
  5. package/src/database-options.ts +10 -0
  6. package/src/dialect-map.ts +12 -0
  7. package/{es6/src/inject-data-source.js → src/inject-data-source.ts} +15 -9
  8. package/src/scripts/user.ts +234 -0
  9. package/src/server-manager.ts +300 -0
  10. package/src/sql-pretty.transport.ts +29 -0
  11. package/src/type-orm.module.ts +155 -0
  12. package/src/type-orm.provider.ts +37 -0
  13. package/src/type-orm.repository.ts +33 -0
  14. package/src/typeorm-scribe.logger.ts +55 -0
  15. package/src/typeorm.extensions.ts +42 -0
  16. package/src/unit-of-work.ts +52 -0
  17. package/tests/bootstrap-schemas.docker.spec.ts +145 -0
  18. package/tests/bootstrap.spec.ts +156 -0
  19. package/tests/db-least-privilege.pentest.spec.ts +125 -0
  20. package/tests/postgres-container.ts +139 -0
  21. package/tests/prepare-database.docker.spec.ts +96 -0
  22. package/tests/server-manager.bootstrap.spec.ts +72 -0
  23. package/tests/trusted-dev-postgres.spec.ts +212 -0
  24. package/tests/type-orm.module.spec.ts +123 -0
  25. package/tests/typeorm.lifecycle.spec.ts +97 -0
  26. package/tests/unit-of-work.spec.ts +54 -0
  27. package/tests/user-scripts.pentest.spec.ts +42 -0
  28. package/LICENSE +0 -15
  29. package/commonjs/src/bootstrap-policy.js +0 -69
  30. package/commonjs/src/bootstrap.js +0 -142
  31. package/commonjs/src/data-source.factory.js +0 -42
  32. package/commonjs/src/database-options.js +0 -4
  33. package/commonjs/src/dialect-map.js +0 -26
  34. package/commonjs/src/index.js +0 -24
  35. package/commonjs/src/inject-data-source.js +0 -33
  36. package/commonjs/src/scripts/user.js +0 -259
  37. package/commonjs/src/server-manager.js +0 -294
  38. package/commonjs/src/sql-pretty.transport.js +0 -35
  39. package/commonjs/src/type-orm.module.js +0 -115
  40. package/commonjs/src/type-orm.provider.js +0 -36
  41. package/commonjs/src/type-orm.repository.js +0 -30
  42. package/commonjs/src/typeorm-scribe.logger.js +0 -70
  43. package/commonjs/src/typeorm.extensions.js +0 -60
  44. package/commonjs/src/unit-of-work.js +0 -49
  45. package/es6/src/bootstrap-policy.js +0 -55
  46. package/es6/src/bootstrap.js +0 -139
  47. package/es6/src/data-source.factory.js +0 -27
  48. package/es6/src/database-options.js +0 -1
  49. package/es6/src/dialect-map.js +0 -8
  50. package/es6/src/scripts/user.js +0 -209
  51. package/es6/src/server-manager.js +0 -284
  52. package/es6/src/sql-pretty.transport.js +0 -20
  53. package/es6/src/type-orm.module.js +0 -105
  54. package/es6/src/type-orm.provider.js +0 -26
  55. package/es6/src/type-orm.repository.js +0 -20
  56. package/es6/src/typeorm-scribe.logger.js +0 -60
  57. package/es6/src/typeorm.extensions.js +0 -50
  58. package/es6/src/unit-of-work.js +0 -39
  59. package/types/src/bootstrap-policy.d.ts +0 -19
  60. package/types/src/bootstrap.d.ts +0 -16
  61. package/types/src/data-source.factory.d.ts +0 -17
  62. package/types/src/database-options.d.ts +0 -9
  63. package/types/src/dialect-map.d.ts +0 -8
  64. package/types/src/index.d.ts +0 -7
  65. package/types/src/inject-data-source.d.ts +0 -14
  66. package/types/src/scripts/user.d.ts +0 -56
  67. package/types/src/server-manager.d.ts +0 -39
  68. package/types/src/sql-pretty.transport.d.ts +0 -2
  69. package/types/src/type-orm.module.d.ts +0 -17
  70. package/types/src/type-orm.provider.d.ts +0 -14
  71. package/types/src/type-orm.repository.d.ts +0 -12
  72. package/types/src/typeorm-scribe.logger.d.ts +0 -9
  73. package/types/src/typeorm.extensions.d.ts +0 -11
  74. package/types/src/unit-of-work.d.ts +0 -20
  75. /package/{es6/src/index.js → src/index.ts} +0 -0
@@ -1,284 +0,0 @@
1
- function _ts_add_disposable_resource(env, value, async) {
2
- if (value !== null && value !== void 0) {
3
- if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
4
- var dispose, inner;
5
- if (async) {
6
- if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
7
- dispose = value[Symbol.asyncDispose];
8
- }
9
- if (dispose === void 0) {
10
- if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
11
- dispose = value[Symbol.dispose];
12
- if (async) inner = dispose;
13
- }
14
- if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
15
- if (inner) dispose = function() {
16
- try {
17
- inner.call(this);
18
- } catch (e) {
19
- return Promise.reject(e);
20
- }
21
- };
22
- env.stack.push({
23
- value: value,
24
- dispose: dispose,
25
- async: async
26
- });
27
- } else if (async) {
28
- env.stack.push({
29
- async: true
30
- });
31
- }
32
- return value;
33
- }
34
- function _ts_dispose_resources(env) {
35
- var _SuppressedError = typeof SuppressedError === "function" ? SuppressedError : function(error, suppressed, message) {
36
- var e = new Error(message);
37
- return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
38
- };
39
- return (_ts_dispose_resources = function _ts_dispose_resources(env) {
40
- function fail(e) {
41
- env.error = env.hasError ? new _SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
42
- env.hasError = true;
43
- }
44
- var r, s = 0;
45
- function next() {
46
- while(r = env.stack.pop()){
47
- try {
48
- if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
49
- if (r.dispose) {
50
- var result = r.dispose.call(r.value);
51
- if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) {
52
- fail(e);
53
- return next();
54
- });
55
- } else s |= 1;
56
- } catch (e) {
57
- fail(e);
58
- }
59
- }
60
- if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
61
- if (env.hasError) throw env.error;
62
- }
63
- return next();
64
- })(env);
65
- }
66
- import { DataConfigurationError } from "@decohere/data";
67
- import { Pool } from "pg";
68
- import { alterDatabaseOwnerQuery, createSchemaQuery, createUserQuery, databaseOwnerQuery, defaultPrivilegesForAppQueries, grantConnectQuery, grantRoleMembershipQuery, grantSchemaCreateQuery, grantSchemaPrivilegesQuery, grantSchemaUsageQuery, hasDatabasePrivilegeQuery, hasRolePrivilegesQuery, hasSchemaPrivilegeQuery, quoteIdent, revokeRoleMembershipQuery, roleExistsQuery } from "./scripts/user.js";
69
- /**
70
- * Official Postgres images always have this DB. `pg` defaults an omitted
71
- * `database` to the username — bootstrap roles like `decohere_bootstrap` are
72
- * not databases, so connect here before CREATE DATABASE.
73
- */ const MAINTENANCE_DATABASE = "postgres";
74
- export class PostgresServerManager {
75
- applicationName;
76
- config;
77
- pool;
78
- client;
79
- constructor(applicationName, config){
80
- this.applicationName = applicationName;
81
- this.config = config;
82
- }
83
- /** `auth: trust`: the server lets every connection in, so roles have no password. */ get trusted() {
84
- return this.config.auth === "trust";
85
- }
86
- async connect() {
87
- const { adminUser, adminPassword, host, port } = this.config;
88
- if (!adminUser || !adminPassword && !this.trusted) {
89
- throw new DataConfigurationError(`Data source "${this.config.name}": adminUser and adminPassword are required to bootstrap the database (adminPassword may be left out only with auth: trust)`);
90
- }
91
- this.pool = new Pool({
92
- user: adminUser,
93
- host,
94
- password: adminPassword,
95
- port,
96
- database: MAINTENANCE_DATABASE,
97
- application_name: this.applicationName
98
- });
99
- try {
100
- this.client = await this.pool.connect();
101
- } catch (err) {
102
- const cause = err instanceof Error ? err.message : String(err);
103
- throw new DataConfigurationError(`Cannot connect to PostgreSQL at ${host}:${port}. Is the server running? Cause: ${cause}`);
104
- }
105
- }
106
- async initializeDatabase(database) {
107
- quoteIdent(database, "database");
108
- const result = await this.pool.query(`SELECT datname FROM pg_catalog.pg_database WHERE lower(datname) = lower($1)`, [
109
- database
110
- ]);
111
- if (result.rowCount === 1) {
112
- return;
113
- }
114
- const dbIdent = quoteIdent(database, "database");
115
- await this.pool.query(`CREATE DATABASE ${dbIdent}`);
116
- }
117
- /**
118
- * Create the runtime app role (+ optional migrate role) and its schema, with least-privilege
119
- * grants. Running it again changes nothing: each grant is made only where the role lacks it,
120
- * since Postgres checks the grantor's rights even for a grant that would change nothing.
121
- *
122
- * The admin acts with the database owner's rights while it runs (it is made a member of the
123
- * owner, and of the migrate role, only for as long as it needs to be). That is how it creates a
124
- * schema and grants on it once the migrate role owns the database.
125
- */ async provisionRoles(options) {
126
- const { database, appUsername, appPassword, migrateUsername, migratePassword, allowAppDdl, schema = "public" } = options;
127
- quoteIdent(schema, "schema");
128
- if (appUsername === this.config.adminUser) {
129
- throw new DataConfigurationError(`Runtime username "${appUsername}" must not equal adminUser — use a dedicated app role`);
130
- }
131
- if (migrateUsername && migrateUsername === appUsername) {
132
- throw new DataConfigurationError("migrateUsername must differ from the runtime app username");
133
- }
134
- if (migrateUsername && migrateUsername === this.config.adminUser) {
135
- throw new DataConfigurationError("migrateUsername must not equal adminUser");
136
- }
137
- const adminUser = this.config.adminUser;
138
- if (!adminUser) {
139
- throw new DataConfigurationError("adminUser is required to provision database roles");
140
- }
141
- const pool = new Pool({
142
- host: this.config.host,
143
- port: this.config.port,
144
- user: adminUser,
145
- database,
146
- password: this.config.adminPassword,
147
- application_name: this.applicationName
148
- });
149
- try {
150
- const env = {
151
- stack: [],
152
- error: void 0,
153
- hasError: false
154
- };
155
- try {
156
- const roles = _ts_add_disposable_resource(env, new BorrowedRoles(pool, adminUser), true);
157
- if (migrateUsername) {
158
- await this.ensureRole(pool, migrateUsername, migratePassword);
159
- // Membership is required for OWNER TO and DEFAULT PRIVILEGES FOR ROLE (not superuser).
160
- await roles.borrow(migrateUsername);
161
- // The owner holds every privilege on the database, so no separate GRANT is needed.
162
- if (await databaseOwner(pool, database) !== migrateUsername) {
163
- await pool.query(alterDatabaseOwnerQuery(database, migrateUsername).text);
164
- }
165
- }
166
- const owner = await databaseOwner(pool, database);
167
- if (owner !== adminUser) await roles.borrow(owner);
168
- // Owned by the database owner (the migrate role, when there is one), like `public`.
169
- await pool.query(createSchemaQuery(schema, owner).text);
170
- if (migrateUsername && !await hasAll(pool, (privilege)=>hasSchemaPrivilegeQuery(migrateUsername, schema, privilege))) {
171
- await pool.query(grantSchemaPrivilegesQuery(migrateUsername, schema).text);
172
- }
173
- await this.ensureRole(pool, appUsername, appPassword);
174
- if (!await granted(pool, hasDatabasePrivilegeQuery(appUsername, database, "CONNECT"))) {
175
- await pool.query(grantConnectQuery(appUsername, database).text);
176
- }
177
- if (!await granted(pool, hasSchemaPrivilegeQuery(appUsername, schema, "USAGE"))) {
178
- await pool.query(grantSchemaUsageQuery(appUsername, schema).text);
179
- }
180
- // Dev synchronize: the app connection must be able to CREATE tables.
181
- if (allowAppDdl && !await granted(pool, hasSchemaPrivilegeQuery(appUsername, schema, "CREATE"))) {
182
- await pool.query(grantSchemaCreateQuery(appUsername, schema).text);
183
- }
184
- if (migrateUsername) {
185
- // Setting a default privilege that is already set changes nothing.
186
- for (const q of defaultPrivilegesForAppQueries(migrateUsername, appUsername, schema)){
187
- await pool.query(q.text);
188
- }
189
- }
190
- } catch (e) {
191
- env.error = e;
192
- env.hasError = true;
193
- } finally{
194
- const result = _ts_dispose_resources(env);
195
- if (result) await result;
196
- }
197
- } finally{
198
- await pool.end();
199
- }
200
- }
201
- /** @deprecated Use provisionRoles. */ async createApiUser(username, password, database, _role) {
202
- await this.provisionRoles({
203
- database,
204
- appUsername: username,
205
- appPassword: password,
206
- migrateUsername: this.config.migrateUsername,
207
- migratePassword: this.config.migratePassword,
208
- allowAppDdl: this.config.synchronize === true,
209
- schema: this.config.schema
210
- });
211
- }
212
- async ensureRole(pool, username, password) {
213
- if (!password && !this.trusted) {
214
- throw new DataConfigurationError(`A password is required for database role "${username}" unless the data source sets auth: trust`);
215
- }
216
- const exists = roleExistsQuery(username);
217
- const found = await pool.query(exists.text, exists.values);
218
- if (found.rowCount === 1) {
219
- return;
220
- }
221
- const create = createUserQuery(username, password);
222
- await pool.query(create.text, create.values);
223
- }
224
- [Symbol.asyncDispose]() {
225
- return this.closeConnection();
226
- }
227
- async closeConnection() {
228
- this.client?.release();
229
- await this.pool?.end();
230
- }
231
- }
232
- async function granted(pool, query) {
233
- const result = await pool.query(query.text, query.values);
234
- return result.rows[0]?.granted === true;
235
- }
236
- /** Every schema privilege that `GRANT ALL ON SCHEMA` gives. Checked one at a time: Postgres
237
- * answers true for a list when any one of them is held. */ async function hasAll(pool, query) {
238
- for (const privilege of [
239
- "USAGE",
240
- "CREATE"
241
- ]){
242
- if (!await granted(pool, query(privilege))) return false;
243
- }
244
- return true;
245
- }
246
- async function databaseOwner(pool, database) {
247
- const query = databaseOwnerQuery(database);
248
- const result = await pool.query(query.text, query.values);
249
- const owner = result.rows[0]?.owner;
250
- if (owner == null) {
251
- throw new DataConfigurationError(`Database "${database}" does not exist`);
252
- }
253
- return owner;
254
- }
255
- /**
256
- * Roles the admin is made a member of for one provisioning run, so it acts with their rights.
257
- * Disposing gives back exactly what was borrowed, even when the run fails: memberships the admin
258
- * already had (or rights it has as a superuser) are left alone.
259
- */ class BorrowedRoles {
260
- pool;
261
- adminUser;
262
- borrowed = [];
263
- constructor(pool, adminUser){
264
- this.pool = pool;
265
- this.adminUser = adminUser;
266
- }
267
- async borrow(role) {
268
- if (this.borrowed.includes(role)) return;
269
- if (await granted(this.pool, hasRolePrivilegesQuery(this.adminUser, role))) return;
270
- try {
271
- await this.pool.query(grantRoleMembershipQuery(this.adminUser, role).text);
272
- } catch (err) {
273
- throw new DataConfigurationError(`Bootstrap role "${this.adminUser}" cannot act as "${role}". Make it a member of "${role}" (or create "${role}" with it), then run again.`, {
274
- cause: err
275
- });
276
- }
277
- this.borrowed.push(role);
278
- }
279
- async [Symbol.asyncDispose]() {
280
- for (const role of this.borrowed.reverse()){
281
- await this.pool.query(revokeRoleMembershipQuery(this.adminUser, role).text);
282
- }
283
- }
284
- }
@@ -1,20 +0,0 @@
1
- import pinoPretty from "pino-pretty";
2
- import { PlatformTools } from "typeorm/platform/PlatformTools.js";
3
- export const SqlPretty = (opts)=>pinoPretty({
4
- colorizeObjects: true,
5
- customLevels: Object.entries(opts?.levels ?? []).map(([key, value])=>`${key}:${value}`).join(",") ?? [],
6
- customPrettifiers: {
7
- sql: tryParseSql
8
- },
9
- ...opts
10
- });
11
- function tryParseSql(value) {
12
- const isString = typeof value === "string";
13
- const statement = isString ? value : value.statement;
14
- try {
15
- const formatted = PlatformTools.highlightSql(PlatformTools.formatSql(statement));
16
- return formatted;
17
- } catch {
18
- return statement;
19
- }
20
- }
@@ -1,105 +0,0 @@
1
- function _ts_decorate(decorators, target, key, desc) {
2
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
- else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
- return c > 3 && r && Object.defineProperty(target, key, r), r;
6
- }
7
- function _ts_metadata(k, v) {
8
- if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
- }
10
- import { DataConfigurationError, DEFAULT_DATA_SOURCE } from "@decohere/data";
11
- import { ReadinessCheckToken } from "@decohere/http";
12
- import { memoize } from "@decohere/core/caching";
13
- import { bootstrapDataSources } from "./bootstrap.js";
14
- import { DataSourceFactory } from "./data-source.factory.js";
15
- import { typeOrmDataSourceToken } from "./inject-data-source.js";
16
- import { TypeOrmScribeLogger } from "./typeorm-scribe.logger.js";
17
- import { TypeOrmUnitOfWork } from "./unit-of-work.js";
18
- export class TypeOrmModule {
19
- /** Registers and opens a DataSource per module. Returns the names it registered them under. */ async configure(options) {
20
- const { moduleConfigurations, databaseConfig, containerBuilder, applicationName } = options;
21
- if (databaseConfig.dataSources.length === 0) {
22
- throw new DataConfigurationError("No datasources detected in config");
23
- }
24
- const modules = this.resolveDataSources(databaseConfig, moduleConfigurations);
25
- // Once, before any DataSource opens: modules sharing a database must not race to create it.
26
- await bootstrapDataSources(modules.map((m)=>m.connectionInfo), {
27
- applicationName
28
- });
29
- return await Promise.all(modules.map(async ({ name, connectionInfo, moduleConfig })=>{
30
- await this.init(applicationName, name, connectionInfo, moduleConfig, containerBuilder);
31
- return name;
32
- }));
33
- }
34
- resolveDataSources(databaseConfig, moduleConfigurations) {
35
- if (databaseConfig.dataSources.length === 1 && moduleConfigurations.length === 1) {
36
- const moduleConfig = moduleConfigurations.at(0);
37
- return [
38
- {
39
- name: moduleConfig.name ?? DEFAULT_DATA_SOURCE,
40
- connectionInfo: databaseConfig.dataSources.at(0),
41
- moduleConfig
42
- }
43
- ];
44
- }
45
- return moduleConfigurations.map((moduleConfig)=>{
46
- const { name } = moduleConfig;
47
- if (name == null) {
48
- throw new DataConfigurationError(`Name is required if there is more than one datasource`);
49
- }
50
- const connectionInfo = databaseConfig.dataSources.find((ds)=>ds.name === name);
51
- if (connectionInfo == null) {
52
- throw new DataConfigurationError(`Cannot find connection info for ${name.toString()}`);
53
- }
54
- return {
55
- name,
56
- connectionInfo,
57
- moduleConfig
58
- };
59
- });
60
- }
61
- async init(applicationName, name, connectionInfo, moduleConfig, builder) {
62
- const logger = this.getLoggerFor(name, connectionInfo.dialect);
63
- const dataSource = await this.configureTypeOrmDataSource(applicationName, name, moduleConfig, connectionInfo, builder, logger);
64
- if (connectionInfo.seed) {
65
- await moduleConfig.seed?.(dataSource);
66
- }
67
- }
68
- async configureTypeOrmDataSource(applicationName, name, moduleConfig, connectionInfo, builder, logger) {
69
- const { entities, repositories } = moduleConfig;
70
- const dataSource = DataSourceFactory.create({
71
- entities,
72
- applicationName,
73
- ...connectionInfo
74
- }, logger);
75
- await dataSource.initialize();
76
- const unitOfWork = ()=>new TypeOrmUnitOfWork(dataSource);
77
- builder.registerRequestScoped(name, unitOfWork);
78
- builder.registerSingleton(typeOrmDataSourceToken(name), dataSource);
79
- builder.registerSingleton(ReadinessCheckToken, createTypeOrmReadinessCheck(name, dataSource));
80
- for (const repo of repositories ?? []){
81
- builder.registerRequestScoped(repo);
82
- }
83
- return dataSource;
84
- }
85
- getLoggerFor(name, dialectOrProvider) {
86
- return new TypeOrmScribeLogger(name.toString(), dialectOrProvider);
87
- }
88
- }
89
- _ts_decorate([
90
- memoize(),
91
- _ts_metadata("design:type", Function),
92
- _ts_metadata("design:paramtypes", [
93
- Object,
94
- Object
95
- ]),
96
- _ts_metadata("design:returntype", void 0)
97
- ], TypeOrmModule.prototype, "getLoggerFor", null);
98
- function createTypeOrmReadinessCheck(name, dataSource) {
99
- return {
100
- name: `typeorm:${String(name)}`,
101
- async check () {
102
- await dataSource.query("SELECT 1");
103
- }
104
- };
105
- }
@@ -1,26 +0,0 @@
1
- export class TypeOrmQueryProvider {
2
- unitOfWork;
3
- constructor(unitOfWork){
4
- this.unitOfWork = unitOfWork;
5
- }
6
- firstOrDefault(entity, options) {
7
- return this.unitOfWork.entityManager.findOne(entity, options);
8
- }
9
- all(entity, options) {
10
- return this.unitOfWork.getRepository(entity).find(options);
11
- }
12
- findById(entity, key) {
13
- return this.unitOfWork.entityManager.findOneBy(entity, {
14
- id: key
15
- });
16
- }
17
- save(entity) {
18
- return this.unitOfWork.entityManager.save(entity);
19
- }
20
- query(entity, filter) {
21
- return this.unitOfWork.entityManager.find(entity, filter);
22
- }
23
- delete(entity) {
24
- return this.unitOfWork.entityManager.remove(entity);
25
- }
26
- }
@@ -1,20 +0,0 @@
1
- import { Repository } from "@decohere/data";
2
- import { TypeOrmQueryProvider } from "./type-orm.provider.js";
3
- export class TypeOrmRepository extends Repository {
4
- provider;
5
- type;
6
- constructor(unitOfWork, type){
7
- super();
8
- this.type = type;
9
- this.provider = new TypeOrmQueryProvider(unitOfWork);
10
- }
11
- firstOrDefault(optionsOrType, options) {
12
- if (typeof optionsOrType === "function") {
13
- if (!options) {
14
- throw new Error("options parameter is required when type is provided");
15
- }
16
- return this.provider.firstOrDefault(optionsOrType, options);
17
- }
18
- return this.provider.firstOrDefault(this.type, optionsOrType);
19
- }
20
- }
@@ -1,60 +0,0 @@
1
- function _ts_decorate(decorators, target, key, desc) {
2
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
- else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
- return c > 3 && r && Object.defineProperty(target, key, r), r;
6
- }
7
- function _ts_metadata(k, v) {
8
- if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
- }
10
- import { cache } from "@decohere/core/caching";
11
- import { scribe } from "@decohere/scribe";
12
- import { AbstractLogger } from "typeorm";
13
- const MessageTypeMap = {
14
- log: "info",
15
- info: "info",
16
- query: "info",
17
- warn: "warn",
18
- error: "error",
19
- "query-error": "error",
20
- "query-slow": "warn",
21
- "schema-build": "info",
22
- migration: "info"
23
- };
24
- export class TypeOrmScribeLogger extends AbstractLogger {
25
- logger;
26
- dialect;
27
- constructor(name, dialect, enable = true){
28
- super(enable);
29
- this.dialect = dialect;
30
- this.logger = scribe.logger(`${name ?? "TypeOrm"}`);
31
- }
32
- writeLog(level, logMessage, _queryRunner) {
33
- const messages = Array.isArray(logMessage) ? logMessage : [
34
- logMessage
35
- ];
36
- for (const message of messages){
37
- const logLevel = this.getLogLevel(message.type);
38
- const prefix = message.type ?? level;
39
- const statement = {
40
- sql: {
41
- statement: message.message,
42
- language: this.dialect
43
- }
44
- };
45
- this.logger[logLevel](prefix, statement);
46
- }
47
- }
48
- getLogLevel(type) {
49
- const level = Object.entries(MessageTypeMap).find(([key])=>type === key)?.at(1);
50
- return this.logger.isLevelEnabled(level) ? level : this.logger.currentLevel;
51
- }
52
- }
53
- _ts_decorate([
54
- cache(),
55
- _ts_metadata("design:type", Function),
56
- _ts_metadata("design:paramtypes", [
57
- Object
58
- ]),
59
- _ts_metadata("design:returntype", void 0)
60
- ], TypeOrmScribeLogger.prototype, "getLogLevel", null);
@@ -1,50 +0,0 @@
1
- function _ts_decorate(decorators, target, key, desc) {
2
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
- else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
- return c > 3 && r && Object.defineProperty(target, key, r), r;
6
- }
7
- function _ts_metadata(k, v) {
8
- if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
- }
10
- import { DatabaseConfig } from "@decohere/data";
11
- import { Builder } from "@decohere/http";
12
- import { extension } from "@decohere/core/extensibility";
13
- import { typeOrmDataSourceToken } from "./inject-data-source.js";
14
- import { TypeOrmModule } from "./type-orm.module.js";
15
- export class BuilderExtensions {
16
- static withTypeOrm(builder, config) {
17
- const names = [];
18
- builder.registerPreBuildStep(async (configService, containerBuilder)=>{
19
- const databaseConfig = await configService.get(DatabaseConfig);
20
- const module = new TypeOrmModule();
21
- const moduleConfigurations = Array.isArray(config) ? config : [
22
- config
23
- ];
24
- const applicationName = builder.applicationName;
25
- names.push(...await module.configure({
26
- applicationName,
27
- databaseConfig,
28
- moduleConfigurations,
29
- containerBuilder
30
- }));
31
- });
32
- // The container disposes only what it has handed out, and nothing may ever ask for a
33
- // DataSource by its token. Resolving each here makes the root container own it, as one of
34
- // its first instances, so it is closed last: after hosted services and other services
35
- // that use it (#541).
36
- builder.registerPostBuildStep((container)=>{
37
- for (const name of names)container.resolve(typeOrmDataSourceToken(name));
38
- });
39
- return builder;
40
- }
41
- }
42
- _ts_decorate([
43
- extension(Builder),
44
- _ts_metadata("design:type", Function),
45
- _ts_metadata("design:paramtypes", [
46
- typeof Builder === "undefined" ? Object : Builder,
47
- Object
48
- ]),
49
- _ts_metadata("design:returntype", void 0)
50
- ], BuilderExtensions, "withTypeOrm", null);
@@ -1,39 +0,0 @@
1
- export class TypeOrmUnitOfWork {
2
- dataSource;
3
- queryRunner;
4
- constructor(dataSource){
5
- this.dataSource = dataSource;
6
- this.queryRunner = this.dataSource.createQueryRunner();
7
- }
8
- get entityManager() {
9
- return this.queryRunner.manager;
10
- }
11
- getRepository(entity) {
12
- return this.entityManager.getRepository(entity);
13
- }
14
- async beginTransaction() {
15
- return this.queryRunner.startTransaction();
16
- }
17
- async commit() {
18
- return this.queryRunner.commitTransaction();
19
- }
20
- /**
21
- * Hand the connection back to the pool, rolling back first if a transaction is still open
22
- * (e.g. the request failed before commit). Otherwise the next user of that pooled
23
- * connection would inherit the half-finished transaction (#502).
24
- */ async [Symbol.asyncDispose]() {
25
- if (this.queryRunner.isReleased) {
26
- return;
27
- }
28
- try {
29
- if (this.queryRunner.isTransactionActive) {
30
- await this.queryRunner.rollbackTransaction();
31
- }
32
- } finally{
33
- await this.queryRunner.release();
34
- }
35
- }
36
- /** Prefer `await using` / async disposal; this starts the same cleanup without waiting. */ [Symbol.dispose]() {
37
- void this[Symbol.asyncDispose]();
38
- }
39
- }
@@ -1,19 +0,0 @@
1
- import { type ConnectionInfo } from "@decohere/data";
2
- /** True when NODE_ENV says development. Only then may a data source declare `auth: trust`. */
3
- export declare function isDevelopmentEnv(nodeEnv?: string | undefined): boolean;
4
- /**
5
- * Check how a data source signs in, in every environment. A server that needs no password is
6
- * declared with `auth: trust`, never inferred from a missing password: a password left out by
7
- * mistake would otherwise look like a trusted server and fail later, less clearly.
8
- */
9
- export declare function assertDataSourceAuth(info: ConnectionInfo, nodeEnv?: string | undefined): void;
10
- /**
11
- * Decide whether to bootstrap a data source (create its database, roles and schema).
12
- *
13
- * - `bootstrap: false` → never
14
- * - `bootstrap: true` → yes (requires adminUser, and adminPassword unless `auth: trust`)
15
- * - omitted → only in development, when those admin credentials are present
16
- */
17
- export declare function shouldBootstrapDatabase(info: ConnectionInfo, nodeEnv?: string | undefined): boolean;
18
- /** A data source that will be bootstrapped must have every password the bootstrap needs. */
19
- export declare function assertBootstrapCredentials(info: ConnectionInfo): void;
@@ -1,16 +0,0 @@
1
- import { type ConnectionInfo } from "@decohere/data";
2
- export type BootstrapDataSourcesOptions = {
3
- /** Shown as `application_name` on the bootstrap connections. */
4
- applicationName: string;
5
- /** Defaults to `process.env.NODE_ENV`. Decides whether `auth: trust` is allowed. */
6
- nodeEnv?: string;
7
- };
8
- /**
9
- * Check every data source's auth, then create the databases, roles and schemas of those that
10
- * bootstrap. Data sources on the same server and database are bootstrapped once, one after the
11
- * other: in parallel they would race to create the same database and roles. Running it again
12
- * changes nothing.
13
- *
14
- * For runners and scripts that open their own `DataSource`; `withTypeOrm` calls it for you.
15
- */
16
- export declare function bootstrapDataSources(dataSources: readonly ConnectionInfo[], options: BootstrapDataSourcesOptions): Promise<void>;
@@ -1,17 +0,0 @@
1
- import type { ConnectionInfo } from "@decohere/data";
2
- import type { Constructable } from "@decohere/core/reflection";
3
- import { DataSource, type Logger } from "typeorm";
4
- export type ConnectionOptions = ConnectionInfo & {
5
- entities: Constructable[];
6
- applicationName?: string;
7
- };
8
- /**
9
- * A DataSource the container can close: TypeORM's has `destroy()` but no `Symbol.asyncDispose`,
10
- * so disposing the app's container used to leave its connections open (#541).
11
- */
12
- export declare class ManagedDataSource extends DataSource {
13
- [Symbol.asyncDispose](): Promise<void>;
14
- }
15
- export declare class DataSourceFactory {
16
- static create(options: ConnectionOptions, logger: Logger): DataSource;
17
- }
@@ -1,9 +0,0 @@
1
- import type { Constructable } from "@decohere/core/reflection";
2
- import type { DataSource } from "typeorm";
3
- export type ModuleConfig = {
4
- /** The unique identifier for the data source. Required when multiple data sources are configured. */
5
- name?: string | symbol;
6
- repositories?: Constructable[];
7
- entities: Constructable[];
8
- seed?: (dataSource: DataSource) => Promise<void>;
9
- };