@coffre/db 0.9.0 → 0.10.1
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/dist/connect.d.ts +1 -1
- package/dist/connect.js +1 -1
- package/dist/database-BadNeeN-.js +146 -0
- package/dist/{database-hd1hAll-.d.ts → database-DBtn3jqp.d.ts} +27 -3
- package/dist/dialect.d.ts +1 -1
- package/dist/grants.d.ts +2 -2
- package/dist/grants.js +26 -10
- package/dist/hyperdrive.d.ts +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +2 -89
- package/dist/log.d.ts +26 -6
- package/dist/log.js +86 -21
- package/dist/migrations/postgres/0012_sensitive_values.sql +18 -0
- package/dist/migrations/postgres/0013_contributor_role.sql +5 -0
- package/dist/migrations/postgres/0014_custom_roles.sql +4 -0
- package/dist/migrations/postgres/meta/0012_snapshot.json +3004 -0
- package/dist/migrations/postgres/meta/0013_snapshot.json +3004 -0
- package/dist/migrations/postgres/meta/0014_snapshot.json +3000 -0
- package/dist/migrations/postgres/meta/_journal.json +21 -0
- package/dist/migrations/sqlite/0012_sensitive_values.sql +15 -0
- package/dist/migrations/sqlite/0013_contributor_role.sql +28 -0
- package/dist/migrations/sqlite/0014_custom_roles.sql +27 -0
- package/dist/migrations/sqlite/meta/0012_snapshot.json +2726 -0
- package/dist/migrations/sqlite/meta/0013_snapshot.json +2726 -0
- package/dist/migrations/sqlite/meta/0014_snapshot.json +2722 -0
- package/dist/migrations/sqlite/meta/_journal.json +21 -0
- package/dist/portable.d.ts +2 -2
- package/dist/{schema-CCYKTG-E.d.ts → schema-NBVA9q-s.d.ts} +17 -0
- package/dist/schema-sqlite.d.ts +17 -0
- package/dist/schema-sqlite.js +3 -6
- package/dist/schema-version.d.ts +1 -1
- package/dist/schema-version.js +42 -0
- package/dist/schema.d.ts +1 -1
- package/dist/schema.js +3 -7
- package/package.json +2 -2
- package/src/database.ts +35 -3
- package/src/grants.ts +32 -18
- package/src/log.ts +90 -34
- package/src/migrations/postgres/0012_sensitive_values.sql +18 -0
- package/src/migrations/postgres/0013_contributor_role.sql +5 -0
- package/src/migrations/postgres/0014_custom_roles.sql +4 -0
- package/src/migrations/postgres/meta/0012_snapshot.json +3004 -0
- package/src/migrations/postgres/meta/0013_snapshot.json +3004 -0
- package/src/migrations/postgres/meta/0014_snapshot.json +3000 -0
- package/src/migrations/postgres/meta/_journal.json +21 -0
- package/src/migrations/sqlite/0012_sensitive_values.sql +15 -0
- package/src/migrations/sqlite/0013_contributor_role.sql +28 -0
- package/src/migrations/sqlite/0014_custom_roles.sql +27 -0
- package/src/migrations/sqlite/meta/0012_snapshot.json +2726 -0
- package/src/migrations/sqlite/meta/0013_snapshot.json +2726 -0
- package/src/migrations/sqlite/meta/0014_snapshot.json +2722 -0
- package/src/migrations/sqlite/meta/_journal.json +21 -0
- package/src/prepared.ts +60 -0
- package/src/schema.sqlite.ts +8 -9
- package/src/schema.ts +8 -10
|
@@ -85,6 +85,27 @@
|
|
|
85
85
|
"when": 1792317461299,
|
|
86
86
|
"tag": "0011_plain_values",
|
|
87
87
|
"breakpoints": true
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
"idx": 12,
|
|
91
|
+
"version": "6",
|
|
92
|
+
"when": 1792340920098,
|
|
93
|
+
"tag": "0012_sensitive_values",
|
|
94
|
+
"breakpoints": true
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
"idx": 13,
|
|
98
|
+
"version": "6",
|
|
99
|
+
"when": 1792343374795,
|
|
100
|
+
"tag": "0013_contributor_role",
|
|
101
|
+
"breakpoints": true
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
"idx": 14,
|
|
105
|
+
"version": "6",
|
|
106
|
+
"when": 1792345777255,
|
|
107
|
+
"tag": "0014_custom_roles",
|
|
108
|
+
"breakpoints": true
|
|
88
109
|
}
|
|
89
110
|
]
|
|
90
111
|
}
|
package/src/prepared.ts
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { Query } from 'drizzle-orm';
|
|
2
|
+
|
|
3
|
+
import { tablesOf, type Queryable } from './database.ts';
|
|
4
|
+
import { engineOf } from './dialect.ts';
|
|
5
|
+
|
|
6
|
+
/** What Drizzle works out from a select builder before it can run it, once per dialect. */
|
|
7
|
+
type Built = { query: Query; fields: unknown; joinsNotNullableMap: unknown; engine: 'postgres' | 'sqlite' };
|
|
8
|
+
|
|
9
|
+
/** The part of a select builder `prepared` reads, once. */
|
|
10
|
+
type Select = PromiseLike<unknown> & { toSQL(): Query; prepare(...name: never[]): unknown };
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* A select built once per process, or once per isolate on Workers, and run
|
|
14
|
+
* with new values each time. Drizzle rebuilds a query's SQL from its
|
|
15
|
+
* builder at every run, and that was most of a short query's CPU; this
|
|
16
|
+
* builds it from `build` the first time a database of its dialect asks,
|
|
17
|
+
* then hands the SQL and its columns to that call's own session
|
|
18
|
+
* (`session.prepareQuery`), which binds `values` to the placeholders
|
|
19
|
+
* (`sql.placeholder`) and maps the rows as the builder would.
|
|
20
|
+
*
|
|
21
|
+
* `build` must not depend on anything but the database's dialect, and the
|
|
22
|
+
* values go to the driver as they are, not through a column's encoding:
|
|
23
|
+
* strings, numbers, bigints and bytes.
|
|
24
|
+
*
|
|
25
|
+
* const member = prepared((db) => db.select().from(tablesOf(db).vaultMembers).where(eq(…, sql.placeholder('principal'))));
|
|
26
|
+
* const [row] = await member(db, { principal });
|
|
27
|
+
*/
|
|
28
|
+
export function prepared<Q extends Select>(
|
|
29
|
+
build: (db: Queryable) => Q,
|
|
30
|
+
): (db: Queryable, values?: Record<string, unknown>) => Promise<Awaited<Q>> {
|
|
31
|
+
// By the dialect's tables: one Postgres build, one SQLite build.
|
|
32
|
+
const builds = new WeakMap<object, Built>();
|
|
33
|
+
return (db, values = {}) => {
|
|
34
|
+
const tables = tablesOf(db);
|
|
35
|
+
let built = builds.get(tables);
|
|
36
|
+
if (built === undefined) {
|
|
37
|
+
const builder = build(db);
|
|
38
|
+
// Drizzle's own preparation, which runs nothing, says which columns come back and which joins may be null.
|
|
39
|
+
// Unnamed on Postgres: no statement is prepared on the server, whose connections a call does not keep.
|
|
40
|
+
const { fields, joinsNotNullableMap } = (builder as unknown as { prepare(): { fields: unknown; joinsNotNullableMap: unknown } }).prepare();
|
|
41
|
+
built = { query: builder.toSQL(), fields, joinsNotNullableMap, engine: engineOf(db) };
|
|
42
|
+
builds.set(tables, built);
|
|
43
|
+
}
|
|
44
|
+
const session = (db as unknown as { session: Session }).session;
|
|
45
|
+
const query = built.engine === 'sqlite'
|
|
46
|
+
? session.prepareQuery(built.query, built.fields, 'all', true)
|
|
47
|
+
: session.prepareQuery(built.query, built.fields, undefined, true);
|
|
48
|
+
query.joinsNotNullableMap = built.joinsNotNullableMap;
|
|
49
|
+
return (built.engine === 'sqlite' ? query.all(values) : query.execute(values)) as Promise<Awaited<Q>>;
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Drizzle's session, Postgres's or SQLite's, as `prepared` calls it. */
|
|
54
|
+
type Session = {
|
|
55
|
+
prepareQuery(query: Query, fields: unknown, nameOrMethod: string | undefined, isResponseInArrayMode: boolean): {
|
|
56
|
+
joinsNotNullableMap: unknown;
|
|
57
|
+
execute(values: Record<string, unknown>): Promise<unknown>;
|
|
58
|
+
all(values: Record<string, unknown>): Promise<unknown>;
|
|
59
|
+
};
|
|
60
|
+
};
|
package/src/schema.sqlite.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { assignableToEnvironment, ROLE_NAMES } from '@coffre/core/access';
|
|
2
1
|
import { sql } from 'drizzle-orm';
|
|
3
2
|
import {
|
|
4
3
|
blob,
|
|
@@ -182,6 +181,11 @@ export const secretVersions = sqliteTable(
|
|
|
182
181
|
// the vault. Null for a secret. Its ciphertext is kept as any version's,
|
|
183
182
|
// for what opens values through the vault (a reference, a fork's copy).
|
|
184
183
|
plaintext: text(),
|
|
184
|
+
// A sensitive value: never handed back to a person, Owners included, only
|
|
185
|
+
// used for them through the credential proxy, or read by a service
|
|
186
|
+
// account. Its data key's wrap binds it, so the vault opens a version
|
|
187
|
+
// flipped back as nothing. Never plain too.
|
|
188
|
+
sensitive: flag('sensitive').notNull().default(false),
|
|
185
189
|
},
|
|
186
190
|
(table) => [
|
|
187
191
|
unique('secret_versions_secret_id_version_key').on(table.secretId, table.version),
|
|
@@ -221,9 +225,6 @@ export const dismissedKeys = sqliteTable(
|
|
|
221
225
|
],
|
|
222
226
|
);
|
|
223
227
|
|
|
224
|
-
const ROLES = sql.raw(ROLE_NAMES.map((role) => `'${role}'`).join(', '));
|
|
225
|
-
const ENVIRONMENT_ROLES = sql.raw(ROLE_NAMES.filter(assignableToEnvironment).map((role) => `'${role}'`).join(', '));
|
|
226
|
-
|
|
227
228
|
export const vaultMembers = sqliteTable(
|
|
228
229
|
'vault_members',
|
|
229
230
|
{
|
|
@@ -275,11 +276,9 @@ export const vaultGrants = sqliteTable(
|
|
|
275
276
|
sql`((${table.projectId} IS NULL) <> (${table.environmentId} IS NULL) AND ${table.environmentSlug} IS NULL) OR (${table.projectId} IS NULL AND ${table.environmentId} IS NULL)`,
|
|
276
277
|
),
|
|
277
278
|
check('vault_grants_environment_slug_check', sql`${table.environmentSlug} IS NULL OR (${isSlug(table.environmentSlug, 63)})`),
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
sql`(${table.environmentId} IS NULL AND ${table.environmentSlug} IS NULL) OR ${table.role} IN (${ENVIRONMENT_ROLES})`,
|
|
282
|
-
),
|
|
279
|
+
// A role's id, one that ships or one an Owner made (`ROLE_ID`): which the instance's policy defines, and which of them an
|
|
280
|
+
// environment may be granted, the vault decides, as the policy is now (docs/design/roles.md).
|
|
281
|
+
check('vault_grants_role_check', sql`length(${table.role}) BETWEEN 2 AND 32 AND ${table.role} GLOB '[a-z]*' AND ${table.role} NOT GLOB '*[^a-z0-9-]*'`),
|
|
283
282
|
unique('vault_grants_on_project').on(table.principal, table.projectId),
|
|
284
283
|
unique('vault_grants_on_environment').on(table.principal, table.environmentId),
|
|
285
284
|
unique('vault_grants_on_environment_slug').on(table.principal, table.environmentSlug),
|
package/src/schema.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { assignableToEnvironment, ROLE_NAMES } from '@coffre/core/access';
|
|
2
1
|
import { sql } from 'drizzle-orm';
|
|
3
2
|
import {
|
|
4
3
|
bigint,
|
|
@@ -176,6 +175,11 @@ export const secretVersions = pgTable(
|
|
|
176
175
|
// the vault. Null for a secret. Its ciphertext is kept as any version's,
|
|
177
176
|
// for what opens values through the vault (a reference, a fork's copy).
|
|
178
177
|
plaintext: text(),
|
|
178
|
+
// A sensitive value: never handed back to a person, Owners included, only
|
|
179
|
+
// used for them through the credential proxy, or read by a service
|
|
180
|
+
// account. Its data key's wrap binds it, so the vault opens a version
|
|
181
|
+
// flipped back as nothing. Never plain too.
|
|
182
|
+
sensitive: boolean().notNull().default(false),
|
|
179
183
|
},
|
|
180
184
|
(table) => [
|
|
181
185
|
unique('secret_versions_secret_id_version_key').on(table.secretId, table.version),
|
|
@@ -219,10 +223,6 @@ export const dismissedKeys = pgTable(
|
|
|
219
223
|
],
|
|
220
224
|
);
|
|
221
225
|
|
|
222
|
-
/** The role catalogue, as SQL: every role, and those an environment may be granted. */
|
|
223
|
-
const ROLES = sql.raw(ROLE_NAMES.map((role) => `'${role}'`).join(', '));
|
|
224
|
-
const ENVIRONMENT_ROLES = sql.raw(ROLE_NAMES.filter(assignableToEnvironment).map((role) => `'${role}'`).join(', '));
|
|
225
|
-
|
|
226
226
|
/**
|
|
227
227
|
* The vault's member directory: everyone it has admitted, written by the
|
|
228
228
|
* vault alone, read by both. A principal with no row is no member.
|
|
@@ -294,11 +294,9 @@ export const vaultGrants = pgTable(
|
|
|
294
294
|
sql`((${table.projectId} IS NULL) <> (${table.environmentId} IS NULL) AND ${table.environmentSlug} IS NULL) OR (${table.projectId} IS NULL AND ${table.environmentId} IS NULL)`,
|
|
295
295
|
),
|
|
296
296
|
check('vault_grants_environment_slug_check', sql`${table.environmentSlug} IS NULL OR ${table.environmentSlug} ~ '^[a-z0-9][a-z0-9-]{0,62}$'`),
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
sql`(${table.environmentId} IS NULL AND ${table.environmentSlug} IS NULL) OR ${table.role} IN (${ENVIRONMENT_ROLES})`,
|
|
301
|
-
),
|
|
297
|
+
// A role's id, one that ships or one an Owner made (`ROLE_ID`): which the instance's policy defines, and which of them an
|
|
298
|
+
// environment may be granted, the vault decides, as the policy is now (docs/design/roles.md).
|
|
299
|
+
check('vault_grants_role_check', sql`${table.role} ~ '^[a-z][a-z0-9-]{1,31}$'`),
|
|
302
300
|
unique('vault_grants_on_project').on(table.principal, table.projectId),
|
|
303
301
|
unique('vault_grants_on_environment').on(table.principal, table.environmentId),
|
|
304
302
|
unique('vault_grants_on_environment_slug').on(table.principal, table.environmentSlug),
|