@penvhq/provider-contract 0.1.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 Itzfeminisce
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/dist/index.cjs ADDED
@@ -0,0 +1,455 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ InMemoryProvider: () => InMemoryProvider,
24
+ createInMemoryProvider: () => createInMemoryProvider,
25
+ runProviderContractSuite: () => runProviderContractSuite
26
+ });
27
+ module.exports = __toCommonJS(index_exports);
28
+
29
+ // src/contract.ts
30
+ var import_core = require("@penvhq/core");
31
+ var import_vitest = require("vitest");
32
+ var ENVIRONMENT = "production";
33
+ var OTHER_ENVIRONMENT = "development";
34
+ var redisPassword = { namespace: ["redis"], name: "password" };
35
+ var databaseUrl = { namespace: [], name: "database-url" };
36
+ function valueFile(ref, scope, encrypted = false) {
37
+ return { namespace: ref.namespace, name: ref.name, scope, encrypted };
38
+ }
39
+ function scopeKey(scope) {
40
+ switch (scope.kind) {
41
+ case "unscoped":
42
+ return "unscoped";
43
+ case "environment":
44
+ return `environment:${scope.environment}`;
45
+ case "local":
46
+ return "local";
47
+ case "environment-local":
48
+ return `environment-local:${scope.environment}`;
49
+ default:
50
+ return (0, import_core.assertNever)(scope, "scope");
51
+ }
52
+ }
53
+ function identity(file) {
54
+ return `${[...file.namespace, file.name].join("/")}|${scopeKey(file.scope)}|${file.encrypted}`;
55
+ }
56
+ function identities(files) {
57
+ return files.map(identity).sort();
58
+ }
59
+ function runProviderContractSuite(name, makeProvider) {
60
+ (0, import_vitest.describe)(`provider contract: ${name}`, () => {
61
+ let provider;
62
+ let cleanup;
63
+ (0, import_vitest.beforeEach)(async () => {
64
+ const made = await makeProvider();
65
+ provider = made.provider;
66
+ cleanup = made.cleanup;
67
+ });
68
+ (0, import_vitest.afterEach)(async () => {
69
+ await cleanup();
70
+ });
71
+ (0, import_vitest.it)("reports its type", () => {
72
+ (0, import_vitest.expect)(typeof provider.type).toBe("string");
73
+ (0, import_vitest.expect)(provider.type.length).toBeGreaterThan(0);
74
+ });
75
+ (0, import_vitest.describe)("read and write", () => {
76
+ (0, import_vitest.it)("round-trips a written value", async () => {
77
+ const file = valueFile(redisPassword, { kind: "environment", environment: ENVIRONMENT });
78
+ await provider.write(file, "hunter2");
79
+ (0, import_vitest.expect)(await provider.read(file)).toBe("hunter2");
80
+ });
81
+ (0, import_vitest.it)("resolves to undefined for a value that was never written", async () => {
82
+ const file = valueFile(redisPassword, { kind: "environment", environment: ENVIRONMENT });
83
+ (0, import_vitest.expect)(await provider.read(file)).toBeUndefined();
84
+ });
85
+ (0, import_vitest.it)("overwrites in place rather than appending", async () => {
86
+ const file = valueFile(databaseUrl, { kind: "unscoped" });
87
+ await provider.write(file, "postgres://localhost/dev");
88
+ await provider.write(file, "postgres://localhost/other");
89
+ (0, import_vitest.expect)(await provider.read(file)).toBe("postgres://localhost/other");
90
+ });
91
+ (0, import_vitest.it)("creates namespaces as needed", async () => {
92
+ const file = valueFile(
93
+ { namespace: ["app", "auth"], name: "jwt-secret" },
94
+ {
95
+ kind: "unscoped"
96
+ }
97
+ );
98
+ await provider.write(file, "s3cret");
99
+ (0, import_vitest.expect)(await provider.read(file)).toBe("s3cret");
100
+ });
101
+ (0, import_vitest.it)("round-trips a value at the environment-local scope", async () => {
102
+ const file = valueFile(redisPassword, {
103
+ kind: "environment-local",
104
+ environment: ENVIRONMENT
105
+ });
106
+ await provider.write(file, "my-machine-only");
107
+ (0, import_vitest.expect)(await provider.read(file)).toBe("my-machine-only");
108
+ });
109
+ (0, import_vitest.it)("keeps each of the four scopes of one parameter at its own address", async () => {
110
+ const unscoped = valueFile(redisPassword, { kind: "unscoped" });
111
+ const scoped = valueFile(redisPassword, { kind: "environment", environment: ENVIRONMENT });
112
+ const local = valueFile(redisPassword, { kind: "local" });
113
+ const environmentLocal = valueFile(redisPassword, {
114
+ kind: "environment-local",
115
+ environment: ENVIRONMENT
116
+ });
117
+ await provider.write(unscoped, "default");
118
+ await provider.write(scoped, "production");
119
+ await provider.write(local, "personal");
120
+ await provider.write(environmentLocal, "personal-production");
121
+ (0, import_vitest.expect)(await provider.read(unscoped)).toBe("default");
122
+ (0, import_vitest.expect)(await provider.read(scoped)).toBe("production");
123
+ (0, import_vitest.expect)(await provider.read(local)).toBe("personal");
124
+ (0, import_vitest.expect)(await provider.read(environmentLocal)).toBe("personal-production");
125
+ });
126
+ (0, import_vitest.it)("keeps environment-local values for different environments apart", async () => {
127
+ const forProduction = valueFile(redisPassword, {
128
+ kind: "environment-local",
129
+ environment: ENVIRONMENT
130
+ });
131
+ const forOther = valueFile(redisPassword, {
132
+ kind: "environment-local",
133
+ environment: OTHER_ENVIRONMENT
134
+ });
135
+ await provider.write(forProduction, "personal-production");
136
+ await provider.write(forOther, "personal-development");
137
+ (0, import_vitest.expect)(await provider.read(forProduction)).toBe("personal-production");
138
+ (0, import_vitest.expect)(await provider.read(forOther)).toBe("personal-development");
139
+ });
140
+ (0, import_vitest.it)("keeps environment-local apart from local and from the environment scope", async () => {
141
+ const local = valueFile(redisPassword, { kind: "local" });
142
+ const scoped = valueFile(redisPassword, { kind: "environment", environment: ENVIRONMENT });
143
+ const environmentLocal = valueFile(redisPassword, {
144
+ kind: "environment-local",
145
+ environment: ENVIRONMENT
146
+ });
147
+ await provider.write(environmentLocal, "personal-production");
148
+ (0, import_vitest.expect)(await provider.read(local)).toBeUndefined();
149
+ (0, import_vitest.expect)(await provider.read(scoped)).toBeUndefined();
150
+ (0, import_vitest.expect)(await provider.read(environmentLocal)).toBe("personal-production");
151
+ });
152
+ (0, import_vitest.it)("keeps an encrypted value at its own address, orthogonal to scope", async () => {
153
+ const plaintext = valueFile(redisPassword, { kind: "unscoped" });
154
+ const encrypted = valueFile(redisPassword, { kind: "unscoped" }, true);
155
+ await provider.write(plaintext, "plain");
156
+ await provider.write(encrypted, "ciphertext");
157
+ (0, import_vitest.expect)(await provider.read(plaintext)).toBe("plain");
158
+ (0, import_vitest.expect)(await provider.read(encrypted)).toBe("ciphertext");
159
+ });
160
+ (0, import_vitest.it)("keeps an encrypted environment-local value at its own address", async () => {
161
+ const scope = { kind: "environment-local", environment: ENVIRONMENT };
162
+ const plaintext = valueFile(redisPassword, scope);
163
+ const encrypted = valueFile(redisPassword, scope, true);
164
+ await provider.write(plaintext, "plain");
165
+ await provider.write(encrypted, "ciphertext");
166
+ (0, import_vitest.expect)(await provider.read(plaintext)).toBe("plain");
167
+ (0, import_vitest.expect)(await provider.read(encrypted)).toBe("ciphertext");
168
+ });
169
+ });
170
+ (0, import_vitest.describe)("values are opaque", () => {
171
+ const cases = [
172
+ ["an empty value", ""],
173
+ ["leading and trailing spaces", " padded "],
174
+ ["a tab", " indented"],
175
+ ["an embedded newline", "line one\nline two"],
176
+ ["a trailing newline", "trailing\n"],
177
+ ["several trailing newlines", "trailing\n\n\n"],
178
+ ["a lone carriage return", "crlf\r\nvalue"],
179
+ ["unicode", "cl\xE9-priv\xE9e-\u{1F510}-\u03A9"],
180
+ ["quotes and escapes", `{"a":"b\\n"} 'single' "double"`],
181
+ ["a base64-shaped value", "aGVsbG8gd29ybGQ=\n"]
182
+ ];
183
+ for (const [label, value] of cases) {
184
+ (0, import_vitest.it)(`preserves ${label} byte-exactly`, async () => {
185
+ const file = valueFile(redisPassword, { kind: "environment", environment: ENVIRONMENT });
186
+ await provider.write(file, value);
187
+ (0, import_vitest.expect)(await provider.read(file)).toBe(value);
188
+ });
189
+ }
190
+ });
191
+ (0, import_vitest.describe)("list", () => {
192
+ (0, import_vitest.it)("returns nothing for a provider holding no values", async () => {
193
+ (0, import_vitest.expect)(await provider.list()).toEqual([]);
194
+ });
195
+ (0, import_vitest.it)("returns every written value file across every scope", async () => {
196
+ const written = [
197
+ valueFile(databaseUrl, { kind: "unscoped" }),
198
+ valueFile(databaseUrl, { kind: "environment", environment: ENVIRONMENT }, true),
199
+ valueFile(databaseUrl, { kind: "environment-local", environment: ENVIRONMENT }),
200
+ valueFile(redisPassword, { kind: "environment", environment: OTHER_ENVIRONMENT }),
201
+ valueFile(redisPassword, { kind: "local" }),
202
+ valueFile(redisPassword, { kind: "environment-local", environment: OTHER_ENVIRONMENT }),
203
+ valueFile(
204
+ { namespace: ["app"], name: "jwt-secret" },
205
+ { kind: "environment-local", environment: ENVIRONMENT },
206
+ true
207
+ ),
208
+ valueFile({ namespace: ["app"], name: "jwt-secret" }, { kind: "local" }, true)
209
+ ];
210
+ for (const file of written) {
211
+ await provider.write(file, "value");
212
+ }
213
+ (0, import_vitest.expect)(identities(await provider.list())).toEqual(identities(written));
214
+ });
215
+ (0, import_vitest.it)("lists all four scopes of one parameter as four distinct records", async () => {
216
+ const written = [
217
+ valueFile(redisPassword, { kind: "unscoped" }),
218
+ valueFile(redisPassword, { kind: "environment", environment: ENVIRONMENT }),
219
+ valueFile(redisPassword, { kind: "local" }),
220
+ valueFile(redisPassword, { kind: "environment-local", environment: ENVIRONMENT })
221
+ ];
222
+ for (const file of written) {
223
+ await provider.write(file, "value");
224
+ }
225
+ const listed = identities(await provider.list());
226
+ (0, import_vitest.expect)(listed).toEqual(identities(written));
227
+ (0, import_vitest.expect)(new Set(listed).size).toBe(written.length);
228
+ });
229
+ (0, import_vitest.it)("lists environment-local values for two environments as two records", async () => {
230
+ const written = [
231
+ valueFile(redisPassword, { kind: "environment-local", environment: ENVIRONMENT }),
232
+ valueFile(redisPassword, { kind: "environment-local", environment: OTHER_ENVIRONMENT })
233
+ ];
234
+ for (const file of written) {
235
+ await provider.write(file, "value");
236
+ }
237
+ const listed = identities(await provider.list());
238
+ (0, import_vitest.expect)(listed).toEqual(identities(written));
239
+ (0, import_vitest.expect)(new Set(listed).size).toBe(2);
240
+ });
241
+ (0, import_vitest.it)("does not list a removed value", async () => {
242
+ const kept = valueFile(databaseUrl, { kind: "unscoped" });
243
+ const removed = valueFile(redisPassword, { kind: "unscoped" });
244
+ await provider.write(kept, "a");
245
+ await provider.write(removed, "b");
246
+ await provider.remove(removed);
247
+ (0, import_vitest.expect)(identities(await provider.list())).toEqual(identities([kept]));
248
+ });
249
+ (0, import_vitest.it)("does not list meta as a value", async () => {
250
+ await provider.writeMeta(redisPassword, { description: "Redis auth" });
251
+ (0, import_vitest.expect)(await provider.list()).toEqual([]);
252
+ });
253
+ });
254
+ (0, import_vitest.describe)("remove", () => {
255
+ (0, import_vitest.it)("deletes the value", async () => {
256
+ const file = valueFile(redisPassword, { kind: "environment", environment: ENVIRONMENT });
257
+ await provider.write(file, "hunter2");
258
+ await provider.remove(file);
259
+ (0, import_vitest.expect)(await provider.read(file)).toBeUndefined();
260
+ });
261
+ (0, import_vitest.it)("is idempotent \u2014 removing an absent value is not an error", async () => {
262
+ const file = valueFile(redisPassword, { kind: "local" });
263
+ await (0, import_vitest.expect)(provider.remove(file)).resolves.toBeUndefined();
264
+ await (0, import_vitest.expect)(provider.remove(file)).resolves.toBeUndefined();
265
+ });
266
+ (0, import_vitest.it)("removes only the scope it was given", async () => {
267
+ const unscoped = valueFile(redisPassword, { kind: "unscoped" });
268
+ const scoped = valueFile(redisPassword, { kind: "environment", environment: ENVIRONMENT });
269
+ const local = valueFile(redisPassword, { kind: "local" });
270
+ const environmentLocal = valueFile(redisPassword, {
271
+ kind: "environment-local",
272
+ environment: ENVIRONMENT
273
+ });
274
+ await provider.write(unscoped, "default");
275
+ await provider.write(scoped, "production");
276
+ await provider.write(local, "personal");
277
+ await provider.write(environmentLocal, "personal-production");
278
+ await provider.remove(environmentLocal);
279
+ (0, import_vitest.expect)(await provider.read(unscoped)).toBe("default");
280
+ (0, import_vitest.expect)(await provider.read(scoped)).toBe("production");
281
+ (0, import_vitest.expect)(await provider.read(local)).toBe("personal");
282
+ (0, import_vitest.expect)(await provider.read(environmentLocal)).toBeUndefined();
283
+ });
284
+ (0, import_vitest.it)("removes an environment-local value for one environment only", async () => {
285
+ const forProduction = valueFile(redisPassword, {
286
+ kind: "environment-local",
287
+ environment: ENVIRONMENT
288
+ });
289
+ const forOther = valueFile(redisPassword, {
290
+ kind: "environment-local",
291
+ environment: OTHER_ENVIRONMENT
292
+ });
293
+ await provider.write(forProduction, "personal-production");
294
+ await provider.write(forOther, "personal-development");
295
+ await provider.remove(forProduction);
296
+ (0, import_vitest.expect)(await provider.read(forProduction)).toBeUndefined();
297
+ (0, import_vitest.expect)(await provider.read(forOther)).toBe("personal-development");
298
+ });
299
+ (0, import_vitest.it)("is idempotent at the environment-local scope too", async () => {
300
+ const file = valueFile(redisPassword, {
301
+ kind: "environment-local",
302
+ environment: ENVIRONMENT
303
+ });
304
+ await (0, import_vitest.expect)(provider.remove(file)).resolves.toBeUndefined();
305
+ });
306
+ (0, import_vitest.it)("leaves meta alone", async () => {
307
+ const file = valueFile(redisPassword, { kind: "unscoped" });
308
+ await provider.write(file, "hunter2");
309
+ await provider.writeMeta(redisPassword, { description: "Redis auth" });
310
+ await provider.remove(file);
311
+ (0, import_vitest.expect)(await provider.readMeta(redisPassword)).toEqual({ description: "Redis auth" });
312
+ });
313
+ });
314
+ (0, import_vitest.describe)("meta", () => {
315
+ (0, import_vitest.it)("resolves to undefined for a parameter with no meta", async () => {
316
+ (0, import_vitest.expect)(await provider.readMeta(redisPassword)).toBeUndefined();
317
+ });
318
+ (0, import_vitest.it)("round-trips a meta file", async () => {
319
+ const meta = {
320
+ description: "Signs and verifies user session JWTs",
321
+ owner: "auth-team",
322
+ secret: true,
323
+ environments: {
324
+ production: { required: true, owner: "infra-team" },
325
+ development: { required: false }
326
+ }
327
+ };
328
+ await provider.writeMeta(redisPassword, meta);
329
+ (0, import_vitest.expect)(await provider.readMeta(redisPassword)).toEqual(meta);
330
+ });
331
+ (0, import_vitest.it)("preserves unknown keys, so an older penv does not destroy newer fields", async () => {
332
+ const meta = {
333
+ description: "Redis auth",
334
+ rotationPolicy: "90d",
335
+ environments: { production: { rotationState: "active", rotatingSince: null } }
336
+ };
337
+ await provider.writeMeta(redisPassword, meta);
338
+ (0, import_vitest.expect)(await provider.readMeta(redisPassword)).toEqual(meta);
339
+ });
340
+ (0, import_vitest.it)("overwrites meta in place", async () => {
341
+ await provider.writeMeta(redisPassword, { description: "first", owner: "a-team" });
342
+ await provider.writeMeta(redisPassword, { description: "second" });
343
+ (0, import_vitest.expect)(await provider.readMeta(redisPassword)).toEqual({ description: "second" });
344
+ });
345
+ (0, import_vitest.it)("keeps meta per parameter", async () => {
346
+ await provider.writeMeta(redisPassword, { description: "Redis auth" });
347
+ await provider.writeMeta(databaseUrl, { description: "Primary database" });
348
+ (0, import_vitest.expect)(await provider.readMeta(redisPassword)).toEqual({ description: "Redis auth" });
349
+ (0, import_vitest.expect)(await provider.readMeta(databaseUrl)).toEqual({ description: "Primary database" });
350
+ });
351
+ (0, import_vitest.it)("holds meta independently of any value", async () => {
352
+ await provider.writeMeta(redisPassword, { description: "Redis auth", required: true });
353
+ (0, import_vitest.expect)(await provider.read(valueFile(redisPassword, { kind: "unscoped" }))).toBeUndefined();
354
+ });
355
+ });
356
+ (0, import_vitest.describe)("removeMeta", () => {
357
+ (0, import_vitest.it)("deletes the meta", async () => {
358
+ await provider.writeMeta(redisPassword, { description: "Redis auth" });
359
+ await provider.removeMeta(redisPassword);
360
+ (0, import_vitest.expect)(await provider.readMeta(redisPassword)).toBeUndefined();
361
+ });
362
+ (0, import_vitest.it)("is idempotent \u2014 removing absent meta is not an error", async () => {
363
+ await (0, import_vitest.expect)(provider.removeMeta(redisPassword)).resolves.toBeUndefined();
364
+ await (0, import_vitest.expect)(provider.removeMeta(redisPassword)).resolves.toBeUndefined();
365
+ });
366
+ (0, import_vitest.it)("leaves every value alone", async () => {
367
+ const unscoped = valueFile(redisPassword, { kind: "unscoped" });
368
+ const scoped = valueFile(redisPassword, { kind: "environment", environment: ENVIRONMENT });
369
+ await provider.write(unscoped, "default");
370
+ await provider.write(scoped, "production");
371
+ await provider.writeMeta(redisPassword, { description: "Redis auth" });
372
+ await provider.removeMeta(redisPassword);
373
+ (0, import_vitest.expect)(await provider.read(unscoped)).toBe("default");
374
+ (0, import_vitest.expect)(await provider.read(scoped)).toBe("production");
375
+ });
376
+ (0, import_vitest.it)("removes only the parameter it was given", async () => {
377
+ await provider.writeMeta(redisPassword, { description: "Redis auth" });
378
+ await provider.writeMeta(databaseUrl, { description: "Primary database" });
379
+ await provider.removeMeta(redisPassword);
380
+ (0, import_vitest.expect)(await provider.readMeta(redisPassword)).toBeUndefined();
381
+ (0, import_vitest.expect)(await provider.readMeta(databaseUrl)).toEqual({ description: "Primary database" });
382
+ });
383
+ });
384
+ });
385
+ }
386
+
387
+ // src/memory.ts
388
+ var import_core2 = require("@penvhq/core");
389
+ function scopeKey2(scope) {
390
+ switch (scope.kind) {
391
+ case "unscoped":
392
+ return "unscoped";
393
+ case "environment":
394
+ return `environment:${scope.environment}`;
395
+ case "local":
396
+ return "local";
397
+ case "environment-local":
398
+ return `environment-local:${scope.environment}`;
399
+ default:
400
+ return (0, import_core2.assertNever)(scope, "scope");
401
+ }
402
+ }
403
+ function valueKey(file) {
404
+ return [file.namespace.join("/"), file.name, scopeKey2(file.scope), String(file.encrypted)].join(
405
+ "\0"
406
+ );
407
+ }
408
+ function metaKey(ref) {
409
+ return [ref.namespace.join("/"), ref.name].join("\0");
410
+ }
411
+ function cloneValueFile(file) {
412
+ return {
413
+ namespace: [...file.namespace],
414
+ name: file.name,
415
+ scope: file.scope,
416
+ encrypted: file.encrypted
417
+ };
418
+ }
419
+ var InMemoryProvider = class {
420
+ type = "memory";
421
+ #values = /* @__PURE__ */ new Map();
422
+ #meta = /* @__PURE__ */ new Map();
423
+ async read(file) {
424
+ return this.#values.get(valueKey(file))?.value;
425
+ }
426
+ async write(file, value) {
427
+ this.#values.set(valueKey(file), { file: cloneValueFile(file), value });
428
+ }
429
+ async list() {
430
+ return [...this.#values.values()].map((stored) => cloneValueFile(stored.file));
431
+ }
432
+ async remove(file) {
433
+ this.#values.delete(valueKey(file));
434
+ }
435
+ async readMeta(ref) {
436
+ const stored = this.#meta.get(metaKey(ref));
437
+ return stored === void 0 ? void 0 : JSON.parse(stored);
438
+ }
439
+ async writeMeta(ref, meta) {
440
+ this.#meta.set(metaKey(ref), JSON.stringify(meta));
441
+ }
442
+ async removeMeta(ref) {
443
+ this.#meta.delete(metaKey(ref));
444
+ }
445
+ };
446
+ function createInMemoryProvider() {
447
+ return new InMemoryProvider();
448
+ }
449
+ // Annotate the CommonJS export names for ESM import in node:
450
+ 0 && (module.exports = {
451
+ InMemoryProvider,
452
+ createInMemoryProvider,
453
+ runProviderContractSuite
454
+ });
455
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/contract.ts","../src/memory.ts"],"sourcesContent":["/**\n * The shared, provider-agnostic contract suite and its in-memory fixture.\n *\n * This package is test-only: `contract.ts` imports vitest at module scope, so no\n * runtime bundle may depend on it. A provider imports {@link runProviderContractSuite}\n * from here in its `*.test.ts` files — never from its own package entry — and the\n * v0.6 SSM and Kubernetes adapters will reuse it unchanged, which is why it lives\n * here rather than beside any one provider.\n */\n\nexport { runProviderContractSuite } from \"./contract.js\";\nexport { createInMemoryProvider, InMemoryProvider } from \"./memory.js\";\n","/**\n * The provider contract suite: the behaviour every provider must satisfy.\n *\n * This is the suite the Vault adapter must pass unchanged. Nothing here may\n * assume a filesystem — no paths, no `node:fs`, no on-disk layout. It speaks\n * only the vocabulary of `@penvhq/core`: a provider is addressed by\n * `(namespace, name, scope, encrypted)` and nothing else.\n *\n * A provider handed to this suite must accept the environments `development`\n * and `production`, and must start empty.\n */\n\nimport type { Meta, ParameterRef, Provider, Scope, ValueFile } from \"@penvhq/core\";\nimport { assertNever } from \"@penvhq/core\";\nimport { afterEach, beforeEach, describe, expect, it } from \"vitest\";\n\nconst ENVIRONMENT = \"production\";\nconst OTHER_ENVIRONMENT = \"development\";\n\nconst redisPassword: ParameterRef = { namespace: [\"redis\"], name: \"password\" };\nconst databaseUrl: ParameterRef = { namespace: [], name: \"database-url\" };\n\nfunction valueFile(ref: ParameterRef, scope: Scope, encrypted = false): ValueFile {\n return { namespace: ref.namespace, name: ref.name, scope, encrypted };\n}\n\n/**\n * Every scope that carries an environment must key on it. Two scopes that differ\n * only by environment are two records, and a key that dropped the environment\n * would make this suite pass while a provider overwrote one with the other.\n */\nfunction scopeKey(scope: Scope): string {\n switch (scope.kind) {\n case \"unscoped\":\n return \"unscoped\";\n case \"environment\":\n return `environment:${scope.environment}`;\n case \"local\":\n return \"local\";\n case \"environment-local\":\n return `environment-local:${scope.environment}`;\n default:\n return assertNever(scope, \"scope\");\n }\n}\n\n/** A stable identity built only from contract fields, so `list` order is not asserted. */\nfunction identity(file: ValueFile): string {\n return `${[...file.namespace, file.name].join(\"/\")}|${scopeKey(file.scope)}|${file.encrypted}`;\n}\n\nfunction identities(files: readonly ValueFile[]): string[] {\n return files.map(identity).sort();\n}\n\nexport function runProviderContractSuite(\n name: string,\n makeProvider: () => Promise<{ provider: Provider; cleanup: () => Promise<void> }>,\n): void {\n describe(`provider contract: ${name}`, () => {\n let provider: Provider;\n let cleanup: () => Promise<void>;\n\n beforeEach(async () => {\n const made = await makeProvider();\n provider = made.provider;\n cleanup = made.cleanup;\n });\n\n afterEach(async () => {\n await cleanup();\n });\n\n it(\"reports its type\", () => {\n expect(typeof provider.type).toBe(\"string\");\n expect(provider.type.length).toBeGreaterThan(0);\n });\n\n describe(\"read and write\", () => {\n it(\"round-trips a written value\", async () => {\n const file = valueFile(redisPassword, { kind: \"environment\", environment: ENVIRONMENT });\n\n await provider.write(file, \"hunter2\");\n\n expect(await provider.read(file)).toBe(\"hunter2\");\n });\n\n it(\"resolves to undefined for a value that was never written\", async () => {\n const file = valueFile(redisPassword, { kind: \"environment\", environment: ENVIRONMENT });\n\n expect(await provider.read(file)).toBeUndefined();\n });\n\n it(\"overwrites in place rather than appending\", async () => {\n const file = valueFile(databaseUrl, { kind: \"unscoped\" });\n\n await provider.write(file, \"postgres://localhost/dev\");\n await provider.write(file, \"postgres://localhost/other\");\n\n expect(await provider.read(file)).toBe(\"postgres://localhost/other\");\n });\n\n it(\"creates namespaces as needed\", async () => {\n const file = valueFile(\n { namespace: [\"app\", \"auth\"], name: \"jwt-secret\" },\n {\n kind: \"unscoped\",\n },\n );\n\n await provider.write(file, \"s3cret\");\n\n expect(await provider.read(file)).toBe(\"s3cret\");\n });\n\n it(\"round-trips a value at the environment-local scope\", async () => {\n const file = valueFile(redisPassword, {\n kind: \"environment-local\",\n environment: ENVIRONMENT,\n });\n\n await provider.write(file, \"my-machine-only\");\n\n expect(await provider.read(file)).toBe(\"my-machine-only\");\n });\n\n it(\"keeps each of the four scopes of one parameter at its own address\", async () => {\n const unscoped = valueFile(redisPassword, { kind: \"unscoped\" });\n const scoped = valueFile(redisPassword, { kind: \"environment\", environment: ENVIRONMENT });\n const local = valueFile(redisPassword, { kind: \"local\" });\n const environmentLocal = valueFile(redisPassword, {\n kind: \"environment-local\",\n environment: ENVIRONMENT,\n });\n\n await provider.write(unscoped, \"default\");\n await provider.write(scoped, \"production\");\n await provider.write(local, \"personal\");\n await provider.write(environmentLocal, \"personal-production\");\n\n expect(await provider.read(unscoped)).toBe(\"default\");\n expect(await provider.read(scoped)).toBe(\"production\");\n expect(await provider.read(local)).toBe(\"personal\");\n expect(await provider.read(environmentLocal)).toBe(\"personal-production\");\n });\n\n /*\n * The failure this guards: an environment-local value stored without its\n * environment. One developer's override for one environment would become\n * the override for every environment — the scope-widening leak penv exists\n * to delete.\n */\n it(\"keeps environment-local values for different environments apart\", async () => {\n const forProduction = valueFile(redisPassword, {\n kind: \"environment-local\",\n environment: ENVIRONMENT,\n });\n const forOther = valueFile(redisPassword, {\n kind: \"environment-local\",\n environment: OTHER_ENVIRONMENT,\n });\n\n await provider.write(forProduction, \"personal-production\");\n await provider.write(forOther, \"personal-development\");\n\n expect(await provider.read(forProduction)).toBe(\"personal-production\");\n expect(await provider.read(forOther)).toBe(\"personal-development\");\n });\n\n it(\"keeps environment-local apart from local and from the environment scope\", async () => {\n const local = valueFile(redisPassword, { kind: \"local\" });\n const scoped = valueFile(redisPassword, { kind: \"environment\", environment: ENVIRONMENT });\n const environmentLocal = valueFile(redisPassword, {\n kind: \"environment-local\",\n environment: ENVIRONMENT,\n });\n\n await provider.write(environmentLocal, \"personal-production\");\n\n expect(await provider.read(local)).toBeUndefined();\n expect(await provider.read(scoped)).toBeUndefined();\n expect(await provider.read(environmentLocal)).toBe(\"personal-production\");\n });\n\n it(\"keeps an encrypted value at its own address, orthogonal to scope\", async () => {\n const plaintext = valueFile(redisPassword, { kind: \"unscoped\" });\n const encrypted = valueFile(redisPassword, { kind: \"unscoped\" }, true);\n\n await provider.write(plaintext, \"plain\");\n await provider.write(encrypted, \"ciphertext\");\n\n expect(await provider.read(plaintext)).toBe(\"plain\");\n expect(await provider.read(encrypted)).toBe(\"ciphertext\");\n });\n\n it(\"keeps an encrypted environment-local value at its own address\", async () => {\n const scope: Scope = { kind: \"environment-local\", environment: ENVIRONMENT };\n const plaintext = valueFile(redisPassword, scope);\n const encrypted = valueFile(redisPassword, scope, true);\n\n await provider.write(plaintext, \"plain\");\n await provider.write(encrypted, \"ciphertext\");\n\n expect(await provider.read(plaintext)).toBe(\"plain\");\n expect(await provider.read(encrypted)).toBe(\"ciphertext\");\n });\n });\n\n describe(\"values are opaque\", () => {\n const cases: readonly (readonly [string, string])[] = [\n [\"an empty value\", \"\"],\n [\"leading and trailing spaces\", \" padded \"],\n [\"a tab\", \"\\tindented\"],\n [\"an embedded newline\", \"line one\\nline two\"],\n [\"a trailing newline\", \"trailing\\n\"],\n [\"several trailing newlines\", \"trailing\\n\\n\\n\"],\n [\"a lone carriage return\", \"crlf\\r\\nvalue\"],\n [\"unicode\", \"clé-privée-🔐-Ω\"],\n [\"quotes and escapes\", `{\"a\":\"b\\\\n\"} 'single' \"double\"`],\n [\"a base64-shaped value\", \"aGVsbG8gd29ybGQ=\\n\"],\n ];\n\n for (const [label, value] of cases) {\n it(`preserves ${label} byte-exactly`, async () => {\n const file = valueFile(redisPassword, { kind: \"environment\", environment: ENVIRONMENT });\n\n await provider.write(file, value);\n\n expect(await provider.read(file)).toBe(value);\n });\n }\n });\n\n describe(\"list\", () => {\n it(\"returns nothing for a provider holding no values\", async () => {\n expect(await provider.list()).toEqual([]);\n });\n\n it(\"returns every written value file across every scope\", async () => {\n const written: ValueFile[] = [\n valueFile(databaseUrl, { kind: \"unscoped\" }),\n valueFile(databaseUrl, { kind: \"environment\", environment: ENVIRONMENT }, true),\n valueFile(databaseUrl, { kind: \"environment-local\", environment: ENVIRONMENT }),\n valueFile(redisPassword, { kind: \"environment\", environment: OTHER_ENVIRONMENT }),\n valueFile(redisPassword, { kind: \"local\" }),\n valueFile(redisPassword, { kind: \"environment-local\", environment: OTHER_ENVIRONMENT }),\n valueFile(\n { namespace: [\"app\"], name: \"jwt-secret\" },\n { kind: \"environment-local\", environment: ENVIRONMENT },\n true,\n ),\n valueFile({ namespace: [\"app\"], name: \"jwt-secret\" }, { kind: \"local\" }, true),\n ];\n\n for (const file of written) {\n await provider.write(file, \"value\");\n }\n\n expect(identities(await provider.list())).toEqual(identities(written));\n });\n\n it(\"lists all four scopes of one parameter as four distinct records\", async () => {\n const written: ValueFile[] = [\n valueFile(redisPassword, { kind: \"unscoped\" }),\n valueFile(redisPassword, { kind: \"environment\", environment: ENVIRONMENT }),\n valueFile(redisPassword, { kind: \"local\" }),\n valueFile(redisPassword, { kind: \"environment-local\", environment: ENVIRONMENT }),\n ];\n\n for (const file of written) {\n await provider.write(file, \"value\");\n }\n\n const listed = identities(await provider.list());\n expect(listed).toEqual(identities(written));\n expect(new Set(listed).size).toBe(written.length);\n });\n\n /*\n * The distinctness assertion is load-bearing, not decoration. Both sides of\n * the `toEqual` run through `identity`, so an identity that dropped the\n * environment would collapse each side equally and the comparison would\n * still hold — the suite would pass while treating two records as one.\n */\n it(\"lists environment-local values for two environments as two records\", async () => {\n const written: ValueFile[] = [\n valueFile(redisPassword, { kind: \"environment-local\", environment: ENVIRONMENT }),\n valueFile(redisPassword, { kind: \"environment-local\", environment: OTHER_ENVIRONMENT }),\n ];\n\n for (const file of written) {\n await provider.write(file, \"value\");\n }\n\n const listed = identities(await provider.list());\n expect(listed).toEqual(identities(written));\n expect(new Set(listed).size).toBe(2);\n });\n\n it(\"does not list a removed value\", async () => {\n const kept = valueFile(databaseUrl, { kind: \"unscoped\" });\n const removed = valueFile(redisPassword, { kind: \"unscoped\" });\n\n await provider.write(kept, \"a\");\n await provider.write(removed, \"b\");\n await provider.remove(removed);\n\n expect(identities(await provider.list())).toEqual(identities([kept]));\n });\n\n it(\"does not list meta as a value\", async () => {\n await provider.writeMeta(redisPassword, { description: \"Redis auth\" });\n\n expect(await provider.list()).toEqual([]);\n });\n });\n\n describe(\"remove\", () => {\n it(\"deletes the value\", async () => {\n const file = valueFile(redisPassword, { kind: \"environment\", environment: ENVIRONMENT });\n await provider.write(file, \"hunter2\");\n\n await provider.remove(file);\n\n expect(await provider.read(file)).toBeUndefined();\n });\n\n it(\"is idempotent — removing an absent value is not an error\", async () => {\n const file = valueFile(redisPassword, { kind: \"local\" });\n\n await expect(provider.remove(file)).resolves.toBeUndefined();\n await expect(provider.remove(file)).resolves.toBeUndefined();\n });\n\n it(\"removes only the scope it was given\", async () => {\n const unscoped = valueFile(redisPassword, { kind: \"unscoped\" });\n const scoped = valueFile(redisPassword, { kind: \"environment\", environment: ENVIRONMENT });\n const local = valueFile(redisPassword, { kind: \"local\" });\n const environmentLocal = valueFile(redisPassword, {\n kind: \"environment-local\",\n environment: ENVIRONMENT,\n });\n await provider.write(unscoped, \"default\");\n await provider.write(scoped, \"production\");\n await provider.write(local, \"personal\");\n await provider.write(environmentLocal, \"personal-production\");\n\n await provider.remove(environmentLocal);\n\n expect(await provider.read(unscoped)).toBe(\"default\");\n expect(await provider.read(scoped)).toBe(\"production\");\n expect(await provider.read(local)).toBe(\"personal\");\n expect(await provider.read(environmentLocal)).toBeUndefined();\n });\n\n it(\"removes an environment-local value for one environment only\", async () => {\n const forProduction = valueFile(redisPassword, {\n kind: \"environment-local\",\n environment: ENVIRONMENT,\n });\n const forOther = valueFile(redisPassword, {\n kind: \"environment-local\",\n environment: OTHER_ENVIRONMENT,\n });\n await provider.write(forProduction, \"personal-production\");\n await provider.write(forOther, \"personal-development\");\n\n await provider.remove(forProduction);\n\n expect(await provider.read(forProduction)).toBeUndefined();\n expect(await provider.read(forOther)).toBe(\"personal-development\");\n });\n\n it(\"is idempotent at the environment-local scope too\", async () => {\n const file = valueFile(redisPassword, {\n kind: \"environment-local\",\n environment: ENVIRONMENT,\n });\n\n await expect(provider.remove(file)).resolves.toBeUndefined();\n });\n\n it(\"leaves meta alone\", async () => {\n const file = valueFile(redisPassword, { kind: \"unscoped\" });\n await provider.write(file, \"hunter2\");\n await provider.writeMeta(redisPassword, { description: \"Redis auth\" });\n\n await provider.remove(file);\n\n expect(await provider.readMeta(redisPassword)).toEqual({ description: \"Redis auth\" });\n });\n });\n\n describe(\"meta\", () => {\n it(\"resolves to undefined for a parameter with no meta\", async () => {\n expect(await provider.readMeta(redisPassword)).toBeUndefined();\n });\n\n it(\"round-trips a meta file\", async () => {\n const meta: Meta = {\n description: \"Signs and verifies user session JWTs\",\n owner: \"auth-team\",\n secret: true,\n environments: {\n production: { required: true, owner: \"infra-team\" },\n development: { required: false },\n },\n };\n\n await provider.writeMeta(redisPassword, meta);\n\n expect(await provider.readMeta(redisPassword)).toEqual(meta);\n });\n\n it(\"preserves unknown keys, so an older penv does not destroy newer fields\", async () => {\n const meta: Meta = {\n description: \"Redis auth\",\n rotationPolicy: \"90d\",\n environments: { production: { rotationState: \"active\", rotatingSince: null } },\n };\n\n await provider.writeMeta(redisPassword, meta);\n\n expect(await provider.readMeta(redisPassword)).toEqual(meta);\n });\n\n it(\"overwrites meta in place\", async () => {\n await provider.writeMeta(redisPassword, { description: \"first\", owner: \"a-team\" });\n await provider.writeMeta(redisPassword, { description: \"second\" });\n\n expect(await provider.readMeta(redisPassword)).toEqual({ description: \"second\" });\n });\n\n it(\"keeps meta per parameter\", async () => {\n await provider.writeMeta(redisPassword, { description: \"Redis auth\" });\n await provider.writeMeta(databaseUrl, { description: \"Primary database\" });\n\n expect(await provider.readMeta(redisPassword)).toEqual({ description: \"Redis auth\" });\n expect(await provider.readMeta(databaseUrl)).toEqual({ description: \"Primary database\" });\n });\n\n it(\"holds meta independently of any value\", async () => {\n await provider.writeMeta(redisPassword, { description: \"Redis auth\", required: true });\n\n expect(await provider.read(valueFile(redisPassword, { kind: \"unscoped\" }))).toBeUndefined();\n });\n });\n\n describe(\"removeMeta\", () => {\n it(\"deletes the meta\", async () => {\n await provider.writeMeta(redisPassword, { description: \"Redis auth\" });\n\n await provider.removeMeta(redisPassword);\n\n expect(await provider.readMeta(redisPassword)).toBeUndefined();\n });\n\n it(\"is idempotent — removing absent meta is not an error\", async () => {\n await expect(provider.removeMeta(redisPassword)).resolves.toBeUndefined();\n await expect(provider.removeMeta(redisPassword)).resolves.toBeUndefined();\n });\n\n /*\n * The mirror of `remove`'s \"leaves meta alone\". Policy and value are two\n * faces of one record and are removed independently, so a provider that\n * dropped the values with the policy would delete a secret on a `penv mv`\n * that was only meant to move its description.\n */\n it(\"leaves every value alone\", async () => {\n const unscoped = valueFile(redisPassword, { kind: \"unscoped\" });\n const scoped = valueFile(redisPassword, { kind: \"environment\", environment: ENVIRONMENT });\n await provider.write(unscoped, \"default\");\n await provider.write(scoped, \"production\");\n await provider.writeMeta(redisPassword, { description: \"Redis auth\" });\n\n await provider.removeMeta(redisPassword);\n\n expect(await provider.read(unscoped)).toBe(\"default\");\n expect(await provider.read(scoped)).toBe(\"production\");\n });\n\n it(\"removes only the parameter it was given\", async () => {\n await provider.writeMeta(redisPassword, { description: \"Redis auth\" });\n await provider.writeMeta(databaseUrl, { description: \"Primary database\" });\n\n await provider.removeMeta(redisPassword);\n\n expect(await provider.readMeta(redisPassword)).toBeUndefined();\n expect(await provider.readMeta(databaseUrl)).toEqual({ description: \"Primary database\" });\n });\n });\n });\n}\n","/**\n * An in-memory provider: a Map-backed store that satisfies the same `@penvhq/core`\n * provider contract the filesystem does, with no disk, no paths, and no code\n * shared with the reference provider.\n *\n * It is a test fixture, not a registered provider. Its whole reason to exist is to\n * prove {@link runProviderContractSuite} is genuinely provider-agnostic: a second,\n * structurally unrelated provider type passes the same behavioural suite the\n * filesystem passes and the Vault adapter must. That demonstrates the portability\n * seam without standing up a live backend.\n *\n * Two choices keep it honest rather than a suite grading its own reflection:\n * - Values and meta live in two maps under disjoint keyspaces, so a written meta\n * can never be returned by `list()` as though it were a value.\n * - Meta is round-tripped through JSON on write and read, the way any real store\n * holding a string does, so nothing here hands back a live object reference a\n * caller could mutate after the fact.\n *\n * It declares no retention: `readPrevious` is omitted, the general case the\n * contract permits (see `retainsPrevious` in `@penvhq/core`). The filesystem and\n * Kubernetes sit on the same side of that line.\n */\n\nimport type { Meta, ParameterRef, Provider, Scope, ValueFile } from \"@penvhq/core\";\nimport { assertNever } from \"@penvhq/core\";\n\n/** Every scope that carries an environment keys on it, so two environments never collide. */\nfunction scopeKey(scope: Scope): string {\n switch (scope.kind) {\n case \"unscoped\":\n return \"unscoped\";\n case \"environment\":\n return `environment:${scope.environment}`;\n case \"local\":\n return \"local\";\n case \"environment-local\":\n return `environment-local:${scope.environment}`;\n default:\n return assertNever(scope, \"scope\");\n }\n}\n\n/**\n * Every contract field of a value file joined into one key. `\u0000` never appears\n * in a namespace segment, name, or scope, so it separates them unambiguously.\n */\nfunction valueKey(file: ValueFile): string {\n return [file.namespace.join(\"/\"), file.name, scopeKey(file.scope), String(file.encrypted)].join(\n \"\u0000\",\n );\n}\n\nfunction metaKey(ref: ParameterRef): string {\n return [ref.namespace.join(\"/\"), ref.name].join(\"\u0000\");\n}\n\n/** A defensive copy, so a caller mutating the file it handed us cannot reach the store. */\nfunction cloneValueFile(file: ValueFile): ValueFile {\n return {\n namespace: [...file.namespace],\n name: file.name,\n scope: file.scope,\n encrypted: file.encrypted,\n };\n}\n\ninterface StoredValue {\n readonly file: ValueFile;\n readonly value: string;\n}\n\nexport class InMemoryProvider implements Provider {\n readonly type = \"memory\";\n\n readonly #values = new Map<string, StoredValue>();\n readonly #meta = new Map<string, string>();\n\n async read(file: ValueFile): Promise<string | undefined> {\n return this.#values.get(valueKey(file))?.value;\n }\n\n async write(file: ValueFile, value: string): Promise<void> {\n this.#values.set(valueKey(file), { file: cloneValueFile(file), value });\n }\n\n async list(): Promise<ValueFile[]> {\n return [...this.#values.values()].map((stored) => cloneValueFile(stored.file));\n }\n\n async remove(file: ValueFile): Promise<void> {\n this.#values.delete(valueKey(file));\n }\n\n async readMeta(ref: ParameterRef): Promise<Meta | undefined> {\n const stored = this.#meta.get(metaKey(ref));\n return stored === undefined ? undefined : (JSON.parse(stored) as Meta);\n }\n\n async writeMeta(ref: ParameterRef, meta: Meta): Promise<void> {\n this.#meta.set(metaKey(ref), JSON.stringify(meta));\n }\n\n async removeMeta(ref: ParameterRef): Promise<void> {\n this.#meta.delete(metaKey(ref));\n }\n}\n\nexport function createInMemoryProvider(): InMemoryProvider {\n return new InMemoryProvider();\n}\n"],"mappings":"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","names":["import_core","scopeKey"]}
@@ -0,0 +1,56 @@
1
+ import { Provider, ValueFile, ParameterRef, Meta } from '@penvhq/core';
2
+
3
+ /**
4
+ * The provider contract suite: the behaviour every provider must satisfy.
5
+ *
6
+ * This is the suite the Vault adapter must pass unchanged. Nothing here may
7
+ * assume a filesystem — no paths, no `node:fs`, no on-disk layout. It speaks
8
+ * only the vocabulary of `@penvhq/core`: a provider is addressed by
9
+ * `(namespace, name, scope, encrypted)` and nothing else.
10
+ *
11
+ * A provider handed to this suite must accept the environments `development`
12
+ * and `production`, and must start empty.
13
+ */
14
+
15
+ declare function runProviderContractSuite(name: string, makeProvider: () => Promise<{
16
+ provider: Provider;
17
+ cleanup: () => Promise<void>;
18
+ }>): void;
19
+
20
+ /**
21
+ * An in-memory provider: a Map-backed store that satisfies the same `@penvhq/core`
22
+ * provider contract the filesystem does, with no disk, no paths, and no code
23
+ * shared with the reference provider.
24
+ *
25
+ * It is a test fixture, not a registered provider. Its whole reason to exist is to
26
+ * prove {@link runProviderContractSuite} is genuinely provider-agnostic: a second,
27
+ * structurally unrelated provider type passes the same behavioural suite the
28
+ * filesystem passes and the Vault adapter must. That demonstrates the portability
29
+ * seam without standing up a live backend.
30
+ *
31
+ * Two choices keep it honest rather than a suite grading its own reflection:
32
+ * - Values and meta live in two maps under disjoint keyspaces, so a written meta
33
+ * can never be returned by `list()` as though it were a value.
34
+ * - Meta is round-tripped through JSON on write and read, the way any real store
35
+ * holding a string does, so nothing here hands back a live object reference a
36
+ * caller could mutate after the fact.
37
+ *
38
+ * It declares no retention: `readPrevious` is omitted, the general case the
39
+ * contract permits (see `retainsPrevious` in `@penvhq/core`). The filesystem and
40
+ * Kubernetes sit on the same side of that line.
41
+ */
42
+
43
+ declare class InMemoryProvider implements Provider {
44
+ #private;
45
+ readonly type = "memory";
46
+ read(file: ValueFile): Promise<string | undefined>;
47
+ write(file: ValueFile, value: string): Promise<void>;
48
+ list(): Promise<ValueFile[]>;
49
+ remove(file: ValueFile): Promise<void>;
50
+ readMeta(ref: ParameterRef): Promise<Meta | undefined>;
51
+ writeMeta(ref: ParameterRef, meta: Meta): Promise<void>;
52
+ removeMeta(ref: ParameterRef): Promise<void>;
53
+ }
54
+ declare function createInMemoryProvider(): InMemoryProvider;
55
+
56
+ export { InMemoryProvider, createInMemoryProvider, runProviderContractSuite };
@@ -0,0 +1,56 @@
1
+ import { Provider, ValueFile, ParameterRef, Meta } from '@penvhq/core';
2
+
3
+ /**
4
+ * The provider contract suite: the behaviour every provider must satisfy.
5
+ *
6
+ * This is the suite the Vault adapter must pass unchanged. Nothing here may
7
+ * assume a filesystem — no paths, no `node:fs`, no on-disk layout. It speaks
8
+ * only the vocabulary of `@penvhq/core`: a provider is addressed by
9
+ * `(namespace, name, scope, encrypted)` and nothing else.
10
+ *
11
+ * A provider handed to this suite must accept the environments `development`
12
+ * and `production`, and must start empty.
13
+ */
14
+
15
+ declare function runProviderContractSuite(name: string, makeProvider: () => Promise<{
16
+ provider: Provider;
17
+ cleanup: () => Promise<void>;
18
+ }>): void;
19
+
20
+ /**
21
+ * An in-memory provider: a Map-backed store that satisfies the same `@penvhq/core`
22
+ * provider contract the filesystem does, with no disk, no paths, and no code
23
+ * shared with the reference provider.
24
+ *
25
+ * It is a test fixture, not a registered provider. Its whole reason to exist is to
26
+ * prove {@link runProviderContractSuite} is genuinely provider-agnostic: a second,
27
+ * structurally unrelated provider type passes the same behavioural suite the
28
+ * filesystem passes and the Vault adapter must. That demonstrates the portability
29
+ * seam without standing up a live backend.
30
+ *
31
+ * Two choices keep it honest rather than a suite grading its own reflection:
32
+ * - Values and meta live in two maps under disjoint keyspaces, so a written meta
33
+ * can never be returned by `list()` as though it were a value.
34
+ * - Meta is round-tripped through JSON on write and read, the way any real store
35
+ * holding a string does, so nothing here hands back a live object reference a
36
+ * caller could mutate after the fact.
37
+ *
38
+ * It declares no retention: `readPrevious` is omitted, the general case the
39
+ * contract permits (see `retainsPrevious` in `@penvhq/core`). The filesystem and
40
+ * Kubernetes sit on the same side of that line.
41
+ */
42
+
43
+ declare class InMemoryProvider implements Provider {
44
+ #private;
45
+ readonly type = "memory";
46
+ read(file: ValueFile): Promise<string | undefined>;
47
+ write(file: ValueFile, value: string): Promise<void>;
48
+ list(): Promise<ValueFile[]>;
49
+ remove(file: ValueFile): Promise<void>;
50
+ readMeta(ref: ParameterRef): Promise<Meta | undefined>;
51
+ writeMeta(ref: ParameterRef, meta: Meta): Promise<void>;
52
+ removeMeta(ref: ParameterRef): Promise<void>;
53
+ }
54
+ declare function createInMemoryProvider(): InMemoryProvider;
55
+
56
+ export { InMemoryProvider, createInMemoryProvider, runProviderContractSuite };
package/dist/index.js ADDED
@@ -0,0 +1,426 @@
1
+ // src/contract.ts
2
+ import { assertNever } from "@penvhq/core";
3
+ import { afterEach, beforeEach, describe, expect, it } from "vitest";
4
+ var ENVIRONMENT = "production";
5
+ var OTHER_ENVIRONMENT = "development";
6
+ var redisPassword = { namespace: ["redis"], name: "password" };
7
+ var databaseUrl = { namespace: [], name: "database-url" };
8
+ function valueFile(ref, scope, encrypted = false) {
9
+ return { namespace: ref.namespace, name: ref.name, scope, encrypted };
10
+ }
11
+ function scopeKey(scope) {
12
+ switch (scope.kind) {
13
+ case "unscoped":
14
+ return "unscoped";
15
+ case "environment":
16
+ return `environment:${scope.environment}`;
17
+ case "local":
18
+ return "local";
19
+ case "environment-local":
20
+ return `environment-local:${scope.environment}`;
21
+ default:
22
+ return assertNever(scope, "scope");
23
+ }
24
+ }
25
+ function identity(file) {
26
+ return `${[...file.namespace, file.name].join("/")}|${scopeKey(file.scope)}|${file.encrypted}`;
27
+ }
28
+ function identities(files) {
29
+ return files.map(identity).sort();
30
+ }
31
+ function runProviderContractSuite(name, makeProvider) {
32
+ describe(`provider contract: ${name}`, () => {
33
+ let provider;
34
+ let cleanup;
35
+ beforeEach(async () => {
36
+ const made = await makeProvider();
37
+ provider = made.provider;
38
+ cleanup = made.cleanup;
39
+ });
40
+ afterEach(async () => {
41
+ await cleanup();
42
+ });
43
+ it("reports its type", () => {
44
+ expect(typeof provider.type).toBe("string");
45
+ expect(provider.type.length).toBeGreaterThan(0);
46
+ });
47
+ describe("read and write", () => {
48
+ it("round-trips a written value", async () => {
49
+ const file = valueFile(redisPassword, { kind: "environment", environment: ENVIRONMENT });
50
+ await provider.write(file, "hunter2");
51
+ expect(await provider.read(file)).toBe("hunter2");
52
+ });
53
+ it("resolves to undefined for a value that was never written", async () => {
54
+ const file = valueFile(redisPassword, { kind: "environment", environment: ENVIRONMENT });
55
+ expect(await provider.read(file)).toBeUndefined();
56
+ });
57
+ it("overwrites in place rather than appending", async () => {
58
+ const file = valueFile(databaseUrl, { kind: "unscoped" });
59
+ await provider.write(file, "postgres://localhost/dev");
60
+ await provider.write(file, "postgres://localhost/other");
61
+ expect(await provider.read(file)).toBe("postgres://localhost/other");
62
+ });
63
+ it("creates namespaces as needed", async () => {
64
+ const file = valueFile(
65
+ { namespace: ["app", "auth"], name: "jwt-secret" },
66
+ {
67
+ kind: "unscoped"
68
+ }
69
+ );
70
+ await provider.write(file, "s3cret");
71
+ expect(await provider.read(file)).toBe("s3cret");
72
+ });
73
+ it("round-trips a value at the environment-local scope", async () => {
74
+ const file = valueFile(redisPassword, {
75
+ kind: "environment-local",
76
+ environment: ENVIRONMENT
77
+ });
78
+ await provider.write(file, "my-machine-only");
79
+ expect(await provider.read(file)).toBe("my-machine-only");
80
+ });
81
+ it("keeps each of the four scopes of one parameter at its own address", async () => {
82
+ const unscoped = valueFile(redisPassword, { kind: "unscoped" });
83
+ const scoped = valueFile(redisPassword, { kind: "environment", environment: ENVIRONMENT });
84
+ const local = valueFile(redisPassword, { kind: "local" });
85
+ const environmentLocal = valueFile(redisPassword, {
86
+ kind: "environment-local",
87
+ environment: ENVIRONMENT
88
+ });
89
+ await provider.write(unscoped, "default");
90
+ await provider.write(scoped, "production");
91
+ await provider.write(local, "personal");
92
+ await provider.write(environmentLocal, "personal-production");
93
+ expect(await provider.read(unscoped)).toBe("default");
94
+ expect(await provider.read(scoped)).toBe("production");
95
+ expect(await provider.read(local)).toBe("personal");
96
+ expect(await provider.read(environmentLocal)).toBe("personal-production");
97
+ });
98
+ it("keeps environment-local values for different environments apart", async () => {
99
+ const forProduction = valueFile(redisPassword, {
100
+ kind: "environment-local",
101
+ environment: ENVIRONMENT
102
+ });
103
+ const forOther = valueFile(redisPassword, {
104
+ kind: "environment-local",
105
+ environment: OTHER_ENVIRONMENT
106
+ });
107
+ await provider.write(forProduction, "personal-production");
108
+ await provider.write(forOther, "personal-development");
109
+ expect(await provider.read(forProduction)).toBe("personal-production");
110
+ expect(await provider.read(forOther)).toBe("personal-development");
111
+ });
112
+ it("keeps environment-local apart from local and from the environment scope", async () => {
113
+ const local = valueFile(redisPassword, { kind: "local" });
114
+ const scoped = valueFile(redisPassword, { kind: "environment", environment: ENVIRONMENT });
115
+ const environmentLocal = valueFile(redisPassword, {
116
+ kind: "environment-local",
117
+ environment: ENVIRONMENT
118
+ });
119
+ await provider.write(environmentLocal, "personal-production");
120
+ expect(await provider.read(local)).toBeUndefined();
121
+ expect(await provider.read(scoped)).toBeUndefined();
122
+ expect(await provider.read(environmentLocal)).toBe("personal-production");
123
+ });
124
+ it("keeps an encrypted value at its own address, orthogonal to scope", async () => {
125
+ const plaintext = valueFile(redisPassword, { kind: "unscoped" });
126
+ const encrypted = valueFile(redisPassword, { kind: "unscoped" }, true);
127
+ await provider.write(plaintext, "plain");
128
+ await provider.write(encrypted, "ciphertext");
129
+ expect(await provider.read(plaintext)).toBe("plain");
130
+ expect(await provider.read(encrypted)).toBe("ciphertext");
131
+ });
132
+ it("keeps an encrypted environment-local value at its own address", async () => {
133
+ const scope = { kind: "environment-local", environment: ENVIRONMENT };
134
+ const plaintext = valueFile(redisPassword, scope);
135
+ const encrypted = valueFile(redisPassword, scope, true);
136
+ await provider.write(plaintext, "plain");
137
+ await provider.write(encrypted, "ciphertext");
138
+ expect(await provider.read(plaintext)).toBe("plain");
139
+ expect(await provider.read(encrypted)).toBe("ciphertext");
140
+ });
141
+ });
142
+ describe("values are opaque", () => {
143
+ const cases = [
144
+ ["an empty value", ""],
145
+ ["leading and trailing spaces", " padded "],
146
+ ["a tab", " indented"],
147
+ ["an embedded newline", "line one\nline two"],
148
+ ["a trailing newline", "trailing\n"],
149
+ ["several trailing newlines", "trailing\n\n\n"],
150
+ ["a lone carriage return", "crlf\r\nvalue"],
151
+ ["unicode", "cl\xE9-priv\xE9e-\u{1F510}-\u03A9"],
152
+ ["quotes and escapes", `{"a":"b\\n"} 'single' "double"`],
153
+ ["a base64-shaped value", "aGVsbG8gd29ybGQ=\n"]
154
+ ];
155
+ for (const [label, value] of cases) {
156
+ it(`preserves ${label} byte-exactly`, async () => {
157
+ const file = valueFile(redisPassword, { kind: "environment", environment: ENVIRONMENT });
158
+ await provider.write(file, value);
159
+ expect(await provider.read(file)).toBe(value);
160
+ });
161
+ }
162
+ });
163
+ describe("list", () => {
164
+ it("returns nothing for a provider holding no values", async () => {
165
+ expect(await provider.list()).toEqual([]);
166
+ });
167
+ it("returns every written value file across every scope", async () => {
168
+ const written = [
169
+ valueFile(databaseUrl, { kind: "unscoped" }),
170
+ valueFile(databaseUrl, { kind: "environment", environment: ENVIRONMENT }, true),
171
+ valueFile(databaseUrl, { kind: "environment-local", environment: ENVIRONMENT }),
172
+ valueFile(redisPassword, { kind: "environment", environment: OTHER_ENVIRONMENT }),
173
+ valueFile(redisPassword, { kind: "local" }),
174
+ valueFile(redisPassword, { kind: "environment-local", environment: OTHER_ENVIRONMENT }),
175
+ valueFile(
176
+ { namespace: ["app"], name: "jwt-secret" },
177
+ { kind: "environment-local", environment: ENVIRONMENT },
178
+ true
179
+ ),
180
+ valueFile({ namespace: ["app"], name: "jwt-secret" }, { kind: "local" }, true)
181
+ ];
182
+ for (const file of written) {
183
+ await provider.write(file, "value");
184
+ }
185
+ expect(identities(await provider.list())).toEqual(identities(written));
186
+ });
187
+ it("lists all four scopes of one parameter as four distinct records", async () => {
188
+ const written = [
189
+ valueFile(redisPassword, { kind: "unscoped" }),
190
+ valueFile(redisPassword, { kind: "environment", environment: ENVIRONMENT }),
191
+ valueFile(redisPassword, { kind: "local" }),
192
+ valueFile(redisPassword, { kind: "environment-local", environment: ENVIRONMENT })
193
+ ];
194
+ for (const file of written) {
195
+ await provider.write(file, "value");
196
+ }
197
+ const listed = identities(await provider.list());
198
+ expect(listed).toEqual(identities(written));
199
+ expect(new Set(listed).size).toBe(written.length);
200
+ });
201
+ it("lists environment-local values for two environments as two records", async () => {
202
+ const written = [
203
+ valueFile(redisPassword, { kind: "environment-local", environment: ENVIRONMENT }),
204
+ valueFile(redisPassword, { kind: "environment-local", environment: OTHER_ENVIRONMENT })
205
+ ];
206
+ for (const file of written) {
207
+ await provider.write(file, "value");
208
+ }
209
+ const listed = identities(await provider.list());
210
+ expect(listed).toEqual(identities(written));
211
+ expect(new Set(listed).size).toBe(2);
212
+ });
213
+ it("does not list a removed value", async () => {
214
+ const kept = valueFile(databaseUrl, { kind: "unscoped" });
215
+ const removed = valueFile(redisPassword, { kind: "unscoped" });
216
+ await provider.write(kept, "a");
217
+ await provider.write(removed, "b");
218
+ await provider.remove(removed);
219
+ expect(identities(await provider.list())).toEqual(identities([kept]));
220
+ });
221
+ it("does not list meta as a value", async () => {
222
+ await provider.writeMeta(redisPassword, { description: "Redis auth" });
223
+ expect(await provider.list()).toEqual([]);
224
+ });
225
+ });
226
+ describe("remove", () => {
227
+ it("deletes the value", async () => {
228
+ const file = valueFile(redisPassword, { kind: "environment", environment: ENVIRONMENT });
229
+ await provider.write(file, "hunter2");
230
+ await provider.remove(file);
231
+ expect(await provider.read(file)).toBeUndefined();
232
+ });
233
+ it("is idempotent \u2014 removing an absent value is not an error", async () => {
234
+ const file = valueFile(redisPassword, { kind: "local" });
235
+ await expect(provider.remove(file)).resolves.toBeUndefined();
236
+ await expect(provider.remove(file)).resolves.toBeUndefined();
237
+ });
238
+ it("removes only the scope it was given", async () => {
239
+ const unscoped = valueFile(redisPassword, { kind: "unscoped" });
240
+ const scoped = valueFile(redisPassword, { kind: "environment", environment: ENVIRONMENT });
241
+ const local = valueFile(redisPassword, { kind: "local" });
242
+ const environmentLocal = valueFile(redisPassword, {
243
+ kind: "environment-local",
244
+ environment: ENVIRONMENT
245
+ });
246
+ await provider.write(unscoped, "default");
247
+ await provider.write(scoped, "production");
248
+ await provider.write(local, "personal");
249
+ await provider.write(environmentLocal, "personal-production");
250
+ await provider.remove(environmentLocal);
251
+ expect(await provider.read(unscoped)).toBe("default");
252
+ expect(await provider.read(scoped)).toBe("production");
253
+ expect(await provider.read(local)).toBe("personal");
254
+ expect(await provider.read(environmentLocal)).toBeUndefined();
255
+ });
256
+ it("removes an environment-local value for one environment only", async () => {
257
+ const forProduction = valueFile(redisPassword, {
258
+ kind: "environment-local",
259
+ environment: ENVIRONMENT
260
+ });
261
+ const forOther = valueFile(redisPassword, {
262
+ kind: "environment-local",
263
+ environment: OTHER_ENVIRONMENT
264
+ });
265
+ await provider.write(forProduction, "personal-production");
266
+ await provider.write(forOther, "personal-development");
267
+ await provider.remove(forProduction);
268
+ expect(await provider.read(forProduction)).toBeUndefined();
269
+ expect(await provider.read(forOther)).toBe("personal-development");
270
+ });
271
+ it("is idempotent at the environment-local scope too", async () => {
272
+ const file = valueFile(redisPassword, {
273
+ kind: "environment-local",
274
+ environment: ENVIRONMENT
275
+ });
276
+ await expect(provider.remove(file)).resolves.toBeUndefined();
277
+ });
278
+ it("leaves meta alone", async () => {
279
+ const file = valueFile(redisPassword, { kind: "unscoped" });
280
+ await provider.write(file, "hunter2");
281
+ await provider.writeMeta(redisPassword, { description: "Redis auth" });
282
+ await provider.remove(file);
283
+ expect(await provider.readMeta(redisPassword)).toEqual({ description: "Redis auth" });
284
+ });
285
+ });
286
+ describe("meta", () => {
287
+ it("resolves to undefined for a parameter with no meta", async () => {
288
+ expect(await provider.readMeta(redisPassword)).toBeUndefined();
289
+ });
290
+ it("round-trips a meta file", async () => {
291
+ const meta = {
292
+ description: "Signs and verifies user session JWTs",
293
+ owner: "auth-team",
294
+ secret: true,
295
+ environments: {
296
+ production: { required: true, owner: "infra-team" },
297
+ development: { required: false }
298
+ }
299
+ };
300
+ await provider.writeMeta(redisPassword, meta);
301
+ expect(await provider.readMeta(redisPassword)).toEqual(meta);
302
+ });
303
+ it("preserves unknown keys, so an older penv does not destroy newer fields", async () => {
304
+ const meta = {
305
+ description: "Redis auth",
306
+ rotationPolicy: "90d",
307
+ environments: { production: { rotationState: "active", rotatingSince: null } }
308
+ };
309
+ await provider.writeMeta(redisPassword, meta);
310
+ expect(await provider.readMeta(redisPassword)).toEqual(meta);
311
+ });
312
+ it("overwrites meta in place", async () => {
313
+ await provider.writeMeta(redisPassword, { description: "first", owner: "a-team" });
314
+ await provider.writeMeta(redisPassword, { description: "second" });
315
+ expect(await provider.readMeta(redisPassword)).toEqual({ description: "second" });
316
+ });
317
+ it("keeps meta per parameter", async () => {
318
+ await provider.writeMeta(redisPassword, { description: "Redis auth" });
319
+ await provider.writeMeta(databaseUrl, { description: "Primary database" });
320
+ expect(await provider.readMeta(redisPassword)).toEqual({ description: "Redis auth" });
321
+ expect(await provider.readMeta(databaseUrl)).toEqual({ description: "Primary database" });
322
+ });
323
+ it("holds meta independently of any value", async () => {
324
+ await provider.writeMeta(redisPassword, { description: "Redis auth", required: true });
325
+ expect(await provider.read(valueFile(redisPassword, { kind: "unscoped" }))).toBeUndefined();
326
+ });
327
+ });
328
+ describe("removeMeta", () => {
329
+ it("deletes the meta", async () => {
330
+ await provider.writeMeta(redisPassword, { description: "Redis auth" });
331
+ await provider.removeMeta(redisPassword);
332
+ expect(await provider.readMeta(redisPassword)).toBeUndefined();
333
+ });
334
+ it("is idempotent \u2014 removing absent meta is not an error", async () => {
335
+ await expect(provider.removeMeta(redisPassword)).resolves.toBeUndefined();
336
+ await expect(provider.removeMeta(redisPassword)).resolves.toBeUndefined();
337
+ });
338
+ it("leaves every value alone", async () => {
339
+ const unscoped = valueFile(redisPassword, { kind: "unscoped" });
340
+ const scoped = valueFile(redisPassword, { kind: "environment", environment: ENVIRONMENT });
341
+ await provider.write(unscoped, "default");
342
+ await provider.write(scoped, "production");
343
+ await provider.writeMeta(redisPassword, { description: "Redis auth" });
344
+ await provider.removeMeta(redisPassword);
345
+ expect(await provider.read(unscoped)).toBe("default");
346
+ expect(await provider.read(scoped)).toBe("production");
347
+ });
348
+ it("removes only the parameter it was given", async () => {
349
+ await provider.writeMeta(redisPassword, { description: "Redis auth" });
350
+ await provider.writeMeta(databaseUrl, { description: "Primary database" });
351
+ await provider.removeMeta(redisPassword);
352
+ expect(await provider.readMeta(redisPassword)).toBeUndefined();
353
+ expect(await provider.readMeta(databaseUrl)).toEqual({ description: "Primary database" });
354
+ });
355
+ });
356
+ });
357
+ }
358
+
359
+ // src/memory.ts
360
+ import { assertNever as assertNever2 } from "@penvhq/core";
361
+ function scopeKey2(scope) {
362
+ switch (scope.kind) {
363
+ case "unscoped":
364
+ return "unscoped";
365
+ case "environment":
366
+ return `environment:${scope.environment}`;
367
+ case "local":
368
+ return "local";
369
+ case "environment-local":
370
+ return `environment-local:${scope.environment}`;
371
+ default:
372
+ return assertNever2(scope, "scope");
373
+ }
374
+ }
375
+ function valueKey(file) {
376
+ return [file.namespace.join("/"), file.name, scopeKey2(file.scope), String(file.encrypted)].join(
377
+ "\0"
378
+ );
379
+ }
380
+ function metaKey(ref) {
381
+ return [ref.namespace.join("/"), ref.name].join("\0");
382
+ }
383
+ function cloneValueFile(file) {
384
+ return {
385
+ namespace: [...file.namespace],
386
+ name: file.name,
387
+ scope: file.scope,
388
+ encrypted: file.encrypted
389
+ };
390
+ }
391
+ var InMemoryProvider = class {
392
+ type = "memory";
393
+ #values = /* @__PURE__ */ new Map();
394
+ #meta = /* @__PURE__ */ new Map();
395
+ async read(file) {
396
+ return this.#values.get(valueKey(file))?.value;
397
+ }
398
+ async write(file, value) {
399
+ this.#values.set(valueKey(file), { file: cloneValueFile(file), value });
400
+ }
401
+ async list() {
402
+ return [...this.#values.values()].map((stored) => cloneValueFile(stored.file));
403
+ }
404
+ async remove(file) {
405
+ this.#values.delete(valueKey(file));
406
+ }
407
+ async readMeta(ref) {
408
+ const stored = this.#meta.get(metaKey(ref));
409
+ return stored === void 0 ? void 0 : JSON.parse(stored);
410
+ }
411
+ async writeMeta(ref, meta) {
412
+ this.#meta.set(metaKey(ref), JSON.stringify(meta));
413
+ }
414
+ async removeMeta(ref) {
415
+ this.#meta.delete(metaKey(ref));
416
+ }
417
+ };
418
+ function createInMemoryProvider() {
419
+ return new InMemoryProvider();
420
+ }
421
+ export {
422
+ InMemoryProvider,
423
+ createInMemoryProvider,
424
+ runProviderContractSuite
425
+ };
426
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/contract.ts","../src/memory.ts"],"sourcesContent":["/**\n * The provider contract suite: the behaviour every provider must satisfy.\n *\n * This is the suite the Vault adapter must pass unchanged. Nothing here may\n * assume a filesystem — no paths, no `node:fs`, no on-disk layout. It speaks\n * only the vocabulary of `@penvhq/core`: a provider is addressed by\n * `(namespace, name, scope, encrypted)` and nothing else.\n *\n * A provider handed to this suite must accept the environments `development`\n * and `production`, and must start empty.\n */\n\nimport type { Meta, ParameterRef, Provider, Scope, ValueFile } from \"@penvhq/core\";\nimport { assertNever } from \"@penvhq/core\";\nimport { afterEach, beforeEach, describe, expect, it } from \"vitest\";\n\nconst ENVIRONMENT = \"production\";\nconst OTHER_ENVIRONMENT = \"development\";\n\nconst redisPassword: ParameterRef = { namespace: [\"redis\"], name: \"password\" };\nconst databaseUrl: ParameterRef = { namespace: [], name: \"database-url\" };\n\nfunction valueFile(ref: ParameterRef, scope: Scope, encrypted = false): ValueFile {\n return { namespace: ref.namespace, name: ref.name, scope, encrypted };\n}\n\n/**\n * Every scope that carries an environment must key on it. Two scopes that differ\n * only by environment are two records, and a key that dropped the environment\n * would make this suite pass while a provider overwrote one with the other.\n */\nfunction scopeKey(scope: Scope): string {\n switch (scope.kind) {\n case \"unscoped\":\n return \"unscoped\";\n case \"environment\":\n return `environment:${scope.environment}`;\n case \"local\":\n return \"local\";\n case \"environment-local\":\n return `environment-local:${scope.environment}`;\n default:\n return assertNever(scope, \"scope\");\n }\n}\n\n/** A stable identity built only from contract fields, so `list` order is not asserted. */\nfunction identity(file: ValueFile): string {\n return `${[...file.namespace, file.name].join(\"/\")}|${scopeKey(file.scope)}|${file.encrypted}`;\n}\n\nfunction identities(files: readonly ValueFile[]): string[] {\n return files.map(identity).sort();\n}\n\nexport function runProviderContractSuite(\n name: string,\n makeProvider: () => Promise<{ provider: Provider; cleanup: () => Promise<void> }>,\n): void {\n describe(`provider contract: ${name}`, () => {\n let provider: Provider;\n let cleanup: () => Promise<void>;\n\n beforeEach(async () => {\n const made = await makeProvider();\n provider = made.provider;\n cleanup = made.cleanup;\n });\n\n afterEach(async () => {\n await cleanup();\n });\n\n it(\"reports its type\", () => {\n expect(typeof provider.type).toBe(\"string\");\n expect(provider.type.length).toBeGreaterThan(0);\n });\n\n describe(\"read and write\", () => {\n it(\"round-trips a written value\", async () => {\n const file = valueFile(redisPassword, { kind: \"environment\", environment: ENVIRONMENT });\n\n await provider.write(file, \"hunter2\");\n\n expect(await provider.read(file)).toBe(\"hunter2\");\n });\n\n it(\"resolves to undefined for a value that was never written\", async () => {\n const file = valueFile(redisPassword, { kind: \"environment\", environment: ENVIRONMENT });\n\n expect(await provider.read(file)).toBeUndefined();\n });\n\n it(\"overwrites in place rather than appending\", async () => {\n const file = valueFile(databaseUrl, { kind: \"unscoped\" });\n\n await provider.write(file, \"postgres://localhost/dev\");\n await provider.write(file, \"postgres://localhost/other\");\n\n expect(await provider.read(file)).toBe(\"postgres://localhost/other\");\n });\n\n it(\"creates namespaces as needed\", async () => {\n const file = valueFile(\n { namespace: [\"app\", \"auth\"], name: \"jwt-secret\" },\n {\n kind: \"unscoped\",\n },\n );\n\n await provider.write(file, \"s3cret\");\n\n expect(await provider.read(file)).toBe(\"s3cret\");\n });\n\n it(\"round-trips a value at the environment-local scope\", async () => {\n const file = valueFile(redisPassword, {\n kind: \"environment-local\",\n environment: ENVIRONMENT,\n });\n\n await provider.write(file, \"my-machine-only\");\n\n expect(await provider.read(file)).toBe(\"my-machine-only\");\n });\n\n it(\"keeps each of the four scopes of one parameter at its own address\", async () => {\n const unscoped = valueFile(redisPassword, { kind: \"unscoped\" });\n const scoped = valueFile(redisPassword, { kind: \"environment\", environment: ENVIRONMENT });\n const local = valueFile(redisPassword, { kind: \"local\" });\n const environmentLocal = valueFile(redisPassword, {\n kind: \"environment-local\",\n environment: ENVIRONMENT,\n });\n\n await provider.write(unscoped, \"default\");\n await provider.write(scoped, \"production\");\n await provider.write(local, \"personal\");\n await provider.write(environmentLocal, \"personal-production\");\n\n expect(await provider.read(unscoped)).toBe(\"default\");\n expect(await provider.read(scoped)).toBe(\"production\");\n expect(await provider.read(local)).toBe(\"personal\");\n expect(await provider.read(environmentLocal)).toBe(\"personal-production\");\n });\n\n /*\n * The failure this guards: an environment-local value stored without its\n * environment. One developer's override for one environment would become\n * the override for every environment — the scope-widening leak penv exists\n * to delete.\n */\n it(\"keeps environment-local values for different environments apart\", async () => {\n const forProduction = valueFile(redisPassword, {\n kind: \"environment-local\",\n environment: ENVIRONMENT,\n });\n const forOther = valueFile(redisPassword, {\n kind: \"environment-local\",\n environment: OTHER_ENVIRONMENT,\n });\n\n await provider.write(forProduction, \"personal-production\");\n await provider.write(forOther, \"personal-development\");\n\n expect(await provider.read(forProduction)).toBe(\"personal-production\");\n expect(await provider.read(forOther)).toBe(\"personal-development\");\n });\n\n it(\"keeps environment-local apart from local and from the environment scope\", async () => {\n const local = valueFile(redisPassword, { kind: \"local\" });\n const scoped = valueFile(redisPassword, { kind: \"environment\", environment: ENVIRONMENT });\n const environmentLocal = valueFile(redisPassword, {\n kind: \"environment-local\",\n environment: ENVIRONMENT,\n });\n\n await provider.write(environmentLocal, \"personal-production\");\n\n expect(await provider.read(local)).toBeUndefined();\n expect(await provider.read(scoped)).toBeUndefined();\n expect(await provider.read(environmentLocal)).toBe(\"personal-production\");\n });\n\n it(\"keeps an encrypted value at its own address, orthogonal to scope\", async () => {\n const plaintext = valueFile(redisPassword, { kind: \"unscoped\" });\n const encrypted = valueFile(redisPassword, { kind: \"unscoped\" }, true);\n\n await provider.write(plaintext, \"plain\");\n await provider.write(encrypted, \"ciphertext\");\n\n expect(await provider.read(plaintext)).toBe(\"plain\");\n expect(await provider.read(encrypted)).toBe(\"ciphertext\");\n });\n\n it(\"keeps an encrypted environment-local value at its own address\", async () => {\n const scope: Scope = { kind: \"environment-local\", environment: ENVIRONMENT };\n const plaintext = valueFile(redisPassword, scope);\n const encrypted = valueFile(redisPassword, scope, true);\n\n await provider.write(plaintext, \"plain\");\n await provider.write(encrypted, \"ciphertext\");\n\n expect(await provider.read(plaintext)).toBe(\"plain\");\n expect(await provider.read(encrypted)).toBe(\"ciphertext\");\n });\n });\n\n describe(\"values are opaque\", () => {\n const cases: readonly (readonly [string, string])[] = [\n [\"an empty value\", \"\"],\n [\"leading and trailing spaces\", \" padded \"],\n [\"a tab\", \"\\tindented\"],\n [\"an embedded newline\", \"line one\\nline two\"],\n [\"a trailing newline\", \"trailing\\n\"],\n [\"several trailing newlines\", \"trailing\\n\\n\\n\"],\n [\"a lone carriage return\", \"crlf\\r\\nvalue\"],\n [\"unicode\", \"clé-privée-🔐-Ω\"],\n [\"quotes and escapes\", `{\"a\":\"b\\\\n\"} 'single' \"double\"`],\n [\"a base64-shaped value\", \"aGVsbG8gd29ybGQ=\\n\"],\n ];\n\n for (const [label, value] of cases) {\n it(`preserves ${label} byte-exactly`, async () => {\n const file = valueFile(redisPassword, { kind: \"environment\", environment: ENVIRONMENT });\n\n await provider.write(file, value);\n\n expect(await provider.read(file)).toBe(value);\n });\n }\n });\n\n describe(\"list\", () => {\n it(\"returns nothing for a provider holding no values\", async () => {\n expect(await provider.list()).toEqual([]);\n });\n\n it(\"returns every written value file across every scope\", async () => {\n const written: ValueFile[] = [\n valueFile(databaseUrl, { kind: \"unscoped\" }),\n valueFile(databaseUrl, { kind: \"environment\", environment: ENVIRONMENT }, true),\n valueFile(databaseUrl, { kind: \"environment-local\", environment: ENVIRONMENT }),\n valueFile(redisPassword, { kind: \"environment\", environment: OTHER_ENVIRONMENT }),\n valueFile(redisPassword, { kind: \"local\" }),\n valueFile(redisPassword, { kind: \"environment-local\", environment: OTHER_ENVIRONMENT }),\n valueFile(\n { namespace: [\"app\"], name: \"jwt-secret\" },\n { kind: \"environment-local\", environment: ENVIRONMENT },\n true,\n ),\n valueFile({ namespace: [\"app\"], name: \"jwt-secret\" }, { kind: \"local\" }, true),\n ];\n\n for (const file of written) {\n await provider.write(file, \"value\");\n }\n\n expect(identities(await provider.list())).toEqual(identities(written));\n });\n\n it(\"lists all four scopes of one parameter as four distinct records\", async () => {\n const written: ValueFile[] = [\n valueFile(redisPassword, { kind: \"unscoped\" }),\n valueFile(redisPassword, { kind: \"environment\", environment: ENVIRONMENT }),\n valueFile(redisPassword, { kind: \"local\" }),\n valueFile(redisPassword, { kind: \"environment-local\", environment: ENVIRONMENT }),\n ];\n\n for (const file of written) {\n await provider.write(file, \"value\");\n }\n\n const listed = identities(await provider.list());\n expect(listed).toEqual(identities(written));\n expect(new Set(listed).size).toBe(written.length);\n });\n\n /*\n * The distinctness assertion is load-bearing, not decoration. Both sides of\n * the `toEqual` run through `identity`, so an identity that dropped the\n * environment would collapse each side equally and the comparison would\n * still hold — the suite would pass while treating two records as one.\n */\n it(\"lists environment-local values for two environments as two records\", async () => {\n const written: ValueFile[] = [\n valueFile(redisPassword, { kind: \"environment-local\", environment: ENVIRONMENT }),\n valueFile(redisPassword, { kind: \"environment-local\", environment: OTHER_ENVIRONMENT }),\n ];\n\n for (const file of written) {\n await provider.write(file, \"value\");\n }\n\n const listed = identities(await provider.list());\n expect(listed).toEqual(identities(written));\n expect(new Set(listed).size).toBe(2);\n });\n\n it(\"does not list a removed value\", async () => {\n const kept = valueFile(databaseUrl, { kind: \"unscoped\" });\n const removed = valueFile(redisPassword, { kind: \"unscoped\" });\n\n await provider.write(kept, \"a\");\n await provider.write(removed, \"b\");\n await provider.remove(removed);\n\n expect(identities(await provider.list())).toEqual(identities([kept]));\n });\n\n it(\"does not list meta as a value\", async () => {\n await provider.writeMeta(redisPassword, { description: \"Redis auth\" });\n\n expect(await provider.list()).toEqual([]);\n });\n });\n\n describe(\"remove\", () => {\n it(\"deletes the value\", async () => {\n const file = valueFile(redisPassword, { kind: \"environment\", environment: ENVIRONMENT });\n await provider.write(file, \"hunter2\");\n\n await provider.remove(file);\n\n expect(await provider.read(file)).toBeUndefined();\n });\n\n it(\"is idempotent — removing an absent value is not an error\", async () => {\n const file = valueFile(redisPassword, { kind: \"local\" });\n\n await expect(provider.remove(file)).resolves.toBeUndefined();\n await expect(provider.remove(file)).resolves.toBeUndefined();\n });\n\n it(\"removes only the scope it was given\", async () => {\n const unscoped = valueFile(redisPassword, { kind: \"unscoped\" });\n const scoped = valueFile(redisPassword, { kind: \"environment\", environment: ENVIRONMENT });\n const local = valueFile(redisPassword, { kind: \"local\" });\n const environmentLocal = valueFile(redisPassword, {\n kind: \"environment-local\",\n environment: ENVIRONMENT,\n });\n await provider.write(unscoped, \"default\");\n await provider.write(scoped, \"production\");\n await provider.write(local, \"personal\");\n await provider.write(environmentLocal, \"personal-production\");\n\n await provider.remove(environmentLocal);\n\n expect(await provider.read(unscoped)).toBe(\"default\");\n expect(await provider.read(scoped)).toBe(\"production\");\n expect(await provider.read(local)).toBe(\"personal\");\n expect(await provider.read(environmentLocal)).toBeUndefined();\n });\n\n it(\"removes an environment-local value for one environment only\", async () => {\n const forProduction = valueFile(redisPassword, {\n kind: \"environment-local\",\n environment: ENVIRONMENT,\n });\n const forOther = valueFile(redisPassword, {\n kind: \"environment-local\",\n environment: OTHER_ENVIRONMENT,\n });\n await provider.write(forProduction, \"personal-production\");\n await provider.write(forOther, \"personal-development\");\n\n await provider.remove(forProduction);\n\n expect(await provider.read(forProduction)).toBeUndefined();\n expect(await provider.read(forOther)).toBe(\"personal-development\");\n });\n\n it(\"is idempotent at the environment-local scope too\", async () => {\n const file = valueFile(redisPassword, {\n kind: \"environment-local\",\n environment: ENVIRONMENT,\n });\n\n await expect(provider.remove(file)).resolves.toBeUndefined();\n });\n\n it(\"leaves meta alone\", async () => {\n const file = valueFile(redisPassword, { kind: \"unscoped\" });\n await provider.write(file, \"hunter2\");\n await provider.writeMeta(redisPassword, { description: \"Redis auth\" });\n\n await provider.remove(file);\n\n expect(await provider.readMeta(redisPassword)).toEqual({ description: \"Redis auth\" });\n });\n });\n\n describe(\"meta\", () => {\n it(\"resolves to undefined for a parameter with no meta\", async () => {\n expect(await provider.readMeta(redisPassword)).toBeUndefined();\n });\n\n it(\"round-trips a meta file\", async () => {\n const meta: Meta = {\n description: \"Signs and verifies user session JWTs\",\n owner: \"auth-team\",\n secret: true,\n environments: {\n production: { required: true, owner: \"infra-team\" },\n development: { required: false },\n },\n };\n\n await provider.writeMeta(redisPassword, meta);\n\n expect(await provider.readMeta(redisPassword)).toEqual(meta);\n });\n\n it(\"preserves unknown keys, so an older penv does not destroy newer fields\", async () => {\n const meta: Meta = {\n description: \"Redis auth\",\n rotationPolicy: \"90d\",\n environments: { production: { rotationState: \"active\", rotatingSince: null } },\n };\n\n await provider.writeMeta(redisPassword, meta);\n\n expect(await provider.readMeta(redisPassword)).toEqual(meta);\n });\n\n it(\"overwrites meta in place\", async () => {\n await provider.writeMeta(redisPassword, { description: \"first\", owner: \"a-team\" });\n await provider.writeMeta(redisPassword, { description: \"second\" });\n\n expect(await provider.readMeta(redisPassword)).toEqual({ description: \"second\" });\n });\n\n it(\"keeps meta per parameter\", async () => {\n await provider.writeMeta(redisPassword, { description: \"Redis auth\" });\n await provider.writeMeta(databaseUrl, { description: \"Primary database\" });\n\n expect(await provider.readMeta(redisPassword)).toEqual({ description: \"Redis auth\" });\n expect(await provider.readMeta(databaseUrl)).toEqual({ description: \"Primary database\" });\n });\n\n it(\"holds meta independently of any value\", async () => {\n await provider.writeMeta(redisPassword, { description: \"Redis auth\", required: true });\n\n expect(await provider.read(valueFile(redisPassword, { kind: \"unscoped\" }))).toBeUndefined();\n });\n });\n\n describe(\"removeMeta\", () => {\n it(\"deletes the meta\", async () => {\n await provider.writeMeta(redisPassword, { description: \"Redis auth\" });\n\n await provider.removeMeta(redisPassword);\n\n expect(await provider.readMeta(redisPassword)).toBeUndefined();\n });\n\n it(\"is idempotent — removing absent meta is not an error\", async () => {\n await expect(provider.removeMeta(redisPassword)).resolves.toBeUndefined();\n await expect(provider.removeMeta(redisPassword)).resolves.toBeUndefined();\n });\n\n /*\n * The mirror of `remove`'s \"leaves meta alone\". Policy and value are two\n * faces of one record and are removed independently, so a provider that\n * dropped the values with the policy would delete a secret on a `penv mv`\n * that was only meant to move its description.\n */\n it(\"leaves every value alone\", async () => {\n const unscoped = valueFile(redisPassword, { kind: \"unscoped\" });\n const scoped = valueFile(redisPassword, { kind: \"environment\", environment: ENVIRONMENT });\n await provider.write(unscoped, \"default\");\n await provider.write(scoped, \"production\");\n await provider.writeMeta(redisPassword, { description: \"Redis auth\" });\n\n await provider.removeMeta(redisPassword);\n\n expect(await provider.read(unscoped)).toBe(\"default\");\n expect(await provider.read(scoped)).toBe(\"production\");\n });\n\n it(\"removes only the parameter it was given\", async () => {\n await provider.writeMeta(redisPassword, { description: \"Redis auth\" });\n await provider.writeMeta(databaseUrl, { description: \"Primary database\" });\n\n await provider.removeMeta(redisPassword);\n\n expect(await provider.readMeta(redisPassword)).toBeUndefined();\n expect(await provider.readMeta(databaseUrl)).toEqual({ description: \"Primary database\" });\n });\n });\n });\n}\n","/**\n * An in-memory provider: a Map-backed store that satisfies the same `@penvhq/core`\n * provider contract the filesystem does, with no disk, no paths, and no code\n * shared with the reference provider.\n *\n * It is a test fixture, not a registered provider. Its whole reason to exist is to\n * prove {@link runProviderContractSuite} is genuinely provider-agnostic: a second,\n * structurally unrelated provider type passes the same behavioural suite the\n * filesystem passes and the Vault adapter must. That demonstrates the portability\n * seam without standing up a live backend.\n *\n * Two choices keep it honest rather than a suite grading its own reflection:\n * - Values and meta live in two maps under disjoint keyspaces, so a written meta\n * can never be returned by `list()` as though it were a value.\n * - Meta is round-tripped through JSON on write and read, the way any real store\n * holding a string does, so nothing here hands back a live object reference a\n * caller could mutate after the fact.\n *\n * It declares no retention: `readPrevious` is omitted, the general case the\n * contract permits (see `retainsPrevious` in `@penvhq/core`). The filesystem and\n * Kubernetes sit on the same side of that line.\n */\n\nimport type { Meta, ParameterRef, Provider, Scope, ValueFile } from \"@penvhq/core\";\nimport { assertNever } from \"@penvhq/core\";\n\n/** Every scope that carries an environment keys on it, so two environments never collide. */\nfunction scopeKey(scope: Scope): string {\n switch (scope.kind) {\n case \"unscoped\":\n return \"unscoped\";\n case \"environment\":\n return `environment:${scope.environment}`;\n case \"local\":\n return \"local\";\n case \"environment-local\":\n return `environment-local:${scope.environment}`;\n default:\n return assertNever(scope, \"scope\");\n }\n}\n\n/**\n * Every contract field of a value file joined into one key. `\u0000` never appears\n * in a namespace segment, name, or scope, so it separates them unambiguously.\n */\nfunction valueKey(file: ValueFile): string {\n return [file.namespace.join(\"/\"), file.name, scopeKey(file.scope), String(file.encrypted)].join(\n \"\u0000\",\n );\n}\n\nfunction metaKey(ref: ParameterRef): string {\n return [ref.namespace.join(\"/\"), ref.name].join(\"\u0000\");\n}\n\n/** A defensive copy, so a caller mutating the file it handed us cannot reach the store. */\nfunction cloneValueFile(file: ValueFile): ValueFile {\n return {\n namespace: [...file.namespace],\n name: file.name,\n scope: file.scope,\n encrypted: file.encrypted,\n };\n}\n\ninterface StoredValue {\n readonly file: ValueFile;\n readonly value: string;\n}\n\nexport class InMemoryProvider implements Provider {\n readonly type = \"memory\";\n\n readonly #values = new Map<string, StoredValue>();\n readonly #meta = new Map<string, string>();\n\n async read(file: ValueFile): Promise<string | undefined> {\n return this.#values.get(valueKey(file))?.value;\n }\n\n async write(file: ValueFile, value: string): Promise<void> {\n this.#values.set(valueKey(file), { file: cloneValueFile(file), value });\n }\n\n async list(): Promise<ValueFile[]> {\n return [...this.#values.values()].map((stored) => cloneValueFile(stored.file));\n }\n\n async remove(file: ValueFile): Promise<void> {\n this.#values.delete(valueKey(file));\n }\n\n async readMeta(ref: ParameterRef): Promise<Meta | undefined> {\n const stored = this.#meta.get(metaKey(ref));\n return stored === undefined ? undefined : (JSON.parse(stored) as Meta);\n }\n\n async writeMeta(ref: ParameterRef, meta: Meta): Promise<void> {\n this.#meta.set(metaKey(ref), JSON.stringify(meta));\n }\n\n async removeMeta(ref: ParameterRef): Promise<void> {\n this.#meta.delete(metaKey(ref));\n }\n}\n\nexport function createInMemoryProvider(): InMemoryProvider {\n return new InMemoryProvider();\n}\n"],"mappings":"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","names":["assertNever","scopeKey"]}
package/package.json ADDED
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "@penvhq/provider-contract",
3
+ "version": "0.1.0",
4
+ "description": "The provider-agnostic acceptance suite every penv provider must pass, plus an in-memory provider that proves the seam is pluggable. Test-only.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "main": "./dist/index.cjs",
8
+ "module": "./dist/index.js",
9
+ "types": "./dist/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "import": "./dist/index.js",
14
+ "require": "./dist/index.cjs"
15
+ }
16
+ },
17
+ "files": [
18
+ "dist"
19
+ ],
20
+ "dependencies": {
21
+ "@penvhq/core": "0.1.0"
22
+ },
23
+ "scripts": {
24
+ "build": "tsup"
25
+ }
26
+ }