@terreno/api 0.18.0 → 0.20.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/CHANGELOG.md +25 -0
- package/dist/api.test.js +18 -8
- package/dist/auth.d.ts +5 -5
- package/dist/auth.js +123 -131
- package/dist/configuration.test.js +289 -10
- package/dist/configurationApp.d.ts +72 -5
- package/dist/configurationApp.js +168 -48
- package/dist/configurationPlugin.d.ts +64 -7
- package/dist/configurationPlugin.js +161 -39
- package/dist/configurationPlugin.test.js +238 -1
- package/dist/expressServer.test.js +0 -1
- package/dist/openApi.d.ts +6 -6
- package/dist/openApi.js +21 -21
- package/dist/populate.test.js +23 -0
- package/dist/realtime/queryMatcher.js +0 -6
- package/dist/realtime/queryStore.js +3 -11
- package/dist/realtime/realtime.test.js +41 -34
- package/dist/secretProviders.d.ts +79 -2
- package/dist/secretProviders.js +177 -9
- package/dist/secretProviders.test.d.ts +1 -0
- package/dist/secretProviders.test.js +391 -0
- package/package.json +1 -1
- package/src/actions.openApi.test.ts +1 -1
- package/src/actions.ts +0 -1
- package/src/api.test.ts +10 -2
- package/src/auth.ts +19 -19
- package/src/configuration.test.ts +171 -7
- package/src/configurationApp.ts +213 -30
- package/src/configurationPlugin.test.ts +174 -2
- package/src/configurationPlugin.ts +157 -28
- package/src/expressServer.test.ts +0 -1
- package/src/openApi.ts +21 -21
- package/src/populate.test.ts +25 -0
- package/src/realtime/queryMatcher.ts +0 -6
- package/src/realtime/queryStore.ts +1 -10
- package/src/realtime/realtime.test.ts +24 -24
- package/src/realtime/realtimeApp.ts +0 -1
- package/src/realtime/registry.ts +0 -1
- package/src/realtime/types.ts +0 -4
- package/src/secretProviders.test.ts +186 -0
- package/src/secretProviders.ts +145 -5
package/dist/secretProviders.js
CHANGED
|
@@ -84,8 +84,19 @@ 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;
|
|
89
100
|
var errors_1 = require("./errors");
|
|
90
101
|
var logger_1 = require("./logger");
|
|
91
102
|
/**
|
|
@@ -106,7 +117,11 @@ var EnvSecretProvider = /** @class */ (function () {
|
|
|
106
117
|
function EnvSecretProvider() {
|
|
107
118
|
this.name = "env";
|
|
108
119
|
}
|
|
109
|
-
|
|
120
|
+
/**
|
|
121
|
+
* Resolve a secret from an environment variable. Environment variables have no
|
|
122
|
+
* versions, so the optional `version` parameter is ignored.
|
|
123
|
+
*/
|
|
124
|
+
EnvSecretProvider.prototype.getSecret = function (secretName, _version) {
|
|
110
125
|
return __awaiter(this, void 0, void 0, function () {
|
|
111
126
|
var envKey, value;
|
|
112
127
|
var _a;
|
|
@@ -180,30 +195,42 @@ var GcpSecretProvider = /** @class */ (function () {
|
|
|
180
195
|
});
|
|
181
196
|
});
|
|
182
197
|
};
|
|
183
|
-
|
|
198
|
+
/**
|
|
199
|
+
* Resolve a secret from Google Cloud Secret Manager.
|
|
200
|
+
*
|
|
201
|
+
* @param secretName - A short secret id (e.g. "openai-api-key") or a full
|
|
202
|
+
* resource path (e.g. "projects/p/secrets/s" or
|
|
203
|
+
* "projects/p/secrets/s/versions/3").
|
|
204
|
+
* @param version - Optional version to resolve when `secretName` is a short id
|
|
205
|
+
* (e.g. "3"). Defaults to "latest". Ignored when `secretName` already
|
|
206
|
+
* contains an explicit `/versions/...` suffix.
|
|
207
|
+
*/
|
|
208
|
+
GcpSecretProvider.prototype.getSecret = function (secretName, version) {
|
|
184
209
|
return __awaiter(this, void 0, void 0, function () {
|
|
185
|
-
var client, resourceName, _a,
|
|
210
|
+
var client, resolvedVersion, resourceName, _a, version_1, payload, error_1;
|
|
186
211
|
var _b;
|
|
187
212
|
return __generator(this, function (_c) {
|
|
188
213
|
switch (_c.label) {
|
|
189
214
|
case 0: return [4 /*yield*/, this.getClient()];
|
|
190
215
|
case 1:
|
|
191
216
|
client = _c.sent();
|
|
217
|
+
resolvedVersion = version !== null && version !== void 0 ? version : "latest";
|
|
192
218
|
if (secretName.startsWith("projects/")) {
|
|
193
|
-
|
|
219
|
+
// Honor a full resource path. Only append a version when one is not present.
|
|
220
|
+
resourceName = secretName.includes("/versions/")
|
|
194
221
|
? secretName
|
|
195
|
-
: "".concat(secretName, "/versions/
|
|
222
|
+
: "".concat(secretName, "/versions/").concat(resolvedVersion);
|
|
196
223
|
}
|
|
197
224
|
else {
|
|
198
|
-
resourceName = "projects/".concat(this.projectId, "/secrets/").concat(secretName, "/versions/
|
|
225
|
+
resourceName = "projects/".concat(this.projectId, "/secrets/").concat(secretName, "/versions/").concat(resolvedVersion);
|
|
199
226
|
}
|
|
200
227
|
_c.label = 2;
|
|
201
228
|
case 2:
|
|
202
229
|
_c.trys.push([2, 4, , 5]);
|
|
203
230
|
return [4 /*yield*/, client.accessSecretVersion({ name: resourceName })];
|
|
204
231
|
case 3:
|
|
205
|
-
_a = __read.apply(void 0, [_c.sent(), 1]),
|
|
206
|
-
payload = (_b =
|
|
232
|
+
_a = __read.apply(void 0, [_c.sent(), 1]), version_1 = _a[0];
|
|
233
|
+
payload = (_b = version_1.payload) === null || _b === void 0 ? void 0 : _b.data;
|
|
207
234
|
if (!payload) {
|
|
208
235
|
logger_1.logger.warn("GcpSecretProvider: secret ".concat(secretName, " has no payload"));
|
|
209
236
|
return [2 /*return*/, null];
|
|
@@ -225,3 +252,144 @@ var GcpSecretProvider = /** @class */ (function () {
|
|
|
225
252
|
return GcpSecretProvider;
|
|
226
253
|
}());
|
|
227
254
|
exports.GcpSecretProvider = GcpSecretProvider;
|
|
255
|
+
/**
|
|
256
|
+
* Secret provider that delegates to an ordered list of providers, returning the
|
|
257
|
+
* first non-null result.
|
|
258
|
+
*
|
|
259
|
+
* A provider that throws is warn-logged (secret name only — never the value) and
|
|
260
|
+
* resolution falls through to the next provider. This makes it easy to compose a
|
|
261
|
+
* primary provider with a fallback, e.g. GCP with an environment-variable
|
|
262
|
+
* fallback:
|
|
263
|
+
*
|
|
264
|
+
* @example
|
|
265
|
+
* ```typescript
|
|
266
|
+
* const provider = new CompositeSecretProvider([
|
|
267
|
+
* new GcpSecretProvider({projectId: "my-project"}),
|
|
268
|
+
* new EnvSecretProvider(),
|
|
269
|
+
* ]);
|
|
270
|
+
* const key = await provider.getSecret("openai-api-key");
|
|
271
|
+
* ```
|
|
272
|
+
*/
|
|
273
|
+
var CompositeSecretProvider = /** @class */ (function () {
|
|
274
|
+
function CompositeSecretProvider(providers) {
|
|
275
|
+
if (!providers || providers.length === 0) {
|
|
276
|
+
throw new errors_1.APIError({
|
|
277
|
+
status: 500,
|
|
278
|
+
title: "CompositeSecretProvider requires at least one provider",
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
this.providers = providers;
|
|
282
|
+
this.name = "composite(".concat(providers.map(function (p) { return p.name; }).join(","), ")");
|
|
283
|
+
}
|
|
284
|
+
CompositeSecretProvider.prototype.getSecret = function (secretName, version) {
|
|
285
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
286
|
+
var _a, _b, provider, value, error_2, message, e_1_1;
|
|
287
|
+
var e_1, _c;
|
|
288
|
+
return __generator(this, function (_d) {
|
|
289
|
+
switch (_d.label) {
|
|
290
|
+
case 0:
|
|
291
|
+
_d.trys.push([0, 7, 8, 9]);
|
|
292
|
+
_a = __values(this.providers), _b = _a.next();
|
|
293
|
+
_d.label = 1;
|
|
294
|
+
case 1:
|
|
295
|
+
if (!!_b.done) return [3 /*break*/, 6];
|
|
296
|
+
provider = _b.value;
|
|
297
|
+
_d.label = 2;
|
|
298
|
+
case 2:
|
|
299
|
+
_d.trys.push([2, 4, , 5]);
|
|
300
|
+
return [4 /*yield*/, provider.getSecret(secretName, version)];
|
|
301
|
+
case 3:
|
|
302
|
+
value = _d.sent();
|
|
303
|
+
if (value !== null) {
|
|
304
|
+
return [2 /*return*/, value];
|
|
305
|
+
}
|
|
306
|
+
return [3 /*break*/, 5];
|
|
307
|
+
case 4:
|
|
308
|
+
error_2 = _d.sent();
|
|
309
|
+
message = error_2 instanceof Error ? error_2.message : String(error_2);
|
|
310
|
+
logger_1.logger.warn("CompositeSecretProvider: provider ".concat(provider.name, " failed for secret ").concat(secretName, ": ").concat(message));
|
|
311
|
+
return [3 /*break*/, 5];
|
|
312
|
+
case 5:
|
|
313
|
+
_b = _a.next();
|
|
314
|
+
return [3 /*break*/, 1];
|
|
315
|
+
case 6: return [3 /*break*/, 9];
|
|
316
|
+
case 7:
|
|
317
|
+
e_1_1 = _d.sent();
|
|
318
|
+
e_1 = { error: e_1_1 };
|
|
319
|
+
return [3 /*break*/, 9];
|
|
320
|
+
case 8:
|
|
321
|
+
try {
|
|
322
|
+
if (_b && !_b.done && (_c = _a.return)) _c.call(_a);
|
|
323
|
+
}
|
|
324
|
+
finally { if (e_1) throw e_1.error; }
|
|
325
|
+
return [7 /*endfinally*/];
|
|
326
|
+
case 9: return [2 /*return*/, null];
|
|
327
|
+
}
|
|
328
|
+
});
|
|
329
|
+
});
|
|
330
|
+
};
|
|
331
|
+
return CompositeSecretProvider;
|
|
332
|
+
}());
|
|
333
|
+
exports.CompositeSecretProvider = CompositeSecretProvider;
|
|
334
|
+
/**
|
|
335
|
+
* Secret provider that wraps any provider with an in-memory TTL cache.
|
|
336
|
+
*
|
|
337
|
+
* Cache entries are keyed by `secretName@version` so that pinned versions are
|
|
338
|
+
* cached independently. `null` results (secret not found) are cached too, to
|
|
339
|
+
* avoid hammering the underlying provider for missing secrets. Secret values are
|
|
340
|
+
* never logged.
|
|
341
|
+
*
|
|
342
|
+
* Use `clear()` to drop the entire cache (e.g. on rotation) or `clearKey()` to
|
|
343
|
+
* invalidate a single secret.
|
|
344
|
+
*
|
|
345
|
+
* @example
|
|
346
|
+
* ```typescript
|
|
347
|
+
* const provider = new CachingSecretProvider(
|
|
348
|
+
* new CompositeSecretProvider([gcp, env]),
|
|
349
|
+
* {ttlMs: 30_000}
|
|
350
|
+
* );
|
|
351
|
+
* ```
|
|
352
|
+
*/
|
|
353
|
+
var CachingSecretProvider = /** @class */ (function () {
|
|
354
|
+
function CachingSecretProvider(provider, options) {
|
|
355
|
+
var _a;
|
|
356
|
+
this.cache = new Map();
|
|
357
|
+
this.provider = provider;
|
|
358
|
+
this.ttlMs = (_a = options === null || options === void 0 ? void 0 : options.ttlMs) !== null && _a !== void 0 ? _a : 60000;
|
|
359
|
+
this.name = "caching(".concat(provider.name, ")");
|
|
360
|
+
}
|
|
361
|
+
CachingSecretProvider.prototype.cacheKey = function (secretName, version) {
|
|
362
|
+
return "".concat(secretName, "@").concat(version !== null && version !== void 0 ? version : "latest");
|
|
363
|
+
};
|
|
364
|
+
CachingSecretProvider.prototype.getSecret = function (secretName, version) {
|
|
365
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
366
|
+
var key, now, cached, value;
|
|
367
|
+
return __generator(this, function (_a) {
|
|
368
|
+
switch (_a.label) {
|
|
369
|
+
case 0:
|
|
370
|
+
key = this.cacheKey(secretName, version);
|
|
371
|
+
now = Date.now();
|
|
372
|
+
cached = this.cache.get(key);
|
|
373
|
+
if (cached && cached.expiresAt > now) {
|
|
374
|
+
return [2 /*return*/, cached.value];
|
|
375
|
+
}
|
|
376
|
+
return [4 /*yield*/, this.provider.getSecret(secretName, version)];
|
|
377
|
+
case 1:
|
|
378
|
+
value = _a.sent();
|
|
379
|
+
this.cache.set(key, { expiresAt: now + this.ttlMs, value: value });
|
|
380
|
+
return [2 /*return*/, value];
|
|
381
|
+
}
|
|
382
|
+
});
|
|
383
|
+
});
|
|
384
|
+
};
|
|
385
|
+
/** Clears the entire cache. Useful on secret rotation and in tests. */
|
|
386
|
+
CachingSecretProvider.prototype.clear = function () {
|
|
387
|
+
this.cache.clear();
|
|
388
|
+
};
|
|
389
|
+
/** Invalidates a single cached secret by name (and optional version). */
|
|
390
|
+
CachingSecretProvider.prototype.clearKey = function (secretName, version) {
|
|
391
|
+
this.cache.delete(this.cacheKey(secretName, version));
|
|
392
|
+
};
|
|
393
|
+
return CachingSecretProvider;
|
|
394
|
+
}());
|
|
395
|
+
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
|
@@ -7,7 +7,7 @@ import {addAuthRoutes, setupAuth} from "./auth";
|
|
|
7
7
|
import {setupServer} from "./expressServer";
|
|
8
8
|
import {Permissions} from "./permissions";
|
|
9
9
|
import {TerrenoApp} from "./terrenoApp";
|
|
10
|
-
import {
|
|
10
|
+
import {FoodModel, setupDb, UserModel} from "./tests";
|
|
11
11
|
import {z} from "./zodOpenApi";
|
|
12
12
|
|
|
13
13
|
const foodActionPermissions = {
|
package/src/actions.ts
CHANGED
|
@@ -9,7 +9,6 @@ import {loadDocOr404} from "./docLoader";
|
|
|
9
9
|
import {APIError} from "./errors";
|
|
10
10
|
import {defaultOpenApiErrorResponses} from "./openApi";
|
|
11
11
|
import {checkPermissions, type PermissionMethod} from "./permissions";
|
|
12
|
-
import {z} from "./zodOpenApi";
|
|
13
12
|
|
|
14
13
|
// At least two characters: leading letter plus one or more alphanumeric/_/- chars.
|
|
15
14
|
export const ACTION_NAME_PATTERN = /^[A-Za-z][A-Za-z0-9_-]+$/;
|