@createcms/core 0.1.1
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 +169 -0
- package/dist/ab-edge/index.cjs +214 -0
- package/dist/ab-edge/index.d.cts +121 -0
- package/dist/ab-edge/index.d.ts +121 -0
- package/dist/ab-edge/index.js +205 -0
- package/dist/bin/createcms.js +3082 -0
- package/dist/db.cjs +496 -0
- package/dist/db.d.cts +128 -0
- package/dist/db.d.ts +128 -0
- package/dist/db.js +488 -0
- package/dist/index.cjs +13789 -0
- package/dist/index.d.cts +10277 -0
- package/dist/index.d.ts +10277 -0
- package/dist/index.js +13737 -0
- package/dist/nanoid.cjs +50 -0
- package/dist/nanoid.d.cts +29 -0
- package/dist/nanoid.d.ts +29 -0
- package/dist/nanoid.js +47 -0
- package/dist/next/index.cjs +60 -0
- package/dist/next/index.d.cts +141 -0
- package/dist/next/index.d.ts +141 -0
- package/dist/next/index.js +58 -0
- package/dist/next/middleware.cjs +113 -0
- package/dist/next/middleware.d.cts +77 -0
- package/dist/next/middleware.d.ts +77 -0
- package/dist/next/middleware.js +111 -0
- package/dist/plugins/ab-test/analytics/upstash.cjs +345 -0
- package/dist/plugins/ab-test/analytics/upstash.d.cts +193 -0
- package/dist/plugins/ab-test/analytics/upstash.d.ts +193 -0
- package/dist/plugins/ab-test/analytics/upstash.js +343 -0
- package/dist/plugins/ab-test/client.cjs +686 -0
- package/dist/plugins/ab-test/client.d.cts +233 -0
- package/dist/plugins/ab-test/client.d.ts +233 -0
- package/dist/plugins/ab-test/client.js +684 -0
- package/dist/plugins/ab-test/index.cjs +3400 -0
- package/dist/plugins/ab-test/index.d.cts +1131 -0
- package/dist/plugins/ab-test/index.d.ts +1131 -0
- package/dist/plugins/ab-test/index.js +3367 -0
- package/dist/plugins/client.cjs +20 -0
- package/dist/plugins/client.d.cts +3 -0
- package/dist/plugins/client.d.ts +3 -0
- package/dist/plugins/client.js +3 -0
- package/dist/plugins/consent/client.cjs +315 -0
- package/dist/plugins/consent/client.d.cts +145 -0
- package/dist/plugins/consent/client.d.ts +145 -0
- package/dist/plugins/consent/client.js +313 -0
- package/dist/plugins/consent/index.cjs +267 -0
- package/dist/plugins/consent/index.d.cts +618 -0
- package/dist/plugins/consent/index.d.ts +618 -0
- package/dist/plugins/consent/index.js +258 -0
- package/dist/plugins/i18n/index.cjs +2177 -0
- package/dist/plugins/i18n/index.d.cts +562 -0
- package/dist/plugins/i18n/index.d.ts +562 -0
- package/dist/plugins/i18n/index.js +2150 -0
- package/dist/plugins/media-optimize/index.cjs +315 -0
- package/dist/plugins/media-optimize/index.d.cts +144 -0
- package/dist/plugins/media-optimize/index.d.ts +144 -0
- package/dist/plugins/media-optimize/index.js +311 -0
- package/dist/plugins/multi-tenant/index.cjs +210 -0
- package/dist/plugins/multi-tenant/index.d.cts +431 -0
- package/dist/plugins/multi-tenant/index.d.ts +431 -0
- package/dist/plugins/multi-tenant/index.js +207 -0
- package/dist/plugins/server.cjs +24 -0
- package/dist/plugins/server.d.cts +3 -0
- package/dist/plugins/server.d.ts +3 -0
- package/dist/plugins/server.js +3 -0
- package/dist/react/blocks.cjs +233 -0
- package/dist/react/blocks.d.cts +320 -0
- package/dist/react/blocks.d.ts +320 -0
- package/dist/react/blocks.js +226 -0
- package/dist/react/index.cjs +901 -0
- package/dist/react/index.d.cts +992 -0
- package/dist/react/index.d.ts +992 -0
- package/dist/react/index.js +872 -0
- package/dist/react/tracking.cjs +243 -0
- package/dist/react/tracking.d.cts +364 -0
- package/dist/react/tracking.d.ts +364 -0
- package/dist/react/tracking.js +216 -0
- package/dist/react/variant.cjs +59 -0
- package/dist/react/variant.d.cts +26 -0
- package/dist/react/variant.d.ts +26 -0
- package/dist/react/variant.js +57 -0
- package/package.json +303 -0
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MurmurHash3 (32-bit) for deterministic variant assignment.
|
|
3
|
+
* Inlined to avoid an external dependency for ~30 lines of code.
|
|
4
|
+
*/ function murmur3(key, seed = 0) {
|
|
5
|
+
let h = seed >>> 0;
|
|
6
|
+
const len = key.length;
|
|
7
|
+
let i = 0;
|
|
8
|
+
while(i + 4 <= len){
|
|
9
|
+
let k = key.charCodeAt(i) & 0xff | (key.charCodeAt(i + 1) & 0xff) << 8 | (key.charCodeAt(i + 2) & 0xff) << 16 | (key.charCodeAt(i + 3) & 0xff) << 24;
|
|
10
|
+
k = Math.imul(k, 0xcc9e2d51);
|
|
11
|
+
k = k << 15 | k >>> 17;
|
|
12
|
+
k = Math.imul(k, 0x1b873593);
|
|
13
|
+
h ^= k;
|
|
14
|
+
h = h << 13 | h >>> 19;
|
|
15
|
+
h = Math.imul(h, 5) + 0xe6546b64;
|
|
16
|
+
i += 4;
|
|
17
|
+
}
|
|
18
|
+
let k = 0;
|
|
19
|
+
switch(len & 3){
|
|
20
|
+
case 3:
|
|
21
|
+
k ^= (key.charCodeAt(i + 2) & 0xff) << 16;
|
|
22
|
+
// falls through
|
|
23
|
+
case 2:
|
|
24
|
+
k ^= (key.charCodeAt(i + 1) & 0xff) << 8;
|
|
25
|
+
// falls through
|
|
26
|
+
case 1:
|
|
27
|
+
k ^= key.charCodeAt(i) & 0xff;
|
|
28
|
+
k = Math.imul(k, 0xcc9e2d51);
|
|
29
|
+
k = k << 15 | k >>> 17;
|
|
30
|
+
k = Math.imul(k, 0x1b873593);
|
|
31
|
+
h ^= k;
|
|
32
|
+
}
|
|
33
|
+
h ^= len;
|
|
34
|
+
h ^= h >>> 16;
|
|
35
|
+
h = Math.imul(h, 0x85ebca6b);
|
|
36
|
+
h ^= h >>> 13;
|
|
37
|
+
h = Math.imul(h, 0xc2b2ae35);
|
|
38
|
+
h ^= h >>> 16;
|
|
39
|
+
return h >>> 0;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Deterministic variant assignment.
|
|
43
|
+
*
|
|
44
|
+
* The same `contextKey + testId` always produces the same variant.
|
|
45
|
+
* No DB writes needed -- pure function.
|
|
46
|
+
*
|
|
47
|
+
* @param contextKey - Visitor identifier (user ID or anonymous key)
|
|
48
|
+
* @param testId - The A/B test ID
|
|
49
|
+
* @param trafficPercentage - 0-100, how much total traffic enters the test
|
|
50
|
+
* @param variants - Must be sorted by id for stability
|
|
51
|
+
*/ function resolveVariant(contextKey, testId, trafficPercentage, variants) {
|
|
52
|
+
const hash = murmur3(contextKey + ':' + testId);
|
|
53
|
+
const bucket = hash % 10000;
|
|
54
|
+
const control = variants.find((v)=>v.isControl);
|
|
55
|
+
if (bucket >= trafficPercentage * 100) {
|
|
56
|
+
return {
|
|
57
|
+
variantId: control.id,
|
|
58
|
+
inTest: false
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
const sorted = [
|
|
62
|
+
...variants
|
|
63
|
+
].sort((a, b)=>a.id.localeCompare(b.id));
|
|
64
|
+
const normalizedBucket = Math.floor(bucket * 100 / (trafficPercentage * 100));
|
|
65
|
+
let cumulative = 0;
|
|
66
|
+
for (const v of sorted){
|
|
67
|
+
cumulative += v.weight;
|
|
68
|
+
if (normalizedBucket < cumulative) {
|
|
69
|
+
return {
|
|
70
|
+
variantId: v.id,
|
|
71
|
+
inTest: true
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return {
|
|
76
|
+
variantId: control.id,
|
|
77
|
+
inTest: true
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* The control sentinel code. Pattern A ALWAYS rewrites (Vercel "precompute"
|
|
83
|
+
* model): a request with no running test / no consent / outside traffic is
|
|
84
|
+
* rewritten to `/<CONTROL_CODE><pathname>` so it still lands on the single
|
|
85
|
+
* `[abVariant]` route — there is no un-coded passthrough. It is never a real
|
|
86
|
+
* branch id (ids are prefixed), and the render route maps it to the control
|
|
87
|
+
* tree (pickVariant fails closed to control for any non-variant code anyway).
|
|
88
|
+
*/ const CONTROL_CODE = 'control';
|
|
89
|
+
/**
|
|
90
|
+
* The static path prefix the render route lives under (`app/<prefix>/[abVariant]/
|
|
91
|
+
* [[...rest]]`). Pattern A rewrites every public request UNDER this prefix so the
|
|
92
|
+
* variant-coded route never sits at the URL root — otherwise a root catch-all
|
|
93
|
+
* would shadow sibling app routes (e.g. a `/app/*` dashboard). It is internal +
|
|
94
|
+
* transparent (the browser keeps the original URL), so the value only needs to
|
|
95
|
+
* differ from your real top-level app prefixes; with always-rewrite even a real
|
|
96
|
+
* page slugged `/ab` still works (it is rewritten THROUGH the prefix).
|
|
97
|
+
*/ const DEFAULT_VARIANT_PREFIX = '/ab';
|
|
98
|
+
/**
|
|
99
|
+
* Build the variant-coded rewrite path: `<prefix>/<code><pathname>`. The variant
|
|
100
|
+
* code is the first segment AFTER the static prefix — the cache key — never a
|
|
101
|
+
* header/searchParam, so each variant is its own CDN/ISR cache entry (Vercel
|
|
102
|
+
* Flags precompute pattern, nested under `<prefix>` to coexist with other app
|
|
103
|
+
* routes). The matching render route reads the code segment back.
|
|
104
|
+
*/ function variantRewritePath(prefix, code, pathname) {
|
|
105
|
+
// For the root path, omit the trailing slash (`<prefix>/<code>`, not
|
|
106
|
+
// `<prefix>/<code>/`) so the optional-catch-all variant route matches cleanly.
|
|
107
|
+
const suffix = pathname === '/' ? '' : pathname;
|
|
108
|
+
return `${prefix}/${encodeURIComponent(code)}${suffix}`;
|
|
109
|
+
}
|
|
110
|
+
/** Parse a first-party consent cookie value; analytics granted → true. */ function parseConsentCookie(value) {
|
|
111
|
+
if (!value) return false;
|
|
112
|
+
try {
|
|
113
|
+
const state = JSON.parse(decodeURIComponent(value));
|
|
114
|
+
return state.analytics_storage === 'granted';
|
|
115
|
+
} catch {
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Edge-safe random visitor id (mirrors the client `anon_` key shape). Uses the
|
|
121
|
+
* `crypto` global (Web Crypto), available in every edge/worker/browser runtime.
|
|
122
|
+
*/ function generateEdgeVisitorId() {
|
|
123
|
+
const bytes = new Uint8Array(16);
|
|
124
|
+
crypto.getRandomValues(bytes);
|
|
125
|
+
let hex = '';
|
|
126
|
+
for (const b of bytes)hex += b.toString(16).padStart(2, '0');
|
|
127
|
+
return `anon_${hex}`;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* PURE bucketing: given a one-shot `key`, the branch to render IN-TEST, or null =
|
|
131
|
+
* serve control (no running test, or the roll lands outside the test traffic).
|
|
132
|
+
* NO consent / NO persistent identifier — the caller passes an EPHEMERAL random
|
|
133
|
+
* key for a first assignment and then persists only the chosen branch (a
|
|
134
|
+
* variant-only cookie), so nothing identifying is stored. Reuses the same
|
|
135
|
+
* weighted hash as the server pick.
|
|
136
|
+
*/ function pickEdgeVariant(opts) {
|
|
137
|
+
const test = opts.resolve.test;
|
|
138
|
+
if (!test) return {
|
|
139
|
+
branchId: null
|
|
140
|
+
};
|
|
141
|
+
const result = resolveVariant(opts.key, test.testId, test.trafficPercentage, test.variants.map((v)=>({
|
|
142
|
+
id: v.variantId,
|
|
143
|
+
weight: v.weight,
|
|
144
|
+
isControl: v.isControl
|
|
145
|
+
})));
|
|
146
|
+
if (!result.inTest) return {
|
|
147
|
+
branchId: null
|
|
148
|
+
}; // outside traffic → control
|
|
149
|
+
const picked = test.variants.find((v)=>v.variantId === result.variantId);
|
|
150
|
+
return {
|
|
151
|
+
branchId: picked?.branchId ?? null
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* The framework-agnostic edge decision (ALWAYS-rewrite / Vercel precompute).
|
|
156
|
+
* CONSENT-FREE by design: serving a variant + a VARIANT-ONLY cookie (no visitor
|
|
157
|
+
* id, no behavioural data, no third-party transmission) falls under the ePrivacy
|
|
158
|
+
* "strictly necessary" exemption, so fresh ad traffic gets real variants (not
|
|
159
|
+
* always control).
|
|
160
|
+
*
|
|
161
|
+
* - `assignedCode` is the value of the test's variant cookie (`ab_<testId>`) — a
|
|
162
|
+
* branch id, or the control sentinel — from a previous visit. If still valid
|
|
163
|
+
* it is REUSED (consistent variant across requests, no re-roll).
|
|
164
|
+
* - Otherwise a FIRST assignment is rolled from an ephemeral random key; the
|
|
165
|
+
* chosen code is returned in `assignCode` for the adapter to persist in the
|
|
166
|
+
* variant cookie. Out-of-traffic → the control sentinel (no impression).
|
|
167
|
+
*
|
|
168
|
+
* `rewritePath` is always non-null. Returns the `testId` so the adapter knows
|
|
169
|
+
* which cookie to set. No persistent identifier is ever minted here — the
|
|
170
|
+
* consent-gated visitor id + GA4 forwarding live in the client pipeline.
|
|
171
|
+
*/ function decideEdgeVariant(input) {
|
|
172
|
+
const controlCode = input.controlCode ?? CONTROL_CODE;
|
|
173
|
+
const prefix = input.variantPrefix ?? DEFAULT_VARIANT_PREFIX;
|
|
174
|
+
const test = input.resolve.test;
|
|
175
|
+
if (!test) {
|
|
176
|
+
return {
|
|
177
|
+
rewritePath: variantRewritePath(prefix, controlCode, input.pathname),
|
|
178
|
+
assignCode: null,
|
|
179
|
+
testId: null
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
// Reuse a still-valid prior assignment (a published variant branch, or the
|
|
183
|
+
// control sentinel for an out-of-traffic visitor) → consistent, no re-roll.
|
|
184
|
+
const reusable = input.assignedCode === controlCode || input.assignedCode != null && test.variants.some((v)=>v.branchId === input.assignedCode);
|
|
185
|
+
if (reusable) {
|
|
186
|
+
return {
|
|
187
|
+
rewritePath: variantRewritePath(prefix, input.assignedCode, input.pathname),
|
|
188
|
+
assignCode: null,
|
|
189
|
+
testId: test.testId
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
// First assignment: roll once from an ephemeral (non-persisted) random key.
|
|
193
|
+
const { branchId } = pickEdgeVariant({
|
|
194
|
+
key: generateEdgeVisitorId(),
|
|
195
|
+
resolve: input.resolve
|
|
196
|
+
});
|
|
197
|
+
const code = branchId ?? controlCode; // out-of-traffic → control sentinel
|
|
198
|
+
return {
|
|
199
|
+
rewritePath: variantRewritePath(prefix, code, input.pathname),
|
|
200
|
+
assignCode: code,
|
|
201
|
+
testId: test.testId
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export { CONTROL_CODE, DEFAULT_VARIANT_PREFIX, decideEdgeVariant, generateEdgeVisitorId, parseConsentCookie, pickEdgeVariant, resolveVariant, variantRewritePath };
|