@terreno/api 0.19.0 → 0.20.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -84,8 +84,20 @@ var __read = (this && this.__read) || function (o, n) {
84
84
  }
85
85
  return ar;
86
86
  };
87
+ var __values = (this && this.__values) || function(o) {
88
+ var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
89
+ if (m) return m.call(o);
90
+ if (o && typeof o.length === "number") return {
91
+ next: function () {
92
+ if (o && i >= o.length) o = void 0;
93
+ return { value: o && o[i++], done: !o };
94
+ }
95
+ };
96
+ throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
97
+ };
87
98
  Object.defineProperty(exports, "__esModule", { value: true });
88
- exports.GcpSecretProvider = exports.EnvSecretProvider = void 0;
99
+ exports.CachingSecretProvider = exports.CompositeSecretProvider = exports.GcpSecretProvider = exports.EnvSecretProvider = void 0;
100
+ var luxon_1 = require("luxon");
89
101
  var errors_1 = require("./errors");
90
102
  var logger_1 = require("./logger");
91
103
  /**
@@ -106,7 +118,11 @@ var EnvSecretProvider = /** @class */ (function () {
106
118
  function EnvSecretProvider() {
107
119
  this.name = "env";
108
120
  }
109
- EnvSecretProvider.prototype.getSecret = function (secretName) {
121
+ /**
122
+ * Resolve a secret from an environment variable. Environment variables have no
123
+ * versions, so the optional `version` parameter is ignored.
124
+ */
125
+ EnvSecretProvider.prototype.getSecret = function (secretName, _version) {
110
126
  return __awaiter(this, void 0, void 0, function () {
111
127
  var envKey, value;
112
128
  var _a;
@@ -180,30 +196,42 @@ var GcpSecretProvider = /** @class */ (function () {
180
196
  });
181
197
  });
182
198
  };
183
- GcpSecretProvider.prototype.getSecret = function (secretName) {
199
+ /**
200
+ * Resolve a secret from Google Cloud Secret Manager.
201
+ *
202
+ * @param secretName - A short secret id (e.g. "openai-api-key") or a full
203
+ * resource path (e.g. "projects/p/secrets/s" or
204
+ * "projects/p/secrets/s/versions/3").
205
+ * @param version - Optional version to resolve when `secretName` is a short id
206
+ * (e.g. "3"). Defaults to "latest". Ignored when `secretName` already
207
+ * contains an explicit `/versions/...` suffix.
208
+ */
209
+ GcpSecretProvider.prototype.getSecret = function (secretName, version) {
184
210
  return __awaiter(this, void 0, void 0, function () {
185
- var client, resourceName, _a, version, payload, error_1;
211
+ var client, resolvedVersion, resourceName, _a, version_1, payload, error_1;
186
212
  var _b;
187
213
  return __generator(this, function (_c) {
188
214
  switch (_c.label) {
189
215
  case 0: return [4 /*yield*/, this.getClient()];
190
216
  case 1:
191
217
  client = _c.sent();
218
+ resolvedVersion = version !== null && version !== void 0 ? version : "latest";
192
219
  if (secretName.startsWith("projects/")) {
193
- resourceName = secretName.endsWith("/versions/latest")
220
+ // Honor a full resource path. Only append a version when one is not present.
221
+ resourceName = secretName.includes("/versions/")
194
222
  ? secretName
195
- : "".concat(secretName, "/versions/latest");
223
+ : "".concat(secretName, "/versions/").concat(resolvedVersion);
196
224
  }
197
225
  else {
198
- resourceName = "projects/".concat(this.projectId, "/secrets/").concat(secretName, "/versions/latest");
226
+ resourceName = "projects/".concat(this.projectId, "/secrets/").concat(secretName, "/versions/").concat(resolvedVersion);
199
227
  }
200
228
  _c.label = 2;
201
229
  case 2:
202
230
  _c.trys.push([2, 4, , 5]);
203
231
  return [4 /*yield*/, client.accessSecretVersion({ name: resourceName })];
204
232
  case 3:
205
- _a = __read.apply(void 0, [_c.sent(), 1]), version = _a[0];
206
- payload = (_b = version.payload) === null || _b === void 0 ? void 0 : _b.data;
233
+ _a = __read.apply(void 0, [_c.sent(), 1]), version_1 = _a[0];
234
+ payload = (_b = version_1.payload) === null || _b === void 0 ? void 0 : _b.data;
207
235
  if (!payload) {
208
236
  logger_1.logger.warn("GcpSecretProvider: secret ".concat(secretName, " has no payload"));
209
237
  return [2 /*return*/, null];
@@ -225,3 +253,144 @@ var GcpSecretProvider = /** @class */ (function () {
225
253
  return GcpSecretProvider;
226
254
  }());
227
255
  exports.GcpSecretProvider = GcpSecretProvider;
256
+ /**
257
+ * Secret provider that delegates to an ordered list of providers, returning the
258
+ * first non-null result.
259
+ *
260
+ * A provider that throws is warn-logged (secret name only — never the value) and
261
+ * resolution falls through to the next provider. This makes it easy to compose a
262
+ * primary provider with a fallback, e.g. GCP with an environment-variable
263
+ * fallback:
264
+ *
265
+ * @example
266
+ * ```typescript
267
+ * const provider = new CompositeSecretProvider([
268
+ * new GcpSecretProvider({projectId: "my-project"}),
269
+ * new EnvSecretProvider(),
270
+ * ]);
271
+ * const key = await provider.getSecret("openai-api-key");
272
+ * ```
273
+ */
274
+ var CompositeSecretProvider = /** @class */ (function () {
275
+ function CompositeSecretProvider(providers) {
276
+ if (!providers || providers.length === 0) {
277
+ throw new errors_1.APIError({
278
+ status: 500,
279
+ title: "CompositeSecretProvider requires at least one provider",
280
+ });
281
+ }
282
+ this.providers = providers;
283
+ this.name = "composite(".concat(providers.map(function (p) { return p.name; }).join(","), ")");
284
+ }
285
+ CompositeSecretProvider.prototype.getSecret = function (secretName, version) {
286
+ return __awaiter(this, void 0, void 0, function () {
287
+ var _a, _b, provider, value, error_2, message, e_1_1;
288
+ var e_1, _c;
289
+ return __generator(this, function (_d) {
290
+ switch (_d.label) {
291
+ case 0:
292
+ _d.trys.push([0, 7, 8, 9]);
293
+ _a = __values(this.providers), _b = _a.next();
294
+ _d.label = 1;
295
+ case 1:
296
+ if (!!_b.done) return [3 /*break*/, 6];
297
+ provider = _b.value;
298
+ _d.label = 2;
299
+ case 2:
300
+ _d.trys.push([2, 4, , 5]);
301
+ return [4 /*yield*/, provider.getSecret(secretName, version)];
302
+ case 3:
303
+ value = _d.sent();
304
+ if (value !== null) {
305
+ return [2 /*return*/, value];
306
+ }
307
+ return [3 /*break*/, 5];
308
+ case 4:
309
+ error_2 = _d.sent();
310
+ message = error_2 instanceof Error ? error_2.message : String(error_2);
311
+ logger_1.logger.warn("CompositeSecretProvider: provider ".concat(provider.name, " failed for secret ").concat(secretName, ": ").concat(message));
312
+ return [3 /*break*/, 5];
313
+ case 5:
314
+ _b = _a.next();
315
+ return [3 /*break*/, 1];
316
+ case 6: return [3 /*break*/, 9];
317
+ case 7:
318
+ e_1_1 = _d.sent();
319
+ e_1 = { error: e_1_1 };
320
+ return [3 /*break*/, 9];
321
+ case 8:
322
+ try {
323
+ if (_b && !_b.done && (_c = _a.return)) _c.call(_a);
324
+ }
325
+ finally { if (e_1) throw e_1.error; }
326
+ return [7 /*endfinally*/];
327
+ case 9: return [2 /*return*/, null];
328
+ }
329
+ });
330
+ });
331
+ };
332
+ return CompositeSecretProvider;
333
+ }());
334
+ exports.CompositeSecretProvider = CompositeSecretProvider;
335
+ /**
336
+ * Secret provider that wraps any provider with an in-memory TTL cache.
337
+ *
338
+ * Cache entries are keyed by `secretName@version` so that pinned versions are
339
+ * cached independently. `null` results (secret not found) are cached too, to
340
+ * avoid hammering the underlying provider for missing secrets. Secret values are
341
+ * never logged.
342
+ *
343
+ * Use `clear()` to drop the entire cache (e.g. on rotation) or `clearKey()` to
344
+ * invalidate a single secret.
345
+ *
346
+ * @example
347
+ * ```typescript
348
+ * const provider = new CachingSecretProvider(
349
+ * new CompositeSecretProvider([gcp, env]),
350
+ * {ttlMs: 30_000}
351
+ * );
352
+ * ```
353
+ */
354
+ var CachingSecretProvider = /** @class */ (function () {
355
+ function CachingSecretProvider(provider, options) {
356
+ var _a;
357
+ this.cache = new Map();
358
+ this.provider = provider;
359
+ this.ttlMs = (_a = options === null || options === void 0 ? void 0 : options.ttlMs) !== null && _a !== void 0 ? _a : 60000;
360
+ this.name = "caching(".concat(provider.name, ")");
361
+ }
362
+ CachingSecretProvider.prototype.cacheKey = function (secretName, version) {
363
+ return "".concat(secretName, "@").concat(version !== null && version !== void 0 ? version : "latest");
364
+ };
365
+ CachingSecretProvider.prototype.getSecret = function (secretName, version) {
366
+ return __awaiter(this, void 0, void 0, function () {
367
+ var key, now, cached, value;
368
+ return __generator(this, function (_a) {
369
+ switch (_a.label) {
370
+ case 0:
371
+ key = this.cacheKey(secretName, version);
372
+ now = luxon_1.DateTime.now().toMillis();
373
+ cached = this.cache.get(key);
374
+ if (cached && cached.expiresAt > now) {
375
+ return [2 /*return*/, cached.value];
376
+ }
377
+ return [4 /*yield*/, this.provider.getSecret(secretName, version)];
378
+ case 1:
379
+ value = _a.sent();
380
+ this.cache.set(key, { expiresAt: now + this.ttlMs, value: value });
381
+ return [2 /*return*/, value];
382
+ }
383
+ });
384
+ });
385
+ };
386
+ /** Clears the entire cache. Useful on secret rotation and in tests. */
387
+ CachingSecretProvider.prototype.clear = function () {
388
+ this.cache.clear();
389
+ };
390
+ /** Invalidates a single cached secret by name (and optional version). */
391
+ CachingSecretProvider.prototype.clearKey = function (secretName, version) {
392
+ this.cache.delete(this.cacheKey(secretName, version));
393
+ };
394
+ return CachingSecretProvider;
395
+ }());
396
+ exports.CachingSecretProvider = CachingSecretProvider;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,391 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __generator = (this && this.__generator) || function (thisArg, body) {
12
+ var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
13
+ return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
14
+ function verb(n) { return function (v) { return step([n, v]); }; }
15
+ function step(op) {
16
+ if (f) throw new TypeError("Generator is already executing.");
17
+ while (g && (g = 0, op[0] && (_ = 0)), _) try {
18
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
19
+ if (y = 0, t) op = [op[0] & 2, t.value];
20
+ switch (op[0]) {
21
+ case 0: case 1: t = op; break;
22
+ case 4: _.label++; return { value: op[1], done: false };
23
+ case 5: _.label++; y = op[1]; op = [0]; continue;
24
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
25
+ default:
26
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
27
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
28
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
29
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
30
+ if (t[2]) _.ops.pop();
31
+ _.trys.pop(); continue;
32
+ }
33
+ op = body.call(thisArg, _);
34
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
35
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
36
+ }
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ var bun_test_1 = require("bun:test");
40
+ var secretProviders_1 = require("./secretProviders");
41
+ (0, bun_test_1.describe)("EnvSecretProvider", function () {
42
+ (0, bun_test_1.beforeEach)(function () {
43
+ delete process.env.MY_SECRET_KEY;
44
+ });
45
+ (0, bun_test_1.it)("resolves from a SCREAMING_SNAKE_CASE env var", function () { return __awaiter(void 0, void 0, void 0, function () {
46
+ var provider, _a;
47
+ return __generator(this, function (_b) {
48
+ switch (_b.label) {
49
+ case 0:
50
+ process.env.MY_SECRET_KEY = "from-env";
51
+ provider = new secretProviders_1.EnvSecretProvider();
52
+ _a = bun_test_1.expect;
53
+ return [4 /*yield*/, provider.getSecret("my-secret-key")];
54
+ case 1:
55
+ _a.apply(void 0, [_b.sent()]).toBe("from-env");
56
+ return [2 /*return*/];
57
+ }
58
+ });
59
+ }); });
60
+ (0, bun_test_1.it)("returns null when the env var is missing", function () { return __awaiter(void 0, void 0, void 0, function () {
61
+ var provider, _a;
62
+ return __generator(this, function (_b) {
63
+ switch (_b.label) {
64
+ case 0:
65
+ provider = new secretProviders_1.EnvSecretProvider();
66
+ _a = bun_test_1.expect;
67
+ return [4 /*yield*/, provider.getSecret("my-secret-key")];
68
+ case 1:
69
+ _a.apply(void 0, [_b.sent()]).toBeNull();
70
+ return [2 /*return*/];
71
+ }
72
+ });
73
+ }); });
74
+ (0, bun_test_1.it)("ignores the version parameter", function () { return __awaiter(void 0, void 0, void 0, function () {
75
+ var provider, _a;
76
+ return __generator(this, function (_b) {
77
+ switch (_b.label) {
78
+ case 0:
79
+ process.env.MY_SECRET_KEY = "value";
80
+ provider = new secretProviders_1.EnvSecretProvider();
81
+ _a = bun_test_1.expect;
82
+ return [4 /*yield*/, provider.getSecret("my-secret-key", "5")];
83
+ case 1:
84
+ _a.apply(void 0, [_b.sent()]).toBe("value");
85
+ return [2 /*return*/];
86
+ }
87
+ });
88
+ }); });
89
+ });
90
+ (0, bun_test_1.describe)("CompositeSecretProvider", function () {
91
+ (0, bun_test_1.it)("throws when constructed with no providers", function () {
92
+ (0, bun_test_1.expect)(function () { return new secretProviders_1.CompositeSecretProvider([]); }).toThrow();
93
+ });
94
+ (0, bun_test_1.it)("returns the first non-null result", function () { return __awaiter(void 0, void 0, void 0, function () {
95
+ var a, b, c, provider, _a;
96
+ return __generator(this, function (_b) {
97
+ switch (_b.label) {
98
+ case 0:
99
+ a = { getSecret: function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
100
+ return [2 /*return*/, null];
101
+ }); }); }, name: "a" };
102
+ b = { getSecret: function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
103
+ return [2 /*return*/, "from-b"];
104
+ }); }); }, name: "b" };
105
+ c = { getSecret: function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
106
+ return [2 /*return*/, "from-c"];
107
+ }); }); }, name: "c" };
108
+ provider = new secretProviders_1.CompositeSecretProvider([a, b, c]);
109
+ _a = bun_test_1.expect;
110
+ return [4 /*yield*/, provider.getSecret("x")];
111
+ case 1:
112
+ _a.apply(void 0, [_b.sent()]).toBe("from-b");
113
+ return [2 /*return*/];
114
+ }
115
+ });
116
+ }); });
117
+ (0, bun_test_1.it)("falls through to the next provider when one throws", function () { return __awaiter(void 0, void 0, void 0, function () {
118
+ var failing, fallback, provider, _a;
119
+ return __generator(this, function (_b) {
120
+ switch (_b.label) {
121
+ case 0:
122
+ failing = {
123
+ getSecret: function () { return __awaiter(void 0, void 0, void 0, function () {
124
+ return __generator(this, function (_a) {
125
+ throw new Error("provider down");
126
+ });
127
+ }); },
128
+ name: "failing",
129
+ };
130
+ fallback = { getSecret: function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
131
+ return [2 /*return*/, "from-fallback"];
132
+ }); }); }, name: "fallback" };
133
+ provider = new secretProviders_1.CompositeSecretProvider([failing, fallback]);
134
+ _a = bun_test_1.expect;
135
+ return [4 /*yield*/, provider.getSecret("x")];
136
+ case 1:
137
+ _a.apply(void 0, [_b.sent()]).toBe("from-fallback");
138
+ return [2 /*return*/];
139
+ }
140
+ });
141
+ }); });
142
+ (0, bun_test_1.it)("returns null when every provider yields null", function () { return __awaiter(void 0, void 0, void 0, function () {
143
+ var a, b, provider, _a;
144
+ return __generator(this, function (_b) {
145
+ switch (_b.label) {
146
+ case 0:
147
+ a = { getSecret: function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
148
+ return [2 /*return*/, null];
149
+ }); }); }, name: "a" };
150
+ b = { getSecret: function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
151
+ return [2 /*return*/, null];
152
+ }); }); }, name: "b" };
153
+ provider = new secretProviders_1.CompositeSecretProvider([a, b]);
154
+ _a = bun_test_1.expect;
155
+ return [4 /*yield*/, provider.getSecret("x")];
156
+ case 1:
157
+ _a.apply(void 0, [_b.sent()]).toBeNull();
158
+ return [2 /*return*/];
159
+ }
160
+ });
161
+ }); });
162
+ (0, bun_test_1.it)("forwards the version parameter to each provider", function () { return __awaiter(void 0, void 0, void 0, function () {
163
+ var seen, a, b, provider;
164
+ return __generator(this, function (_a) {
165
+ switch (_a.label) {
166
+ case 0:
167
+ seen = [];
168
+ a = {
169
+ getSecret: function (_name, version) { return __awaiter(void 0, void 0, void 0, function () {
170
+ return __generator(this, function (_a) {
171
+ seen.push(version);
172
+ return [2 /*return*/, null];
173
+ });
174
+ }); },
175
+ name: "a",
176
+ };
177
+ b = {
178
+ getSecret: function (_name, version) { return __awaiter(void 0, void 0, void 0, function () {
179
+ return __generator(this, function (_a) {
180
+ seen.push(version);
181
+ return [2 /*return*/, "value"];
182
+ });
183
+ }); },
184
+ name: "b",
185
+ };
186
+ provider = new secretProviders_1.CompositeSecretProvider([a, b]);
187
+ return [4 /*yield*/, provider.getSecret("x", "7")];
188
+ case 1:
189
+ _a.sent();
190
+ (0, bun_test_1.expect)(seen).toEqual(["7", "7"]);
191
+ return [2 /*return*/];
192
+ }
193
+ });
194
+ }); });
195
+ (0, bun_test_1.it)("builds a composite name from the underlying providers", function () {
196
+ var provider = new secretProviders_1.CompositeSecretProvider([
197
+ { getSecret: function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
198
+ return [2 /*return*/, null];
199
+ }); }); }, name: "gcp" },
200
+ { getSecret: function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
201
+ return [2 /*return*/, null];
202
+ }); }); }, name: "env" },
203
+ ]);
204
+ (0, bun_test_1.expect)(provider.name).toBe("composite(gcp,env)");
205
+ });
206
+ });
207
+ (0, bun_test_1.describe)("CachingSecretProvider", function () {
208
+ (0, bun_test_1.it)("memoizes a value within the TTL (single underlying call)", function () { return __awaiter(void 0, void 0, void 0, function () {
209
+ var calls, underlying, provider, _a, _b;
210
+ return __generator(this, function (_c) {
211
+ switch (_c.label) {
212
+ case 0:
213
+ calls = 0;
214
+ underlying = {
215
+ getSecret: function () { return __awaiter(void 0, void 0, void 0, function () {
216
+ return __generator(this, function (_a) {
217
+ calls++;
218
+ return [2 /*return*/, "value"];
219
+ });
220
+ }); },
221
+ name: "underlying",
222
+ };
223
+ provider = new secretProviders_1.CachingSecretProvider(underlying, { ttlMs: 10000 });
224
+ _a = bun_test_1.expect;
225
+ return [4 /*yield*/, provider.getSecret("x")];
226
+ case 1:
227
+ _a.apply(void 0, [_c.sent()]).toBe("value");
228
+ _b = bun_test_1.expect;
229
+ return [4 /*yield*/, provider.getSecret("x")];
230
+ case 2:
231
+ _b.apply(void 0, [_c.sent()]).toBe("value");
232
+ (0, bun_test_1.expect)(calls).toBe(1);
233
+ return [2 /*return*/];
234
+ }
235
+ });
236
+ }); });
237
+ (0, bun_test_1.it)("re-fetches after clear()", function () { return __awaiter(void 0, void 0, void 0, function () {
238
+ var calls, underlying, provider, _a, _b;
239
+ return __generator(this, function (_c) {
240
+ switch (_c.label) {
241
+ case 0:
242
+ calls = 0;
243
+ underlying = {
244
+ getSecret: function () { return __awaiter(void 0, void 0, void 0, function () {
245
+ return __generator(this, function (_a) {
246
+ calls++;
247
+ return [2 /*return*/, "value-".concat(calls)];
248
+ });
249
+ }); },
250
+ name: "underlying",
251
+ };
252
+ provider = new secretProviders_1.CachingSecretProvider(underlying, { ttlMs: 10000 });
253
+ _a = bun_test_1.expect;
254
+ return [4 /*yield*/, provider.getSecret("x")];
255
+ case 1:
256
+ _a.apply(void 0, [_c.sent()]).toBe("value-1");
257
+ provider.clear();
258
+ _b = bun_test_1.expect;
259
+ return [4 /*yield*/, provider.getSecret("x")];
260
+ case 2:
261
+ _b.apply(void 0, [_c.sent()]).toBe("value-2");
262
+ (0, bun_test_1.expect)(calls).toBe(2);
263
+ return [2 /*return*/];
264
+ }
265
+ });
266
+ }); });
267
+ (0, bun_test_1.it)("re-fetches after the TTL expires", function () { return __awaiter(void 0, void 0, void 0, function () {
268
+ var calls, underlying, provider, _a, _b;
269
+ return __generator(this, function (_c) {
270
+ switch (_c.label) {
271
+ case 0:
272
+ calls = 0;
273
+ underlying = {
274
+ getSecret: function () { return __awaiter(void 0, void 0, void 0, function () {
275
+ return __generator(this, function (_a) {
276
+ calls++;
277
+ return [2 /*return*/, "value-".concat(calls)];
278
+ });
279
+ }); },
280
+ name: "underlying",
281
+ };
282
+ provider = new secretProviders_1.CachingSecretProvider(underlying, { ttlMs: 1 });
283
+ _a = bun_test_1.expect;
284
+ return [4 /*yield*/, provider.getSecret("x")];
285
+ case 1:
286
+ _a.apply(void 0, [_c.sent()]).toBe("value-1");
287
+ return [4 /*yield*/, new Promise(function (resolve) { return setTimeout(resolve, 5); })];
288
+ case 2:
289
+ _c.sent();
290
+ _b = bun_test_1.expect;
291
+ return [4 /*yield*/, provider.getSecret("x")];
292
+ case 3:
293
+ _b.apply(void 0, [_c.sent()]).toBe("value-2");
294
+ (0, bun_test_1.expect)(calls).toBe(2);
295
+ return [2 /*return*/];
296
+ }
297
+ });
298
+ }); });
299
+ (0, bun_test_1.it)("caches different versions independently", function () { return __awaiter(void 0, void 0, void 0, function () {
300
+ var seen, underlying, provider, _a, _b, _c;
301
+ return __generator(this, function (_d) {
302
+ switch (_d.label) {
303
+ case 0:
304
+ seen = [];
305
+ underlying = {
306
+ getSecret: function (_name, version) { return __awaiter(void 0, void 0, void 0, function () {
307
+ return __generator(this, function (_a) {
308
+ seen.push(version);
309
+ return [2 /*return*/, "v-".concat(version !== null && version !== void 0 ? version : "latest")];
310
+ });
311
+ }); },
312
+ name: "underlying",
313
+ };
314
+ provider = new secretProviders_1.CachingSecretProvider(underlying, { ttlMs: 10000 });
315
+ _a = bun_test_1.expect;
316
+ return [4 /*yield*/, provider.getSecret("x", "1")];
317
+ case 1:
318
+ _a.apply(void 0, [_d.sent()]).toBe("v-1");
319
+ _b = bun_test_1.expect;
320
+ return [4 /*yield*/, provider.getSecret("x", "2")];
321
+ case 2:
322
+ _b.apply(void 0, [_d.sent()]).toBe("v-2");
323
+ // Cached hits, no additional underlying calls.
324
+ _c = bun_test_1.expect;
325
+ return [4 /*yield*/, provider.getSecret("x", "1")];
326
+ case 3:
327
+ // Cached hits, no additional underlying calls.
328
+ _c.apply(void 0, [_d.sent()]).toBe("v-1");
329
+ (0, bun_test_1.expect)(seen).toEqual(["1", "2"]);
330
+ return [2 /*return*/];
331
+ }
332
+ });
333
+ }); });
334
+ (0, bun_test_1.it)("clearKey invalidates a single secret", function () { return __awaiter(void 0, void 0, void 0, function () {
335
+ var calls, underlying, provider;
336
+ return __generator(this, function (_a) {
337
+ switch (_a.label) {
338
+ case 0:
339
+ calls = 0;
340
+ underlying = {
341
+ getSecret: function () { return __awaiter(void 0, void 0, void 0, function () {
342
+ return __generator(this, function (_a) {
343
+ calls++;
344
+ return [2 /*return*/, "value-".concat(calls)];
345
+ });
346
+ }); },
347
+ name: "underlying",
348
+ };
349
+ provider = new secretProviders_1.CachingSecretProvider(underlying, { ttlMs: 10000 });
350
+ return [4 /*yield*/, provider.getSecret("x")];
351
+ case 1:
352
+ _a.sent();
353
+ provider.clearKey("x");
354
+ return [4 /*yield*/, provider.getSecret("x")];
355
+ case 2:
356
+ _a.sent();
357
+ (0, bun_test_1.expect)(calls).toBe(2);
358
+ return [2 /*return*/];
359
+ }
360
+ });
361
+ }); });
362
+ (0, bun_test_1.it)("caches null results", function () { return __awaiter(void 0, void 0, void 0, function () {
363
+ var calls, underlying, provider, _a, _b;
364
+ return __generator(this, function (_c) {
365
+ switch (_c.label) {
366
+ case 0:
367
+ calls = 0;
368
+ underlying = {
369
+ getSecret: function () { return __awaiter(void 0, void 0, void 0, function () {
370
+ return __generator(this, function (_a) {
371
+ calls++;
372
+ return [2 /*return*/, null];
373
+ });
374
+ }); },
375
+ name: "underlying",
376
+ };
377
+ provider = new secretProviders_1.CachingSecretProvider(underlying, { ttlMs: 10000 });
378
+ _a = bun_test_1.expect;
379
+ return [4 /*yield*/, provider.getSecret("missing")];
380
+ case 1:
381
+ _a.apply(void 0, [_c.sent()]).toBeNull();
382
+ _b = bun_test_1.expect;
383
+ return [4 /*yield*/, provider.getSecret("missing")];
384
+ case 2:
385
+ _b.apply(void 0, [_c.sent()]).toBeNull();
386
+ (0, bun_test_1.expect)(calls).toBe(1);
387
+ return [2 /*return*/];
388
+ }
389
+ });
390
+ }); });
391
+ });
package/package.json CHANGED
@@ -109,5 +109,5 @@
109
109
  "updateSnapshot": "bun test --update-snapshots"
110
110
  },
111
111
  "types": "dist/index.d.ts",
112
- "version": "0.19.0"
112
+ "version": "0.20.1"
113
113
  }
package/src/auth.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import {randomUUID} from "node:crypto";
2
2
  import express from "express";
3
3
  import jwt, {type JwtPayload} from "jsonwebtoken";
4
+ import {DateTime} from "luxon";
4
5
  import type {Model, ObjectId} from "mongoose";
5
6
  import ms, {type StringValue} from "ms";
6
7
  import passport from "passport";
@@ -185,7 +186,6 @@ export const generateTokens = async (
185
186
  return {refreshToken, sessionId, token};
186
187
  };
187
188
 
188
- // TODO allow customization
189
189
  export const setupAuth = (app: express.Application, userModel: UserModel): void => {
190
190
  passport.use(new AnonymousStrategy());
191
191
  passport.use(userModel.createStrategy());
@@ -300,7 +300,7 @@ export const setupAuth = (app: express.Application, userModel: UserModel): void
300
300
  ? (error as {expiredAt?: unknown}).expiredAt
301
301
  : undefined;
302
302
  const message = errorMessage(error);
303
- const details = `[jwt] Error decoding token${userText}: ${error}, expired at ${expiredAt}, current time: ${Date.now()}`;
303
+ const details = `[jwt] Error decoding token${userText}: ${error}, expired at ${expiredAt}, current time: ${DateTime.now().toMillis()}`;
304
304
  logger.debug(details);
305
305
  return res.status(401).json({details, message});
306
306
  }