@crvouga/mockingbird-service-google-maps 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.
- package/CHANGELOG.md +5 -0
- package/README.md +151 -0
- package/dist/chunk-5HEE5U7V.js +3393 -0
- package/dist/chunk-5HEE5U7V.js.map +7 -0
- package/dist/chunk-YHBPY6U2.js +358 -0
- package/dist/chunk-YHBPY6U2.js.map +7 -0
- package/dist/cli.js +19 -0
- package/dist/cli.js.map +7 -0
- package/dist/index.d.ts +948 -0
- package/dist/index.js +37 -0
- package/dist/index.js.map +7 -0
- package/dist/server.d.ts +1209 -0
- package/dist/server.js +12 -0
- package/dist/server.js.map +7 -0
- package/package.json +89 -0
|
@@ -0,0 +1,3393 @@
|
|
|
1
|
+
// ../core/dist/clock.js
|
|
2
|
+
var createClock = (source = Date.now) => {
|
|
3
|
+
let offsetMs = 0;
|
|
4
|
+
let frozenAt;
|
|
5
|
+
const now = () => frozenAt ?? source() + offsetMs;
|
|
6
|
+
return {
|
|
7
|
+
now,
|
|
8
|
+
set: (epochMs) => {
|
|
9
|
+
if (frozenAt !== void 0)
|
|
10
|
+
frozenAt = epochMs;
|
|
11
|
+
else
|
|
12
|
+
offsetMs = epochMs - source();
|
|
13
|
+
},
|
|
14
|
+
advance: (deltaMs) => {
|
|
15
|
+
if (frozenAt !== void 0)
|
|
16
|
+
frozenAt += deltaMs;
|
|
17
|
+
else
|
|
18
|
+
offsetMs += deltaMs;
|
|
19
|
+
},
|
|
20
|
+
freeze: () => {
|
|
21
|
+
frozenAt = now();
|
|
22
|
+
},
|
|
23
|
+
unfreeze: () => {
|
|
24
|
+
if (frozenAt === void 0)
|
|
25
|
+
return;
|
|
26
|
+
offsetMs = frozenAt - source();
|
|
27
|
+
frozenAt = void 0;
|
|
28
|
+
},
|
|
29
|
+
reset: () => {
|
|
30
|
+
offsetMs = 0;
|
|
31
|
+
frozenAt = void 0;
|
|
32
|
+
},
|
|
33
|
+
state: () => ({ now: now(), frozen: frozenAt !== void 0, offsetMs })
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
// ../core/dist/collection.js
|
|
38
|
+
var Collection = class {
|
|
39
|
+
sqlite;
|
|
40
|
+
namespace;
|
|
41
|
+
name;
|
|
42
|
+
constructor(sqlite, namespace, name) {
|
|
43
|
+
this.sqlite = sqlite;
|
|
44
|
+
this.namespace = namespace;
|
|
45
|
+
this.name = name;
|
|
46
|
+
}
|
|
47
|
+
bumpCollectionSeq() {
|
|
48
|
+
const row2 = this.sqlite.prepare("SELECT value FROM mockingbird_sequences WHERE namespace = ? AND name = ? AND kind = 'collection'").get(this.namespace, this.name);
|
|
49
|
+
const next = (row2?.value ?? 0) + 1;
|
|
50
|
+
this.sqlite.prepare(`INSERT INTO mockingbird_sequences (namespace, name, kind, value) VALUES (?, ?, 'collection', ?)
|
|
51
|
+
ON CONFLICT(namespace, name, kind) DO UPDATE SET value = excluded.value`).run(this.namespace, this.name, next);
|
|
52
|
+
return next;
|
|
53
|
+
}
|
|
54
|
+
nextSequence() {
|
|
55
|
+
return this.sqlite.transaction(() => this.bumpCollectionSeq());
|
|
56
|
+
}
|
|
57
|
+
get(id) {
|
|
58
|
+
const row2 = this.sqlite.prepare("SELECT value FROM mockingbird_records WHERE namespace = ? AND collection = ? AND id = ?").get(this.namespace, this.name, id);
|
|
59
|
+
if (!row2)
|
|
60
|
+
return void 0;
|
|
61
|
+
return JSON.parse(row2.value).value;
|
|
62
|
+
}
|
|
63
|
+
has(id) {
|
|
64
|
+
const row2 = this.sqlite.prepare("SELECT 1 AS ok FROM mockingbird_records WHERE namespace = ? AND collection = ? AND id = ?").get(this.namespace, this.name, id);
|
|
65
|
+
return row2 !== void 0;
|
|
66
|
+
}
|
|
67
|
+
/** Insert a new record, assigning it the next sequence number. */
|
|
68
|
+
insert(id, value) {
|
|
69
|
+
return this.sqlite.transaction(() => {
|
|
70
|
+
const seq = this.bumpCollectionSeq();
|
|
71
|
+
const stored = { seq, value };
|
|
72
|
+
this.sqlite.prepare(`INSERT INTO mockingbird_records (namespace, collection, id, seq, value)
|
|
73
|
+
VALUES (?, ?, ?, ?, ?)
|
|
74
|
+
ON CONFLICT(namespace, collection, id) DO UPDATE SET seq = excluded.seq, value = excluded.value`).run(this.namespace, this.name, id, seq, JSON.stringify(stored));
|
|
75
|
+
return stored;
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
/** Replace an existing record's value, keeping its position. */
|
|
79
|
+
update(id, value) {
|
|
80
|
+
return this.sqlite.transaction(() => {
|
|
81
|
+
const row2 = this.sqlite.prepare("SELECT seq, value FROM mockingbird_records WHERE namespace = ? AND collection = ? AND id = ?").get(this.namespace, this.name, id);
|
|
82
|
+
if (!row2)
|
|
83
|
+
return void 0;
|
|
84
|
+
const stored = { seq: row2.seq, value };
|
|
85
|
+
this.sqlite.prepare("UPDATE mockingbird_records SET value = ? WHERE namespace = ? AND collection = ? AND id = ?").run(JSON.stringify(stored), this.namespace, this.name, id);
|
|
86
|
+
return stored;
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
delete(id) {
|
|
90
|
+
const result = this.sqlite.prepare("DELETE FROM mockingbird_records WHERE namespace = ? AND collection = ? AND id = ?").run(this.namespace, this.name, id);
|
|
91
|
+
return result.changes > 0;
|
|
92
|
+
}
|
|
93
|
+
/** How many records the collection holds, without reading them. */
|
|
94
|
+
count() {
|
|
95
|
+
const row2 = this.sqlite.prepare("SELECT COUNT(*) AS n FROM mockingbird_records WHERE namespace = ? AND collection = ?").get(this.namespace, this.name);
|
|
96
|
+
return Number(row2?.n ?? 0);
|
|
97
|
+
}
|
|
98
|
+
list(options = {}) {
|
|
99
|
+
const rows = this.sqlite.prepare("SELECT id, seq, value FROM mockingbird_records WHERE namespace = ? AND collection = ?").all(this.namespace, this.name);
|
|
100
|
+
const out = [];
|
|
101
|
+
for (const row2 of rows) {
|
|
102
|
+
const stored = JSON.parse(row2.value);
|
|
103
|
+
if (options.where && !options.where(stored.value, stored.seq))
|
|
104
|
+
continue;
|
|
105
|
+
out.push({ id: row2.id, seq: stored.seq, value: stored.value });
|
|
106
|
+
}
|
|
107
|
+
out.sort((a, b) => options.order === "oldest" ? a.seq - b.seq : b.seq - a.seq);
|
|
108
|
+
return out;
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
// ../core/dist/control.js
|
|
113
|
+
var HEALTH_PATH = "/health";
|
|
114
|
+
var ADMIN_PREFIX = "/__admin";
|
|
115
|
+
var ADMIN_KEY_HEADER = "x-mockingbird-admin-key";
|
|
116
|
+
var NAMESPACE_HEADER = "x-mockingbird-namespace";
|
|
117
|
+
var json = (status, body) => new Response(JSON.stringify(body), {
|
|
118
|
+
status,
|
|
119
|
+
headers: { "content-type": "application/json" }
|
|
120
|
+
});
|
|
121
|
+
var adminError = (status, message) => json(status, { error: { type: "mockingbird_admin", message } });
|
|
122
|
+
var UNITS = {
|
|
123
|
+
ms: 1,
|
|
124
|
+
s: 1e3,
|
|
125
|
+
m: 6e4,
|
|
126
|
+
h: 36e5,
|
|
127
|
+
d: 864e5
|
|
128
|
+
};
|
|
129
|
+
var parseDuration = (value) => {
|
|
130
|
+
if (typeof value === "number" && Number.isFinite(value))
|
|
131
|
+
return value;
|
|
132
|
+
if (typeof value !== "string")
|
|
133
|
+
return void 0;
|
|
134
|
+
const match = /^(-?\d+(?:\.\d+)?)\s*(ms|s|m|h|d)$/.exec(value.trim());
|
|
135
|
+
if (!match)
|
|
136
|
+
return void 0;
|
|
137
|
+
return Number(match[1]) * UNITS[match[2]];
|
|
138
|
+
};
|
|
139
|
+
var parseInstant = (value) => {
|
|
140
|
+
if (typeof value === "number" && Number.isFinite(value))
|
|
141
|
+
return value;
|
|
142
|
+
if (typeof value !== "string")
|
|
143
|
+
return void 0;
|
|
144
|
+
const parsed = Date.parse(value);
|
|
145
|
+
return Number.isNaN(parsed) ? void 0 : parsed;
|
|
146
|
+
};
|
|
147
|
+
var matchRoute = (pattern, path) => {
|
|
148
|
+
const want = pattern.split("/").filter(Boolean);
|
|
149
|
+
const have = path.split("/").filter(Boolean);
|
|
150
|
+
if (want.length !== have.length)
|
|
151
|
+
return void 0;
|
|
152
|
+
const params = {};
|
|
153
|
+
for (let i = 0; i < want.length; i++) {
|
|
154
|
+
const segment = want[i];
|
|
155
|
+
const actual = have[i];
|
|
156
|
+
if (segment.startsWith(":"))
|
|
157
|
+
params[segment.slice(1)] = decodeURIComponent(actual);
|
|
158
|
+
else if (segment !== actual)
|
|
159
|
+
return void 0;
|
|
160
|
+
}
|
|
161
|
+
return params;
|
|
162
|
+
};
|
|
163
|
+
var readJson = async (request) => {
|
|
164
|
+
const text2 = await request.text();
|
|
165
|
+
if (text2.trim() === "")
|
|
166
|
+
return void 0;
|
|
167
|
+
return JSON.parse(text2);
|
|
168
|
+
};
|
|
169
|
+
var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
170
|
+
var createControlPlane = (context) => {
|
|
171
|
+
const snapshots = /* @__PURE__ */ new Map();
|
|
172
|
+
let snapshotCounter = 0;
|
|
173
|
+
const headerNamespace = (request) => request.headers.get(NAMESPACE_HEADER) ?? context.defaultNamespace;
|
|
174
|
+
const adminNamespace = (request, url) => url.searchParams.get("namespace") ?? headerNamespace(request);
|
|
175
|
+
const builtin = {
|
|
176
|
+
"GET /": () => json(200, {
|
|
177
|
+
service: context.name,
|
|
178
|
+
routes: [...Object.keys(builtin), ...Object.keys(context.routes)].sort()
|
|
179
|
+
}),
|
|
180
|
+
"POST /reset": async ({ url, namespace }) => {
|
|
181
|
+
const target = url.searchParams.get("all") === "1" ? "*" : namespace;
|
|
182
|
+
await context.reset(target);
|
|
183
|
+
return json(200, { status: "ok", reset: target === "*" ? context.namespaces() : [target] });
|
|
184
|
+
},
|
|
185
|
+
"GET /namespaces": () => json(200, { default: context.defaultNamespace, namespaces: context.namespaces() }),
|
|
186
|
+
"GET /clock": () => json(200, context.clock.state()),
|
|
187
|
+
"POST /clock": ({ body }) => {
|
|
188
|
+
if (!isRecord(body))
|
|
189
|
+
return adminError(400, "expected a JSON object");
|
|
190
|
+
if (body.reset === true)
|
|
191
|
+
context.clock.reset();
|
|
192
|
+
if (body.set !== void 0) {
|
|
193
|
+
const instant = parseInstant(body.set);
|
|
194
|
+
if (instant === void 0)
|
|
195
|
+
return adminError(400, "set: expected epoch ms or ISO-8601");
|
|
196
|
+
context.clock.set(instant);
|
|
197
|
+
}
|
|
198
|
+
if (body.advance !== void 0) {
|
|
199
|
+
const delta = parseDuration(body.advance);
|
|
200
|
+
if (delta === void 0)
|
|
201
|
+
return adminError(400, 'advance: expected ms or "15m"-style');
|
|
202
|
+
context.clock.advance(delta);
|
|
203
|
+
}
|
|
204
|
+
if (body.freeze === true)
|
|
205
|
+
context.clock.freeze();
|
|
206
|
+
if (body.freeze === false)
|
|
207
|
+
context.clock.unfreeze();
|
|
208
|
+
return json(200, context.clock.state());
|
|
209
|
+
},
|
|
210
|
+
"GET /faults": () => json(200, { faults: context.faults.list() }),
|
|
211
|
+
"POST /faults": ({ body, namespace }) => {
|
|
212
|
+
if (isRecord(body) && typeof body.preset === "string") {
|
|
213
|
+
if (!context.applyPreset)
|
|
214
|
+
return adminError(400, `${context.name} has no fault presets`);
|
|
215
|
+
const { preset, ...overrides } = body;
|
|
216
|
+
try {
|
|
217
|
+
return json(201, {
|
|
218
|
+
preset,
|
|
219
|
+
rules: context.applyPreset(preset, namespace, overrides)
|
|
220
|
+
});
|
|
221
|
+
} catch (error) {
|
|
222
|
+
return adminError(404, error instanceof Error ? error.message : String(error));
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
if (!isRecord(body) || typeof body.status !== "number" && typeof body.delayMs !== "number" && typeof body.latencyMs !== "number" && body.drop !== true && typeof body.effect !== "string") {
|
|
226
|
+
return adminError(400, "a fault needs a numeric status, a delayMs/latencyMs, drop: true, an effect, or a preset");
|
|
227
|
+
}
|
|
228
|
+
const rule = {
|
|
229
|
+
// Scoped to the caller's namespace unless it asks for every one, so one worker's
|
|
230
|
+
// injected failure never lands on another's request.
|
|
231
|
+
namespace,
|
|
232
|
+
...body,
|
|
233
|
+
id: typeof body.id === "string" ? body.id : `fault_${context.faults.list().length + 1}`
|
|
234
|
+
};
|
|
235
|
+
return json(201, context.faults.add(rule));
|
|
236
|
+
},
|
|
237
|
+
"DELETE /faults": ({ url }) => {
|
|
238
|
+
const id = url.searchParams.get("id");
|
|
239
|
+
if (id === null) {
|
|
240
|
+
context.faults.clear();
|
|
241
|
+
return json(200, { status: "ok" });
|
|
242
|
+
}
|
|
243
|
+
return context.faults.remove(id) ? json(200, { status: "ok" }) : adminError(404, `no fault ${id}`);
|
|
244
|
+
},
|
|
245
|
+
"POST /snapshots": ({ namespace }) => {
|
|
246
|
+
const point = context.timeTravel.checkpoint(namespace, "main");
|
|
247
|
+
context.timeTravel.retain(namespace, point.id);
|
|
248
|
+
snapshotCounter++;
|
|
249
|
+
const id = `snap_${snapshotCounter}`;
|
|
250
|
+
snapshots.set(id, { namespace, checkpoint: point.id });
|
|
251
|
+
return json(201, { id, namespace, records: point.records ?? 0 });
|
|
252
|
+
},
|
|
253
|
+
"POST /snapshots/:id/restore": ({ params, namespace }) => {
|
|
254
|
+
const alias = snapshots.get(params.id);
|
|
255
|
+
if (!alias)
|
|
256
|
+
return adminError(404, `no snapshot ${params.id}`);
|
|
257
|
+
if (alias.namespace !== namespace) {
|
|
258
|
+
return adminError(409, `snapshot ${params.id} belongs to namespace ${alias.namespace}`);
|
|
259
|
+
}
|
|
260
|
+
context.timeTravel.checkout(alias.checkpoint, { namespace, branch: "main" });
|
|
261
|
+
return json(200, { status: "ok", id: params.id, namespace });
|
|
262
|
+
},
|
|
263
|
+
"DELETE /snapshots/:id": ({ params }) => {
|
|
264
|
+
const id = params.id;
|
|
265
|
+
const alias = snapshots.get(id);
|
|
266
|
+
if (!alias)
|
|
267
|
+
return adminError(404, `no snapshot ${id}`);
|
|
268
|
+
snapshots.delete(id);
|
|
269
|
+
context.timeTravel.release(alias.namespace, alias.checkpoint);
|
|
270
|
+
return json(200, { status: "ok" });
|
|
271
|
+
},
|
|
272
|
+
"GET /timeline": ({ namespace }) => json(200, context.timeTravel.inspect(namespace)),
|
|
273
|
+
"POST /checkpoints": ({ body, namespace }) => {
|
|
274
|
+
const branch = isRecord(body) && typeof body.branch === "string" ? body.branch : "main";
|
|
275
|
+
try {
|
|
276
|
+
return json(201, context.timeTravel.checkpoint(namespace, branch));
|
|
277
|
+
} catch (error) {
|
|
278
|
+
return adminError(409, error instanceof Error ? error.message : String(error));
|
|
279
|
+
}
|
|
280
|
+
},
|
|
281
|
+
"POST /branches/:name": ({ params, body, namespace }) => {
|
|
282
|
+
const at = isRecord(body) && typeof body.at === "string" ? body.at : void 0;
|
|
283
|
+
try {
|
|
284
|
+
return json(201, context.timeTravel.branch(params.name, {
|
|
285
|
+
namespace,
|
|
286
|
+
...at !== void 0 ? { at } : {}
|
|
287
|
+
}));
|
|
288
|
+
} catch (error) {
|
|
289
|
+
return adminError(409, error instanceof Error ? error.message : String(error));
|
|
290
|
+
}
|
|
291
|
+
},
|
|
292
|
+
"POST /branches/:name/checkout": ({ params, body, namespace }) => {
|
|
293
|
+
if (!isRecord(body) || typeof body.checkpoint !== "string") {
|
|
294
|
+
return adminError(400, 'expected {"checkpoint":"cp_..."}');
|
|
295
|
+
}
|
|
296
|
+
try {
|
|
297
|
+
context.timeTravel.checkout(body.checkpoint, {
|
|
298
|
+
namespace,
|
|
299
|
+
branch: params.name
|
|
300
|
+
});
|
|
301
|
+
return json(200, { status: "ok", branch: params.name, checkpoint: body.checkpoint });
|
|
302
|
+
} catch (error) {
|
|
303
|
+
return adminError(409, error instanceof Error ? error.message : String(error));
|
|
304
|
+
}
|
|
305
|
+
},
|
|
306
|
+
"GET /requests": ({ url, namespace }) => {
|
|
307
|
+
const status = url.searchParams.get("status");
|
|
308
|
+
const since = url.searchParams.get("since");
|
|
309
|
+
const limit = url.searchParams.get("limit");
|
|
310
|
+
const sinceMs = since === null ? void 0 : parseInstant(/^\d+$/.test(since) ? Number(since) : since);
|
|
311
|
+
if (since !== null && sinceMs === void 0) {
|
|
312
|
+
return adminError(400, "since: expected epoch ms or ISO-8601");
|
|
313
|
+
}
|
|
314
|
+
if (status !== null && !/^\d{3}$/.test(status))
|
|
315
|
+
return adminError(400, "status: expected an HTTP status");
|
|
316
|
+
if (limit !== null && !/^\d+$/.test(limit))
|
|
317
|
+
return adminError(400, "limit: expected a count");
|
|
318
|
+
const operationId = url.searchParams.get("operationId");
|
|
319
|
+
const everyNamespace = url.searchParams.get("all") === "1";
|
|
320
|
+
return json(200, {
|
|
321
|
+
size: context.journal.size,
|
|
322
|
+
requests: context.journal.list({
|
|
323
|
+
...everyNamespace ? {} : { namespace },
|
|
324
|
+
...operationId !== null ? { operationId } : {},
|
|
325
|
+
...status !== null ? { status: Number(status) } : {},
|
|
326
|
+
...sinceMs !== void 0 ? { since: sinceMs } : {},
|
|
327
|
+
...limit !== null ? { limit: Number(limit) } : {}
|
|
328
|
+
})
|
|
329
|
+
});
|
|
330
|
+
},
|
|
331
|
+
"DELETE /requests": ({ url, namespace }) => {
|
|
332
|
+
context.journal.clear(url.searchParams.get("all") === "1" ? void 0 : namespace);
|
|
333
|
+
return json(200, { status: "ok" });
|
|
334
|
+
},
|
|
335
|
+
"GET /metrics": () => json(200, context.metrics.report()),
|
|
336
|
+
"DELETE /metrics": () => {
|
|
337
|
+
context.metrics.reset();
|
|
338
|
+
return json(200, { status: "ok" });
|
|
339
|
+
}
|
|
340
|
+
};
|
|
341
|
+
const routes = [...Object.entries(context.routes), ...Object.entries(builtin)].map(([key, handler]) => {
|
|
342
|
+
const space = key.indexOf(" ");
|
|
343
|
+
return { method: key.slice(0, space), pattern: key.slice(space + 1), handler };
|
|
344
|
+
});
|
|
345
|
+
return {
|
|
346
|
+
namespaceOf: headerNamespace,
|
|
347
|
+
async handle(request) {
|
|
348
|
+
const url = new URL(request.url);
|
|
349
|
+
if (url.pathname === HEALTH_PATH && request.method === "GET") {
|
|
350
|
+
return json(200, {
|
|
351
|
+
status: "ok",
|
|
352
|
+
service: context.name,
|
|
353
|
+
uptimeMs: context.wallNow() - context.startedAt,
|
|
354
|
+
clock: context.clock.state(),
|
|
355
|
+
namespaces: context.namespaces().length,
|
|
356
|
+
...context.describe()
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
if (url.pathname !== ADMIN_PREFIX && !url.pathname.startsWith(`${ADMIN_PREFIX}/`)) {
|
|
360
|
+
return void 0;
|
|
361
|
+
}
|
|
362
|
+
if (context.adminKey !== void 0 && request.headers.get(ADMIN_KEY_HEADER) !== context.adminKey) {
|
|
363
|
+
return adminError(401, `missing or wrong ${ADMIN_KEY_HEADER}`);
|
|
364
|
+
}
|
|
365
|
+
const path = url.pathname.slice(ADMIN_PREFIX.length) || "/";
|
|
366
|
+
for (const route of routes) {
|
|
367
|
+
if (route.method !== request.method)
|
|
368
|
+
continue;
|
|
369
|
+
const params = matchRoute(route.pattern, path);
|
|
370
|
+
if (!params)
|
|
371
|
+
continue;
|
|
372
|
+
let body;
|
|
373
|
+
try {
|
|
374
|
+
body = await readJson(request);
|
|
375
|
+
} catch {
|
|
376
|
+
return adminError(400, "request body is not valid JSON");
|
|
377
|
+
}
|
|
378
|
+
return route.handler({
|
|
379
|
+
request,
|
|
380
|
+
url,
|
|
381
|
+
params,
|
|
382
|
+
namespace: adminNamespace(request, url),
|
|
383
|
+
body
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
return adminError(404, `no admin route ${request.method} ${path}; GET ${ADMIN_PREFIX} lists them`);
|
|
387
|
+
}
|
|
388
|
+
};
|
|
389
|
+
};
|
|
390
|
+
|
|
391
|
+
// ../core/dist/credentials.js
|
|
392
|
+
var createCredentialRegistry = () => {
|
|
393
|
+
const map = /* @__PURE__ */ new Map();
|
|
394
|
+
return {
|
|
395
|
+
set: (credential, namespace) => {
|
|
396
|
+
map.set(credential, namespace);
|
|
397
|
+
},
|
|
398
|
+
get: (credential) => map.get(credential),
|
|
399
|
+
remove: (credential) => map.delete(credential),
|
|
400
|
+
clear: () => map.clear(),
|
|
401
|
+
entries: () => [...map].map(([credential, namespace]) => ({ credential, namespace })).sort((a, b) => a.credential.localeCompare(b.credential))
|
|
402
|
+
};
|
|
403
|
+
};
|
|
404
|
+
var maskCredential = (credential) => credential.length <= 8 ? `${credential.slice(0, 2)}\u2026` : `${credential.slice(0, 6)}\u2026${credential.slice(-2)}`;
|
|
405
|
+
|
|
406
|
+
// ../core/dist/rng.js
|
|
407
|
+
var seedFrom = (value) => {
|
|
408
|
+
let hash = 2166136261;
|
|
409
|
+
for (let i = 0; i < value.length; i++) {
|
|
410
|
+
hash ^= value.charCodeAt(i);
|
|
411
|
+
hash = Math.imul(hash, 16777619);
|
|
412
|
+
}
|
|
413
|
+
return hash >>> 0;
|
|
414
|
+
};
|
|
415
|
+
var createRng = (seed = 0) => {
|
|
416
|
+
const numeric = typeof seed === "string" ? seedFrom(seed) : seed >>> 0;
|
|
417
|
+
let state = numeric;
|
|
418
|
+
const next = () => {
|
|
419
|
+
state = state + 1831565813 >>> 0;
|
|
420
|
+
let t = state;
|
|
421
|
+
t = Math.imul(t ^ t >>> 15, t | 1);
|
|
422
|
+
t ^= t + Math.imul(t ^ t >>> 7, t | 61);
|
|
423
|
+
return ((t ^ t >>> 14) >>> 0) / 4294967296;
|
|
424
|
+
};
|
|
425
|
+
return {
|
|
426
|
+
next,
|
|
427
|
+
int: (min, max) => min + Math.floor(next() * (max - min + 1)),
|
|
428
|
+
reset: () => {
|
|
429
|
+
state = numeric;
|
|
430
|
+
},
|
|
431
|
+
state: () => state,
|
|
432
|
+
setState: (next2) => {
|
|
433
|
+
if (!Number.isSafeInteger(next2) || next2 < 0 || next2 > 4294967295) {
|
|
434
|
+
throw new RangeError("rng state must be an unsigned 32-bit integer");
|
|
435
|
+
}
|
|
436
|
+
state = next2 >>> 0;
|
|
437
|
+
},
|
|
438
|
+
seed: numeric
|
|
439
|
+
};
|
|
440
|
+
};
|
|
441
|
+
|
|
442
|
+
// ../core/dist/faults.js
|
|
443
|
+
var matches = (rule, candidate) => {
|
|
444
|
+
if (rule.namespace !== void 0 && rule.namespace !== "*" && rule.namespace !== candidate.namespace) {
|
|
445
|
+
return false;
|
|
446
|
+
}
|
|
447
|
+
if (rule.operationId !== void 0 && rule.operationId !== candidate.operationId)
|
|
448
|
+
return false;
|
|
449
|
+
if (rule.method !== void 0 && rule.method.toUpperCase() !== candidate.method.toUpperCase()) {
|
|
450
|
+
return false;
|
|
451
|
+
}
|
|
452
|
+
if (rule.pathPrefix !== void 0 && !candidate.path.startsWith(rule.pathPrefix))
|
|
453
|
+
return false;
|
|
454
|
+
return true;
|
|
455
|
+
};
|
|
456
|
+
var faultResponse = (rule) => {
|
|
457
|
+
const status = rule.status ?? 500;
|
|
458
|
+
const headers = { "content-type": "application/json", ...rule.headers };
|
|
459
|
+
if (typeof rule.body === "string")
|
|
460
|
+
return new Response(rule.body, { status, headers });
|
|
461
|
+
if (rule.body === null)
|
|
462
|
+
return new Response(null, { status, headers: rule.headers ?? {} });
|
|
463
|
+
const body = rule.body === void 0 ? { detail: "Injected by Mockingbird" } : rule.body;
|
|
464
|
+
return new Response(JSON.stringify(body), { status, headers });
|
|
465
|
+
};
|
|
466
|
+
var createFaultRegistry = (rng = createRng(0), sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms))) => {
|
|
467
|
+
const entries = [];
|
|
468
|
+
return {
|
|
469
|
+
add(rule) {
|
|
470
|
+
const existing = entries.findIndex((e) => e.rule.id === rule.id);
|
|
471
|
+
const entry = { rule, remaining: rule.count ?? null, hits: 0 };
|
|
472
|
+
if (existing >= 0)
|
|
473
|
+
entries[existing] = entry;
|
|
474
|
+
else
|
|
475
|
+
entries.push(entry);
|
|
476
|
+
return rule;
|
|
477
|
+
},
|
|
478
|
+
list: () => entries.map((e) => ({ ...e.rule, remaining: e.remaining, hits: e.hits })),
|
|
479
|
+
remove(id) {
|
|
480
|
+
const index = entries.findIndex((e) => e.rule.id === id);
|
|
481
|
+
if (index < 0)
|
|
482
|
+
return false;
|
|
483
|
+
entries.splice(index, 1);
|
|
484
|
+
return true;
|
|
485
|
+
},
|
|
486
|
+
clear() {
|
|
487
|
+
entries.length = 0;
|
|
488
|
+
},
|
|
489
|
+
async take(candidate) {
|
|
490
|
+
const hits = [];
|
|
491
|
+
for (const entry of entries) {
|
|
492
|
+
if (entry.remaining === 0)
|
|
493
|
+
continue;
|
|
494
|
+
if (!matches(entry.rule, candidate))
|
|
495
|
+
continue;
|
|
496
|
+
const rate = entry.rule.rate ?? 1;
|
|
497
|
+
if (rng.next() >= rate)
|
|
498
|
+
continue;
|
|
499
|
+
entry.hits++;
|
|
500
|
+
if (entry.remaining !== null)
|
|
501
|
+
entry.remaining--;
|
|
502
|
+
const delay = entry.rule.delayMs ?? entry.rule.latencyMs;
|
|
503
|
+
if (delay !== void 0 && delay > 0) {
|
|
504
|
+
await sleep(delay);
|
|
505
|
+
}
|
|
506
|
+
const hit = { id: entry.rule.id };
|
|
507
|
+
if (entry.rule.effect !== void 0) {
|
|
508
|
+
hit.effect = { name: entry.rule.effect, params: entry.rule.params ?? {} };
|
|
509
|
+
}
|
|
510
|
+
if (entry.rule.drop === true)
|
|
511
|
+
hit.drop = true;
|
|
512
|
+
else if (entry.rule.status !== void 0)
|
|
513
|
+
hit.response = faultResponse(entry.rule);
|
|
514
|
+
hits.push(hit);
|
|
515
|
+
if (hit.drop || hit.response)
|
|
516
|
+
break;
|
|
517
|
+
}
|
|
518
|
+
return hits;
|
|
519
|
+
}
|
|
520
|
+
};
|
|
521
|
+
};
|
|
522
|
+
|
|
523
|
+
// ../../openapi/core/dist/refs.js
|
|
524
|
+
var OpenAPIReferenceError = class extends Error {
|
|
525
|
+
ref;
|
|
526
|
+
constructor(ref) {
|
|
527
|
+
super(`unresolvable $ref: ${ref}`);
|
|
528
|
+
this.ref = ref;
|
|
529
|
+
this.name = "OpenAPIReferenceError";
|
|
530
|
+
}
|
|
531
|
+
};
|
|
532
|
+
var unescapePointer = (segment) => segment.replace(/~1/g, "/").replace(/~0/g, "~");
|
|
533
|
+
var isReference = (value) => typeof value === "object" && value !== null && typeof value.$ref === "string";
|
|
534
|
+
var resolveRef = (document2, ref) => {
|
|
535
|
+
if (!ref.startsWith("#/"))
|
|
536
|
+
throw new OpenAPIReferenceError(ref);
|
|
537
|
+
let cursor = document2;
|
|
538
|
+
for (const raw of ref.slice(2).split("/")) {
|
|
539
|
+
const segment = unescapePointer(raw);
|
|
540
|
+
if (typeof cursor !== "object" || cursor === null || !(segment in cursor)) {
|
|
541
|
+
throw new OpenAPIReferenceError(ref);
|
|
542
|
+
}
|
|
543
|
+
cursor = cursor[segment];
|
|
544
|
+
}
|
|
545
|
+
if (cursor === void 0)
|
|
546
|
+
throw new OpenAPIReferenceError(ref);
|
|
547
|
+
return cursor;
|
|
548
|
+
};
|
|
549
|
+
var deref = (document2, value) => {
|
|
550
|
+
let current = value;
|
|
551
|
+
const seen = /* @__PURE__ */ new Set();
|
|
552
|
+
while (isReference(current)) {
|
|
553
|
+
if (seen.has(current.$ref))
|
|
554
|
+
throw new OpenAPIReferenceError(`${current.$ref} (cycle)`);
|
|
555
|
+
seen.add(current.$ref);
|
|
556
|
+
current = resolveRef(document2, current.$ref);
|
|
557
|
+
}
|
|
558
|
+
return current;
|
|
559
|
+
};
|
|
560
|
+
|
|
561
|
+
// ../../openapi/core/dist/types.js
|
|
562
|
+
var HTTP_METHODS = [
|
|
563
|
+
"get",
|
|
564
|
+
"put",
|
|
565
|
+
"post",
|
|
566
|
+
"delete",
|
|
567
|
+
"options",
|
|
568
|
+
"head",
|
|
569
|
+
"patch",
|
|
570
|
+
"trace"
|
|
571
|
+
];
|
|
572
|
+
|
|
573
|
+
// ../../openapi/core/dist/document.js
|
|
574
|
+
var mergeParameters = (document2, item, own) => {
|
|
575
|
+
const merged = /* @__PURE__ */ new Map();
|
|
576
|
+
for (const raw of item.parameters ?? []) {
|
|
577
|
+
const parameter = deref(document2, raw);
|
|
578
|
+
merged.set(`${parameter.in}:${parameter.name}`, parameter);
|
|
579
|
+
}
|
|
580
|
+
for (const raw of own ?? []) {
|
|
581
|
+
const parameter = deref(document2, raw);
|
|
582
|
+
merged.set(`${parameter.in}:${parameter.name}`, parameter);
|
|
583
|
+
}
|
|
584
|
+
return [...merged.values()];
|
|
585
|
+
};
|
|
586
|
+
var listOperations = (document2) => {
|
|
587
|
+
const operations = [];
|
|
588
|
+
for (const [path, item] of Object.entries(document2.paths)) {
|
|
589
|
+
for (const method of HTTP_METHODS) {
|
|
590
|
+
const operation = item[method];
|
|
591
|
+
if (operation?.operationId === void 0)
|
|
592
|
+
continue;
|
|
593
|
+
const responses = {};
|
|
594
|
+
for (const [status, response] of Object.entries(operation.responses)) {
|
|
595
|
+
responses[status] = deref(document2, response);
|
|
596
|
+
}
|
|
597
|
+
operations.push({
|
|
598
|
+
operationId: operation.operationId,
|
|
599
|
+
method,
|
|
600
|
+
path,
|
|
601
|
+
operation,
|
|
602
|
+
parameters: mergeParameters(document2, item, operation.parameters),
|
|
603
|
+
requestBody: operation.requestBody === void 0 ? void 0 : deref(document2, operation.requestBody),
|
|
604
|
+
responses
|
|
605
|
+
});
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
return operations;
|
|
609
|
+
};
|
|
610
|
+
|
|
611
|
+
// ../../http/codec/dist/form.js
|
|
612
|
+
var parsePath = (rawKey) => {
|
|
613
|
+
const open = rawKey.indexOf("[");
|
|
614
|
+
if (open === -1)
|
|
615
|
+
return [rawKey];
|
|
616
|
+
const path = [rawKey.slice(0, open)];
|
|
617
|
+
const rest = rawKey.slice(open);
|
|
618
|
+
const pattern = /\[([^\]]*)\]/g;
|
|
619
|
+
let match = pattern.exec(rest);
|
|
620
|
+
let consumed = 0;
|
|
621
|
+
while (match !== null) {
|
|
622
|
+
if (match.index !== consumed)
|
|
623
|
+
return [rawKey];
|
|
624
|
+
path.push(match[1] ?? "");
|
|
625
|
+
consumed = match.index + match[0].length;
|
|
626
|
+
match = pattern.exec(rest);
|
|
627
|
+
}
|
|
628
|
+
if (consumed !== rest.length)
|
|
629
|
+
return [rawKey];
|
|
630
|
+
return path;
|
|
631
|
+
};
|
|
632
|
+
var isIndex = (segment) => /^(0|[1-9][0-9]*)$/.test(segment);
|
|
633
|
+
var put = (target, key, value) => {
|
|
634
|
+
if (key === "__proto__") {
|
|
635
|
+
Object.defineProperty(target, key, {
|
|
636
|
+
value,
|
|
637
|
+
enumerable: true,
|
|
638
|
+
writable: true,
|
|
639
|
+
configurable: true
|
|
640
|
+
});
|
|
641
|
+
return;
|
|
642
|
+
}
|
|
643
|
+
;
|
|
644
|
+
target[key] = value;
|
|
645
|
+
};
|
|
646
|
+
var assign = (target, path, value) => {
|
|
647
|
+
let cursor = target;
|
|
648
|
+
for (let i = 0; i < path.length; i++) {
|
|
649
|
+
const segment = path[i];
|
|
650
|
+
const last = i === path.length - 1;
|
|
651
|
+
if (Array.isArray(cursor)) {
|
|
652
|
+
const index = segment === "" ? cursor.length : isIndex(segment) ? Number(segment) : void 0;
|
|
653
|
+
if (index === void 0)
|
|
654
|
+
return;
|
|
655
|
+
if (last) {
|
|
656
|
+
put(cursor, index, value);
|
|
657
|
+
return;
|
|
658
|
+
}
|
|
659
|
+
const next = Object.hasOwn(cursor, index) ? cursor[index] : void 0;
|
|
660
|
+
if (next === void 0 || typeof next === "string") {
|
|
661
|
+
const created = path[i + 1] === "" || isIndex(path[i + 1]) ? [] : {};
|
|
662
|
+
put(cursor, index, created);
|
|
663
|
+
cursor = created;
|
|
664
|
+
} else {
|
|
665
|
+
cursor = next;
|
|
666
|
+
}
|
|
667
|
+
continue;
|
|
668
|
+
}
|
|
669
|
+
if (typeof cursor === "string")
|
|
670
|
+
return;
|
|
671
|
+
if (last) {
|
|
672
|
+
put(cursor, segment, value);
|
|
673
|
+
return;
|
|
674
|
+
}
|
|
675
|
+
const nextSegment = path[i + 1];
|
|
676
|
+
const existing = Object.hasOwn(cursor, segment) ? cursor[segment] : void 0;
|
|
677
|
+
if (existing === void 0 || typeof existing === "string") {
|
|
678
|
+
const created = nextSegment === "" || isIndex(nextSegment) ? [] : {};
|
|
679
|
+
put(cursor, segment, created);
|
|
680
|
+
cursor = created;
|
|
681
|
+
} else {
|
|
682
|
+
cursor = existing;
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
};
|
|
686
|
+
var decodeFormPairs = (pairs) => {
|
|
687
|
+
const out = {};
|
|
688
|
+
for (const [rawKey, value] of pairs)
|
|
689
|
+
assign(out, parsePath(rawKey), value);
|
|
690
|
+
return densify(out);
|
|
691
|
+
};
|
|
692
|
+
var densify = (value) => {
|
|
693
|
+
if (typeof value === "string")
|
|
694
|
+
return value;
|
|
695
|
+
if (Array.isArray(value))
|
|
696
|
+
return value.filter((item) => item !== void 0).map(densify);
|
|
697
|
+
const out = {};
|
|
698
|
+
for (const [key, item] of Object.entries(value))
|
|
699
|
+
put(out, key, densify(item));
|
|
700
|
+
return out;
|
|
701
|
+
};
|
|
702
|
+
var decodeForm = (text2) => {
|
|
703
|
+
const source = text2.startsWith("?") ? text2.slice(1) : text2;
|
|
704
|
+
return decodeFormPairs(new URLSearchParams(source).entries());
|
|
705
|
+
};
|
|
706
|
+
|
|
707
|
+
// ../../http/codec/dist/content.js
|
|
708
|
+
var JSON_MEDIA_TYPE = "application/json";
|
|
709
|
+
var FORM_MEDIA_TYPE = "application/x-www-form-urlencoded";
|
|
710
|
+
var mediaTypeOf = (contentType) => {
|
|
711
|
+
if (!contentType)
|
|
712
|
+
return void 0;
|
|
713
|
+
const essence = contentType.split(";")[0]?.trim().toLowerCase();
|
|
714
|
+
return essence ? essence : void 0;
|
|
715
|
+
};
|
|
716
|
+
var isJsonMediaType = (mediaType) => mediaType === JSON_MEDIA_TYPE || mediaType.endsWith("+json") || mediaType === "text/json";
|
|
717
|
+
var utf8 = new TextDecoder("utf-8", { fatal: false });
|
|
718
|
+
var decodeBody = (contentType, bytes) => {
|
|
719
|
+
if (bytes.byteLength === 0)
|
|
720
|
+
return { kind: "empty" };
|
|
721
|
+
const mediaType = mediaTypeOf(contentType);
|
|
722
|
+
if (mediaType === void 0)
|
|
723
|
+
return { kind: "bytes", value: bytes };
|
|
724
|
+
if (isJsonMediaType(mediaType)) {
|
|
725
|
+
const text2 = utf8.decode(bytes);
|
|
726
|
+
try {
|
|
727
|
+
return { kind: "json", value: JSON.parse(text2) };
|
|
728
|
+
} catch (error) {
|
|
729
|
+
return {
|
|
730
|
+
kind: "invalid",
|
|
731
|
+
mediaType,
|
|
732
|
+
text: text2,
|
|
733
|
+
error: error instanceof Error ? error.message : String(error)
|
|
734
|
+
};
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
if (mediaType === FORM_MEDIA_TYPE) {
|
|
738
|
+
return { kind: "form", value: decodeForm(utf8.decode(bytes)) };
|
|
739
|
+
}
|
|
740
|
+
if (mediaType.startsWith("text/"))
|
|
741
|
+
return { kind: "text", value: utf8.decode(bytes) };
|
|
742
|
+
return { kind: "bytes", value: bytes };
|
|
743
|
+
};
|
|
744
|
+
var readBody = async (message) => {
|
|
745
|
+
const bytes = new Uint8Array(await message.arrayBuffer());
|
|
746
|
+
return decodeBody(message.headers.get("content-type"), bytes);
|
|
747
|
+
};
|
|
748
|
+
|
|
749
|
+
// ../core/dist/http.js
|
|
750
|
+
var jsonRes = (status, body, headers = {}) => new Response(JSON.stringify(body), {
|
|
751
|
+
status,
|
|
752
|
+
headers: { "content-type": JSON_MEDIA_TYPE, ...headers }
|
|
753
|
+
});
|
|
754
|
+
var HttpError = class extends Error {
|
|
755
|
+
status;
|
|
756
|
+
body;
|
|
757
|
+
headers;
|
|
758
|
+
constructor(status, body, headers = {}) {
|
|
759
|
+
super(`HTTP ${status}`);
|
|
760
|
+
this.status = status;
|
|
761
|
+
this.body = body;
|
|
762
|
+
this.headers = headers;
|
|
763
|
+
this.name = "HttpError";
|
|
764
|
+
}
|
|
765
|
+
toResponse() {
|
|
766
|
+
const contentType = this.headers["content-type"]?.split(";", 1)[0]?.trim().toLowerCase();
|
|
767
|
+
if (contentType === "text/plain") {
|
|
768
|
+
return new Response(String(this.body), {
|
|
769
|
+
status: this.status,
|
|
770
|
+
headers: this.headers
|
|
771
|
+
});
|
|
772
|
+
}
|
|
773
|
+
return jsonRes(this.status, this.body, this.headers);
|
|
774
|
+
}
|
|
775
|
+
};
|
|
776
|
+
|
|
777
|
+
// ../core/dist/ids.js
|
|
778
|
+
var ALPHABET = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
|
779
|
+
var mix = (input) => {
|
|
780
|
+
let hash = 2166136261;
|
|
781
|
+
for (let i = 0; i < input.length; i++) {
|
|
782
|
+
hash ^= input.charCodeAt(i);
|
|
783
|
+
hash = Math.imul(hash, 16777619) >>> 0;
|
|
784
|
+
}
|
|
785
|
+
hash ^= hash >>> 16;
|
|
786
|
+
hash = Math.imul(hash, 2246822507) >>> 0;
|
|
787
|
+
hash ^= hash >>> 13;
|
|
788
|
+
return hash >>> 0;
|
|
789
|
+
};
|
|
790
|
+
var opaqueToken = (input, length) => {
|
|
791
|
+
let out = "";
|
|
792
|
+
let round2 = 0;
|
|
793
|
+
while (out.length < length) {
|
|
794
|
+
let hash = mix(`${input}:${round2++}`);
|
|
795
|
+
for (let i = 0; i < 5 && out.length < length; i++) {
|
|
796
|
+
out += ALPHABET.charAt(hash % ALPHABET.length);
|
|
797
|
+
hash = Math.floor(hash / ALPHABET.length);
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
return out;
|
|
801
|
+
};
|
|
802
|
+
|
|
803
|
+
// ../core/dist/journal.js
|
|
804
|
+
var DEFAULT_JOURNAL_SIZE = 1e3;
|
|
805
|
+
var createJournal = (size = DEFAULT_JOURNAL_SIZE) => {
|
|
806
|
+
const capacity = Math.max(0, Math.floor(size));
|
|
807
|
+
const rings = /* @__PURE__ */ new Map();
|
|
808
|
+
let sequence = 0;
|
|
809
|
+
const order = /* @__PURE__ */ new WeakMap();
|
|
810
|
+
const inOrder = (ring) => ring.entries.length < capacity ? ring.entries : [...ring.entries.slice(ring.next), ...ring.entries.slice(0, ring.next)];
|
|
811
|
+
return {
|
|
812
|
+
size: capacity,
|
|
813
|
+
record(entry) {
|
|
814
|
+
if (capacity === 0)
|
|
815
|
+
return;
|
|
816
|
+
order.set(entry, sequence++);
|
|
817
|
+
let ring = rings.get(entry.namespace);
|
|
818
|
+
if (!ring) {
|
|
819
|
+
ring = { entries: [], next: 0 };
|
|
820
|
+
rings.set(entry.namespace, ring);
|
|
821
|
+
}
|
|
822
|
+
if (ring.entries.length < capacity)
|
|
823
|
+
ring.entries.push(entry);
|
|
824
|
+
else {
|
|
825
|
+
ring.entries[ring.next] = entry;
|
|
826
|
+
ring.next = (ring.next + 1) % capacity;
|
|
827
|
+
}
|
|
828
|
+
},
|
|
829
|
+
list(query = {}) {
|
|
830
|
+
const source = query.namespace !== void 0 ? inOrder(rings.get(query.namespace) ?? { entries: [], next: 0 }) : [...rings.values()].flatMap(inOrder).sort((a, b) => (order.get(a) ?? 0) - (order.get(b) ?? 0));
|
|
831
|
+
const matched = source.filter((entry) => (query.operationId === void 0 || entry.operationId === query.operationId) && (query.status === void 0 || entry.status === query.status) && (query.since === void 0 || Date.parse(entry.at) >= query.since));
|
|
832
|
+
return query.limit !== void 0 ? matched.slice(-Math.max(0, query.limit)) : matched;
|
|
833
|
+
},
|
|
834
|
+
clear(namespace) {
|
|
835
|
+
if (namespace === void 0)
|
|
836
|
+
rings.clear();
|
|
837
|
+
else
|
|
838
|
+
rings.delete(namespace);
|
|
839
|
+
}
|
|
840
|
+
};
|
|
841
|
+
};
|
|
842
|
+
var notes = /* @__PURE__ */ new WeakMap();
|
|
843
|
+
var annotateResponse = (response, extra) => {
|
|
844
|
+
const existing = notes.get(response);
|
|
845
|
+
notes.set(response, {
|
|
846
|
+
...existing,
|
|
847
|
+
...extra,
|
|
848
|
+
...existing?.ids || extra.ids ? { ids: { ...existing?.ids, ...extra.ids } } : {}
|
|
849
|
+
});
|
|
850
|
+
return response;
|
|
851
|
+
};
|
|
852
|
+
var responseNotes = (response) => notes.get(response);
|
|
853
|
+
|
|
854
|
+
// ../core/dist/metrics.js
|
|
855
|
+
var createMetrics = () => {
|
|
856
|
+
let requests = 0;
|
|
857
|
+
let faults = 0;
|
|
858
|
+
let totalDurationMs = 0;
|
|
859
|
+
const byOperation = /* @__PURE__ */ new Map();
|
|
860
|
+
const unmatched = /* @__PURE__ */ new Map();
|
|
861
|
+
return {
|
|
862
|
+
record(entry) {
|
|
863
|
+
requests++;
|
|
864
|
+
totalDurationMs += entry.durationMs;
|
|
865
|
+
if (entry.faultId !== void 0)
|
|
866
|
+
faults++;
|
|
867
|
+
const key = `${entry.operationId ?? "(unmatched)"} ${entry.status}`;
|
|
868
|
+
byOperation.set(key, (byOperation.get(key) ?? 0) + 1);
|
|
869
|
+
if (entry.unmatched) {
|
|
870
|
+
const route = `${entry.method} ${entry.path}`;
|
|
871
|
+
unmatched.set(route, (unmatched.get(route) ?? 0) + 1);
|
|
872
|
+
}
|
|
873
|
+
},
|
|
874
|
+
report: () => ({
|
|
875
|
+
requests,
|
|
876
|
+
byOperation: Object.fromEntries([...byOperation].sort(([a], [b]) => a.localeCompare(b))),
|
|
877
|
+
unmatched: [...unmatched].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])).map(([route, count]) => {
|
|
878
|
+
const space = route.indexOf(" ");
|
|
879
|
+
return { method: route.slice(0, space), path: route.slice(space + 1), count };
|
|
880
|
+
}),
|
|
881
|
+
faults,
|
|
882
|
+
totalDurationMs
|
|
883
|
+
}),
|
|
884
|
+
reset() {
|
|
885
|
+
requests = 0;
|
|
886
|
+
faults = 0;
|
|
887
|
+
totalDurationMs = 0;
|
|
888
|
+
byOperation.clear();
|
|
889
|
+
unmatched.clear();
|
|
890
|
+
}
|
|
891
|
+
};
|
|
892
|
+
};
|
|
893
|
+
|
|
894
|
+
// ../../core/dist/timeline.js
|
|
895
|
+
var BRANCH_PATTERN = /^[A-Za-z0-9_.-]{1,64}$/;
|
|
896
|
+
var Timeline = class {
|
|
897
|
+
maxCheckpoints;
|
|
898
|
+
now;
|
|
899
|
+
makeId;
|
|
900
|
+
nodes = /* @__PURE__ */ new Map();
|
|
901
|
+
heads = /* @__PURE__ */ new Map();
|
|
902
|
+
/** Unreferenced nodes in the exact order they became collectible. */
|
|
903
|
+
evictable = /* @__PURE__ */ new Set();
|
|
904
|
+
/** Branch heads plus explicit retainers. Absent means zero. */
|
|
905
|
+
references = /* @__PURE__ */ new Map();
|
|
906
|
+
explicitPins = /* @__PURE__ */ new Map();
|
|
907
|
+
sequence = 0;
|
|
908
|
+
constructor(options = {}) {
|
|
909
|
+
const max = options.maxCheckpoints ?? 1e3;
|
|
910
|
+
if (!Number.isSafeInteger(max) || max < 1)
|
|
911
|
+
throw new RangeError("maxCheckpoints must be a positive integer");
|
|
912
|
+
this.maxCheckpoints = max;
|
|
913
|
+
this.now = options.now ?? (() => this.sequence);
|
|
914
|
+
this.makeId = options.id ?? ((sequence) => `cp_${sequence.toString(36).padStart(8, "0")}`);
|
|
915
|
+
}
|
|
916
|
+
/** Capture a new immutable value and move `branch` to it. */
|
|
917
|
+
commit(value, options = {}) {
|
|
918
|
+
const branch = options.branch ?? "main";
|
|
919
|
+
this.assertBranch(branch);
|
|
920
|
+
const parent = options.parent === void 0 ? this.heads.get(branch) ?? null : options.parent;
|
|
921
|
+
if (parent !== null && !this.nodes.has(parent))
|
|
922
|
+
throw new RangeError(`no checkpoint ${parent}`);
|
|
923
|
+
const id = this.makeId(++this.sequence);
|
|
924
|
+
if (this.nodes.has(id))
|
|
925
|
+
throw new RangeError(`duplicate checkpoint id ${id}`);
|
|
926
|
+
const checkpoint = Object.freeze({ id, branch, parent, at: this.now(), value });
|
|
927
|
+
this.nodes.set(id, checkpoint);
|
|
928
|
+
this.moveHead(branch, id);
|
|
929
|
+
this.collect(this.maxCheckpoints);
|
|
930
|
+
return checkpoint;
|
|
931
|
+
}
|
|
932
|
+
/** Create a branch pointer without copying its checkpoint value. */
|
|
933
|
+
fork(branch, options = {}) {
|
|
934
|
+
this.assertBranch(branch);
|
|
935
|
+
if (this.heads.has(branch))
|
|
936
|
+
throw new RangeError(`branch already exists: ${branch}`);
|
|
937
|
+
const from = options.from ?? this.heads.get("main");
|
|
938
|
+
if (from === void 0)
|
|
939
|
+
return void 0;
|
|
940
|
+
const checkpoint = this.get(from);
|
|
941
|
+
this.moveHead(branch, checkpoint.id);
|
|
942
|
+
return checkpoint;
|
|
943
|
+
}
|
|
944
|
+
/** Move a branch pointer to an existing checkpoint. */
|
|
945
|
+
checkout(branch, id) {
|
|
946
|
+
this.assertBranch(branch);
|
|
947
|
+
const checkpoint = this.get(id);
|
|
948
|
+
this.moveHead(branch, checkpoint.id);
|
|
949
|
+
return checkpoint;
|
|
950
|
+
}
|
|
951
|
+
get(id) {
|
|
952
|
+
const checkpoint = this.nodes.get(id);
|
|
953
|
+
if (!checkpoint)
|
|
954
|
+
throw new RangeError(`no checkpoint ${id}`);
|
|
955
|
+
return checkpoint;
|
|
956
|
+
}
|
|
957
|
+
head(branch = "main") {
|
|
958
|
+
const id = this.heads.get(branch);
|
|
959
|
+
return id === void 0 ? void 0 : this.get(id);
|
|
960
|
+
}
|
|
961
|
+
hasBranch(branch) {
|
|
962
|
+
return this.heads.has(branch);
|
|
963
|
+
}
|
|
964
|
+
branches() {
|
|
965
|
+
return Object.freeze(Object.fromEntries([...this.heads].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0)));
|
|
966
|
+
}
|
|
967
|
+
checkpoints() {
|
|
968
|
+
return [...this.nodes.values()];
|
|
969
|
+
}
|
|
970
|
+
/** Number of retained checkpoints without allocating an array. */
|
|
971
|
+
get size() {
|
|
972
|
+
return this.nodes.size;
|
|
973
|
+
}
|
|
974
|
+
/** Pin a checkpoint independently of branch heads (used by compatibility snapshot handles). */
|
|
975
|
+
retain(id) {
|
|
976
|
+
const checkpoint = this.get(id);
|
|
977
|
+
this.explicitPins.set(id, (this.explicitPins.get(id) ?? 0) + 1);
|
|
978
|
+
this.addReference(id);
|
|
979
|
+
return checkpoint;
|
|
980
|
+
}
|
|
981
|
+
/** Release one explicit pin. Branch heads remain pinned until moved or deleted. */
|
|
982
|
+
release(id) {
|
|
983
|
+
if (!this.nodes.has(id))
|
|
984
|
+
return false;
|
|
985
|
+
const pins = this.explicitPins.get(id) ?? 0;
|
|
986
|
+
if (pins === 0)
|
|
987
|
+
return false;
|
|
988
|
+
if (pins === 1)
|
|
989
|
+
this.explicitPins.delete(id);
|
|
990
|
+
else
|
|
991
|
+
this.explicitPins.set(id, pins - 1);
|
|
992
|
+
this.removeReference(id);
|
|
993
|
+
this.collect(this.maxCheckpoints);
|
|
994
|
+
return true;
|
|
995
|
+
}
|
|
996
|
+
deleteBranch(branch) {
|
|
997
|
+
if (branch === "main")
|
|
998
|
+
throw new RangeError("cannot delete main branch");
|
|
999
|
+
const previous = this.heads.get(branch);
|
|
1000
|
+
const deleted = this.heads.delete(branch);
|
|
1001
|
+
if (previous !== void 0)
|
|
1002
|
+
this.removeReference(previous);
|
|
1003
|
+
this.collect(this.maxCheckpoints);
|
|
1004
|
+
return deleted;
|
|
1005
|
+
}
|
|
1006
|
+
/**
|
|
1007
|
+
* Deterministically discard oldest unpinned checkpoints. Collection is O(number removed):
|
|
1008
|
+
* commits never scan pinned nodes or the retained history. Parents are metadata rather than a
|
|
1009
|
+
* storage dependency, so a retained node remains usable after pruning.
|
|
1010
|
+
*/
|
|
1011
|
+
gc(max = this.maxCheckpoints) {
|
|
1012
|
+
if (!Number.isSafeInteger(max) || max < 1)
|
|
1013
|
+
throw new RangeError("max must be a positive integer");
|
|
1014
|
+
const removed = [];
|
|
1015
|
+
this.collect(max, removed);
|
|
1016
|
+
return removed;
|
|
1017
|
+
}
|
|
1018
|
+
collect(max, removed) {
|
|
1019
|
+
while (this.nodes.size > max && this.evictable.size > 0) {
|
|
1020
|
+
const id = this.evictable.values().next().value;
|
|
1021
|
+
this.evictable.delete(id);
|
|
1022
|
+
this.nodes.delete(id);
|
|
1023
|
+
removed?.push(id);
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
moveHead(branch, id) {
|
|
1027
|
+
const previous = this.heads.get(branch);
|
|
1028
|
+
if (previous === id)
|
|
1029
|
+
return;
|
|
1030
|
+
if (previous !== void 0)
|
|
1031
|
+
this.removeReference(previous);
|
|
1032
|
+
this.heads.set(branch, id);
|
|
1033
|
+
this.addReference(id);
|
|
1034
|
+
}
|
|
1035
|
+
addReference(id) {
|
|
1036
|
+
this.references.set(id, (this.references.get(id) ?? 0) + 1);
|
|
1037
|
+
this.evictable.delete(id);
|
|
1038
|
+
}
|
|
1039
|
+
removeReference(id) {
|
|
1040
|
+
const next = (this.references.get(id) ?? 0) - 1;
|
|
1041
|
+
if (next > 0)
|
|
1042
|
+
this.references.set(id, next);
|
|
1043
|
+
else {
|
|
1044
|
+
this.references.delete(id);
|
|
1045
|
+
if (this.nodes.has(id))
|
|
1046
|
+
this.evictable.add(id);
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
assertBranch(branch) {
|
|
1050
|
+
if (!BRANCH_PATTERN.test(branch))
|
|
1051
|
+
throw new RangeError(`branch must match ${BRANCH_PATTERN}`);
|
|
1052
|
+
}
|
|
1053
|
+
};
|
|
1054
|
+
|
|
1055
|
+
// ../../sqlite/dist/default.js
|
|
1056
|
+
import { Database } from "@crvouga/mockingbird-service-sqlite";
|
|
1057
|
+
var createDefaultSqlite = () => new Database();
|
|
1058
|
+
var resolveSqlite = (sqlite) => sqlite ?? createDefaultSqlite();
|
|
1059
|
+
|
|
1060
|
+
// ../../sqlite/dist/migrate.js
|
|
1061
|
+
var ensureMigrationsTable = (sqlite) => {
|
|
1062
|
+
sqlite.exec(`
|
|
1063
|
+
CREATE TABLE IF NOT EXISTS schema_migrations (
|
|
1064
|
+
id TEXT PRIMARY KEY NOT NULL,
|
|
1065
|
+
applied_at INTEGER NOT NULL
|
|
1066
|
+
)
|
|
1067
|
+
`);
|
|
1068
|
+
};
|
|
1069
|
+
var migrate = (sqlite, migrations) => {
|
|
1070
|
+
ensureMigrationsTable(sqlite);
|
|
1071
|
+
const applied = new Set(sqlite.prepare("SELECT id FROM schema_migrations").all().map((row2) => row2.id));
|
|
1072
|
+
const pending = migrations.filter((migration) => !applied.has(migration.id));
|
|
1073
|
+
if (pending.length === 0)
|
|
1074
|
+
return;
|
|
1075
|
+
const insert = sqlite.prepare("INSERT INTO schema_migrations (id, applied_at) VALUES (?, ?)");
|
|
1076
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
1077
|
+
sqlite.transaction(() => {
|
|
1078
|
+
for (const migration of pending) {
|
|
1079
|
+
sqlite.exec(migration.sql);
|
|
1080
|
+
insert.run(migration.id, now);
|
|
1081
|
+
}
|
|
1082
|
+
});
|
|
1083
|
+
};
|
|
1084
|
+
|
|
1085
|
+
// ../../sqlite/dist/schema.js
|
|
1086
|
+
var CORE_MIGRATIONS = [
|
|
1087
|
+
{
|
|
1088
|
+
id: "20260322_core_records_sequences",
|
|
1089
|
+
sql: `
|
|
1090
|
+
CREATE TABLE IF NOT EXISTS mockingbird_records (
|
|
1091
|
+
namespace TEXT NOT NULL,
|
|
1092
|
+
collection TEXT NOT NULL,
|
|
1093
|
+
id TEXT NOT NULL,
|
|
1094
|
+
seq INTEGER NOT NULL,
|
|
1095
|
+
value TEXT NOT NULL,
|
|
1096
|
+
PRIMARY KEY (namespace, collection, id)
|
|
1097
|
+
);
|
|
1098
|
+
CREATE INDEX IF NOT EXISTS mockingbird_records_seq
|
|
1099
|
+
ON mockingbird_records (namespace, collection, seq);
|
|
1100
|
+
CREATE TABLE IF NOT EXISTS mockingbird_sequences (
|
|
1101
|
+
namespace TEXT NOT NULL,
|
|
1102
|
+
name TEXT NOT NULL,
|
|
1103
|
+
kind TEXT NOT NULL,
|
|
1104
|
+
value INTEGER NOT NULL,
|
|
1105
|
+
PRIMARY KEY (namespace, name, kind)
|
|
1106
|
+
);
|
|
1107
|
+
`
|
|
1108
|
+
}
|
|
1109
|
+
];
|
|
1110
|
+
var migrateCore = (sqlite) => {
|
|
1111
|
+
migrate(sqlite, CORE_MIGRATIONS);
|
|
1112
|
+
};
|
|
1113
|
+
var clearNamespace = (sqlite, namespace) => {
|
|
1114
|
+
sqlite.transaction(() => {
|
|
1115
|
+
sqlite.prepare("DELETE FROM mockingbird_records WHERE namespace = ?").run(namespace);
|
|
1116
|
+
sqlite.prepare("DELETE FROM mockingbird_sequences WHERE namespace = ?").run(namespace);
|
|
1117
|
+
});
|
|
1118
|
+
};
|
|
1119
|
+
|
|
1120
|
+
// ../../openapi/metadata/dist/types.js
|
|
1121
|
+
var EXTENSION_KEYS = {
|
|
1122
|
+
operation: "x-mockingbird",
|
|
1123
|
+
resource: "x-mockingbird-resource",
|
|
1124
|
+
resourceRef: "x-mockingbird-resource-ref",
|
|
1125
|
+
volatile: "x-mockingbird-volatile",
|
|
1126
|
+
scope: "x-mockingbird-scope",
|
|
1127
|
+
unsupported: "x-mockingbird-unsupported",
|
|
1128
|
+
parityHeader: "x-mockingbird-parity-header"
|
|
1129
|
+
};
|
|
1130
|
+
|
|
1131
|
+
// ../../openapi/metadata/dist/read.js
|
|
1132
|
+
var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1133
|
+
var extensionOf = (holder, key) => holder[key];
|
|
1134
|
+
var operationMetadata = (operation) => {
|
|
1135
|
+
const raw = extensionOf(operation, EXTENSION_KEYS.operation);
|
|
1136
|
+
const ext = isRecord2(raw) ? raw : {};
|
|
1137
|
+
const supported = ext.supported ?? true;
|
|
1138
|
+
const parity = ext.parity ?? {};
|
|
1139
|
+
return {
|
|
1140
|
+
supported,
|
|
1141
|
+
reason: typeof ext.reason === "string" ? ext.reason : void 0,
|
|
1142
|
+
parity: {
|
|
1143
|
+
enabled: supported && (parity.enabled ?? true),
|
|
1144
|
+
safe: parity.safe ?? true,
|
|
1145
|
+
reason: typeof parity.reason === "string" ? parity.reason : void 0
|
|
1146
|
+
}
|
|
1147
|
+
};
|
|
1148
|
+
};
|
|
1149
|
+
|
|
1150
|
+
// ../core/dist/service.js
|
|
1151
|
+
import { Hono } from "hono";
|
|
1152
|
+
var defineOperations = (handlers) => handlers;
|
|
1153
|
+
var OperationRegistryError = class extends Error {
|
|
1154
|
+
problems;
|
|
1155
|
+
constructor(problems) {
|
|
1156
|
+
super(`operation registry is inconsistent:
|
|
1157
|
+
${problems.map((p) => ` - ${p}`).join("\n")}`);
|
|
1158
|
+
this.problems = problems;
|
|
1159
|
+
this.name = "OperationRegistryError";
|
|
1160
|
+
}
|
|
1161
|
+
};
|
|
1162
|
+
var verifyOperations = (document2, handlers) => {
|
|
1163
|
+
const problems = [];
|
|
1164
|
+
const operations = listOperations(document2);
|
|
1165
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1166
|
+
for (const operation of operations) {
|
|
1167
|
+
if (seen.has(operation.operationId))
|
|
1168
|
+
problems.push(`duplicate operationId ${operation.operationId}`);
|
|
1169
|
+
seen.add(operation.operationId);
|
|
1170
|
+
const supported = operationMetadata(operation.operation).supported;
|
|
1171
|
+
const handler = handlers[operation.operationId];
|
|
1172
|
+
if (supported && !handler)
|
|
1173
|
+
problems.push(`supported operation ${operation.operationId} has no handler`);
|
|
1174
|
+
if (!supported && handler)
|
|
1175
|
+
problems.push(`operation ${operation.operationId} is marked unsupported but has a handler`);
|
|
1176
|
+
}
|
|
1177
|
+
for (const id of Object.keys(handlers)) {
|
|
1178
|
+
if (!seen.has(id))
|
|
1179
|
+
problems.push(`handler ${id} has no OpenAPI operation`);
|
|
1180
|
+
}
|
|
1181
|
+
return problems;
|
|
1182
|
+
};
|
|
1183
|
+
var honoPath = (template) => template.replace(/\{([^}]+)\}/g, ":$1");
|
|
1184
|
+
var routeOrder = (a, b) => {
|
|
1185
|
+
const sa = a.path.split("/");
|
|
1186
|
+
const sb = b.path.split("/");
|
|
1187
|
+
for (let i = 0; i < Math.max(sa.length, sb.length); i++) {
|
|
1188
|
+
const x = sa[i] ?? "";
|
|
1189
|
+
const y = sb[i] ?? "";
|
|
1190
|
+
const px = x.startsWith("{");
|
|
1191
|
+
const py = y.startsWith("{");
|
|
1192
|
+
if (px !== py)
|
|
1193
|
+
return px ? 1 : -1;
|
|
1194
|
+
if (x !== y)
|
|
1195
|
+
return x < y ? -1 : 1;
|
|
1196
|
+
}
|
|
1197
|
+
return 0;
|
|
1198
|
+
};
|
|
1199
|
+
var queryOf = (url) => decodeFormPairs(url.searchParams.entries());
|
|
1200
|
+
var bootSqlite = (sqlite) => {
|
|
1201
|
+
const client = resolveSqlite(sqlite);
|
|
1202
|
+
migrateCore(client);
|
|
1203
|
+
return client;
|
|
1204
|
+
};
|
|
1205
|
+
var createService = (options) => {
|
|
1206
|
+
const problems = verifyOperations(options.document, options.handlers);
|
|
1207
|
+
if (problems.length > 0)
|
|
1208
|
+
throw new OperationRegistryError(problems);
|
|
1209
|
+
migrateCore(options.sqlite);
|
|
1210
|
+
const now = options.now ?? (() => Date.now());
|
|
1211
|
+
const app = new Hono();
|
|
1212
|
+
app.notFound((c) => options.notFound(c.req.raw));
|
|
1213
|
+
app.onError((error, c) => options.onError(error, c.req.raw));
|
|
1214
|
+
const operations = [...listOperations(options.document)].sort(routeOrder);
|
|
1215
|
+
for (const operation of operations) {
|
|
1216
|
+
const metadata = operationMetadata(operation.operation);
|
|
1217
|
+
const handler = options.handlers[operation.operationId];
|
|
1218
|
+
const route = async (c) => {
|
|
1219
|
+
const request = c.req.raw;
|
|
1220
|
+
if (!metadata.supported || !handler) {
|
|
1221
|
+
return options.unsupported ? options.unsupported(request, operation) : options.notFound(request);
|
|
1222
|
+
}
|
|
1223
|
+
const url = new URL(request.url);
|
|
1224
|
+
const context = {
|
|
1225
|
+
request,
|
|
1226
|
+
url,
|
|
1227
|
+
params: c.req.param(),
|
|
1228
|
+
query: queryOf(url),
|
|
1229
|
+
body: await readBody(request),
|
|
1230
|
+
sqlite: options.sqlite,
|
|
1231
|
+
namespace: options.namespace,
|
|
1232
|
+
operation,
|
|
1233
|
+
document: options.document,
|
|
1234
|
+
now
|
|
1235
|
+
};
|
|
1236
|
+
const short = await options.before?.(context);
|
|
1237
|
+
if (short)
|
|
1238
|
+
return short;
|
|
1239
|
+
return handler(context);
|
|
1240
|
+
};
|
|
1241
|
+
app.on(operation.method.toUpperCase(), honoPath(operation.path), route);
|
|
1242
|
+
}
|
|
1243
|
+
return {
|
|
1244
|
+
app,
|
|
1245
|
+
sqlite: options.sqlite,
|
|
1246
|
+
namespace: options.namespace,
|
|
1247
|
+
fetch: async (request) => app.fetch(request),
|
|
1248
|
+
reset: async () => {
|
|
1249
|
+
clearNamespace(options.sqlite, options.namespace);
|
|
1250
|
+
}
|
|
1251
|
+
};
|
|
1252
|
+
};
|
|
1253
|
+
|
|
1254
|
+
// ../core/dist/snapshot.js
|
|
1255
|
+
var snapshotNamespace = (sqlite, namespace) => ({
|
|
1256
|
+
namespace,
|
|
1257
|
+
records: sqlite.prepare("SELECT collection, id, seq, value FROM mockingbird_records WHERE namespace = ? ORDER BY collection, seq").all(namespace),
|
|
1258
|
+
sequences: sqlite.prepare("SELECT name, kind, value FROM mockingbird_sequences WHERE namespace = ? ORDER BY name, kind").all(namespace)
|
|
1259
|
+
});
|
|
1260
|
+
var restoreNamespace = (sqlite, namespace, snapshot) => {
|
|
1261
|
+
sqlite.transaction(() => {
|
|
1262
|
+
sqlite.prepare("DELETE FROM mockingbird_records WHERE namespace = ?").run(namespace);
|
|
1263
|
+
sqlite.prepare("DELETE FROM mockingbird_sequences WHERE namespace = ?").run(namespace);
|
|
1264
|
+
const record = sqlite.prepare("INSERT INTO mockingbird_records (namespace, collection, id, seq, value) VALUES (?, ?, ?, ?, ?)");
|
|
1265
|
+
for (const row2 of snapshot.records) {
|
|
1266
|
+
record.run(namespace, row2.collection, row2.id, row2.seq, row2.value);
|
|
1267
|
+
}
|
|
1268
|
+
const sequence = sqlite.prepare("INSERT INTO mockingbird_sequences (namespace, name, kind, value) VALUES (?, ?, ?, ?)");
|
|
1269
|
+
for (const row2 of snapshot.sequences) {
|
|
1270
|
+
sequence.run(namespace, row2.name, row2.kind, row2.value);
|
|
1271
|
+
}
|
|
1272
|
+
});
|
|
1273
|
+
};
|
|
1274
|
+
|
|
1275
|
+
// ../core/dist/version.js
|
|
1276
|
+
var PACKAGE_VERSION = true ? "0.1.0" : UNRELEASED_VERSION;
|
|
1277
|
+
|
|
1278
|
+
// ../core/dist/signing.js
|
|
1279
|
+
var encoder = new TextEncoder();
|
|
1280
|
+
var toBase64 = (bytes) => {
|
|
1281
|
+
let binary = "";
|
|
1282
|
+
for (const byte of bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes)) {
|
|
1283
|
+
binary += String.fromCharCode(byte);
|
|
1284
|
+
}
|
|
1285
|
+
return btoa(binary);
|
|
1286
|
+
};
|
|
1287
|
+
var fromBase64 = (value) => Uint8Array.from(atob(value), (char) => char.charCodeAt(0));
|
|
1288
|
+
|
|
1289
|
+
// ../core/dist/webhooks.js
|
|
1290
|
+
var json2 = (status, body) => new Response(JSON.stringify(body), { status, headers: { "content-type": "application/json" } });
|
|
1291
|
+
var adminError2 = (status, message) => json2(status, { error: { type: "mockingbird_admin", message } });
|
|
1292
|
+
var isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1293
|
+
var parseEndpoint = (value) => {
|
|
1294
|
+
if (!isRecord3(value) || typeof value.url !== "string")
|
|
1295
|
+
return "each endpoint needs a url";
|
|
1296
|
+
try {
|
|
1297
|
+
new URL(value.url);
|
|
1298
|
+
} catch {
|
|
1299
|
+
return `not a URL: ${value.url}`;
|
|
1300
|
+
}
|
|
1301
|
+
const endpoint = { url: value.url };
|
|
1302
|
+
if (typeof value.id === "string")
|
|
1303
|
+
endpoint.id = value.id;
|
|
1304
|
+
if (typeof value.secret === "string")
|
|
1305
|
+
endpoint.secret = value.secret;
|
|
1306
|
+
if (typeof value.signUrl === "string")
|
|
1307
|
+
endpoint.signUrl = value.signUrl;
|
|
1308
|
+
const events = value.events ?? value.enabledEvents;
|
|
1309
|
+
if (Array.isArray(events))
|
|
1310
|
+
endpoint.events = events.map(String);
|
|
1311
|
+
if (isRecord3(value.tags)) {
|
|
1312
|
+
endpoint.tags = Object.fromEntries(Object.entries(value.tags).map(([k, v]) => [k, String(v)]));
|
|
1313
|
+
}
|
|
1314
|
+
if (typeof value.account === "string")
|
|
1315
|
+
endpoint.tags = { ...endpoint.tags, account: value.account };
|
|
1316
|
+
if (isRecord3(value.headers)) {
|
|
1317
|
+
endpoint.headers = Object.fromEntries(Object.entries(value.headers).map(([k, v]) => [k, String(v)]));
|
|
1318
|
+
}
|
|
1319
|
+
return endpoint;
|
|
1320
|
+
};
|
|
1321
|
+
var webhookAdminRoutes = (hub) => ({
|
|
1322
|
+
"GET /webhooks": ({ url, namespace }) => json2(200, {
|
|
1323
|
+
deliveries: hub.deliveries(url.searchParams.get("all") === "1" ? void 0 : namespace).filter((d) => {
|
|
1324
|
+
const type = url.searchParams.get("type");
|
|
1325
|
+
return type === null || d.type === type;
|
|
1326
|
+
})
|
|
1327
|
+
}),
|
|
1328
|
+
"GET /webhooks/events": ({ url, namespace }) => {
|
|
1329
|
+
const type = url.searchParams.get("type");
|
|
1330
|
+
return json2(200, {
|
|
1331
|
+
events: hub.messages(url.searchParams.get("all") === "1" ? void 0 : namespace).filter((m) => type === null || m.type === type).map((m) => ({ ...m, payload: parsePayload(m) }))
|
|
1332
|
+
});
|
|
1333
|
+
},
|
|
1334
|
+
"POST /webhooks/:id/replay": async ({ params }) => {
|
|
1335
|
+
const replayed = await hub.replay(params.id);
|
|
1336
|
+
return replayed ? json2(200, replayed) : adminError2(404, `no delivery ${params.id}`);
|
|
1337
|
+
},
|
|
1338
|
+
"POST /webhooks/flush": async () => {
|
|
1339
|
+
await hub.flush();
|
|
1340
|
+
return json2(200, { status: "ok" });
|
|
1341
|
+
},
|
|
1342
|
+
"POST /webhooks/faults": ({ body, namespace }) => {
|
|
1343
|
+
if (!isRecord3(body) || !["duplicate", "reorder", "drop"].includes(String(body.mode))) {
|
|
1344
|
+
return adminError2(400, "mode must be duplicate, reorder or drop");
|
|
1345
|
+
}
|
|
1346
|
+
const fault = { mode: body.mode };
|
|
1347
|
+
if (typeof body.count === "number")
|
|
1348
|
+
fault.count = body.count;
|
|
1349
|
+
hub.fault(namespace, fault);
|
|
1350
|
+
return json2(201, { namespace, ...fault });
|
|
1351
|
+
},
|
|
1352
|
+
"GET /webhook-endpoints": ({ namespace }) => json2(200, {
|
|
1353
|
+
endpoints: hub.endpoints(namespace).map(({ secret, ...rest }) => ({
|
|
1354
|
+
...rest,
|
|
1355
|
+
secret: secret ? "(set)" : null
|
|
1356
|
+
}))
|
|
1357
|
+
}),
|
|
1358
|
+
"PUT /webhook-endpoints": ({ body, namespace }) => {
|
|
1359
|
+
const list = Array.isArray(body) ? body : isRecord3(body) ? body.endpoints : void 0;
|
|
1360
|
+
if (!Array.isArray(list))
|
|
1361
|
+
return adminError2(400, "expected [{url, secret?, events?}]");
|
|
1362
|
+
const parsed = [];
|
|
1363
|
+
for (const each of list) {
|
|
1364
|
+
const endpoint = parseEndpoint(each);
|
|
1365
|
+
if (typeof endpoint === "string")
|
|
1366
|
+
return adminError2(400, endpoint);
|
|
1367
|
+
parsed.push(endpoint);
|
|
1368
|
+
}
|
|
1369
|
+
const set = hub.setEndpoints(namespace, parsed);
|
|
1370
|
+
return json2(200, { endpoints: set.map((e) => ({ ...e, secret: e.secret ? "(set)" : null })) });
|
|
1371
|
+
},
|
|
1372
|
+
"DELETE /webhook-endpoints": ({ namespace }) => {
|
|
1373
|
+
hub.setEndpoints(namespace, []);
|
|
1374
|
+
return json2(200, { status: "ok" });
|
|
1375
|
+
}
|
|
1376
|
+
});
|
|
1377
|
+
var parsePayload = (message) => {
|
|
1378
|
+
if (message.contentType.startsWith("application/json")) {
|
|
1379
|
+
try {
|
|
1380
|
+
return JSON.parse(message.body);
|
|
1381
|
+
} catch {
|
|
1382
|
+
return message.body;
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1385
|
+
if (message.contentType.startsWith("application/x-www-form-urlencoded")) {
|
|
1386
|
+
return Object.fromEntries(new URLSearchParams(message.body));
|
|
1387
|
+
}
|
|
1388
|
+
return message.body;
|
|
1389
|
+
};
|
|
1390
|
+
|
|
1391
|
+
// ../core/dist/runtime.js
|
|
1392
|
+
var MOCKINGBIRD_HEADER = "x-mockingbird";
|
|
1393
|
+
var BRANCH_HEADER = "x-mockingbird-branch";
|
|
1394
|
+
var AT_HEADER = "x-mockingbird-at";
|
|
1395
|
+
var CHECKPOINT_HEADER = "x-mockingbird-checkpoint";
|
|
1396
|
+
var DEFAULT_NAMESPACE = "default";
|
|
1397
|
+
var NAMESPACE_PATTERN = /^[A-Za-z0-9_.-]{1,64}$/;
|
|
1398
|
+
var PATH_PREFIX = /^\/ns\/([^/]+)(\/.*)?$/;
|
|
1399
|
+
var BRANCH_PATTERN2 = /^[A-Za-z0-9_.-]{1,64}$/;
|
|
1400
|
+
var MUTATING_METHODS = /* @__PURE__ */ new Set(["POST", "PUT", "PATCH", "DELETE"]);
|
|
1401
|
+
var effects = /* @__PURE__ */ new WeakMap();
|
|
1402
|
+
var reuseSorted = (fresh, previous, compare, equal) => {
|
|
1403
|
+
if (!previous || previous.length === 0)
|
|
1404
|
+
return fresh.map((row2) => Object.freeze(row2));
|
|
1405
|
+
const result = new Array(fresh.length);
|
|
1406
|
+
let unchanged = fresh.length === previous.length;
|
|
1407
|
+
let oldIndex = 0;
|
|
1408
|
+
for (let index = 0; index < fresh.length; index++) {
|
|
1409
|
+
const row2 = fresh[index];
|
|
1410
|
+
while (oldIndex < previous.length && compare(previous[oldIndex], row2) < 0) {
|
|
1411
|
+
oldIndex++;
|
|
1412
|
+
}
|
|
1413
|
+
const old = previous[oldIndex];
|
|
1414
|
+
result[index] = old !== void 0 && compare(old, row2) === 0 && equal(old, row2) ? old : Object.freeze(row2);
|
|
1415
|
+
if (result[index] !== previous[index])
|
|
1416
|
+
unchanged = false;
|
|
1417
|
+
}
|
|
1418
|
+
return unchanged ? previous : result;
|
|
1419
|
+
};
|
|
1420
|
+
var faultEffects = (request) => (effects.get(request) ?? []).filter((e) => e !== void 0);
|
|
1421
|
+
var faultEffect = (request, name) => faultEffects(request).find((e) => e.name === name)?.params;
|
|
1422
|
+
var DroppedConnectionError = class extends TypeError {
|
|
1423
|
+
code = "MOCKINGBIRD_DROP";
|
|
1424
|
+
constructor() {
|
|
1425
|
+
super("fetch failed: connection dropped by Mockingbird fault");
|
|
1426
|
+
this.name = "TypeError";
|
|
1427
|
+
}
|
|
1428
|
+
};
|
|
1429
|
+
var operationMatcher = (document2) => {
|
|
1430
|
+
const matchers = listOperations(document2).map((operation) => ({
|
|
1431
|
+
operationId: operation.operationId,
|
|
1432
|
+
method: operation.method.toUpperCase(),
|
|
1433
|
+
pattern: new RegExp(`^${operation.path.split("/").map((segment) => segment.startsWith("{") ? "[^/]+" : segment.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("/")}/?$`),
|
|
1434
|
+
params: (operation.path.match(/\{/g) ?? []).length
|
|
1435
|
+
})).sort((a, b) => a.params - b.params);
|
|
1436
|
+
return (request, path) => matchers.find((m) => m.method === request.method && m.pattern.test(path))?.operationId;
|
|
1437
|
+
};
|
|
1438
|
+
var createRuntime = (options) => {
|
|
1439
|
+
const sqlite = bootSqlite(options.sqlite);
|
|
1440
|
+
const clock = options.clock ?? createClock();
|
|
1441
|
+
const rng = createRng(options.seed ?? 0);
|
|
1442
|
+
const wallNow = options.io?.wallNow ?? Date.now;
|
|
1443
|
+
const monotonicNow = options.io?.monotonicNow ?? (() => performance.now());
|
|
1444
|
+
const sleep = options.io?.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
1445
|
+
const faults = createFaultRegistry(createRng(options.seed ?? 0), sleep);
|
|
1446
|
+
const metrics = createMetrics();
|
|
1447
|
+
const journal = createJournal(options.journalSize ?? DEFAULT_JOURNAL_SIZE);
|
|
1448
|
+
const version = options.version ?? PACKAGE_VERSION;
|
|
1449
|
+
const instances = /* @__PURE__ */ new Map();
|
|
1450
|
+
const publicNamespaces = /* @__PURE__ */ new Set();
|
|
1451
|
+
const branchRngs = /* @__PURE__ */ new Map();
|
|
1452
|
+
const timelines = /* @__PURE__ */ new Map();
|
|
1453
|
+
const branchStorage = /* @__PURE__ */ new Map();
|
|
1454
|
+
const captured = /* @__PURE__ */ new Map();
|
|
1455
|
+
const credentials = createCredentialRegistry();
|
|
1456
|
+
const operationIdFor = options.document ? operationMatcher(options.document) : () => void 0;
|
|
1457
|
+
const storageNamespace = (name) => name === DEFAULT_NAMESPACE ? options.name : `${options.name}:${name}`;
|
|
1458
|
+
const instanceFor = (key, publicNamespace = key, isolatedRng) => {
|
|
1459
|
+
const existing = instances.get(key);
|
|
1460
|
+
if (existing)
|
|
1461
|
+
return existing;
|
|
1462
|
+
if (!NAMESPACE_PATTERN.test(key) || !NAMESPACE_PATTERN.test(publicNamespace)) {
|
|
1463
|
+
throw new RangeError(`namespace must match ${NAMESPACE_PATTERN}: ${JSON.stringify(publicNamespace)}`);
|
|
1464
|
+
}
|
|
1465
|
+
const created = options.create({
|
|
1466
|
+
namespace: storageNamespace(key),
|
|
1467
|
+
publicNamespace,
|
|
1468
|
+
sqlite,
|
|
1469
|
+
clock,
|
|
1470
|
+
rng: isolatedRng ?? rng
|
|
1471
|
+
});
|
|
1472
|
+
instances.set(key, created);
|
|
1473
|
+
publicNamespaces.add(publicNamespace);
|
|
1474
|
+
if (isolatedRng)
|
|
1475
|
+
branchRngs.set(key, isolatedRng);
|
|
1476
|
+
return created;
|
|
1477
|
+
};
|
|
1478
|
+
const instance = (name = DEFAULT_NAMESPACE) => instanceFor(name);
|
|
1479
|
+
const capture = (storage) => {
|
|
1480
|
+
const fresh = snapshotNamespace(sqlite, storageNamespace(storage));
|
|
1481
|
+
const previous = captured.get(storage);
|
|
1482
|
+
const snapshot2 = {
|
|
1483
|
+
namespace: fresh.namespace,
|
|
1484
|
+
records: reuseSorted(fresh.records, previous?.records, (left, right) => left.collection < right.collection ? -1 : left.collection > right.collection ? 1 : left.seq - right.seq, (left, right) => left.id === right.id && left.value === right.value),
|
|
1485
|
+
sequences: reuseSorted(fresh.sequences, previous?.sequences, (left, right) => left.name < right.name ? -1 : left.name > right.name ? 1 : left.kind < right.kind ? -1 : left.kind > right.kind ? 1 : 0, (left, right) => left.value === right.value)
|
|
1486
|
+
};
|
|
1487
|
+
Object.freeze(snapshot2.records);
|
|
1488
|
+
Object.freeze(snapshot2.sequences);
|
|
1489
|
+
Object.freeze(snapshot2);
|
|
1490
|
+
captured.set(storage, snapshot2);
|
|
1491
|
+
return Object.freeze({
|
|
1492
|
+
snapshot: snapshot2,
|
|
1493
|
+
clock: Object.freeze(clock.state()),
|
|
1494
|
+
rngState: (branchRngs.get(storage) ?? rng).state()
|
|
1495
|
+
});
|
|
1496
|
+
};
|
|
1497
|
+
const timeline = (name = DEFAULT_NAMESPACE) => {
|
|
1498
|
+
let found = timelines.get(name);
|
|
1499
|
+
if (found)
|
|
1500
|
+
return found;
|
|
1501
|
+
instance(name);
|
|
1502
|
+
found = new Timeline({
|
|
1503
|
+
now: clock.now,
|
|
1504
|
+
...options.maxCheckpoints !== void 0 ? { maxCheckpoints: options.maxCheckpoints } : {}
|
|
1505
|
+
});
|
|
1506
|
+
found.commit(capture(name));
|
|
1507
|
+
timelines.set(name, found);
|
|
1508
|
+
return found;
|
|
1509
|
+
};
|
|
1510
|
+
const physicalBranch = (namespace, branch2) => {
|
|
1511
|
+
if (branch2 === "main")
|
|
1512
|
+
return namespace;
|
|
1513
|
+
const mapKey = `${namespace}\0${branch2}`;
|
|
1514
|
+
const existing = branchStorage.get(mapKey);
|
|
1515
|
+
if (existing)
|
|
1516
|
+
return existing;
|
|
1517
|
+
const key = `branch_${seedFrom(`${options.name}\0${namespace}\0${branch2}`).toString(36)}`;
|
|
1518
|
+
branchStorage.set(mapKey, key);
|
|
1519
|
+
return key;
|
|
1520
|
+
};
|
|
1521
|
+
const ensureBranch = (namespace, branch2, at) => {
|
|
1522
|
+
if (!BRANCH_PATTERN2.test(branch2))
|
|
1523
|
+
throw new RangeError(`branch must match ${BRANCH_PATTERN2}`);
|
|
1524
|
+
const history = timeline(namespace);
|
|
1525
|
+
if (branch2 === "main") {
|
|
1526
|
+
if (at !== void 0) {
|
|
1527
|
+
const point = history.checkout("main", at);
|
|
1528
|
+
restoreNamespace(sqlite, storageNamespace(namespace), point.value.snapshot);
|
|
1529
|
+
captured.set(namespace, point.value.snapshot);
|
|
1530
|
+
rng.setState(point.value.rngState);
|
|
1531
|
+
clock.set(point.value.clock.now);
|
|
1532
|
+
if (point.value.clock.frozen)
|
|
1533
|
+
clock.freeze();
|
|
1534
|
+
else
|
|
1535
|
+
clock.unfreeze();
|
|
1536
|
+
}
|
|
1537
|
+
return namespace;
|
|
1538
|
+
}
|
|
1539
|
+
const storage = physicalBranch(namespace, branch2);
|
|
1540
|
+
if (!history.hasBranch(branch2)) {
|
|
1541
|
+
if (at === void 0)
|
|
1542
|
+
history.commit(capture(namespace));
|
|
1543
|
+
const point = history.fork(branch2, at === void 0 ? {} : { from: at });
|
|
1544
|
+
const branchRng = createRng(options.seed ?? 0);
|
|
1545
|
+
if (point)
|
|
1546
|
+
branchRng.setState(point.value.rngState);
|
|
1547
|
+
instanceFor(storage, namespace, branchRng);
|
|
1548
|
+
if (point)
|
|
1549
|
+
restoreNamespace(sqlite, storageNamespace(storage), point.value.snapshot);
|
|
1550
|
+
if (point)
|
|
1551
|
+
captured.set(storage, point.value.snapshot);
|
|
1552
|
+
} else if (at !== void 0 && history.head(branch2)?.id !== at) {
|
|
1553
|
+
const point = history.checkout(branch2, at);
|
|
1554
|
+
if (!instances.has(storage)) {
|
|
1555
|
+
const branchRng = createRng(options.seed ?? 0);
|
|
1556
|
+
branchRng.setState(point.value.rngState);
|
|
1557
|
+
instanceFor(storage, namespace, branchRng);
|
|
1558
|
+
}
|
|
1559
|
+
branchRngs.get(storage)?.setState(point.value.rngState);
|
|
1560
|
+
restoreNamespace(sqlite, storageNamespace(storage), point.value.snapshot);
|
|
1561
|
+
captured.set(storage, point.value.snapshot);
|
|
1562
|
+
} else {
|
|
1563
|
+
if (!instances.has(storage)) {
|
|
1564
|
+
const point = history.head(branch2);
|
|
1565
|
+
const branchRng = createRng(options.seed ?? 0);
|
|
1566
|
+
if (point)
|
|
1567
|
+
branchRng.setState(point.value.rngState);
|
|
1568
|
+
instanceFor(storage, namespace, branchRng);
|
|
1569
|
+
}
|
|
1570
|
+
}
|
|
1571
|
+
return storage;
|
|
1572
|
+
};
|
|
1573
|
+
const checkpoint = (namespace = DEFAULT_NAMESPACE, branch2 = "main") => {
|
|
1574
|
+
const storage = ensureBranch(namespace, branch2);
|
|
1575
|
+
return timeline(namespace).commit(capture(storage), { branch: branch2 });
|
|
1576
|
+
};
|
|
1577
|
+
const branch = (name, branchOptions = {}) => {
|
|
1578
|
+
const namespace = branchOptions.namespace ?? DEFAULT_NAMESPACE;
|
|
1579
|
+
ensureBranch(namespace, name, branchOptions.at);
|
|
1580
|
+
const head = timeline(namespace).head(name);
|
|
1581
|
+
if (!head)
|
|
1582
|
+
throw new RangeError(`branch ${name} has no checkpoint`);
|
|
1583
|
+
return head;
|
|
1584
|
+
};
|
|
1585
|
+
const checkout = (checkpointId, checkoutOptions = {}) => {
|
|
1586
|
+
const namespace = checkoutOptions.namespace ?? DEFAULT_NAMESPACE;
|
|
1587
|
+
const branchName = checkoutOptions.branch ?? "main";
|
|
1588
|
+
const history = timeline(namespace);
|
|
1589
|
+
const point = history.checkout(branchName, checkpointId);
|
|
1590
|
+
const storage = ensureBranch(namespace, branchName);
|
|
1591
|
+
restoreNamespace(sqlite, storageNamespace(storage), point.value.snapshot);
|
|
1592
|
+
captured.set(storage, point.value.snapshot);
|
|
1593
|
+
clock.set(point.value.clock.now);
|
|
1594
|
+
if (point.value.clock.frozen)
|
|
1595
|
+
clock.freeze();
|
|
1596
|
+
else
|
|
1597
|
+
clock.unfreeze();
|
|
1598
|
+
(branchRngs.get(storage) ?? rng).setState(point.value.rngState);
|
|
1599
|
+
};
|
|
1600
|
+
const reset = async (name = DEFAULT_NAMESPACE) => {
|
|
1601
|
+
if (name === "*") {
|
|
1602
|
+
options.webhooks?.clear();
|
|
1603
|
+
for (const each of instances.values())
|
|
1604
|
+
await each.reset();
|
|
1605
|
+
timelines.clear();
|
|
1606
|
+
branchStorage.clear();
|
|
1607
|
+
branchRngs.clear();
|
|
1608
|
+
captured.clear();
|
|
1609
|
+
return;
|
|
1610
|
+
}
|
|
1611
|
+
options.webhooks?.clear(name);
|
|
1612
|
+
const target = instances.get(name);
|
|
1613
|
+
if (target)
|
|
1614
|
+
await target.reset();
|
|
1615
|
+
else
|
|
1616
|
+
clearNamespace(sqlite, storageNamespace(name));
|
|
1617
|
+
for (const [mapping, storage] of branchStorage) {
|
|
1618
|
+
if (!mapping.startsWith(`${name}\0`))
|
|
1619
|
+
continue;
|
|
1620
|
+
const branchInstance = instances.get(storage);
|
|
1621
|
+
if (branchInstance)
|
|
1622
|
+
await branchInstance.reset();
|
|
1623
|
+
else
|
|
1624
|
+
clearNamespace(sqlite, storageNamespace(storage));
|
|
1625
|
+
branchStorage.delete(mapping);
|
|
1626
|
+
branchRngs.delete(storage);
|
|
1627
|
+
captured.delete(storage);
|
|
1628
|
+
}
|
|
1629
|
+
timelines.delete(name);
|
|
1630
|
+
captured.delete(name);
|
|
1631
|
+
};
|
|
1632
|
+
const snapshot = (name = DEFAULT_NAMESPACE) => {
|
|
1633
|
+
return checkpoint(name, "main").value.snapshot;
|
|
1634
|
+
};
|
|
1635
|
+
const restore = (from, name = DEFAULT_NAMESPACE) => {
|
|
1636
|
+
instance(name);
|
|
1637
|
+
restoreNamespace(sqlite, storageNamespace(name), from);
|
|
1638
|
+
captured.set(name, from);
|
|
1639
|
+
const history = timelines.get(name);
|
|
1640
|
+
if (history)
|
|
1641
|
+
history.commit(capture(name), { branch: "main" });
|
|
1642
|
+
else
|
|
1643
|
+
timeline(name);
|
|
1644
|
+
};
|
|
1645
|
+
const runtime = {
|
|
1646
|
+
name: options.name,
|
|
1647
|
+
sqlite,
|
|
1648
|
+
clock,
|
|
1649
|
+
faults,
|
|
1650
|
+
metrics,
|
|
1651
|
+
journal,
|
|
1652
|
+
rng,
|
|
1653
|
+
credentials,
|
|
1654
|
+
webhooks: options.webhooks,
|
|
1655
|
+
applyPreset: (name, namespace = DEFAULT_NAMESPACE, overrides = {}) => {
|
|
1656
|
+
const preset = options.presets?.[name];
|
|
1657
|
+
if (!preset)
|
|
1658
|
+
throw new RangeError(`no fault preset ${JSON.stringify(name)}`);
|
|
1659
|
+
const added = (preset.rules ?? []).map((rule, index) => faults.add({
|
|
1660
|
+
namespace,
|
|
1661
|
+
...rule,
|
|
1662
|
+
...overrides,
|
|
1663
|
+
preset: name,
|
|
1664
|
+
id: `${overrides.id ?? name}${(preset.rules ?? []).length > 1 ? `_${index + 1}` : ""}`
|
|
1665
|
+
}));
|
|
1666
|
+
if (preset.webhook && options.webhooks) {
|
|
1667
|
+
options.webhooks.fault(namespace, {
|
|
1668
|
+
...preset.webhook,
|
|
1669
|
+
...overrides.count !== void 0 ? { count: overrides.count } : {}
|
|
1670
|
+
});
|
|
1671
|
+
}
|
|
1672
|
+
return added;
|
|
1673
|
+
},
|
|
1674
|
+
instance,
|
|
1675
|
+
namespaces: () => [...publicNamespaces].sort(),
|
|
1676
|
+
reset,
|
|
1677
|
+
snapshot,
|
|
1678
|
+
restore,
|
|
1679
|
+
checkpoint,
|
|
1680
|
+
branch,
|
|
1681
|
+
checkout,
|
|
1682
|
+
timeline,
|
|
1683
|
+
fetch: async (incoming) => {
|
|
1684
|
+
let request = incoming;
|
|
1685
|
+
const prefixed = PATH_PREFIX.exec(new URL(request.url).pathname);
|
|
1686
|
+
if (prefixed) {
|
|
1687
|
+
const url2 = new URL(request.url);
|
|
1688
|
+
url2.pathname = prefixed[2] ?? "/";
|
|
1689
|
+
const headers = new Headers(request.headers);
|
|
1690
|
+
if (!headers.has(NAMESPACE_HEADER)) {
|
|
1691
|
+
headers.set(NAMESPACE_HEADER, decodeURIComponent(prefixed[1]));
|
|
1692
|
+
}
|
|
1693
|
+
const hasBody = request.method !== "GET" && request.method !== "HEAD";
|
|
1694
|
+
request = new Request(url2, {
|
|
1695
|
+
method: request.method,
|
|
1696
|
+
headers,
|
|
1697
|
+
...hasBody ? { body: await request.arrayBuffer() } : {},
|
|
1698
|
+
signal: request.signal
|
|
1699
|
+
});
|
|
1700
|
+
}
|
|
1701
|
+
let namespace = control.namespaceOf(request);
|
|
1702
|
+
if (!request.headers.has(NAMESPACE_HEADER) && options.credential) {
|
|
1703
|
+
const credential = options.credential(request);
|
|
1704
|
+
const mapped = credential !== void 0 ? credentials.get(credential) : void 0;
|
|
1705
|
+
if (mapped !== void 0)
|
|
1706
|
+
namespace = mapped;
|
|
1707
|
+
}
|
|
1708
|
+
const selectedBranch = request.headers.get(BRANCH_HEADER) ?? "main";
|
|
1709
|
+
const at = request.headers.get(AT_HEADER) ?? void 0;
|
|
1710
|
+
const stamp = (response2) => {
|
|
1711
|
+
const value = NAMESPACE_PATTERN.test(namespace) ? `${options.name}@${version}; ns=${namespace}` : `${options.name}@${version}`;
|
|
1712
|
+
try {
|
|
1713
|
+
response2.headers.set(MOCKINGBIRD_HEADER, value);
|
|
1714
|
+
return response2;
|
|
1715
|
+
} catch {
|
|
1716
|
+
const copy = new Response(response2.body, response2);
|
|
1717
|
+
copy.headers.set(MOCKINGBIRD_HEADER, value);
|
|
1718
|
+
return copy;
|
|
1719
|
+
}
|
|
1720
|
+
};
|
|
1721
|
+
const handled = await control.handle(request);
|
|
1722
|
+
if (handled)
|
|
1723
|
+
return stamp(handled);
|
|
1724
|
+
const started = monotonicNow();
|
|
1725
|
+
const url = new URL(request.url);
|
|
1726
|
+
const operationId = operationIdFor(request, url.pathname);
|
|
1727
|
+
const log = (status, faultId, response2) => {
|
|
1728
|
+
const noted = response2 ? responseNotes(response2) : void 0;
|
|
1729
|
+
const entry = {
|
|
1730
|
+
service: options.name,
|
|
1731
|
+
namespace,
|
|
1732
|
+
operationId,
|
|
1733
|
+
method: request.method,
|
|
1734
|
+
path: url.pathname,
|
|
1735
|
+
status,
|
|
1736
|
+
durationMs: Math.round((monotonicNow() - started) * 100) / 100,
|
|
1737
|
+
unmatched: options.document !== void 0 && operationId === void 0,
|
|
1738
|
+
...faultId !== void 0 ? { faultId } : {},
|
|
1739
|
+
...noted?.ids && Object.keys(noted.ids).length > 0 ? { ids: noted.ids } : {},
|
|
1740
|
+
...noted?.adopted ? { adopted: true } : {}
|
|
1741
|
+
};
|
|
1742
|
+
metrics.record(entry);
|
|
1743
|
+
journal.record({ ...entry, at: new Date(clock.now()).toISOString() });
|
|
1744
|
+
options.onLog?.(entry);
|
|
1745
|
+
};
|
|
1746
|
+
if (!NAMESPACE_PATTERN.test(namespace)) {
|
|
1747
|
+
log(400);
|
|
1748
|
+
return stamp(new Response(JSON.stringify({
|
|
1749
|
+
error: {
|
|
1750
|
+
type: "mockingbird_admin",
|
|
1751
|
+
message: `${NAMESPACE_HEADER} must match ${NAMESPACE_PATTERN}`
|
|
1752
|
+
}
|
|
1753
|
+
}), { status: 400, headers: { "content-type": "application/json" } }));
|
|
1754
|
+
}
|
|
1755
|
+
if (!BRANCH_PATTERN2.test(selectedBranch)) {
|
|
1756
|
+
log(400);
|
|
1757
|
+
return stamp(adminFail(400, `${BRANCH_HEADER} must match ${BRANCH_PATTERN2}`));
|
|
1758
|
+
}
|
|
1759
|
+
let storage;
|
|
1760
|
+
try {
|
|
1761
|
+
if (at !== void 0 && selectedBranch === "main" && !MUTATING_METHODS.has(request.method)) {
|
|
1762
|
+
const point = timeline(namespace).get(at);
|
|
1763
|
+
storage = physicalBranch(namespace, `at_${at}`);
|
|
1764
|
+
let viewRng = branchRngs.get(storage);
|
|
1765
|
+
if (!viewRng) {
|
|
1766
|
+
viewRng = createRng(options.seed ?? 0);
|
|
1767
|
+
instanceFor(storage, namespace, viewRng);
|
|
1768
|
+
}
|
|
1769
|
+
viewRng.setState(point.value.rngState);
|
|
1770
|
+
restoreNamespace(sqlite, storageNamespace(storage), point.value.snapshot);
|
|
1771
|
+
captured.set(storage, point.value.snapshot);
|
|
1772
|
+
} else {
|
|
1773
|
+
storage = ensureBranch(namespace, selectedBranch, at);
|
|
1774
|
+
}
|
|
1775
|
+
} catch (error) {
|
|
1776
|
+
log(409);
|
|
1777
|
+
return stamp(adminFail(409, error instanceof Error ? error.message : String(error)));
|
|
1778
|
+
}
|
|
1779
|
+
const hits = await faults.take({
|
|
1780
|
+
operationId,
|
|
1781
|
+
method: request.method,
|
|
1782
|
+
path: url.pathname,
|
|
1783
|
+
namespace
|
|
1784
|
+
});
|
|
1785
|
+
const final = hits.find((hit) => hit.drop || hit.response);
|
|
1786
|
+
if (final?.drop) {
|
|
1787
|
+
log(0, final.id);
|
|
1788
|
+
throw new DroppedConnectionError();
|
|
1789
|
+
}
|
|
1790
|
+
if (final?.response) {
|
|
1791
|
+
log(final.response.status, final.id);
|
|
1792
|
+
return stamp(final.response);
|
|
1793
|
+
}
|
|
1794
|
+
const fired = hits.filter((hit) => hit.effect !== void 0);
|
|
1795
|
+
if (fired.length > 0)
|
|
1796
|
+
effects.set(request, fired.map((hit) => hit.effect));
|
|
1797
|
+
let response = await instanceFor(storage, namespace).fetch(request);
|
|
1798
|
+
if (MUTATING_METHODS.has(request.method) && response.status >= 200 && response.status < 400) {
|
|
1799
|
+
const point = timeline(namespace).commit(capture(storage), { branch: selectedBranch });
|
|
1800
|
+
response = mutableResponse(response);
|
|
1801
|
+
response.headers.set(CHECKPOINT_HEADER, point.id);
|
|
1802
|
+
}
|
|
1803
|
+
if (selectedBranch !== "main") {
|
|
1804
|
+
response = mutableResponse(response);
|
|
1805
|
+
response.headers.set(BRANCH_HEADER, selectedBranch);
|
|
1806
|
+
}
|
|
1807
|
+
if (at !== void 0) {
|
|
1808
|
+
response = mutableResponse(response);
|
|
1809
|
+
response.headers.set(AT_HEADER, at);
|
|
1810
|
+
}
|
|
1811
|
+
log(response.status, fired[0]?.id, response);
|
|
1812
|
+
return stamp(response);
|
|
1813
|
+
}
|
|
1814
|
+
};
|
|
1815
|
+
const control = createControlPlane({
|
|
1816
|
+
name: options.name,
|
|
1817
|
+
startedAt: wallNow(),
|
|
1818
|
+
wallNow,
|
|
1819
|
+
clock,
|
|
1820
|
+
faults,
|
|
1821
|
+
metrics,
|
|
1822
|
+
journal,
|
|
1823
|
+
defaultNamespace: DEFAULT_NAMESPACE,
|
|
1824
|
+
namespaces: runtime.namespaces,
|
|
1825
|
+
reset,
|
|
1826
|
+
timeTravel: {
|
|
1827
|
+
checkpoint: (name, branchName) => {
|
|
1828
|
+
const point = checkpoint(name, branchName);
|
|
1829
|
+
return {
|
|
1830
|
+
id: point.id,
|
|
1831
|
+
branch: point.branch,
|
|
1832
|
+
parent: point.parent,
|
|
1833
|
+
at: point.at,
|
|
1834
|
+
records: point.value.snapshot.records.length
|
|
1835
|
+
};
|
|
1836
|
+
},
|
|
1837
|
+
branch: (branchName, branchOptions) => {
|
|
1838
|
+
const point = branch(branchName, branchOptions);
|
|
1839
|
+
return { id: point.id, branch: point.branch, parent: point.parent, at: point.at };
|
|
1840
|
+
},
|
|
1841
|
+
checkout: (checkpointId, checkoutOptions) => checkout(checkpointId, checkoutOptions),
|
|
1842
|
+
retain: (name, checkpointId) => {
|
|
1843
|
+
timeline(name).retain(checkpointId);
|
|
1844
|
+
},
|
|
1845
|
+
release: (name, checkpointId) => timeline(name).release(checkpointId),
|
|
1846
|
+
inspect: (name) => {
|
|
1847
|
+
const history = timeline(name);
|
|
1848
|
+
return {
|
|
1849
|
+
branches: history.branches(),
|
|
1850
|
+
checkpoints: history.checkpoints().map(({ id, branch: branchName, parent, at }) => ({
|
|
1851
|
+
id,
|
|
1852
|
+
branch: branchName,
|
|
1853
|
+
parent,
|
|
1854
|
+
at
|
|
1855
|
+
}))
|
|
1856
|
+
};
|
|
1857
|
+
}
|
|
1858
|
+
},
|
|
1859
|
+
describe: options.describe ?? (() => ({})),
|
|
1860
|
+
...options.presets ? {
|
|
1861
|
+
applyPreset: (name, namespace, overrides) => runtime.applyPreset(name, namespace, overrides)
|
|
1862
|
+
} : {},
|
|
1863
|
+
routes: {
|
|
1864
|
+
...credentialRoutes(credentials),
|
|
1865
|
+
...options.presets ? presetRoutes(options.presets, runtime) : {},
|
|
1866
|
+
...options.webhooks ? webhookAdminRoutes(options.webhooks) : {},
|
|
1867
|
+
...options.admin?.(runtime) ?? {}
|
|
1868
|
+
},
|
|
1869
|
+
adminKey: options.adminKey
|
|
1870
|
+
});
|
|
1871
|
+
return runtime;
|
|
1872
|
+
};
|
|
1873
|
+
var mutableResponse = (response) => {
|
|
1874
|
+
try {
|
|
1875
|
+
response.headers.set("x-mockingbird-mutable-probe", "1");
|
|
1876
|
+
response.headers.delete("x-mockingbird-mutable-probe");
|
|
1877
|
+
return response;
|
|
1878
|
+
} catch {
|
|
1879
|
+
return new Response(response.body, response);
|
|
1880
|
+
}
|
|
1881
|
+
};
|
|
1882
|
+
var adminJson = (status, body) => new Response(JSON.stringify(body), { status, headers: { "content-type": "application/json" } });
|
|
1883
|
+
var adminFail = (status, message) => adminJson(status, { error: { type: "mockingbird_admin", message } });
|
|
1884
|
+
var isObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1885
|
+
var credentialRoutes = (registry) => ({
|
|
1886
|
+
"GET /credentials": () => adminJson(200, {
|
|
1887
|
+
credentials: registry.entries().map(({ credential, namespace }) => ({
|
|
1888
|
+
credential: maskCredential(credential),
|
|
1889
|
+
namespace
|
|
1890
|
+
}))
|
|
1891
|
+
}),
|
|
1892
|
+
"PUT /credentials": ({ body, namespace }) => {
|
|
1893
|
+
const pairs = [];
|
|
1894
|
+
const list = Array.isArray(body) ? body : isObject(body) ? body.credentials : void 0;
|
|
1895
|
+
if (Array.isArray(list)) {
|
|
1896
|
+
for (const each of list) {
|
|
1897
|
+
if (typeof each === "string")
|
|
1898
|
+
pairs.push([each, namespace]);
|
|
1899
|
+
else if (isObject(each) && typeof each.credential === "string") {
|
|
1900
|
+
pairs.push([
|
|
1901
|
+
each.credential,
|
|
1902
|
+
typeof each.namespace === "string" ? each.namespace : namespace
|
|
1903
|
+
]);
|
|
1904
|
+
} else
|
|
1905
|
+
return adminFail(400, "each entry is a credential string or {credential, namespace}");
|
|
1906
|
+
}
|
|
1907
|
+
} else if (isObject(list)) {
|
|
1908
|
+
for (const [credential, target] of Object.entries(list)) {
|
|
1909
|
+
if (typeof target !== "string")
|
|
1910
|
+
return adminFail(400, `namespace for ${credential} must be a string`);
|
|
1911
|
+
pairs.push([credential, target]);
|
|
1912
|
+
}
|
|
1913
|
+
} else if (isObject(body) && typeof body.credential === "string") {
|
|
1914
|
+
pairs.push([body.credential, typeof body.namespace === "string" ? body.namespace : namespace]);
|
|
1915
|
+
} else {
|
|
1916
|
+
return adminFail(400, 'expected {"credentials": {"<credential>": "<namespace>"}}');
|
|
1917
|
+
}
|
|
1918
|
+
for (const [credential, target] of pairs) {
|
|
1919
|
+
if (!NAMESPACE_PATTERN.test(target))
|
|
1920
|
+
return adminFail(400, `bad namespace ${JSON.stringify(target)}`);
|
|
1921
|
+
registry.set(credential, target);
|
|
1922
|
+
}
|
|
1923
|
+
return adminJson(200, { mapped: pairs.length });
|
|
1924
|
+
},
|
|
1925
|
+
"DELETE /credentials": ({ url }) => {
|
|
1926
|
+
const credential = url.searchParams.get("credential");
|
|
1927
|
+
if (credential === null)
|
|
1928
|
+
registry.clear();
|
|
1929
|
+
else
|
|
1930
|
+
registry.remove(credential);
|
|
1931
|
+
return adminJson(200, { status: "ok" });
|
|
1932
|
+
}
|
|
1933
|
+
});
|
|
1934
|
+
var presetRoutes = (presets, runtime) => ({
|
|
1935
|
+
"GET /faults/presets": () => adminJson(200, {
|
|
1936
|
+
presets: Object.entries(presets).map(([name, preset]) => ({ name, ...preset }))
|
|
1937
|
+
}),
|
|
1938
|
+
"POST /faults/presets/:name": ({ params, body, namespace }) => {
|
|
1939
|
+
const name = params.name;
|
|
1940
|
+
if (!presets[name])
|
|
1941
|
+
return adminFail(404, `no fault preset ${name}; GET /__admin/faults/presets`);
|
|
1942
|
+
const overrides = isObject(body) ? body : {};
|
|
1943
|
+
return adminJson(201, { preset: name, rules: runtime.applyPreset(name, namespace, overrides) });
|
|
1944
|
+
}
|
|
1945
|
+
});
|
|
1946
|
+
|
|
1947
|
+
// src/generated/openapi.ts
|
|
1948
|
+
var document = JSON.parse(`{"openapi":"3.1.0","info":{"title":"Google Places, Geocoding and Maps JavaScript API (Mockingbird subset)","description":"The Google Maps Platform surface our member app calls: Places Autocomplete, Place Details\\nand Find Place From Text (the legacy JSON web services), the Geocoding API, and the Maps\\nJavaScript API loader (\`/maps/api/js?libraries=places\`). Every web-service answer is HTTP 200\\nwith Google's \`status\` field; the mock resolves addresses from a QA corpus.\\n","version":"1","x-mockingbird-upstream":{"note":"Hand-authored from Google's documented response shapes and the fields our consumer reads (address-autocomplete-native-rest.tsx, address-autocomplete-web.tsx, use-geocoded-address.ts). Google publishes no OpenAPI document for these endpoints."}},"servers":[{"url":"https://maps.googleapis.com"}],"paths":{"/maps/api/place/autocomplete/json":{"get":{"operationId":"PlaceAutocomplete","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"$ref":"#/components/parameters/Key"},{"name":"input","in":"query","required":true,"schema":{"type":"string","minLength":1,"maxLength":60,"examples":["1625 N Central","Phoenix","85004","4821 Maple Street Denver","200 N Spring St, Los Angeles","136 Murray"]}},{"name":"types","in":"query","schema":{"type":"string","enum":["address","geocode"]}},{"name":"components","in":"query","schema":{"type":"string","enum":["country:us","country:ca","country:us|country:ca"]}},{"$ref":"#/components/parameters/SessionToken"}],"responses":{"200":{"description":"Predictions, or a non-OK status (HTTP 200 either way)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AutocompleteResponse"}}}},"500":{"description":"Google front-end failure (server_error preset)","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/maps/api/place/details/json":{"get":{"operationId":"PlaceDetails","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"$ref":"#/components/parameters/Key"},{"name":"place_id","in":"query","required":true,"schema":{"type":"string","minLength":1,"maxLength":200,"examples":["ChIJ5BKAwJuCB5951g03ZzrkHx9","ChIJE1ellR5XyWiy2lHIlJ5cls3","ChIJaWYsCINV1XBQQ97jO3pznpg","EieyJsIjoiNDgyMSBNYXBsZSBTdHJlZXQiLCJjIjoiRGVudmVyIiwicyI6IkNPIiwieiI6IjgwMjAyIiwibiI6IkRlbnZlciBDb3VudHkiLCJhIjozOS43NDc4OCwibyI6LTEwNC45OTQyNX0","ChIJdoesNotExist000000000"]}},{"name":"fields","in":"query","schema":{"type":"string","enum":["address_component","address_components","geometry","formatted_address,geometry","place_id,name,types","address_component,geometry,formatted_address"]}},{"$ref":"#/components/parameters/SessionToken"}],"responses":{"200":{"description":"The place, or a non-OK status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DetailsResponse"}}}},"500":{"description":"Google front-end failure (server_error preset)","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/maps/api/geocode/json":{"get":{"operationId":"Geocode","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"$ref":"#/components/parameters/Key"},{"name":"address","in":"query","schema":{"type":"string","minLength":1,"maxLength":80,"examples":["1625 N Central Ave, Phoenix, AZ 85004","88 Greenwich St, New York, NY 10006","4821 Maple Street, Denver, CO 80202","85004","Phoenix, AZ","1625 N Central Ave"]}},{"name":"place_id","in":"query","schema":{"type":"string","examples":["ChIJ5BKAwJuCB5951g03ZzrkHx9"]}},{"name":"components","in":"query","schema":{"type":"string","enum":["postal_code:85004","postal_code:10006","postal_code:00000"]}}],"responses":{"200":{"description":"Geocoding results, or a non-OK status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GeocodeResponse"}}}},"500":{"description":"Google front-end failure (server_error preset)","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/maps/api/place/findplacefromtext/json":{"get":{"operationId":"FindPlaceFromText","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"$ref":"#/components/parameters/Key"},{"name":"input","in":"query","required":true,"schema":{"type":"string","minLength":1,"maxLength":80,"examples":["1625 N Central Ave, Phoenix, AZ 85004","1625 N Central","Gilbert"]}},{"name":"inputtype","in":"query","required":true,"schema":{"type":"string","enum":["textquery","phonenumber"]}},{"name":"fields","in":"query","schema":{"type":"string","enum":["geometry","formatted_address,geometry","place_id,name","place_id"]}}],"responses":{"200":{"description":"Candidates, or a non-OK status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FindPlaceResponse"}}}},"500":{"description":"Google front-end failure (server_error preset)","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/maps/api/js":{"get":{"operationId":"MapsJavaScriptApi","description":"The Maps JavaScript API loader. The mock serves a shim defining google.maps.places (AutocompleteService, PlacesService, AutocompleteSessionToken, PlacesServiceStatus) and google.maps.Geocoder, backed by the REST operations above.","x-mockingbird":{"supported":true,"parity":{"enabled":false,"reason":"A JavaScript program, not data: it is exercised by evaluating it in a fake window and driving our web client through it (google-maps.acceptance.test.ts)."}},"parameters":[{"name":"key","in":"query","schema":{"type":"string"}},{"name":"libraries","in":"query","schema":{"type":"string"}},{"name":"callback","in":"query","schema":{"type":"string"}},{"name":"v","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"The Maps JavaScript shim","content":{"text/javascript":{"schema":{"type":"string"}}}},"503":{"description":"Script unavailable (script_unavailable preset)","content":{"text/plain":{"schema":{"type":"string"}}}}}}}},"components":{"parameters":{"Key":{"name":"key","in":"query","required":true,"description":"The Google Maps Platform API key (\`PLACES_KEY\`); also the namespace credential.","schema":{"type":"string","minLength":1,"maxLength":40,"examples":["parity-key"]}},"SessionToken":{"name":"sessiontoken","in":"query","description":"Autocomplete session token (billing grouping); accepted and journaled as metadata.","schema":{"type":"string","format":"uuid"}}},"schemas":{"Status":{"type":"string","enum":["OK","ZERO_RESULTS","INVALID_REQUEST","OVER_QUERY_LIMIT","REQUEST_DENIED","UNKNOWN_ERROR","NOT_FOUND"]},"LatLng":{"type":"object","required":["lat","lng"],"properties":{"lat":{"type":"number"},"lng":{"type":"number"}}},"Viewport":{"type":"object","required":["northeast","southwest"],"properties":{"northeast":{"$ref":"#/components/schemas/LatLng"},"southwest":{"$ref":"#/components/schemas/LatLng"}}},"Geometry":{"type":"object","required":["location","viewport"],"properties":{"location":{"$ref":"#/components/schemas/LatLng"},"location_type":{"type":"string","enum":["ROOFTOP","RANGE_INTERPOLATED","GEOMETRIC_CENTER","APPROXIMATE"]},"viewport":{"$ref":"#/components/schemas/Viewport"}}},"AddressComponent":{"type":"object","required":["long_name","short_name","types"],"properties":{"long_name":{"type":"string"},"short_name":{"type":"string"},"types":{"type":"array","items":{"type":"string"}}}},"MatchedSubstring":{"type":"object","required":["length","offset"],"properties":{"length":{"type":"integer"},"offset":{"type":"integer"}}},"Prediction":{"type":"object","required":["description","place_id","reference","structured_formatting","terms","types","matched_substrings"],"properties":{"description":{"type":"string"},"matched_substrings":{"type":"array","items":{"$ref":"#/components/schemas/MatchedSubstring"}},"place_id":{"type":"string","x-mockingbird-resource":{"type":"place","identity":true}},"reference":{"type":"string"},"structured_formatting":{"type":"object","required":["main_text","secondary_text"],"properties":{"main_text":{"type":"string"},"main_text_matched_substrings":{"type":"array","items":{"$ref":"#/components/schemas/MatchedSubstring"}},"secondary_text":{"type":"string"}}},"terms":{"type":"array","items":{"type":"object","required":["offset","value"],"properties":{"offset":{"type":"integer"},"value":{"type":"string"}}}},"types":{"type":"array","items":{"type":"string"}}}},"PlaceResult":{"type":"object","description":"Only the fields requested with \`fields=\` are present.","properties":{"address_components":{"type":"array","items":{"$ref":"#/components/schemas/AddressComponent"}},"adr_address":{"type":"string"},"formatted_address":{"type":"string"},"geometry":{"$ref":"#/components/schemas/Geometry"},"name":{"type":"string"},"place_id":{"type":"string"},"reference":{"type":"string"},"types":{"type":"array","items":{"type":"string"}},"url":{"type":"string"},"utc_offset":{"type":"integer"},"vicinity":{"type":"string"}}},"AutocompleteResponse":{"type":"object","required":["predictions","status"],"properties":{"predictions":{"type":"array","items":{"$ref":"#/components/schemas/Prediction"}},"status":{"$ref":"#/components/schemas/Status"},"error_message":{"type":"string"}}},"DetailsResponse":{"type":"object","required":["html_attributions","status"],"properties":{"html_attributions":{"type":"array","items":{"type":"string"}},"result":{"$ref":"#/components/schemas/PlaceResult"},"status":{"$ref":"#/components/schemas/Status"},"error_message":{"type":"string"}}},"GeocodeResult":{"type":"object","required":["address_components","formatted_address","geometry","place_id","types"],"properties":{"address_components":{"type":"array","items":{"$ref":"#/components/schemas/AddressComponent"}},"formatted_address":{"type":"string"},"geometry":{"$ref":"#/components/schemas/Geometry"},"place_id":{"type":"string"},"types":{"type":"array","items":{"type":"string"}}}},"GeocodeResponse":{"type":"object","required":["results","status"],"properties":{"results":{"type":"array","items":{"$ref":"#/components/schemas/GeocodeResult"}},"status":{"$ref":"#/components/schemas/Status"},"error_message":{"type":"string"}}},"FindPlaceResponse":{"type":"object","required":["candidates","status"],"properties":{"candidates":{"type":"array","items":{"$ref":"#/components/schemas/PlaceResult"}},"status":{"$ref":"#/components/schemas/Status"},"error_message":{"type":"string"}}}}}}`);
|
|
1949
|
+
var operationIds = ["PlaceAutocomplete", "PlaceDetails", "Geocode", "FindPlaceFromText", "MapsJavaScriptApi"];
|
|
1950
|
+
var supportedOperationIds = ["PlaceAutocomplete", "PlaceDetails", "Geocode", "FindPlaceFromText", "MapsJavaScriptApi"];
|
|
1951
|
+
|
|
1952
|
+
// src/corpus.ts
|
|
1953
|
+
var row = (id, line1, city, state, zip, county, lat, lng) => ({ id, line1, city, state, zip, county, lat, lng });
|
|
1954
|
+
var DEFAULT_CORPUS = [
|
|
1955
|
+
row(
|
|
1956
|
+
"al-birmingham",
|
|
1957
|
+
"2100 1st Ave N",
|
|
1958
|
+
"Birmingham",
|
|
1959
|
+
"AL",
|
|
1960
|
+
"35203",
|
|
1961
|
+
"Jefferson County",
|
|
1962
|
+
33.5167,
|
|
1963
|
+
-86.807
|
|
1964
|
+
),
|
|
1965
|
+
row("ak-anchorage", "320 W 5th Ave", "Anchorage", "AK", "99501", "Anchorage", 61.218, -149.894),
|
|
1966
|
+
row(
|
|
1967
|
+
"az-phoenix",
|
|
1968
|
+
"1625 N Central Ave",
|
|
1969
|
+
"Phoenix",
|
|
1970
|
+
"AZ",
|
|
1971
|
+
"85004",
|
|
1972
|
+
"Maricopa County",
|
|
1973
|
+
33.4665,
|
|
1974
|
+
-112.0738
|
|
1975
|
+
),
|
|
1976
|
+
row(
|
|
1977
|
+
"az-phoenix-2",
|
|
1978
|
+
"501 N 5th St",
|
|
1979
|
+
"Phoenix",
|
|
1980
|
+
"AZ",
|
|
1981
|
+
"85004",
|
|
1982
|
+
"Maricopa County",
|
|
1983
|
+
33.454,
|
|
1984
|
+
-112.066
|
|
1985
|
+
),
|
|
1986
|
+
row(
|
|
1987
|
+
"az-gilbert",
|
|
1988
|
+
"420 E Stonebridge Dr",
|
|
1989
|
+
"Gilbert",
|
|
1990
|
+
"AZ",
|
|
1991
|
+
"85234",
|
|
1992
|
+
"Maricopa County",
|
|
1993
|
+
33.37,
|
|
1994
|
+
-111.783
|
|
1995
|
+
),
|
|
1996
|
+
row(
|
|
1997
|
+
"ar-little-rock",
|
|
1998
|
+
"500 President Clinton Ave",
|
|
1999
|
+
"Little Rock",
|
|
2000
|
+
"AR",
|
|
2001
|
+
"72201",
|
|
2002
|
+
"Pulaski County",
|
|
2003
|
+
34.747,
|
|
2004
|
+
-92.265
|
|
2005
|
+
),
|
|
2006
|
+
row(
|
|
2007
|
+
"ca-alturas",
|
|
2008
|
+
"712 Western Street",
|
|
2009
|
+
"Alturas",
|
|
2010
|
+
"CA",
|
|
2011
|
+
"96101",
|
|
2012
|
+
"Modoc County",
|
|
2013
|
+
41.487,
|
|
2014
|
+
-120.542
|
|
2015
|
+
),
|
|
2016
|
+
row(
|
|
2017
|
+
"ca-los-angeles",
|
|
2018
|
+
"200 N Spring St",
|
|
2019
|
+
"Los Angeles",
|
|
2020
|
+
"CA",
|
|
2021
|
+
"90012",
|
|
2022
|
+
"Los Angeles County",
|
|
2023
|
+
34.0537,
|
|
2024
|
+
-118.2427
|
|
2025
|
+
),
|
|
2026
|
+
row("co-denver", "1437 Bannock St", "Denver", "CO", "80202", "Denver County", 39.7392, -104.9903),
|
|
2027
|
+
row(
|
|
2028
|
+
"ct-hartford",
|
|
2029
|
+
"550 Main St",
|
|
2030
|
+
"Hartford",
|
|
2031
|
+
"CT",
|
|
2032
|
+
"06103",
|
|
2033
|
+
"Hartford County",
|
|
2034
|
+
41.7637,
|
|
2035
|
+
-72.6851
|
|
2036
|
+
),
|
|
2037
|
+
row(
|
|
2038
|
+
"dc-washington",
|
|
2039
|
+
"1350 Pennsylvania Ave NW",
|
|
2040
|
+
"Washington",
|
|
2041
|
+
"DC",
|
|
2042
|
+
"20001",
|
|
2043
|
+
"District of Columbia",
|
|
2044
|
+
38.8951,
|
|
2045
|
+
-77.031
|
|
2046
|
+
),
|
|
2047
|
+
row(
|
|
2048
|
+
"de-wilmington",
|
|
2049
|
+
"800 N French St",
|
|
2050
|
+
"Wilmington",
|
|
2051
|
+
"DE",
|
|
2052
|
+
"19801",
|
|
2053
|
+
"New Castle County",
|
|
2054
|
+
39.7447,
|
|
2055
|
+
-75.5484
|
|
2056
|
+
),
|
|
2057
|
+
row(
|
|
2058
|
+
"fl-miami",
|
|
2059
|
+
"3500 Pan American Dr",
|
|
2060
|
+
"Miami",
|
|
2061
|
+
"FL",
|
|
2062
|
+
"33130",
|
|
2063
|
+
"Miami-Dade County",
|
|
2064
|
+
25.728,
|
|
2065
|
+
-80.233
|
|
2066
|
+
),
|
|
2067
|
+
row("ga-atlanta", "55 Trinity Ave SW", "Atlanta", "GA", "30303", "Fulton County", 33.749, -84.39),
|
|
2068
|
+
row(
|
|
2069
|
+
"hi-honolulu",
|
|
2070
|
+
"530 S King St",
|
|
2071
|
+
"Honolulu",
|
|
2072
|
+
"HI",
|
|
2073
|
+
"96813",
|
|
2074
|
+
"Honolulu County",
|
|
2075
|
+
21.3069,
|
|
2076
|
+
-157.8583
|
|
2077
|
+
),
|
|
2078
|
+
row("id-boise", "150 N Capitol Blvd", "Boise", "ID", "83702", "Ada County", 43.615, -116.2023),
|
|
2079
|
+
row("il-chicago", "121 N LaSalle St", "Chicago", "IL", "60601", "Cook County", 41.8837, -87.6323),
|
|
2080
|
+
row(
|
|
2081
|
+
"in-newburgh",
|
|
2082
|
+
"5366 E Sherwood Dr",
|
|
2083
|
+
"Newburgh",
|
|
2084
|
+
"IN",
|
|
2085
|
+
"47630",
|
|
2086
|
+
"Warrick County",
|
|
2087
|
+
37.9445,
|
|
2088
|
+
-87.4053
|
|
2089
|
+
),
|
|
2090
|
+
row(
|
|
2091
|
+
"in-indianapolis",
|
|
2092
|
+
"200 E Washington St",
|
|
2093
|
+
"Indianapolis",
|
|
2094
|
+
"IN",
|
|
2095
|
+
"46204",
|
|
2096
|
+
"Marion County",
|
|
2097
|
+
39.767,
|
|
2098
|
+
-86.155
|
|
2099
|
+
),
|
|
2100
|
+
row(
|
|
2101
|
+
"ia-des-moines",
|
|
2102
|
+
"400 Robert D Ray Dr",
|
|
2103
|
+
"Des Moines",
|
|
2104
|
+
"IA",
|
|
2105
|
+
"50309",
|
|
2106
|
+
"Polk County",
|
|
2107
|
+
41.5868,
|
|
2108
|
+
-93.625
|
|
2109
|
+
),
|
|
2110
|
+
row("ks-wichita", "455 N Main St", "Wichita", "KS", "67202", "Sedgwick County", 37.6872, -97.339),
|
|
2111
|
+
row(
|
|
2112
|
+
"ky-louisville",
|
|
2113
|
+
"527 W Jefferson St",
|
|
2114
|
+
"Louisville",
|
|
2115
|
+
"KY",
|
|
2116
|
+
"40202",
|
|
2117
|
+
"Jefferson County",
|
|
2118
|
+
38.2542,
|
|
2119
|
+
-85.7594
|
|
2120
|
+
),
|
|
2121
|
+
row(
|
|
2122
|
+
"la-new-orleans",
|
|
2123
|
+
"1300 Perdido St",
|
|
2124
|
+
"New Orleans",
|
|
2125
|
+
"LA",
|
|
2126
|
+
"70112",
|
|
2127
|
+
"Orleans Parish",
|
|
2128
|
+
29.9511,
|
|
2129
|
+
-90.0715
|
|
2130
|
+
),
|
|
2131
|
+
row(
|
|
2132
|
+
"me-portland",
|
|
2133
|
+
"389 Congress St",
|
|
2134
|
+
"Portland",
|
|
2135
|
+
"ME",
|
|
2136
|
+
"04101",
|
|
2137
|
+
"Cumberland County",
|
|
2138
|
+
43.6591,
|
|
2139
|
+
-70.2568
|
|
2140
|
+
),
|
|
2141
|
+
row(
|
|
2142
|
+
"md-baltimore",
|
|
2143
|
+
"100 Holliday St",
|
|
2144
|
+
"Baltimore",
|
|
2145
|
+
"MD",
|
|
2146
|
+
"21201",
|
|
2147
|
+
"Baltimore City",
|
|
2148
|
+
39.2904,
|
|
2149
|
+
-76.6122
|
|
2150
|
+
),
|
|
2151
|
+
row(
|
|
2152
|
+
"ma-boston",
|
|
2153
|
+
"1 City Hall Square",
|
|
2154
|
+
"Boston",
|
|
2155
|
+
"MA",
|
|
2156
|
+
"02108",
|
|
2157
|
+
"Suffolk County",
|
|
2158
|
+
42.3601,
|
|
2159
|
+
-71.0589
|
|
2160
|
+
),
|
|
2161
|
+
row("mi-detroit", "2 Woodward Ave", "Detroit", "MI", "48226", "Wayne County", 42.3292, -83.044),
|
|
2162
|
+
row(
|
|
2163
|
+
"mn-minneapolis",
|
|
2164
|
+
"350 S 5th St",
|
|
2165
|
+
"Minneapolis",
|
|
2166
|
+
"MN",
|
|
2167
|
+
"55415",
|
|
2168
|
+
"Hennepin County",
|
|
2169
|
+
44.9778,
|
|
2170
|
+
-93.265
|
|
2171
|
+
),
|
|
2172
|
+
row(
|
|
2173
|
+
"ms-jackson",
|
|
2174
|
+
"219 S President St",
|
|
2175
|
+
"Jackson",
|
|
2176
|
+
"MS",
|
|
2177
|
+
"39201",
|
|
2178
|
+
"Hinds County",
|
|
2179
|
+
32.2988,
|
|
2180
|
+
-90.1848
|
|
2181
|
+
),
|
|
2182
|
+
row(
|
|
2183
|
+
"mo-kansas-city",
|
|
2184
|
+
"414 E 12th St",
|
|
2185
|
+
"Kansas City",
|
|
2186
|
+
"MO",
|
|
2187
|
+
"64106",
|
|
2188
|
+
"Jackson County",
|
|
2189
|
+
39.0997,
|
|
2190
|
+
-94.5786
|
|
2191
|
+
),
|
|
2192
|
+
row(
|
|
2193
|
+
"mt-billings",
|
|
2194
|
+
"210 N 27th St",
|
|
2195
|
+
"Billings",
|
|
2196
|
+
"MT",
|
|
2197
|
+
"59101",
|
|
2198
|
+
"Yellowstone County",
|
|
2199
|
+
45.7833,
|
|
2200
|
+
-108.5007
|
|
2201
|
+
),
|
|
2202
|
+
row("ne-omaha", "1819 Farnam St", "Omaha", "NE", "68102", "Douglas County", 41.2587, -95.9378),
|
|
2203
|
+
row(
|
|
2204
|
+
"nv-las-vegas",
|
|
2205
|
+
"495 S Main St",
|
|
2206
|
+
"Las Vegas",
|
|
2207
|
+
"NV",
|
|
2208
|
+
"89101",
|
|
2209
|
+
"Clark County",
|
|
2210
|
+
36.1672,
|
|
2211
|
+
-115.1485
|
|
2212
|
+
),
|
|
2213
|
+
row(
|
|
2214
|
+
"nh-manchester",
|
|
2215
|
+
"1 City Hall Plaza",
|
|
2216
|
+
"Manchester",
|
|
2217
|
+
"NH",
|
|
2218
|
+
"03101",
|
|
2219
|
+
"Hillsborough County",
|
|
2220
|
+
42.9956,
|
|
2221
|
+
-71.4548
|
|
2222
|
+
),
|
|
2223
|
+
row("nj-hoboken", "1 Hudson Pl", "Hoboken", "NJ", "07030", "Hudson County", 40.7359, -74.0275),
|
|
2224
|
+
row(
|
|
2225
|
+
"nm-albuquerque",
|
|
2226
|
+
"1 Civic Plaza NW",
|
|
2227
|
+
"Albuquerque",
|
|
2228
|
+
"NM",
|
|
2229
|
+
"87102",
|
|
2230
|
+
"Bernalillo County",
|
|
2231
|
+
35.0853,
|
|
2232
|
+
-106.651
|
|
2233
|
+
),
|
|
2234
|
+
row(
|
|
2235
|
+
"ny-port-washington",
|
|
2236
|
+
"136 Murray Avenue",
|
|
2237
|
+
"Port Washington",
|
|
2238
|
+
"NY",
|
|
2239
|
+
"11050",
|
|
2240
|
+
"Nassau County",
|
|
2241
|
+
40.8257,
|
|
2242
|
+
-73.6982
|
|
2243
|
+
),
|
|
2244
|
+
row(
|
|
2245
|
+
"ny-manhattan",
|
|
2246
|
+
"88 Greenwich St",
|
|
2247
|
+
"New York",
|
|
2248
|
+
"NY",
|
|
2249
|
+
"10006",
|
|
2250
|
+
"New York County",
|
|
2251
|
+
40.7077,
|
|
2252
|
+
-74.0137
|
|
2253
|
+
),
|
|
2254
|
+
row(
|
|
2255
|
+
"nc-charlotte",
|
|
2256
|
+
"600 E 4th St",
|
|
2257
|
+
"Charlotte",
|
|
2258
|
+
"NC",
|
|
2259
|
+
"28202",
|
|
2260
|
+
"Mecklenburg County",
|
|
2261
|
+
35.221,
|
|
2262
|
+
-80.839
|
|
2263
|
+
),
|
|
2264
|
+
row("nd-fargo", "225 4th St N", "Fargo", "ND", "58102", "Cass County", 46.8772, -96.7898),
|
|
2265
|
+
row(
|
|
2266
|
+
"oh-columbus",
|
|
2267
|
+
"90 W Broad St",
|
|
2268
|
+
"Columbus",
|
|
2269
|
+
"OH",
|
|
2270
|
+
"43215",
|
|
2271
|
+
"Franklin County",
|
|
2272
|
+
39.9612,
|
|
2273
|
+
-83.0007
|
|
2274
|
+
),
|
|
2275
|
+
row(
|
|
2276
|
+
"ok-oklahoma-city",
|
|
2277
|
+
"200 N Walker Ave",
|
|
2278
|
+
"Oklahoma City",
|
|
2279
|
+
"OK",
|
|
2280
|
+
"73102",
|
|
2281
|
+
"Oklahoma County",
|
|
2282
|
+
35.4689,
|
|
2283
|
+
-97.5195
|
|
2284
|
+
),
|
|
2285
|
+
row(
|
|
2286
|
+
"or-portland",
|
|
2287
|
+
"1221 SW 4th Ave",
|
|
2288
|
+
"Portland",
|
|
2289
|
+
"OR",
|
|
2290
|
+
"97204",
|
|
2291
|
+
"Multnomah County",
|
|
2292
|
+
45.5152,
|
|
2293
|
+
-122.6784
|
|
2294
|
+
),
|
|
2295
|
+
row(
|
|
2296
|
+
"pa-east-berlin",
|
|
2297
|
+
"100 W King St",
|
|
2298
|
+
"East Berlin",
|
|
2299
|
+
"PA",
|
|
2300
|
+
"17316",
|
|
2301
|
+
"Adams County",
|
|
2302
|
+
39.9376,
|
|
2303
|
+
-76.9786
|
|
2304
|
+
),
|
|
2305
|
+
row(
|
|
2306
|
+
"pa-philadelphia",
|
|
2307
|
+
"1401 John F Kennedy Blvd",
|
|
2308
|
+
"Philadelphia",
|
|
2309
|
+
"PA",
|
|
2310
|
+
"19107",
|
|
2311
|
+
"Philadelphia County",
|
|
2312
|
+
39.9535,
|
|
2313
|
+
-75.1636
|
|
2314
|
+
),
|
|
2315
|
+
row(
|
|
2316
|
+
"ri-providence",
|
|
2317
|
+
"1 Park Row",
|
|
2318
|
+
"Providence",
|
|
2319
|
+
"RI",
|
|
2320
|
+
"02903",
|
|
2321
|
+
"Providence County",
|
|
2322
|
+
41.824,
|
|
2323
|
+
-71.4128
|
|
2324
|
+
),
|
|
2325
|
+
row(
|
|
2326
|
+
"sc-columbia",
|
|
2327
|
+
"1737 Main St",
|
|
2328
|
+
"Columbia",
|
|
2329
|
+
"SC",
|
|
2330
|
+
"29201",
|
|
2331
|
+
"Richland County",
|
|
2332
|
+
34.0007,
|
|
2333
|
+
-81.0348
|
|
2334
|
+
),
|
|
2335
|
+
row(
|
|
2336
|
+
"sd-sioux-falls",
|
|
2337
|
+
"224 W 9th St",
|
|
2338
|
+
"Sioux Falls",
|
|
2339
|
+
"SD",
|
|
2340
|
+
"57104",
|
|
2341
|
+
"Minnehaha County",
|
|
2342
|
+
43.5446,
|
|
2343
|
+
-96.7311
|
|
2344
|
+
),
|
|
2345
|
+
row(
|
|
2346
|
+
"tn-nashville",
|
|
2347
|
+
"1 Public Square",
|
|
2348
|
+
"Nashville",
|
|
2349
|
+
"TN",
|
|
2350
|
+
"37219",
|
|
2351
|
+
"Davidson County",
|
|
2352
|
+
36.1667,
|
|
2353
|
+
-86.7784
|
|
2354
|
+
),
|
|
2355
|
+
row("tx-plano", "2601 Preston Rd", "Frisco", "TX", "75034", "Collin County", 33.1507, -96.8236),
|
|
2356
|
+
row("tx-houston", "901 Bagby St", "Houston", "TX", "77002", "Harris County", 29.7604, -95.3698),
|
|
2357
|
+
row(
|
|
2358
|
+
"ut-salt-lake",
|
|
2359
|
+
"451 S State St",
|
|
2360
|
+
"Salt Lake City",
|
|
2361
|
+
"UT",
|
|
2362
|
+
"84111",
|
|
2363
|
+
"Salt Lake County",
|
|
2364
|
+
40.7608,
|
|
2365
|
+
-111.891
|
|
2366
|
+
),
|
|
2367
|
+
row(
|
|
2368
|
+
"vt-burlington",
|
|
2369
|
+
"149 Church St",
|
|
2370
|
+
"Burlington",
|
|
2371
|
+
"VT",
|
|
2372
|
+
"05401",
|
|
2373
|
+
"Chittenden County",
|
|
2374
|
+
44.4759,
|
|
2375
|
+
-73.2121
|
|
2376
|
+
),
|
|
2377
|
+
row(
|
|
2378
|
+
"va-richmond",
|
|
2379
|
+
"900 E Broad St",
|
|
2380
|
+
"Richmond",
|
|
2381
|
+
"VA",
|
|
2382
|
+
"23219",
|
|
2383
|
+
"Richmond City",
|
|
2384
|
+
37.5407,
|
|
2385
|
+
-77.436
|
|
2386
|
+
),
|
|
2387
|
+
row("wa-seattle", "600 4th Ave", "Seattle", "WA", "98104", "King County", 47.6036, -122.3294),
|
|
2388
|
+
row(
|
|
2389
|
+
"wv-charleston",
|
|
2390
|
+
"501 Virginia St E",
|
|
2391
|
+
"Charleston",
|
|
2392
|
+
"WV",
|
|
2393
|
+
"25301",
|
|
2394
|
+
"Kanawha County",
|
|
2395
|
+
38.3498,
|
|
2396
|
+
-81.6326
|
|
2397
|
+
),
|
|
2398
|
+
row(
|
|
2399
|
+
"wi-scandinavia",
|
|
2400
|
+
"N7145 Rosholt Road",
|
|
2401
|
+
"Scandinavia",
|
|
2402
|
+
"WI",
|
|
2403
|
+
"54977",
|
|
2404
|
+
"Waupaca County",
|
|
2405
|
+
44.46,
|
|
2406
|
+
-89.146
|
|
2407
|
+
),
|
|
2408
|
+
row(
|
|
2409
|
+
"wi-milwaukee",
|
|
2410
|
+
"200 E Wells St",
|
|
2411
|
+
"Milwaukee",
|
|
2412
|
+
"WI",
|
|
2413
|
+
"53202",
|
|
2414
|
+
"Milwaukee County",
|
|
2415
|
+
43.0418,
|
|
2416
|
+
-87.9097
|
|
2417
|
+
),
|
|
2418
|
+
row(
|
|
2419
|
+
"wy-cheyenne",
|
|
2420
|
+
"2101 O Neil Ave",
|
|
2421
|
+
"Cheyenne",
|
|
2422
|
+
"WY",
|
|
2423
|
+
"82001",
|
|
2424
|
+
"Laramie County",
|
|
2425
|
+
41.14,
|
|
2426
|
+
-104.8202
|
|
2427
|
+
)
|
|
2428
|
+
];
|
|
2429
|
+
var PHOENIX_DEMO_ADDRESS = DEFAULT_CORPUS.find(
|
|
2430
|
+
(a) => a.id === "az-phoenix"
|
|
2431
|
+
);
|
|
2432
|
+
var STATE_NAMES = {
|
|
2433
|
+
AL: "Alabama",
|
|
2434
|
+
AK: "Alaska",
|
|
2435
|
+
AZ: "Arizona",
|
|
2436
|
+
AR: "Arkansas",
|
|
2437
|
+
CA: "California",
|
|
2438
|
+
CO: "Colorado",
|
|
2439
|
+
CT: "Connecticut",
|
|
2440
|
+
DC: "District of Columbia",
|
|
2441
|
+
DE: "Delaware",
|
|
2442
|
+
FL: "Florida",
|
|
2443
|
+
GA: "Georgia",
|
|
2444
|
+
HI: "Hawaii",
|
|
2445
|
+
ID: "Idaho",
|
|
2446
|
+
IL: "Illinois",
|
|
2447
|
+
IN: "Indiana",
|
|
2448
|
+
IA: "Iowa",
|
|
2449
|
+
KS: "Kansas",
|
|
2450
|
+
KY: "Kentucky",
|
|
2451
|
+
LA: "Louisiana",
|
|
2452
|
+
ME: "Maine",
|
|
2453
|
+
MD: "Maryland",
|
|
2454
|
+
MA: "Massachusetts",
|
|
2455
|
+
MI: "Michigan",
|
|
2456
|
+
MN: "Minnesota",
|
|
2457
|
+
MS: "Mississippi",
|
|
2458
|
+
MO: "Missouri",
|
|
2459
|
+
MT: "Montana",
|
|
2460
|
+
NE: "Nebraska",
|
|
2461
|
+
NV: "Nevada",
|
|
2462
|
+
NH: "New Hampshire",
|
|
2463
|
+
NJ: "New Jersey",
|
|
2464
|
+
NM: "New Mexico",
|
|
2465
|
+
NY: "New York",
|
|
2466
|
+
NC: "North Carolina",
|
|
2467
|
+
ND: "North Dakota",
|
|
2468
|
+
OH: "Ohio",
|
|
2469
|
+
OK: "Oklahoma",
|
|
2470
|
+
OR: "Oregon",
|
|
2471
|
+
PA: "Pennsylvania",
|
|
2472
|
+
RI: "Rhode Island",
|
|
2473
|
+
SC: "South Carolina",
|
|
2474
|
+
SD: "South Dakota",
|
|
2475
|
+
TN: "Tennessee",
|
|
2476
|
+
TX: "Texas",
|
|
2477
|
+
UT: "Utah",
|
|
2478
|
+
VT: "Vermont",
|
|
2479
|
+
VA: "Virginia",
|
|
2480
|
+
WA: "Washington",
|
|
2481
|
+
WV: "West Virginia",
|
|
2482
|
+
WI: "Wisconsin",
|
|
2483
|
+
WY: "Wyoming"
|
|
2484
|
+
};
|
|
2485
|
+
|
|
2486
|
+
// src/places.ts
|
|
2487
|
+
var MAX_PREDICTIONS = 5;
|
|
2488
|
+
var normalize = (value) => value.toLowerCase().replace(/[^a-z0-9]+/g, " ").trim();
|
|
2489
|
+
var base64url = (value) => toBase64(new TextEncoder().encode(value)).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
2490
|
+
var fromBase64url = (value) => {
|
|
2491
|
+
try {
|
|
2492
|
+
return new TextDecoder("utf-8", { fatal: true }).decode(
|
|
2493
|
+
fromBase64(value.replace(/-/g, "+").replace(/_/g, "/"))
|
|
2494
|
+
);
|
|
2495
|
+
} catch {
|
|
2496
|
+
return void 0;
|
|
2497
|
+
}
|
|
2498
|
+
};
|
|
2499
|
+
var corpusPlaceId = (row2) => `ChIJ${opaqueToken(`gmaps:${row2.id}`, 23)}`;
|
|
2500
|
+
var postalPlaceId = (zip) => `ChIJ${opaqueToken(`gmaps:zip:${zip}`, 23)}`;
|
|
2501
|
+
var fromRow = (row2) => ({
|
|
2502
|
+
placeId: corpusPlaceId(row2),
|
|
2503
|
+
kind: "street_address",
|
|
2504
|
+
line1: row2.line1,
|
|
2505
|
+
city: row2.city,
|
|
2506
|
+
state: row2.state,
|
|
2507
|
+
zip: row2.zip,
|
|
2508
|
+
county: row2.county,
|
|
2509
|
+
lat: row2.lat,
|
|
2510
|
+
lng: row2.lng
|
|
2511
|
+
});
|
|
2512
|
+
var jitter = (seed, axis) => {
|
|
2513
|
+
const token = opaqueToken(`gmaps:jitter:${axis}:${seed}`, 4);
|
|
2514
|
+
let n = 0;
|
|
2515
|
+
for (const char of token) n = (n * 31 + char.charCodeAt(0)) % 1800;
|
|
2516
|
+
return (n - 900) / 1e5;
|
|
2517
|
+
};
|
|
2518
|
+
var round = (value) => Math.round(value * 1e7) / 1e7;
|
|
2519
|
+
var synthesize = (line1, row2) => {
|
|
2520
|
+
const payload = {
|
|
2521
|
+
l: line1,
|
|
2522
|
+
c: row2.city,
|
|
2523
|
+
s: row2.state,
|
|
2524
|
+
z: row2.zip,
|
|
2525
|
+
n: row2.county,
|
|
2526
|
+
a: round(row2.lat + jitter(line1, "lat")),
|
|
2527
|
+
o: round(row2.lng + jitter(line1, "lng"))
|
|
2528
|
+
};
|
|
2529
|
+
return {
|
|
2530
|
+
placeId: `Ei${base64url(JSON.stringify(payload))}`,
|
|
2531
|
+
kind: "street_address",
|
|
2532
|
+
line1,
|
|
2533
|
+
city: row2.city,
|
|
2534
|
+
state: row2.state,
|
|
2535
|
+
zip: row2.zip,
|
|
2536
|
+
county: row2.county,
|
|
2537
|
+
lat: payload.a,
|
|
2538
|
+
lng: payload.o
|
|
2539
|
+
};
|
|
2540
|
+
};
|
|
2541
|
+
var postalPlace = (row2) => ({
|
|
2542
|
+
placeId: postalPlaceId(row2.zip),
|
|
2543
|
+
kind: "postal_code",
|
|
2544
|
+
line1: "",
|
|
2545
|
+
city: row2.city,
|
|
2546
|
+
state: row2.state,
|
|
2547
|
+
zip: row2.zip,
|
|
2548
|
+
county: row2.county,
|
|
2549
|
+
lat: row2.lat,
|
|
2550
|
+
lng: row2.lng
|
|
2551
|
+
});
|
|
2552
|
+
var placeById = (placeId, rows) => {
|
|
2553
|
+
if (placeId.startsWith("Ei")) {
|
|
2554
|
+
const decoded = fromBase64url(placeId.slice(2));
|
|
2555
|
+
if (decoded === void 0) return void 0;
|
|
2556
|
+
try {
|
|
2557
|
+
const p = JSON.parse(decoded);
|
|
2558
|
+
if (typeof p.l !== "string" || typeof p.c !== "string" || typeof p.s !== "string" || typeof p.z !== "string" || typeof p.a !== "number" || typeof p.o !== "number") {
|
|
2559
|
+
return void 0;
|
|
2560
|
+
}
|
|
2561
|
+
return {
|
|
2562
|
+
placeId,
|
|
2563
|
+
kind: "street_address",
|
|
2564
|
+
line1: p.l,
|
|
2565
|
+
city: p.c,
|
|
2566
|
+
state: p.s,
|
|
2567
|
+
zip: p.z,
|
|
2568
|
+
county: typeof p.n === "string" ? p.n : "",
|
|
2569
|
+
lat: p.a,
|
|
2570
|
+
lng: p.o
|
|
2571
|
+
};
|
|
2572
|
+
} catch {
|
|
2573
|
+
return void 0;
|
|
2574
|
+
}
|
|
2575
|
+
}
|
|
2576
|
+
for (const row2 of rows) {
|
|
2577
|
+
if (corpusPlaceId(row2) === placeId) return fromRow(row2);
|
|
2578
|
+
if (postalPlaceId(row2.zip) === placeId) return postalPlace(row2);
|
|
2579
|
+
}
|
|
2580
|
+
return void 0;
|
|
2581
|
+
};
|
|
2582
|
+
var STREET_NUMBER = /^([A-Za-z]?\d+[A-Za-z]?(?:-\d+)?)\s+(.+)$/;
|
|
2583
|
+
var splitStreet = (line1) => {
|
|
2584
|
+
const match = STREET_NUMBER.exec(line1.trim());
|
|
2585
|
+
return match ? { number: match[1], route: match[2].trim() } : { number: void 0, route: line1.trim() };
|
|
2586
|
+
};
|
|
2587
|
+
var addressComponents = (place) => {
|
|
2588
|
+
const out = [];
|
|
2589
|
+
if (place.kind === "street_address") {
|
|
2590
|
+
const { number, route } = splitStreet(place.line1);
|
|
2591
|
+
if (number) out.push({ long_name: number, short_name: number, types: ["street_number"] });
|
|
2592
|
+
out.push({ long_name: route, short_name: route, types: ["route"] });
|
|
2593
|
+
} else {
|
|
2594
|
+
out.push({ long_name: place.zip, short_name: place.zip, types: ["postal_code"] });
|
|
2595
|
+
}
|
|
2596
|
+
out.push({ long_name: place.city, short_name: place.city, types: ["locality", "political"] });
|
|
2597
|
+
if (place.county) {
|
|
2598
|
+
out.push({
|
|
2599
|
+
long_name: place.county,
|
|
2600
|
+
short_name: place.county,
|
|
2601
|
+
types: ["administrative_area_level_2", "political"]
|
|
2602
|
+
});
|
|
2603
|
+
}
|
|
2604
|
+
out.push({
|
|
2605
|
+
long_name: STATE_NAMES[place.state] ?? place.state,
|
|
2606
|
+
short_name: place.state,
|
|
2607
|
+
types: ["administrative_area_level_1", "political"]
|
|
2608
|
+
});
|
|
2609
|
+
out.push({ long_name: "United States", short_name: "US", types: ["country", "political"] });
|
|
2610
|
+
if (place.kind === "street_address") {
|
|
2611
|
+
out.push({ long_name: place.zip, short_name: place.zip, types: ["postal_code"] });
|
|
2612
|
+
}
|
|
2613
|
+
return out;
|
|
2614
|
+
};
|
|
2615
|
+
var formattedAddress = (place) => place.kind === "street_address" ? `${place.line1}, ${place.city}, ${place.state} ${place.zip}, USA` : `${place.city}, ${place.state} ${place.zip}, USA`;
|
|
2616
|
+
var geometry = (place, withType = false) => {
|
|
2617
|
+
const span = place.kind === "street_address" ? 135e-5 : 0.02;
|
|
2618
|
+
return {
|
|
2619
|
+
location: { lat: place.lat, lng: place.lng },
|
|
2620
|
+
...withType ? { location_type: place.kind === "street_address" ? "ROOFTOP" : "APPROXIMATE" } : {},
|
|
2621
|
+
viewport: {
|
|
2622
|
+
northeast: { lat: round(place.lat + span), lng: round(place.lng + span) },
|
|
2623
|
+
southwest: { lat: round(place.lat - span), lng: round(place.lng - span) }
|
|
2624
|
+
}
|
|
2625
|
+
};
|
|
2626
|
+
};
|
|
2627
|
+
var placeTypes = (place) => place.kind === "street_address" ? ["street_address"] : ["postal_code"];
|
|
2628
|
+
var placeResult = (place) => ({
|
|
2629
|
+
address_components: addressComponents(place),
|
|
2630
|
+
adr_address: place.kind === "street_address" ? `<span class="street-address">${place.line1}</span>, <span class="locality">${place.city}</span>, <span class="region">${place.state}</span> <span class="postal-code">${place.zip}</span>, <span class="country-name">USA</span>` : `<span class="locality">${place.city}</span>, <span class="region">${place.state}</span> <span class="postal-code">${place.zip}</span>, <span class="country-name">USA</span>`,
|
|
2631
|
+
formatted_address: formattedAddress(place),
|
|
2632
|
+
geometry: geometry(place),
|
|
2633
|
+
name: place.kind === "street_address" ? place.line1 : place.zip,
|
|
2634
|
+
place_id: place.placeId,
|
|
2635
|
+
reference: place.placeId,
|
|
2636
|
+
types: placeTypes(place),
|
|
2637
|
+
url: `https://maps.google.com/?q=${encodeURIComponent(formattedAddress(place))}`,
|
|
2638
|
+
utc_offset: 0,
|
|
2639
|
+
vicinity: place.city
|
|
2640
|
+
});
|
|
2641
|
+
var FIELD_ALIASES = {
|
|
2642
|
+
address_component: "address_components",
|
|
2643
|
+
address_components: "address_components",
|
|
2644
|
+
adr_address: "adr_address",
|
|
2645
|
+
formatted_address: "formatted_address",
|
|
2646
|
+
geometry: "geometry",
|
|
2647
|
+
"geometry/location": "geometry",
|
|
2648
|
+
"geometry/viewport": "geometry",
|
|
2649
|
+
name: "name",
|
|
2650
|
+
place_id: "place_id",
|
|
2651
|
+
reference: "reference",
|
|
2652
|
+
type: "types",
|
|
2653
|
+
types: "types",
|
|
2654
|
+
url: "url",
|
|
2655
|
+
utc_offset: "utc_offset",
|
|
2656
|
+
vicinity: "vicinity"
|
|
2657
|
+
};
|
|
2658
|
+
var pickFields = (result, fields, fallback) => {
|
|
2659
|
+
const requested = (fields ?? "").split(",").map((f) => f.trim()).filter(Boolean);
|
|
2660
|
+
if (requested.length === 0) {
|
|
2661
|
+
if (fallback === "all") return result;
|
|
2662
|
+
return Object.fromEntries(fallback.map((key) => [key, result[key]]));
|
|
2663
|
+
}
|
|
2664
|
+
if (requested.includes("*")) return result;
|
|
2665
|
+
const keys = new Set(requested.map((f) => FIELD_ALIASES[f]).filter((k) => !!k));
|
|
2666
|
+
return Object.fromEntries(Object.entries(result).filter(([key]) => keys.has(key)));
|
|
2667
|
+
};
|
|
2668
|
+
var unknownFields = (fields) => (fields ?? "").split(",").map((f) => f.trim()).filter((f) => f && f !== "*" && !(f in FIELD_ALIASES));
|
|
2669
|
+
var secondary = (place) => `${place.city}, ${place.state}, USA`;
|
|
2670
|
+
var prediction = (place, input) => {
|
|
2671
|
+
const main = place.kind === "street_address" ? place.line1 : place.zip;
|
|
2672
|
+
const second = secondary(place);
|
|
2673
|
+
const description = `${main}, ${second}`;
|
|
2674
|
+
const matched = Math.min(input.trim().length, main.length);
|
|
2675
|
+
const terms = [];
|
|
2676
|
+
let offset = 0;
|
|
2677
|
+
for (const value of [main, place.city, place.state, "USA"]) {
|
|
2678
|
+
terms.push({ offset, value });
|
|
2679
|
+
offset += value.length + 2;
|
|
2680
|
+
}
|
|
2681
|
+
return {
|
|
2682
|
+
description,
|
|
2683
|
+
matched_substrings: [{ length: matched, offset: 0 }],
|
|
2684
|
+
place_id: place.placeId,
|
|
2685
|
+
reference: place.placeId,
|
|
2686
|
+
structured_formatting: {
|
|
2687
|
+
main_text: main,
|
|
2688
|
+
main_text_matched_substrings: [{ length: matched, offset: 0 }],
|
|
2689
|
+
secondary_text: second
|
|
2690
|
+
},
|
|
2691
|
+
terms,
|
|
2692
|
+
types: place.kind === "street_address" ? ["premise", "geocode"] : ["postal_code", "geocode"]
|
|
2693
|
+
};
|
|
2694
|
+
};
|
|
2695
|
+
var tokens = (value) => normalize(value).split(" ").filter(Boolean);
|
|
2696
|
+
var IGNORED = /* @__PURE__ */ new Set(["usa", "us", "united", "states"]);
|
|
2697
|
+
var tokensMatch = (input, haystack) => {
|
|
2698
|
+
const used = /* @__PURE__ */ new Set();
|
|
2699
|
+
for (const token of input) {
|
|
2700
|
+
const at = haystack.findIndex((h, i) => !used.has(i) && h.startsWith(token));
|
|
2701
|
+
if (at < 0) return false;
|
|
2702
|
+
used.add(at);
|
|
2703
|
+
}
|
|
2704
|
+
return true;
|
|
2705
|
+
};
|
|
2706
|
+
var rowHaystack = (row2) => tokens(`${row2.line1} ${row2.city} ${row2.state} ${STATE_NAMES[row2.state] ?? ""} ${row2.zip}`);
|
|
2707
|
+
var parseStreetInCity = (input, rows) => {
|
|
2708
|
+
const trimmed = input.trim().replace(/[,\s]+(USA|US|United States)$/i, "");
|
|
2709
|
+
const match = STREET_NUMBER.exec(trimmed);
|
|
2710
|
+
if (!match) return void 0;
|
|
2711
|
+
const number = match[1];
|
|
2712
|
+
const rest = match[2].replace(/,/g, " ").replace(/\s+/g, " ").trim();
|
|
2713
|
+
const restNorm = normalize(rest);
|
|
2714
|
+
const candidates = [...rows].sort((a, b) => b.city.length - a.city.length);
|
|
2715
|
+
for (const row2 of candidates) {
|
|
2716
|
+
const city = normalize(row2.city);
|
|
2717
|
+
const suffixes = [
|
|
2718
|
+
`${city} ${row2.state.toLowerCase()} ${row2.zip}`,
|
|
2719
|
+
`${city} ${row2.state.toLowerCase()}`,
|
|
2720
|
+
`${city} ${row2.zip}`,
|
|
2721
|
+
city
|
|
2722
|
+
];
|
|
2723
|
+
for (const suffix of suffixes) {
|
|
2724
|
+
if (!restNorm.endsWith(` ${suffix}`)) continue;
|
|
2725
|
+
const streetNorm = restNorm.slice(0, restNorm.length - suffix.length).trim();
|
|
2726
|
+
if (!streetNorm) continue;
|
|
2727
|
+
const words = rest.split(" ");
|
|
2728
|
+
const streetWords = words.slice(0, streetNorm.split(" ").length);
|
|
2729
|
+
const street = streetWords.join(" ").replace(/[,\s]+$/, "");
|
|
2730
|
+
return { line1: `${number} ${street}`, row: row2 };
|
|
2731
|
+
}
|
|
2732
|
+
}
|
|
2733
|
+
return void 0;
|
|
2734
|
+
};
|
|
2735
|
+
var autocomplete = (input, rows) => {
|
|
2736
|
+
const wanted = tokens(input).filter((t) => !IGNORED.has(t));
|
|
2737
|
+
if (wanted.length === 0) return [];
|
|
2738
|
+
const out = [];
|
|
2739
|
+
const synth = parseStreetInCity(input, rows);
|
|
2740
|
+
if (synth) {
|
|
2741
|
+
const exact = rows.find(
|
|
2742
|
+
(row2) => normalize(row2.line1) === normalize(synth.line1) && normalize(row2.city) === normalize(synth.row.city)
|
|
2743
|
+
);
|
|
2744
|
+
out.push(exact ? fromRow(exact) : synthesize(synth.line1, synth.row));
|
|
2745
|
+
}
|
|
2746
|
+
for (const row2 of rows) {
|
|
2747
|
+
if (out.length >= MAX_PREDICTIONS) break;
|
|
2748
|
+
if (out.some((p) => p.placeId === corpusPlaceId(row2))) continue;
|
|
2749
|
+
if (tokensMatch(wanted, rowHaystack(row2))) out.push(fromRow(row2));
|
|
2750
|
+
}
|
|
2751
|
+
return out;
|
|
2752
|
+
};
|
|
2753
|
+
var geocode = (address, rows) => {
|
|
2754
|
+
const n = normalize(address);
|
|
2755
|
+
if (!n) return void 0;
|
|
2756
|
+
const synth = parseStreetInCity(address, rows);
|
|
2757
|
+
if (synth) {
|
|
2758
|
+
const exact = rows.find(
|
|
2759
|
+
(row2) => normalize(row2.line1) === normalize(synth.line1) && normalize(row2.city) === normalize(synth.row.city)
|
|
2760
|
+
);
|
|
2761
|
+
return exact ? fromRow(exact) : synthesize(synth.line1, synth.row);
|
|
2762
|
+
}
|
|
2763
|
+
const wanted = tokens(address).filter((t) => !IGNORED.has(t));
|
|
2764
|
+
if (wanted.length === 1 && /^\d{5}$/.test(wanted[0])) {
|
|
2765
|
+
const row2 = rows.find((r) => r.zip === wanted[0]);
|
|
2766
|
+
return row2 ? postalPlace(row2) : void 0;
|
|
2767
|
+
}
|
|
2768
|
+
if (!/\d/.test(n)) {
|
|
2769
|
+
const row2 = rows.find((r) => {
|
|
2770
|
+
const city = tokens(r.city);
|
|
2771
|
+
return wanted.length >= city.length && city.every((t, i) => wanted[i] === t) && wanted.slice(city.length).every((t) => t === r.state.toLowerCase());
|
|
2772
|
+
});
|
|
2773
|
+
return row2 ? postalPlace(row2) : void 0;
|
|
2774
|
+
}
|
|
2775
|
+
return void 0;
|
|
2776
|
+
};
|
|
2777
|
+
var findPlace = (input, rows) => geocode(input, rows) ?? autocomplete(input, rows)[0];
|
|
2778
|
+
|
|
2779
|
+
// src/shim.ts
|
|
2780
|
+
var embed = (value) => JSON.stringify(value).replace(/</g, "\\u003c");
|
|
2781
|
+
var mapsJavaScript = (options) => `/* Mockingbird Google Maps JavaScript API shim (places, geocoder) */
|
|
2782
|
+
(function () {
|
|
2783
|
+
var BASE = ${embed(options.base)};
|
|
2784
|
+
var KEY = ${embed(options.key)};
|
|
2785
|
+
var AUTH_FAILED = ${options.authFailed ? "true" : "false"};
|
|
2786
|
+
var CALLBACK = ${embed(options.callback)};
|
|
2787
|
+
var w = typeof window !== "undefined" ? window : globalThis;
|
|
2788
|
+
var doFetch = (w.fetch && w.fetch.bind(w)) || fetch;
|
|
2789
|
+
|
|
2790
|
+
function request(path, params) {
|
|
2791
|
+
var parts = [];
|
|
2792
|
+
for (var name in params) {
|
|
2793
|
+
var value = params[name];
|
|
2794
|
+
if (value === undefined || value === null || value === "") continue;
|
|
2795
|
+
parts.push(encodeURIComponent(name) + "=" + encodeURIComponent(String(value)));
|
|
2796
|
+
}
|
|
2797
|
+
parts.push("key=" + encodeURIComponent(KEY));
|
|
2798
|
+
return doFetch(BASE + path + "?" + parts.join("&")).then(
|
|
2799
|
+
function (response) {
|
|
2800
|
+
if (!response.ok) return { status: "UNKNOWN_ERROR" };
|
|
2801
|
+
return response.json().then(null, function () { return { status: "UNKNOWN_ERROR" }; });
|
|
2802
|
+
},
|
|
2803
|
+
function () { return { status: "UNKNOWN_ERROR" }; }
|
|
2804
|
+
);
|
|
2805
|
+
}
|
|
2806
|
+
|
|
2807
|
+
function LatLng(lat, lng) { this._lat = Number(lat); this._lng = Number(lng); }
|
|
2808
|
+
LatLng.prototype.lat = function () { return this._lat; };
|
|
2809
|
+
LatLng.prototype.lng = function () { return this._lng; };
|
|
2810
|
+
LatLng.prototype.toJSON = function () { return { lat: this._lat, lng: this._lng }; };
|
|
2811
|
+
LatLng.prototype.toString = function () { return "(" + this._lat + ", " + this._lng + ")"; };
|
|
2812
|
+
LatLng.prototype.equals = function (other) { return !!other && other.lat() === this._lat && other.lng() === this._lng; };
|
|
2813
|
+
|
|
2814
|
+
function LatLngBounds(sw, ne) { this._sw = sw; this._ne = ne; }
|
|
2815
|
+
LatLngBounds.prototype.getSouthWest = function () { return this._sw; };
|
|
2816
|
+
LatLngBounds.prototype.getNorthEast = function () { return this._ne; };
|
|
2817
|
+
LatLngBounds.prototype.toJSON = function () {
|
|
2818
|
+
return { south: this._sw.lat(), west: this._sw.lng(), north: this._ne.lat(), east: this._ne.lng() };
|
|
2819
|
+
};
|
|
2820
|
+
|
|
2821
|
+
function wrapGeometry(g) {
|
|
2822
|
+
if (!g || !g.location) return g;
|
|
2823
|
+
var out = { location: new LatLng(g.location.lat, g.location.lng) };
|
|
2824
|
+
if (g.location_type) out.location_type = g.location_type;
|
|
2825
|
+
if (g.viewport) {
|
|
2826
|
+
out.viewport = new LatLngBounds(
|
|
2827
|
+
new LatLng(g.viewport.southwest.lat, g.viewport.southwest.lng),
|
|
2828
|
+
new LatLng(g.viewport.northeast.lat, g.viewport.northeast.lng)
|
|
2829
|
+
);
|
|
2830
|
+
}
|
|
2831
|
+
return out;
|
|
2832
|
+
}
|
|
2833
|
+
|
|
2834
|
+
function wrapPlace(p) {
|
|
2835
|
+
if (!p) return p;
|
|
2836
|
+
var out = {};
|
|
2837
|
+
for (var k in p) out[k] = p[k];
|
|
2838
|
+
if (p.geometry) out.geometry = wrapGeometry(p.geometry);
|
|
2839
|
+
return out;
|
|
2840
|
+
}
|
|
2841
|
+
|
|
2842
|
+
var PlacesServiceStatus = {
|
|
2843
|
+
OK: "OK", ZERO_RESULTS: "ZERO_RESULTS", INVALID_REQUEST: "INVALID_REQUEST",
|
|
2844
|
+
OVER_QUERY_LIMIT: "OVER_QUERY_LIMIT", REQUEST_DENIED: "REQUEST_DENIED",
|
|
2845
|
+
UNKNOWN_ERROR: "UNKNOWN_ERROR", NOT_FOUND: "NOT_FOUND"
|
|
2846
|
+
};
|
|
2847
|
+
var GeocoderStatus = {
|
|
2848
|
+
OK: "OK", ZERO_RESULTS: "ZERO_RESULTS", INVALID_REQUEST: "INVALID_REQUEST",
|
|
2849
|
+
OVER_QUERY_LIMIT: "OVER_QUERY_LIMIT", REQUEST_DENIED: "REQUEST_DENIED",
|
|
2850
|
+
UNKNOWN_ERROR: "UNKNOWN_ERROR", ERROR: "ERROR"
|
|
2851
|
+
};
|
|
2852
|
+
|
|
2853
|
+
function MapsRequestError(status, endpoint) {
|
|
2854
|
+
var error = new Error(endpoint + ": " + status);
|
|
2855
|
+
error.name = "MapsRequestError";
|
|
2856
|
+
error.code = status;
|
|
2857
|
+
error.endpoint = endpoint;
|
|
2858
|
+
return error;
|
|
2859
|
+
}
|
|
2860
|
+
|
|
2861
|
+
/** Call back, or settle the returned promise (rejecting on errors) when there is no callback. */
|
|
2862
|
+
function settle(callback, results, status, value, endpoint, okStatuses) {
|
|
2863
|
+
if (typeof callback === "function") {
|
|
2864
|
+
callback(results, status);
|
|
2865
|
+
return value;
|
|
2866
|
+
}
|
|
2867
|
+
if (okStatuses.indexOf(status) < 0) throw MapsRequestError(status, endpoint);
|
|
2868
|
+
return value;
|
|
2869
|
+
}
|
|
2870
|
+
|
|
2871
|
+
var tokenSeq = 0;
|
|
2872
|
+
function AutocompleteSessionToken() {
|
|
2873
|
+
tokenSeq += 1;
|
|
2874
|
+
this._id = "mbst-" + tokenSeq + "-" + tokenSeq.toString(36).padStart(8, "0");
|
|
2875
|
+
}
|
|
2876
|
+
AutocompleteSessionToken.prototype.toString = function () { return this._id; };
|
|
2877
|
+
|
|
2878
|
+
function countries(restrictions) {
|
|
2879
|
+
if (!restrictions || !restrictions.country) return undefined;
|
|
2880
|
+
var list = [].concat(restrictions.country);
|
|
2881
|
+
return list.map(function (c) { return "country:" + String(c).toLowerCase(); }).join("|");
|
|
2882
|
+
}
|
|
2883
|
+
|
|
2884
|
+
function fieldList(fields) {
|
|
2885
|
+
if (!fields) return undefined;
|
|
2886
|
+
return [].concat(fields).join(",");
|
|
2887
|
+
}
|
|
2888
|
+
|
|
2889
|
+
function AutocompleteService() {}
|
|
2890
|
+
AutocompleteService.prototype.getPlacePredictions = function (req, callback) {
|
|
2891
|
+
req = req || {};
|
|
2892
|
+
return request("/maps/api/place/autocomplete/json", {
|
|
2893
|
+
input: req.input,
|
|
2894
|
+
sessiontoken: req.sessionToken ? String(req.sessionToken) : undefined,
|
|
2895
|
+
types: req.types ? [].concat(req.types).join("|") : undefined,
|
|
2896
|
+
components: countries(req.componentRestrictions)
|
|
2897
|
+
}).then(function (data) {
|
|
2898
|
+
var status = data.status || "UNKNOWN_ERROR";
|
|
2899
|
+
var predictions = status === "OK" ? data.predictions : null;
|
|
2900
|
+
return settle(callback, predictions, status, { predictions: predictions || [] },
|
|
2901
|
+
"PLACES_AUTOCOMPLETE", ["OK", "ZERO_RESULTS"]);
|
|
2902
|
+
});
|
|
2903
|
+
};
|
|
2904
|
+
|
|
2905
|
+
function PlacesService(attributions) { this._attributions = attributions || null; }
|
|
2906
|
+
PlacesService.prototype.getDetails = function (req, callback) {
|
|
2907
|
+
req = req || {};
|
|
2908
|
+
return request("/maps/api/place/details/json", {
|
|
2909
|
+
place_id: req.placeId,
|
|
2910
|
+
fields: fieldList(req.fields),
|
|
2911
|
+
sessiontoken: req.sessionToken ? String(req.sessionToken) : undefined
|
|
2912
|
+
}).then(function (data) {
|
|
2913
|
+
var status = data.status || "UNKNOWN_ERROR";
|
|
2914
|
+
var place = status === "OK" ? wrapPlace(data.result) : null;
|
|
2915
|
+
return settle(callback, place, status, place, "PLACES_GET_PLACE", ["OK"]);
|
|
2916
|
+
});
|
|
2917
|
+
};
|
|
2918
|
+
PlacesService.prototype.findPlaceFromQuery = function (req, callback) {
|
|
2919
|
+
req = req || {};
|
|
2920
|
+
return request("/maps/api/place/findplacefromtext/json", {
|
|
2921
|
+
input: req.query,
|
|
2922
|
+
inputtype: "textquery",
|
|
2923
|
+
fields: fieldList(req.fields)
|
|
2924
|
+
}).then(function (data) {
|
|
2925
|
+
var status = data.status || "UNKNOWN_ERROR";
|
|
2926
|
+
var results = status === "OK" ? (data.candidates || []).map(wrapPlace) : null;
|
|
2927
|
+
return settle(callback, results, status, { results: results || [] },
|
|
2928
|
+
"PLACES_FIND_PLACE_FROM_QUERY", ["OK", "ZERO_RESULTS"]);
|
|
2929
|
+
});
|
|
2930
|
+
};
|
|
2931
|
+
|
|
2932
|
+
function Geocoder() {}
|
|
2933
|
+
Geocoder.prototype.geocode = function (req, callback) {
|
|
2934
|
+
req = req || {};
|
|
2935
|
+
return request("/maps/api/geocode/json", {
|
|
2936
|
+
address: req.address,
|
|
2937
|
+
place_id: req.placeId,
|
|
2938
|
+
components: req.componentRestrictions && req.componentRestrictions.postalCode
|
|
2939
|
+
? "postal_code:" + req.componentRestrictions.postalCode : undefined
|
|
2940
|
+
}).then(function (data) {
|
|
2941
|
+
var status = data.status || "ERROR";
|
|
2942
|
+
var results = status === "OK" ? (data.results || []).map(wrapPlace) : null;
|
|
2943
|
+
return settle(callback, results, status, { results: results || [] }, "GEOCODER_GEOCODE", ["OK"]);
|
|
2944
|
+
});
|
|
2945
|
+
};
|
|
2946
|
+
|
|
2947
|
+
w.google = w.google || {};
|
|
2948
|
+
w.google.maps = {
|
|
2949
|
+
version: "mockingbird",
|
|
2950
|
+
LatLng: LatLng,
|
|
2951
|
+
LatLngBounds: LatLngBounds,
|
|
2952
|
+
Geocoder: Geocoder,
|
|
2953
|
+
GeocoderStatus: GeocoderStatus,
|
|
2954
|
+
places: {
|
|
2955
|
+
AutocompleteService: AutocompleteService,
|
|
2956
|
+
AutocompleteSessionToken: AutocompleteSessionToken,
|
|
2957
|
+
PlacesService: PlacesService,
|
|
2958
|
+
PlacesServiceStatus: PlacesServiceStatus
|
|
2959
|
+
},
|
|
2960
|
+
importLibrary: function (name) {
|
|
2961
|
+
if (name === "places") return Promise.resolve(w.google.maps.places);
|
|
2962
|
+
if (name === "geocoding") return Promise.resolve({ Geocoder: Geocoder, GeocoderStatus: GeocoderStatus });
|
|
2963
|
+
return Promise.resolve({ LatLng: LatLng, LatLngBounds: LatLngBounds });
|
|
2964
|
+
}
|
|
2965
|
+
};
|
|
2966
|
+
|
|
2967
|
+
if (AUTH_FAILED) {
|
|
2968
|
+
setTimeout(function () { if (typeof w.gm_authFailure === "function") w.gm_authFailure(); }, 0);
|
|
2969
|
+
}
|
|
2970
|
+
if (CALLBACK && typeof w[CALLBACK] === "function") w[CALLBACK]();
|
|
2971
|
+
})();
|
|
2972
|
+
`;
|
|
2973
|
+
|
|
2974
|
+
// src/state.ts
|
|
2975
|
+
var DEFAULT_SETTINGS = { keys: [], publicUrl: null };
|
|
2976
|
+
var GoogleMapsState = class {
|
|
2977
|
+
constructor(sqlite, namespace, seed) {
|
|
2978
|
+
this.seed = seed;
|
|
2979
|
+
this.custom = new Collection(sqlite, namespace, "addresses");
|
|
2980
|
+
this.settings = new Collection(sqlite, namespace, "settings");
|
|
2981
|
+
this.ensureSeeded();
|
|
2982
|
+
}
|
|
2983
|
+
seed;
|
|
2984
|
+
/** Addresses a suite added to this namespace, on top of the built-in corpus. */
|
|
2985
|
+
custom;
|
|
2986
|
+
settings;
|
|
2987
|
+
ensureSeeded() {
|
|
2988
|
+
if (!this.settings.has("settings")) {
|
|
2989
|
+
this.settings.insert("settings", { ...DEFAULT_SETTINGS, ...this.seed.settings });
|
|
2990
|
+
}
|
|
2991
|
+
}
|
|
2992
|
+
current() {
|
|
2993
|
+
return this.settings.get("settings") ?? DEFAULT_SETTINGS;
|
|
2994
|
+
}
|
|
2995
|
+
update(patch) {
|
|
2996
|
+
const next = { ...this.current(), ...patch };
|
|
2997
|
+
this.settings.insert("settings", next);
|
|
2998
|
+
return next;
|
|
2999
|
+
}
|
|
3000
|
+
/** Custom rows first (a suite's own addresses win ties), then the built-in corpus. */
|
|
3001
|
+
corpus() {
|
|
3002
|
+
const custom = this.custom.list({ order: "oldest" }).map((row2) => row2.value);
|
|
3003
|
+
const ids = new Set(custom.map((row2) => row2.id));
|
|
3004
|
+
const base = this.seed.corpus.length > 0 ? this.seed.corpus : DEFAULT_CORPUS;
|
|
3005
|
+
return [...custom, ...base.filter((row2) => !ids.has(row2.id))];
|
|
3006
|
+
}
|
|
3007
|
+
replaceCustom(rows) {
|
|
3008
|
+
for (const { id } of this.custom.list()) this.custom.delete(id);
|
|
3009
|
+
for (const row2 of rows) this.custom.insert(row2.id, row2);
|
|
3010
|
+
}
|
|
3011
|
+
};
|
|
3012
|
+
|
|
3013
|
+
// src/runtime.ts
|
|
3014
|
+
var WEB_SERVICES = ["PlaceAutocomplete", "PlaceDetails", "Geocode", "FindPlaceFromText"];
|
|
3015
|
+
var everyWebService = (rule) => WEB_SERVICES.map((operationId) => ({ operationId, ...rule }));
|
|
3016
|
+
var GOOGLE_MAPS_PRESETS = {
|
|
3017
|
+
over_query_limit: {
|
|
3018
|
+
description: "Every web-service call answers status OVER_QUERY_LIMIT (HTTP 200)",
|
|
3019
|
+
rules: everyWebService({ effect: "google_status", params: { status: "OVER_QUERY_LIMIT" } })
|
|
3020
|
+
},
|
|
3021
|
+
request_denied: {
|
|
3022
|
+
description: "Every web-service call answers status REQUEST_DENIED, as for a revoked key",
|
|
3023
|
+
rules: everyWebService({ effect: "google_status", params: { status: "REQUEST_DENIED" } })
|
|
3024
|
+
},
|
|
3025
|
+
unknown_error: {
|
|
3026
|
+
description: "Every web-service call answers status UNKNOWN_ERROR (Google's transient failure)",
|
|
3027
|
+
rules: everyWebService({ effect: "google_status", params: { status: "UNKNOWN_ERROR" } })
|
|
3028
|
+
},
|
|
3029
|
+
zero_results: {
|
|
3030
|
+
description: "Every web-service call answers status ZERO_RESULTS",
|
|
3031
|
+
rules: everyWebService({ effect: "google_status", params: { status: "ZERO_RESULTS" } })
|
|
3032
|
+
},
|
|
3033
|
+
geocode_zero_results: {
|
|
3034
|
+
description: "Geocoding answers ZERO_RESULTS, so our client falls back to Find Place",
|
|
3035
|
+
rules: [
|
|
3036
|
+
{ operationId: "Geocode", effect: "google_status", params: { status: "ZERO_RESULTS" } }
|
|
3037
|
+
]
|
|
3038
|
+
},
|
|
3039
|
+
autocomplete_over_query_limit: {
|
|
3040
|
+
description: "Only Place Autocomplete answers OVER_QUERY_LIMIT (the manual-entry fallback path)",
|
|
3041
|
+
rules: [
|
|
3042
|
+
{
|
|
3043
|
+
operationId: "PlaceAutocomplete",
|
|
3044
|
+
effect: "google_status",
|
|
3045
|
+
params: { status: "OVER_QUERY_LIMIT" }
|
|
3046
|
+
}
|
|
3047
|
+
]
|
|
3048
|
+
},
|
|
3049
|
+
server_error: {
|
|
3050
|
+
description: "Every web-service call answers HTTP 500 (our client counts !res.ok as a failure)",
|
|
3051
|
+
rules: everyWebService({ status: 500, body: { error: "Internal Server Error" } })
|
|
3052
|
+
},
|
|
3053
|
+
slow: {
|
|
3054
|
+
description: "Every web-service call is held 6 s: past QA's 5 s wait for predictions",
|
|
3055
|
+
rules: everyWebService({ latencyMs: 6e3 })
|
|
3056
|
+
},
|
|
3057
|
+
script_unavailable: {
|
|
3058
|
+
description: "The Maps JavaScript API answers 503, so the script's onerror fires",
|
|
3059
|
+
rules: [{ operationId: "MapsJavaScriptApi", status: 503, body: "Service Unavailable" }]
|
|
3060
|
+
}
|
|
3061
|
+
};
|
|
3062
|
+
var json3 = (status, body) => new Response(JSON.stringify(body), { status, headers: { "content-type": "application/json" } });
|
|
3063
|
+
var adminError3 = (status, message) => json3(status, { error: { type: "mockingbird_admin", message } });
|
|
3064
|
+
var isRecord4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3065
|
+
var parseAddress = (value, index) => {
|
|
3066
|
+
if (!isRecord4(value)) return `addresses[${index}] must be an object`;
|
|
3067
|
+
for (const key of ["line1", "city", "state", "zip"]) {
|
|
3068
|
+
if (typeof value[key] !== "string" || !value[key].trim()) {
|
|
3069
|
+
return `addresses[${index}].${key} must be a non-empty string`;
|
|
3070
|
+
}
|
|
3071
|
+
}
|
|
3072
|
+
const state = String(value.state).toUpperCase();
|
|
3073
|
+
if (!/^[A-Z]{2}$/.test(state)) return `addresses[${index}].state must be a two-letter code`;
|
|
3074
|
+
const lat = value.lat === void 0 ? 0 : Number(value.lat);
|
|
3075
|
+
const lng = value.lng === void 0 ? 0 : Number(value.lng);
|
|
3076
|
+
if (!Number.isFinite(lat) || !Number.isFinite(lng)) {
|
|
3077
|
+
return `addresses[${index}].lat/lng must be numbers`;
|
|
3078
|
+
}
|
|
3079
|
+
return {
|
|
3080
|
+
id: typeof value.id === "string" && value.id ? value.id : `custom-${String(value.zip)}-${String(value.line1).toLowerCase().replace(/[^a-z0-9]+/g, "-")}`,
|
|
3081
|
+
line1: String(value.line1).trim(),
|
|
3082
|
+
city: String(value.city).trim(),
|
|
3083
|
+
state,
|
|
3084
|
+
zip: String(value.zip).trim(),
|
|
3085
|
+
county: typeof value.county === "string" ? value.county : "",
|
|
3086
|
+
lat,
|
|
3087
|
+
lng
|
|
3088
|
+
};
|
|
3089
|
+
};
|
|
3090
|
+
var adminRoutes = (runtime) => ({
|
|
3091
|
+
"GET /corpus": ({ namespace }) => {
|
|
3092
|
+
const api = runtime.instance(namespace);
|
|
3093
|
+
return json3(200, {
|
|
3094
|
+
addresses: api.corpus(),
|
|
3095
|
+
custom: api.state.custom.count()
|
|
3096
|
+
});
|
|
3097
|
+
},
|
|
3098
|
+
"PUT /corpus": ({ body, namespace }) => {
|
|
3099
|
+
const list = Array.isArray(body) ? body : isRecord4(body) ? body.addresses : void 0;
|
|
3100
|
+
if (!Array.isArray(list)) {
|
|
3101
|
+
return adminError3(400, 'expected {"addresses": [{line1, city, state, zip, lat?, lng?}]}');
|
|
3102
|
+
}
|
|
3103
|
+
const rows = [];
|
|
3104
|
+
for (const [index, each] of list.entries()) {
|
|
3105
|
+
const parsed = parseAddress(each, index);
|
|
3106
|
+
if (typeof parsed === "string") return adminError3(400, parsed);
|
|
3107
|
+
rows.push(parsed);
|
|
3108
|
+
}
|
|
3109
|
+
const api = runtime.instance(namespace);
|
|
3110
|
+
api.state.replaceCustom(rows);
|
|
3111
|
+
return json3(200, { custom: rows });
|
|
3112
|
+
},
|
|
3113
|
+
"DELETE /corpus": ({ namespace }) => {
|
|
3114
|
+
runtime.instance(namespace).state.replaceCustom([]);
|
|
3115
|
+
return json3(200, { status: "ok" });
|
|
3116
|
+
},
|
|
3117
|
+
"GET /settings": ({ namespace }) => json3(200, runtime.instance(namespace).state.current()),
|
|
3118
|
+
"PUT /settings": ({ body, namespace }) => {
|
|
3119
|
+
if (!isRecord4(body)) return adminError3(400, "expected a JSON object");
|
|
3120
|
+
const patch = {};
|
|
3121
|
+
if (body.keys !== void 0) {
|
|
3122
|
+
if (!Array.isArray(body.keys)) return adminError3(400, "keys: string[]");
|
|
3123
|
+
patch.keys = body.keys.map(String);
|
|
3124
|
+
}
|
|
3125
|
+
if (body.publicUrl !== void 0) {
|
|
3126
|
+
if (body.publicUrl !== null && typeof body.publicUrl !== "string") {
|
|
3127
|
+
return adminError3(400, "publicUrl: string | null");
|
|
3128
|
+
}
|
|
3129
|
+
patch.publicUrl = body.publicUrl;
|
|
3130
|
+
}
|
|
3131
|
+
return json3(200, runtime.instance(namespace).state.update(patch));
|
|
3132
|
+
}
|
|
3133
|
+
});
|
|
3134
|
+
var createRuntime2 = (options = {}) => createRuntime({
|
|
3135
|
+
name: GOOGLE_MAPS_NAMESPACE,
|
|
3136
|
+
document,
|
|
3137
|
+
...options.sqlite ? { sqlite: options.sqlite } : {},
|
|
3138
|
+
...options.clock ? { clock: options.clock } : {},
|
|
3139
|
+
...options.seed !== void 0 ? { seed: options.seed } : {},
|
|
3140
|
+
...options.adminKey !== void 0 ? { adminKey: options.adminKey } : {},
|
|
3141
|
+
...options.onLog ? { onLog: options.onLog } : {},
|
|
3142
|
+
credential: keyCredential,
|
|
3143
|
+
presets: GOOGLE_MAPS_PRESETS,
|
|
3144
|
+
create: ({ sqlite, namespace, publicNamespace, clock }) => new GoogleMapsAPI({
|
|
3145
|
+
sqlite,
|
|
3146
|
+
namespace,
|
|
3147
|
+
publicNamespace,
|
|
3148
|
+
now: clock.now,
|
|
3149
|
+
...options.corpus ? { corpus: options.corpus } : {},
|
|
3150
|
+
...options.settings ? { settings: options.settings } : {}
|
|
3151
|
+
}),
|
|
3152
|
+
describe: () => ({
|
|
3153
|
+
corpus: options.corpus ? `custom (${options.corpus.length})` : `qa-routing-zip-corpus (${DEFAULT_CORPUS.length})`
|
|
3154
|
+
}),
|
|
3155
|
+
admin: adminRoutes
|
|
3156
|
+
});
|
|
3157
|
+
|
|
3158
|
+
// src/index.ts
|
|
3159
|
+
var GOOGLE_MAPS_NAMESPACE = "google-maps";
|
|
3160
|
+
var 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";
|
|
3161
|
+
var INVALID_KEY_MESSAGE = "The provided API key is invalid. ";
|
|
3162
|
+
var keyCredential = (request) => new URL(request.url).searchParams.get("key") ?? void 0;
|
|
3163
|
+
var CORS = { "access-control-allow-origin": "*" };
|
|
3164
|
+
var json4 = (body, status = 200) => new Response(JSON.stringify(body, null, 3), {
|
|
3165
|
+
status,
|
|
3166
|
+
headers: { "content-type": "application/json; charset=UTF-8", ...CORS }
|
|
3167
|
+
});
|
|
3168
|
+
var EMPTY = {
|
|
3169
|
+
PlaceAutocomplete: { predictions: [] },
|
|
3170
|
+
PlaceDetails: { html_attributions: [] },
|
|
3171
|
+
Geocode: { results: [] },
|
|
3172
|
+
FindPlaceFromText: { candidates: [] }
|
|
3173
|
+
};
|
|
3174
|
+
var DEFAULT_MESSAGES = {
|
|
3175
|
+
OVER_QUERY_LIMIT: "You have exceeded your rate-limit for this API. For more information on Google Maps Platform rate limits, please see https://developers.google.com/maps/documentation/places/web-service/usage-and-billing",
|
|
3176
|
+
UNKNOWN_ERROR: "An unknown error occurred. Please try again.",
|
|
3177
|
+
REQUEST_DENIED: INVALID_KEY_MESSAGE
|
|
3178
|
+
};
|
|
3179
|
+
var text = (value) => typeof value === "string" ? value : Array.isArray(value) ? String(value[0]) : void 0;
|
|
3180
|
+
var GoogleMapsAPI = class {
|
|
3181
|
+
app;
|
|
3182
|
+
sqlite;
|
|
3183
|
+
state;
|
|
3184
|
+
service;
|
|
3185
|
+
publicNamespace;
|
|
3186
|
+
constructor(options = {}) {
|
|
3187
|
+
const sqlite = bootSqlite(options.sqlite);
|
|
3188
|
+
const namespace = options.namespace ?? GOOGLE_MAPS_NAMESPACE;
|
|
3189
|
+
this.publicNamespace = options.publicNamespace ?? "default";
|
|
3190
|
+
this.state = new GoogleMapsState(sqlite, namespace, {
|
|
3191
|
+
corpus: options.corpus ?? [],
|
|
3192
|
+
settings: options.settings ?? {}
|
|
3193
|
+
});
|
|
3194
|
+
const handlers = defineOperations({
|
|
3195
|
+
PlaceAutocomplete: (context) => this.autocomplete(context),
|
|
3196
|
+
PlaceDetails: (context) => this.details(context),
|
|
3197
|
+
Geocode: (context) => this.geocode(context),
|
|
3198
|
+
FindPlaceFromText: (context) => this.findPlace(context),
|
|
3199
|
+
MapsJavaScriptApi: (context) => this.script(context)
|
|
3200
|
+
});
|
|
3201
|
+
this.service = createService({
|
|
3202
|
+
document,
|
|
3203
|
+
handlers,
|
|
3204
|
+
sqlite,
|
|
3205
|
+
namespace,
|
|
3206
|
+
...options.now ? { now: options.now } : {},
|
|
3207
|
+
notFound: () => new Response("<html><body><h1>Not Found</h1></body></html>", {
|
|
3208
|
+
status: 404,
|
|
3209
|
+
headers: { "content-type": "text/html; charset=UTF-8", ...CORS }
|
|
3210
|
+
}),
|
|
3211
|
+
onError: (error) => {
|
|
3212
|
+
if (error instanceof HttpError) return error.toResponse();
|
|
3213
|
+
throw error;
|
|
3214
|
+
},
|
|
3215
|
+
before: (context) => this.gate(context)
|
|
3216
|
+
});
|
|
3217
|
+
this.app = this.service.app;
|
|
3218
|
+
this.sqlite = this.service.sqlite;
|
|
3219
|
+
}
|
|
3220
|
+
fetch(request) {
|
|
3221
|
+
return this.service.fetch(request);
|
|
3222
|
+
}
|
|
3223
|
+
async reset() {
|
|
3224
|
+
await this.service.reset();
|
|
3225
|
+
this.state.ensureSeeded();
|
|
3226
|
+
}
|
|
3227
|
+
/** The addresses this namespace resolves (custom rows first). */
|
|
3228
|
+
corpus() {
|
|
3229
|
+
return this.state.corpus();
|
|
3230
|
+
}
|
|
3231
|
+
status(operationId, status, message) {
|
|
3232
|
+
return json4({
|
|
3233
|
+
...EMPTY[operationId],
|
|
3234
|
+
...message ? { error_message: message } : {},
|
|
3235
|
+
status
|
|
3236
|
+
});
|
|
3237
|
+
}
|
|
3238
|
+
/** Key check and status-effect faults, before every web-service call. */
|
|
3239
|
+
gate(context) {
|
|
3240
|
+
const operationId = context.operation.operationId;
|
|
3241
|
+
if (operationId === "MapsJavaScriptApi") return void 0;
|
|
3242
|
+
const key = text(context.query.key);
|
|
3243
|
+
if (!key) return this.status(operationId, "REQUEST_DENIED", MISSING_KEY_MESSAGE);
|
|
3244
|
+
const keys = this.state.current().keys;
|
|
3245
|
+
if (keys.length > 0 && !keys.includes(key)) {
|
|
3246
|
+
return this.status(operationId, "REQUEST_DENIED", INVALID_KEY_MESSAGE);
|
|
3247
|
+
}
|
|
3248
|
+
const forced = faultEffect(context.request, "google_status");
|
|
3249
|
+
if (forced !== void 0) {
|
|
3250
|
+
const status = typeof forced.status === "string" ? forced.status : "UNKNOWN_ERROR";
|
|
3251
|
+
const message = typeof forced.error_message === "string" ? forced.error_message : DEFAULT_MESSAGES[status];
|
|
3252
|
+
return this.status(operationId, status, message);
|
|
3253
|
+
}
|
|
3254
|
+
return void 0;
|
|
3255
|
+
}
|
|
3256
|
+
noted(response, context, place) {
|
|
3257
|
+
const session = text(context.query.sessiontoken);
|
|
3258
|
+
const ids = {};
|
|
3259
|
+
if (place) ids.placeId = place.placeId;
|
|
3260
|
+
if (session) ids.sessionToken = session;
|
|
3261
|
+
return Object.keys(ids).length > 0 ? annotateResponse(response, { ids }) : response;
|
|
3262
|
+
}
|
|
3263
|
+
autocomplete(context) {
|
|
3264
|
+
const input = text(context.query.input);
|
|
3265
|
+
if (!input) return this.status("PlaceAutocomplete", "INVALID_REQUEST");
|
|
3266
|
+
const components = text(context.query.components);
|
|
3267
|
+
if (components !== void 0) {
|
|
3268
|
+
const countries = components.split("|").map((c) => c.trim().toLowerCase()).filter((c) => c.startsWith("country:")).map((c) => c.slice("country:".length));
|
|
3269
|
+
if (countries.length > 0 && !countries.includes("us")) {
|
|
3270
|
+
return this.noted(json4({ predictions: [], status: "ZERO_RESULTS" }), context);
|
|
3271
|
+
}
|
|
3272
|
+
}
|
|
3273
|
+
const types = text(context.query.types);
|
|
3274
|
+
let places = autocomplete(input, this.state.corpus());
|
|
3275
|
+
if (types === "address") places = places.filter((p) => p.kind === "street_address");
|
|
3276
|
+
if (places.length === 0) {
|
|
3277
|
+
return this.noted(json4({ predictions: [], status: "ZERO_RESULTS" }), context);
|
|
3278
|
+
}
|
|
3279
|
+
return this.noted(
|
|
3280
|
+
json4({ predictions: places.map((p) => prediction(p, input)), status: "OK" }),
|
|
3281
|
+
context,
|
|
3282
|
+
places[0]
|
|
3283
|
+
);
|
|
3284
|
+
}
|
|
3285
|
+
details(context) {
|
|
3286
|
+
const placeId = text(context.query.place_id);
|
|
3287
|
+
if (!placeId) return this.status("PlaceDetails", "INVALID_REQUEST");
|
|
3288
|
+
const fields = text(context.query.fields);
|
|
3289
|
+
if (unknownFields(fields).length > 0) {
|
|
3290
|
+
return this.status(
|
|
3291
|
+
"PlaceDetails",
|
|
3292
|
+
"INVALID_REQUEST",
|
|
3293
|
+
`Error while parsing 'fields' parameter: Unsupported field name '${unknownFields(fields)[0]}'. `
|
|
3294
|
+
);
|
|
3295
|
+
}
|
|
3296
|
+
const place = placeById(placeId, this.state.corpus());
|
|
3297
|
+
if (!place) return this.noted(this.status("PlaceDetails", "NOT_FOUND"), context);
|
|
3298
|
+
return this.noted(
|
|
3299
|
+
json4({
|
|
3300
|
+
html_attributions: [],
|
|
3301
|
+
result: pickFields(placeResult(place), fields, "all"),
|
|
3302
|
+
status: "OK"
|
|
3303
|
+
}),
|
|
3304
|
+
context,
|
|
3305
|
+
place
|
|
3306
|
+
);
|
|
3307
|
+
}
|
|
3308
|
+
geocode(context) {
|
|
3309
|
+
const address = text(context.query.address);
|
|
3310
|
+
const placeId = text(context.query.place_id);
|
|
3311
|
+
const components = text(context.query.components);
|
|
3312
|
+
const zip = components?.split("|").find((c) => c.startsWith("postal_code:"))?.slice("postal_code:".length);
|
|
3313
|
+
if (!address && !placeId && !zip) return this.status("Geocode", "INVALID_REQUEST");
|
|
3314
|
+
const rows = this.state.corpus();
|
|
3315
|
+
const place = placeId ? placeById(placeId, rows) : address ? geocode(address, rows) : geocode(zip, rows);
|
|
3316
|
+
if (!place) return this.noted(json4({ results: [], status: "ZERO_RESULTS" }), context);
|
|
3317
|
+
const full = placeResult(place);
|
|
3318
|
+
return this.noted(
|
|
3319
|
+
json4({
|
|
3320
|
+
results: [
|
|
3321
|
+
{
|
|
3322
|
+
address_components: full.address_components,
|
|
3323
|
+
formatted_address: full.formatted_address,
|
|
3324
|
+
geometry: geometry(place, true),
|
|
3325
|
+
place_id: place.placeId,
|
|
3326
|
+
types: full.types
|
|
3327
|
+
}
|
|
3328
|
+
],
|
|
3329
|
+
status: "OK"
|
|
3330
|
+
}),
|
|
3331
|
+
context,
|
|
3332
|
+
place
|
|
3333
|
+
);
|
|
3334
|
+
}
|
|
3335
|
+
findPlace(context) {
|
|
3336
|
+
const input = text(context.query.input);
|
|
3337
|
+
const inputtype = text(context.query.inputtype);
|
|
3338
|
+
if (!input || inputtype !== "textquery" && inputtype !== "phonenumber") {
|
|
3339
|
+
return this.status("FindPlaceFromText", "INVALID_REQUEST");
|
|
3340
|
+
}
|
|
3341
|
+
const fields = text(context.query.fields);
|
|
3342
|
+
if (unknownFields(fields).length > 0) {
|
|
3343
|
+
return this.status(
|
|
3344
|
+
"FindPlaceFromText",
|
|
3345
|
+
"INVALID_REQUEST",
|
|
3346
|
+
`Error while parsing 'fields' parameter: Unsupported field name '${unknownFields(fields)[0]}'. `
|
|
3347
|
+
);
|
|
3348
|
+
}
|
|
3349
|
+
const place = inputtype === "textquery" ? findPlace(input, this.state.corpus()) : void 0;
|
|
3350
|
+
if (!place) return this.noted(json4({ candidates: [], status: "ZERO_RESULTS" }), context);
|
|
3351
|
+
return this.noted(
|
|
3352
|
+
json4({ candidates: [pickFields(placeResult(place), fields, ["place_id"])], status: "OK" }),
|
|
3353
|
+
context,
|
|
3354
|
+
place
|
|
3355
|
+
);
|
|
3356
|
+
}
|
|
3357
|
+
script(context) {
|
|
3358
|
+
const key = text(context.query.key) ?? "";
|
|
3359
|
+
const settings = this.state.current();
|
|
3360
|
+
const origin = (settings.publicUrl ?? context.url.origin).replace(/\/$/, "");
|
|
3361
|
+
const prefix = this.publicNamespace === "default" ? "" : `/ns/${encodeURIComponent(this.publicNamespace)}`;
|
|
3362
|
+
const callback = text(context.query.callback) ?? null;
|
|
3363
|
+
const safeCallback = callback && /^[A-Za-z_$][\w$.]*$/.test(callback) ? callback : null;
|
|
3364
|
+
const authFailed = !key || settings.keys.length > 0 && !settings.keys.includes(key);
|
|
3365
|
+
return new Response(
|
|
3366
|
+
mapsJavaScript({ base: `${origin}${prefix}`, key, authFailed, callback: safeCallback }),
|
|
3367
|
+
{
|
|
3368
|
+
status: 200,
|
|
3369
|
+
headers: { "content-type": "text/javascript; charset=UTF-8", ...CORS }
|
|
3370
|
+
}
|
|
3371
|
+
);
|
|
3372
|
+
}
|
|
3373
|
+
};
|
|
3374
|
+
|
|
3375
|
+
export {
|
|
3376
|
+
document,
|
|
3377
|
+
operationIds,
|
|
3378
|
+
supportedOperationIds,
|
|
3379
|
+
DEFAULT_CORPUS,
|
|
3380
|
+
PHOENIX_DEMO_ADDRESS,
|
|
3381
|
+
STATE_NAMES,
|
|
3382
|
+
normalize,
|
|
3383
|
+
corpusPlaceId,
|
|
3384
|
+
mapsJavaScript,
|
|
3385
|
+
GOOGLE_MAPS_PRESETS,
|
|
3386
|
+
createRuntime2 as createRuntime,
|
|
3387
|
+
GOOGLE_MAPS_NAMESPACE,
|
|
3388
|
+
MISSING_KEY_MESSAGE,
|
|
3389
|
+
INVALID_KEY_MESSAGE,
|
|
3390
|
+
keyCredential,
|
|
3391
|
+
GoogleMapsAPI
|
|
3392
|
+
};
|
|
3393
|
+
//# sourceMappingURL=chunk-5HEE5U7V.js.map
|