@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.
Files changed (83) hide show
  1. package/README.md +169 -0
  2. package/dist/ab-edge/index.cjs +214 -0
  3. package/dist/ab-edge/index.d.cts +121 -0
  4. package/dist/ab-edge/index.d.ts +121 -0
  5. package/dist/ab-edge/index.js +205 -0
  6. package/dist/bin/createcms.js +3082 -0
  7. package/dist/db.cjs +496 -0
  8. package/dist/db.d.cts +128 -0
  9. package/dist/db.d.ts +128 -0
  10. package/dist/db.js +488 -0
  11. package/dist/index.cjs +13789 -0
  12. package/dist/index.d.cts +10277 -0
  13. package/dist/index.d.ts +10277 -0
  14. package/dist/index.js +13737 -0
  15. package/dist/nanoid.cjs +50 -0
  16. package/dist/nanoid.d.cts +29 -0
  17. package/dist/nanoid.d.ts +29 -0
  18. package/dist/nanoid.js +47 -0
  19. package/dist/next/index.cjs +60 -0
  20. package/dist/next/index.d.cts +141 -0
  21. package/dist/next/index.d.ts +141 -0
  22. package/dist/next/index.js +58 -0
  23. package/dist/next/middleware.cjs +113 -0
  24. package/dist/next/middleware.d.cts +77 -0
  25. package/dist/next/middleware.d.ts +77 -0
  26. package/dist/next/middleware.js +111 -0
  27. package/dist/plugins/ab-test/analytics/upstash.cjs +345 -0
  28. package/dist/plugins/ab-test/analytics/upstash.d.cts +193 -0
  29. package/dist/plugins/ab-test/analytics/upstash.d.ts +193 -0
  30. package/dist/plugins/ab-test/analytics/upstash.js +343 -0
  31. package/dist/plugins/ab-test/client.cjs +686 -0
  32. package/dist/plugins/ab-test/client.d.cts +233 -0
  33. package/dist/plugins/ab-test/client.d.ts +233 -0
  34. package/dist/plugins/ab-test/client.js +684 -0
  35. package/dist/plugins/ab-test/index.cjs +3400 -0
  36. package/dist/plugins/ab-test/index.d.cts +1131 -0
  37. package/dist/plugins/ab-test/index.d.ts +1131 -0
  38. package/dist/plugins/ab-test/index.js +3367 -0
  39. package/dist/plugins/client.cjs +20 -0
  40. package/dist/plugins/client.d.cts +3 -0
  41. package/dist/plugins/client.d.ts +3 -0
  42. package/dist/plugins/client.js +3 -0
  43. package/dist/plugins/consent/client.cjs +315 -0
  44. package/dist/plugins/consent/client.d.cts +145 -0
  45. package/dist/plugins/consent/client.d.ts +145 -0
  46. package/dist/plugins/consent/client.js +313 -0
  47. package/dist/plugins/consent/index.cjs +267 -0
  48. package/dist/plugins/consent/index.d.cts +618 -0
  49. package/dist/plugins/consent/index.d.ts +618 -0
  50. package/dist/plugins/consent/index.js +258 -0
  51. package/dist/plugins/i18n/index.cjs +2177 -0
  52. package/dist/plugins/i18n/index.d.cts +562 -0
  53. package/dist/plugins/i18n/index.d.ts +562 -0
  54. package/dist/plugins/i18n/index.js +2150 -0
  55. package/dist/plugins/media-optimize/index.cjs +315 -0
  56. package/dist/plugins/media-optimize/index.d.cts +144 -0
  57. package/dist/plugins/media-optimize/index.d.ts +144 -0
  58. package/dist/plugins/media-optimize/index.js +311 -0
  59. package/dist/plugins/multi-tenant/index.cjs +210 -0
  60. package/dist/plugins/multi-tenant/index.d.cts +431 -0
  61. package/dist/plugins/multi-tenant/index.d.ts +431 -0
  62. package/dist/plugins/multi-tenant/index.js +207 -0
  63. package/dist/plugins/server.cjs +24 -0
  64. package/dist/plugins/server.d.cts +3 -0
  65. package/dist/plugins/server.d.ts +3 -0
  66. package/dist/plugins/server.js +3 -0
  67. package/dist/react/blocks.cjs +233 -0
  68. package/dist/react/blocks.d.cts +320 -0
  69. package/dist/react/blocks.d.ts +320 -0
  70. package/dist/react/blocks.js +226 -0
  71. package/dist/react/index.cjs +901 -0
  72. package/dist/react/index.d.cts +992 -0
  73. package/dist/react/index.d.ts +992 -0
  74. package/dist/react/index.js +872 -0
  75. package/dist/react/tracking.cjs +243 -0
  76. package/dist/react/tracking.d.cts +364 -0
  77. package/dist/react/tracking.d.ts +364 -0
  78. package/dist/react/tracking.js +216 -0
  79. package/dist/react/variant.cjs +59 -0
  80. package/dist/react/variant.d.cts +26 -0
  81. package/dist/react/variant.d.ts +26 -0
  82. package/dist/react/variant.js +57 -0
  83. package/package.json +303 -0
@@ -0,0 +1,243 @@
1
+ 'use client';
2
+ Object.defineProperty(exports, '__esModule', { value: true });
3
+
4
+ var jsxRuntime = require('react/jsx-runtime');
5
+ var React = require('react');
6
+
7
+ function _interopNamespace(e) {
8
+ if (e && e.__esModule) return e;
9
+ var n = Object.create(null);
10
+ if (e) {
11
+ Object.keys(e).forEach(function (k) {
12
+ if (k !== 'default') {
13
+ var d = Object.getOwnPropertyDescriptor(e, k);
14
+ Object.defineProperty(n, k, d.get ? d : {
15
+ enumerable: true,
16
+ get: function () { return e[k]; }
17
+ });
18
+ }
19
+ });
20
+ }
21
+ n.default = e;
22
+ return n;
23
+ }
24
+
25
+ var React__namespace = /*#__PURE__*/_interopNamespace(React);
26
+
27
+ /**
28
+ * Resolves the GA4/dataLayer wire name for a block event key: the author's
29
+ * `EventDeclaration.name` override, else the default `cms_<blockType>_<key>`
30
+ * (locked measurement decision #7). Pure + framework-free so BOTH the client
31
+ * tracker (react/tracking.tsx, where a fire happens) and the server goal-picker
32
+ * (ab-test listGoalEvents, which advertises the goal) resolve names identically
33
+ * — the stored event_type and the offered goal must be the same string.
34
+ */ function resolveWireName(key, blockType, events) {
35
+ return events?.[key]?.name ?? `cms_${blockType}_${key}`;
36
+ }
37
+
38
+ const TrackingRuntimeContext = /*#__PURE__*/ React.createContext(null);
39
+ /**
40
+ * Mount once, high in the CLIENT tree, wrapping the rendered page. Supplies the
41
+ * dispatch + ambient ab-context every {@link BlockTracker} below it reads.
42
+ */ function TrackingRuntimeProvider({ runtime, children }) {
43
+ return /*#__PURE__*/ jsxRuntime.jsx(TrackingRuntimeContext.Provider, {
44
+ value: runtime,
45
+ children: children
46
+ });
47
+ }
48
+ const BlockTrackingContext = /*#__PURE__*/ React.createContext(null);
49
+ /**
50
+ * Scopes the per-block tracking identity for its children. Rendered BY
51
+ * renderContentNode around a functional block (children-as-props — the wrapped
52
+ * subtree is server-rendered and just passed through). Itself renders nothing but
53
+ * the context provider, so it is safe during SSR and never blocks paint.
54
+ */ function BlockTracker({ blockType, blockId, trackingId, events, children }) {
55
+ const value = React.useMemo(()=>({
56
+ blockType,
57
+ blockId,
58
+ trackingId,
59
+ events
60
+ }), [
61
+ blockType,
62
+ blockId,
63
+ trackingId,
64
+ events
65
+ ]);
66
+ return /*#__PURE__*/ jsxRuntime.jsx(BlockTrackingContext.Provider, {
67
+ value: value,
68
+ children: children
69
+ });
70
+ }
71
+ /**
72
+ * Builds the {@link ClientCMSEvent} a fired block event dispatches. Pure +
73
+ * exported so it is unit-testable without a React renderer: stamps the ambient
74
+ * ab-context, maps the block identity to `source` (trackingId → handle, type →
75
+ * block type), and marks it anonymous (the consent-free aggregate path; the
76
+ * consent-gated legs live inside dispatch).
77
+ */ function buildBlockEvent(key, params, runtime, block, interactionId) {
78
+ return {
79
+ // The typed API fires the event KEY; the wire (GA4/dataLayer + the stored
80
+ // event_type) carries the resolved wire name. Outside a BlockTracker
81
+ // (block === null) there is nothing to resolve against, so the key passes.
82
+ name: block ? resolveWireName(key, block.blockType, block.events) : key,
83
+ anonymous: true,
84
+ ...runtime.ab ? {
85
+ ab: runtime.ab
86
+ } : {},
87
+ ...block ? {
88
+ source: {
89
+ handle: block.trackingId,
90
+ type: block.blockType
91
+ }
92
+ } : {},
93
+ ...interactionId ? {
94
+ interactionId
95
+ } : {},
96
+ ...params ? {
97
+ params
98
+ } : {}
99
+ };
100
+ }
101
+ /**
102
+ * The UNTYPED core hook. Reads the runtime + the per-block identity from context
103
+ * and returns a `fire` that builds a {@link ClientCMSEvent} and dispatches it
104
+ * (anonymous aggregate by design — the consent-gated legs are inside dispatch).
105
+ * If no {@link TrackingRuntimeProvider} is mounted, `fire` is a dev-warned no-op
106
+ * (degrade-safe). Prefer the typed {@link createTrackedBlocks} facade.
107
+ */ function useBlockTrackerRaw(expectedBlockType) {
108
+ const runtime = React.useContext(TrackingRuntimeContext);
109
+ const block = React.useContext(BlockTrackingContext);
110
+ const emit = React.useCallback((name, params, interactionId)=>{
111
+ if (!runtime) {
112
+ if (process.env.NODE_ENV !== 'production') {
113
+ console.warn(`[cms] useBlockTracker fire("${name}") called with no <TrackingRuntimeProvider> mounted — event dropped.`);
114
+ }
115
+ return;
116
+ }
117
+ if (process.env.NODE_ENV !== 'production') {
118
+ // Fired outside any <BlockTracker> (e.g. a <TrackedForm> not inside a
119
+ // functional block): the event name is NOT wire-resolved and carries no
120
+ // source, so it won't match a picked goal. Loud, not silent.
121
+ if (!block) {
122
+ console.warn(`[cms] fire("${name}") called outside a <BlockTracker> — the event name is unresolved (no wire name) and has no source; render the firing component inside a functional block.`);
123
+ }
124
+ // The block key is a TYPE-only selector; the dispatched source is the
125
+ // ENCLOSING <BlockTracker>. Warn when they disagree (wrong-key call, or
126
+ // a call from outside the block's own subtree) so a mis-stamped event
127
+ // is loud, not silent.
128
+ if (block && expectedBlockType && block.blockType !== expectedBlockType) {
129
+ console.warn(`[cms] useTrackedBlock("${expectedBlockType}").fire is running inside a "${block.blockType}" block — the event is stamped with "${block.blockType}". Call it from the block's own component.`);
130
+ }
131
+ if (block?.events && !(name in block.events)) {
132
+ console.warn(`[cms] fire("${name}") is not a declared event of block "${block.blockType}".`);
133
+ }
134
+ }
135
+ runtime.dispatch(buildBlockEvent(name, params, runtime, block, interactionId));
136
+ }, [
137
+ runtime,
138
+ block,
139
+ expectedBlockType
140
+ ]);
141
+ const fire = React.useCallback((name, params)=>emit(name, params, undefined), [
142
+ emit
143
+ ]);
144
+ const fireInteraction = React.useCallback((name, interactionId, params)=>emit(name, params, interactionId), [
145
+ emit
146
+ ]);
147
+ return {
148
+ fire,
149
+ fireInteraction
150
+ };
151
+ }
152
+ /**
153
+ * Builds the per-collection typed tracking facade. Pass the collection
154
+ * DEFINITION (single source of truth, same object as `createBlocksMap`).
155
+ * `useTrackedBlock('signupForm').fire` is narrowed to that block's declared
156
+ * events — `fire('typo')`, a missing required param, or a wrong-typed param are
157
+ * all compile errors; a non-functional block key is rejected too.
158
+ *
159
+ * The block key is a TYPE-level selector: the dispatched source is always the
160
+ * ENCLOSING <BlockTracker> (set by the renderer from the rendered node), so call
161
+ * `useTrackedBlock('x')` from block x's own component. A mismatch dev-warns.
162
+ *
163
+ * @example
164
+ * ```tsx
165
+ * // blocks/index.tsx
166
+ * export const trackedBlocks = createTrackedBlocks(pagesCollection);
167
+ *
168
+ * // signup-form.tsx ('use client')
169
+ * const { fire } = trackedBlocks.useTrackedBlock('signupForm');
170
+ * <form action={() => fire('submitSuccess', { plan: 'pro' })} />
171
+ * ```
172
+ */ function createTrackedBlocks(_collection) {
173
+ function useTrackedBlock(block) {
174
+ // The block-key literal selects the event union at the TYPE level; at
175
+ // runtime the BlockTracker has already scoped identity, so the raw hook's
176
+ // fire forwards the (compile-checked) name + params unchanged. The cast is
177
+ // the single controlled erasure point (mirrors BlocksMap._components: any).
178
+ // `block` is passed for the dev-mismatch warning, not for dispatch.
179
+ return useBlockTrackerRaw(block);
180
+ }
181
+ return {
182
+ useTrackedBlock
183
+ };
184
+ }
185
+ /**
186
+ * Wraps a `<form>` so one submit becomes a funnel: it mints an `interactionId`,
187
+ * fires the `attempt` event when the submit STARTS, runs `action`, and fires the
188
+ * `success` event only if `action` resolves — both legs share the interactionId
189
+ * so `completion_rate` (= successes / attempts) can pair them. A throw from
190
+ * `action` leaves the attempt unmatched (a started-but-not-completed interaction).
191
+ *
192
+ * Must be rendered inside a functional block (the renderer's <BlockTracker>), so
193
+ * the events stamp that block's source. Uses React 19 `useActionState` — render
194
+ * it only on React 19 (the rest of this module works on 18). `attempt`/`success`
195
+ * are the block's declared event keys (a typo dev-warns via the raw tracker).
196
+ *
197
+ * @example
198
+ * ```tsx
199
+ * <TrackedForm attempt="submitAttempt" success="submitSuccess" action={subscribe}>
200
+ * <input name="email" /> <button>Join</button>
201
+ * </TrackedForm>
202
+ * ```
203
+ */ function TrackedForm({ attempt, success, action, children, ...formProps }) {
204
+ const { fireInteraction } = useBlockTrackerRaw();
205
+ const run = React.useCallback(async (_prev, formData)=>{
206
+ const interactionId = typeof crypto !== 'undefined' && 'randomUUID' in crypto ? crypto.randomUUID() : `ix_${Date.now()}_${Math.round(Math.random() * 1e9)}`;
207
+ fireInteraction(attempt, interactionId);
208
+ try {
209
+ await action(formData);
210
+ fireInteraction(success, interactionId);
211
+ return {
212
+ ok: true
213
+ };
214
+ } catch {
215
+ return {
216
+ ok: false
217
+ }; // attempt stays unmatched (failed interaction)
218
+ }
219
+ }, [
220
+ attempt,
221
+ success,
222
+ action,
223
+ fireInteraction
224
+ ]);
225
+ // React 19. Namespaced so importing this module never breaks on React 18 —
226
+ // only rendering <TrackedForm> requires 19.
227
+ const [, formAction] = React__namespace.useActionState(run, {
228
+ ok: false
229
+ });
230
+ return /*#__PURE__*/ jsxRuntime.jsx("form", {
231
+ action: formAction,
232
+ ...formProps,
233
+ children: children
234
+ });
235
+ }
236
+
237
+ exports.BlockTracker = BlockTracker;
238
+ exports.TrackedForm = TrackedForm;
239
+ exports.TrackingRuntimeProvider = TrackingRuntimeProvider;
240
+ exports.buildBlockEvent = buildBlockEvent;
241
+ exports.createTrackedBlocks = createTrackedBlocks;
242
+ exports.resolveWireName = resolveWireName;
243
+ exports.useBlockTrackerRaw = useBlockTrackerRaw;
@@ -0,0 +1,364 @@
1
+ import * as React from 'react';
2
+ import { ReactNode, ComponentProps } from 'react';
3
+ import { ConsentState } from '../plugins/consent/index.cjs';
4
+
5
+ type BlockTreeNode = {
6
+ blockId: string;
7
+ type: string;
8
+ properties: Record<string, unknown>;
9
+ children: BlockTreeNode[];
10
+ };
11
+
12
+ /**
13
+ * One published branch of a root, as a snapshot. Shared by the page-level
14
+ * variant shape and the block-level A/B `variants` (below) so the two can't
15
+ * drift. `properties` mirrors the (depth-1 typed) reference `properties`.
16
+ */
17
+ type PublishedBranchSnapshot<TProps = Record<string, unknown>> = {
18
+ branchId: string;
19
+ isControl: boolean;
20
+ properties: TProps;
21
+ tree: BlockTreeNode;
22
+ };
23
+ type ResolvedReference<TProps = Record<string, unknown>> = {
24
+ rootId: string;
25
+ collection: string;
26
+ properties: TProps;
27
+ tree: BlockTreeNode;
28
+ /**
29
+ * Present only when this referenced root has a RUNNING A/B test (AB_FANOUT
30
+ * F2 server fan-out). The server stays a pure, cacheable function: top-level
31
+ * `tree`/`properties` are the CONTROL branch (a no-JS / AB-disabled client
32
+ * renders it as-is), and the client pre-render pass (F3) picks the visitor's
33
+ * variant from `variants` and swaps it in. `variants` includes the control.
34
+ * An OPTIONAL field (not a discriminated union) so `isResolvedReference` —
35
+ * which narrows on `tree`/`properties` — keeps matching.
36
+ */
37
+ abTest?: {
38
+ testId: string;
39
+ trafficPercentage: number;
40
+ variants: PublishedBranchSnapshot<TProps>[];
41
+ };
42
+ };
43
+ type BlockTypes = {
44
+ string: string;
45
+ number: number;
46
+ boolean: boolean;
47
+ date: string;
48
+ richText: string;
49
+ image: string;
50
+ select: string;
51
+ reference: string;
52
+ };
53
+ /** Reference inference mode: `raw` (write input + getBlockTree editor read) keeps
54
+ * a `reference` as its stored rootId string; `resolved` (getPublishedContent)
55
+ * surfaces the inlined `ResolvedReference`. */
56
+ type RefMode = 'raw' | 'resolved';
57
+ type BlockPropertyType = keyof BlockTypes;
58
+ type SelectOption = {
59
+ readonly label: string;
60
+ readonly value: string;
61
+ };
62
+ type BlockPropertySpec<T extends BlockPropertyType> = {
63
+ type: T;
64
+ required?: boolean;
65
+ defaultValue?: BlockTypes[T];
66
+ label: string;
67
+ description?: string;
68
+ placeholder?: string;
69
+ } & (T extends 'select' ? {
70
+ options: readonly SelectOption[];
71
+ } : {}) & (T extends 'reference' ? {
72
+ collection: string;
73
+ } : {});
74
+ /** Discriminated union over all concrete block-property specs. */
75
+ type BlockProperty = {
76
+ [K in BlockPropertyType]: BlockPropertySpec<K>;
77
+ }[BlockPropertyType];
78
+ type Simplify<T> = {
79
+ [K in keyof T]: T[K];
80
+ };
81
+ /** Extracts the runtime value type for a block property.
82
+ * For `select` properties with options, returns the union of option values.
83
+ * A `reference` is a rootId string in `raw` mode (write input + editor read) and
84
+ * a `ResolvedReference` in `resolved` mode (published read).
85
+ * For all other types, returns the corresponding primitive type. */
86
+ type InferPropertyValue<T extends BlockProperty, M extends RefMode = 'raw', TCol extends Record<string, AnyCollectionDefinition> = {}> = T extends {
87
+ type: 'select';
88
+ options: readonly {
89
+ readonly value: infer V extends string;
90
+ }[];
91
+ } ? V : T extends {
92
+ type: 'reference';
93
+ collection: infer C extends string;
94
+ } ? M extends 'resolved' ? C extends keyof TCol ? ResolvedReference<NonNullable<InferBlockProperties<TCol[C]['root']['properties'], 'resolved'>>> : ResolvedReference : string : BlockTypes[T['type']];
95
+ type RequiredPart<T extends Record<string, BlockProperty>, M extends RefMode, TCol extends Record<string, AnyCollectionDefinition>> = {
96
+ [K in keyof T as T[K] extends {
97
+ required: true;
98
+ } ? K : never]: InferPropertyValue<T[K], M, TCol>;
99
+ };
100
+ type OptionalPart<T extends Record<string, BlockProperty>, M extends RefMode, TCol extends Record<string, AnyCollectionDefinition>> = {
101
+ [K in keyof T as T[K] extends {
102
+ required: true;
103
+ } ? never : K]?: InferPropertyValue<T[K], M, TCol>;
104
+ };
105
+ type HasRequiredKeys<T extends Record<string, BlockProperty>> = true extends {
106
+ [K in keyof T]: T[K] extends {
107
+ required: true;
108
+ } ? true : never;
109
+ }[keyof T] ? true : false;
110
+ /** Maps a properties record to its runtime value types, respecting `required`.
111
+ * When all properties are optional, the entire input becomes optional (| undefined). */
112
+ type InferBlockProperties<T extends Record<string, BlockProperty>, M extends RefMode = 'raw', TCol extends Record<string, AnyCollectionDefinition> = {}> = HasRequiredKeys<T> extends true ? Simplify<RequiredPart<T, M, TCol> & OptionalPart<T, M, TCol>> : Simplify<RequiredPart<T, M, TCol> & OptionalPart<T, M, TCol>> | undefined;
113
+ /** Scalar property subset usable as an event parameter (no references/media). */
114
+ type ScalarBlockProperty = Extract<BlockProperty, {
115
+ type: 'string' | 'number' | 'boolean' | 'select' | 'date';
116
+ }>;
117
+ /**
118
+ * Declares a meaningful event a functional block can emit (e.g. a form's
119
+ * `submitSuccess`). Living on the block DEFINITION makes it the single source of
120
+ * truth for the typed `fire(...)` union, the test-creation goal picker, and the
121
+ * analytics wire name. `name` overrides the GA4/dataLayer wire name (defaults to
122
+ * `cms_<blockType>_<eventKey>`, computed by the measurement layer). Whether an
123
+ * event counts as a conversion is decided per test in the UI, not here.
124
+ */
125
+ type EventDeclaration = {
126
+ /** Analytics wire-name override (snake_case). Defaults to cms_<type>_<key>. */
127
+ name?: string;
128
+ /** Typed parameters carried with the event (scalar only). */
129
+ params?: Record<string, ScalarBlockProperty>;
130
+ /** Human label for the goal picker. */
131
+ label?: string;
132
+ };
133
+ /** Call-args tuple for `fire`: required iff the event declares a required param. */
134
+ type EventFireArgs<E extends EventDeclaration> = E extends {
135
+ params: infer P extends Record<string, BlockProperty>;
136
+ } ? HasRequiredKeys<P> extends true ? [params: InferBlockProperties<P>] : [params?: InferBlockProperties<P>] : [];
137
+ /** Event keys a block declares. */
138
+ type BlockEventNames<TEvents extends Record<string, EventDeclaration>> = keyof TEvents & string;
139
+ /**
140
+ * The typed `fire` signature derived from a block's event declarations — the
141
+ * runtime tracker (M3) implements this. `fire('unknown')`, a missing required
142
+ * param, and a wrong-typed param are all compile errors.
143
+ */
144
+ type BlockEventFire<TEvents extends Record<string, EventDeclaration>> = <K extends BlockEventNames<TEvents>>(name: K, ...args: EventFireArgs<TEvents[K]>) => void;
145
+ type BlockDefinition<TProps extends Record<string, BlockProperty> = Record<string, BlockProperty>, TEvents extends Record<string, EventDeclaration> = Record<string, never>> = {
146
+ properties: TProps;
147
+ label: string;
148
+ description?: string;
149
+ previewImageUrl?: string;
150
+ /** Events this (functional) block can emit — see {@link EventDeclaration}. */
151
+ events?: TEvents;
152
+ } & ({
153
+ allowChildren?: false;
154
+ } | {
155
+ allowChildren: true;
156
+ allowedChildBlocks?: string[];
157
+ });
158
+ type AnyBlockDefinition = BlockDefinition<Record<string, BlockProperty>, Record<string, EventDeclaration>>;
159
+ type RootDefinition<TProps extends Record<string, BlockProperty> = Record<string, BlockProperty>> = {
160
+ properties: TProps;
161
+ };
162
+ type SlugConfig = {
163
+ enabled: false;
164
+ } | {
165
+ enabled: true;
166
+ root: string;
167
+ allowRoot?: boolean;
168
+ normalize?: boolean;
169
+ nested?: boolean;
170
+ };
171
+ type CollectionDefinition<TProps extends Record<string, BlockProperty> = Record<string, BlockProperty>, TBlocks extends Record<string, AnyBlockDefinition> = Record<string, AnyBlockDefinition>> = {
172
+ slug?: SlugConfig;
173
+ root: RootDefinition<TProps>;
174
+ blocks?: TBlocks;
175
+ label: string;
176
+ description?: string;
177
+ /**
178
+ * Marks this collection as one whose roots are meant to be EMBEDDED into other
179
+ * roots via a `reference` property (a "reusable block" library). Purely an
180
+ * ergonomic hint — it informs editor pickers and which endpoints to surface; it
181
+ * NEVER gates safety (the delete-in-use guard protects every referenced root
182
+ * regardless of this flag). Any collection can still be a reference target.
183
+ */
184
+ reusableBlock?: boolean;
185
+ };
186
+ type AnyCollectionDefinition = CollectionDefinition<Record<string, BlockProperty>, Record<string, AnyBlockDefinition>>;
187
+
188
+ /**
189
+ * The client-side event a sink receives. Decoupled from the server `CMSEvent`
190
+ * (no `timestamp`/storage `id` — those are minted server-side): this is what the
191
+ * browser knows at fire time.
192
+ */
193
+ type ClientCMSEvent = {
194
+ /** Canonical event name, e.g. `'impression' | 'conversion' | 'form_submit'`. */
195
+ name: string;
196
+ /**
197
+ * A/B attribution by the SERVER-served branch (Pattern A: the variant came
198
+ * from the URL). `branchId` is what the client has; the store leg resolves it
199
+ * to a `variantId` server-side. Absent for non-A/B analytics events.
200
+ */
201
+ ab?: {
202
+ testId: string;
203
+ branchId?: string;
204
+ variantId?: string;
205
+ };
206
+ /** Identity — only on the consent-gated unique-visitor path; never anonymous. */
207
+ visitorId?: string;
208
+ /** True when no identifier is attached (the consent-free aggregate path). */
209
+ anonymous: boolean;
210
+ /** Originating functional block instance (the `trackingId` + block type). */
211
+ source?: {
212
+ handle?: string;
213
+ type?: string;
214
+ };
215
+ /** Funnel grouping id (M4): shared by the attempt + success legs of one interaction. */
216
+ interactionId?: string;
217
+ /** GA4 stitching ids (M5): set only when consent is granted; the store leg
218
+ * forwards them so the server-MP can attribute the hit. */
219
+ transport?: {
220
+ clientId?: string;
221
+ sessionId?: string;
222
+ engagementTimeMsec?: number;
223
+ };
224
+ /**
225
+ * Consent Mode v2 state at fire time (M5). Stamped ALONGSIDE `transport` (only
226
+ * when analytics_storage is granted), so the store leg can relay it to the
227
+ * server, where `buildGa4Payload` needs it to authorize the server-MP forward.
228
+ * Absent on the consent-free anonymous path — its absence is exactly what keeps
229
+ * the server's denied-consent guard from dropping the anonymous aggregate count.
230
+ */
231
+ consent?: ConsentState;
232
+ /** Scalar event params (GA4 wire params). */
233
+ params?: Record<string, string | number | boolean>;
234
+ metadata?: Record<string, unknown>;
235
+ };
236
+
237
+ /**
238
+ * Resolves the GA4/dataLayer wire name for a block event key: the author's
239
+ * `EventDeclaration.name` override, else the default `cms_<blockType>_<key>`
240
+ * (locked measurement decision #7). Pure + framework-free so BOTH the client
241
+ * tracker (react/tracking.tsx, where a fire happens) and the server goal-picker
242
+ * (ab-test listGoalEvents, which advertises the goal) resolve names identically
243
+ * — the stored event_type and the offered goal must be the same string.
244
+ */
245
+ declare function resolveWireName(key: string, blockType: string, events: Record<string, EventDeclaration> | undefined): string;
246
+
247
+ /**
248
+ * The tracking runtime the consumer supplies via {@link TrackingRuntimeProvider}.
249
+ * `dispatch` is the M3a sink fan-out (e.g. `cmsClient.abTest.dispatchEvent`); the
250
+ * package reaches it through context, never by importing the client instance.
251
+ * `ab` is the ambient, server-served A/B attribution for THIS page — single-valued
252
+ * per the XOR rule, so every block event on the page binds to the one running test.
253
+ */
254
+ type TrackingRuntime = {
255
+ dispatch: (event: ClientCMSEvent) => void;
256
+ ab?: {
257
+ testId: string;
258
+ branchId: string;
259
+ };
260
+ };
261
+ /**
262
+ * Mount once, high in the CLIENT tree, wrapping the rendered page. Supplies the
263
+ * dispatch + ambient ab-context every {@link BlockTracker} below it reads.
264
+ */
265
+ declare function TrackingRuntimeProvider({ runtime, children, }: {
266
+ runtime: TrackingRuntime;
267
+ children: ReactNode;
268
+ }): React.JSX.Element;
269
+ /** Per-block identity injected by the renderer from the RSC node (serializable). */
270
+ type BlockTrackingCtx = {
271
+ blockType: string;
272
+ blockId: string;
273
+ /** The block's authored, branch-stable goal anchor (`trackingId` property). */
274
+ trackingId?: string;
275
+ /** The block's declared events — used to resolve wire names + dev-validate. */
276
+ events?: Record<string, EventDeclaration>;
277
+ };
278
+ /**
279
+ * Scopes the per-block tracking identity for its children. Rendered BY
280
+ * renderContentNode around a functional block (children-as-props — the wrapped
281
+ * subtree is server-rendered and just passed through). Itself renders nothing but
282
+ * the context provider, so it is safe during SSR and never blocks paint.
283
+ */
284
+ declare function BlockTracker({ blockType, blockId, trackingId, events, children, }: BlockTrackingCtx & {
285
+ children: ReactNode;
286
+ }): React.JSX.Element;
287
+ /**
288
+ * Builds the {@link ClientCMSEvent} a fired block event dispatches. Pure +
289
+ * exported so it is unit-testable without a React renderer: stamps the ambient
290
+ * ab-context, maps the block identity to `source` (trackingId → handle, type →
291
+ * block type), and marks it anonymous (the consent-free aggregate path; the
292
+ * consent-gated legs live inside dispatch).
293
+ */
294
+ declare function buildBlockEvent(key: string, params: Record<string, string | number | boolean> | undefined, runtime: TrackingRuntime, block: BlockTrackingCtx | null, interactionId?: string): ClientCMSEvent;
295
+ /**
296
+ * The UNTYPED core hook. Reads the runtime + the per-block identity from context
297
+ * and returns a `fire` that builds a {@link ClientCMSEvent} and dispatches it
298
+ * (anonymous aggregate by design — the consent-gated legs are inside dispatch).
299
+ * If no {@link TrackingRuntimeProvider} is mounted, `fire` is a dev-warned no-op
300
+ * (degrade-safe). Prefer the typed {@link createTrackedBlocks} facade.
301
+ */
302
+ declare function useBlockTrackerRaw(expectedBlockType?: string): {
303
+ fire: (name: string, params?: Record<string, string | number | boolean>) => void;
304
+ /** Like {@link fire} but stamps a funnel `interactionId` (M4 — <TrackedForm>). */
305
+ fireInteraction: (name: string, interactionId: string, params?: Record<string, string | number | boolean>) => void;
306
+ };
307
+ /** The functional blocks of a collection (those that declared a non-empty `events`). */
308
+ type FunctionalBlocks<TBlocks extends Record<string, AnyBlockDefinition>> = {
309
+ [K in keyof TBlocks as TBlocks[K]['events'] extends Record<string, EventDeclaration> ? [keyof TBlocks[K]['events']] extends [never] ? never : K : never]: NonNullable<TBlocks[K]['events']>;
310
+ };
311
+ /**
312
+ * Builds the per-collection typed tracking facade. Pass the collection
313
+ * DEFINITION (single source of truth, same object as `createBlocksMap`).
314
+ * `useTrackedBlock('signupForm').fire` is narrowed to that block's declared
315
+ * events — `fire('typo')`, a missing required param, or a wrong-typed param are
316
+ * all compile errors; a non-functional block key is rejected too.
317
+ *
318
+ * The block key is a TYPE-level selector: the dispatched source is always the
319
+ * ENCLOSING <BlockTracker> (set by the renderer from the rendered node), so call
320
+ * `useTrackedBlock('x')` from block x's own component. A mismatch dev-warns.
321
+ *
322
+ * @example
323
+ * ```tsx
324
+ * // blocks/index.tsx
325
+ * export const trackedBlocks = createTrackedBlocks(pagesCollection);
326
+ *
327
+ * // signup-form.tsx ('use client')
328
+ * const { fire } = trackedBlocks.useTrackedBlock('signupForm');
329
+ * <form action={() => fire('submitSuccess', { plan: 'pro' })} />
330
+ * ```
331
+ */
332
+ declare function createTrackedBlocks<TProps extends Record<string, BlockProperty>, TBlocks extends Record<string, AnyBlockDefinition>>(_collection: CollectionDefinition<TProps, TBlocks>): {
333
+ useTrackedBlock: <K extends keyof FunctionalBlocks<TBlocks> & string>(block: K) => {
334
+ fire: BlockEventFire<FunctionalBlocks<TBlocks>[K]>;
335
+ };
336
+ };
337
+ /**
338
+ * Wraps a `<form>` so one submit becomes a funnel: it mints an `interactionId`,
339
+ * fires the `attempt` event when the submit STARTS, runs `action`, and fires the
340
+ * `success` event only if `action` resolves — both legs share the interactionId
341
+ * so `completion_rate` (= successes / attempts) can pair them. A throw from
342
+ * `action` leaves the attempt unmatched (a started-but-not-completed interaction).
343
+ *
344
+ * Must be rendered inside a functional block (the renderer's <BlockTracker>), so
345
+ * the events stamp that block's source. Uses React 19 `useActionState` — render
346
+ * it only on React 19 (the rest of this module works on 18). `attempt`/`success`
347
+ * are the block's declared event keys (a typo dev-warns via the raw tracker).
348
+ *
349
+ * @example
350
+ * ```tsx
351
+ * <TrackedForm attempt="submitAttempt" success="submitSuccess" action={subscribe}>
352
+ * <input name="email" /> <button>Join</button>
353
+ * </TrackedForm>
354
+ * ```
355
+ */
356
+ declare function TrackedForm({ attempt, success, action, children, ...formProps }: {
357
+ attempt: string;
358
+ success: string;
359
+ action: (formData: FormData) => void | Promise<void>;
360
+ children: ReactNode;
361
+ } & Omit<ComponentProps<'form'>, 'action' | 'children'>): React.JSX.Element;
362
+
363
+ export { BlockTracker, TrackedForm, TrackingRuntimeProvider, buildBlockEvent, createTrackedBlocks, resolveWireName, useBlockTrackerRaw };
364
+ export type { BlockTrackingCtx, TrackingRuntime };