@geekmidas/constructs 10.0.0-alpha.116 → 10.0.0-alpha.118
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/adaptors/testing.cjs +1 -1
- package/dist/adaptors/testing.d.cts +1 -1
- package/dist/adaptors/testing.d.mts +1 -1
- package/dist/adaptors/testing.mjs +1 -1
- package/dist/cipher-C1lEFX63.mjs.map +1 -1
- package/dist/cipher-CVxfwLjk.cjs.map +1 -1
- package/dist/cipher-DSDxPRIp.d.cts +106 -0
- package/dist/cipher-DSDxPRIp.d.cts.map +1 -0
- package/dist/cipher-DSDxPRIp.d.mts +106 -0
- package/dist/cipher-DSDxPRIp.d.mts.map +1 -0
- package/dist/database/kysely.cjs +1 -1
- package/dist/database/kysely.d.cts +2 -2
- package/dist/database/kysely.d.mts +2 -2
- package/dist/database/kysely.mjs +1 -1
- package/dist/{kms-B-Hm3d5r.cjs → encryption/kms.cjs} +14 -4
- package/dist/encryption/kms.cjs.map +1 -0
- package/dist/encryption/kms.d.cts +7 -0
- package/dist/encryption/kms.d.cts.map +1 -0
- package/dist/encryption/kms.d.mts +7 -0
- package/dist/encryption/kms.d.mts.map +1 -0
- package/dist/{kms-BdUlMJEZ.mjs → encryption/kms.mjs} +13 -5
- package/dist/encryption/kms.mjs.map +1 -0
- package/dist/encryption.cjs +24 -4
- package/dist/encryption.cjs.map +1 -1
- package/dist/encryption.d.cts +8 -88
- package/dist/encryption.d.cts.map +1 -1
- package/dist/encryption.d.mts +8 -88
- package/dist/encryption.d.mts.map +1 -1
- package/dist/encryption.mjs +23 -5
- package/dist/encryption.mjs.map +1 -1
- package/dist/{kysely-D0bpzgPs.d.cts → kysely-BHYV-WC-.d.cts} +53 -12
- package/dist/kysely-BHYV-WC-.d.cts.map +1 -0
- package/dist/{kysely-DKwnl0nf.d.mts → kysely-CAW3QSqp.d.mts} +53 -12
- package/dist/kysely-CAW3QSqp.d.mts.map +1 -0
- package/dist/{kysely-B_0BAr0T.mjs → kysely-Cs4uPO8k.mjs} +6 -5
- package/dist/kysely-Cs4uPO8k.mjs.map +1 -0
- package/dist/{kysely-B3zfg9ti.cjs → kysely-CukArHJ0.cjs} +6 -5
- package/dist/kysely-CukArHJ0.cjs.map +1 -0
- package/package.json +41 -31
- package/dist/kms-B-Hm3d5r.cjs.map +0 -1
- package/dist/kms-BdUlMJEZ.mjs.map +0 -1
- package/dist/kysely-B3zfg9ti.cjs.map +0 -1
- package/dist/kysely-B_0BAr0T.mjs.map +0 -1
- package/dist/kysely-D0bpzgPs.d.cts.map +0 -1
- package/dist/kysely-DKwnl0nf.d.mts.map +0 -1
package/dist/encryption.cjs
CHANGED
|
@@ -34,6 +34,25 @@ let _geekmidas_manifest = require("@geekmidas/manifest");
|
|
|
34
34
|
* `gkm encryption:retire` — and a value still under an old key says so when it
|
|
35
35
|
* is decrypted, once per key per process.
|
|
36
36
|
*/
|
|
37
|
+
const drivers = /* @__PURE__ */ new Map();
|
|
38
|
+
/** Make a cipher available for its scheme. Idempotent. */
|
|
39
|
+
function registerCipherDriver(driver) {
|
|
40
|
+
drivers.set(driver.scheme, driver);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* A key whose URL names a cipher this process registered no driver for —
|
|
44
|
+
* `kms:` in an entry built for a target whose keys are local.
|
|
45
|
+
*/
|
|
46
|
+
var UnregisteredEncryptionScheme = class extends Error {
|
|
47
|
+
construct;
|
|
48
|
+
scheme;
|
|
49
|
+
constructor(construct, scheme) {
|
|
50
|
+
super(`${construct}'s key is a '${scheme}' URL, and this build registered no cipher for it. ` + (scheme === _geekmidas_manifest.KMS_SCHEME ? "A KMS key is an AWS target's: build for it (gkm build --provider aws), which registers the KMS cipher and needs @aws-sdk/client-kms installed." : "Register its driver with registerCipherDriver before the first request."));
|
|
51
|
+
this.construct = construct;
|
|
52
|
+
this.scheme = scheme;
|
|
53
|
+
this.name = "UnregisteredEncryptionScheme";
|
|
54
|
+
}
|
|
55
|
+
};
|
|
37
56
|
var Encryption = class {
|
|
38
57
|
id;
|
|
39
58
|
service;
|
|
@@ -71,10 +90,9 @@ var Encryption = class {
|
|
|
71
90
|
const { url } = options.envParser.create((get) => ({ url: get(this.key).string() })).parse();
|
|
72
91
|
const scheme = url.slice(0, url.indexOf(":") + 1);
|
|
73
92
|
if (scheme === "aes256gcm:") return require_cipher.keyringCipher(this.id, url, (stale, current) => console.warn(`${this.id} decrypted a value still under '${stale}' (current: '${current}'). reencrypt it, and retire '${stale}' once nothing is left under it.`));
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
}
|
|
93
|
+
const driver = drivers.get(scheme);
|
|
94
|
+
if (driver) return driver.open(this.id, url);
|
|
95
|
+
if (scheme === _geekmidas_manifest.KMS_SCHEME) throw new UnregisteredEncryptionScheme(this.id, scheme);
|
|
78
96
|
throw new require_cipher.UnknownEncryptionScheme(this.id, scheme);
|
|
79
97
|
}
|
|
80
98
|
};
|
|
@@ -86,10 +104,12 @@ exports.Encryption = Encryption;
|
|
|
86
104
|
exports.EncryptionKeyRetired = require_cipher.EncryptionKeyRetired;
|
|
87
105
|
exports.EncryptionUrlInvalid = require_cipher.EncryptionUrlInvalid;
|
|
88
106
|
exports.UnknownEncryptionScheme = require_cipher.UnknownEncryptionScheme;
|
|
107
|
+
exports.UnregisteredEncryptionScheme = UnregisteredEncryptionScheme;
|
|
89
108
|
exports.formatKeyring = require_cipher.formatKeyring;
|
|
90
109
|
exports.generateKeyring = require_cipher.generateKeyring;
|
|
91
110
|
exports.keyringCipher = require_cipher.keyringCipher;
|
|
92
111
|
exports.parseKeyring = require_cipher.parseKeyring;
|
|
112
|
+
exports.registerCipherDriver = registerCipherDriver;
|
|
93
113
|
exports.retireKey = require_cipher.retireKey;
|
|
94
114
|
exports.rotateKeyring = require_cipher.rotateKeyring;
|
|
95
115
|
|
package/dist/encryption.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"encryption.cjs","names":["canonicalId","provideKey","serviceKey","keyringCipher","
|
|
1
|
+
{"version":3,"file":"encryption.cjs","names":["KMS_SCHEME","canonicalId","provideKey","serviceKey","keyringCipher","UnknownEncryptionScheme"],"sources":["../src/encryption.ts"],"sourcesContent":["/**\n * `Encryption` — a key that encrypts what the application stores.\n *\n * ```ts\n * export const pii = new Encryption('Pii');\n *\n * // an endpoint that `.dependsOn([pii])`\n * const email = await services.pii.encrypt(body.email);\n * const emailIndex = await services.pii.index(body.email);\n * await db.insertInto('users').values({ email, emailIndex }).execute();\n *\n * // and to find them again, without decrypting a column\n * db.selectFrom('users').where('emailIndex', '=', await services.pii.index(q));\n * ```\n *\n * The app names no cipher and holds no key. The construct provides one URL,\n * `<ID>_URL`, and its scheme picks the backend the way a queue's connection\n * string picks its broker:\n *\n * - **AWS** — `kms://`: envelope encryption under a KMS key that rotates\n * yearly, and a KMS HMAC key for the index. Only a function that depends on\n * the construct is granted either key.\n * - **Server, local, tests** — `aes256gcm://`: a keyring the process holds.\n * Derived locally, so `gkm dev` and `gkm test` need nothing set; generated\n * into a server stage's secrets on its first deploy, and rotated there with\n * `gkm encryption:rotate`.\n *\n * Every ciphertext names the key that wrote it, so a rotated key still opens\n * what it wrote. Retiring one is explicit — `reencrypt` the column, then\n * `gkm encryption:retire` — and a value still under an old key says so when it\n * is decrypted, once per key per process.\n */\n\nimport {\n\ttype ConstructName,\n\tcanonicalId,\n\ttype Declaration,\n\tKMS_SCHEME,\n\tprovideKey,\n\tserviceKey,\n} from '@geekmidas/manifest';\nimport type { Service, ServiceRegisterOptions } from '@geekmidas/services';\nimport type { Construct } from './construct-interface';\nimport {\n\ttype Cipher,\n\ttype CipherDriver,\n\tkeyringCipher,\n\tLOCAL_SCHEME,\n\tUnknownEncryptionScheme,\n} from './encryption/cipher';\n\nexport type { Cipher, CipherDriver } from './encryption/cipher';\nexport {\n\tCiphertextMalformed,\n\tCiphertextNotAuthentic,\n\tCurrentKeyCannotRetire,\n\tEncryptionKeyRetired,\n\tEncryptionUrlInvalid,\n\tformatKeyring,\n\tgenerateKeyring,\n\ttype Keyring,\n\tkeyringCipher,\n\tparseKeyring,\n\tretireKey,\n\trotateKeyring,\n\tUnknownEncryptionScheme,\n} from './encryption/cipher';\n\nconst drivers = new Map<string, CipherDriver>();\n\n/** Make a cipher available for its scheme. Idempotent. */\nexport function registerCipherDriver(driver: CipherDriver): void {\n\tdrivers.set(driver.scheme, driver);\n}\n\n/**\n * A key whose URL names a cipher this process registered no driver for —\n * `kms:` in an entry built for a target whose keys are local.\n */\nexport class UnregisteredEncryptionScheme extends Error {\n\tconstructor(\n\t\treadonly construct: string,\n\t\treadonly scheme: string,\n\t) {\n\t\tsuper(\n\t\t\t`${construct}'s key is a '${scheme}' URL, and this build registered no cipher for it. ` +\n\t\t\t\t(scheme === KMS_SCHEME\n\t\t\t\t\t? \"A KMS key is an AWS target's: build for it (gkm build --provider aws), which registers the KMS cipher and needs @aws-sdk/client-kms installed.\"\n\t\t\t\t\t: 'Register its driver with registerCipherDriver before the first request.'),\n\t\t);\n\t\tthis.name = 'UnregisteredEncryptionScheme';\n\t}\n}\n\nexport class Encryption<TName extends string = string>\n\timplements Construct<TName, Cipher>\n{\n\treadonly id: TName;\n\treadonly service: Service<Uncapitalize<TName>, Cipher>;\n\n\t/**\n\t * Declared once and read by both `declare()` and `connect()`, so the key the\n\t * target publishes and the key the client reads cannot drift.\n\t */\n\tprivate readonly key: string;\n\n\t/**\n\t * One cipher per process, held as the promise: a keyring is parsed once and\n\t * a KMS client is built once, however many handlers register it at once.\n\t */\n\tprivate cipher?: Promise<Cipher>;\n\n\tconstructor(id: ConstructName<TName>) {\n\t\tconst canonical = canonicalId(id as string);\n\n\t\tthis.id = canonical as TName;\n\t\tthis.key = provideKey(canonical, 'url');\n\n\t\t// A field, not a getter: consumers cache services by object identity.\n\t\tthis.service = {\n\t\t\tserviceName: serviceKey(canonical) as Uncapitalize<TName>,\n\t\t\tregister: (options) => this.connect(options),\n\t\t};\n\t}\n\n\tdeclare(): Declaration[] {\n\t\treturn [{ kind: 'encryption', id: this.id, provides: [this.key] }];\n\t}\n\n\tprivate connect(options: ServiceRegisterOptions): Promise<Cipher> {\n\t\tthis.cipher ??= this.open(options);\n\t\treturn this.cipher;\n\t}\n\n\tprivate async open(options: ServiceRegisterOptions): Promise<Cipher> {\n\t\tconst { url } = options.envParser\n\t\t\t.create((get) => ({ url: get(this.key).string() }))\n\t\t\t.parse();\n\t\tconst scheme = url.slice(0, url.indexOf(':') + 1);\n\n\t\tif (scheme === LOCAL_SCHEME) {\n\t\t\treturn keyringCipher(this.id, url, (stale, current) =>\n\t\t\t\tconsole.warn(\n\t\t\t\t\t`${this.id} decrypted a value still under '${stale}' (current: '${current}'). reencrypt it, and retire '${stale}' once nothing is left under it.`,\n\t\t\t\t),\n\t\t\t);\n\t\t}\n\n\t\t// Registered by the entry a build generates, only for a target whose\n\t\t// keys live there — so no other process imports that cipher's SDK.\n\t\tconst driver = drivers.get(scheme);\n\t\tif (driver) return driver.open(this.id, url);\n\t\tif (scheme === KMS_SCHEME) {\n\t\t\tthrow new UnregisteredEncryptionScheme(this.id, scheme);\n\t\t}\n\n\t\tthrow new UnknownEncryptionScheme(this.id, scheme);\n\t}\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoEA,MAAM,0BAAU,IAAI,IAA0B;;AAG9C,SAAgB,qBAAqB,QAA4B;CAChE,QAAQ,IAAI,OAAO,QAAQ,MAAM;AAClC;;;;;AAMA,IAAa,+BAAb,cAAkD,MAAM;CAE7C;CACA;CAFV,YACC,WACA,QACC;EACD,MACC,GAAG,UAAU,eAAe,OAAO,wDACjC,WAAWA,oBAAAA,aACT,mJACA,0EACL;EARS,KAAA,YAAA;EACA,KAAA,SAAA;EAQT,KAAK,OAAO;CACb;AACD;AAEA,IAAa,aAAb,MAEA;CACC;CACA;;;;;CAMA;;;;;CAMA;CAEA,YAAY,IAA0B;EACrC,MAAM,aAAA,GAAYC,oBAAAA,YAAAA,CAAY,EAAY;EAE1C,KAAK,KAAK;EACV,KAAK,OAAA,GAAMC,oBAAAA,WAAAA,CAAW,WAAW,KAAK;EAGtC,KAAK,UAAU;GACd,cAAA,GAAaC,oBAAAA,WAAAA,CAAW,SAAS;GACjC,WAAW,YAAY,KAAK,QAAQ,OAAO;EAC5C;CACD;CAEA,UAAyB;EACxB,OAAO,CAAC;GAAE,MAAM;GAAc,IAAI,KAAK;GAAI,UAAU,CAAC,KAAK,GAAG;EAAE,CAAC;CAClE;CAEA,QAAgB,SAAkD;EACjE,KAAK,WAAW,KAAK,KAAK,OAAO;EACjC,OAAO,KAAK;CACb;CAEA,MAAc,KAAK,SAAkD;EACpE,MAAM,EAAE,QAAQ,QAAQ,UACtB,QAAQ,SAAS,EAAE,KAAK,IAAI,KAAK,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAClD,MAAM;EACR,MAAM,SAAS,IAAI,MAAM,GAAG,IAAI,QAAQ,GAAG,IAAI,CAAC;EAEhD,IAAI,WAAA,cACH,OAAOC,eAAAA,cAAc,KAAK,IAAI,MAAM,OAAO,YAC1C,QAAQ,KACP,GAAG,KAAK,GAAG,kCAAkC,MAAM,eAAe,QAAQ,gCAAgC,MAAM,iCACjH,CACD;EAKD,MAAM,SAAS,QAAQ,IAAI,MAAM;EACjC,IAAI,QAAQ,OAAO,OAAO,KAAK,KAAK,IAAI,GAAG;EAC3C,IAAI,WAAWJ,oBAAAA,YACd,MAAM,IAAI,6BAA6B,KAAK,IAAI,MAAM;EAGvD,MAAM,IAAIK,eAAAA,wBAAwB,KAAK,IAAI,MAAM;CAClD;AACD"}
|
package/dist/encryption.d.cts
CHANGED
|
@@ -1,99 +1,19 @@
|
|
|
1
1
|
import { n as Construct } from "./construct-interface-B1AfqRl4.cjs";
|
|
2
|
+
import { a as CurrentKeyCannotRetire, c as Keyring, d as generateKeyring, f as keyringCipher, h as rotateKeyring, i as CiphertextNotAuthentic, l as UnknownEncryptionScheme, m as retireKey, n as CipherDriver, o as EncryptionKeyRetired, p as parseKeyring, r as CiphertextMalformed, s as EncryptionUrlInvalid, t as Cipher, u as formatKeyring } from "./cipher-DSDxPRIp.cjs";
|
|
2
3
|
import { Service } from "@geekmidas/services";
|
|
3
4
|
import { ConstructName, Declaration } from "@geekmidas/manifest";
|
|
4
|
-
//#region src/encryption
|
|
5
|
+
//#region src/encryption.d.ts
|
|
6
|
+
/** Make a cipher available for its scheme. Idempotent. */
|
|
7
|
+
export declare function registerCipherDriver(driver: CipherDriver): void;
|
|
5
8
|
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* Every ciphertext names the key that wrote it — `gkm1.<keyId>.<payload>` — so
|
|
9
|
-
* `decrypt` never has to guess, a rotated key still opens what it wrote, and
|
|
10
|
-
* retiring a key is a decision made against data rather than a hope. The
|
|
11
|
-
* format version leads, so the day it has to change, both can be read.
|
|
12
|
-
*
|
|
13
|
-
* Two backends, picked by the scheme of the construct's URL, the way a queue's
|
|
14
|
-
* connection string picks its broker:
|
|
15
|
-
*
|
|
16
|
-
* - `aes256gcm://` — a keyring this process holds: AES-256-GCM with the newest
|
|
17
|
-
* key, HMAC-SHA256 for the blind index. Local, test and server stages.
|
|
18
|
-
* - `kms://` — envelope encryption under an AWS KMS key, and a KMS HMAC key for
|
|
19
|
-
* the index. The key material never leaves KMS. See `./kms`.
|
|
9
|
+
* A key whose URL names a cipher this process registered no driver for —
|
|
10
|
+
* `kms:` in an entry built for a target whose keys are local.
|
|
20
11
|
*/
|
|
21
|
-
|
|
22
|
-
interface Cipher {
|
|
23
|
-
/** Plaintext in, a self-describing ciphertext string out. */
|
|
24
|
-
encrypt(plaintext: string): Promise<string>;
|
|
25
|
-
/** Opens any ciphertext this construct wrote, under whichever key wrote it. */
|
|
26
|
-
decrypt(ciphertext: string): Promise<string>;
|
|
27
|
-
/**
|
|
28
|
-
* A blind index: the same value always gives the same string, so an
|
|
29
|
-
* encrypted column can still be looked up by an indexed column beside it.
|
|
30
|
-
*
|
|
31
|
-
* Deterministic on purpose, which is also why its key never rotates — a
|
|
32
|
-
* rotated index key would make every stored index miss.
|
|
33
|
-
*/
|
|
34
|
-
index(value: string): Promise<string>;
|
|
35
|
-
/**
|
|
36
|
-
* The same plaintext under the current key — what a sweep runs over a
|
|
37
|
-
* column before an old key is retired. Unchanged when it is already current.
|
|
38
|
-
*/
|
|
39
|
-
reencrypt(ciphertext: string): Promise<string>;
|
|
40
|
-
}
|
|
41
|
-
export declare class EncryptionUrlInvalid extends Error {
|
|
42
|
-
readonly construct: string;
|
|
43
|
-
readonly reason: string;
|
|
44
|
-
constructor(construct: string, reason: string);
|
|
45
|
-
}
|
|
46
|
-
export declare class UnknownEncryptionScheme extends Error {
|
|
12
|
+
export declare class UnregisteredEncryptionScheme extends Error {
|
|
47
13
|
readonly construct: string;
|
|
48
14
|
readonly scheme: string;
|
|
49
15
|
constructor(construct: string, scheme: string);
|
|
50
16
|
}
|
|
51
|
-
export declare class CiphertextMalformed extends Error {
|
|
52
|
-
readonly construct: string;
|
|
53
|
-
constructor(construct: string);
|
|
54
|
-
}
|
|
55
|
-
export declare class EncryptionKeyRetired extends Error {
|
|
56
|
-
readonly construct: string;
|
|
57
|
-
readonly keyId: string;
|
|
58
|
-
constructor(construct: string, keyId: string);
|
|
59
|
-
}
|
|
60
|
-
export declare class CiphertextNotAuthentic extends Error {
|
|
61
|
-
readonly construct: string;
|
|
62
|
-
constructor(construct: string);
|
|
63
|
-
}
|
|
64
|
-
export declare class CurrentKeyCannotRetire extends Error {
|
|
65
|
-
readonly construct: string;
|
|
66
|
-
readonly keyId: string;
|
|
67
|
-
constructor(construct: string, keyId: string);
|
|
68
|
-
}
|
|
69
|
-
/** A local keyring: newest key first, and the index key that never rotates. */
|
|
70
|
-
interface Keyring {
|
|
71
|
-
keys: {
|
|
72
|
-
id: string;
|
|
73
|
-
key: Buffer;
|
|
74
|
-
}[];
|
|
75
|
-
index: Buffer;
|
|
76
|
-
}
|
|
77
|
-
/** Parse `aes256gcm://local?keys=k2.<key>,k1.<key>&index=<key>`. */
|
|
78
|
-
export declare function parseKeyring(construct: string, url: string): Keyring;
|
|
79
|
-
export declare function formatKeyring(keyring: Keyring): string;
|
|
80
|
-
/** A keyring with one fresh key, `k1`, and a fresh index key. */
|
|
81
|
-
export declare function generateKeyring(): string;
|
|
82
|
-
/**
|
|
83
|
-
* The keyring with a new current key in front, and every older one kept.
|
|
84
|
-
*
|
|
85
|
-
* Nothing is dropped: what the old keys wrote still has to open until a sweep
|
|
86
|
-
* has re-encrypted it — see {@link retireKey}.
|
|
87
|
-
*/
|
|
88
|
-
export declare function rotateKeyring(construct: string, url: string): string;
|
|
89
|
-
/** The keyring without `keyId`, which must not be the current key. */
|
|
90
|
-
export declare function retireKey(construct: string, url: string, keyId: string): string;
|
|
91
|
-
/** Says, once per key per process, that a value is still under an old key. */
|
|
92
|
-
type StaleKeyReporter = (keyId: string, current: string) => void;
|
|
93
|
-
/** The `aes256gcm://` cipher: a keyring held in process. */
|
|
94
|
-
export declare function keyringCipher(construct: string, url: string, onStaleKey?: StaleKeyReporter): Cipher;
|
|
95
|
-
//#endregion
|
|
96
|
-
//#region src/encryption.d.ts
|
|
97
17
|
export declare class Encryption<TName extends string = string> implements Construct<TName, Cipher> {
|
|
98
18
|
readonly id: TName;
|
|
99
19
|
readonly service: Service<Uncapitalize<TName>, Cipher>;
|
|
@@ -113,5 +33,5 @@ export declare class Encryption<TName extends string = string> implements Constr
|
|
|
113
33
|
private open;
|
|
114
34
|
}
|
|
115
35
|
//#endregion
|
|
116
|
-
export type
|
|
36
|
+
export { type Cipher, type CipherDriver, CiphertextMalformed, CiphertextNotAuthentic, CurrentKeyCannotRetire, EncryptionKeyRetired, EncryptionUrlInvalid, type Keyring, UnknownEncryptionScheme, formatKeyring, generateKeyring, keyringCipher, parseKeyring, retireKey, rotateKeyring };
|
|
117
37
|
//# sourceMappingURL=encryption.d.cts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"encryption.d.cts","names":[],"sources":["../src/encryption
|
|
1
|
+
{"version":3,"file":"encryption.d.cts","names":[],"sources":["../src/encryption.ts"],"mappings":";;;;;;wBAuEgB,qBAAqB,QAAQ;;;;;qBAQhC,qCAAqC;WAEvC;WACA;EAFV,YACU,mBACA;;qBAYE,WAAW,0CACZ,UAAU,OAAO;WAEnB,IAAI;WACJ,SAAS,QAAQ,aAAa,QAAQ;;;;;mBAM9B;;;;;UAMT;EAER,YAAY,IAAI,cAAc;EAa9B,WAAW;UAIH;UAKM"}
|
package/dist/encryption.d.mts
CHANGED
|
@@ -1,99 +1,19 @@
|
|
|
1
1
|
import { n as Construct } from "./construct-interface-ZvYQbivO.mjs";
|
|
2
|
+
import { a as CurrentKeyCannotRetire, c as Keyring, d as generateKeyring, f as keyringCipher, h as rotateKeyring, i as CiphertextNotAuthentic, l as UnknownEncryptionScheme, m as retireKey, n as CipherDriver, o as EncryptionKeyRetired, p as parseKeyring, r as CiphertextMalformed, s as EncryptionUrlInvalid, t as Cipher, u as formatKeyring } from "./cipher-DSDxPRIp.mjs";
|
|
2
3
|
import { ConstructName, Declaration } from "@geekmidas/manifest";
|
|
3
4
|
import { Service } from "@geekmidas/services";
|
|
4
|
-
//#region src/encryption
|
|
5
|
+
//#region src/encryption.d.ts
|
|
6
|
+
/** Make a cipher available for its scheme. Idempotent. */
|
|
7
|
+
export declare function registerCipherDriver(driver: CipherDriver): void;
|
|
5
8
|
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* Every ciphertext names the key that wrote it — `gkm1.<keyId>.<payload>` — so
|
|
9
|
-
* `decrypt` never has to guess, a rotated key still opens what it wrote, and
|
|
10
|
-
* retiring a key is a decision made against data rather than a hope. The
|
|
11
|
-
* format version leads, so the day it has to change, both can be read.
|
|
12
|
-
*
|
|
13
|
-
* Two backends, picked by the scheme of the construct's URL, the way a queue's
|
|
14
|
-
* connection string picks its broker:
|
|
15
|
-
*
|
|
16
|
-
* - `aes256gcm://` — a keyring this process holds: AES-256-GCM with the newest
|
|
17
|
-
* key, HMAC-SHA256 for the blind index. Local, test and server stages.
|
|
18
|
-
* - `kms://` — envelope encryption under an AWS KMS key, and a KMS HMAC key for
|
|
19
|
-
* the index. The key material never leaves KMS. See `./kms`.
|
|
9
|
+
* A key whose URL names a cipher this process registered no driver for —
|
|
10
|
+
* `kms:` in an entry built for a target whose keys are local.
|
|
20
11
|
*/
|
|
21
|
-
|
|
22
|
-
interface Cipher {
|
|
23
|
-
/** Plaintext in, a self-describing ciphertext string out. */
|
|
24
|
-
encrypt(plaintext: string): Promise<string>;
|
|
25
|
-
/** Opens any ciphertext this construct wrote, under whichever key wrote it. */
|
|
26
|
-
decrypt(ciphertext: string): Promise<string>;
|
|
27
|
-
/**
|
|
28
|
-
* A blind index: the same value always gives the same string, so an
|
|
29
|
-
* encrypted column can still be looked up by an indexed column beside it.
|
|
30
|
-
*
|
|
31
|
-
* Deterministic on purpose, which is also why its key never rotates — a
|
|
32
|
-
* rotated index key would make every stored index miss.
|
|
33
|
-
*/
|
|
34
|
-
index(value: string): Promise<string>;
|
|
35
|
-
/**
|
|
36
|
-
* The same plaintext under the current key — what a sweep runs over a
|
|
37
|
-
* column before an old key is retired. Unchanged when it is already current.
|
|
38
|
-
*/
|
|
39
|
-
reencrypt(ciphertext: string): Promise<string>;
|
|
40
|
-
}
|
|
41
|
-
export declare class EncryptionUrlInvalid extends Error {
|
|
42
|
-
readonly construct: string;
|
|
43
|
-
readonly reason: string;
|
|
44
|
-
constructor(construct: string, reason: string);
|
|
45
|
-
}
|
|
46
|
-
export declare class UnknownEncryptionScheme extends Error {
|
|
12
|
+
export declare class UnregisteredEncryptionScheme extends Error {
|
|
47
13
|
readonly construct: string;
|
|
48
14
|
readonly scheme: string;
|
|
49
15
|
constructor(construct: string, scheme: string);
|
|
50
16
|
}
|
|
51
|
-
export declare class CiphertextMalformed extends Error {
|
|
52
|
-
readonly construct: string;
|
|
53
|
-
constructor(construct: string);
|
|
54
|
-
}
|
|
55
|
-
export declare class EncryptionKeyRetired extends Error {
|
|
56
|
-
readonly construct: string;
|
|
57
|
-
readonly keyId: string;
|
|
58
|
-
constructor(construct: string, keyId: string);
|
|
59
|
-
}
|
|
60
|
-
export declare class CiphertextNotAuthentic extends Error {
|
|
61
|
-
readonly construct: string;
|
|
62
|
-
constructor(construct: string);
|
|
63
|
-
}
|
|
64
|
-
export declare class CurrentKeyCannotRetire extends Error {
|
|
65
|
-
readonly construct: string;
|
|
66
|
-
readonly keyId: string;
|
|
67
|
-
constructor(construct: string, keyId: string);
|
|
68
|
-
}
|
|
69
|
-
/** A local keyring: newest key first, and the index key that never rotates. */
|
|
70
|
-
interface Keyring {
|
|
71
|
-
keys: {
|
|
72
|
-
id: string;
|
|
73
|
-
key: Buffer;
|
|
74
|
-
}[];
|
|
75
|
-
index: Buffer;
|
|
76
|
-
}
|
|
77
|
-
/** Parse `aes256gcm://local?keys=k2.<key>,k1.<key>&index=<key>`. */
|
|
78
|
-
export declare function parseKeyring(construct: string, url: string): Keyring;
|
|
79
|
-
export declare function formatKeyring(keyring: Keyring): string;
|
|
80
|
-
/** A keyring with one fresh key, `k1`, and a fresh index key. */
|
|
81
|
-
export declare function generateKeyring(): string;
|
|
82
|
-
/**
|
|
83
|
-
* The keyring with a new current key in front, and every older one kept.
|
|
84
|
-
*
|
|
85
|
-
* Nothing is dropped: what the old keys wrote still has to open until a sweep
|
|
86
|
-
* has re-encrypted it — see {@link retireKey}.
|
|
87
|
-
*/
|
|
88
|
-
export declare function rotateKeyring(construct: string, url: string): string;
|
|
89
|
-
/** The keyring without `keyId`, which must not be the current key. */
|
|
90
|
-
export declare function retireKey(construct: string, url: string, keyId: string): string;
|
|
91
|
-
/** Says, once per key per process, that a value is still under an old key. */
|
|
92
|
-
type StaleKeyReporter = (keyId: string, current: string) => void;
|
|
93
|
-
/** The `aes256gcm://` cipher: a keyring held in process. */
|
|
94
|
-
export declare function keyringCipher(construct: string, url: string, onStaleKey?: StaleKeyReporter): Cipher;
|
|
95
|
-
//#endregion
|
|
96
|
-
//#region src/encryption.d.ts
|
|
97
17
|
export declare class Encryption<TName extends string = string> implements Construct<TName, Cipher> {
|
|
98
18
|
readonly id: TName;
|
|
99
19
|
readonly service: Service<Uncapitalize<TName>, Cipher>;
|
|
@@ -113,5 +33,5 @@ export declare class Encryption<TName extends string = string> implements Constr
|
|
|
113
33
|
private open;
|
|
114
34
|
}
|
|
115
35
|
//#endregion
|
|
116
|
-
export type
|
|
36
|
+
export { type Cipher, type CipherDriver, CiphertextMalformed, CiphertextNotAuthentic, CurrentKeyCannotRetire, EncryptionKeyRetired, EncryptionUrlInvalid, type Keyring, UnknownEncryptionScheme, formatKeyring, generateKeyring, keyringCipher, parseKeyring, retireKey, rotateKeyring };
|
|
117
37
|
//# sourceMappingURL=encryption.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"encryption.d.mts","names":[],"sources":["../src/encryption
|
|
1
|
+
{"version":3,"file":"encryption.d.mts","names":[],"sources":["../src/encryption.ts"],"mappings":";;;;;;wBAuEgB,qBAAqB,QAAQ;;;;;qBAQhC,qCAAqC;WAEvC;WACA;EAFV,YACU,mBACA;;qBAYE,WAAW,0CACZ,UAAU,OAAO;WAEnB,IAAI;WACJ,SAAS,QAAQ,aAAa,QAAQ;;;;;mBAM9B;;;;;UAMT;EAER,YAAY,IAAI,cAAc;EAa9B,WAAW;UAIH;UAKM"}
|
package/dist/encryption.mjs
CHANGED
|
@@ -33,6 +33,25 @@ import { KMS_SCHEME, canonicalId, provideKey, serviceKey } from "@geekmidas/mani
|
|
|
33
33
|
* `gkm encryption:retire` — and a value still under an old key says so when it
|
|
34
34
|
* is decrypted, once per key per process.
|
|
35
35
|
*/
|
|
36
|
+
const drivers = /* @__PURE__ */ new Map();
|
|
37
|
+
/** Make a cipher available for its scheme. Idempotent. */
|
|
38
|
+
function registerCipherDriver(driver) {
|
|
39
|
+
drivers.set(driver.scheme, driver);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* A key whose URL names a cipher this process registered no driver for —
|
|
43
|
+
* `kms:` in an entry built for a target whose keys are local.
|
|
44
|
+
*/
|
|
45
|
+
var UnregisteredEncryptionScheme = class extends Error {
|
|
46
|
+
construct;
|
|
47
|
+
scheme;
|
|
48
|
+
constructor(construct, scheme) {
|
|
49
|
+
super(`${construct}'s key is a '${scheme}' URL, and this build registered no cipher for it. ` + (scheme === KMS_SCHEME ? "A KMS key is an AWS target's: build for it (gkm build --provider aws), which registers the KMS cipher and needs @aws-sdk/client-kms installed." : "Register its driver with registerCipherDriver before the first request."));
|
|
50
|
+
this.construct = construct;
|
|
51
|
+
this.scheme = scheme;
|
|
52
|
+
this.name = "UnregisteredEncryptionScheme";
|
|
53
|
+
}
|
|
54
|
+
};
|
|
36
55
|
var Encryption = class {
|
|
37
56
|
id;
|
|
38
57
|
service;
|
|
@@ -70,14 +89,13 @@ var Encryption = class {
|
|
|
70
89
|
const { url } = options.envParser.create((get) => ({ url: get(this.key).string() })).parse();
|
|
71
90
|
const scheme = url.slice(0, url.indexOf(":") + 1);
|
|
72
91
|
if (scheme === "aes256gcm:") return keyringCipher(this.id, url, (stale, current) => console.warn(`${this.id} decrypted a value still under '${stale}' (current: '${current}'). reencrypt it, and retire '${stale}' once nothing is left under it.`));
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
}
|
|
92
|
+
const driver = drivers.get(scheme);
|
|
93
|
+
if (driver) return driver.open(this.id, url);
|
|
94
|
+
if (scheme === KMS_SCHEME) throw new UnregisteredEncryptionScheme(this.id, scheme);
|
|
77
95
|
throw new UnknownEncryptionScheme(this.id, scheme);
|
|
78
96
|
}
|
|
79
97
|
};
|
|
80
98
|
//#endregion
|
|
81
|
-
export { CiphertextMalformed, CiphertextNotAuthentic, CurrentKeyCannotRetire, Encryption, EncryptionKeyRetired, EncryptionUrlInvalid, UnknownEncryptionScheme, formatKeyring, generateKeyring, keyringCipher, parseKeyring, retireKey, rotateKeyring };
|
|
99
|
+
export { CiphertextMalformed, CiphertextNotAuthentic, CurrentKeyCannotRetire, Encryption, EncryptionKeyRetired, EncryptionUrlInvalid, UnknownEncryptionScheme, UnregisteredEncryptionScheme, formatKeyring, generateKeyring, keyringCipher, parseKeyring, registerCipherDriver, retireKey, rotateKeyring };
|
|
82
100
|
|
|
83
101
|
//# sourceMappingURL=encryption.mjs.map
|
package/dist/encryption.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"encryption.mjs","names":[],"sources":["../src/encryption.ts"],"sourcesContent":["/**\n * `Encryption` — a key that encrypts what the application stores.\n *\n * ```ts\n * export const pii = new Encryption('Pii');\n *\n * // an endpoint that `.dependsOn([pii])`\n * const email = await services.pii.encrypt(body.email);\n * const emailIndex = await services.pii.index(body.email);\n * await db.insertInto('users').values({ email, emailIndex }).execute();\n *\n * // and to find them again, without decrypting a column\n * db.selectFrom('users').where('emailIndex', '=', await services.pii.index(q));\n * ```\n *\n * The app names no cipher and holds no key. The construct provides one URL,\n * `<ID>_URL`, and its scheme picks the backend the way a queue's connection\n * string picks its broker:\n *\n * - **AWS** — `kms://`: envelope encryption under a KMS key that rotates\n * yearly, and a KMS HMAC key for the index. Only a function that depends on\n * the construct is granted either key.\n * - **Server, local, tests** — `aes256gcm://`: a keyring the process holds.\n * Derived locally, so `gkm dev` and `gkm test` need nothing set; generated\n * into a server stage's secrets on its first deploy, and rotated there with\n * `gkm encryption:rotate`.\n *\n * Every ciphertext names the key that wrote it, so a rotated key still opens\n * what it wrote. Retiring one is explicit — `reencrypt` the column, then\n * `gkm encryption:retire` — and a value still under an old key says so when it\n * is decrypted, once per key per process.\n */\n\nimport {\n\ttype ConstructName,\n\tcanonicalId,\n\ttype Declaration,\n\tKMS_SCHEME,\n\tprovideKey,\n\tserviceKey,\n} from '@geekmidas/manifest';\nimport type { Service, ServiceRegisterOptions } from '@geekmidas/services';\nimport type { Construct } from './construct-interface';\nimport {\n\ttype Cipher,\n\tkeyringCipher,\n\tLOCAL_SCHEME,\n\tUnknownEncryptionScheme,\n} from './encryption/cipher';\n\nexport type { Cipher } from './encryption/cipher';\nexport {\n\tCiphertextMalformed,\n\tCiphertextNotAuthentic,\n\tCurrentKeyCannotRetire,\n\tEncryptionKeyRetired,\n\tEncryptionUrlInvalid,\n\tformatKeyring,\n\tgenerateKeyring,\n\ttype Keyring,\n\tkeyringCipher,\n\tparseKeyring,\n\tretireKey,\n\trotateKeyring,\n\tUnknownEncryptionScheme,\n} from './encryption/cipher';\n\nexport class Encryption<TName extends string = string>\n\timplements Construct<TName, Cipher>\n{\n\treadonly id: TName;\n\treadonly service: Service<Uncapitalize<TName>, Cipher>;\n\n\t/**\n\t * Declared once and read by both `declare()` and `connect()`, so the key the\n\t * target publishes and the key the client reads cannot drift.\n\t */\n\tprivate readonly key: string;\n\n\t/**\n\t * One cipher per process, held as the promise: a keyring is parsed once and\n\t * a KMS client is built once, however many handlers register it at once.\n\t */\n\tprivate cipher?: Promise<Cipher>;\n\n\tconstructor(id: ConstructName<TName>) {\n\t\tconst canonical = canonicalId(id as string);\n\n\t\tthis.id = canonical as TName;\n\t\tthis.key = provideKey(canonical, 'url');\n\n\t\t// A field, not a getter: consumers cache services by object identity.\n\t\tthis.service = {\n\t\t\tserviceName: serviceKey(canonical) as Uncapitalize<TName>,\n\t\t\tregister: (options) => this.connect(options),\n\t\t};\n\t}\n\n\tdeclare(): Declaration[] {\n\t\treturn [{ kind: 'encryption', id: this.id, provides: [this.key] }];\n\t}\n\n\tprivate connect(options: ServiceRegisterOptions): Promise<Cipher> {\n\t\tthis.cipher ??= this.open(options);\n\t\treturn this.cipher;\n\t}\n\n\tprivate async open(options: ServiceRegisterOptions): Promise<Cipher> {\n\t\tconst { url } = options.envParser\n\t\t\t.create((get) => ({ url: get(this.key).string() }))\n\t\t\t.parse();\n\t\tconst scheme = url.slice(0, url.indexOf(':') + 1);\n\n\t\tif (scheme === LOCAL_SCHEME) {\n\t\t\treturn keyringCipher(this.id, url, (stale, current) =>\n\t\t\t\tconsole.warn(\n\t\t\t\t\t`${this.id} decrypted a value still under '${stale}' (current: '${current}'). reencrypt it, and retire '${stale}' once nothing is left under it.`,\n\t\t\t\t),\n\t\t\t);\n\t\t}\n\n\t\
|
|
1
|
+
{"version":3,"file":"encryption.mjs","names":[],"sources":["../src/encryption.ts"],"sourcesContent":["/**\n * `Encryption` — a key that encrypts what the application stores.\n *\n * ```ts\n * export const pii = new Encryption('Pii');\n *\n * // an endpoint that `.dependsOn([pii])`\n * const email = await services.pii.encrypt(body.email);\n * const emailIndex = await services.pii.index(body.email);\n * await db.insertInto('users').values({ email, emailIndex }).execute();\n *\n * // and to find them again, without decrypting a column\n * db.selectFrom('users').where('emailIndex', '=', await services.pii.index(q));\n * ```\n *\n * The app names no cipher and holds no key. The construct provides one URL,\n * `<ID>_URL`, and its scheme picks the backend the way a queue's connection\n * string picks its broker:\n *\n * - **AWS** — `kms://`: envelope encryption under a KMS key that rotates\n * yearly, and a KMS HMAC key for the index. Only a function that depends on\n * the construct is granted either key.\n * - **Server, local, tests** — `aes256gcm://`: a keyring the process holds.\n * Derived locally, so `gkm dev` and `gkm test` need nothing set; generated\n * into a server stage's secrets on its first deploy, and rotated there with\n * `gkm encryption:rotate`.\n *\n * Every ciphertext names the key that wrote it, so a rotated key still opens\n * what it wrote. Retiring one is explicit — `reencrypt` the column, then\n * `gkm encryption:retire` — and a value still under an old key says so when it\n * is decrypted, once per key per process.\n */\n\nimport {\n\ttype ConstructName,\n\tcanonicalId,\n\ttype Declaration,\n\tKMS_SCHEME,\n\tprovideKey,\n\tserviceKey,\n} from '@geekmidas/manifest';\nimport type { Service, ServiceRegisterOptions } from '@geekmidas/services';\nimport type { Construct } from './construct-interface';\nimport {\n\ttype Cipher,\n\ttype CipherDriver,\n\tkeyringCipher,\n\tLOCAL_SCHEME,\n\tUnknownEncryptionScheme,\n} from './encryption/cipher';\n\nexport type { Cipher, CipherDriver } from './encryption/cipher';\nexport {\n\tCiphertextMalformed,\n\tCiphertextNotAuthentic,\n\tCurrentKeyCannotRetire,\n\tEncryptionKeyRetired,\n\tEncryptionUrlInvalid,\n\tformatKeyring,\n\tgenerateKeyring,\n\ttype Keyring,\n\tkeyringCipher,\n\tparseKeyring,\n\tretireKey,\n\trotateKeyring,\n\tUnknownEncryptionScheme,\n} from './encryption/cipher';\n\nconst drivers = new Map<string, CipherDriver>();\n\n/** Make a cipher available for its scheme. Idempotent. */\nexport function registerCipherDriver(driver: CipherDriver): void {\n\tdrivers.set(driver.scheme, driver);\n}\n\n/**\n * A key whose URL names a cipher this process registered no driver for —\n * `kms:` in an entry built for a target whose keys are local.\n */\nexport class UnregisteredEncryptionScheme extends Error {\n\tconstructor(\n\t\treadonly construct: string,\n\t\treadonly scheme: string,\n\t) {\n\t\tsuper(\n\t\t\t`${construct}'s key is a '${scheme}' URL, and this build registered no cipher for it. ` +\n\t\t\t\t(scheme === KMS_SCHEME\n\t\t\t\t\t? \"A KMS key is an AWS target's: build for it (gkm build --provider aws), which registers the KMS cipher and needs @aws-sdk/client-kms installed.\"\n\t\t\t\t\t: 'Register its driver with registerCipherDriver before the first request.'),\n\t\t);\n\t\tthis.name = 'UnregisteredEncryptionScheme';\n\t}\n}\n\nexport class Encryption<TName extends string = string>\n\timplements Construct<TName, Cipher>\n{\n\treadonly id: TName;\n\treadonly service: Service<Uncapitalize<TName>, Cipher>;\n\n\t/**\n\t * Declared once and read by both `declare()` and `connect()`, so the key the\n\t * target publishes and the key the client reads cannot drift.\n\t */\n\tprivate readonly key: string;\n\n\t/**\n\t * One cipher per process, held as the promise: a keyring is parsed once and\n\t * a KMS client is built once, however many handlers register it at once.\n\t */\n\tprivate cipher?: Promise<Cipher>;\n\n\tconstructor(id: ConstructName<TName>) {\n\t\tconst canonical = canonicalId(id as string);\n\n\t\tthis.id = canonical as TName;\n\t\tthis.key = provideKey(canonical, 'url');\n\n\t\t// A field, not a getter: consumers cache services by object identity.\n\t\tthis.service = {\n\t\t\tserviceName: serviceKey(canonical) as Uncapitalize<TName>,\n\t\t\tregister: (options) => this.connect(options),\n\t\t};\n\t}\n\n\tdeclare(): Declaration[] {\n\t\treturn [{ kind: 'encryption', id: this.id, provides: [this.key] }];\n\t}\n\n\tprivate connect(options: ServiceRegisterOptions): Promise<Cipher> {\n\t\tthis.cipher ??= this.open(options);\n\t\treturn this.cipher;\n\t}\n\n\tprivate async open(options: ServiceRegisterOptions): Promise<Cipher> {\n\t\tconst { url } = options.envParser\n\t\t\t.create((get) => ({ url: get(this.key).string() }))\n\t\t\t.parse();\n\t\tconst scheme = url.slice(0, url.indexOf(':') + 1);\n\n\t\tif (scheme === LOCAL_SCHEME) {\n\t\t\treturn keyringCipher(this.id, url, (stale, current) =>\n\t\t\t\tconsole.warn(\n\t\t\t\t\t`${this.id} decrypted a value still under '${stale}' (current: '${current}'). reencrypt it, and retire '${stale}' once nothing is left under it.`,\n\t\t\t\t),\n\t\t\t);\n\t\t}\n\n\t\t// Registered by the entry a build generates, only for a target whose\n\t\t// keys live there — so no other process imports that cipher's SDK.\n\t\tconst driver = drivers.get(scheme);\n\t\tif (driver) return driver.open(this.id, url);\n\t\tif (scheme === KMS_SCHEME) {\n\t\t\tthrow new UnregisteredEncryptionScheme(this.id, scheme);\n\t\t}\n\n\t\tthrow new UnknownEncryptionScheme(this.id, scheme);\n\t}\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoEA,MAAM,0BAAU,IAAI,IAA0B;;AAG9C,SAAgB,qBAAqB,QAA4B;CAChE,QAAQ,IAAI,OAAO,QAAQ,MAAM;AAClC;;;;;AAMA,IAAa,+BAAb,cAAkD,MAAM;CAE7C;CACA;CAFV,YACC,WACA,QACC;EACD,MACC,GAAG,UAAU,eAAe,OAAO,wDACjC,WAAW,aACT,mJACA,0EACL;EARS,KAAA,YAAA;EACA,KAAA,SAAA;EAQT,KAAK,OAAO;CACb;AACD;AAEA,IAAa,aAAb,MAEA;CACC;CACA;;;;;CAMA;;;;;CAMA;CAEA,YAAY,IAA0B;EACrC,MAAM,YAAY,YAAY,EAAY;EAE1C,KAAK,KAAK;EACV,KAAK,MAAM,WAAW,WAAW,KAAK;EAGtC,KAAK,UAAU;GACd,aAAa,WAAW,SAAS;GACjC,WAAW,YAAY,KAAK,QAAQ,OAAO;EAC5C;CACD;CAEA,UAAyB;EACxB,OAAO,CAAC;GAAE,MAAM;GAAc,IAAI,KAAK;GAAI,UAAU,CAAC,KAAK,GAAG;EAAE,CAAC;CAClE;CAEA,QAAgB,SAAkD;EACjE,KAAK,WAAW,KAAK,KAAK,OAAO;EACjC,OAAO,KAAK;CACb;CAEA,MAAc,KAAK,SAAkD;EACpE,MAAM,EAAE,QAAQ,QAAQ,UACtB,QAAQ,SAAS,EAAE,KAAK,IAAI,KAAK,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAClD,MAAM;EACR,MAAM,SAAS,IAAI,MAAM,GAAG,IAAI,QAAQ,GAAG,IAAI,CAAC;EAEhD,IAAI,WAAA,cACH,OAAO,cAAc,KAAK,IAAI,MAAM,OAAO,YAC1C,QAAQ,KACP,GAAG,KAAK,GAAG,kCAAkC,MAAM,eAAe,QAAQ,gCAAgC,MAAM,iCACjH,CACD;EAKD,MAAM,SAAS,QAAQ,IAAI,MAAM;EACjC,IAAI,QAAQ,OAAO,OAAO,KAAK,KAAK,IAAI,GAAG;EAC3C,IAAI,WAAW,YACd,MAAM,IAAI,6BAA6B,KAAK,IAAI,MAAM;EAGvD,MAAM,IAAI,wBAAwB,KAAK,IAAI,MAAM;CAClD;AACD"}
|
|
@@ -21,7 +21,7 @@ declare function closeDatabasePools(): Promise<void>;
|
|
|
21
21
|
* straight through, so anything Kysely adds later arrives without being
|
|
22
22
|
* tracked here.
|
|
23
23
|
*/
|
|
24
|
-
interface
|
|
24
|
+
interface KyselyDatabaseBaseOptions extends Omit<KyselyConfig, 'dialect'> {
|
|
25
25
|
/**
|
|
26
26
|
* The schema, pinned on both roles' `search_path`.
|
|
27
27
|
*
|
|
@@ -39,16 +39,15 @@ interface KyselyDatabaseOptions extends Omit<KyselyConfig, 'dialect'> {
|
|
|
39
39
|
*/
|
|
40
40
|
roles?: boolean;
|
|
41
41
|
/**
|
|
42
|
-
*
|
|
42
|
+
* Extensions to create in this database, as the cluster master, before the
|
|
43
|
+
* first migration — for the ones the owner role a migration runs as may not
|
|
44
|
+
* create (PostGIS is not a trusted extension). They are created in
|
|
45
|
+
* `public`, which joins this database's roles' `search_path`, so their
|
|
46
|
+
* types resolve unqualified.
|
|
43
47
|
*
|
|
44
|
-
*
|
|
45
|
-
* set in two unrelated places — a container tag locally, nothing at all on
|
|
46
|
-
* AWS (so Aurora chose) — which is how local came to run 18 while deployed
|
|
47
|
-
* ran 17.7 with nothing recording the difference.
|
|
48
|
-
*
|
|
49
|
-
* Defaults to {@link DEFAULT_POSTGRES_VERSION}.
|
|
48
|
+
* Only attempted: the image has to ship them.
|
|
50
49
|
*/
|
|
51
|
-
|
|
50
|
+
extensions?: readonly string[];
|
|
52
51
|
/**
|
|
53
52
|
* End each query run inside a request with a comment naming what ran it —
|
|
54
53
|
* `/*operation='POST /orders',request_id='…'*\/` — so `pg_stat_activity`
|
|
@@ -58,6 +57,48 @@ interface KyselyDatabaseOptions extends Omit<KyselyConfig, 'dialect'> {
|
|
|
58
57
|
*/
|
|
59
58
|
queryTags?: boolean;
|
|
60
59
|
}
|
|
60
|
+
/**
|
|
61
|
+
* Which Postgres the database runs: a major on the stock image, or an image
|
|
62
|
+
* the project names. One or the other, never both — an image's tag already
|
|
63
|
+
* says its major, and a second statement of it is one that can disagree.
|
|
64
|
+
*/
|
|
65
|
+
type PostgresRuntime = {
|
|
66
|
+
/**
|
|
67
|
+
* The Postgres major version to provision, on the stock image.
|
|
68
|
+
*
|
|
69
|
+
* Declared here so one statement reaches every target. It was
|
|
70
|
+
* previously set in two unrelated places — a container tag locally,
|
|
71
|
+
* nothing at all on AWS (so Aurora chose) — which is how local came to
|
|
72
|
+
* run 18 while deployed ran 17.7 with nothing recording the difference.
|
|
73
|
+
*
|
|
74
|
+
* Defaults to {@link DEFAULT_POSTGRES_VERSION}.
|
|
75
|
+
*/
|
|
76
|
+
version?: PostgresVersion;
|
|
77
|
+
image?: never;
|
|
78
|
+
} | {
|
|
79
|
+
/**
|
|
80
|
+
* The container image to run, wherever a target runs one (local,
|
|
81
|
+
* Dokploy), in place of `postgres:<version>`.
|
|
82
|
+
*
|
|
83
|
+
* Yours to choose and used as written: its tag is the major, and it
|
|
84
|
+
* should ship every extension in `extensions`. AWS runs RDS, ignores
|
|
85
|
+
* it, and provisions {@link DEFAULT_POSTGRES_VERSION}.
|
|
86
|
+
*
|
|
87
|
+
* ```ts
|
|
88
|
+
* new KyselyDatabase<GeoDB>('Geo', {
|
|
89
|
+
* image: 'postgis/postgis:18-3.6-alpine',
|
|
90
|
+
* extensions: ['postgis'],
|
|
91
|
+
* });
|
|
92
|
+
* ```
|
|
93
|
+
*/
|
|
94
|
+
image: string;
|
|
95
|
+
version?: never;
|
|
96
|
+
};
|
|
97
|
+
/**
|
|
98
|
+
* Everything Kysely takes, plus what this construct declares — including
|
|
99
|
+
* which Postgres it runs, as a version or an image.
|
|
100
|
+
*/
|
|
101
|
+
type KyselyDatabaseOptions = KyselyDatabaseBaseOptions & PostgresRuntime;
|
|
61
102
|
/** What makes a construct derived rather than a database in its own right. */
|
|
62
103
|
interface DerivedFrom {
|
|
63
104
|
kind: 'database-reader' | 'database-schema';
|
|
@@ -82,7 +123,7 @@ declare class KyselyDatabase<DB = unknown, TName extends string = string> implem
|
|
|
82
123
|
private readonly config;
|
|
83
124
|
private readonly schemaName;
|
|
84
125
|
private readonly roles;
|
|
85
|
-
private readonly
|
|
126
|
+
private readonly runtime;
|
|
86
127
|
private readonly derivedFrom?;
|
|
87
128
|
constructor(id: ConstructName<TName>, options?: KyselyDatabaseOptions, derivedFrom?: DerivedFrom);
|
|
88
129
|
declare(): Declaration[];
|
|
@@ -171,5 +212,5 @@ declare class KyselyDatabase<DB = unknown, TName extends string = string> implem
|
|
|
171
212
|
*/
|
|
172
213
|
type ReadOnlyDatabase<DB, TName extends string> = Omit<KyselyDatabase<DB, TName>, 'reader' | 'cache' | 'schema'>;
|
|
173
214
|
//#endregion
|
|
174
|
-
export { closeDatabasePools as i, KyselyDatabaseOptions as n,
|
|
175
|
-
//# sourceMappingURL=kysely-
|
|
215
|
+
export { closeDatabasePools as a, ReadOnlyDatabase as i, KyselyDatabaseOptions as n, PostgresRuntime as r, KyselyDatabase as t };
|
|
216
|
+
//# sourceMappingURL=kysely-BHYV-WC-.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"kysely-BHYV-WC-.d.cts","names":[],"sources":["../src/database/pool.ts","../src/database/kysely.ts"],"mappings":";;;;;;;;;;;iBAwGsB,sBAAsB;;;;;;;;;;;;UChElC,kCAAkC,KAAK;;;;;;;;EAQhD;;;;;;;;EAQA;;;;;;;;;;EAUA;;;;;;;;EAQA;;;;;;;KAQW;;;;;;;;;;;EAYT,UAAU;EACV;;;;;;;;;;;;;;;;;EAkBA;EACA;;;;;;KAOS,wBAAwB,4BAA4B;;UAGtD;EACT;EACA;EACA;;;;;;;;;cAUY,eAAe,cAAc,0CAC9B,UAAU,OAAO,OAAO;mBAiBjB;WAfT,IAAI;WACJ,SAAS,QAAQ,aAAa,QAAQ,OAAO;;;;;mBAMrC;mBACA;mBACA;mBACA;mBACA;EAEjB,YACC,IAAI,cAAc,QACD,UAAS,uBAC1B,cAAc;EA2Bf,WAAW;;;;;;;;;;;;;EAyDX,UAAU,iBAAiB,OAAO;;;;;;;;;;;;;;;;;;EAyBlC,MAAM,2BAA2B,cAChC,KAAI,cAAc,SAClB;IAAW;MACT,MAAM;;;;;;;;;EAeT,OAAO,UAAU,wBAChB,IAAI,cAAc,UAClB,UAAS,wBACP,eAAe,UAAU;;;;;;;;;;;;;MAoBxB,SAAS,WAAW,aAAa,eAAe,OAAO;;;;;;;;;MAgBvD,gBAAgB,KAAK;UAiBX;;;;;;;;;;;;;;;;;;;KAuCH,iBAAiB,IAAI,wBAAwB,KACxD,eAAe,IAAI"}
|
|
@@ -21,7 +21,7 @@ declare function closeDatabasePools(): Promise<void>;
|
|
|
21
21
|
* straight through, so anything Kysely adds later arrives without being
|
|
22
22
|
* tracked here.
|
|
23
23
|
*/
|
|
24
|
-
interface
|
|
24
|
+
interface KyselyDatabaseBaseOptions extends Omit<KyselyConfig, 'dialect'> {
|
|
25
25
|
/**
|
|
26
26
|
* The schema, pinned on both roles' `search_path`.
|
|
27
27
|
*
|
|
@@ -39,16 +39,15 @@ interface KyselyDatabaseOptions extends Omit<KyselyConfig, 'dialect'> {
|
|
|
39
39
|
*/
|
|
40
40
|
roles?: boolean;
|
|
41
41
|
/**
|
|
42
|
-
*
|
|
42
|
+
* Extensions to create in this database, as the cluster master, before the
|
|
43
|
+
* first migration — for the ones the owner role a migration runs as may not
|
|
44
|
+
* create (PostGIS is not a trusted extension). They are created in
|
|
45
|
+
* `public`, which joins this database's roles' `search_path`, so their
|
|
46
|
+
* types resolve unqualified.
|
|
43
47
|
*
|
|
44
|
-
*
|
|
45
|
-
* set in two unrelated places — a container tag locally, nothing at all on
|
|
46
|
-
* AWS (so Aurora chose) — which is how local came to run 18 while deployed
|
|
47
|
-
* ran 17.7 with nothing recording the difference.
|
|
48
|
-
*
|
|
49
|
-
* Defaults to {@link DEFAULT_POSTGRES_VERSION}.
|
|
48
|
+
* Only attempted: the image has to ship them.
|
|
50
49
|
*/
|
|
51
|
-
|
|
50
|
+
extensions?: readonly string[];
|
|
52
51
|
/**
|
|
53
52
|
* End each query run inside a request with a comment naming what ran it —
|
|
54
53
|
* `/*operation='POST /orders',request_id='…'*\/` — so `pg_stat_activity`
|
|
@@ -58,6 +57,48 @@ interface KyselyDatabaseOptions extends Omit<KyselyConfig, 'dialect'> {
|
|
|
58
57
|
*/
|
|
59
58
|
queryTags?: boolean;
|
|
60
59
|
}
|
|
60
|
+
/**
|
|
61
|
+
* Which Postgres the database runs: a major on the stock image, or an image
|
|
62
|
+
* the project names. One or the other, never both — an image's tag already
|
|
63
|
+
* says its major, and a second statement of it is one that can disagree.
|
|
64
|
+
*/
|
|
65
|
+
type PostgresRuntime = {
|
|
66
|
+
/**
|
|
67
|
+
* The Postgres major version to provision, on the stock image.
|
|
68
|
+
*
|
|
69
|
+
* Declared here so one statement reaches every target. It was
|
|
70
|
+
* previously set in two unrelated places — a container tag locally,
|
|
71
|
+
* nothing at all on AWS (so Aurora chose) — which is how local came to
|
|
72
|
+
* run 18 while deployed ran 17.7 with nothing recording the difference.
|
|
73
|
+
*
|
|
74
|
+
* Defaults to {@link DEFAULT_POSTGRES_VERSION}.
|
|
75
|
+
*/
|
|
76
|
+
version?: PostgresVersion;
|
|
77
|
+
image?: never;
|
|
78
|
+
} | {
|
|
79
|
+
/**
|
|
80
|
+
* The container image to run, wherever a target runs one (local,
|
|
81
|
+
* Dokploy), in place of `postgres:<version>`.
|
|
82
|
+
*
|
|
83
|
+
* Yours to choose and used as written: its tag is the major, and it
|
|
84
|
+
* should ship every extension in `extensions`. AWS runs RDS, ignores
|
|
85
|
+
* it, and provisions {@link DEFAULT_POSTGRES_VERSION}.
|
|
86
|
+
*
|
|
87
|
+
* ```ts
|
|
88
|
+
* new KyselyDatabase<GeoDB>('Geo', {
|
|
89
|
+
* image: 'postgis/postgis:18-3.6-alpine',
|
|
90
|
+
* extensions: ['postgis'],
|
|
91
|
+
* });
|
|
92
|
+
* ```
|
|
93
|
+
*/
|
|
94
|
+
image: string;
|
|
95
|
+
version?: never;
|
|
96
|
+
};
|
|
97
|
+
/**
|
|
98
|
+
* Everything Kysely takes, plus what this construct declares — including
|
|
99
|
+
* which Postgres it runs, as a version or an image.
|
|
100
|
+
*/
|
|
101
|
+
type KyselyDatabaseOptions = KyselyDatabaseBaseOptions & PostgresRuntime;
|
|
61
102
|
/** What makes a construct derived rather than a database in its own right. */
|
|
62
103
|
interface DerivedFrom {
|
|
63
104
|
kind: 'database-reader' | 'database-schema';
|
|
@@ -82,7 +123,7 @@ declare class KyselyDatabase<DB = unknown, TName extends string = string> implem
|
|
|
82
123
|
private readonly config;
|
|
83
124
|
private readonly schemaName;
|
|
84
125
|
private readonly roles;
|
|
85
|
-
private readonly
|
|
126
|
+
private readonly runtime;
|
|
86
127
|
private readonly derivedFrom?;
|
|
87
128
|
constructor(id: ConstructName<TName>, options?: KyselyDatabaseOptions, derivedFrom?: DerivedFrom);
|
|
88
129
|
declare(): Declaration[];
|
|
@@ -171,5 +212,5 @@ declare class KyselyDatabase<DB = unknown, TName extends string = string> implem
|
|
|
171
212
|
*/
|
|
172
213
|
type ReadOnlyDatabase<DB, TName extends string> = Omit<KyselyDatabase<DB, TName>, 'reader' | 'cache' | 'schema'>;
|
|
173
214
|
//#endregion
|
|
174
|
-
export { closeDatabasePools as i, KyselyDatabaseOptions as n,
|
|
175
|
-
//# sourceMappingURL=kysely-
|
|
215
|
+
export { closeDatabasePools as a, ReadOnlyDatabase as i, KyselyDatabaseOptions as n, PostgresRuntime as r, KyselyDatabase as t };
|
|
216
|
+
//# sourceMappingURL=kysely-CAW3QSqp.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"kysely-CAW3QSqp.d.mts","names":[],"sources":["../src/database/pool.ts","../src/database/kysely.ts"],"mappings":";;;;;;;;;;;iBAwGsB,sBAAsB;;;;;;;;;;;;UChElC,kCAAkC,KAAK;;;;;;;;EAQhD;;;;;;;;EAQA;;;;;;;;;;EAUA;;;;;;;;EAQA;;;;;;;KAQW;;;;;;;;;;;EAYT,UAAU;EACV;;;;;;;;;;;;;;;;;EAkBA;EACA;;;;;;KAOS,wBAAwB,4BAA4B;;UAGtD;EACT;EACA;EACA;;;;;;;;;cAUY,eAAe,cAAc,0CAC9B,UAAU,OAAO,OAAO;mBAiBjB;WAfT,IAAI;WACJ,SAAS,QAAQ,aAAa,QAAQ,OAAO;;;;;mBAMrC;mBACA;mBACA;mBACA;mBACA;EAEjB,YACC,IAAI,cAAc,QACD,UAAS,uBAC1B,cAAc;EA2Bf,WAAW;;;;;;;;;;;;;EAyDX,UAAU,iBAAiB,OAAO;;;;;;;;;;;;;;;;;;EAyBlC,MAAM,2BAA2B,cAChC,KAAI,cAAc,SAClB;IAAW;MACT,MAAM;;;;;;;;;EAeT,OAAO,UAAU,wBAChB,IAAI,cAAc,UAClB,UAAS,wBACP,eAAe,UAAU;;;;;;;;;;;;;MAoBxB,SAAS,WAAW,aAAa,eAAe,OAAO;;;;;;;;;MAgBvD,gBAAgB,KAAK;UAiBX;;;;;;;;;;;;;;;;;;;KAuCH,iBAAiB,IAAI,wBAAwB,KACxD,eAAe,IAAI"}
|