@zappie/sdk 0.1.15 → 0.1.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +25 -0
- package/README.md +41 -15
- package/dist/debug.js +1 -1
- package/dist/index.js +1 -1
- package/dist/server/auth/crypto.js +106 -0
- package/dist/server/auth/crypto.js.map +7 -0
- package/dist/server/auth/jwt.js +204 -0
- package/dist/server/auth/jwt.js.map +7 -0
- package/dist/server/auth/session-store.js +46 -0
- package/dist/server/auth/session-store.js.map +7 -0
- package/dist/server/edge-snapshot.js +200 -0
- package/dist/server/edge-snapshot.js.map +7 -0
- package/dist/server/index.d.ts +324 -2
- package/dist/server/index.js +40 -0
- package/dist/server/index.js.map +2 -2
- package/dist/template/index.d.ts +13 -2
- package/dist/template/index.js +35 -2
- package/dist/template/index.js.map +2 -2
- package/dist/template/react.js +12 -1
- package/dist/template/react.js.map +2 -2
- package/package.json +1 -1
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
// src/server/edge-snapshot.ts
|
|
2
|
+
import { mapMenu } from "../transport/mappers.js";
|
|
3
|
+
var snapshotKeys = {
|
|
4
|
+
/** `store:<storeId>` holds a StoreSnapshot. */
|
|
5
|
+
store: (storeId) => `store:${storeId}`,
|
|
6
|
+
/** `menu:<storeId>:<branchId>`, or `menu:<storeId>` for the default branch. */
|
|
7
|
+
menu: (storeId, branchId) => branchId === void 0 ? `menu:${storeId}` : `menu:${storeId}:${branchId}`,
|
|
8
|
+
/** `avail:<storeId>:<branchId>` holds an AvailabilityPatch. */
|
|
9
|
+
availability: (storeId, branchId) => `avail:${storeId}:${branchId}`,
|
|
10
|
+
/** `ver:<storeId>` holds a SnapshotVersionRecord. */
|
|
11
|
+
version: (storeId) => `ver:${storeId}`
|
|
12
|
+
};
|
|
13
|
+
function canonical(value, seen) {
|
|
14
|
+
if (value === null || typeof value === "boolean") {
|
|
15
|
+
return JSON.stringify(value);
|
|
16
|
+
}
|
|
17
|
+
if (typeof value === "string") {
|
|
18
|
+
return JSON.stringify(value);
|
|
19
|
+
}
|
|
20
|
+
if (typeof value === "number") {
|
|
21
|
+
if (!Number.isFinite(value)) {
|
|
22
|
+
throw new TypeError("Canonical JSON can't hold a non-finite number.");
|
|
23
|
+
}
|
|
24
|
+
return JSON.stringify(value);
|
|
25
|
+
}
|
|
26
|
+
if (typeof value !== "object") {
|
|
27
|
+
throw new TypeError(`Canonical JSON can't hold a ${typeof value}.`);
|
|
28
|
+
}
|
|
29
|
+
if (seen.has(value)) {
|
|
30
|
+
throw new TypeError("Canonical JSON can't hold a cycle.");
|
|
31
|
+
}
|
|
32
|
+
seen.add(value);
|
|
33
|
+
let out;
|
|
34
|
+
if (Array.isArray(value)) {
|
|
35
|
+
out = `[${value.map((entry) => entry === void 0 ? "null" : canonical(entry, seen)).join(",")}]`;
|
|
36
|
+
} else {
|
|
37
|
+
const prototype = Object.getPrototypeOf(value);
|
|
38
|
+
if (prototype !== Object.prototype && prototype !== null) {
|
|
39
|
+
throw new TypeError("Canonical JSON holds plain objects only.");
|
|
40
|
+
}
|
|
41
|
+
out = `{${Object.keys(value).filter((key) => value[key] !== void 0).sort().map(
|
|
42
|
+
(key) => `${JSON.stringify(key)}:${canonical(value[key], seen)}`
|
|
43
|
+
).join(",")}}`;
|
|
44
|
+
}
|
|
45
|
+
seen.delete(value);
|
|
46
|
+
return out;
|
|
47
|
+
}
|
|
48
|
+
function canonicalJson(value) {
|
|
49
|
+
return canonical(value, /* @__PURE__ */ new Set());
|
|
50
|
+
}
|
|
51
|
+
async function snapshotHash(value) {
|
|
52
|
+
const digest = await crypto.subtle.digest(
|
|
53
|
+
"SHA-256",
|
|
54
|
+
new TextEncoder().encode(canonicalJson(value))
|
|
55
|
+
);
|
|
56
|
+
return Array.from(
|
|
57
|
+
new Uint8Array(digest).slice(0, 8),
|
|
58
|
+
(byte) => byte.toString(16).padStart(2, "0")
|
|
59
|
+
).join("");
|
|
60
|
+
}
|
|
61
|
+
var isoTime = (value) => {
|
|
62
|
+
const time = value === void 0 ? /* @__PURE__ */ new Date() : new Date(value);
|
|
63
|
+
if (Number.isNaN(time.getTime())) {
|
|
64
|
+
throw new TypeError("Snapshot times must be valid dates.");
|
|
65
|
+
}
|
|
66
|
+
return time.toISOString();
|
|
67
|
+
};
|
|
68
|
+
async function createMenuSnapshot({
|
|
69
|
+
storeId,
|
|
70
|
+
menu,
|
|
71
|
+
builtAt,
|
|
72
|
+
sourceUpdatedAt
|
|
73
|
+
}) {
|
|
74
|
+
if (!storeId || !menu.branchId || !menu.version) {
|
|
75
|
+
throw new TypeError(
|
|
76
|
+
"A menu snapshot needs a store, a branch and a menu version."
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
if (menu.completeness === "partial") {
|
|
80
|
+
throw new TypeError("A partial menu can't become a snapshot.");
|
|
81
|
+
}
|
|
82
|
+
return {
|
|
83
|
+
v: 1,
|
|
84
|
+
storeId,
|
|
85
|
+
branchId: menu.branchId,
|
|
86
|
+
version: menu.version,
|
|
87
|
+
contentHash: await snapshotHash(menu),
|
|
88
|
+
builtAt: isoTime(builtAt),
|
|
89
|
+
...sourceUpdatedAt === void 0 ? {} : { sourceUpdatedAt: isoTime(sourceUpdatedAt) },
|
|
90
|
+
menu
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
async function menuSnapshotFromPayload({
|
|
94
|
+
storeId,
|
|
95
|
+
branchId,
|
|
96
|
+
payload,
|
|
97
|
+
builtAt
|
|
98
|
+
}) {
|
|
99
|
+
const menu = mapMenu(payload, branchId);
|
|
100
|
+
const updates = (Array.isArray(payload?.menus) ? payload.menus : []).map((source) => source?.updated_at).filter(
|
|
101
|
+
(value) => typeof value === "string" && !Number.isNaN(Date.parse(value))
|
|
102
|
+
).sort((left, right) => Date.parse(right) - Date.parse(left));
|
|
103
|
+
return createMenuSnapshot({
|
|
104
|
+
storeId,
|
|
105
|
+
menu,
|
|
106
|
+
...builtAt === void 0 ? {} : { builtAt },
|
|
107
|
+
...updates[0] === void 0 ? {} : { sourceUpdatedAt: updates[0] }
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
async function createStoreSnapshot({
|
|
111
|
+
store,
|
|
112
|
+
builtAt
|
|
113
|
+
}) {
|
|
114
|
+
if (!store.id) {
|
|
115
|
+
throw new TypeError("A store snapshot needs a store id.");
|
|
116
|
+
}
|
|
117
|
+
return {
|
|
118
|
+
v: 1,
|
|
119
|
+
storeId: store.id,
|
|
120
|
+
contentHash: await snapshotHash(store),
|
|
121
|
+
builtAt: isoTime(builtAt),
|
|
122
|
+
store
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
var ids = (values) => [...new Set(values ?? [])].sort();
|
|
126
|
+
async function createAvailabilityPatch(input) {
|
|
127
|
+
if (!input.menuVersion) {
|
|
128
|
+
throw new TypeError("An availability patch needs the API's menu version.");
|
|
129
|
+
}
|
|
130
|
+
const content = {
|
|
131
|
+
v: 1,
|
|
132
|
+
menuVersion: input.menuVersion,
|
|
133
|
+
soldOutItems: ids(input.soldOutItems),
|
|
134
|
+
soldOutVariants: ids(input.soldOutVariants),
|
|
135
|
+
soldOutOptions: ids(input.soldOutOptions),
|
|
136
|
+
...input.closedUntil === void 0 ? {} : { closedUntil: isoTime(input.closedUntil) }
|
|
137
|
+
};
|
|
138
|
+
return {
|
|
139
|
+
...content,
|
|
140
|
+
version: await snapshotHash(content),
|
|
141
|
+
updatedAt: isoTime(input.updatedAt)
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
function applyAvailability(snapshot, patch) {
|
|
145
|
+
if (!patch || Date.parse(patch.updatedAt) < Date.parse(snapshot.builtAt)) {
|
|
146
|
+
return snapshot.menu;
|
|
147
|
+
}
|
|
148
|
+
const items = new Set(patch.soldOutItems);
|
|
149
|
+
const variants = new Set(patch.soldOutVariants);
|
|
150
|
+
const options = new Set(patch.soldOutOptions);
|
|
151
|
+
return {
|
|
152
|
+
...snapshot.menu,
|
|
153
|
+
version: patch.menuVersion,
|
|
154
|
+
items: Object.fromEntries(
|
|
155
|
+
Object.entries(snapshot.menu.items).map(([id, item]) => {
|
|
156
|
+
const touched = item.variants.some(
|
|
157
|
+
(variant) => variants.has(variant.id)
|
|
158
|
+
);
|
|
159
|
+
const patched = item.variants.map(
|
|
160
|
+
(variant) => variants.has(variant.id) ? { ...variant, isAvailable: false } : variant
|
|
161
|
+
);
|
|
162
|
+
return [
|
|
163
|
+
id,
|
|
164
|
+
{
|
|
165
|
+
...item,
|
|
166
|
+
// Selling out every variant sells out the item.
|
|
167
|
+
isAvailable: item.isAvailable && !items.has(id) && (!touched || patched.some((variant) => variant.isAvailable)),
|
|
168
|
+
variants: patched,
|
|
169
|
+
optionGroups: item.optionGroups.map((group) => ({
|
|
170
|
+
...group,
|
|
171
|
+
choices: group.choices.map(
|
|
172
|
+
(choice) => options.has(choice.id) ? { ...choice, isAvailable: false } : choice
|
|
173
|
+
)
|
|
174
|
+
}))
|
|
175
|
+
}
|
|
176
|
+
];
|
|
177
|
+
})
|
|
178
|
+
)
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
function isMenuSnapshot(value) {
|
|
182
|
+
if (!value || typeof value !== "object") {
|
|
183
|
+
return false;
|
|
184
|
+
}
|
|
185
|
+
const snapshot = value;
|
|
186
|
+
const menu = snapshot.menu;
|
|
187
|
+
return snapshot.v === 1 && typeof snapshot.storeId === "string" && typeof snapshot.branchId === "string" && typeof snapshot.version === "string" && typeof snapshot.contentHash === "string" && typeof snapshot.builtAt === "string" && !!menu && typeof menu === "object" && menu.branchId === snapshot.branchId && menu.version === snapshot.version && menu.completeness !== "partial" && Array.isArray(menu.categories) && !!menu.items && typeof menu.items === "object";
|
|
188
|
+
}
|
|
189
|
+
export {
|
|
190
|
+
applyAvailability,
|
|
191
|
+
canonicalJson,
|
|
192
|
+
createAvailabilityPatch,
|
|
193
|
+
createMenuSnapshot,
|
|
194
|
+
createStoreSnapshot,
|
|
195
|
+
isMenuSnapshot,
|
|
196
|
+
menuSnapshotFromPayload,
|
|
197
|
+
snapshotHash,
|
|
198
|
+
snapshotKeys
|
|
199
|
+
};
|
|
200
|
+
//# sourceMappingURL=edge-snapshot.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/server/edge-snapshot.ts"],
|
|
4
|
+
"sourcesContent": ["import type { Menu, Store } from \"../models\";\nimport { mapMenu } from \"../transport/mappers\";\n\n/**\n * A branch catalogue as the edge keeps it: the SDK `Menu` that `menu.get()`\n * returns, with the facts a snapshot writer and reader need around it.\n */\nexport type MenuSnapshot = {\n v: 1;\n storeId: string;\n branchId: string;\n /** The API's menu version, the same value `GET /menus/version` returns. */\n version: string;\n /** The first 16 hex digits of the SHA-256 of the menu's canonical JSON. */\n contentHash: string;\n /** When the snapshot was built, as an ISO time. */\n builtAt: string;\n /** The latest change among the source menus, as an ISO time, when the API sends it. */\n sourceUpdatedAt?: string;\n /** A complete branch catalogue. Partial category pages never become snapshots. */\n menu: Menu;\n};\n\n/** Store details as the edge keeps them: the SDK `Store` that `store.get()` returns. */\nexport type StoreSnapshot = {\n v: 1;\n storeId: string;\n /** The first 16 hex digits of the SHA-256 of the store's canonical JSON. */\n contentHash: string;\n /** When the snapshot was built, as an ISO time. */\n builtAt: string;\n store: Store;\n};\n\n/**\n * What sold out since a branch's menu snapshot was built. It is small, so an\n * availability change rewrites about 1 KB instead of the whole menu.\n */\nexport type AvailabilityPatch = {\n v: 1;\n /** The first 16 hex digits of the SHA-256 of this patch's canonical JSON. */\n version: string;\n /** The API's menu version after this change. A patched menu reports it. */\n menuVersion: string;\n soldOutItems: string[];\n soldOutVariants: string[];\n /** Choice ids in option groups. */\n soldOutOptions: string[];\n /** When the branch reopens, as an ISO time, if it is closed for now. */\n closedUntil?: string;\n /** When the patch was built, as an ISO time. */\n updatedAt: string;\n};\n\n/** The version record a writer saves last, so readers never see unwritten data. */\nexport type SnapshotVersionRecord = {\n /** The default branch's menu version. */\n version: string;\n builtAt: string;\n branches: Record<string, string>;\n};\n\n/** The edge key-value names for each snapshot. */\nexport const snapshotKeys = {\n /** `store:<storeId>` holds a StoreSnapshot. */\n store: (storeId: string) => `store:${storeId}`,\n /** `menu:<storeId>:<branchId>`, or `menu:<storeId>` for the default branch. */\n menu: (storeId: string, branchId?: string) =>\n branchId === undefined ? `menu:${storeId}` : `menu:${storeId}:${branchId}`,\n /** `avail:<storeId>:<branchId>` holds an AvailabilityPatch. */\n availability: (storeId: string, branchId: string) =>\n `avail:${storeId}:${branchId}`,\n /** `ver:<storeId>` holds a SnapshotVersionRecord. */\n version: (storeId: string) => `ver:${storeId}`,\n} as const;\n\nfunction canonical(value: unknown, seen: Set<object>): string {\n if (value === null || typeof value === \"boolean\") {\n return JSON.stringify(value);\n }\n if (typeof value === \"string\") {\n return JSON.stringify(value);\n }\n if (typeof value === \"number\") {\n if (!Number.isFinite(value)) {\n throw new TypeError(\"Canonical JSON can't hold a non-finite number.\");\n }\n return JSON.stringify(value);\n }\n if (typeof value !== \"object\") {\n throw new TypeError(`Canonical JSON can't hold a ${typeof value}.`);\n }\n if (seen.has(value)) {\n throw new TypeError(\"Canonical JSON can't hold a cycle.\");\n }\n seen.add(value);\n let out: string;\n if (Array.isArray(value)) {\n out = `[${value\n .map((entry) => (entry === undefined ? \"null\" : canonical(entry, seen)))\n .join(\",\")}]`;\n } else {\n const prototype = Object.getPrototypeOf(value) as unknown;\n if (prototype !== Object.prototype && prototype !== null) {\n throw new TypeError(\"Canonical JSON holds plain objects only.\");\n }\n out = `{${Object.keys(value)\n .filter((key) => (value as Record<string, unknown>)[key] !== undefined)\n .sort()\n .map(\n (key) =>\n `${JSON.stringify(key)}:${canonical((value as Record<string, unknown>)[key], seen)}`,\n )\n .join(\",\")}}`;\n }\n seen.delete(value);\n return out;\n}\n\n/**\n * Serialise a JSON value with object keys in sorted order and undefined\n * properties left out, so equal values always give the same text.\n */\nexport function canonicalJson(value: unknown): string {\n return canonical(value, new Set());\n}\n\n/** Hash a JSON value: the first 16 hex digits of the SHA-256 of its canonical JSON. */\nexport async function snapshotHash(value: unknown): Promise<string> {\n const digest = await crypto.subtle.digest(\n \"SHA-256\",\n new TextEncoder().encode(canonicalJson(value)),\n );\n return Array.from(new Uint8Array(digest).slice(0, 8), (byte) =>\n byte.toString(16).padStart(2, \"0\"),\n ).join(\"\");\n}\n\nconst isoTime = (value: Date | string | undefined) => {\n const time = value === undefined ? new Date() : new Date(value);\n if (Number.isNaN(time.getTime())) {\n throw new TypeError(\"Snapshot times must be valid dates.\");\n }\n return time.toISOString();\n};\n\n/** Input for building a MenuSnapshot from an SDK menu. */\nexport type MenuSnapshotInput = {\n storeId: string;\n menu: Menu;\n /** Defaults to now. */\n builtAt?: Date | string;\n sourceUpdatedAt?: Date | string;\n};\n\n/** Build a MenuSnapshot from a complete SDK menu. */\nexport async function createMenuSnapshot({\n storeId,\n menu,\n builtAt,\n sourceUpdatedAt,\n}: MenuSnapshotInput): Promise<MenuSnapshot> {\n if (!storeId || !menu.branchId || !menu.version) {\n throw new TypeError(\n \"A menu snapshot needs a store, a branch and a menu version.\",\n );\n }\n if (menu.completeness === \"partial\") {\n throw new TypeError(\"A partial menu can't become a snapshot.\");\n }\n return {\n v: 1,\n storeId,\n branchId: menu.branchId,\n version: menu.version,\n contentHash: await snapshotHash(menu),\n builtAt: isoTime(builtAt),\n ...(sourceUpdatedAt === undefined\n ? {}\n : { sourceUpdatedAt: isoTime(sourceUpdatedAt) }),\n menu,\n };\n}\n\n/** Input for building a MenuSnapshot from the API's menu payload. */\nexport type MenuSnapshotPayloadInput = {\n storeId: string;\n branchId: string;\n /** The JSON body of the API's branch menu read. */\n payload: unknown;\n /** Defaults to now. */\n builtAt?: Date | string;\n};\n\n/**\n * Build a MenuSnapshot from the API's branch menu payload. It maps the payload\n * with the same mapper `menu.get()` uses, so the snapshot's menu is exactly\n * what the client SDK returns.\n */\nexport async function menuSnapshotFromPayload({\n storeId,\n branchId,\n payload,\n builtAt,\n}: MenuSnapshotPayloadInput): Promise<MenuSnapshot> {\n const menu = mapMenu(payload, branchId);\n const updates = (\n Array.isArray((payload as { menus?: unknown } | null)?.menus)\n ? ((payload as { menus: unknown[] }).menus as { updated_at?: unknown }[])\n : []\n )\n .map((source) => source?.updated_at)\n .filter(\n (value): value is string =>\n typeof value === \"string\" && !Number.isNaN(Date.parse(value)),\n )\n .sort((left, right) => Date.parse(right) - Date.parse(left));\n return createMenuSnapshot({\n storeId,\n menu,\n ...(builtAt === undefined ? {} : { builtAt }),\n ...(updates[0] === undefined ? {} : { sourceUpdatedAt: updates[0] }),\n });\n}\n\n/** Build a StoreSnapshot from an SDK store. */\nexport async function createStoreSnapshot({\n store,\n builtAt,\n}: {\n store: Store;\n builtAt?: Date | string;\n}): Promise<StoreSnapshot> {\n if (!store.id) {\n throw new TypeError(\"A store snapshot needs a store id.\");\n }\n return {\n v: 1,\n storeId: store.id,\n contentHash: await snapshotHash(store),\n builtAt: isoTime(builtAt),\n store,\n };\n}\n\n/** Input for building an AvailabilityPatch. */\nexport type AvailabilityPatchInput = {\n menuVersion: string;\n soldOutItems?: readonly string[];\n soldOutVariants?: readonly string[];\n soldOutOptions?: readonly string[];\n closedUntil?: Date | string;\n /** Defaults to now. */\n updatedAt?: Date | string;\n};\n\nconst ids = (values: readonly string[] | undefined) =>\n [...new Set(values ?? [])].sort();\n\n/** Build an AvailabilityPatch with sorted, unique ids and its content hash. */\nexport async function createAvailabilityPatch(\n input: AvailabilityPatchInput,\n): Promise<AvailabilityPatch> {\n if (!input.menuVersion) {\n throw new TypeError(\"An availability patch needs the API's menu version.\");\n }\n const content = {\n v: 1 as const,\n menuVersion: input.menuVersion,\n soldOutItems: ids(input.soldOutItems),\n soldOutVariants: ids(input.soldOutVariants),\n soldOutOptions: ids(input.soldOutOptions),\n ...(input.closedUntil === undefined\n ? {}\n : { closedUntil: isoTime(input.closedUntil) }),\n };\n return {\n ...content,\n version: await snapshotHash(content),\n updatedAt: isoTime(input.updatedAt),\n };\n}\n\n/**\n * Return the snapshot's menu with an availability patch applied. A patch\n * built before the snapshot is ignored, because the rebuilt menu already\n * holds newer availability. The result reports the patch's menu version.\n */\nexport function applyAvailability(\n snapshot: MenuSnapshot,\n patch: AvailabilityPatch | null | undefined,\n): Menu {\n if (!patch || Date.parse(patch.updatedAt) < Date.parse(snapshot.builtAt)) {\n return snapshot.menu;\n }\n const items = new Set(patch.soldOutItems);\n const variants = new Set(patch.soldOutVariants);\n const options = new Set(patch.soldOutOptions);\n return {\n ...snapshot.menu,\n version: patch.menuVersion,\n items: Object.fromEntries(\n Object.entries(snapshot.menu.items).map(([id, item]) => {\n const touched = item.variants.some((variant) =>\n variants.has(variant.id),\n );\n const patched = item.variants.map((variant) =>\n variants.has(variant.id)\n ? { ...variant, isAvailable: false }\n : variant,\n ) as typeof item.variants;\n return [\n id,\n {\n ...item,\n // Selling out every variant sells out the item.\n isAvailable:\n item.isAvailable &&\n !items.has(id) &&\n (!touched || patched.some((variant) => variant.isAvailable)),\n variants: patched,\n optionGroups: item.optionGroups.map((group) => ({\n ...group,\n choices: group.choices.map((choice) =>\n options.has(choice.id)\n ? { ...choice, isAvailable: false }\n : choice,\n ),\n })),\n },\n ];\n }),\n ),\n };\n}\n\n/** Check that a value read from the edge is a MenuSnapshot this SDK can use. */\nexport function isMenuSnapshot(value: unknown): value is MenuSnapshot {\n if (!value || typeof value !== \"object\") {\n return false;\n }\n const snapshot = value as Partial<MenuSnapshot>;\n const menu = snapshot.menu as Partial<Menu> | undefined;\n return (\n snapshot.v === 1 &&\n typeof snapshot.storeId === \"string\" &&\n typeof snapshot.branchId === \"string\" &&\n typeof snapshot.version === \"string\" &&\n typeof snapshot.contentHash === \"string\" &&\n typeof snapshot.builtAt === \"string\" &&\n !!menu &&\n typeof menu === \"object\" &&\n menu.branchId === snapshot.branchId &&\n menu.version === snapshot.version &&\n menu.completeness !== \"partial\" &&\n Array.isArray(menu.categories) &&\n !!menu.items &&\n typeof menu.items === \"object\"\n );\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/dist/server/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as Menu,
|
|
1
|
+
import { a as Menu, S as Store, F as Formatters } from '../models-D__QbqEd.js';
|
|
2
2
|
import { a as ZappieError, b as ZappieConfig, T as Transport, S as StorageLike, c as StoreResource, M as MenuResource, d as ZappieEvents, C as Cache } from '../client-CSOZOwQW.js';
|
|
3
3
|
export { VERSION } from '../index.js';
|
|
4
4
|
import { d as CartModule, b as CartLineInput } from '../types-gQXU2gEA.js';
|
|
@@ -162,6 +162,328 @@ declare const parseStorefrontUrl: (input: StorefrontUrlInput, options?: Storefro
|
|
|
162
162
|
*/
|
|
163
163
|
declare const storefrontRedirect: (input: StorefrontUrlInput, options?: StorefrontUrlOptions) => string | null;
|
|
164
164
|
|
|
165
|
+
/** A PKCE code verifier and its S256 challenge for one sign-in attempt. */
|
|
166
|
+
type PkcePair = {
|
|
167
|
+
/** Keep this on the server and send it with the code exchange. */
|
|
168
|
+
verifier: string;
|
|
169
|
+
/** Send this to the authorize endpoint as `code_challenge`. */
|
|
170
|
+
challenge: string;
|
|
171
|
+
/** Send this as `code_challenge_method`. */
|
|
172
|
+
method: "S256";
|
|
173
|
+
};
|
|
174
|
+
/** One sealing secret, or several while you rotate them. The first one seals. */
|
|
175
|
+
type SealSecret = string | readonly string[];
|
|
176
|
+
/** Options for sealing a value into a cookie-safe string. */
|
|
177
|
+
type SealOptions = {
|
|
178
|
+
/**
|
|
179
|
+
* At least 32 bytes each, from a random generator such as `randomToken()`,
|
|
180
|
+
* never a password or phrase. The key is the secret's SHA-256 with no
|
|
181
|
+
* stretching, so a guessable secret can be found offline from one sealed
|
|
182
|
+
* value. List the new secret first while you rotate.
|
|
183
|
+
*/
|
|
184
|
+
secret: SealSecret;
|
|
185
|
+
/**
|
|
186
|
+
* Binds the sealed value to one use, such as `"tx"`, so it won't open for
|
|
187
|
+
* another. Leave it out to match the hosted sign-in Worker's format.
|
|
188
|
+
*/
|
|
189
|
+
purpose?: string;
|
|
190
|
+
};
|
|
191
|
+
/** Create a random URL-safe token from `bytes` random bytes, for state, nonces and ids. */
|
|
192
|
+
declare function randomToken(bytes?: number): string;
|
|
193
|
+
/** Create a PKCE verifier and its S256 challenge. */
|
|
194
|
+
declare function createPkce(): Promise<PkcePair>;
|
|
195
|
+
/** Encrypt a JSON value with AES-GCM into a cookie-safe string. */
|
|
196
|
+
declare function sealValue(value: unknown, { secret, purpose }: SealOptions): Promise<string>;
|
|
197
|
+
/** Decrypt a sealed value with any listed secret. Returns null when none opens it. */
|
|
198
|
+
declare function openSealedValue<T = unknown>(sealed: string | null | undefined, { secret, purpose }: SealOptions): Promise<T | null>;
|
|
199
|
+
|
|
200
|
+
/** Signing algorithms Sign in with Zappie tokens can use. */
|
|
201
|
+
type TokenAlgorithm = "RS256" | "ES256";
|
|
202
|
+
declare const messages: {
|
|
203
|
+
malformed: string;
|
|
204
|
+
unsupported_algorithm: string;
|
|
205
|
+
unknown_key: string;
|
|
206
|
+
invalid_signature: string;
|
|
207
|
+
wrong_issuer: string;
|
|
208
|
+
wrong_audience: string;
|
|
209
|
+
wrong_nonce: string;
|
|
210
|
+
wrong_subject: string;
|
|
211
|
+
wrong_type: string;
|
|
212
|
+
expired: string;
|
|
213
|
+
not_yet_valid: string;
|
|
214
|
+
keys_unavailable: string;
|
|
215
|
+
};
|
|
216
|
+
/** Why a token was refused. */
|
|
217
|
+
type TokenErrorReason = keyof typeof messages;
|
|
218
|
+
/** A token Zappie's identity issuer didn't sign for you, or one that is no longer valid. */
|
|
219
|
+
declare class ZappieTokenError extends ZappieError {
|
|
220
|
+
readonly reason: TokenErrorReason;
|
|
221
|
+
constructor(reason: TokenErrorReason, cause?: unknown);
|
|
222
|
+
}
|
|
223
|
+
/** The verified claims of a token. Extra claims stay on the object. */
|
|
224
|
+
type TokenClaims = {
|
|
225
|
+
iss: string;
|
|
226
|
+
aud: string | string[];
|
|
227
|
+
exp: number;
|
|
228
|
+
sub?: string;
|
|
229
|
+
iat?: number;
|
|
230
|
+
nbf?: number;
|
|
231
|
+
nonce?: string;
|
|
232
|
+
sid?: string;
|
|
233
|
+
[claim: string]: unknown;
|
|
234
|
+
};
|
|
235
|
+
/** Settings for verifying tokens from Zappie's identity issuer. */
|
|
236
|
+
type TokenVerifierOptions = {
|
|
237
|
+
/** The issuer URL, such as `https://accounts.zappie.ai`. The token's `iss` must equal it. A trailing slash on either is ignored. */
|
|
238
|
+
issuer: string;
|
|
239
|
+
/** The audience the token must name: your site's client id for ID tokens, or the API audience for access tokens. */
|
|
240
|
+
audience: string;
|
|
241
|
+
/** Where to read the signing keys. Defaults to `<issuer>/.well-known/jwks.json`. */
|
|
242
|
+
jwksUrl?: string;
|
|
243
|
+
/** Allowed algorithms. Defaults to RS256 and ES256. */
|
|
244
|
+
algorithms?: readonly TokenAlgorithm[];
|
|
245
|
+
/** Seconds of clock skew allowed on `exp`, `nbf` and `iat`. Defaults to 60. */
|
|
246
|
+
clockToleranceSec?: number;
|
|
247
|
+
/** Seconds to keep the key set before reading it again, 0 or more. Defaults to one hour. */
|
|
248
|
+
cacheTtlSec?: number;
|
|
249
|
+
/** Prefix the `sub` claim must start with. Zappie customers are `cus_`. A token without `sub` is refused. Pass `null` to skip both checks. */
|
|
250
|
+
subjectPrefix?: string | null;
|
|
251
|
+
/** The fetch to read keys with. Defaults to the global fetch. */
|
|
252
|
+
fetch?: typeof fetch;
|
|
253
|
+
/** The current time in milliseconds. For tests. */
|
|
254
|
+
now?: () => number;
|
|
255
|
+
};
|
|
256
|
+
/** Per-call checks on top of the verifier's settings. */
|
|
257
|
+
type VerifyTokenChecks = {
|
|
258
|
+
/** The nonce you sent with the sign-in. When you pass it, the token's `nonce` must equal it. */
|
|
259
|
+
nonce?: string;
|
|
260
|
+
/** Overrides the verifier's audience for this token. */
|
|
261
|
+
audience?: string;
|
|
262
|
+
};
|
|
263
|
+
/** Checks for an ID token from a sign-in. */
|
|
264
|
+
type IdTokenChecks = {
|
|
265
|
+
/** The nonce you sent with this sign-in. The token's `nonce` must equal it. */
|
|
266
|
+
nonce: string;
|
|
267
|
+
/** Overrides the verifier's audience for this token. */
|
|
268
|
+
audience?: string;
|
|
269
|
+
};
|
|
270
|
+
/** The verified claims of an ID token. */
|
|
271
|
+
type IdTokenClaims = TokenClaims & {
|
|
272
|
+
sub: string;
|
|
273
|
+
nonce: string;
|
|
274
|
+
};
|
|
275
|
+
/** Verifies signed tokens against the issuer's published keys. */
|
|
276
|
+
type TokenVerifier = {
|
|
277
|
+
/** Verify a token's signature and claims, and return its claims. */
|
|
278
|
+
verify(token: string, checks?: VerifyTokenChecks): Promise<TokenClaims>;
|
|
279
|
+
/**
|
|
280
|
+
* Verify the ID token from a sign-in. It needs the nonce you sent, a `sub`,
|
|
281
|
+
* and refuses a token whose `typ` header names another kind of token, such
|
|
282
|
+
* as an access token (`at+jwt`).
|
|
283
|
+
*/
|
|
284
|
+
verifyIdToken(token: string, checks: IdTokenChecks): Promise<IdTokenClaims>;
|
|
285
|
+
};
|
|
286
|
+
/**
|
|
287
|
+
* Create a verifier for tokens from Zappie's identity issuer. It caches the
|
|
288
|
+
* issuer's key set, reads it again when a token names an unknown key, and
|
|
289
|
+
* keeps the last good set while the issuer is down. It uses Web Crypto only,
|
|
290
|
+
* so it runs in Workers and Node 20 or later.
|
|
291
|
+
*/
|
|
292
|
+
declare function createTokenVerifier(options: TokenVerifierOptions): TokenVerifier;
|
|
293
|
+
|
|
294
|
+
/** What a signed-in session holds on the server. Tokens never reach the browser. */
|
|
295
|
+
type SessionData = {
|
|
296
|
+
/** The customer's subject, such as `cus_7Kd`. */
|
|
297
|
+
sub: string;
|
|
298
|
+
/** The OAuth client the session belongs to, your site's client id. */
|
|
299
|
+
clientId: string;
|
|
300
|
+
/** What the site may show about the customer. */
|
|
301
|
+
customer: {
|
|
302
|
+
id: string;
|
|
303
|
+
givenName?: string;
|
|
304
|
+
phoneMasked: string;
|
|
305
|
+
};
|
|
306
|
+
accessToken: string;
|
|
307
|
+
/** When the access token expires, in epoch milliseconds. */
|
|
308
|
+
accessExp: number;
|
|
309
|
+
refreshToken: string;
|
|
310
|
+
/** The issuer's session id from the ID token, for back-channel logout. */
|
|
311
|
+
idTokenSid?: string;
|
|
312
|
+
scopes: string[];
|
|
313
|
+
/** Epoch milliseconds. */
|
|
314
|
+
createdAt: number;
|
|
315
|
+
/** Epoch milliseconds. */
|
|
316
|
+
lastSeenAt: number;
|
|
317
|
+
mode: "test" | "live";
|
|
318
|
+
};
|
|
319
|
+
/**
|
|
320
|
+
* Where signed-in sessions live. The session id is what the browser's
|
|
321
|
+
* session cookie holds.
|
|
322
|
+
*/
|
|
323
|
+
interface SessionStore {
|
|
324
|
+
/** Save a new session and return its id. */
|
|
325
|
+
create(data: SessionData): Promise<string>;
|
|
326
|
+
/** Read a session, or null when it doesn't exist or has expired. */
|
|
327
|
+
get(sid: string): Promise<SessionData | null>;
|
|
328
|
+
/**
|
|
329
|
+
* Change a session and return the id to keep in the cookie, or null when
|
|
330
|
+
* the session no longer exists. Stores that keep sessions on the server
|
|
331
|
+
* return the same id. The cookie store returns a new one.
|
|
332
|
+
*/
|
|
333
|
+
update(sid: string, patch: Partial<SessionData>): Promise<string | null>;
|
|
334
|
+
/** End one session. */
|
|
335
|
+
delete(sid: string): Promise<void>;
|
|
336
|
+
/** End every session for a customer and client, for back-channel logout. */
|
|
337
|
+
deleteBySub(sub: string, clientId: string): Promise<void>;
|
|
338
|
+
}
|
|
339
|
+
/** Settings for the encrypted-cookie session store. */
|
|
340
|
+
type CookieSessionStoreOptions = {
|
|
341
|
+
/**
|
|
342
|
+
* At least 32 bytes each, from a random generator such as `randomToken()`,
|
|
343
|
+
* never a password or phrase. The key is the secret's SHA-256 with no
|
|
344
|
+
* stretching, so a guessable secret can be found offline from one sealed
|
|
345
|
+
* value. List the new secret first while you rotate.
|
|
346
|
+
*/
|
|
347
|
+
secret: SealSecret;
|
|
348
|
+
/** How long a session lasts from sign-in, in seconds. Defaults to 30 days. */
|
|
349
|
+
maxAgeSec?: number;
|
|
350
|
+
/** The current time in milliseconds. For tests. */
|
|
351
|
+
now?: () => number;
|
|
352
|
+
};
|
|
353
|
+
/**
|
|
354
|
+
* Create a session store that keeps the whole session, sealed with AES-GCM,
|
|
355
|
+
* in the cookie itself, so it needs no database. It can't end a session from
|
|
356
|
+
* the server, so back-channel logout needs a store that keeps sessions there.
|
|
357
|
+
* Signing out clears the cookie in that browser only. A copy of the cookie
|
|
358
|
+
* still opens the session until it expires, 30 days after sign-in by
|
|
359
|
+
* default, or until you rotate the secret out.
|
|
360
|
+
*/
|
|
361
|
+
declare function cookieSessionStore(options: CookieSessionStoreOptions): SessionStore;
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* A branch catalogue as the edge keeps it: the SDK `Menu` that `menu.get()`
|
|
365
|
+
* returns, with the facts a snapshot writer and reader need around it.
|
|
366
|
+
*/
|
|
367
|
+
type MenuSnapshot = {
|
|
368
|
+
v: 1;
|
|
369
|
+
storeId: string;
|
|
370
|
+
branchId: string;
|
|
371
|
+
/** The API's menu version, the same value `GET /menus/version` returns. */
|
|
372
|
+
version: string;
|
|
373
|
+
/** The first 16 hex digits of the SHA-256 of the menu's canonical JSON. */
|
|
374
|
+
contentHash: string;
|
|
375
|
+
/** When the snapshot was built, as an ISO time. */
|
|
376
|
+
builtAt: string;
|
|
377
|
+
/** The latest change among the source menus, as an ISO time, when the API sends it. */
|
|
378
|
+
sourceUpdatedAt?: string;
|
|
379
|
+
/** A complete branch catalogue. Partial category pages never become snapshots. */
|
|
380
|
+
menu: Menu;
|
|
381
|
+
};
|
|
382
|
+
/** Store details as the edge keeps them: the SDK `Store` that `store.get()` returns. */
|
|
383
|
+
type StoreSnapshot = {
|
|
384
|
+
v: 1;
|
|
385
|
+
storeId: string;
|
|
386
|
+
/** The first 16 hex digits of the SHA-256 of the store's canonical JSON. */
|
|
387
|
+
contentHash: string;
|
|
388
|
+
/** When the snapshot was built, as an ISO time. */
|
|
389
|
+
builtAt: string;
|
|
390
|
+
store: Store;
|
|
391
|
+
};
|
|
392
|
+
/**
|
|
393
|
+
* What sold out since a branch's menu snapshot was built. It is small, so an
|
|
394
|
+
* availability change rewrites about 1 KB instead of the whole menu.
|
|
395
|
+
*/
|
|
396
|
+
type AvailabilityPatch = {
|
|
397
|
+
v: 1;
|
|
398
|
+
/** The first 16 hex digits of the SHA-256 of this patch's canonical JSON. */
|
|
399
|
+
version: string;
|
|
400
|
+
/** The API's menu version after this change. A patched menu reports it. */
|
|
401
|
+
menuVersion: string;
|
|
402
|
+
soldOutItems: string[];
|
|
403
|
+
soldOutVariants: string[];
|
|
404
|
+
/** Choice ids in option groups. */
|
|
405
|
+
soldOutOptions: string[];
|
|
406
|
+
/** When the branch reopens, as an ISO time, if it is closed for now. */
|
|
407
|
+
closedUntil?: string;
|
|
408
|
+
/** When the patch was built, as an ISO time. */
|
|
409
|
+
updatedAt: string;
|
|
410
|
+
};
|
|
411
|
+
/** The version record a writer saves last, so readers never see unwritten data. */
|
|
412
|
+
type SnapshotVersionRecord = {
|
|
413
|
+
/** The default branch's menu version. */
|
|
414
|
+
version: string;
|
|
415
|
+
builtAt: string;
|
|
416
|
+
branches: Record<string, string>;
|
|
417
|
+
};
|
|
418
|
+
/** The edge key-value names for each snapshot. */
|
|
419
|
+
declare const snapshotKeys: {
|
|
420
|
+
/** `store:<storeId>` holds a StoreSnapshot. */
|
|
421
|
+
readonly store: (storeId: string) => string;
|
|
422
|
+
/** `menu:<storeId>:<branchId>`, or `menu:<storeId>` for the default branch. */
|
|
423
|
+
readonly menu: (storeId: string, branchId?: string) => string;
|
|
424
|
+
/** `avail:<storeId>:<branchId>` holds an AvailabilityPatch. */
|
|
425
|
+
readonly availability: (storeId: string, branchId: string) => string;
|
|
426
|
+
/** `ver:<storeId>` holds a SnapshotVersionRecord. */
|
|
427
|
+
readonly version: (storeId: string) => string;
|
|
428
|
+
};
|
|
429
|
+
/**
|
|
430
|
+
* Serialise a JSON value with object keys in sorted order and undefined
|
|
431
|
+
* properties left out, so equal values always give the same text.
|
|
432
|
+
*/
|
|
433
|
+
declare function canonicalJson(value: unknown): string;
|
|
434
|
+
/** Hash a JSON value: the first 16 hex digits of the SHA-256 of its canonical JSON. */
|
|
435
|
+
declare function snapshotHash(value: unknown): Promise<string>;
|
|
436
|
+
/** Input for building a MenuSnapshot from an SDK menu. */
|
|
437
|
+
type MenuSnapshotInput = {
|
|
438
|
+
storeId: string;
|
|
439
|
+
menu: Menu;
|
|
440
|
+
/** Defaults to now. */
|
|
441
|
+
builtAt?: Date | string;
|
|
442
|
+
sourceUpdatedAt?: Date | string;
|
|
443
|
+
};
|
|
444
|
+
/** Build a MenuSnapshot from a complete SDK menu. */
|
|
445
|
+
declare function createMenuSnapshot({ storeId, menu, builtAt, sourceUpdatedAt, }: MenuSnapshotInput): Promise<MenuSnapshot>;
|
|
446
|
+
/** Input for building a MenuSnapshot from the API's menu payload. */
|
|
447
|
+
type MenuSnapshotPayloadInput = {
|
|
448
|
+
storeId: string;
|
|
449
|
+
branchId: string;
|
|
450
|
+
/** The JSON body of the API's branch menu read. */
|
|
451
|
+
payload: unknown;
|
|
452
|
+
/** Defaults to now. */
|
|
453
|
+
builtAt?: Date | string;
|
|
454
|
+
};
|
|
455
|
+
/**
|
|
456
|
+
* Build a MenuSnapshot from the API's branch menu payload. It maps the payload
|
|
457
|
+
* with the same mapper `menu.get()` uses, so the snapshot's menu is exactly
|
|
458
|
+
* what the client SDK returns.
|
|
459
|
+
*/
|
|
460
|
+
declare function menuSnapshotFromPayload({ storeId, branchId, payload, builtAt, }: MenuSnapshotPayloadInput): Promise<MenuSnapshot>;
|
|
461
|
+
/** Build a StoreSnapshot from an SDK store. */
|
|
462
|
+
declare function createStoreSnapshot({ store, builtAt, }: {
|
|
463
|
+
store: Store;
|
|
464
|
+
builtAt?: Date | string;
|
|
465
|
+
}): Promise<StoreSnapshot>;
|
|
466
|
+
/** Input for building an AvailabilityPatch. */
|
|
467
|
+
type AvailabilityPatchInput = {
|
|
468
|
+
menuVersion: string;
|
|
469
|
+
soldOutItems?: readonly string[];
|
|
470
|
+
soldOutVariants?: readonly string[];
|
|
471
|
+
soldOutOptions?: readonly string[];
|
|
472
|
+
closedUntil?: Date | string;
|
|
473
|
+
/** Defaults to now. */
|
|
474
|
+
updatedAt?: Date | string;
|
|
475
|
+
};
|
|
476
|
+
/** Build an AvailabilityPatch with sorted, unique ids and its content hash. */
|
|
477
|
+
declare function createAvailabilityPatch(input: AvailabilityPatchInput): Promise<AvailabilityPatch>;
|
|
478
|
+
/**
|
|
479
|
+
* Return the snapshot's menu with an availability patch applied. A patch
|
|
480
|
+
* built before the snapshot is ignored, because the rebuilt menu already
|
|
481
|
+
* holds newer availability. The result reports the patch's menu version.
|
|
482
|
+
*/
|
|
483
|
+
declare function applyAvailability(snapshot: MenuSnapshot, patch: AvailabilityPatch | null | undefined): Menu;
|
|
484
|
+
/** Check that a value read from the edge is a MenuSnapshot this SDK can use. */
|
|
485
|
+
declare function isMenuSnapshot(value: unknown): value is MenuSnapshot;
|
|
486
|
+
|
|
165
487
|
/**
|
|
166
488
|
* The store, theme and optional build policies you export from zappie.config.ts.
|
|
167
489
|
*/
|
|
@@ -338,4 +660,4 @@ declare function createZappieServer({ secretKey, ...config }: ZappieServerConfig
|
|
|
338
660
|
};
|
|
339
661
|
}>;
|
|
340
662
|
|
|
341
|
-
export { type EdgeSnapshotReader, STOREFRONT_QUERY_PARAMETERS, STOREFRONT_V1_QUERY_PARAMETERS, type SecurityHeadersOptions, type StorefrontInstallation, type StorefrontPageState, type StorefrontQuery, type StorefrontRoute, type StorefrontUrlInput, type StorefrontUrlOptions, type VerifyWebhookOptions, type WebhookEvent, type WebhookEventType, type ZappieServerConfig, ZappieWebhookSignatureError, buildStorefrontUrl, createSecurityNonce, createZappieServer, inlineScriptHashes, parseStorefrontUrl, securityHeaders, storefrontRedirect, verifyWebhook };
|
|
663
|
+
export { type AvailabilityPatch, type AvailabilityPatchInput, type CookieSessionStoreOptions, type EdgeSnapshotReader, type IdTokenChecks, type IdTokenClaims, type MenuSnapshot, type MenuSnapshotInput, type MenuSnapshotPayloadInput, type PkcePair, STOREFRONT_QUERY_PARAMETERS, STOREFRONT_V1_QUERY_PARAMETERS, type SealOptions, type SealSecret, type SecurityHeadersOptions, type SessionData, type SessionStore, type SnapshotVersionRecord, type StoreSnapshot, type StorefrontInstallation, type StorefrontPageState, type StorefrontQuery, type StorefrontRoute, type StorefrontUrlInput, type StorefrontUrlOptions, type TokenAlgorithm, type TokenClaims, type TokenErrorReason, type TokenVerifier, type TokenVerifierOptions, type VerifyTokenChecks, type VerifyWebhookOptions, type WebhookEvent, type WebhookEventType, type ZappieServerConfig, ZappieTokenError, ZappieWebhookSignatureError, applyAvailability, buildStorefrontUrl, canonicalJson, cookieSessionStore, createAvailabilityPatch, createMenuSnapshot, createPkce, createSecurityNonce, createStoreSnapshot, createTokenVerifier, createZappieServer, inlineScriptHashes, isMenuSnapshot, menuSnapshotFromPayload, openSealedValue, parseStorefrontUrl, randomToken, sealValue, securityHeaders, snapshotHash, snapshotKeys, storefrontRedirect, verifyWebhook };
|
package/dist/server/index.js
CHANGED
|
@@ -8,6 +8,30 @@ import {
|
|
|
8
8
|
STOREFRONT_V1_QUERY_PARAMETERS,
|
|
9
9
|
storefrontRedirect
|
|
10
10
|
} from "../url/index.js";
|
|
11
|
+
import {
|
|
12
|
+
createPkce,
|
|
13
|
+
openSealedValue,
|
|
14
|
+
randomToken,
|
|
15
|
+
sealValue
|
|
16
|
+
} from "./auth/crypto.js";
|
|
17
|
+
import {
|
|
18
|
+
createTokenVerifier,
|
|
19
|
+
ZappieTokenError
|
|
20
|
+
} from "./auth/jwt.js";
|
|
21
|
+
import {
|
|
22
|
+
cookieSessionStore
|
|
23
|
+
} from "./auth/session-store.js";
|
|
24
|
+
import {
|
|
25
|
+
applyAvailability,
|
|
26
|
+
canonicalJson,
|
|
27
|
+
createAvailabilityPatch,
|
|
28
|
+
createMenuSnapshot,
|
|
29
|
+
createStoreSnapshot,
|
|
30
|
+
isMenuSnapshot,
|
|
31
|
+
menuSnapshotFromPayload,
|
|
32
|
+
snapshotHash,
|
|
33
|
+
snapshotKeys
|
|
34
|
+
} from "./edge-snapshot.js";
|
|
11
35
|
import {
|
|
12
36
|
createSecurityNonce,
|
|
13
37
|
inlineScriptHashes,
|
|
@@ -27,13 +51,29 @@ export {
|
|
|
27
51
|
STOREFRONT_QUERY_PARAMETERS,
|
|
28
52
|
STOREFRONT_V1_QUERY_PARAMETERS,
|
|
29
53
|
VERSION,
|
|
54
|
+
ZappieTokenError,
|
|
30
55
|
ZappieWebhookSignatureError,
|
|
56
|
+
applyAvailability,
|
|
31
57
|
buildStorefrontUrl,
|
|
58
|
+
canonicalJson,
|
|
59
|
+
cookieSessionStore,
|
|
60
|
+
createAvailabilityPatch,
|
|
61
|
+
createMenuSnapshot,
|
|
62
|
+
createPkce,
|
|
32
63
|
createSecurityNonce,
|
|
64
|
+
createStoreSnapshot,
|
|
65
|
+
createTokenVerifier,
|
|
33
66
|
createZappieServer,
|
|
34
67
|
inlineScriptHashes,
|
|
68
|
+
isMenuSnapshot,
|
|
69
|
+
menuSnapshotFromPayload,
|
|
70
|
+
openSealedValue,
|
|
35
71
|
parseStorefrontUrl,
|
|
72
|
+
randomToken,
|
|
73
|
+
sealValue,
|
|
36
74
|
securityHeaders,
|
|
75
|
+
snapshotHash,
|
|
76
|
+
snapshotKeys,
|
|
37
77
|
storefrontRedirect,
|
|
38
78
|
verifyWebhook
|
|
39
79
|
};
|
package/dist/server/index.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/server/index.ts"],
|
|
4
|
-
"sourcesContent": ["import { buildClient } from \"../client\";\nimport type { ZappieConfig } from \"../config\";\nimport type { Menu } from \"../models\";\nexport { VERSION } from \"../index\";\nexport {\n buildStorefrontUrl,\n parseStorefrontUrl,\n STOREFRONT_QUERY_PARAMETERS,\n STOREFRONT_V1_QUERY_PARAMETERS,\n storefrontRedirect,\n type StorefrontInstallation,\n type StorefrontPageState,\n type StorefrontQuery,\n type StorefrontRoute,\n type StorefrontUrlInput,\n type StorefrontUrlOptions,\n} from \"../url/index\";\nexport {\n createSecurityNonce,\n inlineScriptHashes,\n securityHeaders,\n type SecurityHeadersOptions,\n} from \"./security\";\nexport {\n verifyWebhook,\n ZappieWebhookSignatureError,\n type VerifyWebhookOptions,\n type WebhookEvent,\n type WebhookEventType,\n} from \"./webhook\";\n/** An edge reader that returns a Menu snapshot or null. */\nexport interface EdgeSnapshotReader {\n getMenu(storeId: string, branchId: string): Promise<Menu | null>;\n}\n/** Settings for your server client. Keep secret keys out of browser bundles. */\nexport type ZappieServerConfig = Omit<ZappieConfig, \"key\"> & {\n secretKey: string;\n};\n/** Create a server client with your secret key and configured transport. */\nexport function createZappieServer({\n secretKey,\n ...config\n}: ZappieServerConfig) {\n return buildClient({ ...config, key: secretKey }, true);\n}\n"],
|
|
5
|
-
"mappings": ";AAAA,SAAS,mBAAmB;AAG5B,SAAS,eAAe;AACxB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAOK;AACP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AACP;AAAA,EACE;AAAA,EACA;AAAA,OAIK;AAUA,SAAS,mBAAmB;AAAA,EACjC;AAAA,EACA,GAAG;AACL,GAAuB;AACrB,SAAO,YAAY,EAAE,GAAG,QAAQ,KAAK,UAAU,GAAG,IAAI;AACxD;",
|
|
4
|
+
"sourcesContent": ["import { buildClient } from \"../client\";\nimport type { ZappieConfig } from \"../config\";\nimport type { Menu } from \"../models\";\nexport { VERSION } from \"../index\";\nexport {\n buildStorefrontUrl,\n parseStorefrontUrl,\n STOREFRONT_QUERY_PARAMETERS,\n STOREFRONT_V1_QUERY_PARAMETERS,\n storefrontRedirect,\n type StorefrontInstallation,\n type StorefrontPageState,\n type StorefrontQuery,\n type StorefrontRoute,\n type StorefrontUrlInput,\n type StorefrontUrlOptions,\n} from \"../url/index\";\nexport {\n createPkce,\n openSealedValue,\n randomToken,\n sealValue,\n type PkcePair,\n type SealOptions,\n type SealSecret,\n} from \"./auth/crypto\";\nexport {\n createTokenVerifier,\n ZappieTokenError,\n type IdTokenChecks,\n type IdTokenClaims,\n type TokenAlgorithm,\n type TokenClaims,\n type TokenErrorReason,\n type TokenVerifier,\n type TokenVerifierOptions,\n type VerifyTokenChecks,\n} from \"./auth/jwt\";\nexport {\n cookieSessionStore,\n type CookieSessionStoreOptions,\n type SessionData,\n type SessionStore,\n} from \"./auth/session-store\";\nexport {\n applyAvailability,\n canonicalJson,\n createAvailabilityPatch,\n createMenuSnapshot,\n createStoreSnapshot,\n isMenuSnapshot,\n menuSnapshotFromPayload,\n snapshotHash,\n snapshotKeys,\n type AvailabilityPatch,\n type AvailabilityPatchInput,\n type MenuSnapshot,\n type MenuSnapshotInput,\n type MenuSnapshotPayloadInput,\n type SnapshotVersionRecord,\n type StoreSnapshot,\n} from \"./edge-snapshot\";\nexport {\n createSecurityNonce,\n inlineScriptHashes,\n securityHeaders,\n type SecurityHeadersOptions,\n} from \"./security\";\nexport {\n verifyWebhook,\n ZappieWebhookSignatureError,\n type VerifyWebhookOptions,\n type WebhookEvent,\n type WebhookEventType,\n} from \"./webhook\";\n/** An edge reader that returns a Menu snapshot or null. */\nexport interface EdgeSnapshotReader {\n getMenu(storeId: string, branchId: string): Promise<Menu | null>;\n}\n/** Settings for your server client. Keep secret keys out of browser bundles. */\nexport type ZappieServerConfig = Omit<ZappieConfig, \"key\"> & {\n secretKey: string;\n};\n/** Create a server client with your secret key and configured transport. */\nexport function createZappieServer({\n secretKey,\n ...config\n}: ZappieServerConfig) {\n return buildClient({ ...config, key: secretKey }, true);\n}\n"],
|
|
5
|
+
"mappings": ";AAAA,SAAS,mBAAmB;AAG5B,SAAS,eAAe;AACxB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAOK;AACP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAIK;AACP;AAAA,EACE;AAAA,EACA;AAAA,OASK;AACP;AAAA,EACE;AAAA,OAIK;AACP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAQK;AACP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AACP;AAAA,EACE;AAAA,EACA;AAAA,OAIK;AAUA,SAAS,mBAAmB;AAAA,EACjC;AAAA,EACA,GAAG;AACL,GAAuB;AACrB,SAAO,YAAY,EAAE,GAAG,QAAQ,KAAK,UAAU,GAAG,IAAI;AACxD;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|