@crvouga/mockingbird-service-google-maps 1.0.0 → 1.1.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.
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  createRuntime
3
- } from "./chunk-5HEE5U7V.js";
3
+ } from "./chunk-G5DEDMPE.js";
4
4
 
5
5
  // ../../adapters/node/dist/cli.js
6
6
  import { readFile } from "node:fs/promises";
@@ -208,7 +208,9 @@ var formatLog = (format) => {
208
208
  const ns = entry.namespace === "default" ? "" : ` [${entry.namespace}]`;
209
209
  const fault = entry.faultId ? ` fault=${entry.faultId}` : "";
210
210
  const adopted = entry.adopted ? " adopted" : "";
211
- console.log(`${entry.service} ${entry.method} ${entry.path} ${entry.status} ${op} ${entry.durationMs}ms${ns}${fault}${adopted}`);
211
+ const received = entry.request ? ` received=${entry.request.bodyBytes === null ? "?" : `${entry.request.bodyBytes}B`} ct=${entry.request.contentType ?? "-"}${entry.request.transferEncoding ? ` te=${entry.request.transferEncoding}` : ""}` : "";
212
+ const issues = entry.issues?.length ? ` issues=${entry.issues.slice(0, 3).map((issue) => `${issue.path || "$"}: ${issue.message}`).join("; ")}${entry.issues.length > 3 ? ` (+${entry.issues.length - 3})` : ""}` : "";
213
+ console.log(`${entry.service} ${entry.method} ${entry.path} ${entry.status} ${op} ${entry.durationMs}ms${ns}${fault}${adopted}${received}${issues}`);
212
214
  };
213
215
  };
214
216
  var asString = (value) => typeof value === "string" ? value : void 0;
@@ -344,6 +346,7 @@ var serveTarget = {
344
346
  banner: () => [
345
347
  "REST: /maps/api/place/{autocomplete,details,findplacefromtext}/json, /maps/api/geocode/json (?key=)",
346
348
  'web: <script src="<this server>/maps/api/js?key=\u2026&libraries=places">',
349
+ "Address Validation: POST /v1:validateAddress?key= (addressvalidation.googleapis.com)",
347
350
  "namespaces: x-mockingbird-namespace, /ns/<name>/\u2026, or PUT /__admin/credentials {<key>: <ns>}"
348
351
  ]
349
352
  };
@@ -355,4 +358,4 @@ export {
355
358
  createServer2 as createServer,
356
359
  serveTarget
357
360
  };
358
- //# sourceMappingURL=chunk-YHBPY6U2.js.map
361
+ //# sourceMappingURL=chunk-XR7ESTGM.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../adapters/node/src/cli.ts", "../../../adapters/node/src/serve.ts", "../../../adapters/node/src/listen.ts", "../src/server.ts"],
4
+ "sourcesContent": ["import { readFile } from \"node:fs/promises\"\nimport { type ParseArgsConfig, parseArgs } from \"node:util\"\nimport type { RequestLog, ServiceInstance, ServiceRuntime } from \"@crvouga/mockingbird-service\"\nimport { type Listening, listen } from \"./listen.js\"\n\nexport type CliOption = {\n type: \"string\" | \"boolean\"\n description: string\n /** Shown in help; the value placeholder, e.g. `<port>`. */\n value?: string\n default?: string | boolean\n}\n\nexport type CliValues = Record<string, string | boolean | undefined>\n\nexport type CliCommand = {\n summary: string\n usage?: string\n options?: Record<string, CliOption>\n /** Resolves to an exit code; a server command resolves only when it stops. */\n run(values: CliValues, positionals: string[]): Promise<number>\n}\n\nexport type CliSpec = {\n bin: string\n description: string\n commands: Record<string, CliCommand>\n}\n\nconst optionHelp = (options: Record<string, CliOption>): string[] =>\n Object.entries(options).map(([name, option]) => {\n const flag = `--${name}${option.type === \"string\" ? ` ${option.value ?? \"<value>\"}` : \"\"}`\n const fallback = option.default !== undefined ? ` (default: ${String(option.default)})` : \"\"\n return ` ${flag.padEnd(30)} ${option.description}${fallback}`\n })\n\nconst help = (spec: CliSpec): string =>\n [\n `${spec.bin} \u2014 ${spec.description}`,\n \"\",\n \"Usage:\",\n ` ${spec.bin} <command> [options]`,\n \"\",\n \"Commands:\",\n ...Object.entries(spec.commands).map(([name, c]) => ` ${name.padEnd(30)} ${c.summary}`),\n \"\",\n `Run \\`${spec.bin} <command> --help\\` for a command's options.`,\n ].join(\"\\n\")\n\nconst commandHelp = (spec: CliSpec, name: string, command: CliCommand): string =>\n [\n `${spec.bin} ${name} \u2014 ${command.summary}`,\n \"\",\n \"Usage:\",\n ` ${command.usage ?? `${spec.bin} ${name} [options]`}`,\n ...(command.options ? [\"\", \"Options:\", ...optionHelp(command.options)] : []),\n ].join(\"\\n\")\n\n/** Parse `argv` against `spec` and run the chosen command. Resolves to an exit code. */\nexport const runCli = async (spec: CliSpec, argv: string[]): Promise<number> => {\n // Two-word commands (`corpus pull`) win over one-word ones.\n const pair = argv.length >= 2 ? `${argv[0]} ${argv[1]}` : undefined\n const words = pair !== undefined && spec.commands[pair] ? 2 : 1\n const name = words === 2 ? pair : argv[0]\n const rest = argv.slice(words)\n if (name === undefined || name === \"--help\" || name === \"-h\" || name === \"help\") {\n console.log(help(spec))\n return 0\n }\n const command = spec.commands[name]\n if (!command) {\n console.error(`${spec.bin}: unknown command ${JSON.stringify(name)}\\n\\n${help(spec)}`)\n return 2\n }\n if (rest.includes(\"--help\") || rest.includes(\"-h\")) {\n console.log(commandHelp(spec, name, command))\n return 0\n }\n let parsed: { values: CliValues; positionals: string[] }\n try {\n parsed = parseArgs({\n args: rest,\n allowPositionals: true,\n strict: true,\n options: Object.fromEntries(\n Object.entries(command.options ?? {}).map(([key, option]) => [\n key,\n {\n type: option.type,\n ...(option.default !== undefined ? { default: option.default } : {}),\n },\n ]),\n ) as ParseArgsConfig[\"options\"],\n }) as { values: CliValues; positionals: string[] }\n } catch (error) {\n console.error(\n `${spec.bin} ${name}: ${error instanceof Error ? error.message : String(error)}\\n\\n${commandHelp(spec, name, command)}`,\n )\n return 2\n }\n return command.run(parsed.values, parsed.positionals)\n}\n\n// \u2500\u2500 serve \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport type LogFormat = \"pretty\" | \"json\" | \"off\"\n\nexport type CommonServeOptions = {\n adminKey: string | undefined\n seed: string | undefined\n onLog: ((entry: RequestLog) => void) | undefined\n}\n\n/**\n * What a service contributes to `serve`: how to build its runtime from CLI flags,\n * and what to say at startup. Every service's `./server` entry exports one as\n * `serveTarget`, which is also how `serve --config` finds services by name.\n */\nexport type ServeTarget = {\n name: string\n defaultPort: number\n /** Serve flags beyond the common ones. */\n options?: Record<string, CliOption>\n create(\n values: CliValues,\n common: CommonServeOptions,\n ): Promise<ServiceRuntime<ServiceInstance>> | ServiceRuntime<ServiceInstance>\n /** Startup lines after the listen address, e.g. the loaded corpus version. */\n banner?(runtime: ServiceRuntime<ServiceInstance>): string[]\n}\n\nconst COMMON_SERVE_OPTIONS: Record<string, CliOption> = {\n port: { type: \"string\", value: \"<port>\", description: \"Port to listen on\" },\n host: { type: \"string\", value: \"<host>\", description: \"Interface to bind\", default: \"127.0.0.1\" },\n \"admin-key\": {\n type: \"string\",\n value: \"<key>\",\n description: \"Require x-mockingbird-admin-key on /__admin/* (env MOCKINGBIRD_ADMIN_KEY)\",\n },\n seed: { type: \"string\", value: \"<seed>\", description: \"Seed for every random choice\" },\n log: {\n type: \"string\",\n value: \"<pretty|json|off>\",\n description: \"Request log format\",\n default: \"pretty\",\n },\n \"log-requests\": {\n type: \"boolean\",\n description:\n \"One JSON line per request: namespace, operationId, status, ids touched (never bodies). Same as --log json\",\n },\n config: {\n type: \"string\",\n value: \"<file>\",\n description: \"Serve every service in a mockingbird.json config instead\",\n },\n}\n\nconst formatLog = (format: LogFormat) => {\n if (format === \"off\") return undefined\n if (format === \"json\") return (entry: RequestLog) => console.log(JSON.stringify(entry))\n return (entry: RequestLog) => {\n const op = entry.operationId ?? (entry.unmatched ? \"UNMATCHED\" : \"-\")\n const ns = entry.namespace === \"default\" ? \"\" : ` [${entry.namespace}]`\n const fault = entry.faultId ? ` fault=${entry.faultId}` : \"\"\n const adopted = entry.adopted ? \" adopted\" : \"\"\n // A rejection says what arrived (never the body) and why the service refused it.\n const received = entry.request\n ? ` received=${entry.request.bodyBytes === null ? \"?\" : `${entry.request.bodyBytes}B`} ct=${entry.request.contentType ?? \"-\"}${\n entry.request.transferEncoding ? ` te=${entry.request.transferEncoding}` : \"\"\n }`\n : \"\"\n const issues = entry.issues?.length\n ? ` issues=${entry.issues\n .slice(0, 3)\n .map((issue) => `${issue.path || \"$\"}: ${issue.message}`)\n .join(\"; \")}${entry.issues.length > 3 ? ` (+${entry.issues.length - 3})` : \"\"}`\n : \"\"\n console.log(\n `${entry.service} ${entry.method} ${entry.path} ${entry.status} ${op} ${entry.durationMs}ms${ns}${fault}${adopted}${received}${issues}`,\n )\n }\n}\n\nconst asString = (value: string | boolean | undefined): string | undefined =>\n typeof value === \"string\" ? value : undefined\n\n/** A service entry in `mockingbird.json`. */\nexport type ConfigService = {\n port?: number\n host?: string\n adminKey?: string\n seed?: string\n /** Service-specific serve flags, by long name: `{ \"webhook-url\": \"\u2026\" }`. */\n options?: Record<string, string | boolean>\n}\n\nexport type MockingbirdConfig = {\n /** Keyed by service name: `junction` loads `@crvouga/mockingbird-service-junction`. */\n services: Record<string, ConfigService>\n log?: LogFormat\n}\n\nconst loadTarget = async (name: string, own: ServeTarget): Promise<ServeTarget> => {\n if (name === own.name) return own\n const specifier = `@crvouga/mockingbird-service-${name}/server`\n try {\n const mod = (await import(specifier)) as { serveTarget?: ServeTarget }\n if (!mod.serveTarget) throw new Error(`${specifier} exports no serveTarget`)\n return mod.serveTarget\n } catch (error) {\n const reason = error instanceof Error ? error.message : String(error)\n throw new Error(\n `cannot load service \"${name}\": ${reason}. Install @crvouga/mockingbird-service-${name}.`,\n )\n }\n}\n\nconst start = async (\n target: ServeTarget,\n values: CliValues,\n config: { port: number; host: string; adminKey?: string; seed?: string; log: LogFormat },\n): Promise<Listening> => {\n const runtime = await target.create(values, {\n adminKey: config.adminKey,\n seed: config.seed,\n onLog: formatLog(config.log),\n })\n const listening = await listen(runtime, { port: config.port, host: config.host })\n console.log(`${target.name} mock listening on ${listening.url}`)\n console.log(`${target.name} health: GET ${listening.url}/health`)\n console.log(\n `${target.name} admin: ${listening.url}/__admin (${config.adminKey ? \"x-mockingbird-admin-key required\" : \"open \u2014 pass --admin-key to lock\"})`,\n )\n for (const line of target.banner?.(runtime) ?? []) console.log(`${target.name} ${line}`)\n return listening\n}\n\nconst untilSignal = async (servers: Listening[]): Promise<number> =>\n new Promise((resolve) => {\n const stop = async () => {\n await Promise.allSettled(servers.map((s) => s.close()))\n resolve(0)\n }\n process.once(\"SIGINT\", stop)\n process.once(\"SIGTERM\", stop)\n })\n\n/** The standard `serve` command for a service, including multi-service `--config`. */\nexport const serveCommand = (target: ServeTarget): CliCommand => ({\n summary: `Serve the ${target.name} mock over HTTP`,\n options: { ...COMMON_SERVE_OPTIONS, ...target.options },\n async run(values) {\n const log = (\n values[\"log-requests\"] === true ? \"json\" : (asString(values.log) ?? \"pretty\")\n ) as LogFormat\n if (![\"pretty\", \"json\", \"off\"].includes(log)) {\n console.error(`--log must be pretty, json or off (got ${log})`)\n return 2\n }\n const configPath = asString(values.config)\n if (configPath !== undefined) {\n const config = JSON.parse(await readFile(configPath, \"utf8\")) as MockingbirdConfig\n const servers: Listening[] = []\n try {\n for (const [name, entry] of Object.entries(config.services ?? {})) {\n const each = await loadTarget(name, target)\n servers.push(\n await start(each, entry.options ?? {}, {\n port: entry.port ?? each.defaultPort,\n host: entry.host ?? \"127.0.0.1\",\n ...(entry.adminKey !== undefined ? { adminKey: entry.adminKey } : {}),\n ...(entry.seed !== undefined ? { seed: entry.seed } : {}),\n log: config.log ?? log,\n }),\n )\n }\n } catch (error) {\n await Promise.allSettled(servers.map((s) => s.close()))\n console.error(error instanceof Error ? error.message : String(error))\n return 1\n }\n return untilSignal(servers)\n }\n const port = asString(values.port)\n const adminKey = asString(values[\"admin-key\"]) ?? process.env.MOCKINGBIRD_ADMIN_KEY\n const seed = asString(values.seed)\n let listening: Listening\n try {\n listening = await start(target, values, {\n port: port === undefined ? target.defaultPort : Number.parseInt(port, 10),\n host: asString(values.host) ?? \"127.0.0.1\",\n ...(adminKey !== undefined ? { adminKey } : {}),\n ...(seed !== undefined ? { seed } : {}),\n log,\n })\n } catch (error) {\n console.error(error instanceof Error ? error.message : String(error))\n return 1\n }\n return untilSignal([listening])\n },\n})\n", "import { createServer } from \"node:http\"\nimport type { FetchAPI } from \"@crvouga/mockingbird-core\"\n\n/** Options for {@link serve}. */\nexport type NodeServeOptions = {\n port?: number\n host?: string\n}\n\n/**\n * Serve any Mockingbird {@link FetchAPI} over `node:http`.\n * Port defaults to `0`, so the OS assigns an ephemeral port (read from `server.address()`).\n */\nexport const serve = async (api: FetchAPI, options: NodeServeOptions = {}) => {\n const server = createServer(async (req, res) => {\n const chunks: Buffer[] = []\n for await (const chunk of req) {\n chunks.push(Buffer.from(chunk))\n }\n const body = Buffer.concat(chunks)\n const address = server.address()\n const port = typeof address === \"object\" && address !== null ? address.port : undefined\n const base = `http://${req.headers.host ?? `localhost:${port ?? 80}`}`\n const raw = req.url ?? \"/\"\n const url = new URL(raw.replace(/^\\/+/, \"/\"), base)\n const method = req.method ?? \"GET\"\n const init: RequestInit = { method, headers: req.headers as Record<string, string> }\n if (method !== \"GET\" && method !== \"HEAD\" && body.length > 0) {\n init.body = body\n }\n const aborted = new AbortController()\n res.once(\"close\", () => {\n if (!res.writableFinished) aborted.abort()\n })\n init.signal = aborted.signal\n const request = new Request(url, init)\n let response: Response\n try {\n response = await api.fetch(request)\n } catch (error) {\n // A `drop` fault: destroy the socket so the client sees the connection die.\n if ((error as { code?: string }).code === \"MOCKINGBIRD_DROP\") {\n req.socket.destroy()\n return\n }\n res.writeHead(500, { \"content-type\": \"application/json\" })\n res.end(\n JSON.stringify({\n error: {\n type: \"mockingbird_internal\",\n message: error instanceof Error ? error.message : String(error),\n },\n }),\n )\n return\n }\n // Headers#entries() joins repeated headers; Set-Cookie must stay one header per cookie.\n const headers: Record<string, string | string[]> = Object.fromEntries(response.headers)\n const cookies = response.headers.getSetCookie()\n if (cookies.length > 0) headers[\"set-cookie\"] = cookies\n if (!response.body) {\n res.writeHead(response.status, headers)\n res.end()\n return\n }\n // Stream the body chunk by chunk: event streams and long-polls must not be buffered.\n res.writeHead(response.status, headers)\n res.flushHeaders()\n const reader = response.body.getReader()\n try {\n for (;;) {\n const { done, value } = await reader.read()\n if (done) break\n if (!res.write(value)) await new Promise<void>((resolve) => res.once(\"drain\", resolve))\n }\n res.end()\n } catch {\n res.destroy()\n } finally {\n reader.releaseLock()\n }\n })\n await new Promise<void>((resolve, reject) => {\n server.once(\"error\", reject)\n server.listen(options.port ?? 0, options.host, resolve)\n })\n return server\n}\n", "import type { Server } from \"node:http\"\nimport type { FetchAPI } from \"@crvouga/mockingbird-core\"\nimport { serve } from \"./serve.js\"\n\nexport type ListenOptions = {\n /** Default `0`: the OS picks a free port. */\n port?: number\n /** Default `127.0.0.1`: a mock should not be reachable off the machine by accident. */\n host?: string\n}\n\n/** A running server, with the address it actually bound. */\nexport type Listening = {\n url: string\n port: number\n host: string\n server: Server\n close(): Promise<void>\n}\n\nexport const listen = async (api: FetchAPI, options: ListenOptions = {}): Promise<Listening> => {\n const host = options.host ?? \"127.0.0.1\"\n const server = await serve(api, { port: options.port ?? 0, host })\n const address = server.address()\n const port = typeof address === \"object\" && address !== null ? address.port : (options.port ?? 0)\n const shown = host.includes(\":\") ? `[${host}]` : host\n return {\n url: `http://${shown}:${port}`,\n port,\n host,\n server,\n close: () =>\n new Promise<void>((resolve, reject) => {\n // Stop accepting first, then drop keep-alive sockets so close() can finish.\n server.close((error) => {\n if (error && (error as { code?: string }).code !== \"ERR_SERVER_NOT_RUNNING\") reject(error)\n else resolve()\n })\n server.closeAllConnections?.()\n }),\n }\n}\n", "/// <reference types=\"node\" />\nimport { type Listening, listen, type ServeTarget } from \"@crvouga/mockingbird-adapter-node\"\nimport { createRuntime, type GoogleMapsRuntime, type GoogleMapsRuntimeOptions } from \"./runtime.js\"\n\n/** Port `mockingbird-google-maps serve` listens on when none is given. */\nexport const DEFAULT_PORT = 8814\n\nexport type GoogleMapsServerOptions = GoogleMapsRuntimeOptions & {\n /** Default `0`: the OS picks a free port. */\n port?: number\n /** Default `127.0.0.1`. */\n host?: string\n}\n\nexport type GoogleMapsServer = Listening & { runtime: GoogleMapsRuntime }\n\n/** Serve the Google Maps mock over `node:http`. */\nexport const createServer = async (\n options: GoogleMapsServerOptions = {},\n): Promise<GoogleMapsServer> => {\n const { port, host, ...rest } = options\n const runtime = createRuntime(rest)\n const listening = await listen(runtime, {\n port: port ?? 0,\n ...(host !== undefined ? { host } : {}),\n })\n return { ...listening, runtime }\n}\n\nconst text = (value: string | boolean | undefined) =>\n typeof value === \"string\" ? value : undefined\n\n/** How `serve` (and `serve --config`) builds the Google Maps mock from flags. */\nexport const serveTarget: ServeTarget = {\n name: \"google-maps\",\n defaultPort: DEFAULT_PORT,\n options: {\n \"api-key\": {\n type: \"string\",\n value: \"<key[,key\u2026]>\",\n description: \"Accept only these API keys (the app's PLACES_KEY); default: any non-empty key\",\n },\n \"public-url\": {\n type: \"string\",\n value: \"<url>\",\n description:\n \"Origin the Maps JavaScript shim calls back to, when it differs from the request's\",\n },\n },\n create: (values, common) => {\n const keys = text(values[\"api-key\"])\n const publicUrl = text(values[\"public-url\"])\n return createRuntime({\n settings: {\n ...(keys\n ? {\n keys: keys\n .split(\",\")\n .map((k) => k.trim())\n .filter(Boolean),\n }\n : {}),\n ...(publicUrl ? { publicUrl } : {}),\n },\n ...(common.adminKey !== undefined ? { adminKey: common.adminKey } : {}),\n ...(common.seed !== undefined ? { seed: common.seed } : {}),\n ...(common.onLog ? { onLog: common.onLog } : {}),\n })\n },\n banner: () => [\n \"REST: /maps/api/place/{autocomplete,details,findplacefromtext}/json, /maps/api/geocode/json (?key=)\",\n 'web: <script src=\"<this server>/maps/api/js?key=\u2026&libraries=places\">',\n \"Address Validation: POST /v1:validateAddress?key= (addressvalidation.googleapis.com)\",\n \"namespaces: x-mockingbird-namespace, /ns/<name>/\u2026, or PUT /__admin/credentials {<key>: <ns>}\",\n ],\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["createServer"]
7
+ }
package/dist/cli.js CHANGED
@@ -3,8 +3,8 @@ import {
3
3
  runCli,
4
4
  serveCommand,
5
5
  serveTarget
6
- } from "./chunk-YHBPY6U2.js";
7
- import "./chunk-5HEE5U7V.js";
6
+ } from "./chunk-XR7ESTGM.js";
7
+ import "./chunk-G5DEDMPE.js";
8
8
 
9
9
  // src/cli.ts
10
10
  var code = await runCli(
package/dist/index.d.ts CHANGED
@@ -228,82 +228,6 @@ type FaultRegistry = {
228
228
  take(candidate: FaultCandidate): Promise<FaultHit[]>;
229
229
  };
230
230
 
231
- /** One handled request, as the structured log sees it. */
232
- type RequestLog = {
233
- service: string;
234
- namespace: string;
235
- operationId: string | undefined;
236
- method: string;
237
- path: string;
238
- status: number;
239
- durationMs: number;
240
- /** True when the path matched no operation in the contract. */
241
- unmatched: boolean;
242
- /** Set when a fault rule produced the response. */
243
- faultId?: string;
244
- /** Resource ids the handler touched (`userId`, `orderId`, …), when the service reports them. */
245
- ids?: Record<string, string>;
246
- /** Set when the service created a resource the request referred to but that did not exist. */
247
- adopted?: boolean;
248
- };
249
- type MetricsReport = {
250
- requests: number;
251
- /** Counts keyed `<operationId> <status>`. */
252
- byOperation: Record<string, number>;
253
- /**
254
- * Paths that matched no operation, most frequent first.
255
- *
256
- * This is the early-warning signal: a consumer calling something the mock does
257
- * not implement shows up here as a count, before it fails a suite as a 404.
258
- */
259
- unmatched: {
260
- method: string;
261
- path: string;
262
- count: number;
263
- }[];
264
- faults: number;
265
- totalDurationMs: number;
266
- };
267
- type Metrics = {
268
- record(entry: RequestLog): void;
269
- report(): MetricsReport;
270
- reset(): void;
271
- };
272
-
273
- /** One journal entry: a request log stamped with when (on the mock clock) it was handled. */
274
- type JournalEntry = RequestLog & {
275
- at: string;
276
- };
277
- type JournalQuery = {
278
- /** Only this namespace. Omit for every namespace, oldest first across all of them. */
279
- namespace?: string;
280
- operationId?: string;
281
- status?: number;
282
- /** Only entries at or after this instant (epoch ms). */
283
- since?: number;
284
- /** At most this many, the most recent kept. */
285
- limit?: number;
286
- };
287
- type Journal = {
288
- readonly size: number;
289
- record(entry: JournalEntry): void;
290
- list(query?: JournalQuery): JournalEntry[];
291
- /** Forget one namespace's entries, or every namespace's. */
292
- clear(namespace?: string): void;
293
- };
294
-
295
- /** Credential → namespace mapping behind `PUT /__admin/credentials`. */
296
- type CredentialRegistry = {
297
- set(credential: string, namespace: string): void;
298
- get(credential: string): string | undefined;
299
- remove(credential: string): boolean;
300
- clear(): void;
301
- entries(): {
302
- credential: string;
303
- namespace: string;
304
- }[];
305
- };
306
-
307
231
  /** A stable identifier for a point in a {@link Timeline}. */
308
232
  type CheckpointId = string;
309
233
  /** An immutable node in a timeline's checkpoint DAG. */
@@ -394,6 +318,123 @@ interface FetchAPI {
394
318
  fetch(request: Request): Promise<Response>;
395
319
  }
396
320
 
321
+ /** Options every provider constructor accepts. */
322
+ type APIOptions = {
323
+ /** Sync SQLite client. Defaults to `@crvouga/mockingbird-service-sqlite`. */
324
+ sqlite?: SqliteClient$1;
325
+ /** Clock used for `created`-style fields. Default `Date.now`. */
326
+ now?: () => number;
327
+ /**
328
+ * Storage namespace for this instance's records. Instances sharing one SQLite
329
+ * client stay isolated when their namespaces differ. Defaults to the service name.
330
+ */
331
+ namespace?: string;
332
+ };
333
+
334
+ /**
335
+ * One problem with a request body, at a dotted path (`patient.address.city`, `items.0.sku`).
336
+ * `kind` is `media_type` when the body is present but its media type is not one the operation
337
+ * accepts (a `415` on most vendors), `syntax` when it could not be decoded, `required` when
338
+ * the operation needs a body and none arrived, and `schema` for a contract violation.
339
+ */
340
+ type BodyIssue = {
341
+ path: string;
342
+ message: string;
343
+ kind?: "media_type" | "syntax" | "required" | "schema";
344
+ };
345
+
346
+ /**
347
+ * What the mock received from a request it rejected, so a 4xx can be attributed after the
348
+ * fact (an empty body, a missing `content-type`, a schema violation) without logging the body.
349
+ */
350
+ type RejectedRequest = {
351
+ /** The raw `content-type` header, or `null` when the request sent none. */
352
+ contentType: string | null;
353
+ /** Bytes of body received from `content-length` (`0` for none), or `null` when the client sent no length (a streamed / chunked body). */
354
+ bodyBytes: number | null;
355
+ /** The raw `transfer-encoding` header (`chunked`), or `null`. */
356
+ transferEncoding: string | null;
357
+ };
358
+ /** One handled request, as the structured log sees it. */
359
+ type RequestLog = {
360
+ service: string;
361
+ namespace: string;
362
+ operationId: string | undefined;
363
+ method: string;
364
+ path: string;
365
+ status: number;
366
+ durationMs: number;
367
+ /** True when the path matched no operation in the contract. */
368
+ unmatched: boolean;
369
+ /** Set when a fault rule produced the response. */
370
+ faultId?: string;
371
+ /** Resource ids the handler touched (`userId`, `orderId`, …), when the service reports them. */
372
+ ids?: Record<string, string>;
373
+ /** Set when the service created a resource the request referred to but that did not exist. */
374
+ adopted?: boolean;
375
+ /** Set on a rejection (status ≥ 400) the mock produced itself, never on a scripted fault. */
376
+ request?: RejectedRequest;
377
+ /** The validation issues behind a rejection, when the service found any. */
378
+ issues?: BodyIssue[];
379
+ };
380
+ type MetricsReport = {
381
+ requests: number;
382
+ /** Counts keyed `<operationId> <status>`. */
383
+ byOperation: Record<string, number>;
384
+ /**
385
+ * Paths that matched no operation, most frequent first.
386
+ *
387
+ * This is the early-warning signal: a consumer calling something the mock does
388
+ * not implement shows up here as a count, before it fails a suite as a 404.
389
+ */
390
+ unmatched: {
391
+ method: string;
392
+ path: string;
393
+ count: number;
394
+ }[];
395
+ faults: number;
396
+ totalDurationMs: number;
397
+ };
398
+ type Metrics = {
399
+ record(entry: RequestLog): void;
400
+ report(): MetricsReport;
401
+ reset(): void;
402
+ };
403
+
404
+ /** One journal entry: a request log stamped with when (on the mock clock) it was handled. */
405
+ type JournalEntry = RequestLog & {
406
+ at: string;
407
+ };
408
+ type JournalQuery = {
409
+ /** Only this namespace. Omit for every namespace, oldest first across all of them. */
410
+ namespace?: string;
411
+ operationId?: string;
412
+ status?: number;
413
+ /** Only entries at or after this instant (epoch ms). */
414
+ since?: number;
415
+ /** At most this many, the most recent kept. */
416
+ limit?: number;
417
+ };
418
+ type Journal = {
419
+ readonly size: number;
420
+ record(entry: JournalEntry): void;
421
+ list(query?: JournalQuery): JournalEntry[];
422
+ /** Forget one namespace's entries, or every namespace's. */
423
+ clear(namespace?: string): void;
424
+ };
425
+
426
+ /** Credential → namespace mapping behind `PUT /__admin/credentials`. */
427
+ type CredentialRegistry = {
428
+ set(credential: string, namespace: string): void;
429
+ get(credential: string): string | undefined;
430
+ remove(credential: string): boolean;
431
+ clear(): void;
432
+ entries(): {
433
+ credential: string;
434
+ namespace: string;
435
+ }[];
436
+ };
437
+
397
438
  /**
398
439
  * A point-in-time copy of everything a service namespace holds.
399
440
  *
@@ -544,19 +585,6 @@ type ServiceRuntime<T extends ServiceInstance> = FetchAPI & {
544
585
  timeline(namespace?: string): Timeline<ServiceTimelineState>;
545
586
  };
546
587
 
547
- /** Options every provider constructor accepts. */
548
- type APIOptions = {
549
- /** Sync SQLite client. Defaults to `@crvouga/mockingbird-service-sqlite`. */
550
- sqlite?: SqliteClient$1;
551
- /** Clock used for `created`-style fields. Default `Date.now`. */
552
- now?: () => number;
553
- /**
554
- * Storage namespace for this instance's records. Instances sharing one SQLite
555
- * client stay isolated when their namespaces differ. Defaults to the service name.
556
- */
557
- namespace?: string;
558
- };
559
-
560
588
  /** Bind values accepted by Mockingbird's SQLite port (matches sqlite-mem / better-sqlite3). */
561
589
  type SqliteValue = null | number | bigint | string | Uint8Array | boolean;
562
590
  /** Mutation counters returned by {@link SqliteStatement.run}. */
@@ -586,6 +614,47 @@ interface SqliteClient {
586
614
  transaction<T>(fn: () => T): T;
587
615
  }
588
616
 
617
+ /**
618
+ * The Address Validation API (`POST https://addressvalidation.googleapis.com/v1:validateAddress`)
619
+ * over the same corpus as Places and Geocoding.
620
+ *
621
+ * Shapes follow Google's reference: `ValidationResult` (`verdict`, `address`, `geocode`,
622
+ * `uspsData`) and `google.rpc.Status` errors. Google encodes responses as proto3 JSON, so
623
+ * `false` booleans and empty lists are omitted rather than sent.
624
+ *
625
+ * - https://developers.google.com/maps/documentation/address-validation/reference/rest/v1/TopLevel/validateAddress
626
+ * - https://developers.google.com/maps/documentation/address-validation/reference/rest/v1/ValidationResult
627
+ * - https://developers.google.com/maps/documentation/address-validation/understand-response
628
+ *
629
+ * Every answer is a pure function of the request and the namespace's corpus. A street the corpus
630
+ * holds (or one synthesized in a corpus city, as for Autocomplete) is a delivery point: DPV `Y`,
631
+ * with a deterministic ZIP+4. Admin rows can pin any part of the verdict (`validation`).
632
+ */
633
+ type Granularity = "SUB_PREMISE" | "PREMISE" | "PREMISE_PROXIMITY" | "BLOCK" | "ROUTE" | "OTHER";
634
+ type PossibleNextAction = "FIX" | "CONFIRM_ADD_SUBPREMISES" | "CONFIRM" | "ACCEPT";
635
+ type DpvConfirmation = "Y" | "N" | "S" | "D";
636
+ /** Per-row controls set through `PUT /__admin/corpus`: pin the verdict a test needs. */
637
+ type ValidationOverrides = {
638
+ granularity?: Granularity;
639
+ addressComplete?: boolean;
640
+ possibleNextAction?: PossibleNextAction;
641
+ dpvConfirmation?: DpvConfirmation;
642
+ unconfirmedComponentTypes?: string[];
643
+ /** USPS refuses the address: `uspsData.errorMessage`, and no DPV code. */
644
+ uspsErrorMessage?: string;
645
+ /** An apartment building: a missing unit is `D`, an unknown one `S`. */
646
+ multiUnit?: boolean;
647
+ /** The units USPS knows for a `multiUnit` row. Absent: every unit is known. */
648
+ units?: string[];
649
+ };
650
+ /** A `google.rpc.Status` error, as Google's API front end sends it. */
651
+ type GoogleApiError = {
652
+ code: number;
653
+ message: string;
654
+ status: string;
655
+ details?: Record<string, unknown>[];
656
+ };
657
+
589
658
  /**
590
659
  * The addresses the mock knows, matching our QA fixtures: every row of `ROUTING_ZIP_CORPUS`
591
660
  * (`packages/qa/src/world/gen/addresses.ts`, ≥1 real ZIP per state + DC, ids kept stable), the
@@ -604,6 +673,8 @@ type CorpusAddress = {
604
673
  county: string;
605
674
  lat: number;
606
675
  lng: number;
676
+ /** Address Validation controls for this row (admin rows only): pin a verdict, flag units. */
677
+ validation?: ValidationOverrides;
607
678
  };
608
679
  declare const DEFAULT_CORPUS: readonly CorpusAddress[];
609
680
  /** The Phoenix AZ at-home demo member (`ADDRESS_AT_HOME_PHLEBOTOMY`). */
@@ -856,10 +927,10 @@ type OpenAPIDocument = {
856
927
  };
857
928
 
858
929
  declare const document: OpenAPIDocument;
859
- type OperationId = "PlaceAutocomplete" | "PlaceDetails" | "Geocode" | "FindPlaceFromText" | "MapsJavaScriptApi";
860
- type SupportedOperationId = "PlaceAutocomplete" | "PlaceDetails" | "Geocode" | "FindPlaceFromText" | "MapsJavaScriptApi";
861
- declare const operationIds: readonly ["PlaceAutocomplete", "PlaceDetails", "Geocode", "FindPlaceFromText", "MapsJavaScriptApi"];
862
- declare const supportedOperationIds: readonly ["PlaceAutocomplete", "PlaceDetails", "Geocode", "FindPlaceFromText", "MapsJavaScriptApi"];
930
+ type OperationId = "PlaceAutocomplete" | "PlaceDetails" | "Geocode" | "FindPlaceFromText" | "MapsJavaScriptApi" | "ValidateAddress";
931
+ type SupportedOperationId = "PlaceAutocomplete" | "PlaceDetails" | "Geocode" | "FindPlaceFromText" | "MapsJavaScriptApi" | "ValidateAddress";
932
+ declare const operationIds: readonly ["PlaceAutocomplete", "PlaceDetails", "Geocode", "FindPlaceFromText", "MapsJavaScriptApi", "ValidateAddress"];
933
+ declare const supportedOperationIds: readonly ["PlaceAutocomplete", "PlaceDetails", "Geocode", "FindPlaceFromText", "MapsJavaScriptApi", "ValidateAddress"];
863
934
 
864
935
  /**
865
936
  * The Maps JavaScript API shim served at `GET /maps/api/js`: `google.maps.places.*` and
@@ -906,6 +977,12 @@ declare const GOOGLE_MAPS_NAMESPACE = "google-maps";
906
977
  /** Google's own wording for the two ways a key is refused. */
907
978
  declare const MISSING_KEY_MESSAGE = "You must use an API key to authenticate each request to Google Maps Platform APIs. For additional information, please refer to http://g.co/dev/maps-no-account";
908
979
  declare const INVALID_KEY_MESSAGE = "The provided API key is invalid. ";
980
+ /**
981
+ * The Address Validation API is a Google Cloud API (`addressvalidation.googleapis.com`): keys
982
+ * are refused with a `google.rpc.Status` envelope and an HTTP error, not a 200 with `status`.
983
+ */
984
+ declare const MISSING_API_KEY_MESSAGE = "The request is missing a valid API key.";
985
+ declare const INVALID_API_KEY_MESSAGE = "API key not valid. Please pass a valid API key.";
909
986
  /** Google statuses the mock answers with (always HTTP 200, as Google does). */
910
987
  type GoogleStatus = "OK" | "ZERO_RESULTS" | "INVALID_REQUEST" | "OVER_QUERY_LIMIT" | "REQUEST_DENIED" | "UNKNOWN_ERROR" | "NOT_FOUND";
911
988
  type GoogleMapsAPIOptions = APIOptions & {
@@ -916,7 +993,10 @@ type GoogleMapsAPIOptions = APIOptions & {
916
993
  /** The public namespace name, so the JS shim can call back into the same namespace. */
917
994
  publicNamespace?: string;
918
995
  };
919
- /** The API key a request carries (`?key=`): how keys map to namespaces. */
996
+ /**
997
+ * The API key a request carries (`?key=`, or the `X-Goog-Api-Key` header Google Cloud APIs
998
+ * also accept): how keys map to namespaces.
999
+ */
920
1000
  declare const keyCredential: (request: Request) => string | undefined;
921
1001
  /**
922
1002
  * Stateless-over-a-corpus mock of Google Places Autocomplete / Details / Find Place, the
@@ -936,6 +1016,9 @@ declare class GoogleMapsAPI implements FetchAPI$1 {
936
1016
  private status;
937
1017
  /** Key check and status-effect faults, before every web-service call. */
938
1018
  private gate;
1019
+ /** Key check for the Google Cloud-style Address Validation API. */
1020
+ private cloudGate;
1021
+ private validate;
939
1022
  private noted;
940
1023
  private autocomplete;
941
1024
  private details;
@@ -944,5 +1027,5 @@ declare class GoogleMapsAPI implements FetchAPI$1 {
944
1027
  private script;
945
1028
  }
946
1029
 
947
- export { DEFAULT_CORPUS, GOOGLE_MAPS_NAMESPACE, GOOGLE_MAPS_PRESETS, GoogleMapsAPI, INVALID_KEY_MESSAGE, MISSING_KEY_MESSAGE, PHOENIX_DEMO_ADDRESS, STATE_NAMES, corpusPlaceId, createRuntime, document, keyCredential, mapsJavaScript, normalize, operationIds, supportedOperationIds };
948
- export type { AddressComponent, CorpusAddress, FetchAPI$1 as FetchAPI, GoogleMapsAPIOptions, GoogleMapsRuntime, GoogleMapsRuntimeOptions, GoogleStatus, OperationId, Place, Prediction, Settings, ShimOptions, SqliteClient, SupportedOperationId };
1030
+ export { DEFAULT_CORPUS, GOOGLE_MAPS_NAMESPACE, GOOGLE_MAPS_PRESETS, GoogleMapsAPI, INVALID_API_KEY_MESSAGE, INVALID_KEY_MESSAGE, MISSING_API_KEY_MESSAGE, MISSING_KEY_MESSAGE, PHOENIX_DEMO_ADDRESS, STATE_NAMES, corpusPlaceId, createRuntime, document, keyCredential, mapsJavaScript, normalize, operationIds, supportedOperationIds };
1031
+ export type { AddressComponent, CorpusAddress, DpvConfirmation, FetchAPI$1 as FetchAPI, GoogleApiError, GoogleMapsAPIOptions, GoogleMapsRuntime, GoogleMapsRuntimeOptions, GoogleStatus, Granularity, OperationId, Place, PossibleNextAction, Prediction, Settings, ShimOptions, SqliteClient, SupportedOperationId, ValidationOverrides };
package/dist/index.js CHANGED
@@ -3,7 +3,9 @@ import {
3
3
  GOOGLE_MAPS_NAMESPACE,
4
4
  GOOGLE_MAPS_PRESETS,
5
5
  GoogleMapsAPI,
6
+ INVALID_API_KEY_MESSAGE,
6
7
  INVALID_KEY_MESSAGE,
8
+ MISSING_API_KEY_MESSAGE,
7
9
  MISSING_KEY_MESSAGE,
8
10
  PHOENIX_DEMO_ADDRESS,
9
11
  STATE_NAMES,
@@ -15,13 +17,15 @@ import {
15
17
  normalize,
16
18
  operationIds,
17
19
  supportedOperationIds
18
- } from "./chunk-5HEE5U7V.js";
20
+ } from "./chunk-G5DEDMPE.js";
19
21
  export {
20
22
  DEFAULT_CORPUS,
21
23
  GOOGLE_MAPS_NAMESPACE,
22
24
  GOOGLE_MAPS_PRESETS,
23
25
  GoogleMapsAPI,
26
+ INVALID_API_KEY_MESSAGE,
24
27
  INVALID_KEY_MESSAGE,
28
+ MISSING_API_KEY_MESSAGE,
25
29
  MISSING_KEY_MESSAGE,
26
30
  PHOENIX_DEMO_ADDRESS,
27
31
  STATE_NAMES,