@sponson/sim 0.0.0-stage → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +9 -2
  3. package/dist/auth.d.ts +60 -0
  4. package/dist/auth.js +195 -0
  5. package/dist/auth.js.map +1 -0
  6. package/dist/bin.d.ts +2 -0
  7. package/dist/bin.js +16 -0
  8. package/dist/bin.js.map +1 -0
  9. package/dist/chaos.d.ts +74 -0
  10. package/dist/chaos.js +80 -0
  11. package/dist/chaos.js.map +1 -0
  12. package/dist/cli.d.ts +29 -0
  13. package/dist/cli.js +99 -0
  14. package/dist/cli.js.map +1 -0
  15. package/dist/demo.d.ts +39 -0
  16. package/dist/demo.js +168 -0
  17. package/dist/demo.js.map +1 -0
  18. package/dist/graphql.d.ts +22 -0
  19. package/dist/graphql.js +36 -0
  20. package/dist/graphql.js.map +1 -0
  21. package/dist/index.d.ts +46 -0
  22. package/dist/index.js +54 -0
  23. package/dist/index.js.map +1 -0
  24. package/dist/provider.d.ts +138 -0
  25. package/dist/provider.js +68 -0
  26. package/dist/provider.js.map +1 -0
  27. package/dist/routes/algolia.d.ts +37 -0
  28. package/dist/routes/algolia.js +83 -0
  29. package/dist/routes/algolia.js.map +1 -0
  30. package/dist/routes/aws.d.ts +43 -0
  31. package/dist/routes/aws.js +211 -0
  32. package/dist/routes/aws.js.map +1 -0
  33. package/dist/routes/clerk.d.ts +33 -0
  34. package/dist/routes/clerk.js +85 -0
  35. package/dist/routes/clerk.js.map +1 -0
  36. package/dist/routes/cloudflare.d.ts +80 -0
  37. package/dist/routes/cloudflare.js +176 -0
  38. package/dist/routes/cloudflare.js.map +1 -0
  39. package/dist/routes/digitalocean.d.ts +58 -0
  40. package/dist/routes/digitalocean.js +158 -0
  41. package/dist/routes/digitalocean.js.map +1 -0
  42. package/dist/routes/fly.d.ts +22 -0
  43. package/dist/routes/fly.js +113 -0
  44. package/dist/routes/fly.js.map +1 -0
  45. package/dist/routes/launchdarkly.d.ts +98 -0
  46. package/dist/routes/launchdarkly.js +213 -0
  47. package/dist/routes/launchdarkly.js.map +1 -0
  48. package/dist/routes/neon.d.ts +73 -0
  49. package/dist/routes/neon.js +184 -0
  50. package/dist/routes/neon.js.map +1 -0
  51. package/dist/routes/netlify.d.ts +126 -0
  52. package/dist/routes/netlify.js +347 -0
  53. package/dist/routes/netlify.js.map +1 -0
  54. package/dist/routes/planetscale.d.ts +95 -0
  55. package/dist/routes/planetscale.js +234 -0
  56. package/dist/routes/planetscale.js.map +1 -0
  57. package/dist/routes/railway.d.ts +24 -0
  58. package/dist/routes/railway.js +112 -0
  59. package/dist/routes/railway.js.map +1 -0
  60. package/dist/routes/rest.d.ts +138 -0
  61. package/dist/routes/rest.js +462 -0
  62. package/dist/routes/rest.js.map +1 -0
  63. package/dist/routes/supabase.d.ts +103 -0
  64. package/dist/routes/supabase.js +234 -0
  65. package/dist/routes/supabase.js.map +1 -0
  66. package/dist/routes/vercel.d.ts +114 -0
  67. package/dist/routes/vercel.js +370 -0
  68. package/dist/routes/vercel.js.map +1 -0
  69. package/dist/routes/xata.d.ts +57 -0
  70. package/dist/routes/xata.js +115 -0
  71. package/dist/routes/xata.js.map +1 -0
  72. package/dist/server.d.ts +4 -0
  73. package/dist/server.js +189 -0
  74. package/dist/server.js.map +1 -0
  75. package/dist/state.d.ts +102 -0
  76. package/dist/state.js +121 -0
  77. package/dist/state.js.map +1 -0
  78. package/package.json +51 -3
package/dist/server.js ADDED
@@ -0,0 +1,189 @@
1
+ /**
2
+ * HTTP front of the fake cloud. The core here does what is common to every provider: control endpoints (`/_…`),
3
+ * bearer-token check, latency, chaos (hang / drop / fail) and the write log. Everything under `/<provider>/` is
4
+ * answered by that provider's `routes/<provider>.ts`; each of those files lists the assumptions about the real
5
+ * API it encodes. The one cross-provider assumption:
6
+ * S1. 429s carry `Retry-After` in seconds (chaos `retry_after`).
7
+ */
8
+ import { createHash } from "node:crypto";
9
+ import { createServer } from "node:http";
10
+ import { setTimeout as sleep } from "node:timers/promises";
11
+ import { Reply, route, router } from "./provider.js";
12
+ import { providerEntries } from "./state.js";
13
+ /** `/<provider>` → its routes, one entry per registered provider. */
14
+ const ROUTES = Object.fromEntries(providerEntries().map(([name, p]) => [`/${name}`, (state, req) => p.routes(state, state.provider(name), req)]));
15
+ const WRITE_METHODS = new Set(["POST", "PATCH", "DELETE", "PUT"]);
16
+ /** The sim's HTTP server over `state`, not yet listening. Most callers want `startSim`. */
17
+ export function createSimServer(state) {
18
+ return createServer((req, res) => {
19
+ handle(state, req, res).catch((e) => {
20
+ send(res, 500, { error: e instanceof Error ? e.message : String(e) });
21
+ });
22
+ });
23
+ }
24
+ async function handle(state, req, res) {
25
+ const method = (req.method ?? "GET").toUpperCase();
26
+ const url = new URL(req.url ?? "/", "http://sim.local");
27
+ const raw = await readBody(req);
28
+ // Control endpoints take JSON whatever the client says: `curl -d '{…}'` labels a JSON body as a form.
29
+ const contentType = req.headers["content-type"] ?? "";
30
+ const form = !url.pathname.startsWith("/_") && contentType.startsWith("application/x-www-form-urlencoded");
31
+ // XML bodies reach the provider as text: only it knows the document.
32
+ const xml = !url.pathname.startsWith("/_") && /^[\w.+-]+\/([\w.-]+\+)?xml\b/.test(contentType);
33
+ const parsed = xml ? { body: raw } : form ? { body: parseForm(raw) } : parseJson(raw);
34
+ if (!parsed)
35
+ return send(res, 400, { error: "invalid json" });
36
+ const { body } = parsed;
37
+ if (url.pathname.startsWith("/_")) {
38
+ const r = control(state, state, { method, url, path: url.pathname, body });
39
+ return send(res, r.status, r.body);
40
+ }
41
+ // Any non-empty credential is accepted unless a provider's seed configures real ones; missing-token bugs must
42
+ // surface as 401.
43
+ const verdict = authorized(state, req.headers, { method, url, target: req.url ?? "/", raw });
44
+ if (verdict instanceof Reply)
45
+ return send(res, verdict.status, verdict.body, verdict.headers);
46
+ if (!verdict)
47
+ return send(res, 401, { error: "unauthorized" });
48
+ await serveProvider(state, req, res, { method, url, raw, body });
49
+ }
50
+ const WRITES = Object.fromEntries(providerEntries().flatMap(([name, p]) => (p.isWrite ? [[`/${name}`, (state, req) => p.isWrite(req, state.provider(name))]] : [])));
51
+ /** Whether a POST, PUT, PATCH or DELETE goes into the write log. */
52
+ function changesState(state, method, url, body) {
53
+ const prefix = url.pathname.match(/^\/[^/]+/)?.[0] ?? "";
54
+ const check = WRITES[prefix];
55
+ return check ? check(state, { method, path: url.pathname.slice(prefix.length) || "/", body }) : true;
56
+ }
57
+ /** `/<provider>` → how that provider takes its credential, when not as a bearer token. */
58
+ const AUTH = Object.fromEntries(providerEntries().flatMap(([name, p]) => (p.authorized ? [[`/${name}`, (state, h, req) => p.authorized(h, state.provider(name), req, state)]] : [])));
59
+ /** The credential check of the provider the path belongs to; a non-empty bearer token by default. */
60
+ function authorized(state, headers, req) {
61
+ const prefix = req.url.pathname.match(/^\/[^/]+/)?.[0] ?? "";
62
+ const check = AUTH[prefix];
63
+ return check ? check(state, headers, req) : /^Bearer\s+\S+$/.test(headers.authorization ?? "");
64
+ }
65
+ /** An authenticated provider request: latency, chaos, the write log, then the provider's routes. */
66
+ async function serveProvider(state, req, res, { method, url, raw, body }) {
67
+ if (state.chaos.latency_ms > 0)
68
+ await sleep(state.chaos.latency_ms);
69
+ const isWrite = WRITE_METHODS.has(method) && changesState(state, method, url, body);
70
+ const action = state.chaosFor(method, url.pathname);
71
+ // Held: performed (and logged) only once released, like a request stuck in the provider's queue.
72
+ if (action?.kind === "hold")
73
+ await state.hold();
74
+ const entry = { at: new Date().toISOString(), method, path: url.pathname, bodyHash: sha256(raw) };
75
+ if (action?.kind === "hang" || action?.kind === "fail") {
76
+ // Never performed. A hang is accepted and never answered (sim.close() tears the socket down).
77
+ if (isWrite)
78
+ state.writes.push({ ...entry, failed: true });
79
+ if (action.kind === "fail")
80
+ send(res, action.status, { error: "chaos" }, action.headers);
81
+ return;
82
+ }
83
+ const logged = { ...entry };
84
+ if (isWrite)
85
+ state.writes.push(logged);
86
+ const r = dispatch(state, method, url, body, headersOf(req));
87
+ // A refused write (409, 423, 404, …) did not change anything.
88
+ if (r.status >= 400)
89
+ logged.failed = true;
90
+ if (action?.kind === "drop") {
91
+ // The request was performed; the client never hears about it.
92
+ req.socket.destroy();
93
+ return;
94
+ }
95
+ send(res, r.status, r.body, r.headers);
96
+ }
97
+ /** `/<provider>/…` → that provider's routes, with the prefix stripped from the path (`/<provider>` alone is `/`). */
98
+ function dispatch(state, method, url, body, headers) {
99
+ const prefix = url.pathname.match(/^\/[^/]+(?=\/|$)/)?.[0];
100
+ const routes = prefix === undefined ? undefined : ROUTES[prefix];
101
+ if (prefix === undefined || !routes)
102
+ return new Reply(404, { error: "not found" });
103
+ return routes(state, { method, url, path: url.pathname.slice(prefix.length) || "/", body, headers });
104
+ }
105
+ /** The control endpoints tests and scenarios drive the sim with. */
106
+ const control = router([
107
+ route("POST", "/_chaos", ({ state, body }) => {
108
+ try {
109
+ return new Reply(200, state.applyChaos((body ?? {})));
110
+ }
111
+ catch (e) {
112
+ return new Reply(400, { error: e.message });
113
+ }
114
+ }),
115
+ route("GET", "/_state", ({ state }) => new Reply(200, state.snapshot())),
116
+ route("GET", "/_writes", ({ state, url }) => {
117
+ const since = Number(url.searchParams.get("since") ?? 0);
118
+ return new Reply(200, state.writes.slice(Number.isFinite(since) ? since : 0));
119
+ }),
120
+ route("POST", "/_release", ({ state }) => new Reply(200, { released: state.release() })),
121
+ route("POST", "/_reset", ({ state, body }) => {
122
+ state.reset(body);
123
+ return new Reply(200, { ok: true });
124
+ }),
125
+ ], () => new Reply(404, { error: "not found" }));
126
+ /** The request's headers as strings (multi-valued ones joined with `, `). */
127
+ function headersOf(req) {
128
+ const out = {};
129
+ for (const [k, v] of Object.entries(req.headers))
130
+ if (v !== undefined)
131
+ out[k] = Array.isArray(v) ? v.join(", ") : v;
132
+ return out;
133
+ }
134
+ /** A request body: empty is `undefined`; null when it is not JSON. */
135
+ /**
136
+ * A form body (`application/x-www-form-urlencoded`) as the object it encodes, bracket-nested the way Stripe reads
137
+ * it: `a[b]=1&c[0]=x` is `{ a: { b: "1" }, c: ["x"] }`. Every value is a string.
138
+ */
139
+ function parseForm(raw) {
140
+ const out = {};
141
+ for (const [name, value] of new URLSearchParams(raw)) {
142
+ const path = name.split(/\[|\]\[|\]/).filter((s) => s !== "");
143
+ let cur = out;
144
+ path.forEach((seg, i) => {
145
+ const last = i === path.length - 1;
146
+ const slot = cur;
147
+ if (last)
148
+ slot[seg] = value;
149
+ else
150
+ cur = (slot[seg] ??= /^\d+$/.test(path[i + 1]) ? [] : {});
151
+ });
152
+ }
153
+ return out;
154
+ }
155
+ function parseJson(raw) {
156
+ if (raw.length === 0)
157
+ return { body: undefined };
158
+ try {
159
+ return { body: JSON.parse(raw) };
160
+ }
161
+ catch {
162
+ return null;
163
+ }
164
+ }
165
+ function readBody(req) {
166
+ return new Promise((resolve, reject) => {
167
+ const chunks = [];
168
+ req.on("data", (c) => chunks.push(c));
169
+ req.on("end", () => resolve(Buffer.concat(chunks).toString("utf8")));
170
+ req.on("error", reject);
171
+ });
172
+ }
173
+ function send(res, status, body, headers = {}) {
174
+ if (status === 204) {
175
+ // No Content means no body, not the JSON `null`.
176
+ res.writeHead(204, headers);
177
+ res.end();
178
+ return;
179
+ }
180
+ // A provider that answers in another format (S3's XML) sets its own content type and passes the text.
181
+ const raw = typeof body === "string" && headers["content-type"] !== undefined;
182
+ const text = raw ? body : JSON.stringify(body ?? null);
183
+ res.writeHead(status, { "content-type": "application/json", ...headers, "content-length": Buffer.byteLength(text) });
184
+ res.end(text);
185
+ }
186
+ function sha256(s) {
187
+ return createHash("sha256").update(s).digest("hex");
188
+ }
189
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,102 @@
1
+ /**
2
+ * In-memory state for the fake cloud: the core (chaos, write log, ids) plus one state object per registered
3
+ * provider, reachable as `state.<provider>` (e.g. `state.vercel.projects`).
4
+ */
5
+ import { type ChaosAction, type ChaosConfig, type ChaosRequest } from "./chaos.js";
6
+ import type { ProviderSim, SimCore } from "./provider.js";
7
+ /**
8
+ * Every simulated provider, by URL prefix (`/<name>/…`), drift prefix (`<name>.…`), seed key and state key.
9
+ * Order matters only for determinism: resets seed providers (and so allocate ids) in this order.
10
+ */
11
+ export declare const PROVIDERS: {
12
+ vercel: ProviderSim<import("./routes/vercel.js").VercelState, import("./routes/vercel.js").VercelSeed>;
13
+ neon: ProviderSim<import("./routes/neon.js").NeonState, import("./routes/neon.js").NeonSeed>;
14
+ clerk: ProviderSim<import("./routes/clerk.js").ClerkState, import("./routes/clerk.js").ClerkSeed>;
15
+ planetscale: ProviderSim<import("./routes/planetscale.js").PlanetscaleState, import("./routes/planetscale.js").PlanetscaleSeed>;
16
+ launchdarkly: ProviderSim<import("./routes/launchdarkly.js").LaunchdarklyState, import("./routes/launchdarkly.js").LaunchdarklySeed>;
17
+ rest: ProviderSim<import("./routes/rest.js").RestState, import("./routes/rest.js").RestSeed>;
18
+ supabase: ProviderSim<import("./routes/supabase.js").SupabaseState, import("./routes/supabase.js").SupabaseSeed>;
19
+ netlify: ProviderSim<import("./routes/netlify.js").NetlifyState, import("./routes/netlify.js").NetlifySeed>;
20
+ cloudflare: ProviderSim<import("./routes/cloudflare.js").CloudflareState, import("./routes/cloudflare.js").CloudflareSeed>;
21
+ aws: ProviderSim<import("./routes/aws.js").AwsState, import("./routes/aws.js").AwsSeed>;
22
+ railway: ProviderSim<import("./routes/railway.js").RailwayState, import("./routes/railway.js").RailwaySeed>;
23
+ fly: ProviderSim<import("./routes/fly.js").FlyState, import("./routes/fly.js").FlySeed>;
24
+ algolia: ProviderSim<import("./routes/algolia.js").AlgoliaState, import("./routes/algolia.js").AlgoliaSeed>;
25
+ xata: ProviderSim<import("./routes/xata.js").XataState, import("./routes/xata.js").XataSeed>;
26
+ digitalocean: ProviderSim<import("./routes/digitalocean.js").DigitaloceanState, import("./routes/digitalocean.js").DigitaloceanSeed>;
27
+ };
28
+ type Providers = typeof PROVIDERS;
29
+ /** A simulated provider's name: its URL prefix, seed key and state key. */
30
+ export type ProviderName = keyof Providers;
31
+ type StateOf<P> = P extends ProviderSim<infer S, infer _D> ? S : never;
32
+ type SeedOf<P> = P extends ProviderSim<infer _S, infer D> ? D : never;
33
+ /** The state object of every provider, by name (`state.vercel`, `state.neon`, ...). */
34
+ export type ProviderStates = {
35
+ [K in ProviderName]: StateOf<Providers[K]>;
36
+ };
37
+ /**
38
+ * Initial state for a sim (or a reset): per-provider seeds and optional chaos. Providers it does not name keep
39
+ * their defaults.
40
+ */
41
+ export type SimSeed = {
42
+ /** Chaos applied right after the seed, e.g. `{ page_size: 2 }`. */
43
+ chaos?: Partial<ChaosConfig>;
44
+ } & {
45
+ [K in ProviderName]?: SeedOf<Providers[K]>;
46
+ };
47
+ /** The demo projects every provider starts with when a seed does not name it. */
48
+ export declare const DEFAULT_SEED: SimSeed;
49
+ /**
50
+ * One write the sim received, for assertions such as "the second apply wrote nothing". Bodies are hashed, never
51
+ * stored.
52
+ */
53
+ export interface WriteLogEntry {
54
+ at: string;
55
+ method: string;
56
+ path: string;
57
+ bodyHash: string;
58
+ /** The write changed nothing: failed by chaos, or refused by the provider (4xx/5xx). */
59
+ failed?: true;
60
+ }
61
+ /** The registry as the core sees it: names and type-erased providers. */
62
+ export declare function providerEntries(): Array<[ProviderName, ProviderSim<unknown, unknown>]>;
63
+ /**
64
+ * The whole fake cloud in memory: chaos, the write log, and each provider's state as `state.<provider>`. Tests
65
+ * seed and inspect it directly. The per-provider fields are declared by this interface and assigned in `reset`.
66
+ */
67
+ export interface SimState extends ProviderStates {
68
+ }
69
+ export declare class SimState implements SimCore {
70
+ chaos: ChaosConfig;
71
+ writes: WriteLogEntry[];
72
+ private seq;
73
+ private held;
74
+ constructor(seed?: Partial<SimSeed>);
75
+ nextId(prefix: string): string;
76
+ /** The state object of one provider. */
77
+ provider(name: ProviderName): unknown;
78
+ /**
79
+ * Clear everything (state, chaos, writes) and apply a seed. A partial seed replaces only the providers it names;
80
+ * the others keep their defaults, so `{ clerk: {...} }` still has the demo Vercel and Neon projects.
81
+ */
82
+ reset(seed?: Partial<SimSeed> | null): void;
83
+ /** Merge a chaos request; drift is applied to state right away and not kept. */
84
+ applyChaos(req: ChaosRequest): ChaosConfig;
85
+ /**
86
+ * Drift: change provider state behind the client's back, like a human in a console. The key is
87
+ * `<provider>.<rest>`, or `<provider>:<project>.<rest>` to limit it to one project; each provider documents
88
+ * the `<rest>` forms it accepts in its `drift`. Values are a new value, "delete", or "recreate" (delete and
89
+ * re-create with the same name and a new id).
90
+ */
91
+ applyDrift(key: string, value: string): void;
92
+ /** Requests currently held by chaos `hold_next`. */
93
+ get heldCount(): number;
94
+ /** Park the calling request until `release()`. */
95
+ hold(): Promise<void>;
96
+ /** Let every held request proceed; returns how many there were. */
97
+ release(): number;
98
+ /** Decide what chaos does to this request, consuming one count when it does something. */
99
+ chaosFor(method: string, path: string): ChaosAction | null;
100
+ snapshot(): unknown;
101
+ }
102
+ export {};
package/dist/state.js ADDED
@@ -0,0 +1,121 @@
1
+ /**
2
+ * In-memory state for the fake cloud: the core (chaos, write log, ids) plus one state object per registered
3
+ * provider, reachable as `state.<provider>` (e.g. `state.vercel.projects`).
4
+ */
5
+ import { CHAOS_KEYS, chaosFor, defaultChaos } from "./chaos.js";
6
+ import { clerkSim } from "./routes/clerk.js";
7
+ import { neonSim } from "./routes/neon.js";
8
+ import { restSim } from "./routes/rest.js";
9
+ import { vercelSim } from "./routes/vercel.js";
10
+ import { netlifySim } from "./routes/netlify.js";
11
+ import { planetscaleSim } from "./routes/planetscale.js";
12
+ import { launchdarklySim } from "./routes/launchdarkly.js";
13
+ import { supabaseSim } from "./routes/supabase.js";
14
+ import { awsSim } from "./routes/aws.js";
15
+ import { railwaySim } from "./routes/railway.js";
16
+ import { flySim } from "./routes/fly.js";
17
+ import { cloudflareSim } from "./routes/cloudflare.js";
18
+ import { algoliaSim } from "./routes/algolia.js";
19
+ import { xataSim } from "./routes/xata.js";
20
+ import { digitaloceanSim } from "./routes/digitalocean.js";
21
+ /**
22
+ * Every simulated provider, by URL prefix (`/<name>/…`), drift prefix (`<name>.…`), seed key and state key.
23
+ * Order matters only for determinism: resets seed providers (and so allocate ids) in this order.
24
+ */
25
+ export const PROVIDERS = { vercel: vercelSim, neon: neonSim, clerk: clerkSim, planetscale: planetscaleSim, launchdarkly: launchdarklySim, rest: restSim, supabase: supabaseSim, netlify: netlifySim, cloudflare: cloudflareSim, aws: awsSim, railway: railwaySim, fly: flySim, algolia: algoliaSim, xata: xataSim, digitalocean: digitaloceanSim };
26
+ /** The demo projects every provider starts with when a seed does not name it. */
27
+ export const DEFAULT_SEED = Object.fromEntries(Object.entries(PROVIDERS).map(([name, p]) => [name, p.defaultSeed]));
28
+ /** The registry as the core sees it: names and type-erased providers. */
29
+ export function providerEntries() {
30
+ return Object.entries(PROVIDERS);
31
+ }
32
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging
33
+ export class SimState {
34
+ chaos = defaultChaos();
35
+ writes = [];
36
+ seq = 0;
37
+ held = [];
38
+ constructor(seed) {
39
+ this.reset(seed);
40
+ }
41
+ nextId(prefix) {
42
+ this.seq += 1;
43
+ return `${prefix}${this.seq}`;
44
+ }
45
+ /** The state object of one provider. */
46
+ provider(name) {
47
+ return this[name];
48
+ }
49
+ /**
50
+ * Clear everything (state, chaos, writes) and apply a seed. A partial seed replaces only the providers it names;
51
+ * the others keep their defaults, so `{ clerk: {...} }` still has the demo Vercel and Neon projects.
52
+ */
53
+ reset(seed) {
54
+ const s = seed ?? {};
55
+ this.release();
56
+ this.chaos = defaultChaos();
57
+ this.writes = [];
58
+ this.seq = 0;
59
+ const states = this;
60
+ for (const [name, p] of providerEntries())
61
+ states[name] = p.reset(this, name in s ? s[name] : p.defaultSeed);
62
+ if (s.chaos)
63
+ this.applyChaos(s.chaos);
64
+ }
65
+ /** Merge a chaos request; drift is applied to state right away and not kept. */
66
+ applyChaos(req) {
67
+ const { drift, ...rest } = req;
68
+ // The request arrives as JSON over HTTP: its keys are checked here, not by the type.
69
+ const entries = Object.entries(rest);
70
+ for (const [k, v] of entries) {
71
+ // A misspelt key would silently leave the test running without its chaos.
72
+ if (!CHAOS_KEYS.has(k))
73
+ throw new Error(`unknown chaos key: ${k} (known: ${[...CHAOS_KEYS].join(", ")}, drift)`);
74
+ if (v !== undefined)
75
+ Object.assign(this.chaos, { [k]: v });
76
+ }
77
+ if (drift)
78
+ for (const [key, value] of Object.entries(drift))
79
+ this.applyDrift(key, value);
80
+ return this.chaos;
81
+ }
82
+ /**
83
+ * Drift: change provider state behind the client's back, like a human in a console. The key is
84
+ * `<provider>.<rest>`, or `<provider>:<project>.<rest>` to limit it to one project; each provider documents
85
+ * the `<rest>` forms it accepts in its `drift`. Values are a new value, "delete", or "recreate" (delete and
86
+ * re-create with the same name and a new id).
87
+ */
88
+ applyDrift(key, value) {
89
+ const m = key.match(/^([a-z][a-z0-9_-]*)(?::([^.]+))?\.(.+)$/);
90
+ const entry = m ? providerEntries().find(([name]) => name === m[1]) : undefined;
91
+ if (!m || !entry)
92
+ throw new Error(`unknown drift key: ${key}`);
93
+ const [name, p] = entry;
94
+ if (!p.drift(this, this.provider(name), { key, rest: m[3], value, ...(m[2] !== undefined ? { only: m[2] } : {}) }))
95
+ throw new Error(`unknown drift key: ${key}`);
96
+ }
97
+ /** Requests currently held by chaos `hold_next`. */
98
+ get heldCount() {
99
+ return this.held.length;
100
+ }
101
+ /** Park the calling request until `release()`. */
102
+ hold() {
103
+ return new Promise((resolve) => this.held.push(resolve));
104
+ }
105
+ /** Let every held request proceed; returns how many there were. */
106
+ release() {
107
+ const held = this.held;
108
+ this.held = [];
109
+ for (const go of held)
110
+ go();
111
+ return held.length;
112
+ }
113
+ /** Decide what chaos does to this request, consuming one count when it does something. */
114
+ chaosFor(method, path) {
115
+ return chaosFor(this.chaos, method, path);
116
+ }
117
+ snapshot() {
118
+ return { ...Object.fromEntries(providerEntries().map(([name]) => [name, this.provider(name)])), chaos: this.chaos };
119
+ }
120
+ }
121
+ //# sourceMappingURL=state.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state.js","sourceRoot":"","sources":["../src/state.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,54 @@
1
1
  {
2
2
  "name": "@sponson/sim",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.5.0",
4
+ "description": "Local fake cloud for Sponson: Vercel, Neon and Clerk API subsets with chaos injection.",
5
+ "keywords": [
6
+ "sponson",
7
+ "fake-cloud",
8
+ "api-simulator",
9
+ "chaos-testing",
10
+ "vercel",
11
+ "neon",
12
+ "clerk",
13
+ "testing"
14
+ ],
15
+ "license": "Apache-2.0",
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "git+https://github.com/MishaBear94/Sponson.git",
19
+ "directory": "packages/sim"
20
+ },
21
+ "engines": {
22
+ "node": ">=22"
23
+ },
24
+ "type": "module",
25
+ "main": "./dist/index.js",
26
+ "types": "./dist/index.d.ts",
27
+ "exports": {
28
+ ".": {
29
+ "types": "./dist/index.d.ts",
30
+ "default": "./dist/index.js"
31
+ }
32
+ },
33
+ "bin": {
34
+ "sponson-sim": "./dist/bin.js"
35
+ },
36
+ "dependencies": {},
37
+ "files": [
38
+ "dist",
39
+ "!dist/.tsbuildinfo",
40
+ "README.md",
41
+ "LICENSE"
42
+ ],
43
+ "publishConfig": {
44
+ "access": "public",
45
+ "provenance": true
46
+ },
47
+ "homepage": "https://sponson.mintlify.site",
48
+ "bugs": {
49
+ "url": "https://github.com/MishaBear94/Sponson/issues"
50
+ },
51
+ "scripts": {
52
+ "build": "tsc -b"
53
+ }
6
54
  }