@dudousxd/nestjs-catalog 0.24.0 → 0.25.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.
|
@@ -75,6 +75,58 @@ export interface CatalogEnvironment {
|
|
|
75
75
|
* `obj_<type>`) and carry no environment in them, so two environments in one
|
|
76
76
|
* database would collide on every table. Separate databases make the
|
|
77
77
|
* collision impossible and make MySQL's own `GRANT` the enforcement point.
|
|
78
|
+
*
|
|
79
|
+
* ## PostgreSQL keeps this exactly, and there the choice is a real one
|
|
80
|
+
*
|
|
81
|
+
* The sentence above leans on MySQL not distinguishing the two words. Postgres
|
|
82
|
+
* does distinguish them, and in a way that looks like an invitation: one
|
|
83
|
+
* connection reaches many schemas, which MySQL cannot do, so each environment
|
|
84
|
+
* could be a schema in one database behind one connection pool. That is
|
|
85
|
+
* cheaper — one pool instead of N — and it is refused.
|
|
86
|
+
*
|
|
87
|
+
* **The property that has to survive is the one this file is built around:
|
|
88
|
+
* there is no ambient default, and a cross-environment read is impossible
|
|
89
|
+
* because the database makes it so rather than because the application was
|
|
90
|
+
* careful.** Schema-per-environment cannot keep it, by either of the two
|
|
91
|
+
* routes available.
|
|
92
|
+
*
|
|
93
|
+
* *Route one, `search_path`.* Isolation then rests on a session variable on a
|
|
94
|
+
* pooled connection — and that is not a hypothetical hazard here, it is one
|
|
95
|
+
* this codebase has already measured and written up. `runReadOnlyQuery` in the
|
|
96
|
+
* MikroORM store deliberately passes its statement timeout as a per-statement
|
|
97
|
+
* hint rather than `SET SESSION`, because the session form was observed riding
|
|
98
|
+
* the pooled connection into an unrelated request: "after one query-console
|
|
99
|
+
* request, a *different* `em.fork()` read `@@SESSION.MAX_EXECUTION_TIME` back
|
|
100
|
+
* as the value set here". A leaked `MAX_EXECUTION_TIME` is a slow query. A
|
|
101
|
+
* leaked `search_path` is dev's request answered out of production's schema,
|
|
102
|
+
* returning entirely plausible rows, which is the failure
|
|
103
|
+
* {@link resolveEnvironment} exists to make impossible.
|
|
104
|
+
*
|
|
105
|
+
* *Route two, qualify every identifier.* Then isolation is a prefix that has to
|
|
106
|
+
* be present on every statement — which is the same class of thing as a
|
|
107
|
+
* `WHERE` clause, and this interface's own docblock says why that is refused:
|
|
108
|
+
* "there is no field here that a `WHERE` clause could be built out of, because
|
|
109
|
+
* a filter is precisely the sort of isolation that fails silently the one time
|
|
110
|
+
* somebody forgets it". A forgotten schema qualifier is a forgotten filter.
|
|
111
|
+
*
|
|
112
|
+
* And `GRANT` cannot rescue either route, which is the argument that actually
|
|
113
|
+
* settles it. One connection pool is one role; if that role can reach both
|
|
114
|
+
* schemas — which is what "one connection reaches many schemas" *means* — then
|
|
115
|
+
* `GRANT` is enforcing nothing between them. Making it the enforcement point
|
|
116
|
+
* again requires a role per environment, a role per environment requires a
|
|
117
|
+
* connection per environment, and at that point the shared pool that motivated
|
|
118
|
+
* the whole idea is gone and separate databases cost nothing extra.
|
|
119
|
+
*
|
|
120
|
+
* **What it costs:** N connection pools on Postgres, the same as on MySQL, and
|
|
121
|
+
* no cross-environment SQL join. The second is a feature — data never moves
|
|
122
|
+
* between environments — and the first is the price of the guarantee.
|
|
123
|
+
*
|
|
124
|
+
* **What an operator has to know:** nothing new. The deployment story is the
|
|
125
|
+
* same on both engines, which is the main thing this choice buys: one
|
|
126
|
+
* `catalogDatabaseNameFor`, one `ensureDatabase`, one shape of `GRANT`, and no
|
|
127
|
+
* per-engine paragraph in a runbook. The genuine Postgres/MySQL differences
|
|
128
|
+
* live in the store's `dialect.ts` and are about column case and search, not
|
|
129
|
+
* about isolation.
|
|
78
130
|
*/
|
|
79
131
|
databaseName: string;
|
|
80
132
|
/**
|
|
@@ -77,15 +77,43 @@ const ENVIRONMENT_ID_PATTERN = /^[a-z][a-z0-9_]{0,23}$/;
|
|
|
77
77
|
* module refuses it as a tenant: "default" is the value that means "no
|
|
78
78
|
* namespace at all", so an environment called `default` would derive the bare
|
|
79
79
|
* keyspace and quietly share a results queue with every other engine on the
|
|
80
|
-
* Redis.
|
|
81
|
-
*
|
|
80
|
+
* Redis.
|
|
81
|
+
*
|
|
82
|
+
* The rest are databases the *engine* owns, and an environment must never be
|
|
83
|
+
* pointed at one — an environment id becomes a database name, so `mysql` or
|
|
84
|
+
* `postgres` here means this package running `CREATE TABLE catalog_object_type`
|
|
85
|
+
* inside the server's own maintenance database.
|
|
86
|
+
*
|
|
87
|
+
* **Both engines' lists, on both engines, deliberately.** The alternative is a
|
|
88
|
+
* refusal that depends on which driver happens to be mounted, and that is the
|
|
89
|
+
* shape that bites during a migration: an environment named `postgres` is
|
|
90
|
+
* perfectly legal on MySQL today and becomes a live incident on the day somebody
|
|
91
|
+
* moves the deployment, at which point renaming an environment means renaming
|
|
92
|
+
* its database, its MikroORM context and its Redis keyspace. Refusing the union
|
|
93
|
+
* costs a deployment nothing — nobody wants an environment called
|
|
94
|
+
* `information_schema` — and keeps the answer the same everywhere.
|
|
95
|
+
*
|
|
96
|
+
* `template0` and `template1` are Postgres's own, and are the two most likely to
|
|
97
|
+
* be typed by accident by somebody who has just read a `createdb` man page.
|
|
82
98
|
*/
|
|
83
99
|
const RESERVED_ENVIRONMENT_IDS = [
|
|
84
100
|
'default',
|
|
101
|
+
// MySQL's.
|
|
85
102
|
'information_schema',
|
|
86
103
|
'mysql',
|
|
87
104
|
'performance_schema',
|
|
88
105
|
'sys',
|
|
106
|
+
// PostgreSQL's. `postgres` is the maintenance database every cluster ships
|
|
107
|
+
// with and the one a client connects to when it has nowhere else to go.
|
|
108
|
+
'postgres',
|
|
109
|
+
'template0',
|
|
110
|
+
'template1',
|
|
111
|
+
// Not a database but a schema, and reserved because a Postgres deployment
|
|
112
|
+
// that ever did put environments in schemas would collide with the default
|
|
113
|
+
// one — see the note on `databaseName` for why this package does not.
|
|
114
|
+
'public',
|
|
115
|
+
'pg_catalog',
|
|
116
|
+
'pg_toast',
|
|
89
117
|
];
|
|
90
118
|
function isEnvironmentId(value) {
|
|
91
119
|
return (typeof value === 'string' &&
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dudousxd/nestjs-catalog",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.25.0",
|
|
4
4
|
"description": "A metadata registry for NestJS: object types, properties and relations, derived from your ORM and enriched with decorators.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Davide Carvalho",
|