@preprio/toolkit 0.5.0-rc.4 → 0.5.0-rc.6

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 (45) hide show
  1. package/README.md +337 -165
  2. package/dist/astro/index.cjs +126 -30
  3. package/dist/astro/index.js +130 -30
  4. package/dist/{chunk-TG6N3C22.js → chunk-IPMB32AZ.js} +1 -1
  5. package/dist/{chunk-74KY5EAM.js → chunk-KD3GK6TO.js} +2 -0
  6. package/dist/{chunk-AHBN6FQP.js → chunk-KZIRM54F.js} +1 -1
  7. package/dist/{chunk-PDNCNTDB.js → chunk-L3SDL6MP.js} +11 -3
  8. package/dist/{chunk-46ISDRHT.js → chunk-WHZNCYUS.js} +1 -1
  9. package/dist/codegen/index.cjs +79 -0
  10. package/dist/codegen/index.d.cts +46 -0
  11. package/dist/codegen/index.d.ts +46 -0
  12. package/dist/codegen/index.js +62 -0
  13. package/dist/index.cjs +133 -28
  14. package/dist/index.d.cts +14 -2
  15. package/dist/index.d.ts +14 -2
  16. package/dist/index.js +108 -6
  17. package/dist/{live-preview-H4643QYF.js → live-preview-DLXGXRE5.js} +2 -2
  18. package/dist/nextjs/components.cjs +11 -3
  19. package/dist/nextjs/components.js +2 -2
  20. package/dist/nextjs/index.cjs +124 -28
  21. package/dist/nextjs/index.d.cts +2 -0
  22. package/dist/nextjs/index.d.ts +2 -0
  23. package/dist/nextjs/index.js +128 -28
  24. package/dist/nextjs/live-action.js +3 -3
  25. package/dist/nextjs/live.cjs +4 -1
  26. package/dist/nextjs/live.d.cts +3 -1
  27. package/dist/nextjs/live.d.ts +3 -1
  28. package/dist/nextjs/live.js +5 -2
  29. package/dist/nuxt/chunk-BWPGO25E.js +340 -0
  30. package/dist/nuxt/index.cjs +1 -1
  31. package/dist/nuxt/index.js +1 -1
  32. package/dist/nuxt/{live-preview-O2ZTJNW4.js → live-preview-2RCU35W5.js} +6 -337
  33. package/dist/nuxt/live.cjs +1007 -911
  34. package/dist/nuxt/live.js +109 -5
  35. package/dist/react/components.cjs +11 -3
  36. package/dist/react/components.js +11 -3
  37. package/dist/sveltekit/chunk-BWPGO25E.js +340 -0
  38. package/dist/sveltekit/index.cjs +1 -1
  39. package/dist/sveltekit/index.js +1 -1
  40. package/dist/sveltekit/live-preview-2RCU35W5.js +1189 -0
  41. package/dist/sveltekit/live.cjs +1830 -0
  42. package/dist/sveltekit/live.d.cts +59 -0
  43. package/dist/sveltekit/live.d.ts +59 -0
  44. package/dist/sveltekit/live.js +204 -0
  45. package/package.json +16 -1
@@ -0,0 +1,59 @@
1
+ /**
2
+ * What `normalizeQuery` accepts as the query: GraphQL source text, a parsed
3
+ * `DocumentNode` (from `graphql-tag`, Apollo or GraphQL Code Generator), or a
4
+ * string-like object such as codegen's `TypedDocumentString`.
5
+ */
6
+ type QueryInput = string | {
7
+ kind: 'Document';
8
+ } | {
9
+ toString(): string;
10
+ };
11
+
12
+ interface NormalizedQueryOptions {
13
+ /**
14
+ * Pass your preview flag; never `true` on a production deployment.
15
+ * Defaults to false: the data is returned unchanged.
16
+ */
17
+ enabled?: boolean;
18
+ /** Called when the editor saves, e.g. `invalidateAll` from `$app/navigation`. */
19
+ onSave?: () => void;
20
+ /** Exact editor origins, replacing the `*.prepr.io` default. */
21
+ allowedEditorOrigins?: string[];
22
+ /** See the toolbar's `strictMode`. */
23
+ strictMode?: boolean;
24
+ }
25
+ /**
26
+ * Svelte's store contract, written out so this module needs no build-time
27
+ * dependency on `svelte`. Read it with `$store` in a component.
28
+ */
29
+ interface NormalizedQueryStore<T> {
30
+ subscribe(run: (value: T) => void): () => void;
31
+ }
32
+ /**
33
+ * Show the Prepr editor's unsaved changes in a SvelteKit page, applied in the
34
+ * browser while the editor types:
35
+ *
36
+ * ```svelte
37
+ * <script lang="ts">
38
+ * import { invalidateAll } from '$app/navigation';
39
+ * import { normalizedQuery } from '@preprio/toolkit/sveltekit/live';
40
+ *
41
+ * let { data } = $props();
42
+ * const result = $derived(
43
+ * normalizedQuery(GetPageDocument, data.result, {
44
+ * enabled: data.preview,
45
+ * onSave: invalidateAll,
46
+ * }),
47
+ * );
48
+ * </script>
49
+ *
50
+ * <h1>{$result.Page.title}</h1>
51
+ * ```
52
+ *
53
+ * Returns a store that holds the data unchanged on the server and outside
54
+ * the editor iframe. Select `_id` on every item and component that should
55
+ * update.
56
+ */
57
+ declare function normalizedQuery<T>(query: QueryInput, data: T, options?: NormalizedQueryOptions): NormalizedQueryStore<T>;
58
+
59
+ export { type NormalizedQueryOptions, type NormalizedQueryStore, normalizedQuery };
@@ -0,0 +1,59 @@
1
+ /**
2
+ * What `normalizeQuery` accepts as the query: GraphQL source text, a parsed
3
+ * `DocumentNode` (from `graphql-tag`, Apollo or GraphQL Code Generator), or a
4
+ * string-like object such as codegen's `TypedDocumentString`.
5
+ */
6
+ type QueryInput = string | {
7
+ kind: 'Document';
8
+ } | {
9
+ toString(): string;
10
+ };
11
+
12
+ interface NormalizedQueryOptions {
13
+ /**
14
+ * Pass your preview flag; never `true` on a production deployment.
15
+ * Defaults to false: the data is returned unchanged.
16
+ */
17
+ enabled?: boolean;
18
+ /** Called when the editor saves, e.g. `invalidateAll` from `$app/navigation`. */
19
+ onSave?: () => void;
20
+ /** Exact editor origins, replacing the `*.prepr.io` default. */
21
+ allowedEditorOrigins?: string[];
22
+ /** See the toolbar's `strictMode`. */
23
+ strictMode?: boolean;
24
+ }
25
+ /**
26
+ * Svelte's store contract, written out so this module needs no build-time
27
+ * dependency on `svelte`. Read it with `$store` in a component.
28
+ */
29
+ interface NormalizedQueryStore<T> {
30
+ subscribe(run: (value: T) => void): () => void;
31
+ }
32
+ /**
33
+ * Show the Prepr editor's unsaved changes in a SvelteKit page, applied in the
34
+ * browser while the editor types:
35
+ *
36
+ * ```svelte
37
+ * <script lang="ts">
38
+ * import { invalidateAll } from '$app/navigation';
39
+ * import { normalizedQuery } from '@preprio/toolkit/sveltekit/live';
40
+ *
41
+ * let { data } = $props();
42
+ * const result = $derived(
43
+ * normalizedQuery(GetPageDocument, data.result, {
44
+ * enabled: data.preview,
45
+ * onSave: invalidateAll,
46
+ * }),
47
+ * );
48
+ * </script>
49
+ *
50
+ * <h1>{$result.Page.title}</h1>
51
+ * ```
52
+ *
53
+ * Returns a store that holds the data unchanged on the server and outside
54
+ * the editor iframe. Select `_id` on every item and component that should
55
+ * update.
56
+ */
57
+ declare function normalizedQuery<T>(query: QueryInput, data: T, options?: NormalizedQueryOptions): NormalizedQueryStore<T>;
58
+
59
+ export { type NormalizedQueryOptions, type NormalizedQueryStore, normalizedQuery };
@@ -0,0 +1,204 @@
1
+ import {
2
+ collectFields,
3
+ readQueryDocument
4
+ } from "./chunk-BWPGO25E.js";
5
+
6
+ // src/core/live-preview/edit-links.ts
7
+ import {
8
+ vercelStegaCombine,
9
+ vercelStegaDecode,
10
+ vercelStegaSplit
11
+ } from "@vercel/stega";
12
+ var ENTRY_PARAM = "entry";
13
+ var TOP_LEVEL_ENTRY = /^[A-Za-z0-9_]+\.\d+$/;
14
+ function entryIdOf(element) {
15
+ if (!isRecord(element)) return void 0;
16
+ if (typeof element._id === "string") return element._id;
17
+ if (element.__typename !== "Assets" || !Array.isArray(element.items)) {
18
+ return void 0;
19
+ }
20
+ const first = element.items[0];
21
+ return isRecord(first) && typeof first._id === "string" ? first._id : void 0;
22
+ }
23
+ function isRecord(value) {
24
+ return typeof value === "object" && value !== null && !Array.isArray(value);
25
+ }
26
+ function annotate(value, ctx) {
27
+ if (!ctx.entryId) return value;
28
+ const { cleaned, encoded } = vercelStegaSplit(value);
29
+ if (!encoded) return value;
30
+ let payload;
31
+ try {
32
+ payload = vercelStegaDecode(encoded);
33
+ } catch {
34
+ return value;
35
+ }
36
+ if (typeof payload?.href !== "string") return value;
37
+ let url;
38
+ try {
39
+ url = new URL(payload.href);
40
+ } catch {
41
+ return value;
42
+ }
43
+ if (url.searchParams.has(ENTRY_PARAM)) return value;
44
+ url.searchParams.set(ENTRY_PARAM, ctx.entryId);
45
+ const itemId = decodeURIComponent(url.pathname.split("/").pop() ?? "");
46
+ if (!url.searchParams.has("field") && ctx.entryField && TOP_LEVEL_ENTRY.test(ctx.entryField) && itemId === ctx.entryOwnerId) {
47
+ url.searchParams.set("field", ctx.entryField);
48
+ }
49
+ return vercelStegaCombine(
50
+ cleaned,
51
+ { ...payload, href: url.toString() },
52
+ false
53
+ );
54
+ }
55
+ function walk(value, selections, ctx) {
56
+ if (typeof value === "string") return annotate(value, ctx);
57
+ if (Array.isArray(value)) {
58
+ let changed2 = false;
59
+ const next2 = value.map((element, index) => {
60
+ const path = [...ctx.path, String(index)];
61
+ const entryId = ctx.inAssets ? void 0 : entryIdOf(element);
62
+ const entry = entryId ? {
63
+ entryId,
64
+ entryField: ctx.ownerId ? path.join(".") : void 0,
65
+ entryOwnerId: ctx.ownerId,
66
+ inAssets: isRecord(element) && element._id === void 0
67
+ } : {};
68
+ const out = walk(element, selections, { ...ctx, ...entry, path });
69
+ if (out !== element) changed2 = true;
70
+ return out;
71
+ });
72
+ return changed2 ? next2 : value;
73
+ }
74
+ if (!isRecord(value)) return value;
75
+ const id = typeof value._id === "string" ? value._id : void 0;
76
+ const typename = typeof value.__typename === "string" ? value.__typename : void 0;
77
+ const fields = ctx.doc && selections ? collectFields(selections, typename, ctx.doc) : null;
78
+ let changed = false;
79
+ const next = {};
80
+ for (const [key, child] of Object.entries(value)) {
81
+ const field = fields?.get(key);
82
+ const name = field?.name ?? key;
83
+ const out = walk(child, field?.selections ?? null, {
84
+ ...ctx,
85
+ ownerId: id ?? ctx.ownerId,
86
+ path: id ? [name] : [...ctx.path, name]
87
+ });
88
+ if (out !== child) changed = true;
89
+ next[key] = out;
90
+ }
91
+ return changed ? next : value;
92
+ }
93
+ function annotateEditLinks(result, query, operationName) {
94
+ let doc = null;
95
+ if (query !== void 0) {
96
+ try {
97
+ doc = readQueryDocument(query, operationName);
98
+ } catch {
99
+ doc = null;
100
+ }
101
+ }
102
+ return walk(result, doc?.operation ?? null, { doc, path: [] });
103
+ }
104
+
105
+ // src/core/live-preview/test-reset.ts
106
+ var resets = /* @__PURE__ */ new Set();
107
+ function onTestReset(reset) {
108
+ resets.add(reset);
109
+ }
110
+
111
+ // src/sveltekit/live.ts
112
+ var starting = null;
113
+ var store = null;
114
+ var bridge = null;
115
+ var applyLog = null;
116
+ var log = [];
117
+ var allowedEditorOrigins;
118
+ var strictMode;
119
+ var logListeners = /* @__PURE__ */ new Set();
120
+ var startListeners = /* @__PURE__ */ new Set();
121
+ function constant(value) {
122
+ return {
123
+ subscribe(run) {
124
+ run(value);
125
+ return () => {
126
+ };
127
+ }
128
+ };
129
+ }
130
+ function isFramed() {
131
+ return typeof window !== "undefined" && window.self !== window.top;
132
+ }
133
+ function startLive() {
134
+ starting ?? (starting = import("./live-preview-2RCU35W5.js").then(
135
+ ({ applyPatchLog, createLiveBridge, createLiveStore }) => {
136
+ applyLog = applyPatchLog;
137
+ const started = createLiveStore();
138
+ bridge = createLiveBridge(started, {
139
+ allowedEditorOrigins: () => allowedEditorOrigins,
140
+ strictMode: () => strictMode
141
+ });
142
+ bridge.start();
143
+ started.onLogChange((next) => {
144
+ log = next;
145
+ logListeners.forEach((listener) => listener());
146
+ });
147
+ store = started;
148
+ startListeners.forEach((listener) => listener(started));
149
+ startListeners.clear();
150
+ }
151
+ ));
152
+ }
153
+ function whenLive(listener) {
154
+ if (store) {
155
+ listener(store);
156
+ return () => {
157
+ };
158
+ }
159
+ startListeners.add(listener);
160
+ return () => startListeners.delete(listener);
161
+ }
162
+ function normalizedQuery(query, data, options = {}) {
163
+ if (!options.enabled) return constant(data);
164
+ const annotated = annotateEditLinks(data, query);
165
+ if (!isFramed()) return constant(annotated);
166
+ if (options.strictMode) strictMode = true;
167
+ if (options.allowedEditorOrigins) {
168
+ allowedEditorOrigins = [...options.allowedEditorOrigins];
169
+ }
170
+ startLive();
171
+ const current = () => log.length && applyLog ? applyLog(query, annotated, log) : annotated;
172
+ return {
173
+ subscribe(run) {
174
+ run(current());
175
+ const onLog = () => run(current());
176
+ logListeners.add(onLog);
177
+ let offInvalidate = null;
178
+ const stopWaiting = options.onSave ? whenLive((started) => {
179
+ offInvalidate = started.onInvalidate(() => options.onSave?.());
180
+ }) : () => {
181
+ };
182
+ return () => {
183
+ logListeners.delete(onLog);
184
+ stopWaiting();
185
+ offInvalidate?.();
186
+ };
187
+ }
188
+ };
189
+ }
190
+ onTestReset(() => {
191
+ bridge?.stop();
192
+ bridge = null;
193
+ store = null;
194
+ starting = null;
195
+ applyLog = null;
196
+ log = [];
197
+ logListeners.clear();
198
+ startListeners.clear();
199
+ allowedEditorOrigins = void 0;
200
+ strictMode = void 0;
201
+ });
202
+ export {
203
+ normalizedQuery
204
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@preprio/toolkit",
3
- "version": "0.5.0-rc.4",
3
+ "version": "0.5.0-rc.6",
4
4
  "description": "Prepr personalization, A/B testing and preview toolkit for React, Next.js, Nuxt, Astro, SvelteKit and Express",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Prepr B.V. (https://prepr.io)",
@@ -80,11 +80,21 @@
80
80
  "require": "./dist/astro/index.cjs"
81
81
  },
82
82
  "./astro/components/*": "./src/astro/*.astro",
83
+ "./codegen": {
84
+ "types": "./dist/codegen/index.d.ts",
85
+ "import": "./dist/codegen/index.js",
86
+ "require": "./dist/codegen/index.cjs"
87
+ },
83
88
  "./sveltekit": {
84
89
  "types": "./dist/sveltekit/index.d.ts",
85
90
  "import": "./dist/sveltekit/index.js",
86
91
  "require": "./dist/sveltekit/index.cjs"
87
92
  },
93
+ "./sveltekit/live": {
94
+ "types": "./dist/sveltekit/live.d.ts",
95
+ "import": "./dist/sveltekit/live.js",
96
+ "require": "./dist/sveltekit/live.cjs"
97
+ },
88
98
  "./sveltekit/components/*": "./src/sveltekit/*.svelte",
89
99
  "./nuxt": {
90
100
  "types": "./dist/nuxt/index.d.ts",
@@ -120,6 +130,7 @@
120
130
  "preact": "^10.29.7"
121
131
  },
122
132
  "peerDependencies": {
133
+ "graphql": ">=16",
123
134
  "next": ">=13",
124
135
  "react": ">=17",
125
136
  "react-dom": ">=17",
@@ -127,6 +138,9 @@
127
138
  "vue": ">=3.3"
128
139
  },
129
140
  "peerDependenciesMeta": {
141
+ "graphql": {
142
+ "optional": true
143
+ },
130
144
  "next": {
131
145
  "optional": true
132
146
  },
@@ -148,6 +162,7 @@
148
162
  "@types/react": "^19.2.17",
149
163
  "@types/react-dom": "^19.2.3",
150
164
  "browserslist": "^4.28.7",
165
+ "graphql": "^16.14.2",
151
166
  "happy-dom": "^20.11.1",
152
167
  "lightningcss": "^1.33.0",
153
168
  "next": "^16.2.12",