dineway 0.1.3
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/LICENSE +9 -0
- package/README.md +89 -0
- package/dist/adapters-BlzWJG82.d.mts +106 -0
- package/dist/apply-CAPvMfoU.mjs +1339 -0
- package/dist/astro/index.d.mts +50 -0
- package/dist/astro/index.mjs +1326 -0
- package/dist/astro/middleware/auth.d.mts +30 -0
- package/dist/astro/middleware/auth.mjs +708 -0
- package/dist/astro/middleware/redirect.d.mts +21 -0
- package/dist/astro/middleware/redirect.mjs +62 -0
- package/dist/astro/middleware/request-context.d.mts +17 -0
- package/dist/astro/middleware/request-context.mjs +1371 -0
- package/dist/astro/middleware/setup.d.mts +19 -0
- package/dist/astro/middleware/setup.mjs +46 -0
- package/dist/astro/middleware.d.mts +12 -0
- package/dist/astro/middleware.mjs +1716 -0
- package/dist/astro/types.d.mts +269 -0
- package/dist/astro/types.mjs +1 -0
- package/dist/base64-F8-DUraK.mjs +58 -0
- package/dist/byline-DeWCMU_i.mjs +234 -0
- package/dist/bylines-DyqBV9EQ.mjs +137 -0
- package/dist/chunk-ClPoSABd.mjs +21 -0
- package/dist/cli/index.d.mts +1 -0
- package/dist/cli/index.mjs +3987 -0
- package/dist/client/external-auth-headers.d.mts +38 -0
- package/dist/client/external-auth-headers.mjs +101 -0
- package/dist/client/index.d.mts +397 -0
- package/dist/client/index.mjs +345 -0
- package/dist/config-Cq8H0SfX.mjs +46 -0
- package/dist/connection-C9pxzuag.mjs +52 -0
- package/dist/content-zSgdNmnt.mjs +836 -0
- package/dist/db/index.d.mts +4 -0
- package/dist/db/index.mjs +62 -0
- package/dist/db/libsql.d.mts +10 -0
- package/dist/db/libsql.mjs +21 -0
- package/dist/db/postgres.d.mts +10 -0
- package/dist/db/postgres.mjs +29 -0
- package/dist/db/sqlite.d.mts +10 -0
- package/dist/db/sqlite.mjs +15 -0
- package/dist/default-WYlzADZL.mjs +80 -0
- package/dist/dialect-helpers-B9uSp2GJ.mjs +89 -0
- package/dist/error-DrxtnGPg.mjs +26 -0
- package/dist/index-C-jx21qs.d.mts +4771 -0
- package/dist/index.d.mts +16 -0
- package/dist/index.mjs +30 -0
- package/dist/load-C6FCD1FU.mjs +27 -0
- package/dist/loader-qKmo0wAY.mjs +446 -0
- package/dist/manifest-schema-CTSEyIJ3.mjs +186 -0
- package/dist/media/index.d.mts +25 -0
- package/dist/media/index.mjs +54 -0
- package/dist/media/local-runtime.d.mts +38 -0
- package/dist/media/local-runtime.mjs +132 -0
- package/dist/media-DMTr80Gv.mjs +199 -0
- package/dist/mode-BlyYtIFO.mjs +22 -0
- package/dist/page/index.d.mts +148 -0
- package/dist/page/index.mjs +419 -0
- package/dist/placeholder-B3knXwNc.mjs +267 -0
- package/dist/placeholder-bOx1xCTY.d.mts +283 -0
- package/dist/plugin-utils.d.mts +57 -0
- package/dist/plugin-utils.mjs +77 -0
- package/dist/plugins/adapt-sandbox-entry.d.mts +21 -0
- package/dist/plugins/adapt-sandbox-entry.mjs +112 -0
- package/dist/query-BiaPl_g2.mjs +459 -0
- package/dist/redirect-JPqLAbxa.mjs +328 -0
- package/dist/registry-DSd1GWB8.mjs +851 -0
- package/dist/request-context.d.mts +49 -0
- package/dist/request-context.mjs +42 -0
- package/dist/runner-B5l1JfOj.d.mts +26 -0
- package/dist/runner-BGUGywgG.mjs +1529 -0
- package/dist/runtime.d.mts +25 -0
- package/dist/runtime.mjs +41 -0
- package/dist/search-BNruJHDL.mjs +11054 -0
- package/dist/seed/index.d.mts +3 -0
- package/dist/seed/index.mjs +15 -0
- package/dist/seo/index.d.mts +69 -0
- package/dist/seo/index.mjs +69 -0
- package/dist/storage/local.d.mts +38 -0
- package/dist/storage/local.mjs +165 -0
- package/dist/storage/s3.d.mts +31 -0
- package/dist/storage/s3.mjs +174 -0
- package/dist/tokens-4vgYuXsZ.mjs +170 -0
- package/dist/transport-C5FYnid7.mjs +417 -0
- package/dist/transport-gIL-e43D.d.mts +41 -0
- package/dist/types-BawVha09.mjs +30 -0
- package/dist/types-BgQeVaPj.d.mts +192 -0
- package/dist/types-CLLdsG3g.d.mts +103 -0
- package/dist/types-D38djUXv.d.mts +1196 -0
- package/dist/types-DShnjzb6.mjs +15 -0
- package/dist/types-DkvMXalq.d.mts +425 -0
- package/dist/types-DuNbGKjF.mjs +74 -0
- package/dist/types-ju-_ORz7.d.mts +182 -0
- package/dist/validate-CXnRKfJK.mjs +327 -0
- package/dist/validate-CqRJb_xU.mjs +96 -0
- package/dist/validate-DVKJJ-M_.d.mts +377 -0
- package/locals.d.ts +47 -0
- package/package.json +313 -0
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { i as encodeBase64url, n as decodeBase64url } from "./base64-F8-DUraK.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/preview/tokens.ts
|
|
4
|
+
/**
|
|
5
|
+
* Preview token generation and verification
|
|
6
|
+
*
|
|
7
|
+
* Tokens are compact, URL-safe, and HMAC-signed.
|
|
8
|
+
* Format: base64url(JSON payload).base64url(HMAC signature)
|
|
9
|
+
*
|
|
10
|
+
* Payload: { cid: contentId, exp: expiryTimestamp, iat: issuedAt }
|
|
11
|
+
*/
|
|
12
|
+
const DURATION_PATTERN = /^(\d+)([smhdw])$/;
|
|
13
|
+
/**
|
|
14
|
+
* Parse duration string to seconds
|
|
15
|
+
* Supports: "1h", "30m", "1d", "2w", or raw seconds
|
|
16
|
+
*/
|
|
17
|
+
function parseDuration(duration) {
|
|
18
|
+
if (typeof duration === "number") return duration;
|
|
19
|
+
const match = duration.match(DURATION_PATTERN);
|
|
20
|
+
if (!match) throw new Error(`Invalid duration format: "${duration}". Use "1h", "30m", "1d", "2w", or seconds.`);
|
|
21
|
+
const value = parseInt(match[1], 10);
|
|
22
|
+
const unit = match[2];
|
|
23
|
+
switch (unit) {
|
|
24
|
+
case "s": return value;
|
|
25
|
+
case "m": return value * 60;
|
|
26
|
+
case "h": return value * 60 * 60;
|
|
27
|
+
case "d": return value * 60 * 60 * 24;
|
|
28
|
+
case "w": return value * 60 * 60 * 24 * 7;
|
|
29
|
+
default: throw new Error(`Unknown duration unit: ${unit}`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Create HMAC-SHA256 signature using Web Crypto API
|
|
34
|
+
*/
|
|
35
|
+
async function createSignature(data, secret) {
|
|
36
|
+
const encoder = new TextEncoder();
|
|
37
|
+
const key = await crypto.subtle.importKey("raw", encoder.encode(secret), {
|
|
38
|
+
name: "HMAC",
|
|
39
|
+
hash: "SHA-256"
|
|
40
|
+
}, false, ["sign"]);
|
|
41
|
+
const signature = await crypto.subtle.sign("HMAC", key, encoder.encode(data));
|
|
42
|
+
return new Uint8Array(signature);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Verify HMAC-SHA256 signature
|
|
46
|
+
*/
|
|
47
|
+
async function verifySignature(data, signature, secret) {
|
|
48
|
+
const encoder = new TextEncoder();
|
|
49
|
+
const key = await crypto.subtle.importKey("raw", encoder.encode(secret), {
|
|
50
|
+
name: "HMAC",
|
|
51
|
+
hash: "SHA-256"
|
|
52
|
+
}, false, ["verify"]);
|
|
53
|
+
const sigBuffer = new ArrayBuffer(signature.byteLength);
|
|
54
|
+
new Uint8Array(sigBuffer).set(signature);
|
|
55
|
+
return crypto.subtle.verify("HMAC", key, sigBuffer, encoder.encode(data));
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Generate a preview token for content
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* ```ts
|
|
62
|
+
* const token = await generatePreviewToken({
|
|
63
|
+
* contentId: "posts:abc123",
|
|
64
|
+
* expiresIn: "1h",
|
|
65
|
+
* secret: process.env.PREVIEW_SECRET!,
|
|
66
|
+
* });
|
|
67
|
+
* ```
|
|
68
|
+
*/
|
|
69
|
+
async function generatePreviewToken(options) {
|
|
70
|
+
const { contentId, expiresIn = "1h", secret } = options;
|
|
71
|
+
if (!secret) throw new Error("Preview secret is required");
|
|
72
|
+
if (!contentId || !contentId.includes(":")) throw new Error("Content ID must be in format \"collection:id\"");
|
|
73
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
74
|
+
const payload = {
|
|
75
|
+
cid: contentId,
|
|
76
|
+
exp: now + parseDuration(expiresIn),
|
|
77
|
+
iat: now
|
|
78
|
+
};
|
|
79
|
+
const payloadJson = JSON.stringify(payload);
|
|
80
|
+
const encodedPayload = encodeBase64url(new TextEncoder().encode(payloadJson));
|
|
81
|
+
return `${encodedPayload}.${encodeBase64url(await createSignature(encodedPayload, secret))}`;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Verify a preview token and return the payload
|
|
85
|
+
*
|
|
86
|
+
* @example
|
|
87
|
+
* ```ts
|
|
88
|
+
* // With URL (extracts _preview query param)
|
|
89
|
+
* const result = await verifyPreviewToken({
|
|
90
|
+
* url: Astro.url,
|
|
91
|
+
* secret: import.meta.env.PREVIEW_SECRET,
|
|
92
|
+
* });
|
|
93
|
+
*
|
|
94
|
+
* // With token directly
|
|
95
|
+
* const result = await verifyPreviewToken({
|
|
96
|
+
* token: someToken,
|
|
97
|
+
* secret: import.meta.env.PREVIEW_SECRET,
|
|
98
|
+
* });
|
|
99
|
+
*
|
|
100
|
+
* if (result.valid) {
|
|
101
|
+
* console.log(result.payload.cid); // "posts:abc123"
|
|
102
|
+
* }
|
|
103
|
+
* ```
|
|
104
|
+
*/
|
|
105
|
+
async function verifyPreviewToken(options) {
|
|
106
|
+
const { secret } = options;
|
|
107
|
+
if (!secret) throw new Error("Preview secret is required");
|
|
108
|
+
const token = "url" in options ? options.url.searchParams.get("_preview") : options.token;
|
|
109
|
+
if (!token) return {
|
|
110
|
+
valid: false,
|
|
111
|
+
error: "none"
|
|
112
|
+
};
|
|
113
|
+
const parts = token.split(".");
|
|
114
|
+
if (parts.length !== 2) return {
|
|
115
|
+
valid: false,
|
|
116
|
+
error: "malformed"
|
|
117
|
+
};
|
|
118
|
+
const [encodedPayload, encodedSignature] = parts;
|
|
119
|
+
let signature;
|
|
120
|
+
try {
|
|
121
|
+
signature = decodeBase64url(encodedSignature);
|
|
122
|
+
} catch {
|
|
123
|
+
return {
|
|
124
|
+
valid: false,
|
|
125
|
+
error: "malformed"
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
if (!await verifySignature(encodedPayload, signature, secret)) return {
|
|
129
|
+
valid: false,
|
|
130
|
+
error: "invalid"
|
|
131
|
+
};
|
|
132
|
+
let payload;
|
|
133
|
+
try {
|
|
134
|
+
const payloadBytes = decodeBase64url(encodedPayload);
|
|
135
|
+
const payloadJson = new TextDecoder().decode(payloadBytes);
|
|
136
|
+
payload = JSON.parse(payloadJson);
|
|
137
|
+
} catch {
|
|
138
|
+
return {
|
|
139
|
+
valid: false,
|
|
140
|
+
error: "malformed"
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
if (typeof payload.cid !== "string" || typeof payload.exp !== "number" || typeof payload.iat !== "number") return {
|
|
144
|
+
valid: false,
|
|
145
|
+
error: "malformed"
|
|
146
|
+
};
|
|
147
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
148
|
+
if (payload.exp < now) return {
|
|
149
|
+
valid: false,
|
|
150
|
+
error: "expired"
|
|
151
|
+
};
|
|
152
|
+
return {
|
|
153
|
+
valid: true,
|
|
154
|
+
payload
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Parse a content ID into collection and id
|
|
159
|
+
*/
|
|
160
|
+
function parseContentId(contentId) {
|
|
161
|
+
const colonIndex = contentId.indexOf(":");
|
|
162
|
+
if (colonIndex === -1) throw new Error("Content ID must be in format \"collection:id\"");
|
|
163
|
+
return {
|
|
164
|
+
collection: contentId.slice(0, colonIndex),
|
|
165
|
+
id: contentId.slice(colonIndex + 1)
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
//#endregion
|
|
170
|
+
export { parseContentId as n, verifyPreviewToken as r, generatePreviewToken as t };
|
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
//#region src/client/portable-text.ts
|
|
2
|
+
/**
|
|
3
|
+
* Convert Portable Text blocks to Markdown.
|
|
4
|
+
* Unknown block types are serialized as opaque fences.
|
|
5
|
+
*/
|
|
6
|
+
function portableTextToMarkdown(blocks) {
|
|
7
|
+
const lines = [];
|
|
8
|
+
let prevWasList = false;
|
|
9
|
+
for (let i = 0; i < blocks.length; i++) {
|
|
10
|
+
const block = blocks[i];
|
|
11
|
+
if (block._type === "block") {
|
|
12
|
+
const isList = !!block.listItem;
|
|
13
|
+
if (i > 0 && (!isList || !prevWasList)) lines.push("");
|
|
14
|
+
lines.push(renderStandardBlock(block));
|
|
15
|
+
prevWasList = isList;
|
|
16
|
+
} else if (block._type === "code") {
|
|
17
|
+
if (i > 0) lines.push("");
|
|
18
|
+
const lang = block.language || "";
|
|
19
|
+
const code = block.code || "";
|
|
20
|
+
lines.push("```" + lang);
|
|
21
|
+
lines.push(code);
|
|
22
|
+
lines.push("```");
|
|
23
|
+
prevWasList = false;
|
|
24
|
+
} else if (block._type === "image") {
|
|
25
|
+
if (i > 0) lines.push("");
|
|
26
|
+
const alt = block.alt || "";
|
|
27
|
+
const url = block.asset?.url || "";
|
|
28
|
+
lines.push(``);
|
|
29
|
+
prevWasList = false;
|
|
30
|
+
} else {
|
|
31
|
+
if (i > 0) lines.push("");
|
|
32
|
+
lines.push(`<!--ec:block ${JSON.stringify(block)} -->`);
|
|
33
|
+
prevWasList = false;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return lines.join("\n") + "\n";
|
|
37
|
+
}
|
|
38
|
+
function renderStandardBlock(block) {
|
|
39
|
+
const text = renderSpans(block.children ?? [], block.markDefs ?? []);
|
|
40
|
+
if (block.listItem) return `${" ".repeat(Math.max(0, (block.level ?? 1) - 1))}${block.listItem === "number" ? "1." : "-"} ${text}`;
|
|
41
|
+
if (block.style && block.style.startsWith("h")) {
|
|
42
|
+
const level = parseInt(block.style.substring(1), 10);
|
|
43
|
+
if (level >= 1 && level <= 6) return `${"#".repeat(level)} ${text}`;
|
|
44
|
+
}
|
|
45
|
+
if (block.style === "blockquote") return `> ${text}`;
|
|
46
|
+
return text;
|
|
47
|
+
}
|
|
48
|
+
function renderSpans(spans, markDefs) {
|
|
49
|
+
let result = "";
|
|
50
|
+
for (const span of spans) {
|
|
51
|
+
if (span._type !== "span") continue;
|
|
52
|
+
let text = span.text ?? "";
|
|
53
|
+
const marks = span.marks ?? [];
|
|
54
|
+
for (const mark of marks) {
|
|
55
|
+
const def = markDefs.find((d) => d._key === mark);
|
|
56
|
+
if (def) {
|
|
57
|
+
if (def._type === "link") text = `[${text}](${def.href ?? ""})`;
|
|
58
|
+
} else switch (mark) {
|
|
59
|
+
case "strong":
|
|
60
|
+
text = `**${text}**`;
|
|
61
|
+
break;
|
|
62
|
+
case "em":
|
|
63
|
+
text = `_${text}_`;
|
|
64
|
+
break;
|
|
65
|
+
case "code":
|
|
66
|
+
text = `\`${text}\``;
|
|
67
|
+
break;
|
|
68
|
+
case "strike-through":
|
|
69
|
+
case "strikethrough":
|
|
70
|
+
text = `~~${text}~~`;
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
result += text;
|
|
75
|
+
}
|
|
76
|
+
return result;
|
|
77
|
+
}
|
|
78
|
+
const OPAQUE_FENCE_PATTERN = /^<!--ec:block (.+) -->$/;
|
|
79
|
+
const HEADING_PATTERN = /^(#{1,6})\s+(.+)$/;
|
|
80
|
+
const UNORDERED_LIST_PATTERN = /^(\s*)[-*+]\s+(.+)$/;
|
|
81
|
+
const ORDERED_LIST_PATTERN = /^(\s*)\d+\.\s+(.+)$/;
|
|
82
|
+
const IMAGE_PATTERN = /^!\[([^\]]*)\]\(([^)]+)\)$/;
|
|
83
|
+
const INLINE_MARKDOWN_PATTERN = /(\*\*(.+?)\*\*)|(_(.+?)_)|(`(.+?)`)|(\[(.+?)\]\((.+?)\))|(~~(.+?)~~)/g;
|
|
84
|
+
/**
|
|
85
|
+
* Convert Markdown to Portable Text blocks.
|
|
86
|
+
* Opaque fences (<!--ec:block ... -->) are deserialized and spliced back in.
|
|
87
|
+
*/
|
|
88
|
+
function markdownToPortableText(markdown) {
|
|
89
|
+
const blocks = [];
|
|
90
|
+
const lines = markdown.split("\n");
|
|
91
|
+
let i = 0;
|
|
92
|
+
while (i < lines.length) {
|
|
93
|
+
const line = lines[i];
|
|
94
|
+
const opaqueMatch = line.match(OPAQUE_FENCE_PATTERN);
|
|
95
|
+
if (opaqueMatch) {
|
|
96
|
+
try {
|
|
97
|
+
blocks.push(JSON.parse(opaqueMatch[1]));
|
|
98
|
+
} catch {
|
|
99
|
+
blocks.push(makeBlock(line));
|
|
100
|
+
}
|
|
101
|
+
i++;
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
if (line.startsWith("```")) {
|
|
105
|
+
const lang = line.slice(3).trim();
|
|
106
|
+
const codeLines = [];
|
|
107
|
+
i++;
|
|
108
|
+
while (i < lines.length && !lines[i].startsWith("```")) {
|
|
109
|
+
codeLines.push(lines[i]);
|
|
110
|
+
i++;
|
|
111
|
+
}
|
|
112
|
+
blocks.push({
|
|
113
|
+
_type: "code",
|
|
114
|
+
_key: generateKey(),
|
|
115
|
+
language: lang || void 0,
|
|
116
|
+
code: codeLines.join("\n")
|
|
117
|
+
});
|
|
118
|
+
i++;
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
if (line.trim() === "") {
|
|
122
|
+
i++;
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
const headingMatch = line.match(HEADING_PATTERN);
|
|
126
|
+
if (headingMatch) {
|
|
127
|
+
blocks.push(makeBlock(headingMatch[2], `h${headingMatch[1].length}`));
|
|
128
|
+
i++;
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
if (line.startsWith("> ")) {
|
|
132
|
+
blocks.push(makeBlock(line.slice(2), "blockquote"));
|
|
133
|
+
i++;
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
const ulMatch = line.match(UNORDERED_LIST_PATTERN);
|
|
137
|
+
if (ulMatch) {
|
|
138
|
+
const level = Math.floor(ulMatch[1].length / 2) + 1;
|
|
139
|
+
blocks.push(makeListBlock(ulMatch[2], "bullet", level));
|
|
140
|
+
i++;
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
const olMatch = line.match(ORDERED_LIST_PATTERN);
|
|
144
|
+
if (olMatch) {
|
|
145
|
+
const level = Math.floor(olMatch[1].length / 2) + 1;
|
|
146
|
+
blocks.push(makeListBlock(olMatch[2], "number", level));
|
|
147
|
+
i++;
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
const imgMatch = line.match(IMAGE_PATTERN);
|
|
151
|
+
if (imgMatch) {
|
|
152
|
+
blocks.push({
|
|
153
|
+
_type: "image",
|
|
154
|
+
_key: generateKey(),
|
|
155
|
+
alt: imgMatch[1],
|
|
156
|
+
asset: { url: imgMatch[2] }
|
|
157
|
+
});
|
|
158
|
+
i++;
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
blocks.push(makeBlock(line));
|
|
162
|
+
i++;
|
|
163
|
+
}
|
|
164
|
+
return blocks;
|
|
165
|
+
}
|
|
166
|
+
function makeBlock(text, style = "normal") {
|
|
167
|
+
const { spans, markDefs } = parseInline(text);
|
|
168
|
+
return {
|
|
169
|
+
_type: "block",
|
|
170
|
+
_key: generateKey(),
|
|
171
|
+
style,
|
|
172
|
+
markDefs,
|
|
173
|
+
children: spans
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
function makeListBlock(text, listItem, level) {
|
|
177
|
+
const { spans, markDefs } = parseInline(text);
|
|
178
|
+
return {
|
|
179
|
+
_type: "block",
|
|
180
|
+
_key: generateKey(),
|
|
181
|
+
style: "normal",
|
|
182
|
+
listItem,
|
|
183
|
+
level,
|
|
184
|
+
markDefs,
|
|
185
|
+
children: spans
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Parse inline markdown (bold, italic, code, links, strikethrough) into PT spans + markDefs.
|
|
190
|
+
*/
|
|
191
|
+
function parseInline(text) {
|
|
192
|
+
const spans = [];
|
|
193
|
+
const markDefs = [];
|
|
194
|
+
const regex = INLINE_MARKDOWN_PATTERN;
|
|
195
|
+
let lastIndex = 0;
|
|
196
|
+
let match;
|
|
197
|
+
while ((match = regex.exec(text)) !== null) {
|
|
198
|
+
if (match.index > lastIndex) spans.push({
|
|
199
|
+
_type: "span",
|
|
200
|
+
_key: generateKey(),
|
|
201
|
+
text: text.slice(lastIndex, match.index),
|
|
202
|
+
marks: []
|
|
203
|
+
});
|
|
204
|
+
if (match[2] != null) spans.push({
|
|
205
|
+
_type: "span",
|
|
206
|
+
_key: generateKey(),
|
|
207
|
+
text: match[2],
|
|
208
|
+
marks: ["strong"]
|
|
209
|
+
});
|
|
210
|
+
else if (match[4] != null) spans.push({
|
|
211
|
+
_type: "span",
|
|
212
|
+
_key: generateKey(),
|
|
213
|
+
text: match[4],
|
|
214
|
+
marks: ["em"]
|
|
215
|
+
});
|
|
216
|
+
else if (match[6] != null) spans.push({
|
|
217
|
+
_type: "span",
|
|
218
|
+
_key: generateKey(),
|
|
219
|
+
text: match[6],
|
|
220
|
+
marks: ["code"]
|
|
221
|
+
});
|
|
222
|
+
else if (match[8] != null && match[9] != null) {
|
|
223
|
+
const key = generateKey();
|
|
224
|
+
markDefs.push({
|
|
225
|
+
_key: key,
|
|
226
|
+
_type: "link",
|
|
227
|
+
href: match[9]
|
|
228
|
+
});
|
|
229
|
+
spans.push({
|
|
230
|
+
_type: "span",
|
|
231
|
+
_key: generateKey(),
|
|
232
|
+
text: match[8],
|
|
233
|
+
marks: [key]
|
|
234
|
+
});
|
|
235
|
+
} else if (match[11] != null) spans.push({
|
|
236
|
+
_type: "span",
|
|
237
|
+
_key: generateKey(),
|
|
238
|
+
text: match[11],
|
|
239
|
+
marks: ["strike-through"]
|
|
240
|
+
});
|
|
241
|
+
lastIndex = match.index + match[0].length;
|
|
242
|
+
}
|
|
243
|
+
if (lastIndex < text.length) spans.push({
|
|
244
|
+
_type: "span",
|
|
245
|
+
_key: generateKey(),
|
|
246
|
+
text: text.slice(lastIndex),
|
|
247
|
+
marks: []
|
|
248
|
+
});
|
|
249
|
+
if (spans.length === 0) spans.push({
|
|
250
|
+
_type: "span",
|
|
251
|
+
_key: generateKey(),
|
|
252
|
+
text,
|
|
253
|
+
marks: []
|
|
254
|
+
});
|
|
255
|
+
return {
|
|
256
|
+
spans,
|
|
257
|
+
markDefs
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
let keyCounter = 0;
|
|
261
|
+
function generateKey() {
|
|
262
|
+
return `k${(keyCounter++).toString(36)}`;
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Convert content data for reading: PT fields -> markdown strings.
|
|
266
|
+
* Only converts fields with type "portableText" that contain arrays.
|
|
267
|
+
*/
|
|
268
|
+
function convertDataForRead(data, fields, raw = false) {
|
|
269
|
+
if (raw) return data;
|
|
270
|
+
const result = { ...data };
|
|
271
|
+
for (const field of fields) if (field.type === "portableText" && Array.isArray(result[field.slug])) result[field.slug] = portableTextToMarkdown(result[field.slug]);
|
|
272
|
+
return result;
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Convert content data for writing: markdown strings -> PT arrays.
|
|
276
|
+
* Only converts fields with type "portableText" that contain strings.
|
|
277
|
+
*/
|
|
278
|
+
function convertDataForWrite(data, fields) {
|
|
279
|
+
const result = { ...data };
|
|
280
|
+
for (const field of fields) if (field.type === "portableText" && typeof result[field.slug] === "string") result[field.slug] = markdownToPortableText(result[field.slug]);
|
|
281
|
+
return result;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
//#endregion
|
|
285
|
+
//#region src/client/transport.ts
|
|
286
|
+
/**
|
|
287
|
+
* Transport layer for the Dineway client.
|
|
288
|
+
*
|
|
289
|
+
* Implements a composable interceptor pipeline that modifies requests
|
|
290
|
+
* and responses. The client calls `transport.fetch(request)` — everything
|
|
291
|
+
* else (auth, CSRF, retry) is handled by interceptors.
|
|
292
|
+
*/
|
|
293
|
+
const COOKIE_NAME_VALUE_PATTERN = /^([^;]+)/;
|
|
294
|
+
function baseFetch(request) {
|
|
295
|
+
return globalThis.fetch(request);
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Creates a fetch function that runs requests through an interceptor pipeline.
|
|
299
|
+
*/
|
|
300
|
+
function createTransport(options = {}) {
|
|
301
|
+
const interceptors = options.interceptors ?? [];
|
|
302
|
+
let chain = baseFetch;
|
|
303
|
+
for (let i = interceptors.length - 1; i >= 0; i--) {
|
|
304
|
+
const interceptor = interceptors[i];
|
|
305
|
+
const next = chain;
|
|
306
|
+
chain = (req) => interceptor(req, next);
|
|
307
|
+
}
|
|
308
|
+
return { fetch: chain };
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Adds X-Dineway-Request: 1 and Origin headers to mutation requests
|
|
312
|
+
* (POST, PUT, DELETE). The custom header satisfies Dineway's CSRF check;
|
|
313
|
+
* the Origin header satisfies Astro's built-in origin verification which
|
|
314
|
+
* rejects server-side POST requests that lack a matching Origin.
|
|
315
|
+
*/
|
|
316
|
+
function csrfInterceptor() {
|
|
317
|
+
const MUTATION_METHODS = new Set([
|
|
318
|
+
"POST",
|
|
319
|
+
"PUT",
|
|
320
|
+
"DELETE",
|
|
321
|
+
"PATCH"
|
|
322
|
+
]);
|
|
323
|
+
return (request, next) => {
|
|
324
|
+
if (MUTATION_METHODS.has(request.method)) {
|
|
325
|
+
const headers = new Headers(request.headers);
|
|
326
|
+
headers.set("X-Dineway-Request", "1");
|
|
327
|
+
if (!headers.has("Origin")) {
|
|
328
|
+
const url = new URL(request.url);
|
|
329
|
+
headers.set("Origin", url.origin);
|
|
330
|
+
}
|
|
331
|
+
return next(new Request(request, { headers }));
|
|
332
|
+
}
|
|
333
|
+
return next(request);
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* Adds Authorization: Bearer header from a static token.
|
|
338
|
+
*/
|
|
339
|
+
function tokenInterceptor(token) {
|
|
340
|
+
return (request, next) => {
|
|
341
|
+
const headers = new Headers(request.headers);
|
|
342
|
+
headers.set("Authorization", `Bearer ${token}`);
|
|
343
|
+
return next(new Request(request, { headers }));
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* Dev bypass interceptor. Calls the dev-bypass endpoint on first request
|
|
348
|
+
* to establish a session, then forwards the session cookie on subsequent
|
|
349
|
+
* requests.
|
|
350
|
+
*/
|
|
351
|
+
function devBypassInterceptor(baseUrl) {
|
|
352
|
+
let sessionCookie = null;
|
|
353
|
+
let initializing = null;
|
|
354
|
+
async function init() {
|
|
355
|
+
const bypassUrl = new URL("/_dineway/api/auth/dev-bypass", baseUrl);
|
|
356
|
+
const res = await globalThis.fetch(bypassUrl, { redirect: "manual" });
|
|
357
|
+
const setCookie = res.headers.get("set-cookie");
|
|
358
|
+
if (setCookie) {
|
|
359
|
+
const match = setCookie.match(COOKIE_NAME_VALUE_PATTERN);
|
|
360
|
+
if (match) sessionCookie = match[1];
|
|
361
|
+
}
|
|
362
|
+
if (res.body) await res.text().catch(() => {});
|
|
363
|
+
}
|
|
364
|
+
return async (request, next) => {
|
|
365
|
+
if (!sessionCookie) {
|
|
366
|
+
if (!initializing) initializing = init();
|
|
367
|
+
await initializing;
|
|
368
|
+
}
|
|
369
|
+
if (sessionCookie) {
|
|
370
|
+
const headers = new Headers(request.headers);
|
|
371
|
+
const existing = headers.get("cookie");
|
|
372
|
+
headers.set("cookie", existing ? `${existing}; ${sessionCookie}` : sessionCookie);
|
|
373
|
+
return next(new Request(request, { headers }));
|
|
374
|
+
}
|
|
375
|
+
return next(request);
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* Auto-refreshes expired OAuth tokens on 401 responses.
|
|
380
|
+
* Requires a refresh token and the token endpoint URL.
|
|
381
|
+
*/
|
|
382
|
+
function refreshInterceptor(options) {
|
|
383
|
+
let refreshing = null;
|
|
384
|
+
async function refresh() {
|
|
385
|
+
const res = await globalThis.fetch(options.tokenEndpoint, {
|
|
386
|
+
method: "POST",
|
|
387
|
+
headers: { "Content-Type": "application/json" },
|
|
388
|
+
body: JSON.stringify({
|
|
389
|
+
grant_type: "refresh_token",
|
|
390
|
+
refresh_token: options.refreshToken
|
|
391
|
+
})
|
|
392
|
+
});
|
|
393
|
+
if (!res.ok) return null;
|
|
394
|
+
const data = await res.json();
|
|
395
|
+
const expiresAt = data.expires_in ? new Date(Date.now() + data.expires_in * 1e3).toISOString() : new Date(Date.now() + 36e5).toISOString();
|
|
396
|
+
if (options.onTokenRefreshed) options.onTokenRefreshed(data.access_token, data.refresh_token ?? options.refreshToken, expiresAt);
|
|
397
|
+
return data.access_token;
|
|
398
|
+
}
|
|
399
|
+
return async (request, next) => {
|
|
400
|
+
const response = await next(request);
|
|
401
|
+
if (response.status === 401) {
|
|
402
|
+
if (!refreshing) refreshing = refresh().finally(() => {
|
|
403
|
+
refreshing = null;
|
|
404
|
+
});
|
|
405
|
+
const newToken = await refreshing;
|
|
406
|
+
if (newToken) {
|
|
407
|
+
const headers = new Headers(request.headers);
|
|
408
|
+
headers.set("Authorization", `Bearer ${newToken}`);
|
|
409
|
+
return next(new Request(request, { headers }));
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
return response;
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
//#endregion
|
|
417
|
+
export { tokenInterceptor as a, markdownToPortableText as c, refreshInterceptor as i, portableTextToMarkdown as l, csrfInterceptor as n, convertDataForRead as o, devBypassInterceptor as r, convertDataForWrite as s, createTransport as t };
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
//#region src/client/transport.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Transport layer for the Dineway client.
|
|
4
|
+
*
|
|
5
|
+
* Implements a composable interceptor pipeline that modifies requests
|
|
6
|
+
* and responses. The client calls `transport.fetch(request)` — everything
|
|
7
|
+
* else (auth, CSRF, retry) is handled by interceptors.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* An interceptor can modify the request, call next(), inspect
|
|
11
|
+
* the response, and optionally retry.
|
|
12
|
+
*/
|
|
13
|
+
type Interceptor = (request: Request, next: (request: Request) => Promise<Response>) => Promise<Response>;
|
|
14
|
+
interface TransportOptions {
|
|
15
|
+
interceptors?: Interceptor[];
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Creates a fetch function that runs requests through an interceptor pipeline.
|
|
19
|
+
*/
|
|
20
|
+
declare function createTransport(options?: TransportOptions): {
|
|
21
|
+
fetch: (request: Request) => Promise<Response>;
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Adds X-Dineway-Request: 1 and Origin headers to mutation requests
|
|
25
|
+
* (POST, PUT, DELETE). The custom header satisfies Dineway's CSRF check;
|
|
26
|
+
* the Origin header satisfies Astro's built-in origin verification which
|
|
27
|
+
* rejects server-side POST requests that lack a matching Origin.
|
|
28
|
+
*/
|
|
29
|
+
declare function csrfInterceptor(): Interceptor;
|
|
30
|
+
/**
|
|
31
|
+
* Adds Authorization: Bearer header from a static token.
|
|
32
|
+
*/
|
|
33
|
+
declare function tokenInterceptor(token: string): Interceptor;
|
|
34
|
+
/**
|
|
35
|
+
* Dev bypass interceptor. Calls the dev-bypass endpoint on first request
|
|
36
|
+
* to establish a session, then forwards the session cookie on subsequent
|
|
37
|
+
* requests.
|
|
38
|
+
*/
|
|
39
|
+
declare function devBypassInterceptor(baseUrl: string): Interceptor;
|
|
40
|
+
//#endregion
|
|
41
|
+
export { tokenInterceptor as a, devBypassInterceptor as i, createTransport as n, csrfInterceptor as r, Interceptor as t };
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { r as encodeBase64, t as decodeBase64 } from "./base64-F8-DUraK.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/database/repositories/types.ts
|
|
4
|
+
/** Encode a cursor from order value + id */
|
|
5
|
+
function encodeCursor(orderValue, id) {
|
|
6
|
+
return encodeBase64(JSON.stringify({
|
|
7
|
+
orderValue,
|
|
8
|
+
id
|
|
9
|
+
}));
|
|
10
|
+
}
|
|
11
|
+
/** Decode a cursor to order value + id. Returns null if invalid. */
|
|
12
|
+
function decodeCursor(cursor) {
|
|
13
|
+
try {
|
|
14
|
+
const parsed = JSON.parse(decodeBase64(cursor));
|
|
15
|
+
if (typeof parsed.orderValue === "string" && typeof parsed.id === "string") return parsed;
|
|
16
|
+
return null;
|
|
17
|
+
} catch {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
var DinewayValidationError = class extends Error {
|
|
22
|
+
constructor(message, details) {
|
|
23
|
+
super(message);
|
|
24
|
+
this.details = details;
|
|
25
|
+
this.name = "DinewayValidationError";
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
//#endregion
|
|
30
|
+
export { decodeCursor as n, encodeCursor as r, DinewayValidationError as t };
|