@sparkletree/core 0.1.0 → 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 +45 -43
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
# @sparkletree/core
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
Framework-agnostic client for [SparkleTree](https://sparkletree.io): stream
|
|
4
|
+
adaptive campaign text into your site, with copy that renders immediately,
|
|
5
|
+
theme tokens, and built-in impression analytics.
|
|
5
6
|
|
|
6
|
-
**
|
|
7
|
-
|
|
8
|
-
instead — it
|
|
9
|
-
|
|
7
|
+
**Building a React app?** Install
|
|
8
|
+
[`@sparkletree/react`](https://www.npmjs.com/package/@sparkletree/react)
|
|
9
|
+
instead — it wraps this package. Use `core` directly with Vue, Svelte, Solid,
|
|
10
|
+
or plain DOM.
|
|
10
11
|
|
|
11
12
|
```bash
|
|
12
13
|
npm install @sparkletree/core
|
|
@@ -25,11 +26,11 @@ const handle = openStream({
|
|
|
25
26
|
island: "hero",
|
|
26
27
|
},
|
|
27
28
|
// Required. This is what the visitor reads if the network is slow, the
|
|
28
|
-
// campaign is paused, or generation fails
|
|
29
|
+
// campaign is paused, or generation fails — so write it as copy you would
|
|
30
|
+
// ship on its own.
|
|
29
31
|
initial: { headline: "Software that meets the moment" },
|
|
30
|
-
//
|
|
31
|
-
// navigation).
|
|
32
|
-
// typed over.
|
|
32
|
+
// Set true when that copy is already visible (server-rendered, or carried
|
|
33
|
+
// across a navigation). Text a reader can see is never typed over.
|
|
33
34
|
initialIsOnScreen: false,
|
|
34
35
|
onState: (state) => {
|
|
35
36
|
const { marked } = decideChrome(state.contentSource, false);
|
|
@@ -41,59 +42,61 @@ const final = await handle.finished;
|
|
|
41
42
|
handle.close();
|
|
42
43
|
```
|
|
43
44
|
|
|
44
|
-
`openStream` returns `{ choreographer, finished, close }`.
|
|
45
|
-
is the current state; `finished` resolves with the
|
|
46
|
-
aborts the request and releases timers.
|
|
45
|
+
`openStream` returns `{ choreographer, finished, close }`.
|
|
46
|
+
`choreographer.getState()` is the current state; `finished` resolves with the
|
|
47
|
+
terminal state; `close()` aborts the request and releases timers.
|
|
47
48
|
|
|
48
|
-
## What
|
|
49
|
+
## What's in the box
|
|
49
50
|
|
|
50
51
|
| Export | |
|
|
51
52
|
|---|---|
|
|
52
|
-
| `openStream` |
|
|
53
|
-
| `fetchContent` |
|
|
54
|
-
| `CopyChoreographer`, `COPY_FIELDS`, `COPY_HOLD_MS` | The state machine, if you want to drive it yourself. |
|
|
55
|
-
| `SseParser`, `readSseStream` |
|
|
56
|
-
| `decideChrome`, `viewerPrefersStandard`, `setViewerPrefersStandard`, `STANDARD_MODE_KEY` |
|
|
57
|
-
| `applyTheme`, `themeStyle`, `tokenName`, `tokenSource`, `TOKEN_PREFIX` | Theme tokens. |
|
|
58
|
-
| `track`, `ImpressionRecorder`, `observeVisibility`, `sessionId` |
|
|
59
|
-
| `pageVariants`, `VariantRegistry`, `RESERVATION_TIMEOUT_MS` | Variant pinning — one page view
|
|
60
|
-
| `mountAudio`, `activePlayer`, `destroyActiveAudio` | Audio
|
|
61
|
-
| `buildStreamUrl`, `buildContentUrl`, `negotiatedProtocol`, `PROTOCOL_*`, `SDK_PARAM`, `VARIANT_PARAM` | Protocol. |
|
|
53
|
+
| `openStream` | Opens the stream and manages copy state for you. |
|
|
54
|
+
| `fetchContent` | One-shot fetch of campaign content, for a first-paint fetch of your own. Not used by `openStream`. |
|
|
55
|
+
| `CopyChoreographer`, `COPY_FIELDS`, `COPY_HOLD_MS` | The copy state machine, if you want to drive it yourself. |
|
|
56
|
+
| `SseParser`, `readSseStream` | Server-sent-events parsing, usable standalone. |
|
|
57
|
+
| `decideChrome`, `viewerPrefersStandard`, `setViewerPrefersStandard`, `STANDARD_MODE_KEY` | Whether to show the AI-adaptation mark, and the viewer's opt-out. |
|
|
58
|
+
| `applyTheme`, `themeStyle`, `tokenName`, `tokenSource`, `TOKEN_PREFIX` | Theme tokens delivered with the campaign. |
|
|
59
|
+
| `track`, `ImpressionRecorder`, `observeVisibility`, `sessionId` | Impression analytics. |
|
|
60
|
+
| `pageVariants`, `VariantRegistry`, `RESERVATION_TIMEOUT_MS` | Variant pinning — one page view sees one variant. |
|
|
61
|
+
| `mountAudio`, `activePlayer`, `destroyActiveAudio` | Audio playback (no UI). |
|
|
62
|
+
| `buildStreamUrl`, `buildContentUrl`, `negotiatedProtocol`, `PROTOCOL_*`, `SDK_PARAM`, `VARIANT_PARAM` | Protocol plumbing. |
|
|
62
63
|
|
|
63
64
|
## Copy state
|
|
64
65
|
|
|
65
66
|
`state.fields.{greeting,headline,body,cta}` each carry
|
|
66
|
-
`{ text, source, mode, typing, settled }`, already
|
|
67
|
-
`text` and
|
|
67
|
+
`{ text, source, mode, typing, settled }`, already sequenced for display — you
|
|
68
|
+
render `text` and use `typing`/`settled` for presentation only. Alongside them:
|
|
68
69
|
`phase`, `error`, `rewrites`, `contentSource`, `degraded`, `variantId`,
|
|
69
70
|
`theme`, `layout`, `backgroundImage`, `videoUrl`, `sequence`, `ctaStyle`,
|
|
70
71
|
`ctaAction`, `meta`.
|
|
71
72
|
|
|
72
73
|
`phase` moves `idle → holding? → live → settled | failed`. Nothing throws and
|
|
73
|
-
nothing
|
|
74
|
-
fields fall back to your `initial` text.
|
|
74
|
+
nothing goes blank: on `failed`, whatever was painted stays painted, and
|
|
75
|
+
undelivered fields fall back to your `initial` text.
|
|
75
76
|
|
|
76
|
-
##
|
|
77
|
+
## What the client guarantees
|
|
77
78
|
|
|
78
|
-
1. **First paint is real copy.**
|
|
79
|
+
1. **First paint is real copy.** Your `initial` text renders immediately —
|
|
80
|
+
there is no skeleton state.
|
|
79
81
|
2. **Copy changes at most once.** Text a reader may be mid-sentence on is never
|
|
80
|
-
wiped and retyped
|
|
81
|
-
|
|
82
|
-
3. **The server declares
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
82
|
+
wiped and retyped; updates aimed at visible text are composed off-screen and
|
|
83
|
+
land in one step.
|
|
84
|
+
3. **The server declares how content was produced** (static, cached, or
|
|
85
|
+
generated); the client never guesses from timing.
|
|
86
|
+
4. **AI-adapted copy is always marked.** `decideChrome` returns `marked` for
|
|
87
|
+
generated and cached content, never for static — so readers can tell
|
|
88
|
+
adapted text from authored text.
|
|
86
89
|
|
|
87
|
-
Full documentation
|
|
88
|
-
|
|
90
|
+
Full documentation:
|
|
91
|
+
[docs.sparkletree.io](https://docs.sparkletree.io/developers/sdk/core/)
|
|
89
92
|
|
|
90
93
|
## Requirements
|
|
91
94
|
|
|
92
95
|
ES2022, **ESM only**, Node >= 18. `fetch`, `ReadableStream` and
|
|
93
|
-
`AbortController` must exist — inject `fetchImpl`
|
|
94
|
-
|
|
96
|
+
`AbortController` must exist — inject `fetchImpl` to substitute them. No
|
|
97
|
+
dependencies.
|
|
95
98
|
|
|
96
|
-
There is no `require` export condition, which
|
|
99
|
+
There is no `require` export condition, which matters in two places:
|
|
97
100
|
|
|
98
101
|
- **From CommonJS**, `require("@sparkletree/core")` fails on Node 18 with
|
|
99
102
|
`ERR_REQUIRE_ESM`. Use `await import("@sparkletree/core")`. (Node 26 loads it
|
|
@@ -101,5 +104,4 @@ There is no `require` export condition, which is worth knowing in two places:
|
|
|
101
104
|
- **In TypeScript**, `moduleResolution: "bundler"` resolves cleanly. So does
|
|
102
105
|
`"node16"`/`"nodenext"` *if your own package.json has `"type": "module"`*.
|
|
103
106
|
Without it you get `TS1479` telling you to use a dynamic import — accurate,
|
|
104
|
-
not a false positive
|
|
105
|
-
package.
|
|
107
|
+
not a false positive.
|
package/package.json
CHANGED