@griffin-app/griffin-executor 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.
Files changed (153) hide show
  1. package/README.md +152 -0
  2. package/dist/adapters/axios.d.ts +5 -0
  3. package/dist/adapters/axios.d.ts.map +1 -0
  4. package/dist/adapters/axios.js +36 -0
  5. package/dist/adapters/axios.js.map +1 -0
  6. package/dist/adapters/index.d.ts +3 -0
  7. package/dist/adapters/index.d.ts.map +1 -0
  8. package/dist/adapters/index.js +3 -0
  9. package/dist/adapters/index.js.map +1 -0
  10. package/dist/adapters/stub.d.ts +22 -0
  11. package/dist/adapters/stub.d.ts.map +1 -0
  12. package/dist/adapters/stub.js +36 -0
  13. package/dist/adapters/stub.js.map +1 -0
  14. package/dist/events/adapters/in-memory.d.ts +52 -0
  15. package/dist/events/adapters/in-memory.d.ts.map +1 -0
  16. package/dist/events/adapters/in-memory.js +70 -0
  17. package/dist/events/adapters/in-memory.js.map +1 -0
  18. package/dist/events/adapters/in-memory.test.d.ts +2 -0
  19. package/dist/events/adapters/in-memory.test.d.ts.map +1 -0
  20. package/dist/events/adapters/in-memory.test.js +109 -0
  21. package/dist/events/adapters/in-memory.test.js.map +1 -0
  22. package/dist/events/adapters/index.d.ts +9 -0
  23. package/dist/events/adapters/index.d.ts.map +1 -0
  24. package/dist/events/adapters/index.js +9 -0
  25. package/dist/events/adapters/index.js.map +1 -0
  26. package/dist/events/adapters/kinesis.d.ts +91 -0
  27. package/dist/events/adapters/kinesis.d.ts.map +1 -0
  28. package/dist/events/adapters/kinesis.js +136 -0
  29. package/dist/events/adapters/kinesis.js.map +1 -0
  30. package/dist/events/adapters/kinesis.test.d.ts +2 -0
  31. package/dist/events/adapters/kinesis.test.d.ts.map +1 -0
  32. package/dist/events/adapters/kinesis.test.js +249 -0
  33. package/dist/events/adapters/kinesis.test.js.map +1 -0
  34. package/dist/events/emitter.d.ts +68 -0
  35. package/dist/events/emitter.d.ts.map +1 -0
  36. package/dist/events/emitter.js +83 -0
  37. package/dist/events/emitter.js.map +1 -0
  38. package/dist/events/emitter.test.d.ts +2 -0
  39. package/dist/events/emitter.test.d.ts.map +1 -0
  40. package/dist/events/emitter.test.js +262 -0
  41. package/dist/events/emitter.test.js.map +1 -0
  42. package/dist/events/index.d.ts +4 -0
  43. package/dist/events/index.d.ts.map +1 -0
  44. package/dist/events/index.js +4 -0
  45. package/dist/events/index.js.map +1 -0
  46. package/dist/events/types.d.ts +112 -0
  47. package/dist/events/types.d.ts.map +1 -0
  48. package/dist/events/types.js +9 -0
  49. package/dist/events/types.js.map +1 -0
  50. package/dist/executor.d.ts +4 -0
  51. package/dist/executor.d.ts.map +1 -0
  52. package/dist/executor.js +799 -0
  53. package/dist/executor.js.map +1 -0
  54. package/dist/executor.test.d.ts +2 -0
  55. package/dist/executor.test.d.ts.map +1 -0
  56. package/dist/executor.test.js +1584 -0
  57. package/dist/executor.test.js.map +1 -0
  58. package/dist/index.d.ts +9 -0
  59. package/dist/index.d.ts.map +1 -0
  60. package/dist/index.js +15 -0
  61. package/dist/index.js.map +1 -0
  62. package/dist/secrets/factory.d.ts +121 -0
  63. package/dist/secrets/factory.d.ts.map +1 -0
  64. package/dist/secrets/factory.js +137 -0
  65. package/dist/secrets/factory.js.map +1 -0
  66. package/dist/secrets/index.d.ts +14 -0
  67. package/dist/secrets/index.d.ts.map +1 -0
  68. package/dist/secrets/index.js +18 -0
  69. package/dist/secrets/index.js.map +1 -0
  70. package/dist/secrets/providers/aws.d.ts +63 -0
  71. package/dist/secrets/providers/aws.d.ts.map +1 -0
  72. package/dist/secrets/providers/aws.js +110 -0
  73. package/dist/secrets/providers/aws.js.map +1 -0
  74. package/dist/secrets/providers/env.d.ts +36 -0
  75. package/dist/secrets/providers/env.d.ts.map +1 -0
  76. package/dist/secrets/providers/env.js +37 -0
  77. package/dist/secrets/providers/env.js.map +1 -0
  78. package/dist/secrets/providers/index.d.ts +7 -0
  79. package/dist/secrets/providers/index.d.ts.map +1 -0
  80. package/dist/secrets/providers/index.js +7 -0
  81. package/dist/secrets/providers/index.js.map +1 -0
  82. package/dist/secrets/providers/vault.d.ts +75 -0
  83. package/dist/secrets/providers/vault.d.ts.map +1 -0
  84. package/dist/secrets/providers/vault.js +143 -0
  85. package/dist/secrets/providers/vault.js.map +1 -0
  86. package/dist/secrets/registry.d.ts +39 -0
  87. package/dist/secrets/registry.d.ts.map +1 -0
  88. package/dist/secrets/registry.js +134 -0
  89. package/dist/secrets/registry.js.map +1 -0
  90. package/dist/secrets/resolver.d.ts +45 -0
  91. package/dist/secrets/resolver.d.ts.map +1 -0
  92. package/dist/secrets/resolver.js +188 -0
  93. package/dist/secrets/resolver.js.map +1 -0
  94. package/dist/secrets/secrets.test.d.ts +2 -0
  95. package/dist/secrets/secrets.test.d.ts.map +1 -0
  96. package/dist/secrets/secrets.test.js +317 -0
  97. package/dist/secrets/secrets.test.js.map +1 -0
  98. package/dist/secrets/types.d.ts +70 -0
  99. package/dist/secrets/types.d.ts.map +1 -0
  100. package/dist/secrets/types.js +42 -0
  101. package/dist/secrets/types.js.map +1 -0
  102. package/dist/shared.d.ts +8 -0
  103. package/dist/shared.d.ts.map +1 -0
  104. package/dist/shared.js +30 -0
  105. package/dist/shared.js.map +1 -0
  106. package/dist/test-monitor-types.d.ts +43 -0
  107. package/dist/test-monitor-types.d.ts.map +1 -0
  108. package/dist/test-monitor-types.js +2 -0
  109. package/dist/test-monitor-types.js.map +1 -0
  110. package/dist/test-plan-types.d.ts +43 -0
  111. package/dist/test-plan-types.d.ts.map +1 -0
  112. package/dist/test-plan-types.js +2 -0
  113. package/dist/test-plan-types.js.map +1 -0
  114. package/dist/types.d.ts +93 -0
  115. package/dist/types.d.ts.map +1 -0
  116. package/dist/types.js +3 -0
  117. package/dist/types.js.map +1 -0
  118. package/dist/utils/dates.d.ts +11 -0
  119. package/dist/utils/dates.d.ts.map +1 -0
  120. package/dist/utils/dates.js +13 -0
  121. package/dist/utils/dates.js.map +1 -0
  122. package/package.json +39 -0
  123. package/src/adapters/axios.ts +39 -0
  124. package/src/adapters/index.ts +2 -0
  125. package/src/adapters/stub.ts +47 -0
  126. package/src/events/adapters/README.md +144 -0
  127. package/src/events/adapters/in-memory.test.ts +146 -0
  128. package/src/events/adapters/in-memory.ts +93 -0
  129. package/src/events/adapters/index.ts +9 -0
  130. package/src/events/adapters/kinesis.test.ts +323 -0
  131. package/src/events/adapters/kinesis.ts +211 -0
  132. package/src/events/emitter.test.ts +327 -0
  133. package/src/events/emitter.ts +133 -0
  134. package/src/events/index.ts +3 -0
  135. package/src/events/types.ts +136 -0
  136. package/src/executor.test.ts +1732 -0
  137. package/src/executor.ts +1075 -0
  138. package/src/index.ts +81 -0
  139. package/src/secrets/factory.ts +248 -0
  140. package/src/secrets/index.ts +48 -0
  141. package/src/secrets/providers/aws.ts +178 -0
  142. package/src/secrets/providers/env.ts +66 -0
  143. package/src/secrets/providers/index.ts +15 -0
  144. package/src/secrets/providers/vault.ts +257 -0
  145. package/src/secrets/resolver.ts +269 -0
  146. package/src/secrets/secrets.test.ts +402 -0
  147. package/src/secrets/types.ts +106 -0
  148. package/src/shared.ts +46 -0
  149. package/src/test-monitor-types.ts +49 -0
  150. package/src/types.ts +114 -0
  151. package/src/utils/dates.ts +13 -0
  152. package/tsconfig.json +20 -0
  153. package/vitest.config.ts +14 -0
@@ -0,0 +1,402 @@
1
+ import { describe, it, expect, beforeEach } from "vitest";
2
+ import { isSecretRef, isStringLiteral } from "./types.js";
3
+ import { EnvSecretProvider } from "./providers/env.js";
4
+ import {
5
+ resolveSecretsInMonitor,
6
+ collectSecretsFromMonitor,
7
+ planHasSecrets,
8
+ } from "./resolver.js";
9
+ import { MonitorV1 } from "@griffin-app/griffin-hub-sdk";
10
+
11
+ // Helper to create a secret ref (mirrors the DSL's secret function)
12
+ function createSecretRef(ref: string) {
13
+ return { $secret: { ref } };
14
+ }
15
+
16
+ // Helper to create a string literal (mirrors the schema's StringLiteral)
17
+ function createStringLiteral(value: string) {
18
+ return { $literal: value };
19
+ }
20
+
21
+ describe("Secret Types", () => {
22
+ describe("isSecretRef", () => {
23
+ it("should return true for valid secret refs", () => {
24
+ expect(isSecretRef({ $secret: { ref: "API_KEY" } })).toBe(true);
25
+ expect(
26
+ isSecretRef({
27
+ $secret: { ref: "my-secret", version: "1" },
28
+ }),
29
+ ).toBe(true);
30
+ });
31
+
32
+ it("should return false for non-secret values", () => {
33
+ expect(isSecretRef("string")).toBe(false);
34
+ expect(isSecretRef(123)).toBe(false);
35
+ expect(isSecretRef(null)).toBe(false);
36
+ expect(isSecretRef(undefined)).toBe(false);
37
+ expect(isSecretRef({})).toBe(false);
38
+ expect(isSecretRef({ secret: { ref: "KEY" } })).toBe(false); // wrong key
39
+ expect(isSecretRef({ $literal: "value" })).toBe(false); // string literal
40
+ });
41
+ });
42
+
43
+ describe("isStringLiteral", () => {
44
+ it("should return true for valid string literals", () => {
45
+ expect(isStringLiteral({ $literal: "application/json" })).toBe(true);
46
+ expect(isStringLiteral({ $literal: "" })).toBe(true);
47
+ expect(isStringLiteral({ $literal: "Bearer token-123" })).toBe(true);
48
+ });
49
+
50
+ it("should return false for non-literal values", () => {
51
+ expect(isStringLiteral("string")).toBe(false);
52
+ expect(isStringLiteral(123)).toBe(false);
53
+ expect(isStringLiteral(null)).toBe(false);
54
+ expect(isStringLiteral(undefined)).toBe(false);
55
+ expect(isStringLiteral({})).toBe(false);
56
+ expect(isStringLiteral({ literal: "value" })).toBe(false); // wrong key
57
+ expect(isStringLiteral({ $secret: { ref: "KEY" } })).toBe(false); // secret ref
58
+ });
59
+ });
60
+ });
61
+
62
+ describe("EnvSecretProvider", () => {
63
+ it("should resolve environment variables", async () => {
64
+ const provider = new EnvSecretProvider({
65
+ env: { MY_API_KEY: "test-key-123" },
66
+ });
67
+
68
+ const result = await provider.resolve("MY_API_KEY");
69
+ expect(result).toBe("test-key-123");
70
+ });
71
+
72
+ it("should throw for missing environment variables", async () => {
73
+ const provider = new EnvSecretProvider({ env: {} });
74
+
75
+ await expect(provider.resolve("MISSING_VAR")).rejects.toThrow(/not set/);
76
+ });
77
+
78
+ it("should support prefix", async () => {
79
+ const provider = new EnvSecretProvider({
80
+ env: { APP_API_KEY: "prefixed-value" },
81
+ prefix: "APP_",
82
+ });
83
+
84
+ const result = await provider.resolve("API_KEY");
85
+ expect(result).toBe("prefixed-value");
86
+ });
87
+ });
88
+
89
+ describe("Monitor Secret Resolution", () => {
90
+ const createTestMonitor = (
91
+ headers?: Record<string, unknown>,
92
+ body?: unknown,
93
+ ): MonitorV1 =>
94
+ ({
95
+ id: "test-monitor-1",
96
+ name: "Test Monitor",
97
+ version: "1.0",
98
+ environment: "default",
99
+ project: "test-project",
100
+ frequency: { every: 1, unit: "MINUTE" },
101
+ nodes: [
102
+ {
103
+ id: "endpoint-1",
104
+ type: "HTTP_REQUEST",
105
+ method: "GET",
106
+ path: "/api/test",
107
+ base: "https://api.example.com",
108
+ response_format: "JSON",
109
+ headers,
110
+ body,
111
+ },
112
+ ],
113
+ edges: [
114
+ { from: "__START__", to: "endpoint-1" },
115
+ { from: "endpoint-1", to: "__END__" },
116
+ ],
117
+ }) as MonitorV1;
118
+
119
+ describe("planHasSecrets", () => {
120
+ it("should return false for monitors without secrets or literals", () => {
121
+ const monitor = createTestMonitor({ "Content-Type": "application/json" });
122
+ expect(planHasSecrets(monitor)).toBe(false);
123
+ });
124
+
125
+ it("should return true for monitors with secret refs in headers", () => {
126
+ const monitor = createTestMonitor({
127
+ Authorization: createSecretRef("API_KEY"),
128
+ });
129
+ expect(planHasSecrets(monitor)).toBe(true);
130
+ });
131
+
132
+ it("should return true for monitors with secret refs in body", () => {
133
+ const monitor = createTestMonitor(undefined, {
134
+ token: createSecretRef("TOKEN"),
135
+ });
136
+ expect(planHasSecrets(monitor)).toBe(true);
137
+ });
138
+
139
+ it("should return true for monitors with string literals in headers", () => {
140
+ const monitor = createTestMonitor({
141
+ "Content-Type": createStringLiteral("application/json"),
142
+ });
143
+ expect(planHasSecrets(monitor)).toBe(true);
144
+ });
145
+
146
+ it("should return true for monitors with string literals in body", () => {
147
+ const monitor = createTestMonitor(undefined, {
148
+ type: createStringLiteral("test"),
149
+ });
150
+ expect(planHasSecrets(monitor)).toBe(true);
151
+ });
152
+ });
153
+
154
+ describe("collectSecretsFromMonitor", () => {
155
+ it("should collect secrets from headers", () => {
156
+ const monitor = createTestMonitor({
157
+ Authorization: createSecretRef("API_KEY"),
158
+ "X-Custom": createSecretRef("custom-secret"),
159
+ });
160
+
161
+ const collected = collectSecretsFromMonitor(monitor);
162
+
163
+ expect(collected.refs).toHaveLength(2);
164
+ expect(collected.refs).toContainEqual({ ref: "API_KEY" });
165
+ expect(collected.refs).toContainEqual({ ref: "custom-secret" });
166
+ });
167
+
168
+ it("should collect secrets from nested body", () => {
169
+ const monitor = createTestMonitor(undefined, {
170
+ auth: {
171
+ token: createSecretRef("TOKEN"),
172
+ },
173
+ items: [{ key: createSecretRef("ITEM_KEY") }],
174
+ });
175
+
176
+ const collected = collectSecretsFromMonitor(monitor);
177
+
178
+ expect(collected.refs).toHaveLength(2);
179
+ expect(collected.refs).toContainEqual({ ref: "TOKEN" });
180
+ expect(collected.refs).toContainEqual({ ref: "ITEM_KEY" });
181
+ });
182
+
183
+ it("should deduplicate secret refs", () => {
184
+ const monitor = createTestMonitor({
185
+ Authorization: createSecretRef("API_KEY"),
186
+ "X-Backup-Auth": createSecretRef("API_KEY"),
187
+ });
188
+
189
+ const collected = collectSecretsFromMonitor(monitor);
190
+
191
+ expect(collected.refs).toHaveLength(1);
192
+ expect(collected.paths).toHaveLength(2);
193
+ });
194
+
195
+ it("should collect string literals from headers", () => {
196
+ const monitor = createTestMonitor({
197
+ "Content-Type": createStringLiteral("application/json"),
198
+ Accept: createStringLiteral("application/xml"),
199
+ });
200
+
201
+ const collected = collectSecretsFromMonitor(monitor);
202
+
203
+ expect(collected.refs).toHaveLength(0);
204
+ expect(collected.literalPaths).toHaveLength(2);
205
+ expect(collected.literalPaths).toContainEqual({
206
+ path: ["nodes", 0, "headers", "Content-Type"],
207
+ value: "application/json",
208
+ });
209
+ expect(collected.literalPaths).toContainEqual({
210
+ path: ["nodes", 0, "headers", "Accept"],
211
+ value: "application/xml",
212
+ });
213
+ });
214
+
215
+ it("should collect both secrets and literals", () => {
216
+ const monitor = createTestMonitor({
217
+ Authorization: createSecretRef("API_KEY"),
218
+ "Content-Type": createStringLiteral("application/json"),
219
+ });
220
+
221
+ const collected = collectSecretsFromMonitor(monitor);
222
+
223
+ expect(collected.refs).toHaveLength(1);
224
+ expect(collected.refs).toContainEqual({ ref: "API_KEY" });
225
+ expect(collected.literalPaths).toHaveLength(1);
226
+ expect(collected.literalPaths).toContainEqual({
227
+ path: ["nodes", 0, "headers", "Content-Type"],
228
+ value: "application/json",
229
+ });
230
+ });
231
+ });
232
+
233
+ describe("resolveSecretsInMonitor", () => {
234
+ it("should resolve secrets in headers", async () => {
235
+ const monitor = createTestMonitor({
236
+ Authorization: createSecretRef("API_KEY"),
237
+ "Content-Type": "application/json",
238
+ });
239
+
240
+ const provider = new EnvSecretProvider({
241
+ env: { API_KEY: "Bearer secret-token" },
242
+ });
243
+
244
+ const resolved = await resolveSecretsInMonitor(monitor, provider);
245
+
246
+ const endpoint = resolved.nodes[0];
247
+ if (endpoint.type !== "HTTP_REQUEST") {
248
+ throw new Error("HttpRequest not found");
249
+ }
250
+ expect(endpoint.headers?.Authorization).toBe("Bearer secret-token");
251
+ expect(endpoint.headers?.["Content-Type"]).toBe("application/json");
252
+ });
253
+
254
+ it("should resolve secrets in body", async () => {
255
+ const monitor = createTestMonitor(undefined, {
256
+ token: createSecretRef("TOKEN"),
257
+ data: "plain-value",
258
+ });
259
+
260
+ const provider = new EnvSecretProvider({
261
+ env: { TOKEN: "resolved-token" },
262
+ });
263
+
264
+ const resolved = await resolveSecretsInMonitor(monitor, provider);
265
+
266
+ const endpoint = resolved.nodes[0];
267
+ if (endpoint.type !== "HTTP_REQUEST") {
268
+ throw new Error("HttpRequest not found");
269
+ }
270
+ expect((endpoint.body as { token: string }).token).toBe("resolved-token");
271
+ expect((endpoint.body as { data: string }).data).toBe("plain-value");
272
+ });
273
+
274
+ it("should not modify original monitor", async () => {
275
+ const monitor = createTestMonitor({
276
+ Authorization: createSecretRef("API_KEY"),
277
+ });
278
+
279
+ const provider = new EnvSecretProvider({
280
+ env: { API_KEY: "secret" },
281
+ });
282
+
283
+ const resolved = await resolveSecretsInMonitor(monitor, provider);
284
+
285
+ // Original should still have secret ref
286
+ const originalEndpoint = monitor.nodes[0];
287
+ if (originalEndpoint.type !== "HTTP_REQUEST") {
288
+ throw new Error("HttpRequest not found");
289
+ }
290
+ expect(originalEndpoint.headers?.Authorization).toEqual(
291
+ createSecretRef("API_KEY"),
292
+ );
293
+
294
+ // Resolved should have string
295
+ const resolvedEndpoint = resolved.nodes[0];
296
+ if (resolvedEndpoint.type !== "HTTP_REQUEST") {
297
+ throw new Error("HttpRequest not found");
298
+ }
299
+ expect(resolvedEndpoint.headers?.Authorization).toBe("secret");
300
+ });
301
+
302
+ it("should throw when no provider configured", async () => {
303
+ const monitor = createTestMonitor({
304
+ Authorization: createSecretRef("API_KEY"),
305
+ });
306
+
307
+ await expect(resolveSecretsInMonitor(monitor, null)).rejects.toThrow(
308
+ /Monitor contains secret references but no secret provider was provided/,
309
+ );
310
+ });
311
+
312
+ it("should throw for missing secret", async () => {
313
+ const monitor = createTestMonitor({
314
+ Authorization: createSecretRef("MISSING_KEY"),
315
+ });
316
+
317
+ const provider = new EnvSecretProvider({ env: {} });
318
+
319
+ await expect(resolveSecretsInMonitor(monitor, provider)).rejects.toThrow(
320
+ /not set/,
321
+ );
322
+ });
323
+
324
+ it("should unwrap string literals in headers", async () => {
325
+ const monitor = createTestMonitor({
326
+ "Content-Type": createStringLiteral("application/json"),
327
+ Accept: createStringLiteral("application/xml"),
328
+ });
329
+
330
+ const resolved = await resolveSecretsInMonitor(monitor, null);
331
+
332
+ const endpoint = resolved.nodes[0];
333
+ if (endpoint.type !== "HTTP_REQUEST") {
334
+ throw new Error("HttpRequest not found");
335
+ }
336
+ expect(endpoint.headers?.["Content-Type"]).toBe("application/json");
337
+ expect(endpoint.headers?.Accept).toBe("application/xml");
338
+ });
339
+
340
+ it("should unwrap string literals in body", async () => {
341
+ const monitor = createTestMonitor(undefined, {
342
+ type: createStringLiteral("test-type"),
343
+ data: "plain-value",
344
+ });
345
+
346
+ const resolved = await resolveSecretsInMonitor(monitor, null);
347
+
348
+ const endpoint = resolved.nodes[0];
349
+ if (endpoint.type !== "HTTP_REQUEST") {
350
+ throw new Error("HttpRequest not found");
351
+ }
352
+ expect((endpoint.body as { type: string }).type).toBe("test-type");
353
+ expect((endpoint.body as { data: string }).data).toBe("plain-value");
354
+ });
355
+
356
+ it("should resolve both secrets and literals", async () => {
357
+ const monitor = createTestMonitor({
358
+ Authorization: createSecretRef("API_KEY"),
359
+ "Content-Type": createStringLiteral("application/json"),
360
+ });
361
+
362
+ const provider = new EnvSecretProvider({
363
+ env: { API_KEY: "Bearer secret-token" },
364
+ });
365
+
366
+ const resolved = await resolveSecretsInMonitor(monitor, provider);
367
+
368
+ const endpoint = resolved.nodes[0];
369
+ if (endpoint.type !== "HTTP_REQUEST") {
370
+ throw new Error("HttpRequest not found");
371
+ }
372
+ expect(endpoint.headers?.Authorization).toBe("Bearer secret-token");
373
+ expect(endpoint.headers?.["Content-Type"]).toBe("application/json");
374
+ });
375
+
376
+ it("should not modify original monitor with literals", async () => {
377
+ const monitor = createTestMonitor({
378
+ "Content-Type": createStringLiteral("application/json"),
379
+ });
380
+
381
+ const resolved = await resolveSecretsInMonitor(monitor, null);
382
+
383
+ // Original should still have literal wrapper
384
+ const originalEndpoint = monitor.nodes[0];
385
+ if (originalEndpoint.type !== "HTTP_REQUEST") {
386
+ throw new Error("HttpRequest not found");
387
+ }
388
+ expect(originalEndpoint.headers?.["Content-Type"]).toEqual(
389
+ createStringLiteral("application/json"),
390
+ );
391
+
392
+ // Resolved should have unwrapped string
393
+ const resolvedEndpoint = resolved.nodes[0];
394
+ if (resolvedEndpoint.type !== "HTTP_REQUEST") {
395
+ throw new Error("HttpRequest not found");
396
+ }
397
+ expect(resolvedEndpoint.headers?.["Content-Type"]).toBe(
398
+ "application/json",
399
+ );
400
+ });
401
+ });
402
+ });
@@ -0,0 +1,106 @@
1
+ import { StringLiteral } from "@griffin-app/griffin-hub-sdk";
2
+
3
+ /**
4
+ * Data structure for a secret reference as it appears in a monitor.
5
+ */
6
+ export interface SecretRefData {
7
+ ref: string;
8
+ version?: string;
9
+ field?: string;
10
+ }
11
+
12
+ /**
13
+ * Secret reference marker in monitor JSON.
14
+ */
15
+ export interface SecretRef {
16
+ $secret: SecretRefData;
17
+ }
18
+
19
+ /**
20
+ * Options passed to secret resolution.
21
+ */
22
+ export interface SecretResolveOptions {
23
+ version?: string;
24
+ field?: string;
25
+ }
26
+
27
+ /**
28
+ * Interface that all secret providers must implement.
29
+ */
30
+ export interface SecretProvider {
31
+ /** Unique name of the provider (e.g., "env", "aws", "vault") */
32
+ readonly name: string;
33
+
34
+ /**
35
+ * Resolve a single secret reference.
36
+ * @param ref - The secret path/identifier within this provider
37
+ * @param options - Optional version or field extraction
38
+ * @returns The resolved secret value
39
+ * @throws Error if secret cannot be resolved
40
+ */
41
+ resolve(ref: string, options?: SecretResolveOptions): Promise<string>;
42
+
43
+ /**
44
+ * Optional batch resolution for efficiency.
45
+ * Default implementation calls resolve() for each ref.
46
+ */
47
+ resolveMany?(
48
+ refs: Array<{ ref: string; options?: SecretResolveOptions }>,
49
+ ): Promise<Map<string, string>>;
50
+
51
+ /**
52
+ * Optional validation/health check.
53
+ * Called during provider initialization.
54
+ */
55
+ validate?(): Promise<void>;
56
+ }
57
+
58
+ /**
59
+ * Error thrown when secret resolution fails.
60
+ */
61
+ export class SecretResolutionError extends Error {
62
+ public readonly ref: string;
63
+ public readonly cause?: Error;
64
+
65
+ constructor(message: string, details: { ref: string; cause?: unknown }) {
66
+ super(message);
67
+ this.name = "SecretResolutionError";
68
+ this.ref = details.ref;
69
+ if (details.cause instanceof Error) {
70
+ this.cause = details.cause;
71
+ }
72
+ }
73
+ }
74
+
75
+ /**
76
+ * Type guard to check if a value is a secret reference.
77
+ */
78
+ export function isSecretRef(value: unknown): value is SecretRef {
79
+ if (typeof value !== "object" || value === null) {
80
+ return false;
81
+ }
82
+
83
+ const obj = value as Record<string, unknown>;
84
+ if (
85
+ !("$secret" in obj) ||
86
+ typeof obj.$secret !== "object" ||
87
+ obj.$secret === null
88
+ ) {
89
+ return false;
90
+ }
91
+
92
+ const secretData = obj.$secret as Record<string, unknown>;
93
+ return typeof secretData.ref === "string";
94
+ }
95
+
96
+ /**
97
+ * Type guard to check if a value is a string literal.
98
+ */
99
+ export function isStringLiteral(value: unknown): value is StringLiteral {
100
+ if (typeof value !== "object" || value === null) {
101
+ return false;
102
+ }
103
+
104
+ const obj = value as Record<string, unknown>;
105
+ return "$literal" in obj && typeof obj.$literal === "string";
106
+ }
package/src/shared.ts ADDED
@@ -0,0 +1,46 @@
1
+ import {
2
+ TSchema,
3
+ TSchemaOptions,
4
+ TUnsafe,
5
+ Static,
6
+ Unsafe,
7
+ Type,
8
+ } from "typebox";
9
+ import { Value } from "typebox/value";
10
+ import { Memory } from "typebox/system";
11
+
12
+ export function Ref<T extends TSchema>(
13
+ t: T,
14
+ options: TSchemaOptions = {},
15
+ ): TUnsafe<Static<T>> {
16
+ const id = (t as unknown as Record<string, string | undefined>).$id;
17
+ if (!id) {
18
+ throw new Error("missing ID on schema");
19
+ }
20
+ return Unsafe<Static<T>>({ ...t, $ref: id, $id: undefined, ...options });
21
+ }
22
+
23
+ export class TUnionOneOf<Types extends TSchema[] = TSchema[]> extends Type.Base<
24
+ TSchema[]
25
+ > {
26
+ public oneOf: Types;
27
+ constructor(oneOf: Types) {
28
+ super();
29
+ this.oneOf = oneOf;
30
+ }
31
+ }
32
+ export function UnionOneOf(oneOf: TSchema[]): TUnionOneOf {
33
+ return new TUnionOneOf(oneOf);
34
+ }
35
+
36
+ //export interface TUnionOneOf<Types extends TSchema[] = TSchema[]> extends TSchema {
37
+ // '~kind': 'UnionOneOf'
38
+ // //static: { [K in keyof Types]: Static<Types[K]> }[number]
39
+ // oneOf: Types
40
+ //}
41
+ //
42
+ //export function UnionOneOf<Types extends TSchema[]>(oneOf: [...Types], options: TSchemaOptions = {}) {
43
+ // return { ...options, ["~kind"]: 'UnionOneOf', oneOf } as TUnionOneOf<Types>
44
+ //
45
+ // ///return Memory.Create({ '~kind': 'UnionOneOf' }, { oneOf }, options) as never
46
+ //}
@@ -0,0 +1,49 @@
1
+ export type HttpMethod = "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
2
+ export type ResponseFormat = "JSON" | "XML" | "TEXT";
3
+
4
+ export interface HttpRequest {
5
+ id: string;
6
+ type: "HTTP_REQUEST";
7
+ method: HttpMethod;
8
+ path: string;
9
+ response_format: ResponseFormat;
10
+ headers?: Record<string, string>;
11
+ body?: any;
12
+ }
13
+
14
+ export interface WaitNode {
15
+ id: string;
16
+ type: "WAIT";
17
+ duration_ms: number;
18
+ }
19
+
20
+ export interface AssertionNode {
21
+ id: string;
22
+ type: "ASSERTION";
23
+ assertions: Assertion[];
24
+ }
25
+
26
+ export interface Assertion {
27
+ type: string;
28
+ expected?: any;
29
+ actual?: any;
30
+ message?: string;
31
+ }
32
+
33
+ export interface Edge {
34
+ from: string;
35
+ to: string;
36
+ }
37
+
38
+ export interface Frequency {
39
+ every: number;
40
+ unit: "minute" | "hour" | "day";
41
+ }
42
+
43
+ export interface TestMonitor {
44
+ name: string;
45
+ endpoint_host: string;
46
+ frequency?: Frequency;
47
+ nodes: (HttpRequest | WaitNode | AssertionNode)[];
48
+ edges: Edge[];
49
+ }