@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.
- package/README.md +337 -165
- package/dist/astro/index.cjs +126 -30
- package/dist/astro/index.js +130 -30
- package/dist/{chunk-TG6N3C22.js → chunk-IPMB32AZ.js} +1 -1
- package/dist/{chunk-74KY5EAM.js → chunk-KD3GK6TO.js} +2 -0
- package/dist/{chunk-AHBN6FQP.js → chunk-KZIRM54F.js} +1 -1
- package/dist/{chunk-PDNCNTDB.js → chunk-L3SDL6MP.js} +11 -3
- package/dist/{chunk-46ISDRHT.js → chunk-WHZNCYUS.js} +1 -1
- package/dist/codegen/index.cjs +79 -0
- package/dist/codegen/index.d.cts +46 -0
- package/dist/codegen/index.d.ts +46 -0
- package/dist/codegen/index.js +62 -0
- package/dist/index.cjs +133 -28
- package/dist/index.d.cts +14 -2
- package/dist/index.d.ts +14 -2
- package/dist/index.js +108 -6
- package/dist/{live-preview-H4643QYF.js → live-preview-DLXGXRE5.js} +2 -2
- package/dist/nextjs/components.cjs +11 -3
- package/dist/nextjs/components.js +2 -2
- package/dist/nextjs/index.cjs +124 -28
- package/dist/nextjs/index.d.cts +2 -0
- package/dist/nextjs/index.d.ts +2 -0
- package/dist/nextjs/index.js +128 -28
- package/dist/nextjs/live-action.js +3 -3
- package/dist/nextjs/live.cjs +4 -1
- package/dist/nextjs/live.d.cts +3 -1
- package/dist/nextjs/live.d.ts +3 -1
- package/dist/nextjs/live.js +5 -2
- package/dist/nuxt/chunk-BWPGO25E.js +340 -0
- package/dist/nuxt/index.cjs +1 -1
- package/dist/nuxt/index.js +1 -1
- package/dist/nuxt/{live-preview-O2ZTJNW4.js → live-preview-2RCU35W5.js} +6 -337
- package/dist/nuxt/live.cjs +1007 -911
- package/dist/nuxt/live.js +109 -5
- package/dist/react/components.cjs +11 -3
- package/dist/react/components.js +11 -3
- package/dist/sveltekit/chunk-BWPGO25E.js +340 -0
- package/dist/sveltekit/index.cjs +1 -1
- package/dist/sveltekit/index.js +1 -1
- package/dist/sveltekit/live-preview-2RCU35W5.js +1189 -0
- package/dist/sveltekit/live.cjs +1830 -0
- package/dist/sveltekit/live.d.cts +59 -0
- package/dist/sveltekit/live.d.ts +59 -0
- package/dist/sveltekit/live.js +204 -0
- 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.
|
|
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",
|