@cipherstash/stack 0.4.0 → 0.5.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.
Files changed (80) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +9 -36
  3. package/dist/bin/stash.js +215 -179
  4. package/dist/bin/stash.js.map +1 -1
  5. package/dist/{chunk-OAPLZLR5.js → chunk-GXGEW6T4.js} +2 -76
  6. package/dist/chunk-GXGEW6T4.js.map +1 -0
  7. package/dist/{chunk-KOU2MHXJ.js → chunk-HCL75LXQ.js} +1 -1
  8. package/dist/chunk-HCL75LXQ.js.map +1 -0
  9. package/dist/chunk-LBMC4D6D.js +19 -0
  10. package/dist/chunk-LBMC4D6D.js.map +1 -0
  11. package/dist/chunk-Q5FTQLYG.js +68 -0
  12. package/dist/chunk-Q5FTQLYG.js.map +1 -0
  13. package/dist/{chunk-WUZGKTMG.js → chunk-TGUELLJS.js} +16 -19
  14. package/dist/chunk-TGUELLJS.js.map +1 -0
  15. package/dist/chunk-YLISU7FD.js +357 -0
  16. package/dist/chunk-YLISU7FD.js.map +1 -0
  17. package/dist/{client-CUGXheRq.d.cts → client-BFrHTgC6.d.ts} +36 -3
  18. package/dist/{client-CrGQnSlk.d.ts → client-BVQvgvdY.d.cts} +36 -3
  19. package/dist/client.d.cts +4 -4
  20. package/dist/client.d.ts +4 -4
  21. package/dist/drizzle/index.cjs.map +1 -1
  22. package/dist/drizzle/index.d.cts +14 -6
  23. package/dist/drizzle/index.d.ts +14 -6
  24. package/dist/drizzle/index.js +3 -3
  25. package/dist/drizzle/index.js.map +1 -1
  26. package/dist/dynamodb/index.cjs +68 -0
  27. package/dist/dynamodb/index.cjs.map +1 -1
  28. package/dist/dynamodb/index.d.cts +4 -4
  29. package/dist/dynamodb/index.d.ts +4 -4
  30. package/dist/dynamodb/index.js +9 -0
  31. package/dist/dynamodb/index.js.map +1 -1
  32. package/dist/encryption/index.cjs +2572 -0
  33. package/dist/encryption/index.cjs.map +1 -0
  34. package/dist/encryption/index.d.cts +7 -0
  35. package/dist/encryption/index.d.ts +7 -0
  36. package/dist/encryption/index.js +17 -0
  37. package/dist/encryption/index.js.map +1 -0
  38. package/dist/errors/index.cjs +44 -0
  39. package/dist/errors/index.cjs.map +1 -0
  40. package/dist/errors/index.d.cts +94 -0
  41. package/dist/errors/index.d.ts +94 -0
  42. package/dist/errors/index.js +9 -0
  43. package/dist/errors/index.js.map +1 -0
  44. package/dist/identity/index.cjs +21 -13
  45. package/dist/identity/index.cjs.map +1 -1
  46. package/dist/identity/index.d.cts +78 -2
  47. package/dist/identity/index.d.ts +78 -2
  48. package/dist/identity/index.js +7 -3
  49. package/dist/identity/index.js.map +1 -1
  50. package/dist/index.cjs +379 -41
  51. package/dist/index.cjs.map +1 -1
  52. package/dist/index.d.cts +21 -19
  53. package/dist/index.d.ts +21 -19
  54. package/dist/index.js +14 -8
  55. package/dist/schema/index.d.cts +1 -2
  56. package/dist/schema/index.d.ts +1 -2
  57. package/dist/secrets/index.cjs +70 -39
  58. package/dist/secrets/index.cjs.map +1 -1
  59. package/dist/secrets/index.d.cts +4 -6
  60. package/dist/secrets/index.d.ts +4 -6
  61. package/dist/secrets/index.js +9 -318
  62. package/dist/secrets/index.js.map +1 -1
  63. package/dist/supabase/index.cjs +84 -1
  64. package/dist/supabase/index.cjs.map +1 -1
  65. package/dist/supabase/index.d.cts +4 -4
  66. package/dist/supabase/index.d.ts +4 -4
  67. package/dist/supabase/index.js +24 -1
  68. package/dist/supabase/index.js.map +1 -1
  69. package/dist/{types-public-Wj6tB_ux.d.ts → types-public-dKxDzTIL.d.cts} +1 -9
  70. package/dist/{types-public-Wj6tB_ux.d.cts → types-public-dKxDzTIL.d.ts} +1 -9
  71. package/dist/types-public.cjs.map +1 -1
  72. package/dist/types-public.d.cts +1 -2
  73. package/dist/types-public.d.ts +1 -2
  74. package/dist/types-public.js +1 -1
  75. package/package.json +29 -1
  76. package/dist/chunk-KOU2MHXJ.js.map +0 -1
  77. package/dist/chunk-OAPLZLR5.js.map +0 -1
  78. package/dist/chunk-WUZGKTMG.js.map +0 -1
  79. package/dist/index-9-Ya3fDK.d.cts +0 -169
  80. package/dist/index-9-Ya3fDK.d.ts +0 -169
@@ -1,7 +1,6 @@
1
- import { j as Encrypted } from '../types-public-Wj6tB_ux.cjs';
1
+ import { E as Encrypted } from '../types-public-dKxDzTIL.cjs';
2
2
  import { Result } from '@byteslice/result';
3
3
  import 'zod';
4
- import 'evlog';
5
4
  import '@cipherstash/protect-ffi';
6
5
 
7
6
  /**
@@ -36,11 +35,10 @@ interface SecretsError {
36
35
  * Configuration options for initializing the Stash client
37
36
  */
38
37
  interface SecretsConfig {
39
- workspaceCRN: string;
40
- clientId: string;
41
- clientKey: string;
42
38
  environment: string;
43
- apiKey: string;
39
+ workspaceCRN?: string;
40
+ clientId?: string;
41
+ clientKey?: string;
44
42
  accessKey?: string;
45
43
  }
46
44
  /**
@@ -1,7 +1,6 @@
1
- import { j as Encrypted } from '../types-public-Wj6tB_ux.js';
1
+ import { E as Encrypted } from '../types-public-dKxDzTIL.js';
2
2
  import { Result } from '@byteslice/result';
3
3
  import 'zod';
4
- import 'evlog';
5
4
  import '@cipherstash/protect-ffi';
6
5
 
7
6
  /**
@@ -36,11 +35,10 @@ interface SecretsError {
36
35
  * Configuration options for initializing the Stash client
37
36
  */
38
37
  interface SecretsConfig {
39
- workspaceCRN: string;
40
- clientId: string;
41
- clientKey: string;
42
38
  environment: string;
43
- apiKey: string;
39
+ workspaceCRN?: string;
40
+ clientId?: string;
41
+ clientKey?: string;
44
42
  accessKey?: string;
45
43
  }
46
44
  /**
@@ -1,322 +1,13 @@
1
1
  import {
2
- Encryption
3
- } from "../chunk-WUZGKTMG.js";
4
- import {
5
- encryptedToPgComposite
6
- } from "../chunk-4RNBI3UH.js";
7
- import "../chunk-KOU2MHXJ.js";
8
- import {
9
- extractWorkspaceIdFromCrn
10
- } from "../chunk-OAPLZLR5.js";
11
- import {
12
- encryptedColumn,
13
- encryptedTable
14
- } from "../chunk-Y35W4WFN.js";
15
-
16
- // src/secrets/index.ts
17
- var Secrets = class {
18
- encryptionClient = null;
19
- config;
20
- apiBaseUrl = process.env.STASH_API_URL || "https://dashboard.cipherstash.com/api/secrets";
21
- secretsSchema = encryptedTable("secrets", {
22
- value: encryptedColumn("value")
23
- });
24
- constructor(config) {
25
- this.config = config;
26
- }
27
- initPromise = null;
28
- /**
29
- * Initialize the Secrets client and underlying Encryption client
30
- */
31
- async ensureInitialized() {
32
- if (!this.initPromise) {
33
- this.initPromise = this._doInit();
34
- }
35
- return this.initPromise;
36
- }
37
- async _doInit() {
38
- this.encryptionClient = await Encryption({
39
- schemas: [this.secretsSchema],
40
- config: {
41
- workspaceCrn: this.config.workspaceCRN,
42
- clientId: this.config.clientId,
43
- clientKey: this.config.clientKey,
44
- accessKey: this.config.apiKey,
45
- keyset: {
46
- name: this.config.environment
47
- }
48
- }
49
- });
50
- }
51
- /**
52
- * Get the authorization header for API requests
53
- */
54
- getAuthHeader() {
55
- return `Bearer ${this.config.apiKey}`;
56
- }
57
- /**
58
- * Make an API request with error handling.
59
- *
60
- * For GET requests, `params` are appended as URL query parameters.
61
- * For POST requests, `body` is sent as JSON in the request body.
62
- */
63
- async apiRequest(method, path, options) {
64
- try {
65
- let url = `${this.apiBaseUrl}${path}`;
66
- if (options?.params) {
67
- const searchParams = new URLSearchParams(options.params);
68
- url = `${url}?${searchParams.toString()}`;
69
- }
70
- const headers = {
71
- "Content-Type": "application/json",
72
- Authorization: this.getAuthHeader()
73
- };
74
- const response = await fetch(url, {
75
- method,
76
- headers,
77
- body: options?.body ? JSON.stringify(options.body) : void 0
78
- });
79
- if (!response.ok) {
80
- const errorText = await response.text();
81
- let errorMessage = `API request failed with status ${response.status}`;
82
- try {
83
- const errorJson = JSON.parse(errorText);
84
- errorMessage = errorJson.message || errorJson.error || errorMessage;
85
- } catch {
86
- errorMessage = errorText || errorMessage;
87
- }
88
- return {
89
- failure: {
90
- type: "ApiError",
91
- message: errorMessage
92
- }
93
- };
94
- }
95
- const data = await response.json();
96
- return { data };
97
- } catch (error) {
98
- return {
99
- failure: {
100
- type: "NetworkError",
101
- message: error instanceof Error ? error.message : "Unknown network error occurred"
102
- }
103
- };
104
- }
105
- }
106
- /**
107
- * Store an encrypted secret in the vault.
108
- * The value is encrypted locally before being sent to the API.
109
- *
110
- * API: POST /api/secrets/set
111
- *
112
- * @param name - The name of the secret
113
- * @param value - The plaintext value to encrypt and store
114
- * @returns A Result containing the API response or an error
115
- */
116
- async set(name, value) {
117
- await this.ensureInitialized();
118
- if (!this.encryptionClient) {
119
- return {
120
- failure: {
121
- type: "ClientError",
122
- message: "Failed to initialize Encryption client"
123
- }
124
- };
125
- }
126
- const encryptResult = await this.encryptionClient.encrypt(value, {
127
- column: this.secretsSchema.value,
128
- table: this.secretsSchema
129
- });
130
- if (encryptResult.failure) {
131
- return {
132
- failure: {
133
- type: "EncryptionError",
134
- message: encryptResult.failure.message
135
- }
136
- };
137
- }
138
- const workspaceId = extractWorkspaceIdFromCrn(this.config.workspaceCRN);
139
- return await this.apiRequest("POST", "/set", {
140
- body: {
141
- workspaceId,
142
- environment: this.config.environment,
143
- name,
144
- encryptedValue: encryptedToPgComposite(encryptResult.data)
145
- }
146
- });
147
- }
148
- /**
149
- * Retrieve and decrypt a secret from the vault.
150
- * The secret is decrypted locally after retrieval.
151
- *
152
- * API: GET /api/secrets/get?workspaceId=...&environment=...&name=...
153
- *
154
- * @param name - The name of the secret to retrieve
155
- * @returns A Result containing the decrypted value or an error
156
- */
157
- async get(name) {
158
- await this.ensureInitialized();
159
- if (!this.encryptionClient) {
160
- return {
161
- failure: {
162
- type: "ClientError",
163
- message: "Failed to initialize Encryption client"
164
- }
165
- };
166
- }
167
- const workspaceId = extractWorkspaceIdFromCrn(this.config.workspaceCRN);
168
- const apiResult = await this.apiRequest("GET", "/get", {
169
- params: {
170
- workspaceId,
171
- environment: this.config.environment,
172
- name
173
- }
174
- });
175
- if (apiResult.failure) {
176
- return apiResult;
177
- }
178
- const decryptResult = await this.encryptionClient.decrypt(
179
- apiResult.data.encryptedValue.data
180
- );
181
- if (decryptResult.failure) {
182
- return {
183
- failure: {
184
- type: "DecryptionError",
185
- message: decryptResult.failure.message
186
- }
187
- };
188
- }
189
- if (typeof decryptResult.data !== "string") {
190
- return {
191
- failure: {
192
- type: "DecryptionError",
193
- message: "Decrypted value is not a string"
194
- }
195
- };
196
- }
197
- return { data: decryptResult.data };
198
- }
199
- /**
200
- * Retrieve and decrypt many secrets from the vault.
201
- * The secrets are decrypted locally after retrieval.
202
- * This method only triggers a single network request to the ZeroKMS.
203
- *
204
- * API: GET /api/secrets/get-many?workspaceId=...&environment=...&names=name1,name2,...
205
- *
206
- * Constraints:
207
- * - Minimum 2 secret names required
208
- * - Maximum 100 secret names per request
209
- *
210
- * @param names - The names of the secrets to retrieve (min 2, max 100)
211
- * @returns A Result containing an object mapping secret names to their decrypted values
212
- */
213
- async getMany(names) {
214
- await this.ensureInitialized();
215
- if (!this.encryptionClient) {
216
- return {
217
- failure: {
218
- type: "ClientError",
219
- message: "Failed to initialize Encryption client"
220
- }
221
- };
222
- }
223
- if (names.length < 2) {
224
- return {
225
- failure: {
226
- type: "ClientError",
227
- message: "At least 2 secret names are required for getMany"
228
- }
229
- };
230
- }
231
- if (names.length > 100) {
232
- return {
233
- failure: {
234
- type: "ClientError",
235
- message: "Maximum 100 secret names per request"
236
- }
237
- };
238
- }
239
- const workspaceId = extractWorkspaceIdFromCrn(this.config.workspaceCRN);
240
- const apiResult = await this.apiRequest(
241
- "GET",
242
- "/get-many",
243
- {
244
- params: {
245
- workspaceId,
246
- environment: this.config.environment,
247
- names: names.join(",")
248
- }
249
- }
250
- );
251
- if (apiResult.failure) {
252
- return apiResult;
253
- }
254
- const dataToDecrypt = apiResult.data.map((item) => ({
255
- name: item.name,
256
- value: item.encryptedValue.data
257
- }));
258
- const decryptResult = await this.encryptionClient.bulkDecryptModels(dataToDecrypt);
259
- if (decryptResult.failure) {
260
- return {
261
- failure: {
262
- type: "DecryptionError",
263
- message: decryptResult.failure.message
264
- }
265
- };
266
- }
267
- const decryptedSecrets = decryptResult.data;
268
- const secretsMap = {};
269
- for (const secret of decryptedSecrets) {
270
- if (secret.name && secret.value) {
271
- secretsMap[secret.name] = secret.value;
272
- }
273
- }
274
- return { data: secretsMap };
275
- }
276
- /**
277
- * List all secrets in the environment.
278
- * Only names and metadata are returned; values remain encrypted.
279
- *
280
- * API: GET /api/secrets/list?workspaceId=...&environment=...
281
- *
282
- * @returns A Result containing the list of secrets or an error
283
- */
284
- async list() {
285
- const workspaceId = extractWorkspaceIdFromCrn(this.config.workspaceCRN);
286
- const apiResult = await this.apiRequest(
287
- "GET",
288
- "/list",
289
- {
290
- params: {
291
- workspaceId,
292
- environment: this.config.environment
293
- }
294
- }
295
- );
296
- if (apiResult.failure) {
297
- return apiResult;
298
- }
299
- return { data: apiResult.data.secrets };
300
- }
301
- /**
302
- * Delete a secret from the vault.
303
- *
304
- * API: POST /api/secrets/delete
305
- *
306
- * @param name - The name of the secret to delete
307
- * @returns A Result containing the API response or an error
308
- */
309
- async delete(name) {
310
- const workspaceId = extractWorkspaceIdFromCrn(this.config.workspaceCRN);
311
- return await this.apiRequest("POST", "/delete", {
312
- body: {
313
- workspaceId,
314
- environment: this.config.environment,
315
- name
316
- }
317
- });
318
- }
319
- };
2
+ Secrets
3
+ } from "../chunk-YLISU7FD.js";
4
+ import "../chunk-TGUELLJS.js";
5
+ import "../chunk-4RNBI3UH.js";
6
+ import "../chunk-Y35W4WFN.js";
7
+ import "../chunk-HCL75LXQ.js";
8
+ import "../chunk-GXGEW6T4.js";
9
+ import "../chunk-Q5FTQLYG.js";
10
+ import "../chunk-LBMC4D6D.js";
320
11
  export {
321
12
  Secrets
322
13
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/secrets/index.ts"],"sourcesContent":["/**\n * Placeholder: Corrected Secrets client interface\n *\n * This file reflects the actual dashboard API endpoints as implemented in:\n * apps/dashboard/src/app/api/secrets/{get,set,list,get-many,delete}/route.ts\n *\n * Key corrections from the original interface:\n * 1. get, list, get-many are GET endpoints (not POST) with query params\n * 2. get-many takes a comma-separated `names` string (not a JSON array)\n * 3. set and delete return { success, message } (not void)\n * 4. SecretMetadata fields (id, createdAt, updatedAt) are non-optional\n * 5. GetSecretResponse fields (createdAt, updatedAt) are non-optional\n * 6. get-many enforces min 2 names (comma required) and max 100 names\n */\n\nimport type { EncryptionClient } from '@/encryption/index.js'\nimport { encryptedToPgComposite } from '@/encryption/helpers'\nimport { Encryption } from '@/index'\nimport { encryptedColumn, encryptedTable } from '@/schema'\nimport type { Encrypted } from '@/types'\nimport type { Result } from '@byteslice/result'\nimport { extractWorkspaceIdFromCrn } from '../utils/config/index.js'\n\nexport type SecretName = string\nexport type SecretValue = string\n\n/**\n * Discriminated error type for secrets operations.\n */\nexport type SecretsErrorType =\n | 'ApiError'\n | 'NetworkError'\n | 'ClientError'\n | 'EncryptionError'\n | 'DecryptionError'\n\n/**\n * Error returned by secrets operations.\n */\nexport interface SecretsError {\n type: SecretsErrorType\n message: string\n}\n\n/**\n * Configuration options for initializing the Stash client\n */\nexport interface SecretsConfig {\n workspaceCRN: string\n clientId: string\n clientKey: string\n environment: string\n apiKey: string\n accessKey?: string\n}\n\n/**\n * Secret metadata returned from the API (list endpoint).\n * All fields are always present in API responses.\n */\nexport interface SecretMetadata {\n id: string\n name: string\n environment: string\n createdAt: string\n updatedAt: string\n}\n\n/**\n * API response for listing secrets.\n * GET /api/secrets/list?workspaceId=...&environment=...\n */\nexport interface ListSecretsResponse {\n environment: string\n secrets: SecretMetadata[]\n}\n\n/**\n * API response for getting a single secret.\n * GET /api/secrets/get?workspaceId=...&environment=...&name=...\n *\n * The `encryptedValue` is the raw value stored in the vault's `value` column,\n * which is the `{ data: Encrypted }` object that was passed to the set endpoint.\n */\nexport interface GetSecretResponse {\n name: string\n environment: string\n encryptedValue: {\n data: Encrypted\n }\n createdAt: string\n updatedAt: string\n}\n\n/**\n * API response for getting multiple secrets.\n * GET /api/secrets/get-many?workspaceId=...&environment=...&names=name1,name2,...\n *\n * Returns an array of GetSecretResponse objects.\n * Constraints:\n * - `names` must be comma-separated (minimum 2 names)\n * - Maximum 100 names per request\n */\nexport type GetManySecretsResponse = GetSecretResponse[]\n\n/**\n * API response for setting a secret.\n * POST /api/secrets/set\n */\nexport interface SetSecretResponse {\n success: true\n message: string\n}\n\n/**\n * API request body for setting a secret.\n * POST /api/secrets/set\n */\nexport interface SetSecretRequest {\n workspaceId: string\n environment: string\n name: string\n encryptedValue: {\n data: Encrypted\n }\n}\n\n/**\n * API response for deleting a secret.\n * POST /api/secrets/delete\n */\nexport interface DeleteSecretResponse {\n success: true\n message: string\n}\n\n/**\n * API request body for deleting a secret.\n * POST /api/secrets/delete\n */\nexport interface DeleteSecretRequest {\n workspaceId: string\n environment: string\n name: string\n}\n\n/**\n * API error response for plan limit violations (403).\n * Returned by POST /api/secrets/set when the workspace has reached its secret limit.\n */\nexport interface PlanLimitError {\n error: string\n code: 'PLAN_LIMIT_REACHED'\n}\n\nexport interface DecryptedSecretResponse {\n name: string\n environment: string\n value: string\n createdAt: string\n updatedAt: string\n}\n\n/**\n * The Secrets client provides a high-level API for managing encrypted secrets\n * stored in CipherStash. Secrets are encrypted locally before being sent to\n * the API, ensuring end-to-end encryption.\n */\nexport class Secrets {\n private encryptionClient: EncryptionClient | null = null\n private config: SecretsConfig\n private readonly apiBaseUrl =\n process.env.STASH_API_URL || 'https://dashboard.cipherstash.com/api/secrets'\n private readonly secretsSchema = encryptedTable('secrets', {\n value: encryptedColumn('value'),\n })\n\n constructor(config: SecretsConfig) {\n this.config = config\n }\n\n private initPromise: Promise<void> | null = null\n\n /**\n * Initialize the Secrets client and underlying Encryption client\n */\n private async ensureInitialized(): Promise<void> {\n if (!this.initPromise) {\n this.initPromise = this._doInit()\n }\n return this.initPromise\n }\n\n private async _doInit(): Promise<void> {\n this.encryptionClient = await Encryption({\n schemas: [this.secretsSchema],\n config: {\n workspaceCrn: this.config.workspaceCRN,\n clientId: this.config.clientId,\n clientKey: this.config.clientKey,\n accessKey: this.config.apiKey,\n keyset: {\n name: this.config.environment,\n },\n },\n })\n }\n\n /**\n * Get the authorization header for API requests\n */\n private getAuthHeader(): string {\n return `Bearer ${this.config.apiKey}`\n }\n\n /**\n * Make an API request with error handling.\n *\n * For GET requests, `params` are appended as URL query parameters.\n * For POST requests, `body` is sent as JSON in the request body.\n */\n private async apiRequest<T>(\n method: 'GET' | 'POST',\n path: string,\n options?: {\n body?: unknown\n params?: Record<string, string>\n },\n ): Promise<Result<T, SecretsError>> {\n try {\n let url = `${this.apiBaseUrl}${path}`\n\n if (options?.params) {\n const searchParams = new URLSearchParams(options.params)\n url = `${url}?${searchParams.toString()}`\n }\n\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n Authorization: this.getAuthHeader(),\n }\n\n const response = await fetch(url, {\n method,\n headers,\n body: options?.body ? JSON.stringify(options.body) : undefined,\n })\n\n if (!response.ok) {\n const errorText = await response.text()\n let errorMessage = `API request failed with status ${response.status}`\n try {\n const errorJson = JSON.parse(errorText)\n errorMessage = errorJson.message || errorJson.error || errorMessage\n } catch {\n errorMessage = errorText || errorMessage\n }\n\n return {\n failure: {\n type: 'ApiError',\n message: errorMessage,\n },\n }\n }\n\n const data = await response.json()\n return { data }\n } catch (error) {\n return {\n failure: {\n type: 'NetworkError',\n message:\n error instanceof Error\n ? error.message\n : 'Unknown network error occurred',\n },\n }\n }\n }\n\n /**\n * Store an encrypted secret in the vault.\n * The value is encrypted locally before being sent to the API.\n *\n * API: POST /api/secrets/set\n *\n * @param name - The name of the secret\n * @param value - The plaintext value to encrypt and store\n * @returns A Result containing the API response or an error\n */\n async set(\n name: SecretName,\n value: SecretValue,\n ): Promise<Result<SetSecretResponse, SecretsError>> {\n await this.ensureInitialized()\n\n if (!this.encryptionClient) {\n return {\n failure: {\n type: 'ClientError',\n message: 'Failed to initialize Encryption client',\n },\n }\n }\n\n // Encrypt the value locally\n const encryptResult = await this.encryptionClient.encrypt(value, {\n column: this.secretsSchema.value,\n table: this.secretsSchema,\n })\n\n if (encryptResult.failure) {\n return {\n failure: {\n type: 'EncryptionError',\n message: encryptResult.failure.message,\n },\n }\n }\n\n // Extract workspaceId from CRN\n const workspaceId = extractWorkspaceIdFromCrn(this.config.workspaceCRN)\n\n // Send encrypted value to API\n return await this.apiRequest<SetSecretResponse>('POST', '/set', {\n body: {\n workspaceId,\n environment: this.config.environment,\n name,\n encryptedValue: encryptedToPgComposite(encryptResult.data),\n },\n })\n }\n\n /**\n * Retrieve and decrypt a secret from the vault.\n * The secret is decrypted locally after retrieval.\n *\n * API: GET /api/secrets/get?workspaceId=...&environment=...&name=...\n *\n * @param name - The name of the secret to retrieve\n * @returns A Result containing the decrypted value or an error\n */\n async get(name: SecretName): Promise<Result<SecretValue, SecretsError>> {\n await this.ensureInitialized()\n\n if (!this.encryptionClient) {\n return {\n failure: {\n type: 'ClientError',\n message: 'Failed to initialize Encryption client',\n },\n }\n }\n\n // Extract workspaceId from CRN\n const workspaceId = extractWorkspaceIdFromCrn(this.config.workspaceCRN)\n\n // Fetch encrypted value from API via GET with query params\n const apiResult = await this.apiRequest<GetSecretResponse>('GET', '/get', {\n params: {\n workspaceId,\n environment: this.config.environment,\n name,\n },\n })\n\n if (apiResult.failure) {\n return apiResult\n }\n\n // Decrypt the value locally\n const decryptResult = await this.encryptionClient.decrypt(\n apiResult.data.encryptedValue.data,\n )\n\n if (decryptResult.failure) {\n return {\n failure: {\n type: 'DecryptionError',\n message: decryptResult.failure.message,\n },\n }\n }\n\n if (typeof decryptResult.data !== 'string') {\n return {\n failure: {\n type: 'DecryptionError',\n message: 'Decrypted value is not a string',\n },\n }\n }\n\n return { data: decryptResult.data }\n }\n\n /**\n * Retrieve and decrypt many secrets from the vault.\n * The secrets are decrypted locally after retrieval.\n * This method only triggers a single network request to the ZeroKMS.\n *\n * API: GET /api/secrets/get-many?workspaceId=...&environment=...&names=name1,name2,...\n *\n * Constraints:\n * - Minimum 2 secret names required\n * - Maximum 100 secret names per request\n *\n * @param names - The names of the secrets to retrieve (min 2, max 100)\n * @returns A Result containing an object mapping secret names to their decrypted values\n */\n async getMany(\n names: SecretName[],\n ): Promise<Result<Record<SecretName, SecretValue>, SecretsError>> {\n await this.ensureInitialized()\n\n if (!this.encryptionClient) {\n return {\n failure: {\n type: 'ClientError',\n message: 'Failed to initialize Encryption client',\n },\n }\n }\n\n if (names.length < 2) {\n return {\n failure: {\n type: 'ClientError',\n message: 'At least 2 secret names are required for getMany',\n },\n }\n }\n\n if (names.length > 100) {\n return {\n failure: {\n type: 'ClientError',\n message: 'Maximum 100 secret names per request',\n },\n }\n }\n\n // Extract workspaceId from CRN\n const workspaceId = extractWorkspaceIdFromCrn(this.config.workspaceCRN)\n\n // Fetch encrypted values from API via GET with comma-separated names\n const apiResult = await this.apiRequest<GetManySecretsResponse>(\n 'GET',\n '/get-many',\n {\n params: {\n workspaceId,\n environment: this.config.environment,\n names: names.join(','),\n },\n },\n )\n\n if (apiResult.failure) {\n return apiResult\n }\n\n const dataToDecrypt = apiResult.data.map((item) => ({\n name: item.name,\n value: item.encryptedValue.data,\n }))\n\n const decryptResult =\n await this.encryptionClient.bulkDecryptModels(dataToDecrypt)\n\n if (decryptResult.failure) {\n return {\n failure: {\n type: 'DecryptionError',\n message: decryptResult.failure.message,\n },\n }\n }\n\n // Transform array of decrypted secrets into an object keyed by secret name\n const decryptedSecrets =\n decryptResult.data as unknown as DecryptedSecretResponse[]\n const secretsMap: Record<SecretName, SecretValue> = {}\n\n for (const secret of decryptedSecrets) {\n if (secret.name && secret.value) {\n secretsMap[secret.name] = secret.value\n }\n }\n\n return { data: secretsMap }\n }\n\n /**\n * List all secrets in the environment.\n * Only names and metadata are returned; values remain encrypted.\n *\n * API: GET /api/secrets/list?workspaceId=...&environment=...\n *\n * @returns A Result containing the list of secrets or an error\n */\n async list(): Promise<Result<SecretMetadata[], SecretsError>> {\n // Extract workspaceId from CRN\n const workspaceId = extractWorkspaceIdFromCrn(this.config.workspaceCRN)\n\n const apiResult = await this.apiRequest<ListSecretsResponse>(\n 'GET',\n '/list',\n {\n params: {\n workspaceId,\n environment: this.config.environment,\n },\n },\n )\n\n if (apiResult.failure) {\n return apiResult\n }\n\n return { data: apiResult.data.secrets }\n }\n\n /**\n * Delete a secret from the vault.\n *\n * API: POST /api/secrets/delete\n *\n * @param name - The name of the secret to delete\n * @returns A Result containing the API response or an error\n */\n async delete(\n name: SecretName,\n ): Promise<Result<DeleteSecretResponse, SecretsError>> {\n // Extract workspaceId from CRN\n const workspaceId = extractWorkspaceIdFromCrn(this.config.workspaceCRN)\n\n return await this.apiRequest<DeleteSecretResponse>('POST', '/delete', {\n body: {\n workspaceId,\n environment: this.config.environment,\n name,\n },\n })\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -254,6 +254,69 @@ var EncryptedColumn = class {
254
254
  }
255
255
  };
256
256
 
257
+ // src/utils/logger/index.ts
258
+ var import_evlog = require("evlog");
259
+ var validLevels = ["debug", "info", "error"];
260
+ function levelFromEnv() {
261
+ const env = process.env.STASH_STACK_LOG;
262
+ if (env && validLevels.includes(env)) return env;
263
+ return "error";
264
+ }
265
+ function samplingRatesForLevel(level) {
266
+ switch (level) {
267
+ case "debug":
268
+ return { debug: 100, info: 100, warn: 100, error: 100 };
269
+ case "info":
270
+ return { debug: 0, info: 100, warn: 100, error: 100 };
271
+ case "error":
272
+ default:
273
+ return { debug: 0, info: 0, warn: 0, error: 100 };
274
+ }
275
+ }
276
+ var initialized = false;
277
+ function initStackLogger() {
278
+ if (initialized) return;
279
+ initialized = true;
280
+ const level = levelFromEnv();
281
+ const rates = samplingRatesForLevel(level);
282
+ (0, import_evlog.initLogger)({
283
+ env: { service: "@cipherstash/stack" },
284
+ enabled: true,
285
+ sampling: { rates }
286
+ });
287
+ }
288
+ initStackLogger();
289
+ function safeMessage(args) {
290
+ return typeof args[0] === "string" ? args[0] : "";
291
+ }
292
+ var logger = {
293
+ debug(...args) {
294
+ const log = (0, import_evlog.createRequestLogger)();
295
+ log.set({
296
+ level: "debug",
297
+ source: "@cipherstash/stack",
298
+ message: safeMessage(args)
299
+ });
300
+ log.emit();
301
+ },
302
+ info(...args) {
303
+ const log = (0, import_evlog.createRequestLogger)();
304
+ log.set({ source: "@cipherstash/stack" });
305
+ log.info(safeMessage(args));
306
+ log.emit();
307
+ },
308
+ warn(...args) {
309
+ const log = (0, import_evlog.createRequestLogger)();
310
+ log.warn(safeMessage(args));
311
+ log.emit();
312
+ },
313
+ error(...args) {
314
+ const log = (0, import_evlog.createRequestLogger)();
315
+ log.error(safeMessage(args));
316
+ log.emit();
317
+ }
318
+ };
319
+
257
320
  // src/supabase/helpers.ts
258
321
  function getEncryptedColumnNames(schema) {
259
322
  const built = schema.build();
@@ -569,6 +632,7 @@ var EncryptedQueryBuilderImpl = class {
569
632
  // ---------------------------------------------------------------------------
570
633
  async execute() {
571
634
  try {
635
+ logger.debug(`Supabase encrypted query on table "${this.tableName}".`);
572
636
  const encryptedMutation = await this.encryptMutationData();
573
637
  const selectString = this.buildSelectString();
574
638
  const encryptedFilters = await this.encryptFilterValues();
@@ -579,8 +643,12 @@ var EncryptedQueryBuilderImpl = class {
579
643
  );
580
644
  return await this.decryptResults(result);
581
645
  } catch (err) {
646
+ const message = err instanceof Error ? err.message : String(err);
647
+ logger.error(
648
+ `Supabase encrypted query failed on table "${this.tableName}": ${message}`
649
+ );
582
650
  const error = {
583
- message: err instanceof Error ? err.message : String(err),
651
+ message,
584
652
  encryptionError: void 0
585
653
  };
586
654
  if (this.shouldThrowOnError) {
@@ -608,6 +676,9 @@ var EncryptedQueryBuilderImpl = class {
608
676
  if (this.auditConfig) op2.audit(this.auditConfig);
609
677
  const result2 = await op2;
610
678
  if (result2.failure) {
679
+ logger.error(
680
+ `Supabase: failed to encrypt models for table "${this.tableName}"`
681
+ );
611
682
  throw new EncryptionFailedError(
612
683
  `Failed to encrypt models: ${result2.failure.message}`,
613
684
  result2.failure
@@ -620,6 +691,9 @@ var EncryptedQueryBuilderImpl = class {
620
691
  if (this.auditConfig) op.audit(this.auditConfig);
621
692
  const result = await op;
622
693
  if (result.failure) {
694
+ logger.error(
695
+ `Supabase: failed to encrypt model for table "${this.tableName}"`
696
+ );
623
697
  throw new EncryptionFailedError(
624
698
  `Failed to encrypt model: ${result.failure.message}`,
625
699
  result.failure
@@ -763,6 +837,9 @@ var EncryptedQueryBuilderImpl = class {
763
837
  if (this.auditConfig) op.audit(this.auditConfig);
764
838
  const result = await op;
765
839
  if (result.failure) {
840
+ logger.error(
841
+ `Supabase: failed to encrypt query terms for table "${this.tableName}"`
842
+ );
766
843
  throw new EncryptionFailedError(
767
844
  `Failed to encrypt query terms: ${result.failure.message}`,
768
845
  result.failure
@@ -1027,6 +1104,9 @@ var EncryptedQueryBuilderImpl = class {
1027
1104
  if (this.auditConfig) decryptOp.audit(this.auditConfig);
1028
1105
  const decrypted2 = await decryptOp;
1029
1106
  if (decrypted2.failure) {
1107
+ logger.error(
1108
+ `Supabase: failed to decrypt model for table "${this.tableName}"`
1109
+ );
1030
1110
  throw new EncryptionFailedError(
1031
1111
  `Failed to decrypt model: ${decrypted2.failure.message}`,
1032
1112
  decrypted2.failure
@@ -1055,6 +1135,9 @@ var EncryptedQueryBuilderImpl = class {
1055
1135
  if (this.auditConfig) bulkDecryptOp.audit(this.auditConfig);
1056
1136
  const decrypted = await bulkDecryptOp;
1057
1137
  if (decrypted.failure) {
1138
+ logger.error(
1139
+ `Supabase: failed to decrypt models for table "${this.tableName}"`
1140
+ );
1058
1141
  throw new EncryptionFailedError(
1059
1142
  `Failed to decrypt models: ${decrypted.failure.message}`,
1060
1143
  decrypted.failure