@ai-sdk-byok/drizzle 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Cyril Tsui
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,64 @@
1
+ # @ai-sdk-byok/drizzle
2
+
3
+ Drizzle-backed PostgreSQL storage adapter for [`ai-sdk-byok`](https://github.com/Xyri1/ai-sdk-byok). Credentials are encrypted in trusted application code before they reach SQL.
4
+
5
+ ## Install
6
+
7
+ ```sh
8
+ npm install ai-sdk-byok @ai-sdk-byok/drizzle drizzle-orm
9
+ ```
10
+
11
+ `drizzle-orm` is a peer dependency. Install and configure a PostgreSQL-compatible Drizzle driver in the application; this package does not create connections or own pooling.
12
+
13
+ ## Requirements
14
+
15
+ - Node.js 22 or newer.
16
+ - A PostgreSQL database.
17
+ - A caller-owned Drizzle database instance.
18
+ - Trusted server-side code for the master key and credential retrieval.
19
+
20
+ The initial supported dialect is `postgres`. SQLite is not supported by this adapter yet.
21
+
22
+ ## Migration setup
23
+
24
+ Apply [`migrations/0001_ai_sdk_byok_init.sql`](migrations/0001_ai_sdk_byok_init.sql) to the application database. When installed from npm, the file is available at `node_modules/@ai-sdk-byok/drizzle/migrations/0001_ai_sdk_byok_init.sql` and can be copied into the application's migrations directory.
25
+
26
+ Applications using Drizzle Kit can instead generate a migration from the exported `aiSdkByokKeys` schema. Use either the shipped SQL migration or the generated equivalent, not both.
27
+
28
+ ## Usage
29
+
30
+ `db` is the application's configured Drizzle PostgreSQL database:
31
+
32
+ ```ts
33
+ import { createByokManager } from 'ai-sdk-byok';
34
+ import { drizzleAdapter } from '@ai-sdk-byok/drizzle';
35
+
36
+ export const byok = createByokManager({
37
+ storage: drizzleAdapter({
38
+ db,
39
+ dialect: 'postgres',
40
+ encryption: {
41
+ current: {
42
+ version: 'v1',
43
+ key: process.env.AI_SDK_BYOK_MASTER_KEY!,
44
+ },
45
+ },
46
+ }),
47
+ });
48
+ ```
49
+
50
+ Generate a string master key with `openssl rand -base64 32` and keep it in a server-side secret. The adapter accepts the configured `current` key for new writes and optional `previous` keys for reading older rows.
51
+
52
+ ## Key rotation
53
+
54
+ Set the new key as `current` and keep the old key in `previous`. New writes and credential rotations use `current`; `previous` keys are read-only and decrypt rows carrying their matching version. Re-encryption of existing rows may be deferred, so keep each previous key configured while those rows remain.
55
+
56
+ ## Security
57
+
58
+ - The master key is never stored in SQL.
59
+ - Losing the master key makes stored credentials unrecoverable.
60
+ - If a master key leaks, rows encrypted under that key must be treated as compromised when their ciphertext may also have been exposed. Affected users should rotate their provider API keys.
61
+ - This adapter protects against database-only compromise, including leaked backups, dumps, and read replicas. It does not protect against application-server compromise.
62
+ - Cryptography is pinned to AES-256-GCM with a 32-byte base64 master key, a new random 12-byte nonce for each write, and additional authenticated data bound to `(userId, provider)`.
63
+
64
+ SQL stores metadata plus base64url ciphertext, nonce, and the non-secret encryption-key version. Plaintext credentials and key material remain in trusted application memory.
@@ -0,0 +1,210 @@
1
+ import * as drizzle_orm_pg_core from 'drizzle-orm/pg-core';
2
+ import { PgDatabase, PgQueryResultHKT } from 'drizzle-orm/pg-core';
3
+ import { ByokManagerOptions } from 'ai-sdk-byok';
4
+
5
+ declare const aiSdkByokKeys: drizzle_orm_pg_core.PgTableWithColumns<{
6
+ name: "ai_sdk_byok_keys";
7
+ schema: undefined;
8
+ columns: {
9
+ id: drizzle_orm_pg_core.PgColumn<{
10
+ name: "id";
11
+ tableName: "ai_sdk_byok_keys";
12
+ dataType: "string";
13
+ columnType: "PgText";
14
+ data: string;
15
+ driverParam: string;
16
+ notNull: true;
17
+ hasDefault: false;
18
+ isPrimaryKey: true;
19
+ isAutoincrement: false;
20
+ hasRuntimeDefault: false;
21
+ enumValues: [string, ...string[]];
22
+ baseColumn: never;
23
+ identity: undefined;
24
+ generated: undefined;
25
+ }, {}, {}>;
26
+ userId: drizzle_orm_pg_core.PgColumn<{
27
+ name: "user_id";
28
+ tableName: "ai_sdk_byok_keys";
29
+ dataType: "string";
30
+ columnType: "PgText";
31
+ data: string;
32
+ driverParam: string;
33
+ notNull: true;
34
+ hasDefault: false;
35
+ isPrimaryKey: false;
36
+ isAutoincrement: false;
37
+ hasRuntimeDefault: false;
38
+ enumValues: [string, ...string[]];
39
+ baseColumn: never;
40
+ identity: undefined;
41
+ generated: undefined;
42
+ }, {}, {}>;
43
+ provider: drizzle_orm_pg_core.PgColumn<{
44
+ name: "provider";
45
+ tableName: "ai_sdk_byok_keys";
46
+ dataType: "string";
47
+ columnType: "PgText";
48
+ data: string;
49
+ driverParam: string;
50
+ notNull: true;
51
+ hasDefault: false;
52
+ isPrimaryKey: false;
53
+ isAutoincrement: false;
54
+ hasRuntimeDefault: false;
55
+ enumValues: [string, ...string[]];
56
+ baseColumn: never;
57
+ identity: undefined;
58
+ generated: undefined;
59
+ }, {}, {}>;
60
+ label: drizzle_orm_pg_core.PgColumn<{
61
+ name: "label";
62
+ tableName: "ai_sdk_byok_keys";
63
+ dataType: "string";
64
+ columnType: "PgText";
65
+ data: string;
66
+ driverParam: string;
67
+ notNull: true;
68
+ hasDefault: false;
69
+ isPrimaryKey: false;
70
+ isAutoincrement: false;
71
+ hasRuntimeDefault: false;
72
+ enumValues: [string, ...string[]];
73
+ baseColumn: never;
74
+ identity: undefined;
75
+ generated: undefined;
76
+ }, {}, {}>;
77
+ keyHint: drizzle_orm_pg_core.PgColumn<{
78
+ name: "key_hint";
79
+ tableName: "ai_sdk_byok_keys";
80
+ dataType: "string";
81
+ columnType: "PgText";
82
+ data: string;
83
+ driverParam: string;
84
+ notNull: true;
85
+ hasDefault: false;
86
+ isPrimaryKey: false;
87
+ isAutoincrement: false;
88
+ hasRuntimeDefault: false;
89
+ enumValues: [string, ...string[]];
90
+ baseColumn: never;
91
+ identity: undefined;
92
+ generated: undefined;
93
+ }, {}, {}>;
94
+ credentialsCiphertext: drizzle_orm_pg_core.PgColumn<{
95
+ name: "credentials_ciphertext";
96
+ tableName: "ai_sdk_byok_keys";
97
+ dataType: "string";
98
+ columnType: "PgText";
99
+ data: string;
100
+ driverParam: string;
101
+ notNull: true;
102
+ hasDefault: false;
103
+ isPrimaryKey: false;
104
+ isAutoincrement: false;
105
+ hasRuntimeDefault: false;
106
+ enumValues: [string, ...string[]];
107
+ baseColumn: never;
108
+ identity: undefined;
109
+ generated: undefined;
110
+ }, {}, {}>;
111
+ credentialsNonce: drizzle_orm_pg_core.PgColumn<{
112
+ name: "credentials_nonce";
113
+ tableName: "ai_sdk_byok_keys";
114
+ dataType: "string";
115
+ columnType: "PgText";
116
+ data: string;
117
+ driverParam: string;
118
+ notNull: true;
119
+ hasDefault: false;
120
+ isPrimaryKey: false;
121
+ isAutoincrement: false;
122
+ hasRuntimeDefault: false;
123
+ enumValues: [string, ...string[]];
124
+ baseColumn: never;
125
+ identity: undefined;
126
+ generated: undefined;
127
+ }, {}, {}>;
128
+ encryptionKeyVersion: drizzle_orm_pg_core.PgColumn<{
129
+ name: "encryption_key_version";
130
+ tableName: "ai_sdk_byok_keys";
131
+ dataType: "string";
132
+ columnType: "PgText";
133
+ data: string;
134
+ driverParam: string;
135
+ notNull: true;
136
+ hasDefault: false;
137
+ isPrimaryKey: false;
138
+ isAutoincrement: false;
139
+ hasRuntimeDefault: false;
140
+ enumValues: [string, ...string[]];
141
+ baseColumn: never;
142
+ identity: undefined;
143
+ generated: undefined;
144
+ }, {}, {}>;
145
+ createdAt: drizzle_orm_pg_core.PgColumn<{
146
+ name: "created_at";
147
+ tableName: "ai_sdk_byok_keys";
148
+ dataType: "string";
149
+ columnType: "PgTimestampString";
150
+ data: string;
151
+ driverParam: string;
152
+ notNull: true;
153
+ hasDefault: false;
154
+ isPrimaryKey: false;
155
+ isAutoincrement: false;
156
+ hasRuntimeDefault: false;
157
+ enumValues: undefined;
158
+ baseColumn: never;
159
+ identity: undefined;
160
+ generated: undefined;
161
+ }, {}, {}>;
162
+ updatedAt: drizzle_orm_pg_core.PgColumn<{
163
+ name: "updated_at";
164
+ tableName: "ai_sdk_byok_keys";
165
+ dataType: "string";
166
+ columnType: "PgTimestampString";
167
+ data: string;
168
+ driverParam: string;
169
+ notNull: true;
170
+ hasDefault: false;
171
+ isPrimaryKey: false;
172
+ isAutoincrement: false;
173
+ hasRuntimeDefault: false;
174
+ enumValues: undefined;
175
+ baseColumn: never;
176
+ identity: undefined;
177
+ generated: undefined;
178
+ }, {}, {}>;
179
+ };
180
+ dialect: "pg";
181
+ }>;
182
+
183
+ type EncryptionKey = {
184
+ version: string;
185
+ key: string | Uint8Array | CryptoKey;
186
+ };
187
+ type EncryptionConfig = {
188
+ current: EncryptionKey;
189
+ previous?: EncryptionKey[];
190
+ };
191
+ interface EncryptedPayload {
192
+ ciphertext: string;
193
+ nonce: string;
194
+ keyVersion: string;
195
+ }
196
+ declare function credentialAad(...parts: string[]): string;
197
+ declare function createKeyring(config: EncryptionConfig): {
198
+ encrypt(plaintext: string, aad: string): Promise<EncryptedPayload>;
199
+ decrypt(payload: EncryptedPayload, aad: string): Promise<string>;
200
+ };
201
+
202
+ type DrizzlePostgresDatabase = PgDatabase<PgQueryResultHKT, Record<string, unknown>>;
203
+ interface DrizzleAdapterOptions {
204
+ db: DrizzlePostgresDatabase;
205
+ dialect: 'postgres';
206
+ encryption: EncryptionConfig;
207
+ }
208
+ declare function drizzleAdapter(options: DrizzleAdapterOptions): ByokManagerOptions['storage'];
209
+
210
+ export { type DrizzleAdapterOptions, type EncryptedPayload, type EncryptionConfig, type EncryptionKey, aiSdkByokKeys, createKeyring, credentialAad, drizzleAdapter };
package/dist/index.js ADDED
@@ -0,0 +1,333 @@
1
+ import { pgTable, timestamp, text, unique, index } from 'drizzle-orm/pg-core';
2
+ import { AiSdkByokAdapterError, AiSdkByokValidationError } from 'ai-sdk-byok';
3
+ import { and, eq, desc } from 'drizzle-orm';
4
+
5
+ // packages/drizzle/src/schema.ts
6
+ var aiSdkByokKeys = pgTable(
7
+ "ai_sdk_byok_keys",
8
+ {
9
+ id: text("id").primaryKey(),
10
+ userId: text("user_id").notNull(),
11
+ provider: text("provider").notNull(),
12
+ label: text("label").notNull(),
13
+ keyHint: text("key_hint").notNull(),
14
+ credentialsCiphertext: text("credentials_ciphertext").notNull(),
15
+ credentialsNonce: text("credentials_nonce").notNull(),
16
+ encryptionKeyVersion: text("encryption_key_version").notNull(),
17
+ createdAt: timestamp("created_at", { mode: "string", withTimezone: true }).notNull(),
18
+ updatedAt: timestamp("updated_at", { mode: "string", withTimezone: true }).notNull()
19
+ },
20
+ (table) => [
21
+ unique("ai_sdk_byok_keys_user_provider_label_unique").on(table.userId, table.provider, table.label),
22
+ index("ai_sdk_byok_keys_user_updated_created_idx").on(
23
+ table.userId,
24
+ table.updatedAt.desc(),
25
+ table.createdAt.desc()
26
+ )
27
+ ]
28
+ );
29
+ var NONCE_LENGTH_BYTES = 12;
30
+ var KEY_LENGTH_BYTES = 32;
31
+ var encoder = new TextEncoder();
32
+ var decoder = new TextDecoder();
33
+ function credentialAad(...parts) {
34
+ return parts.join("\0");
35
+ }
36
+ function decodeBase64(value) {
37
+ const binary = atob(value);
38
+ const bytes = new Uint8Array(binary.length);
39
+ for (let index2 = 0; index2 < binary.length; index2 += 1) {
40
+ bytes[index2] = binary.charCodeAt(index2);
41
+ }
42
+ return bytes;
43
+ }
44
+ function toBase64Url(bytes) {
45
+ let binary = "";
46
+ for (const byte of bytes) {
47
+ binary += String.fromCharCode(byte);
48
+ }
49
+ return btoa(binary).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/, "");
50
+ }
51
+ function fromBase64Url(value) {
52
+ const padded = value.replaceAll("-", "+").replaceAll("_", "/");
53
+ return decodeBase64(padded + "=".repeat((4 - padded.length % 4) % 4));
54
+ }
55
+ function decodeKeyMaterial(value) {
56
+ let bytes;
57
+ try {
58
+ bytes = decodeBase64(value.trim());
59
+ } catch {
60
+ throw new AiSdkByokValidationError("encryption key must be a base64-encoded string");
61
+ }
62
+ if (bytes.length !== KEY_LENGTH_BYTES) {
63
+ throw new AiSdkByokValidationError("encryption key must decode to exactly 32 bytes");
64
+ }
65
+ return bytes;
66
+ }
67
+ function isCryptoKey(value) {
68
+ return typeof CryptoKey !== "undefined" && value instanceof CryptoKey;
69
+ }
70
+ function normalizeKey(value) {
71
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
72
+ throw new AiSdkByokValidationError("encryption key must be an object");
73
+ }
74
+ const input = value;
75
+ if (typeof input.version !== "string" || input.version.trim().length === 0) {
76
+ throw new AiSdkByokValidationError("encryption key version must be a non-empty string");
77
+ }
78
+ let material;
79
+ if (typeof input.key === "string") {
80
+ material = decodeKeyMaterial(input.key);
81
+ } else if (input.key instanceof Uint8Array) {
82
+ if (input.key.byteLength !== KEY_LENGTH_BYTES) {
83
+ throw new AiSdkByokValidationError("encryption key must be exactly 32 bytes");
84
+ }
85
+ material = new Uint8Array(input.key);
86
+ } else if (isCryptoKey(input.key)) {
87
+ const algorithm = input.key.algorithm;
88
+ if (input.key.type !== "secret" || algorithm.name !== "AES-GCM" || !("length" in algorithm) || algorithm.length !== 256 || !input.key.usages.includes("encrypt") || !input.key.usages.includes("decrypt")) {
89
+ throw new AiSdkByokValidationError("encryption key must be an AES-GCM key usable for encrypt and decrypt");
90
+ }
91
+ material = input.key;
92
+ } else {
93
+ throw new AiSdkByokValidationError("encryption key must be a string, Uint8Array, or CryptoKey");
94
+ }
95
+ return { version: input.version, material, keyPromise: null };
96
+ }
97
+ function normalizeConfig(config) {
98
+ if (typeof config !== "object" || config === null || Array.isArray(config)) {
99
+ throw new AiSdkByokValidationError("encryption config must be an object");
100
+ }
101
+ const input = config;
102
+ const previous = input.previous === void 0 ? [] : input.previous;
103
+ if (!Array.isArray(previous)) {
104
+ throw new AiSdkByokValidationError("encryption config previous keys must be an array");
105
+ }
106
+ const current = normalizeKey(input.current);
107
+ const keys = /* @__PURE__ */ new Map();
108
+ for (const key of [current, ...previous.map(normalizeKey)]) {
109
+ if (keys.has(key.version)) {
110
+ throw new AiSdkByokValidationError("encryption key versions must be unique");
111
+ }
112
+ keys.set(key.version, key);
113
+ }
114
+ return { current, keys };
115
+ }
116
+ function importKey(key) {
117
+ if (isCryptoKey(key.material)) {
118
+ return Promise.resolve(key.material);
119
+ }
120
+ key.keyPromise ??= globalThis.crypto.subtle.importKey("raw", key.material, { name: "AES-GCM" }, false, ["encrypt", "decrypt"]).catch((error) => {
121
+ key.keyPromise = null;
122
+ throw error;
123
+ });
124
+ return key.keyPromise;
125
+ }
126
+ function createKeyring(config) {
127
+ const { current, keys } = normalizeConfig(config);
128
+ return {
129
+ async encrypt(plaintext, aad) {
130
+ try {
131
+ const key = await importKey(current);
132
+ const nonce = globalThis.crypto.getRandomValues(new Uint8Array(NONCE_LENGTH_BYTES));
133
+ const ciphertext = await globalThis.crypto.subtle.encrypt(
134
+ { name: "AES-GCM", iv: nonce, additionalData: encoder.encode(aad) },
135
+ key,
136
+ encoder.encode(plaintext)
137
+ );
138
+ return {
139
+ ciphertext: toBase64Url(new Uint8Array(ciphertext)),
140
+ nonce: toBase64Url(nonce),
141
+ keyVersion: current.version
142
+ };
143
+ } catch {
144
+ throw new AiSdkByokAdapterError("Drizzle encryption failed");
145
+ }
146
+ },
147
+ async decrypt(payload, aad) {
148
+ const key = keys.get(payload.keyVersion);
149
+ if (key === void 0) {
150
+ throw new AiSdkByokAdapterError("Drizzle credential payload uses an unconfigured encryption key");
151
+ }
152
+ try {
153
+ const plaintext = await globalThis.crypto.subtle.decrypt(
154
+ { name: "AES-GCM", iv: fromBase64Url(payload.nonce), additionalData: encoder.encode(aad) },
155
+ await importKey(key),
156
+ fromBase64Url(payload.ciphertext)
157
+ );
158
+ return decoder.decode(plaintext);
159
+ } catch {
160
+ throw new AiSdkByokAdapterError("Drizzle credential payload failed to decrypt");
161
+ }
162
+ }
163
+ };
164
+ }
165
+ function adapterError(operation) {
166
+ return new AiSdkByokAdapterError(`Drizzle BYOK adapter failed during ${operation}`);
167
+ }
168
+ function toMetadata(row) {
169
+ return {
170
+ id: row.id,
171
+ userId: row.userId,
172
+ provider: row.provider,
173
+ label: row.label,
174
+ keyHint: row.keyHint,
175
+ createdAt: row.createdAt,
176
+ updatedAt: row.updatedAt
177
+ };
178
+ }
179
+ function parseCredentials(plaintext) {
180
+ try {
181
+ const parsed = JSON.parse(plaintext);
182
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed) || Object.keys(parsed).length !== 1 || typeof parsed.apiKey !== "string") {
183
+ throw new Error("invalid credential payload shape");
184
+ }
185
+ return { apiKey: parsed.apiKey };
186
+ } catch {
187
+ throw new AiSdkByokAdapterError("Drizzle credential payload has an invalid shape");
188
+ }
189
+ }
190
+ function drizzleAdapter(options) {
191
+ if (options?.dialect !== "postgres") {
192
+ throw new AiSdkByokValidationError("Drizzle dialect must be postgres");
193
+ }
194
+ const db = options.db;
195
+ const keyring = createKeyring(options.encryption);
196
+ return {
197
+ async save(input) {
198
+ const encrypted = await keyring.encrypt(
199
+ JSON.stringify({ apiKey: input.credentials.apiKey }),
200
+ credentialAad(input.userId, input.provider)
201
+ );
202
+ const now = (/* @__PURE__ */ new Date()).toISOString();
203
+ let row;
204
+ try {
205
+ [row] = await db.insert(aiSdkByokKeys).values({
206
+ id: crypto.randomUUID(),
207
+ userId: input.userId,
208
+ provider: input.provider,
209
+ label: input.label,
210
+ keyHint: input.keyHint,
211
+ credentialsCiphertext: encrypted.ciphertext,
212
+ credentialsNonce: encrypted.nonce,
213
+ encryptionKeyVersion: encrypted.keyVersion,
214
+ createdAt: now,
215
+ updatedAt: now
216
+ }).onConflictDoUpdate({
217
+ target: [aiSdkByokKeys.userId, aiSdkByokKeys.provider, aiSdkByokKeys.label],
218
+ set: {
219
+ keyHint: input.keyHint,
220
+ credentialsCiphertext: encrypted.ciphertext,
221
+ credentialsNonce: encrypted.nonce,
222
+ encryptionKeyVersion: encrypted.keyVersion,
223
+ updatedAt: now
224
+ }
225
+ }).returning({
226
+ id: aiSdkByokKeys.id,
227
+ userId: aiSdkByokKeys.userId,
228
+ provider: aiSdkByokKeys.provider,
229
+ label: aiSdkByokKeys.label,
230
+ keyHint: aiSdkByokKeys.keyHint,
231
+ createdAt: aiSdkByokKeys.createdAt,
232
+ updatedAt: aiSdkByokKeys.updatedAt
233
+ });
234
+ } catch {
235
+ throw adapterError("save");
236
+ }
237
+ if (row === void 0) {
238
+ throw adapterError("save");
239
+ }
240
+ return toMetadata(row);
241
+ },
242
+ async list(input) {
243
+ try {
244
+ const rows = await db.select({
245
+ id: aiSdkByokKeys.id,
246
+ userId: aiSdkByokKeys.userId,
247
+ provider: aiSdkByokKeys.provider,
248
+ label: aiSdkByokKeys.label,
249
+ keyHint: aiSdkByokKeys.keyHint,
250
+ createdAt: aiSdkByokKeys.createdAt,
251
+ updatedAt: aiSdkByokKeys.updatedAt
252
+ }).from(aiSdkByokKeys).where(eq(aiSdkByokKeys.userId, input.userId)).orderBy(desc(aiSdkByokKeys.updatedAt), desc(aiSdkByokKeys.createdAt));
253
+ return rows.map(toMetadata);
254
+ } catch {
255
+ throw adapterError("list");
256
+ }
257
+ },
258
+ async get(input) {
259
+ let row;
260
+ try {
261
+ [row] = await db.select({
262
+ userId: aiSdkByokKeys.userId,
263
+ provider: aiSdkByokKeys.provider,
264
+ credentialsCiphertext: aiSdkByokKeys.credentialsCiphertext,
265
+ credentialsNonce: aiSdkByokKeys.credentialsNonce,
266
+ encryptionKeyVersion: aiSdkByokKeys.encryptionKeyVersion
267
+ }).from(aiSdkByokKeys).where(
268
+ and(
269
+ eq(aiSdkByokKeys.userId, input.userId),
270
+ eq(aiSdkByokKeys.provider, input.provider),
271
+ eq(aiSdkByokKeys.label, input.label)
272
+ )
273
+ );
274
+ } catch {
275
+ throw adapterError("get");
276
+ }
277
+ if (row === void 0) {
278
+ return null;
279
+ }
280
+ const plaintext = await keyring.decrypt(
281
+ {
282
+ ciphertext: row.credentialsCiphertext,
283
+ nonce: row.credentialsNonce,
284
+ keyVersion: row.encryptionKeyVersion
285
+ },
286
+ credentialAad(row.userId, row.provider)
287
+ );
288
+ return parseCredentials(plaintext);
289
+ },
290
+ async getById(input) {
291
+ let row;
292
+ try {
293
+ [row] = await db.select({
294
+ id: aiSdkByokKeys.id,
295
+ userId: aiSdkByokKeys.userId,
296
+ provider: aiSdkByokKeys.provider,
297
+ label: aiSdkByokKeys.label,
298
+ keyHint: aiSdkByokKeys.keyHint,
299
+ createdAt: aiSdkByokKeys.createdAt,
300
+ updatedAt: aiSdkByokKeys.updatedAt,
301
+ credentialsCiphertext: aiSdkByokKeys.credentialsCiphertext,
302
+ credentialsNonce: aiSdkByokKeys.credentialsNonce,
303
+ encryptionKeyVersion: aiSdkByokKeys.encryptionKeyVersion
304
+ }).from(aiSdkByokKeys).where(and(eq(aiSdkByokKeys.userId, input.userId), eq(aiSdkByokKeys.id, input.keyId)));
305
+ } catch {
306
+ throw adapterError("getById");
307
+ }
308
+ if (row === void 0) {
309
+ return null;
310
+ }
311
+ const plaintext = await keyring.decrypt(
312
+ {
313
+ ciphertext: row.credentialsCiphertext,
314
+ nonce: row.credentialsNonce,
315
+ keyVersion: row.encryptionKeyVersion
316
+ },
317
+ credentialAad(row.userId, row.provider)
318
+ );
319
+ return { ...toMetadata(row), credentials: parseCredentials(plaintext) };
320
+ },
321
+ async delete(input) {
322
+ try {
323
+ await db.delete(aiSdkByokKeys).where(and(eq(aiSdkByokKeys.userId, input.userId), eq(aiSdkByokKeys.id, input.keyId)));
324
+ } catch {
325
+ throw adapterError("delete");
326
+ }
327
+ }
328
+ };
329
+ }
330
+
331
+ export { aiSdkByokKeys, createKeyring, credentialAad, drizzleAdapter };
332
+ //# sourceMappingURL=index.js.map
333
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/schema.ts","../src/crypto.ts","../src/adapter.ts"],"names":["index","AiSdkByokAdapterError","AiSdkByokValidationError"],"mappings":"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","file":"index.js","sourcesContent":["import { index, pgTable, text, timestamp, unique } from 'drizzle-orm/pg-core';\n\nexport const aiSdkByokKeys = pgTable(\n 'ai_sdk_byok_keys',\n {\n id: text('id').primaryKey(),\n userId: text('user_id').notNull(),\n provider: text('provider').notNull(),\n label: text('label').notNull(),\n keyHint: text('key_hint').notNull(),\n credentialsCiphertext: text('credentials_ciphertext').notNull(),\n credentialsNonce: text('credentials_nonce').notNull(),\n encryptionKeyVersion: text('encryption_key_version').notNull(),\n createdAt: timestamp('created_at', { mode: 'string', withTimezone: true }).notNull(),\n updatedAt: timestamp('updated_at', { mode: 'string', withTimezone: true }).notNull(),\n },\n (table) => [\n unique('ai_sdk_byok_keys_user_provider_label_unique').on(table.userId, table.provider, table.label),\n index('ai_sdk_byok_keys_user_updated_created_idx').on(\n table.userId,\n table.updatedAt.desc(),\n table.createdAt.desc(),\n ),\n ],\n);\n","import { AiSdkByokAdapterError, AiSdkByokValidationError } from 'ai-sdk-byok';\n\nexport type EncryptionKey = {\n version: string;\n key: string | Uint8Array | CryptoKey;\n};\n\nexport type EncryptionConfig = {\n current: EncryptionKey;\n previous?: EncryptionKey[];\n};\n\nexport interface EncryptedPayload {\n ciphertext: string;\n nonce: string;\n keyVersion: string;\n}\n\ninterface NormalizedEncryptionKey {\n version: string;\n material: Uint8Array<ArrayBuffer> | CryptoKey;\n keyPromise: Promise<CryptoKey> | null;\n}\n\nconst NONCE_LENGTH_BYTES = 12;\nconst KEY_LENGTH_BYTES = 32;\nconst encoder = new TextEncoder();\nconst decoder = new TextDecoder();\n\nexport function credentialAad(...parts: string[]): string {\n return parts.join('\\0');\n}\n\nfunction decodeBase64(value: string): Uint8Array<ArrayBuffer> {\n const binary = atob(value);\n const bytes = new Uint8Array(binary.length);\n for (let index = 0; index < binary.length; index += 1) {\n bytes[index] = binary.charCodeAt(index);\n }\n return bytes;\n}\n\nfunction toBase64Url(bytes: Uint8Array): string {\n let binary = '';\n for (const byte of bytes) {\n binary += String.fromCharCode(byte);\n }\n return btoa(binary).replaceAll('+', '-').replaceAll('/', '_').replace(/=+$/, '');\n}\n\nfunction fromBase64Url(value: string): Uint8Array<ArrayBuffer> {\n const padded = value.replaceAll('-', '+').replaceAll('_', '/');\n return decodeBase64(padded + '='.repeat((4 - (padded.length % 4)) % 4));\n}\n\nfunction decodeKeyMaterial(value: string): Uint8Array<ArrayBuffer> {\n let bytes: Uint8Array<ArrayBuffer>;\n try {\n bytes = decodeBase64(value.trim());\n } catch {\n throw new AiSdkByokValidationError('encryption key must be a base64-encoded string');\n }\n\n if (bytes.length !== KEY_LENGTH_BYTES) {\n throw new AiSdkByokValidationError('encryption key must decode to exactly 32 bytes');\n }\n\n return bytes;\n}\n\nfunction isCryptoKey(value: unknown): value is CryptoKey {\n return typeof CryptoKey !== 'undefined' && value instanceof CryptoKey;\n}\n\nfunction normalizeKey(value: unknown): NormalizedEncryptionKey {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n throw new AiSdkByokValidationError('encryption key must be an object');\n }\n\n const input = value as Partial<EncryptionKey>;\n if (typeof input.version !== 'string' || input.version.trim().length === 0) {\n throw new AiSdkByokValidationError('encryption key version must be a non-empty string');\n }\n\n let material: Uint8Array<ArrayBuffer> | CryptoKey;\n if (typeof input.key === 'string') {\n material = decodeKeyMaterial(input.key);\n } else if (input.key instanceof Uint8Array) {\n if (input.key.byteLength !== KEY_LENGTH_BYTES) {\n throw new AiSdkByokValidationError('encryption key must be exactly 32 bytes');\n }\n material = new Uint8Array(input.key);\n } else if (isCryptoKey(input.key)) {\n const algorithm = input.key.algorithm;\n if (\n input.key.type !== 'secret' ||\n algorithm.name !== 'AES-GCM' ||\n !('length' in algorithm) ||\n algorithm.length !== 256 ||\n !input.key.usages.includes('encrypt') ||\n !input.key.usages.includes('decrypt')\n ) {\n throw new AiSdkByokValidationError('encryption key must be an AES-GCM key usable for encrypt and decrypt');\n }\n material = input.key;\n } else {\n throw new AiSdkByokValidationError('encryption key must be a string, Uint8Array, or CryptoKey');\n }\n\n return { version: input.version, material, keyPromise: null };\n}\n\nfunction normalizeConfig(config: unknown): { current: NormalizedEncryptionKey; keys: Map<string, NormalizedEncryptionKey> } {\n if (typeof config !== 'object' || config === null || Array.isArray(config)) {\n throw new AiSdkByokValidationError('encryption config must be an object');\n }\n\n const input = config as { current?: unknown; previous?: unknown };\n const previous = input.previous === undefined ? [] : input.previous;\n if (!Array.isArray(previous)) {\n throw new AiSdkByokValidationError('encryption config previous keys must be an array');\n }\n\n const current = normalizeKey(input.current);\n const keys = new Map<string, NormalizedEncryptionKey>();\n for (const key of [current, ...previous.map(normalizeKey)]) {\n if (keys.has(key.version)) {\n throw new AiSdkByokValidationError('encryption key versions must be unique');\n }\n keys.set(key.version, key);\n }\n\n return { current, keys };\n}\n\nfunction importKey(key: NormalizedEncryptionKey): Promise<CryptoKey> {\n if (isCryptoKey(key.material)) {\n return Promise.resolve(key.material);\n }\n\n key.keyPromise ??= globalThis.crypto.subtle\n .importKey('raw', key.material, { name: 'AES-GCM' }, false, ['encrypt', 'decrypt'])\n .catch((error: unknown) => {\n key.keyPromise = null;\n throw error;\n });\n return key.keyPromise;\n}\n\nexport function createKeyring(config: EncryptionConfig) {\n const { current, keys } = normalizeConfig(config);\n\n return {\n async encrypt(plaintext: string, aad: string): Promise<EncryptedPayload> {\n try {\n const key = await importKey(current);\n const nonce = globalThis.crypto.getRandomValues(new Uint8Array(NONCE_LENGTH_BYTES));\n const ciphertext = await globalThis.crypto.subtle.encrypt(\n { name: 'AES-GCM', iv: nonce, additionalData: encoder.encode(aad) },\n key,\n encoder.encode(plaintext),\n );\n\n return {\n ciphertext: toBase64Url(new Uint8Array(ciphertext)),\n nonce: toBase64Url(nonce),\n keyVersion: current.version,\n };\n } catch {\n throw new AiSdkByokAdapterError('Drizzle encryption failed');\n }\n },\n\n async decrypt(payload: EncryptedPayload, aad: string): Promise<string> {\n const key = keys.get(payload.keyVersion);\n if (key === undefined) {\n throw new AiSdkByokAdapterError('Drizzle credential payload uses an unconfigured encryption key');\n }\n\n try {\n const plaintext = await globalThis.crypto.subtle.decrypt(\n { name: 'AES-GCM', iv: fromBase64Url(payload.nonce), additionalData: encoder.encode(aad) },\n await importKey(key),\n fromBase64Url(payload.ciphertext),\n );\n return decoder.decode(plaintext);\n } catch {\n throw new AiSdkByokAdapterError('Drizzle credential payload failed to decrypt');\n }\n },\n };\n}\n","import {\n AiSdkByokAdapterError,\n AiSdkByokValidationError,\n type ApiKeyCredentials,\n type ByokManagerOptions,\n type KeyMetadata,\n type StoredKeyCredentialRecord,\n} from 'ai-sdk-byok';\nimport { and, desc, eq } from 'drizzle-orm';\nimport type { PgDatabase, PgQueryResultHKT } from 'drizzle-orm/pg-core';\nimport { createKeyring, credentialAad, type EncryptionConfig } from './crypto.js';\nimport { aiSdkByokKeys } from './schema.js';\n\ntype DrizzlePostgresDatabase = PgDatabase<PgQueryResultHKT, Record<string, unknown>>;\n\ninterface KeyMetadataRow {\n id: string;\n userId: string;\n provider: string;\n label: string;\n keyHint: string;\n createdAt: string;\n updatedAt: string;\n}\n\ninterface CredentialRow extends KeyMetadataRow {\n credentialsCiphertext: string;\n credentialsNonce: string;\n encryptionKeyVersion: string;\n}\n\nexport interface DrizzleAdapterOptions {\n db: DrizzlePostgresDatabase;\n dialect: 'postgres';\n encryption: EncryptionConfig;\n}\n\nfunction adapterError(operation: string): AiSdkByokAdapterError {\n return new AiSdkByokAdapterError(`Drizzle BYOK adapter failed during ${operation}`);\n}\n\nfunction toMetadata(row: KeyMetadataRow): KeyMetadata {\n return {\n id: row.id,\n userId: row.userId,\n provider: row.provider,\n label: row.label,\n keyHint: row.keyHint,\n createdAt: row.createdAt,\n updatedAt: row.updatedAt,\n };\n}\n\nfunction parseCredentials(plaintext: string): ApiKeyCredentials {\n try {\n const parsed: unknown = JSON.parse(plaintext);\n\n if (\n typeof parsed !== 'object' ||\n parsed === null ||\n Array.isArray(parsed) ||\n Object.keys(parsed).length !== 1 ||\n typeof (parsed as { apiKey?: unknown }).apiKey !== 'string'\n ) {\n throw new Error('invalid credential payload shape');\n }\n\n return { apiKey: (parsed as { apiKey: string }).apiKey };\n } catch {\n throw new AiSdkByokAdapterError('Drizzle credential payload has an invalid shape');\n }\n}\n\nexport function drizzleAdapter(options: DrizzleAdapterOptions): ByokManagerOptions['storage'] {\n if (options?.dialect !== 'postgres') {\n throw new AiSdkByokValidationError('Drizzle dialect must be postgres');\n }\n\n const db = options.db;\n const keyring = createKeyring(options.encryption);\n\n return {\n async save(input) {\n const encrypted = await keyring.encrypt(\n JSON.stringify({ apiKey: input.credentials.apiKey }),\n credentialAad(input.userId, input.provider),\n );\n const now = new Date().toISOString();\n let row: KeyMetadataRow | undefined;\n\n try {\n [row] = await db\n .insert(aiSdkByokKeys)\n .values({\n id: crypto.randomUUID(),\n userId: input.userId,\n provider: input.provider,\n label: input.label,\n keyHint: input.keyHint,\n credentialsCiphertext: encrypted.ciphertext,\n credentialsNonce: encrypted.nonce,\n encryptionKeyVersion: encrypted.keyVersion,\n createdAt: now,\n updatedAt: now,\n })\n .onConflictDoUpdate({\n target: [aiSdkByokKeys.userId, aiSdkByokKeys.provider, aiSdkByokKeys.label],\n set: {\n keyHint: input.keyHint,\n credentialsCiphertext: encrypted.ciphertext,\n credentialsNonce: encrypted.nonce,\n encryptionKeyVersion: encrypted.keyVersion,\n updatedAt: now,\n },\n })\n .returning({\n id: aiSdkByokKeys.id,\n userId: aiSdkByokKeys.userId,\n provider: aiSdkByokKeys.provider,\n label: aiSdkByokKeys.label,\n keyHint: aiSdkByokKeys.keyHint,\n createdAt: aiSdkByokKeys.createdAt,\n updatedAt: aiSdkByokKeys.updatedAt,\n });\n } catch {\n throw adapterError('save');\n }\n\n if (row === undefined) {\n throw adapterError('save');\n }\n\n return toMetadata(row);\n },\n\n async list(input) {\n try {\n const rows = await db\n .select({\n id: aiSdkByokKeys.id,\n userId: aiSdkByokKeys.userId,\n provider: aiSdkByokKeys.provider,\n label: aiSdkByokKeys.label,\n keyHint: aiSdkByokKeys.keyHint,\n createdAt: aiSdkByokKeys.createdAt,\n updatedAt: aiSdkByokKeys.updatedAt,\n })\n .from(aiSdkByokKeys)\n .where(eq(aiSdkByokKeys.userId, input.userId))\n .orderBy(desc(aiSdkByokKeys.updatedAt), desc(aiSdkByokKeys.createdAt));\n\n return rows.map(toMetadata);\n } catch {\n throw adapterError('list');\n }\n },\n\n async get(input) {\n let row: Pick<CredentialRow, 'userId' | 'provider' | 'credentialsCiphertext' | 'credentialsNonce' | 'encryptionKeyVersion'> | undefined;\n\n try {\n [row] = await db\n .select({\n userId: aiSdkByokKeys.userId,\n provider: aiSdkByokKeys.provider,\n credentialsCiphertext: aiSdkByokKeys.credentialsCiphertext,\n credentialsNonce: aiSdkByokKeys.credentialsNonce,\n encryptionKeyVersion: aiSdkByokKeys.encryptionKeyVersion,\n })\n .from(aiSdkByokKeys)\n .where(\n and(\n eq(aiSdkByokKeys.userId, input.userId),\n eq(aiSdkByokKeys.provider, input.provider),\n eq(aiSdkByokKeys.label, input.label),\n ),\n );\n } catch {\n throw adapterError('get');\n }\n\n if (row === undefined) {\n return null;\n }\n\n const plaintext = await keyring.decrypt(\n {\n ciphertext: row.credentialsCiphertext,\n nonce: row.credentialsNonce,\n keyVersion: row.encryptionKeyVersion,\n },\n credentialAad(row.userId, row.provider),\n );\n return parseCredentials(plaintext);\n },\n\n async getById(input) {\n let row: CredentialRow | undefined;\n\n try {\n [row] = await db\n .select({\n id: aiSdkByokKeys.id,\n userId: aiSdkByokKeys.userId,\n provider: aiSdkByokKeys.provider,\n label: aiSdkByokKeys.label,\n keyHint: aiSdkByokKeys.keyHint,\n createdAt: aiSdkByokKeys.createdAt,\n updatedAt: aiSdkByokKeys.updatedAt,\n credentialsCiphertext: aiSdkByokKeys.credentialsCiphertext,\n credentialsNonce: aiSdkByokKeys.credentialsNonce,\n encryptionKeyVersion: aiSdkByokKeys.encryptionKeyVersion,\n })\n .from(aiSdkByokKeys)\n .where(and(eq(aiSdkByokKeys.userId, input.userId), eq(aiSdkByokKeys.id, input.keyId)));\n } catch {\n throw adapterError('getById');\n }\n\n if (row === undefined) {\n return null;\n }\n\n const plaintext = await keyring.decrypt(\n {\n ciphertext: row.credentialsCiphertext,\n nonce: row.credentialsNonce,\n keyVersion: row.encryptionKeyVersion,\n },\n credentialAad(row.userId, row.provider),\n );\n return { ...toMetadata(row), credentials: parseCredentials(plaintext) } satisfies StoredKeyCredentialRecord;\n },\n\n async delete(input) {\n try {\n await db\n .delete(aiSdkByokKeys)\n .where(and(eq(aiSdkByokKeys.userId, input.userId), eq(aiSdkByokKeys.id, input.keyId)));\n } catch {\n throw adapterError('delete');\n }\n },\n };\n}\n"]}
@@ -0,0 +1,16 @@
1
+ CREATE TABLE ai_sdk_byok_keys (
2
+ id TEXT PRIMARY KEY,
3
+ user_id TEXT NOT NULL,
4
+ provider TEXT NOT NULL,
5
+ label TEXT NOT NULL,
6
+ key_hint TEXT NOT NULL,
7
+ credentials_ciphertext TEXT NOT NULL,
8
+ credentials_nonce TEXT NOT NULL,
9
+ encryption_key_version TEXT NOT NULL,
10
+ created_at TIMESTAMPTZ NOT NULL,
11
+ updated_at TIMESTAMPTZ NOT NULL,
12
+ CONSTRAINT ai_sdk_byok_keys_user_provider_label_unique UNIQUE (user_id, provider, label)
13
+ );
14
+
15
+ CREATE INDEX ai_sdk_byok_keys_user_updated_created_idx
16
+ ON ai_sdk_byok_keys (user_id, updated_at DESC, created_at DESC);
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "@ai-sdk-byok/drizzle",
3
+ "version": "0.2.0",
4
+ "description": "Drizzle SQL storage adapter for ai-sdk-byok.",
5
+ "license": "MIT",
6
+ "homepage": "https://github.com/Xyri1/ai-sdk-byok#readme",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/Xyri1/ai-sdk-byok.git",
10
+ "directory": "packages/drizzle"
11
+ },
12
+ "bugs": {
13
+ "url": "https://github.com/Xyri1/ai-sdk-byok/issues"
14
+ },
15
+ "type": "module",
16
+ "sideEffects": false,
17
+ "engines": {
18
+ "node": ">=22"
19
+ },
20
+ "exports": {
21
+ ".": {
22
+ "types": "./dist/index.d.ts",
23
+ "import": "./dist/index.js"
24
+ },
25
+ "./migrations/*.sql": "./migrations/*.sql"
26
+ },
27
+ "files": [
28
+ "dist",
29
+ "migrations"
30
+ ],
31
+ "dependencies": {
32
+ "ai-sdk-byok": "0.2.0"
33
+ },
34
+ "peerDependencies": {
35
+ "drizzle-orm": ">=0.41.0"
36
+ },
37
+ "devDependencies": {
38
+ "drizzle-orm": "^0.41.0"
39
+ }
40
+ }