@preprio/toolkit 0.5.0-rc.2 → 0.5.0-rc.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.
Files changed (50) hide show
  1. package/README.md +207 -10
  2. package/dist/astro/index.cjs +1174 -1
  3. package/dist/astro/index.d.cts +47 -1
  4. package/dist/astro/index.d.ts +47 -1
  5. package/dist/astro/index.js +1177 -1
  6. package/dist/chunk-42Z2VW7G.js +1161 -0
  7. package/dist/chunk-6WY3MWCX.js +246 -0
  8. package/dist/chunk-MDAV2FLM.js +26 -0
  9. package/dist/{chunk-FLPNN3CV.js → chunk-PP3O7RB4.js} +1370 -623
  10. package/dist/chunk-S3VT4UWM.js +75 -0
  11. package/dist/chunk-SXUDBGLZ.js +184 -0
  12. package/dist/chunk-URXSDJOU.js +356 -0
  13. package/dist/index.cjs +3108 -392
  14. package/dist/index.d.cts +238 -4
  15. package/dist/index.d.ts +238 -4
  16. package/dist/index.js +189 -149
  17. package/dist/live-preview-EADA3ZL4.js +18 -0
  18. package/dist/nextjs/components.cjs +1522 -359
  19. package/dist/nextjs/components.d.cts +1 -1
  20. package/dist/nextjs/components.d.ts +1 -1
  21. package/dist/nextjs/components.js +4 -1
  22. package/dist/nextjs/index.cjs +1065 -6
  23. package/dist/nextjs/index.d.cts +73 -1
  24. package/dist/nextjs/index.d.ts +73 -1
  25. package/dist/nextjs/index.js +1059 -7
  26. package/dist/nextjs/live-action.cjs +285 -0
  27. package/dist/nextjs/live-action.d.cts +12 -0
  28. package/dist/nextjs/live-action.d.ts +12 -0
  29. package/dist/nextjs/live-action.js +68 -0
  30. package/dist/nextjs/live.cjs +1838 -0
  31. package/dist/nextjs/live.d.cts +52 -0
  32. package/dist/nextjs/live.d.ts +52 -0
  33. package/dist/nextjs/live.js +265 -0
  34. package/dist/nuxt/index.cjs +1 -1
  35. package/dist/nuxt/index.js +1 -1
  36. package/dist/nuxt/live-preview-PTEC3GPC.js +1462 -0
  37. package/dist/nuxt/live.cjs +1662 -0
  38. package/dist/nuxt/live.d.cts +43 -0
  39. package/dist/nuxt/live.d.ts +43 -0
  40. package/dist/nuxt/live.js +94 -0
  41. package/dist/{pixel-B3oQNbZ3.d.cts → pixel-CX422dCO.d.cts} +10 -1
  42. package/dist/{pixel-B3oQNbZ3.d.ts → pixel-CX422dCO.d.ts} +10 -1
  43. package/dist/react/components.cjs +1522 -359
  44. package/dist/react/components.d.cts +9 -0
  45. package/dist/react/components.d.ts +9 -0
  46. package/dist/react/components.js +1522 -359
  47. package/dist/sveltekit/index.cjs +1 -1
  48. package/dist/sveltekit/index.js +1 -1
  49. package/package.json +12 -1
  50. package/src/astro/PreprToolbar.astro +7 -1
@@ -0,0 +1,285 @@
1
+ "use strict";
2
+ "use server";
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __export = (target, all) => {
10
+ for (var name in all)
11
+ __defProp(target, name, { get: all[name], enumerable: true });
12
+ };
13
+ var __copyProps = (to, from, except, desc) => {
14
+ if (from && typeof from === "object" || typeof from === "function") {
15
+ for (let key of __getOwnPropNames(from))
16
+ if (!__hasOwnProp.call(to, key) && key !== except)
17
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
18
+ }
19
+ return to;
20
+ };
21
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
22
+ // If the importer is in node compatibility mode or this is not an ESM
23
+ // file that has been converted to a CommonJS file using a Babel-
24
+ // compatible transform (i.e. "__esModule" has not been set), then set
25
+ // "default" to the CommonJS "module.exports" for node compatibility.
26
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
27
+ mod
28
+ ));
29
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
30
+
31
+ // src/nextjs/live-action.ts
32
+ var live_action_exports = {};
33
+ __export(live_action_exports, {
34
+ syncPreprLiveLog: () => syncPreprLiveLog
35
+ });
36
+ module.exports = __toCommonJS(live_action_exports);
37
+ var nextCache = __toESM(require("next/cache"), 1);
38
+ var import_headers = require("next/headers");
39
+
40
+ // src/core/live-preview/normalize-query.ts
41
+ var import_stega = require("@vercel/stega");
42
+
43
+ // src/core/utils.ts
44
+ var DebugLogger = class {
45
+ constructor(options) {
46
+ this.options = {
47
+ prefix: "[Prepr]",
48
+ ...options
49
+ };
50
+ }
51
+ // A local `enabled` wins; otherwise defer to the global logger so scoped
52
+ // loggers created before initDebugLogger() still pick up the setting.
53
+ isEnabled() {
54
+ if (this.options.enabled !== void 0) {
55
+ return this.options.enabled;
56
+ }
57
+ return globalDebugLogger?.options?.enabled ?? false;
58
+ }
59
+ log(message, ...args) {
60
+ if (!this.isEnabled()) return;
61
+ const prefix = this.options.prefix;
62
+ console.log(`${prefix} ${message}`, ...args);
63
+ }
64
+ warn(message, ...args) {
65
+ if (!this.isEnabled()) return;
66
+ const prefix = this.options.prefix;
67
+ console.warn(`${prefix} ${message}`, ...args);
68
+ }
69
+ error(message, ...args) {
70
+ if (!this.isEnabled()) return;
71
+ const prefix = this.options.prefix;
72
+ console.error(`${prefix} ${message}`, ...args);
73
+ }
74
+ };
75
+ var globalDebugLogger = null;
76
+ function createScopedLogger(scopeName) {
77
+ return new DebugLogger({
78
+ prefix: `[Prepr][${scopeName}]`
79
+ });
80
+ }
81
+
82
+ // src/core/live-preview/validate.ts
83
+ function isScalar(v) {
84
+ return typeof v === "string" || typeof v === "number" || typeof v === "boolean";
85
+ }
86
+ var MAX_DEPTH = 3;
87
+ var MAX_ENTRIES = 50;
88
+ var FORBIDDEN_KEYS = /* @__PURE__ */ new Set(["__proto__", "prototype", "constructor"]);
89
+ var SAFE_URL = /^https?:\/\//i;
90
+ var URL_KEY = /^(?:url|href|src|link)$|(?:_url|Url)$/;
91
+ var URL_IGNORED_ANYWHERE = /[\t\n\r]/g;
92
+ var SCRIPT_SCHEME = /^(?:javascript|vbscript|data):/i;
93
+ function isSafeLinkValue(value) {
94
+ const stripped = value.replace(URL_IGNORED_ANYWHERE, "");
95
+ let start = 0;
96
+ while (start < stripped.length && stripped.charCodeAt(start) <= 32) start++;
97
+ return !SCRIPT_SCHEME.test(stripped.slice(start));
98
+ }
99
+ function isUrlKey(key) {
100
+ return URL_KEY.test(key);
101
+ }
102
+ function isSafeKeyValue(key, value) {
103
+ if (typeof value !== "string") return true;
104
+ if (key === "url") return SAFE_URL.test(value);
105
+ return !isUrlKey(key) || isSafeLinkValue(value);
106
+ }
107
+ function isLiveValue(v, depth = 0) {
108
+ if (v === null || isScalar(v)) return true;
109
+ if (depth >= MAX_DEPTH) return false;
110
+ if (Array.isArray(v)) {
111
+ return depth === 0 && v.length <= MAX_ENTRIES && v.every((entry) => isPlainRecord(entry) && isLiveValue(entry, depth + 1));
112
+ }
113
+ if (!isPlainRecord(v)) return false;
114
+ const keys = Object.keys(v);
115
+ if (keys.length > MAX_ENTRIES) return false;
116
+ return keys.every((key) => {
117
+ if (FORBIDDEN_KEYS.has(key)) return false;
118
+ const value = v[key];
119
+ if (!isSafeKeyValue(key, value)) return false;
120
+ return !Array.isArray(value) && isLiveValue(value, depth + 1);
121
+ });
122
+ }
123
+ function isLiveFieldChange(field, value) {
124
+ if (!isLiveValue(value)) return false;
125
+ const name = field.slice(field.lastIndexOf(".") + 1);
126
+ return typeof value !== "string" || !isUrlKey(name) || isSafeLinkValue(value);
127
+ }
128
+ var MAX_LIST_ENTRIES = 200;
129
+ function parseListEntries(value) {
130
+ if (!Array.isArray(value) || value.length > MAX_LIST_ENTRIES) return null;
131
+ const list = [];
132
+ for (const raw of value) {
133
+ if (!isPlainRecord(raw) || typeof raw._id !== "string" || !raw._id) {
134
+ return null;
135
+ }
136
+ const entry = { _id: raw._id };
137
+ if (typeof raw.__typename === "string") entry.__typename = raw.__typename;
138
+ if (typeof raw.body === "string") entry.body = raw.body;
139
+ if (typeof raw.format === "string" || raw.format === null) {
140
+ entry.format = raw.format;
141
+ }
142
+ if (typeof raw.typeName === "string" && raw.typeName.length <= 200) {
143
+ entry.typeName = raw.typeName;
144
+ }
145
+ if (raw.fields !== void 0) {
146
+ if (!isPlainRecord(raw.fields) || !isFieldValue(raw.fields, 0)) {
147
+ return null;
148
+ }
149
+ entry.fields = raw.fields;
150
+ }
151
+ list.push(entry);
152
+ }
153
+ return list;
154
+ }
155
+ function isPlainRecord(v) {
156
+ return v !== null && typeof v === "object" && Object.getPrototypeOf(v) === Object.prototype;
157
+ }
158
+ var MAX_FIELD_DEPTH = 6;
159
+ var MAX_FIELD_KEYS = 100;
160
+ function isFieldValue(v, depth) {
161
+ if (v === null || isScalar(v)) return true;
162
+ if (depth >= MAX_FIELD_DEPTH) return false;
163
+ if (Array.isArray(v)) {
164
+ return v.length <= MAX_ENTRIES && v.every(
165
+ (entry) => isScalar(entry) || isPlainRecord(entry) && isFieldValue(entry, depth + 1)
166
+ );
167
+ }
168
+ if (!isPlainRecord(v)) return false;
169
+ const keys = Object.keys(v);
170
+ if (keys.length > MAX_FIELD_KEYS) return false;
171
+ return keys.every((key) => {
172
+ if (FORBIDDEN_KEYS.has(key)) return false;
173
+ const value = v[key];
174
+ if (!isSafeKeyValue(key, value)) return false;
175
+ return isFieldValue(value, depth + 1);
176
+ });
177
+ }
178
+ var MAX_EDIT_LENGTH = 1e3;
179
+ function parseEditTarget(raw) {
180
+ if (!isPlainRecord(raw)) return void 0;
181
+ const { item, field } = raw;
182
+ if (typeof item !== "string" || !item || item.length > MAX_EDIT_LENGTH) {
183
+ return void 0;
184
+ }
185
+ if (typeof field !== "string" || !field || field.length > MAX_EDIT_LENGTH) {
186
+ return void 0;
187
+ }
188
+ return { item, field };
189
+ }
190
+
191
+ // src/core/live-preview/normalize-query.ts
192
+ var debug = createScopedLogger("live-preview:normalize");
193
+ var MAX_LOG_ENTRIES = 500;
194
+ var MAX_LOG_SOURCE_LENGTH = 1e6;
195
+ function parsePatchLog(raw) {
196
+ let value = raw;
197
+ if (typeof value === "string") {
198
+ if (!value || value.length > MAX_LOG_SOURCE_LENGTH) return [];
199
+ try {
200
+ value = JSON.parse(value);
201
+ } catch {
202
+ return [];
203
+ }
204
+ }
205
+ if (!Array.isArray(value)) return [];
206
+ const log = [];
207
+ for (const entry of value.slice(-MAX_LOG_ENTRIES)) {
208
+ if (!isPlainRecord(entry)) continue;
209
+ const { type, itemId, field } = entry;
210
+ if (typeof itemId !== "string" || !itemId) continue;
211
+ if (typeof field !== "string" || !field) continue;
212
+ if (type === "field" && isLiveFieldChange(field, entry.value)) {
213
+ const edit = parseEditTarget(entry.edit);
214
+ log.push(
215
+ edit ? { type, itemId, field, value: entry.value, edit } : { type, itemId, field, value: entry.value }
216
+ );
217
+ } else if (type === "list") {
218
+ const entries = parseListEntries(entry.entries);
219
+ if (entries) log.push({ type, itemId, field, entries });
220
+ }
221
+ }
222
+ return log;
223
+ }
224
+
225
+ // src/core/server.ts
226
+ var PREVIEW_PACKAGE_PART = /(?:^|;)\s*preview=true\s*(?:;|$)/;
227
+ function isPreviewFromHeaders(headers2) {
228
+ return PREVIEW_PACKAGE_PART.test(headers2.get("Prepr-Package") ?? "");
229
+ }
230
+
231
+ // src/nextjs/live-log.ts
232
+ var LIVE_LOG_COOKIE = "__prepr_live";
233
+ var TTL_MS = 15e3;
234
+ var MAX_SLOTS = 50;
235
+ var KEY = /* @__PURE__ */ Symbol.for("@preprio/toolkit/live-log");
236
+ function slots() {
237
+ const holder = globalThis;
238
+ let map = holder[KEY];
239
+ if (!map) {
240
+ map = /* @__PURE__ */ new Map();
241
+ holder[KEY] = map;
242
+ }
243
+ return map;
244
+ }
245
+ function prune(map, now) {
246
+ for (const [token, slot] of map) {
247
+ if (slot.expires <= now) map.delete(token);
248
+ }
249
+ while (map.size >= MAX_SLOTS) {
250
+ map.delete(map.keys().next().value);
251
+ }
252
+ }
253
+ function putLiveLog(log) {
254
+ const map = slots();
255
+ const now = Date.now();
256
+ prune(map, now);
257
+ const bytes = new Uint8Array(16);
258
+ globalThis.crypto.getRandomValues(bytes);
259
+ const token = Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join(
260
+ ""
261
+ );
262
+ map.set(token, { log, expires: now + TTL_MS });
263
+ return token;
264
+ }
265
+
266
+ // src/nextjs/live-action.ts
267
+ async function syncPreprLiveLog(log) {
268
+ const refresh2 = nextCache.refresh;
269
+ if (typeof refresh2 !== "function") return;
270
+ if (!isPreviewFromHeaders(await (0, import_headers.headers)())) return;
271
+ const parsed = parsePatchLog(log);
272
+ if (!parsed.length) return;
273
+ (await (0, import_headers.cookies)()).set(LIVE_LOG_COOKIE, putLiveLog(parsed), {
274
+ // Expired on arrival: only the re-render of this request reads it.
275
+ maxAge: 0,
276
+ httpOnly: true,
277
+ sameSite: "strict",
278
+ path: "/"
279
+ });
280
+ refresh2();
281
+ }
282
+ // Annotate the CommonJS export names for ESM import in node:
283
+ 0 && (module.exports = {
284
+ syncPreprLiveLog
285
+ });
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Server action that re-renders the current route with the editor's unsaved
3
+ * changes, for `normalizeQuery` to apply. Called by `PreprRefresh`.
4
+ *
5
+ * Anyone can call a server action on a production deployment, so it does
6
+ * nothing unless the Prepr middleware ran with `preview: true`, and the log
7
+ * is validated as hostile input. Requires Next.js 16 (`refresh`); on older
8
+ * versions it is a no-op and the page updates on save only.
9
+ */
10
+ declare function syncPreprLiveLog(log: unknown): Promise<void>;
11
+
12
+ export { syncPreprLiveLog };
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Server action that re-renders the current route with the editor's unsaved
3
+ * changes, for `normalizeQuery` to apply. Called by `PreprRefresh`.
4
+ *
5
+ * Anyone can call a server action on a production deployment, so it does
6
+ * nothing unless the Prepr middleware ran with `preview: true`, and the log
7
+ * is validated as hostile input. Requires Next.js 16 (`refresh`); on older
8
+ * versions it is a no-op and the page updates on save only.
9
+ */
10
+ declare function syncPreprLiveLog(log: unknown): Promise<void>;
11
+
12
+ export { syncPreprLiveLog };
@@ -0,0 +1,68 @@
1
+ "use server";
2
+ import {
3
+ isPreviewFromHeaders
4
+ } from "../chunk-SXUDBGLZ.js";
5
+ import "../chunk-MDAV2FLM.js";
6
+ import {
7
+ parsePatchLog
8
+ } from "../chunk-42Z2VW7G.js";
9
+ import "../chunk-S3VT4UWM.js";
10
+
11
+ // src/nextjs/live-action.ts
12
+ import * as nextCache from "next/cache";
13
+ import { cookies, headers } from "next/headers";
14
+
15
+ // src/nextjs/live-log.ts
16
+ var LIVE_LOG_COOKIE = "__prepr_live";
17
+ var TTL_MS = 15e3;
18
+ var MAX_SLOTS = 50;
19
+ var KEY = /* @__PURE__ */ Symbol.for("@preprio/toolkit/live-log");
20
+ function slots() {
21
+ const holder = globalThis;
22
+ let map = holder[KEY];
23
+ if (!map) {
24
+ map = /* @__PURE__ */ new Map();
25
+ holder[KEY] = map;
26
+ }
27
+ return map;
28
+ }
29
+ function prune(map, now) {
30
+ for (const [token, slot] of map) {
31
+ if (slot.expires <= now) map.delete(token);
32
+ }
33
+ while (map.size >= MAX_SLOTS) {
34
+ map.delete(map.keys().next().value);
35
+ }
36
+ }
37
+ function putLiveLog(log) {
38
+ const map = slots();
39
+ const now = Date.now();
40
+ prune(map, now);
41
+ const bytes = new Uint8Array(16);
42
+ globalThis.crypto.getRandomValues(bytes);
43
+ const token = Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join(
44
+ ""
45
+ );
46
+ map.set(token, { log, expires: now + TTL_MS });
47
+ return token;
48
+ }
49
+
50
+ // src/nextjs/live-action.ts
51
+ async function syncPreprLiveLog(log) {
52
+ const refresh2 = nextCache.refresh;
53
+ if (typeof refresh2 !== "function") return;
54
+ if (!isPreviewFromHeaders(await headers())) return;
55
+ const parsed = parsePatchLog(log);
56
+ if (!parsed.length) return;
57
+ (await cookies()).set(LIVE_LOG_COOKIE, putLiveLog(parsed), {
58
+ // Expired on arrival: only the re-render of this request reads it.
59
+ maxAge: 0,
60
+ httpOnly: true,
61
+ sameSite: "strict",
62
+ path: "/"
63
+ });
64
+ refresh2();
65
+ }
66
+ export {
67
+ syncPreprLiveLog
68
+ };