claude-artifact-framework 0.1.0 → 0.2.0
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/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/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.
|
|
3
|
+
"version": "0.2.0",
|
|
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
|
+
}
|