claude-artifact-framework 0.1.0 → 0.2.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 +96 -0
- package/dist/index.esm.js +56 -0
- package/dist/index.esm.js.map +3 -3
- package/dist/index.global.js +1 -1
- package/dist/index.global.js.map +4 -4
- package/package.json +1 -1
- package/src/index.js +1 -0
- package/src/state.js +81 -0
package/README.md
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# claude-artifact-framework
|
|
2
|
+
|
|
3
|
+
Utilities for building apps inside Claude Artifacts: platform storage, reactive
|
|
4
|
+
state, and other primitives verified against the real runtime. Zero
|
|
5
|
+
dependencies, no build step — drop it into an artifact straight from a CDN.
|
|
6
|
+
|
|
7
|
+
## Usage via CDN
|
|
8
|
+
|
|
9
|
+
Everything is served through [jsDelivr](https://www.jsdelivr.com/), which
|
|
10
|
+
mirrors npm packages automatically.
|
|
11
|
+
|
|
12
|
+
### As a global script (`<script>` tag)
|
|
13
|
+
|
|
14
|
+
```html
|
|
15
|
+
<script src="https://cdn.jsdelivr.net/npm/claude-artifact-framework@0.2.0/dist/index.global.js"></script>
|
|
16
|
+
<script>
|
|
17
|
+
const { storage, createStore, createPersistedStore, createSharedStore } = ArtifactKit;
|
|
18
|
+
</script>
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### As an ES module
|
|
22
|
+
|
|
23
|
+
```html
|
|
24
|
+
<script type="module">
|
|
25
|
+
import {
|
|
26
|
+
storage,
|
|
27
|
+
createStore,
|
|
28
|
+
createPersistedStore,
|
|
29
|
+
createSharedStore,
|
|
30
|
+
} from "https://cdn.jsdelivr.net/npm/claude-artifact-framework@0.2.0/dist/index.esm.js";
|
|
31
|
+
</script>
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Pin a version (`@0.2.0`) for reproducible artifacts, or drop the version
|
|
35
|
+
(`claude-artifact-framework/dist/...`) to always get the latest release.
|
|
36
|
+
|
|
37
|
+
## `storage`
|
|
38
|
+
|
|
39
|
+
Thin wrapper over `window.storage`, the persistence API Claude injects into a
|
|
40
|
+
**published** Chat Artifact. Values are JSON-serialized for you. Two scopes:
|
|
41
|
+
personal (`storage.*`, one copy per viewer) and shared (`storage.shared.*`,
|
|
42
|
+
one copy for every viewer of the artifact).
|
|
43
|
+
|
|
44
|
+
```js
|
|
45
|
+
await storage.set("theme", "dark");
|
|
46
|
+
await storage.get("theme", "light"); // "dark" — second arg is the fallback
|
|
47
|
+
await storage.list(); // ["theme", ...]
|
|
48
|
+
await storage.delete("theme");
|
|
49
|
+
|
|
50
|
+
await storage.shared.set("board", { count: 0 });
|
|
51
|
+
await storage.shared.get("board", null);
|
|
52
|
+
|
|
53
|
+
storage.available(); // false in draft mode / outside a Chat Artifact
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## `createStore(initialState)`
|
|
57
|
+
|
|
58
|
+
In-memory reactive store. No persistence.
|
|
59
|
+
|
|
60
|
+
```js
|
|
61
|
+
const counter = createStore(0);
|
|
62
|
+
counter.subscribe((value) => console.log("count is now", value));
|
|
63
|
+
counter.set((n) => n + 1);
|
|
64
|
+
counter.get(); // 1
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## `createPersistedStore(key, initialState, options?)`
|
|
68
|
+
|
|
69
|
+
Same as `createStore`, but loads from `storage` once on creation and saves
|
|
70
|
+
(debounced) on every change. Personal scope only.
|
|
71
|
+
|
|
72
|
+
```js
|
|
73
|
+
const prefs = createPersistedStore("prefs", { theme: "light" });
|
|
74
|
+
await prefs.ready; // resolves once the initial load finishes
|
|
75
|
+
prefs.set({ theme: "dark" }); // persisted automatically
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
`options.debounceMs` (default `300`) controls how long to wait after the last
|
|
79
|
+
change before writing to storage.
|
|
80
|
+
|
|
81
|
+
## `createSharedStore(key, initialState, options?)`
|
|
82
|
+
|
|
83
|
+
Same as `createPersistedStore`, but backed by `storage.shared` and polled on
|
|
84
|
+
an interval, so every viewer of a published artifact converges on the same
|
|
85
|
+
state.
|
|
86
|
+
|
|
87
|
+
```js
|
|
88
|
+
const board = createSharedStore("board", { count: 0 }, { pollMs: 2000 });
|
|
89
|
+
await board.ready;
|
|
90
|
+
board.set({ count: board.get().count + 1 }); // pushed to every viewer
|
|
91
|
+
board.stop(); // stop polling, e.g. on teardown
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Conflict handling is last-write-wins: if two viewers change the same key
|
|
95
|
+
within one poll window, whichever write reaches storage last overwrites the
|
|
96
|
+
other. There is no merge/CRDT logic.
|
package/dist/index.esm.js
CHANGED
|
@@ -47,7 +47,63 @@ function scope(shared) {
|
|
|
47
47
|
};
|
|
48
48
|
}
|
|
49
49
|
var storage = { ...scope(false), shared: scope(true), available };
|
|
50
|
+
|
|
51
|
+
// src/state.js
|
|
52
|
+
function makeStore(initialState) {
|
|
53
|
+
let state = initialState;
|
|
54
|
+
const subscribers = /* @__PURE__ */ new Set();
|
|
55
|
+
function get() {
|
|
56
|
+
return state;
|
|
57
|
+
}
|
|
58
|
+
function set(next) {
|
|
59
|
+
state = typeof next === "function" ? next(state) : next;
|
|
60
|
+
subscribers.forEach((fn) => fn(state));
|
|
61
|
+
}
|
|
62
|
+
function subscribe(fn) {
|
|
63
|
+
subscribers.add(fn);
|
|
64
|
+
return () => subscribers.delete(fn);
|
|
65
|
+
}
|
|
66
|
+
return { get, set, subscribe };
|
|
67
|
+
}
|
|
68
|
+
function debounce(fn, ms) {
|
|
69
|
+
let timer;
|
|
70
|
+
return (...args) => {
|
|
71
|
+
clearTimeout(timer);
|
|
72
|
+
timer = setTimeout(() => fn(...args), ms);
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
function createStore(initialState) {
|
|
76
|
+
return makeStore(initialState);
|
|
77
|
+
}
|
|
78
|
+
function createPersistedStore(key, initialState, { debounceMs = 300 } = {}) {
|
|
79
|
+
const store = makeStore(initialState);
|
|
80
|
+
const persist = debounce((value) => storage.set(key, value), debounceMs);
|
|
81
|
+
const ready = storage.get(key, initialState).then((loaded) => store.set(loaded));
|
|
82
|
+
store.subscribe(persist);
|
|
83
|
+
return { ...store, ready };
|
|
84
|
+
}
|
|
85
|
+
function createSharedStore(key, initialState, { pollMs = 2e3, debounceMs = 300 } = {}) {
|
|
86
|
+
const store = makeStore(initialState);
|
|
87
|
+
const persist = debounce((value) => storage.shared.set(key, value), debounceMs);
|
|
88
|
+
let applyingRemote = false;
|
|
89
|
+
store.subscribe((value) => {
|
|
90
|
+
if (!applyingRemote) persist(value);
|
|
91
|
+
});
|
|
92
|
+
async function poll() {
|
|
93
|
+
const remote = await storage.shared.get(key, initialState);
|
|
94
|
+
if (JSON.stringify(remote) === JSON.stringify(store.get())) return;
|
|
95
|
+
applyingRemote = true;
|
|
96
|
+
store.set(remote);
|
|
97
|
+
applyingRemote = false;
|
|
98
|
+
}
|
|
99
|
+
const ready = poll();
|
|
100
|
+
const timer = setInterval(poll, pollMs);
|
|
101
|
+
return { ...store, ready, stop: () => clearInterval(timer) };
|
|
102
|
+
}
|
|
50
103
|
export {
|
|
104
|
+
createPersistedStore,
|
|
105
|
+
createSharedStore,
|
|
106
|
+
createStore,
|
|
51
107
|
storage
|
|
52
108
|
};
|
|
53
109
|
//# sourceMappingURL=index.esm.js.map
|
package/dist/index.esm.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
|
-
"sources": ["../src/storage.js"],
|
|
4
|
-
"sourcesContent": ["// storage.js \u2014 wrapper over window.storage (Chat Artifacts, published only).\n//\n// Verified empirically against the real runtime: get/set/delete/list all\n// take (key, shared = false) and return a Promise resolving to\n// { key, value, shared, \"@type\": \"...StorageXResponse\" }. Confirmed:\n// - values must be strings (a raw object throws \"Invalid payload content\")\n// - shared=true/false is a real, working scope switch\n// - failures (including \"key not found\") all throw the same generic\n// message with no distinguishing `code` \u2014 there is no reliable way to\n// tell \"empty\" from \"backend error\" from the error alone.\n\nfunction available() {\n return typeof window !== \"undefined\" && typeof window.storage === \"object\" && window.storage !== null;\n}\n\nfunction scope(shared) {\n return {\n async get(key, fallback = undefined) {\n if (!available()) return fallback;\n try {\n const res = await window.storage.get(key, shared);\n if (res?.value === undefined || res?.value === null) return fallback;\n try {\n return JSON.parse(res.value);\n } catch {\n return res.value; // not JSON \u2014 a plain string written via set(key, value, { raw: true })\n }\n } catch {\n // See file header: this same catch fires for \"key does not exist\"\n // and for a real backend failure. Treating both as \"no value\" is\n // the safer default for a read path, at the cost of hiding outages.\n return fallback;\n }\n },\n\n async set(key, value, { raw = false } = {}) {\n if (!available()) {\n throw new Error(\"window.storage is not available (is this a Code Artifact rather than a published Chat Artifact?)\");\n }\n const payload = raw ? value : JSON.stringify(value);\n if (typeof payload !== \"string\") {\n throw new Error(\"window.storage only accepts strings \u2014 the value must be JSON-serializable\");\n }\n return window.storage.set(key, payload, shared);\n },\n\n async delete(key) {\n if (!available()) return;\n try {\n await window.storage.delete(key, shared);\n } catch {\n // same ambiguity as get() \u2014 silently a no-op if the key never existed\n }\n },\n\n async list(prefix) {\n if (!available()) return [];\n try {\n const res = await window.storage.list(prefix, shared);\n return res?.keys || [];\n } catch {\n return [];\n }\n },\n };\n}\n\n// storage.get/set/delete/list operate on the per-viewer (personal) scope.\n// storage.shared.* are the same four methods against the shared scope, where\n// every viewer of the artifact reads and writes the same state.\nexport const storage = { ...scope(false), shared: scope(true), available };\n"],
|
|
5
|
-
"mappings": ";AAWA,SAAS,YAAY;AACnB,SAAO,OAAO,WAAW,eAAe,OAAO,OAAO,YAAY,YAAY,OAAO,YAAY;AACnG;AAEA,SAAS,MAAM,QAAQ;AACrB,SAAO;AAAA,IACL,MAAM,IAAI,KAAK,WAAW,QAAW;AACnC,UAAI,CAAC,UAAU,EAAG,QAAO;AACzB,UAAI;AACF,cAAM,MAAM,MAAM,OAAO,QAAQ,IAAI,KAAK,MAAM;AAChD,YAAI,KAAK,UAAU,UAAa,KAAK,UAAU,KAAM,QAAO;AAC5D,YAAI;AACF,iBAAO,KAAK,MAAM,IAAI,KAAK;AAAA,QAC7B,QAAQ;AACN,iBAAO,IAAI;AAAA,QACb;AAAA,MACF,QAAQ;AAIN,eAAO;AAAA,MACT;AAAA,IACF;AAAA,IAEA,MAAM,IAAI,KAAK,OAAO,EAAE,MAAM,MAAM,IAAI,CAAC,GAAG;AAC1C,UAAI,CAAC,UAAU,GAAG;AAChB,cAAM,IAAI,MAAM,kGAAkG;AAAA,MACpH;AACA,YAAM,UAAU,MAAM,QAAQ,KAAK,UAAU,KAAK;AAClD,UAAI,OAAO,YAAY,UAAU;AAC/B,cAAM,IAAI,MAAM,gFAA2E;AAAA,MAC7F;AACA,aAAO,OAAO,QAAQ,IAAI,KAAK,SAAS,MAAM;AAAA,IAChD;AAAA,IAEA,MAAM,OAAO,KAAK;AAChB,UAAI,CAAC,UAAU,EAAG;AAClB,UAAI;AACF,cAAM,OAAO,QAAQ,OAAO,KAAK,MAAM;AAAA,MACzC,QAAQ;AAAA,MAER;AAAA,IACF;AAAA,IAEA,MAAM,KAAK,QAAQ;AACjB,UAAI,CAAC,UAAU,EAAG,QAAO,CAAC;AAC1B,UAAI;AACF,cAAM,MAAM,MAAM,OAAO,QAAQ,KAAK,QAAQ,MAAM;AACpD,eAAO,KAAK,QAAQ,CAAC;AAAA,MACvB,QAAQ;AACN,eAAO,CAAC;AAAA,MACV;AAAA,IACF;AAAA,EACF;AACF;AAKO,IAAM,UAAU,EAAE,GAAG,MAAM,KAAK,GAAG,QAAQ,MAAM,IAAI,GAAG,UAAU;",
|
|
3
|
+
"sources": ["../src/storage.js", "../src/state.js"],
|
|
4
|
+
"sourcesContent": ["// storage.js \u2014 wrapper over window.storage (Chat Artifacts, published only).\n//\n// Verified empirically against the real runtime: get/set/delete/list all\n// take (key, shared = false) and return a Promise resolving to\n// { key, value, shared, \"@type\": \"...StorageXResponse\" }. Confirmed:\n// - values must be strings (a raw object throws \"Invalid payload content\")\n// - shared=true/false is a real, working scope switch\n// - failures (including \"key not found\") all throw the same generic\n// message with no distinguishing `code` \u2014 there is no reliable way to\n// tell \"empty\" from \"backend error\" from the error alone.\n\nfunction available() {\n return typeof window !== \"undefined\" && typeof window.storage === \"object\" && window.storage !== null;\n}\n\nfunction scope(shared) {\n return {\n async get(key, fallback = undefined) {\n if (!available()) return fallback;\n try {\n const res = await window.storage.get(key, shared);\n if (res?.value === undefined || res?.value === null) return fallback;\n try {\n return JSON.parse(res.value);\n } catch {\n return res.value; // not JSON \u2014 a plain string written via set(key, value, { raw: true })\n }\n } catch {\n // See file header: this same catch fires for \"key does not exist\"\n // and for a real backend failure. Treating both as \"no value\" is\n // the safer default for a read path, at the cost of hiding outages.\n return fallback;\n }\n },\n\n async set(key, value, { raw = false } = {}) {\n if (!available()) {\n throw new Error(\"window.storage is not available (is this a Code Artifact rather than a published Chat Artifact?)\");\n }\n const payload = raw ? value : JSON.stringify(value);\n if (typeof payload !== \"string\") {\n throw new Error(\"window.storage only accepts strings \u2014 the value must be JSON-serializable\");\n }\n return window.storage.set(key, payload, shared);\n },\n\n async delete(key) {\n if (!available()) return;\n try {\n await window.storage.delete(key, shared);\n } catch {\n // same ambiguity as get() \u2014 silently a no-op if the key never existed\n }\n },\n\n async list(prefix) {\n if (!available()) return [];\n try {\n const res = await window.storage.list(prefix, shared);\n return res?.keys || [];\n } catch {\n return [];\n }\n },\n };\n}\n\n// storage.get/set/delete/list operate on the per-viewer (personal) scope.\n// storage.shared.* are the same four methods against the shared scope, where\n// every viewer of the artifact reads and writes the same state.\nexport const storage = { ...scope(false), shared: scope(true), available };\n", "// state.js \u2014 small reactive stores for artifact UI state, layered on storage.js.\n//\n// Three tiers, each a superset of the previous:\n// createStore in-memory only, no persistence\n// createPersistedStore loads from storage once, saves (debounced) on every\n// change \u2014 personal scope, single viewer\n// createSharedStore same, but polls storage.shared so every viewer of a\n// published artifact converges on the same state\n//\n// createSharedStore is last-write-wins with no merge: if two viewers change\n// the same key inside one poll window, whichever write lands last on the\n// server wins and the other is silently overwritten on the next poll. Fine\n// for \"everyone sees the same board/list\", not a CRDT.\n\nimport { storage } from \"./storage.js\";\n\nfunction makeStore(initialState) {\n let state = initialState;\n const subscribers = new Set();\n\n function get() {\n return state;\n }\n\n function set(next) {\n state = typeof next === \"function\" ? next(state) : next;\n subscribers.forEach((fn) => fn(state));\n }\n\n function subscribe(fn) {\n subscribers.add(fn);\n return () => subscribers.delete(fn);\n }\n\n return { get, set, subscribe };\n}\n\nfunction debounce(fn, ms) {\n let timer;\n return (...args) => {\n clearTimeout(timer);\n timer = setTimeout(() => fn(...args), ms);\n };\n}\n\nexport function createStore(initialState) {\n return makeStore(initialState);\n}\n\nexport function createPersistedStore(key, initialState, { debounceMs = 300 } = {}) {\n const store = makeStore(initialState);\n const persist = debounce((value) => storage.set(key, value), debounceMs);\n\n const ready = storage.get(key, initialState).then((loaded) => store.set(loaded));\n store.subscribe(persist);\n\n return { ...store, ready };\n}\n\nexport function createSharedStore(key, initialState, { pollMs = 2000, debounceMs = 300 } = {}) {\n const store = makeStore(initialState);\n const persist = debounce((value) => storage.shared.set(key, value), debounceMs);\n let applyingRemote = false;\n\n store.subscribe((value) => {\n if (!applyingRemote) persist(value);\n });\n\n async function poll() {\n const remote = await storage.shared.get(key, initialState);\n if (JSON.stringify(remote) === JSON.stringify(store.get())) return;\n applyingRemote = true;\n store.set(remote);\n applyingRemote = false;\n }\n\n const ready = poll();\n const timer = setInterval(poll, pollMs);\n\n return { ...store, ready, stop: () => clearInterval(timer) };\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/index.global.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
var ArtifactKit=(()=>{var
|
|
1
|
+
var ArtifactKit=(()=>{var d=Object.defineProperty;var m=Object.getOwnPropertyDescriptor;var v=Object.getOwnPropertyNames;var J=Object.prototype.hasOwnProperty;var N=(t,e)=>{for(var r in e)d(t,r,{get:e[r],enumerable:!0})},O=(t,e,r,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let o of v(e))!J.call(t,o)&&o!==r&&d(t,o,{get:()=>e[o],enumerable:!(n=m(e,o))||n.enumerable});return t};var x=t=>O(d({},"__esModule",{value:!0}),t);var E={};N(E,{createPersistedStore:()=>y,createSharedStore:()=>h,createStore:()=>p,storage:()=>a});function u(){return typeof window<"u"&&typeof window.storage=="object"&&window.storage!==null}function w(t){return{async get(e,r=void 0){if(!u())return r;try{let n=await window.storage.get(e,t);if(n?.value===void 0||n?.value===null)return r;try{return JSON.parse(n.value)}catch{return n.value}}catch{return r}},async set(e,r,{raw:n=!1}={}){if(!u())throw new Error("window.storage is not available (is this a Code Artifact rather than a published Chat Artifact?)");let o=n?r:JSON.stringify(r);if(typeof o!="string")throw new Error("window.storage only accepts strings \u2014 the value must be JSON-serializable");return window.storage.set(e,o,t)},async delete(e){if(u())try{await window.storage.delete(e,t)}catch{}},async list(e){if(!u())return[];try{return(await window.storage.list(e,t))?.keys||[]}catch{return[]}}}}var a={...w(!1),shared:w(!0),available:u};function l(t){let e=t,r=new Set;function n(){return e}function o(s){e=typeof s=="function"?s(e):s,r.forEach(f=>f(e))}function c(s){return r.add(s),()=>r.delete(s)}return{get:n,set:o,subscribe:c}}function g(t,e){let r;return(...n)=>{clearTimeout(r),r=setTimeout(()=>t(...n),e)}}function p(t){return l(t)}function y(t,e,{debounceMs:r=300}={}){let n=l(e),o=g(s=>a.set(t,s),r),c=a.get(t,e).then(s=>n.set(s));return n.subscribe(o),{...n,ready:c}}function h(t,e,{pollMs:r=2e3,debounceMs:n=300}={}){let o=l(e),c=g(i=>a.shared.set(t,i),n),s=!1;o.subscribe(i=>{s||c(i)});async function f(){let i=await a.shared.get(t,e);JSON.stringify(i)!==JSON.stringify(o.get())&&(s=!0,o.set(i),s=!1)}let b=f(),S=setInterval(f,r);return{...o,ready:b,stop:()=>clearInterval(S)}}return x(E);})();
|
|
2
2
|
//# sourceMappingURL=index.global.js.map
|
package/dist/index.global.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
|
-
"sources": ["../src/index.js", "../src/storage.js"],
|
|
4
|
-
"sourcesContent": ["// Barrel file \u2014 every module lives in its own src/<name>.js and gets one\n// export line here. Nothing else needs to change to add a module: no build\n// config, no separate entry points, both dist bundles pick it up automatically.\n\nexport { storage } from \"./storage.js\";\n", "// storage.js \u2014 wrapper over window.storage (Chat Artifacts, published only).\n//\n// Verified empirically against the real runtime: get/set/delete/list all\n// take (key, shared = false) and return a Promise resolving to\n// { key, value, shared, \"@type\": \"...StorageXResponse\" }. Confirmed:\n// - values must be strings (a raw object throws \"Invalid payload content\")\n// - shared=true/false is a real, working scope switch\n// - failures (including \"key not found\") all throw the same generic\n// message with no distinguishing `code` \u2014 there is no reliable way to\n// tell \"empty\" from \"backend error\" from the error alone.\n\nfunction available() {\n return typeof window !== \"undefined\" && typeof window.storage === \"object\" && window.storage !== null;\n}\n\nfunction scope(shared) {\n return {\n async get(key, fallback = undefined) {\n if (!available()) return fallback;\n try {\n const res = await window.storage.get(key, shared);\n if (res?.value === undefined || res?.value === null) return fallback;\n try {\n return JSON.parse(res.value);\n } catch {\n return res.value; // not JSON \u2014 a plain string written via set(key, value, { raw: true })\n }\n } catch {\n // See file header: this same catch fires for \"key does not exist\"\n // and for a real backend failure. Treating both as \"no value\" is\n // the safer default for a read path, at the cost of hiding outages.\n return fallback;\n }\n },\n\n async set(key, value, { raw = false } = {}) {\n if (!available()) {\n throw new Error(\"window.storage is not available (is this a Code Artifact rather than a published Chat Artifact?)\");\n }\n const payload = raw ? value : JSON.stringify(value);\n if (typeof payload !== \"string\") {\n throw new Error(\"window.storage only accepts strings \u2014 the value must be JSON-serializable\");\n }\n return window.storage.set(key, payload, shared);\n },\n\n async delete(key) {\n if (!available()) return;\n try {\n await window.storage.delete(key, shared);\n } catch {\n // same ambiguity as get() \u2014 silently a no-op if the key never existed\n }\n },\n\n async list(prefix) {\n if (!available()) return [];\n try {\n const res = await window.storage.list(prefix, shared);\n return res?.keys || [];\n } catch {\n return [];\n }\n },\n };\n}\n\n// storage.get/set/delete/list operate on the per-viewer (personal) scope.\n// storage.shared.* are the same four methods against the shared scope, where\n// every viewer of the artifact reads and writes the same state.\nexport const storage = { ...scope(false), shared: scope(true), available };\n"],
|
|
5
|
-
"mappings": "kbAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,
|
|
6
|
-
"names": ["index_exports", "__export", "storage", "available", "scope", "shared", "key", "fallback", "res", "value", "raw", "payload", "prefix", "storage"]
|
|
3
|
+
"sources": ["../src/index.js", "../src/storage.js", "../src/state.js"],
|
|
4
|
+
"sourcesContent": ["// Barrel file \u2014 every module lives in its own src/<name>.js and gets one\n// export line here. Nothing else needs to change to add a module: no build\n// config, no separate entry points, both dist bundles pick it up automatically.\n\nexport { storage } from \"./storage.js\";\nexport { createStore, createPersistedStore, createSharedStore } from \"./state.js\";\n", "// storage.js \u2014 wrapper over window.storage (Chat Artifacts, published only).\n//\n// Verified empirically against the real runtime: get/set/delete/list all\n// take (key, shared = false) and return a Promise resolving to\n// { key, value, shared, \"@type\": \"...StorageXResponse\" }. Confirmed:\n// - values must be strings (a raw object throws \"Invalid payload content\")\n// - shared=true/false is a real, working scope switch\n// - failures (including \"key not found\") all throw the same generic\n// message with no distinguishing `code` \u2014 there is no reliable way to\n// tell \"empty\" from \"backend error\" from the error alone.\n\nfunction available() {\n return typeof window !== \"undefined\" && typeof window.storage === \"object\" && window.storage !== null;\n}\n\nfunction scope(shared) {\n return {\n async get(key, fallback = undefined) {\n if (!available()) return fallback;\n try {\n const res = await window.storage.get(key, shared);\n if (res?.value === undefined || res?.value === null) return fallback;\n try {\n return JSON.parse(res.value);\n } catch {\n return res.value; // not JSON \u2014 a plain string written via set(key, value, { raw: true })\n }\n } catch {\n // See file header: this same catch fires for \"key does not exist\"\n // and for a real backend failure. Treating both as \"no value\" is\n // the safer default for a read path, at the cost of hiding outages.\n return fallback;\n }\n },\n\n async set(key, value, { raw = false } = {}) {\n if (!available()) {\n throw new Error(\"window.storage is not available (is this a Code Artifact rather than a published Chat Artifact?)\");\n }\n const payload = raw ? value : JSON.stringify(value);\n if (typeof payload !== \"string\") {\n throw new Error(\"window.storage only accepts strings \u2014 the value must be JSON-serializable\");\n }\n return window.storage.set(key, payload, shared);\n },\n\n async delete(key) {\n if (!available()) return;\n try {\n await window.storage.delete(key, shared);\n } catch {\n // same ambiguity as get() \u2014 silently a no-op if the key never existed\n }\n },\n\n async list(prefix) {\n if (!available()) return [];\n try {\n const res = await window.storage.list(prefix, shared);\n return res?.keys || [];\n } catch {\n return [];\n }\n },\n };\n}\n\n// storage.get/set/delete/list operate on the per-viewer (personal) scope.\n// storage.shared.* are the same four methods against the shared scope, where\n// every viewer of the artifact reads and writes the same state.\nexport const storage = { ...scope(false), shared: scope(true), available };\n", "// state.js \u2014 small reactive stores for artifact UI state, layered on storage.js.\n//\n// Three tiers, each a superset of the previous:\n// createStore in-memory only, no persistence\n// createPersistedStore loads from storage once, saves (debounced) on every\n// change \u2014 personal scope, single viewer\n// createSharedStore same, but polls storage.shared so every viewer of a\n// published artifact converges on the same state\n//\n// createSharedStore is last-write-wins with no merge: if two viewers change\n// the same key inside one poll window, whichever write lands last on the\n// server wins and the other is silently overwritten on the next poll. Fine\n// for \"everyone sees the same board/list\", not a CRDT.\n\nimport { storage } from \"./storage.js\";\n\nfunction makeStore(initialState) {\n let state = initialState;\n const subscribers = new Set();\n\n function get() {\n return state;\n }\n\n function set(next) {\n state = typeof next === \"function\" ? next(state) : next;\n subscribers.forEach((fn) => fn(state));\n }\n\n function subscribe(fn) {\n subscribers.add(fn);\n return () => subscribers.delete(fn);\n }\n\n return { get, set, subscribe };\n}\n\nfunction debounce(fn, ms) {\n let timer;\n return (...args) => {\n clearTimeout(timer);\n timer = setTimeout(() => fn(...args), ms);\n };\n}\n\nexport function createStore(initialState) {\n return makeStore(initialState);\n}\n\nexport function createPersistedStore(key, initialState, { debounceMs = 300 } = {}) {\n const store = makeStore(initialState);\n const persist = debounce((value) => storage.set(key, value), debounceMs);\n\n const ready = storage.get(key, initialState).then((loaded) => store.set(loaded));\n store.subscribe(persist);\n\n return { ...store, ready };\n}\n\nexport function createSharedStore(key, initialState, { pollMs = 2000, debounceMs = 300 } = {}) {\n const store = makeStore(initialState);\n const persist = debounce((value) => storage.shared.set(key, value), debounceMs);\n let applyingRemote = false;\n\n store.subscribe((value) => {\n if (!applyingRemote) persist(value);\n });\n\n async function poll() {\n const remote = await storage.shared.get(key, initialState);\n if (JSON.stringify(remote) === JSON.stringify(store.get())) return;\n applyingRemote = true;\n store.set(remote);\n applyingRemote = false;\n }\n\n const ready = poll();\n const timer = setInterval(poll, pollMs);\n\n return { ...store, ready, stop: () => clearInterval(timer) };\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["index_exports", "__export", "createPersistedStore", "createSharedStore", "createStore", "storage", "available", "scope", "shared", "key", "fallback", "res", "value", "raw", "payload", "prefix", "storage", "makeStore", "initialState", "state", "subscribers", "get", "set", "next", "fn", "subscribe", "debounce", "ms", "timer", "args", "createStore", "createPersistedStore", "key", "debounceMs", "store", "persist", "value", "storage", "ready", "loaded", "createSharedStore", "pollMs", "applyingRemote", "poll", "remote"]
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-artifact-framework",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Utilities for building apps inside Claude Artifacts: platform storage, chat tool-calling loops, and other primitives verified against the real runtime.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.esm.js",
|
package/src/index.js
CHANGED
package/src/state.js
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
// state.js — small reactive stores for artifact UI state, layered on storage.js.
|
|
2
|
+
//
|
|
3
|
+
// Three tiers, each a superset of the previous:
|
|
4
|
+
// createStore in-memory only, no persistence
|
|
5
|
+
// createPersistedStore loads from storage once, saves (debounced) on every
|
|
6
|
+
// change — personal scope, single viewer
|
|
7
|
+
// createSharedStore same, but polls storage.shared so every viewer of a
|
|
8
|
+
// published artifact converges on the same state
|
|
9
|
+
//
|
|
10
|
+
// createSharedStore is last-write-wins with no merge: if two viewers change
|
|
11
|
+
// the same key inside one poll window, whichever write lands last on the
|
|
12
|
+
// server wins and the other is silently overwritten on the next poll. Fine
|
|
13
|
+
// for "everyone sees the same board/list", not a CRDT.
|
|
14
|
+
|
|
15
|
+
import { storage } from "./storage.js";
|
|
16
|
+
|
|
17
|
+
function makeStore(initialState) {
|
|
18
|
+
let state = initialState;
|
|
19
|
+
const subscribers = new Set();
|
|
20
|
+
|
|
21
|
+
function get() {
|
|
22
|
+
return state;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function set(next) {
|
|
26
|
+
state = typeof next === "function" ? next(state) : next;
|
|
27
|
+
subscribers.forEach((fn) => fn(state));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function subscribe(fn) {
|
|
31
|
+
subscribers.add(fn);
|
|
32
|
+
return () => subscribers.delete(fn);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return { get, set, subscribe };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function debounce(fn, ms) {
|
|
39
|
+
let timer;
|
|
40
|
+
return (...args) => {
|
|
41
|
+
clearTimeout(timer);
|
|
42
|
+
timer = setTimeout(() => fn(...args), ms);
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function createStore(initialState) {
|
|
47
|
+
return makeStore(initialState);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function createPersistedStore(key, initialState, { debounceMs = 300 } = {}) {
|
|
51
|
+
const store = makeStore(initialState);
|
|
52
|
+
const persist = debounce((value) => storage.set(key, value), debounceMs);
|
|
53
|
+
|
|
54
|
+
const ready = storage.get(key, initialState).then((loaded) => store.set(loaded));
|
|
55
|
+
store.subscribe(persist);
|
|
56
|
+
|
|
57
|
+
return { ...store, ready };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function createSharedStore(key, initialState, { pollMs = 2000, debounceMs = 300 } = {}) {
|
|
61
|
+
const store = makeStore(initialState);
|
|
62
|
+
const persist = debounce((value) => storage.shared.set(key, value), debounceMs);
|
|
63
|
+
let applyingRemote = false;
|
|
64
|
+
|
|
65
|
+
store.subscribe((value) => {
|
|
66
|
+
if (!applyingRemote) persist(value);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
async function poll() {
|
|
70
|
+
const remote = await storage.shared.get(key, initialState);
|
|
71
|
+
if (JSON.stringify(remote) === JSON.stringify(store.get())) return;
|
|
72
|
+
applyingRemote = true;
|
|
73
|
+
store.set(remote);
|
|
74
|
+
applyingRemote = false;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const ready = poll();
|
|
78
|
+
const timer = setInterval(poll, pollMs);
|
|
79
|
+
|
|
80
|
+
return { ...store, ready, stop: () => clearInterval(timer) };
|
|
81
|
+
}
|