tinybase 4.4.0 → 4.4.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.
@@ -1 +1 @@
1
- "use strict";const t=t=>typeof t,a="",e=t(a),s="t",n=(t,a)=>t.startsWith(a),r=Promise,i=t=>null==t,c=(t,a,e)=>i(t)?e?.():a(t),o=(t,a,e)=>t.slice(a,e),l=t=>t.length,h=async t=>r.all(t),u=(t,a)=>t.map(a),w=(t,...a)=>t.push(...a),g=Object,y=(t=[])=>g.fromEntries(t),f=(t,a)=>u(g.entries(t),(([t,e])=>a(e,t))),p=(t,a,e)=>(((t,a)=>!i(((t,a)=>c(t,(t=>t[a])))(t,a)))(t,a)||(t[a]=e()),t[a]),S=t=>JSON.stringify(t,((t,a)=>a instanceof Map?g.fromEntries([...a]):a)),d=JSON.parse,D=(a,s,n)=>a+s+(t(n)==e?n:S(n)),P=(t,a,e)=>{const s=l(t);return n(a,t)?[a[s],(e?d:String)(o(a,s+1))]:void 0},m=(t,a)=>((t,a)=>t?.forEach(a))(t,((t,e)=>a(e,t))),R="hasStore",b=y(u(["Origin","Methods","Headers"],(t=>["Access-Control-Allow-"+t,"*"]))),x=async t=>await t.party.storage.get((t.config.storagePrefix??a)+R),T=async(t,e,r,c)=>{const o=t.party.storage,u=t.config.storagePrefix??a,g={[u+R]:1},y=[],p=[];await h(f(e[0],(async(a,e)=>i(a)?!r&&t.canDelTable(e,c)&&((t,...a)=>t.unshift(...a))(p,v(u,s,e)):t.canSetTable(e,r,c)&&await h(f(a,(async(a,n)=>i(a)?!r&&t.canDelRow(e,n,c)&&w(p,v(u,s,e,n)):t.canSetRow(e,n,r,c)&&await h(f(a,(async(a,l)=>{const h=[e,n,l],f=v(u,s,...h);i(a)?!r&&t.canDelCell(...h,c)&&w(y,f):t.canSetCell(...h,a,r,c,await o.get(f))&&(g[f]=a)}))))))))),await h(f(e[1],(async(a,e)=>{const s=u+"v"+e;i(a)?!r&&t.canDelValue(e,c)&&w(y,s):t.canSetValue(e,a,r,c,await o.get(s))&&(g[s]=a)}))),0!=l(p)&&m(await o.list(),(t=>p.every((a=>!n(t,a)||w(y,t)&&0)))),await o.delete(y),await o.put(g)},v=(t,a,...e)=>D(t,a,o(S(e),1,-1)),C=async(t,a,e=null)=>new Response(e,{status:a,headers:t.config.responseHeaders??b});exports.TinyBasePartyKitServer=class{constructor(t){this.party=t,this.config={}}async onRequest(t){const e=this.config.storePath??"/store";if(new URL(t.url).pathname.endsWith(e)){const e=await x(this),n=await t.text();return"PUT"==t.method?e?C(this,205):(await T(this,d(n),!0,t),C(this,201)):C(this,200,e?S(await(async t=>{const e={},n={},r=t.config.storagePrefix??a;return m(await t.party.storage.list(),((t,a)=>c(P(r,t),(([t,r])=>{if(t==s){const[t,s,n]=d("["+r+"]");p(p(e,t,y),s,y)[n]=a}else"v"==t&&(n[r]=a)})))),[e,n]})(this)):a)}return C(this,404)}async onMessage(t,e){const s=this.config.messagePrefix??a;await c(P(s,t,1),(async([t,a])=>{"s"==t&&await x(this)&&(await T(this,a,!1,e),this.party.broadcast(D(s,"s",a)))}))}canSetTable(t,a,e){return!0}canDelTable(t,a){return!0}canSetRow(t,a,e,s){return!0}canDelRow(t,a,e){return!0}canSetCell(t,a,e,s,n,r,i){return!0}canDelCell(t,a,e,s){return!0}canSetValue(t,a,e,s,n){return!0}canDelValue(t,a){return!0}};
1
+ "use strict";const t=t=>typeof t,e=t(""),a="t",s=(t,e)=>t.startsWith(e),n=Promise,r=t=>null==t,i=(t,e,a)=>r(t)?a?.():e(t),o=(t,e,a)=>t.slice(e,a),c=t=>t.length,l=async t=>n.all(t),g=(t,e)=>t.map(e),h=(t,...e)=>t.push(...e),u=Object,w=(t=[])=>u.fromEntries(t),f=(t,e)=>g(u.entries(t),(([t,a])=>e(a,t))),y=(t,e,a)=>(((t,e)=>!r(((t,e)=>i(t,(t=>t[e])))(t,e)))(t,e)||(t[e]=a()),t[e]),p=t=>JSON.stringify(t,((t,e)=>e instanceof Map?u.fromEntries([...e]):e)),S=JSON.parse,d=(a,s,n)=>a+s+(t(n)==e?n:p(n)),P=(t,e,a)=>{const n=c(t);return s(e,t)?[e[n],(a?S:String)(o(e,n+1))]:void 0},x=(t,e)=>((t,e)=>t?.forEach(e))(t,((t,a)=>e(a,t))),m="hasStore",D=w(g(["Origin","Methods","Headers"],(t=>["Access-Control-Allow-"+t,"*"]))),R=async(t,e="")=>!!await t.get(e+m),b=async(t,e="")=>{const s={},n={};return x(await t.list(),((t,r)=>i(P(e,t),(([t,e])=>{if(t==a){const[t,a,n]=S("["+e+"]");y(y(s,t,w),a,w)[n]=r}else"v"==t&&(n[e]=r)})))),[s,n]},T=async(t,e,n,i)=>{const o=t.party.storage,g=t.config.storagePrefix,u={[g+m]:1},w=[],y=[];await l(f(e[0],(async(e,s)=>r(e)?!n&&t.canDelTable(s,i)&&((t,...e)=>t.unshift(...e))(y,v(g,a,s)):t.canSetTable(s,n,i)&&await l(f(e,(async(e,c)=>r(e)?!n&&t.canDelRow(s,c,i)&&h(y,v(g,a,s,c)):t.canSetRow(s,c,n,i)&&await l(f(e,(async(e,l)=>{const f=[s,c,l],y=v(g,a,...f);r(e)?!n&&t.canDelCell(...f,i)&&h(w,y):t.canSetCell(...f,e,n,i,await o.get(y))&&(u[y]=e)}))))))))),await l(f(e[1],(async(e,a)=>{const s=g+"v"+a;r(e)?!n&&t.canDelValue(a,i)&&h(w,s):t.canSetValue(a,e,n,i,await o.get(s))&&(u[s]=e)}))),0!=c(y)&&x(await o.list(),(t=>y.every((e=>!s(t,e)||h(w,t)&&0)))),await o.delete(w),await o.put(u)},v=(t,e,...a)=>d(t,e,o(p(a),1,-1)),C=async(t,e,a=null)=>new Response(a,{status:e,headers:t.config.responseHeaders});exports.TinyBasePartyKitServer=class{constructor(t){this.party=t,this.config={},this.config.storePath??="/store",this.config.messagePrefix??="",this.config.storagePrefix??="",this.config.responseHeaders??=D}async onRequest(t){const{party:{storage:e},config:{storePath:a,storagePrefix:s}}=this;if(new URL(t.url).pathname.endsWith(a)){const a=await R(e,s),n=await t.text();return"PUT"==t.method?a?C(this,205):(await T(this,S(n),!0,t),C(this,201)):C(this,200,a?p(await b(e,s)):"")}return C(this,404)}async onMessage(t,e){const{party:{storage:a,broadcast:s},config:{messagePrefix:n,storagePrefix:r}}=this;await i(P(n,t,1),(async([t,i])=>{"s"==t&&await R(a,r)&&(await T(this,i,!1,e),s(d(n,"s",i),[e.id]))}))}canSetTable(t,e,a){return!0}canDelTable(t,e){return!0}canSetRow(t,e,a,s){return!0}canDelRow(t,e,a){return!0}canSetCell(t,e,a,s,n,r,i){return!0}canDelCell(t,e,a,s){return!0}canSetValue(t,e,a,s,n){return!0}canDelValue(t,e){return!0}},exports.hasStoreInStorage=R,exports.loadStoreFromStorage=b;
@@ -1 +1 @@
1
- "use strict";const e=e=>typeof e,t="",n=e(t),r="t",l=(e,t)=>e.startsWith(t),i=Promise,s=e=>null==e,o=(e,t,n)=>s(e)?null==n?void 0:n():t(e),a=(e,t,n)=>e.slice(t,n),u=e=>e.length,c=e=>{return t=function*(){return i.all(e)},new Promise(((e,n)=>{var r=e=>{try{i(t.next(e))}catch(e){n(e)}},l=e=>{try{i(t.throw(e))}catch(e){n(e)}},i=t=>t.done?e(t.value):Promise.resolve(t.value).then(r,l);i((t=t.apply(void 0,null)).next())}));var t},d=(e,t)=>e.map(t),h=(e,...t)=>e.push(...t),y=Object,v=(e=[])=>y.fromEntries(e),f=(e,t)=>d(y.entries(e),(([e,n])=>t(n,e))),g=(e,t,n)=>(((e,t)=>!s(((e,t)=>o(e,(e=>e[t])))(e,t)))(e,t)||(e[t]=n()),e[t]),p=e=>JSON.stringify(e,((e,t)=>t instanceof Map?y.fromEntries([...t]):t)),S=JSON.parse,P=(t,r,l)=>t+r+(e(l)==n?l:p(l)),m=(e,t,n)=>{const r=u(e);return l(t,e)?[t[r],(n?S:String)(a(t,r+1))]:void 0},w=(e,t)=>((e,t)=>null==e?void 0:e.forEach(t))(e,((e,n)=>t(n,e)));var x=(e,t,n)=>new Promise(((r,l)=>{var i=e=>{try{o(n.next(e))}catch(e){l(e)}},s=e=>{try{o(n.throw(e))}catch(e){l(e)}},o=e=>e.done?r(e.value):Promise.resolve(e.value).then(i,s);o((n=n.apply(e,t)).next())}));const D="hasStore",R=v(d(["Origin","Methods","Headers"],(e=>["Access-Control-Allow-"+e,"*"]))),b=e=>x(void 0,null,(function*(){var n;return yield e.party.storage.get((null!=(n=e.config.storagePrefix)?n:t)+D)})),T=(e,n,i,o)=>x(void 0,null,(function*(){var a;const d=e.party.storage,y=null!=(a=e.config.storagePrefix)?a:t,v={[y+D]:1},g=[],p=[];yield c(f(n[0],((t,n)=>x(void 0,null,(function*(){return s(t)?!i&&e.canDelTable(n,o)&&((e,...t)=>e.unshift(...t))(p,C(y,r,n)):e.canSetTable(n,i,o)&&(yield c(f(t,((t,l)=>x(void 0,null,(function*(){return s(t)?!i&&e.canDelRow(n,l,o)&&h(p,C(y,r,n,l)):e.canSetRow(n,l,i,o)&&(yield c(f(t,((t,a)=>x(void 0,null,(function*(){const u=[n,l,a],c=C(y,r,...u);s(t)?!i&&e.canDelCell(...u,o)&&h(g,c):e.canSetCell(...u,t,i,o,yield d.get(c))&&(v[c]=t)}))))))}))))))}))))),yield c(f(n[1],((t,n)=>x(void 0,null,(function*(){const r=y+"v"+n;s(t)?!i&&e.canDelValue(n,o)&&h(g,r):e.canSetValue(n,t,i,o,yield d.get(r))&&(v[r]=t)}))))),0!=u(p)&&w(yield d.list(),(e=>p.every((t=>!l(e,t)||h(g,e)&&0)))),yield d.delete(g),yield d.put(v)})),C=(e,t,...n)=>P(e,t,a(p(n),1,-1)),O=(e,t,n=null)=>x(void 0,null,(function*(){var r;return new Response(n,{status:t,headers:null!=(r=e.config.responseHeaders)?r:R})}));exports.TinyBasePartyKitServer=class{constructor(e){this.party=e,this.config={}}onRequest(e){return x(this,null,(function*(){var n;const l=null!=(n=this.config.storePath)?n:"/store";if(new URL(e.url).pathname.endsWith(l)){const n=yield b(this),l=yield e.text();return"PUT"==e.method?n?O(this,205):(yield T(this,S(l),!0,e),O(this,201)):O(this,200,n?p(yield(i=this,x(void 0,null,(function*(){var e;const n={},l={},s=null!=(e=i.config.storagePrefix)?e:t;return w(yield i.party.storage.list(),((e,t)=>o(m(s,e),(([e,i])=>{if(e==r){const[e,r,l]=S("["+i+"]");g(g(n,e,v),r,v)[l]=t}else"v"==e&&(l[i]=t)})))),[n,l]})))):t)}var i;return O(this,404)}))}onMessage(e,n){return x(this,null,(function*(){var r;const l=null!=(r=this.config.messagePrefix)?r:t;yield o(m(l,e,1),(e=>x(this,[e],(function*([e,t]){"s"==e&&(yield b(this))&&(yield T(this,t,!1,n),this.party.broadcast(P(l,"s",t)))}))))}))}canSetTable(e,t,n){return!0}canDelTable(e,t){return!0}canSetRow(e,t,n,r){return!0}canDelRow(e,t,n){return!0}canSetCell(e,t,n,r,l,i,s){return!0}canDelCell(e,t,n,r){return!0}canSetValue(e,t,n,r,l){return!0}canDelValue(e,t){return!0}};
1
+ "use strict";const e=e=>typeof e,t=e(""),n="t",r=(e,t)=>e.startsWith(t),s=Promise,i=e=>null==e,o=(e,t,n)=>i(e)?null==n?void 0:n():t(e),l=(e,t,n)=>e.slice(t,n),a=e=>e.length,c=e=>{return t=function*(){return s.all(e)},new Promise(((e,n)=>{var r=e=>{try{i(t.next(e))}catch(e){n(e)}},s=e=>{try{i(t.throw(e))}catch(e){n(e)}},i=t=>t.done?e(t.value):Promise.resolve(t.value).then(r,s);i((t=t.apply(void 0,null)).next())}));var t},u=(e,t)=>e.map(t),d=(e,...t)=>e.push(...t),h=Object,f=(e=[])=>h.fromEntries(e),y=(e,t)=>u(h.entries(e),(([e,n])=>t(n,e))),g=(e,t,n)=>(((e,t)=>!i(((e,t)=>o(e,(e=>e[t])))(e,t)))(e,t)||(e[t]=n()),e[t]),v=e=>JSON.stringify(e,((e,t)=>t instanceof Map?h.fromEntries([...t]):t)),p=JSON.parse,P=(n,r,s)=>n+r+(e(s)==t?s:v(s)),S=(e,t,n)=>{const s=a(e);return r(t,e)?[t[s],(n?p:String)(l(t,s+1))]:void 0},x=(e,t)=>((e,t)=>null==e?void 0:e.forEach(t))(e,((e,n)=>t(n,e)));var m=(e,t,n)=>new Promise(((r,s)=>{var i=e=>{try{l(n.next(e))}catch(e){s(e)}},o=e=>{try{l(n.throw(e))}catch(e){s(e)}},l=e=>e.done?r(e.value):Promise.resolve(e.value).then(i,o);l((n=n.apply(e,t)).next())}));const w="hasStore",D=f(u(["Origin","Methods","Headers"],(e=>["Access-Control-Allow-"+e,"*"]))),R=(e,...t)=>m(void 0,[e,...t],(function*(e,t=""){return!!(yield e.get(t+w))})),b=(e,...t)=>m(void 0,[e,...t],(function*(e,t=""){const r={},s={};return x(yield e.list(),((e,i)=>o(S(t,e),(([e,t])=>{if(e==n){const[e,n,s]=p("["+t+"]");g(g(r,e,f),n,f)[s]=i}else"v"==e&&(s[t]=i)})))),[r,s]})),T=(e,t,s,o)=>m(void 0,null,(function*(){const l=e.party.storage,u=e.config.storagePrefix,h={[u+w]:1},f=[],g=[];yield c(y(t[0],((t,r)=>m(void 0,null,(function*(){return i(t)?!s&&e.canDelTable(r,o)&&((e,...t)=>e.unshift(...t))(g,C(u,n,r)):e.canSetTable(r,s,o)&&(yield c(y(t,((t,a)=>m(void 0,null,(function*(){return i(t)?!s&&e.canDelRow(r,a,o)&&d(g,C(u,n,r,a)):e.canSetRow(r,a,s,o)&&(yield c(y(t,((t,c)=>m(void 0,null,(function*(){const y=[r,a,c],g=C(u,n,...y);i(t)?!s&&e.canDelCell(...y,o)&&d(f,g):e.canSetCell(...y,t,s,o,yield l.get(g))&&(h[g]=t)}))))))}))))))}))))),yield c(y(t[1],((t,n)=>m(void 0,null,(function*(){const r=u+"v"+n;i(t)?!s&&e.canDelValue(n,o)&&d(f,r):e.canSetValue(n,t,s,o,yield l.get(r))&&(h[r]=t)}))))),0!=a(g)&&x(yield l.list(),(e=>g.every((t=>!r(e,t)||d(f,e)&&0)))),yield l.delete(f),yield l.put(h)})),C=(e,t,...n)=>P(e,t,l(v(n),1,-1)),H=(e,t,n=null)=>m(void 0,null,(function*(){return new Response(n,{status:t,headers:e.config.responseHeaders})}));exports.TinyBasePartyKitServer=class{constructor(e){var t,n,r,s;this.party=e,this.config={},null!=(t=this.config).storePath||(t.storePath="/store"),null!=(n=this.config).messagePrefix||(n.messagePrefix=""),null!=(r=this.config).storagePrefix||(r.storagePrefix=""),null!=(s=this.config).responseHeaders||(s.responseHeaders=D)}onRequest(e){return m(this,null,(function*(){const{party:{storage:t},config:{storePath:n,storagePrefix:r}}=this;if(new URL(e.url).pathname.endsWith(n)){const n=yield R(t,r),s=yield e.text();return"PUT"==e.method?n?H(this,205):(yield T(this,p(s),!0,e),H(this,201)):H(this,200,n?v(yield b(t,r)):"")}return H(this,404)}))}onMessage(e,t){return m(this,null,(function*(){const{party:{storage:n,broadcast:r},config:{messagePrefix:s,storagePrefix:i}}=this;yield o(S(s,e,1),(e=>m(this,[e],(function*([e,o]){"s"==e&&(yield R(n,i))&&(yield T(this,o,!1,t),r(P(s,"s",o),[t.id]))}))))}))}canSetTable(e,t,n){return!0}canDelTable(e,t){return!0}canSetRow(e,t,n,r){return!0}canDelRow(e,t,n){return!0}canSetCell(e,t,n,r,s,i,o){return!0}canDelCell(e,t,n,r){return!0}canSetValue(e,t,n,r,s){return!0}canDelValue(e,t){return!0}},exports.hasStoreInStorage=R,exports.loadStoreFromStorage=b;
@@ -67,15 +67,12 @@ const RESPONSE_HEADERS = objNew(
67
67
  '*',
68
68
  ]),
69
69
  );
70
- const hasStore = async (that) =>
71
- await that.party.storage.get(
72
- (that.config.storagePrefix ?? EMPTY_STRING) + HAS_STORE,
73
- );
74
- const loadStore = async (that) => {
70
+ const hasStoreInStorage = async (storage, storagePrefix = EMPTY_STRING) =>
71
+ !!(await storage.get(storagePrefix + HAS_STORE));
72
+ const loadStoreFromStorage = async (storage, storagePrefix = EMPTY_STRING) => {
75
73
  const tables = {};
76
74
  const values = {};
77
- const storagePrefix = that.config.storagePrefix ?? EMPTY_STRING;
78
- mapForEach(await that.party.storage.list(), (key, cellOrValue) =>
75
+ mapForEach(await storage.list(), (key, cellOrValue) =>
79
76
  ifNotUndefined(deconstruct(storagePrefix, key), ([type, ids]) => {
80
77
  if (type == T) {
81
78
  const [tableId, rowId, cellId] = jsonParse('[' + ids + ']');
@@ -95,9 +92,9 @@ const saveStore = async (
95
92
  requestOrConnection,
96
93
  ) => {
97
94
  const storage = that.party.storage;
98
- const prefix = that.config.storagePrefix ?? EMPTY_STRING;
95
+ const storagePrefix = that.config.storagePrefix;
99
96
  const keysToSet = {
100
- [prefix + HAS_STORE]: 1,
97
+ [storagePrefix + HAS_STORE]: 1,
101
98
  };
102
99
  const keysToDel = [];
103
100
  const keyPrefixesToDel = [];
@@ -108,7 +105,7 @@ const saveStore = async (
108
105
  that.canDelTable(tableId, requestOrConnection) &&
109
106
  arrayUnshift(
110
107
  keyPrefixesToDel,
111
- constructStorageKey(prefix, T, tableId),
108
+ constructStorageKey(storagePrefix, T, tableId),
112
109
  )
113
110
  : that.canSetTable(tableId, initialSave, requestOrConnection) &&
114
111
  (await promiseAll(
@@ -118,7 +115,7 @@ const saveStore = async (
118
115
  that.canDelRow(tableId, rowId, requestOrConnection) &&
119
116
  arrayPush(
120
117
  keyPrefixesToDel,
121
- constructStorageKey(prefix, T, tableId, rowId),
118
+ constructStorageKey(storagePrefix, T, tableId, rowId),
122
119
  )
123
120
  : that.canSetRow(
124
121
  tableId,
@@ -129,7 +126,7 @@ const saveStore = async (
129
126
  (await promiseAll(
130
127
  objMap(row, async (cell, cellId) => {
131
128
  const ids = [tableId, rowId, cellId];
132
- const key = constructStorageKey(prefix, T, ...ids);
129
+ const key = constructStorageKey(storagePrefix, T, ...ids);
133
130
  isUndefined(cell)
134
131
  ? !initialSave &&
135
132
  that.canDelCell(...ids, requestOrConnection) &&
@@ -149,7 +146,7 @@ const saveStore = async (
149
146
  );
150
147
  await promiseAll(
151
148
  objMap(transactionChanges[1], async (value, valueId) => {
152
- const key = prefix + V + valueId;
149
+ const key = storagePrefix + V + valueId;
153
150
  isUndefined(value)
154
151
  ? !initialSave &&
155
152
  that.canDelValue(valueId, requestOrConnection) &&
@@ -181,17 +178,24 @@ const constructStorageKey = (storagePrefix, type, ...ids) =>
181
178
  const createResponse = async (that, status, body = null) =>
182
179
  new Response(body, {
183
180
  status,
184
- headers: that.config.responseHeaders ?? RESPONSE_HEADERS,
181
+ headers: that.config.responseHeaders,
185
182
  });
186
183
  class TinyBasePartyKitServer {
187
184
  constructor(party) {
188
185
  this.party = party;
189
186
  this.config = {};
187
+ this.config.storePath ??= STORE_PATH;
188
+ this.config.messagePrefix ??= EMPTY_STRING;
189
+ this.config.storagePrefix ??= EMPTY_STRING;
190
+ this.config.responseHeaders ??= RESPONSE_HEADERS;
190
191
  }
191
192
  async onRequest(request) {
192
- const storePath = this.config.storePath ?? STORE_PATH;
193
+ const {
194
+ party: {storage},
195
+ config: {storePath, storagePrefix},
196
+ } = this;
193
197
  if (new URL(request.url).pathname.endsWith(storePath)) {
194
- const hasExistingStore = await hasStore(this);
198
+ const hasExistingStore = await hasStoreInStorage(storage, storagePrefix);
195
199
  const text = await request.text();
196
200
  if (request.method == PUT) {
197
201
  if (hasExistingStore) {
@@ -203,19 +207,29 @@ class TinyBasePartyKitServer {
203
207
  return createResponse(
204
208
  this,
205
209
  200,
206
- hasExistingStore ? jsonString(await loadStore(this)) : EMPTY_STRING,
210
+ hasExistingStore
211
+ ? jsonString(await loadStoreFromStorage(storage, storagePrefix))
212
+ : EMPTY_STRING,
207
213
  );
208
214
  }
209
215
  return createResponse(this, 404);
210
216
  }
211
217
  async onMessage(message, connection) {
212
- const messagePrefix = this.config.messagePrefix ?? EMPTY_STRING;
218
+ const {
219
+ party: {storage, broadcast},
220
+ config: {messagePrefix, storagePrefix},
221
+ } = this;
213
222
  await ifNotUndefined(
214
223
  deconstruct(messagePrefix, message, 1),
215
224
  async ([type, payload]) => {
216
- if (type == SET_CHANGES && (await hasStore(this))) {
225
+ if (
226
+ type == SET_CHANGES &&
227
+ (await hasStoreInStorage(storage, storagePrefix))
228
+ ) {
217
229
  await saveStore(this, payload, false, connection);
218
- this.party.broadcast(construct(messagePrefix, SET_CHANGES, payload));
230
+ broadcast(construct(messagePrefix, SET_CHANGES, payload), [
231
+ connection.id,
232
+ ]);
219
233
  }
220
234
  },
221
235
  );
@@ -254,4 +268,4 @@ class TinyBasePartyKitServer {
254
268
  }
255
269
  }
256
270
 
257
- export {TinyBasePartyKitServer};
271
+ export {TinyBasePartyKitServer, hasStoreInStorage, loadStoreFromStorage};
@@ -1 +1 @@
1
- const e=e=>typeof e,t="",n=e(t),l="t",r=(e,t)=>e.startsWith(t),i=Promise,o=e=>null==e,s=(e,t,n)=>o(e)?null==n?void 0:n():t(e),a=(e,t,n)=>e.slice(t,n),u=e=>e.length,c=e=>{return t=function*(){return i.all(e)},new Promise(((e,n)=>{var l=e=>{try{i(t.next(e))}catch(e){n(e)}},r=e=>{try{i(t.throw(e))}catch(e){n(e)}},i=t=>t.done?e(t.value):Promise.resolve(t.value).then(l,r);i((t=t.apply(void 0,null)).next())}));var t},d=(e,t)=>e.map(t),h=(e,...t)=>e.push(...t),f=Object,v=(e=[])=>f.fromEntries(e),y=(e,t)=>d(f.entries(e),(([e,n])=>t(n,e))),g=(e,t,n)=>(((e,t)=>!o(((e,t)=>s(e,(e=>e[t])))(e,t)))(e,t)||(e[t]=n()),e[t]),p=e=>JSON.stringify(e,((e,t)=>t instanceof Map?f.fromEntries([...t]):t)),S=JSON.parse,m=(t,l,r)=>t+l+(e(r)==n?r:p(r)),w=(e,t,n)=>{const l=u(e);return r(t,e)?[t[l],(n?S:String)(a(t,l+1))]:void 0},P=(e,t)=>((e,t)=>null==e?void 0:e.forEach(t))(e,((e,n)=>t(n,e)));var x=(e,t,n)=>new Promise(((l,r)=>{var i=e=>{try{s(n.next(e))}catch(e){r(e)}},o=e=>{try{s(n.throw(e))}catch(e){r(e)}},s=e=>e.done?l(e.value):Promise.resolve(e.value).then(i,o);s((n=n.apply(e,t)).next())}));const D="hasStore",R=v(d(["Origin","Methods","Headers"],(e=>["Access-Control-Allow-"+e,"*"]))),b=e=>x(void 0,null,(function*(){var n;return yield e.party.storage.get((null!=(n=e.config.storagePrefix)?n:t)+D)})),C=(e,n,i,s)=>x(void 0,null,(function*(){var a;const d=e.party.storage,f=null!=(a=e.config.storagePrefix)?a:t,v={[f+D]:1},g=[],p=[];yield c(y(n[0],((t,n)=>x(void 0,null,(function*(){return o(t)?!i&&e.canDelTable(n,s)&&((e,...t)=>e.unshift(...t))(p,T(f,l,n)):e.canSetTable(n,i,s)&&(yield c(y(t,((t,r)=>x(void 0,null,(function*(){return o(t)?!i&&e.canDelRow(n,r,s)&&h(p,T(f,l,n,r)):e.canSetRow(n,r,i,s)&&(yield c(y(t,((t,a)=>x(void 0,null,(function*(){const u=[n,r,a],c=T(f,l,...u);o(t)?!i&&e.canDelCell(...u,s)&&h(g,c):e.canSetCell(...u,t,i,s,yield d.get(c))&&(v[c]=t)}))))))}))))))}))))),yield c(y(n[1],((t,n)=>x(void 0,null,(function*(){const l=f+"v"+n;o(t)?!i&&e.canDelValue(n,s)&&h(g,l):e.canSetValue(n,t,i,s,yield d.get(l))&&(v[l]=t)}))))),0!=u(p)&&P(yield d.list(),(e=>p.every((t=>!r(e,t)||h(g,e)&&0)))),yield d.delete(g),yield d.put(v)})),T=(e,t,...n)=>m(e,t,a(p(n),1,-1)),O=(e,t,n=null)=>x(void 0,null,(function*(){var l;return new Response(n,{status:t,headers:null!=(l=e.config.responseHeaders)?l:R})}));class V{constructor(e){this.party=e,this.config={}}onRequest(e){return x(this,null,(function*(){var n;const r=null!=(n=this.config.storePath)?n:"/store";if(new URL(e.url).pathname.endsWith(r)){const n=yield b(this),r=yield e.text();return"PUT"==e.method?n?O(this,205):(yield C(this,S(r),!0,e),O(this,201)):O(this,200,n?p(yield(i=this,x(void 0,null,(function*(){var e;const n={},r={},o=null!=(e=i.config.storagePrefix)?e:t;return P(yield i.party.storage.list(),((e,t)=>s(w(o,e),(([e,i])=>{if(e==l){const[e,l,r]=S("["+i+"]");g(g(n,e,v),l,v)[r]=t}else"v"==e&&(r[i]=t)})))),[n,r]})))):t)}var i;return O(this,404)}))}onMessage(e,n){return x(this,null,(function*(){var l;const r=null!=(l=this.config.messagePrefix)?l:t;yield s(w(r,e,1),(e=>x(this,[e],(function*([e,t]){"s"==e&&(yield b(this))&&(yield C(this,t,!1,n),this.party.broadcast(m(r,"s",t)))}))))}))}canSetTable(e,t,n){return!0}canDelTable(e,t){return!0}canSetRow(e,t,n,l){return!0}canDelRow(e,t,n){return!0}canSetCell(e,t,n,l,r,i,o){return!0}canDelCell(e,t,n,l){return!0}canSetValue(e,t,n,l,r){return!0}canDelValue(e,t){return!0}}export{V as TinyBasePartyKitServer};
1
+ const e=e=>typeof e,t=e(""),n="t",r=(e,t)=>e.startsWith(t),i=Promise,l=e=>null==e,s=(e,t,n)=>l(e)?null==n?void 0:n():t(e),o=(e,t,n)=>e.slice(t,n),a=e=>e.length,c=e=>{return t=function*(){return i.all(e)},new Promise(((e,n)=>{var r=e=>{try{l(t.next(e))}catch(e){n(e)}},i=e=>{try{l(t.throw(e))}catch(e){n(e)}},l=t=>t.done?e(t.value):Promise.resolve(t.value).then(r,i);l((t=t.apply(void 0,null)).next())}));var t},u=(e,t)=>e.map(t),d=(e,...t)=>e.push(...t),h=Object,f=(e=[])=>h.fromEntries(e),y=(e,t)=>u(h.entries(e),(([e,n])=>t(n,e))),g=(e,t,n)=>(((e,t)=>!l(((e,t)=>s(e,(e=>e[t])))(e,t)))(e,t)||(e[t]=n()),e[t]),v=e=>JSON.stringify(e,((e,t)=>t instanceof Map?h.fromEntries([...t]):t)),p=JSON.parse,P=(n,r,i)=>n+r+(e(i)==t?i:v(i)),x=(e,t,n)=>{const i=a(e);return r(t,e)?[t[i],(n?p:String)(o(t,i+1))]:void 0},m=(e,t)=>((e,t)=>null==e?void 0:e.forEach(t))(e,((e,n)=>t(n,e)));var S=(e,t,n)=>new Promise(((r,i)=>{var l=e=>{try{o(n.next(e))}catch(e){i(e)}},s=e=>{try{o(n.throw(e))}catch(e){i(e)}},o=e=>e.done?r(e.value):Promise.resolve(e.value).then(l,s);o((n=n.apply(e,t)).next())}));const w="hasStore",D=f(u(["Origin","Methods","Headers"],(e=>["Access-Control-Allow-"+e,"*"]))),R=(e,...t)=>S(void 0,[e,...t],(function*(e,t=""){return!!(yield e.get(t+w))})),b=(e,...t)=>S(void 0,[e,...t],(function*(e,t=""){const r={},i={};return m(yield e.list(),((e,l)=>s(x(t,e),(([e,t])=>{if(e==n){const[e,n,i]=p("["+t+"]");g(g(r,e,f),n,f)[i]=l}else"v"==e&&(i[t]=l)})))),[r,i]})),C=(e,t,i,s)=>S(void 0,null,(function*(){const o=e.party.storage,u=e.config.storagePrefix,h={[u+w]:1},f=[],g=[];yield c(y(t[0],((t,r)=>S(void 0,null,(function*(){return l(t)?!i&&e.canDelTable(r,s)&&((e,...t)=>e.unshift(...t))(g,T(u,n,r)):e.canSetTable(r,i,s)&&(yield c(y(t,((t,a)=>S(void 0,null,(function*(){return l(t)?!i&&e.canDelRow(r,a,s)&&d(g,T(u,n,r,a)):e.canSetRow(r,a,i,s)&&(yield c(y(t,((t,c)=>S(void 0,null,(function*(){const y=[r,a,c],g=T(u,n,...y);l(t)?!i&&e.canDelCell(...y,s)&&d(f,g):e.canSetCell(...y,t,i,s,yield o.get(g))&&(h[g]=t)}))))))}))))))}))))),yield c(y(t[1],((t,n)=>S(void 0,null,(function*(){const r=u+"v"+n;l(t)?!i&&e.canDelValue(n,s)&&d(f,r):e.canSetValue(n,t,i,s,yield o.get(r))&&(h[r]=t)}))))),0!=a(g)&&m(yield o.list(),(e=>g.every((t=>!r(e,t)||d(f,e)&&0)))),yield o.delete(f),yield o.put(h)})),T=(e,t,...n)=>P(e,t,o(v(n),1,-1)),H=(e,t,n=null)=>S(void 0,null,(function*(){return new Response(n,{status:t,headers:e.config.responseHeaders})}));class O{constructor(e){var t,n,r,i;this.party=e,this.config={},null!=(t=this.config).storePath||(t.storePath="/store"),null!=(n=this.config).messagePrefix||(n.messagePrefix=""),null!=(r=this.config).storagePrefix||(r.storagePrefix=""),null!=(i=this.config).responseHeaders||(i.responseHeaders=D)}onRequest(e){return S(this,null,(function*(){const{party:{storage:t},config:{storePath:n,storagePrefix:r}}=this;if(new URL(e.url).pathname.endsWith(n)){const n=yield R(t,r),i=yield e.text();return"PUT"==e.method?n?H(this,205):(yield C(this,p(i),!0,e),H(this,201)):H(this,200,n?v(yield b(t,r)):"")}return H(this,404)}))}onMessage(e,t){return S(this,null,(function*(){const{party:{storage:n,broadcast:r},config:{messagePrefix:i,storagePrefix:l}}=this;yield s(x(i,e,1),(e=>S(this,[e],(function*([e,s]){"s"==e&&(yield R(n,l))&&(yield C(this,s,!1,t),r(P(i,"s",s),[t.id]))}))))}))}canSetTable(e,t,n){return!0}canDelTable(e,t){return!0}canSetRow(e,t,n,r){return!0}canDelRow(e,t,n){return!0}canSetCell(e,t,n,r,i,l,s){return!0}canDelCell(e,t,n,r){return!0}canSetValue(e,t,n,r,i){return!0}canDelValue(e,t){return!0}}export{O as TinyBasePartyKitServer,R as hasStoreInStorage,b as loadStoreFromStorage};
@@ -1 +1 @@
1
- const t=t=>typeof t,a="",e=t(a),s="t",n=(t,a)=>t.startsWith(a),r=Promise,i=t=>null==t,c=(t,a,e)=>i(t)?e?.():a(t),o=(t,a,e)=>t.slice(a,e),l=t=>t.length,h=async t=>r.all(t),u=(t,a)=>t.map(a),w=(t,...a)=>t.push(...a),g=Object,f=(t=[])=>g.fromEntries(t),y=(t,a)=>u(g.entries(t),(([t,e])=>a(e,t))),p=(t,a,e)=>(((t,a)=>!i(((t,a)=>c(t,(t=>t[a])))(t,a)))(t,a)||(t[a]=e()),t[a]),S=t=>JSON.stringify(t,((t,a)=>a instanceof Map?g.fromEntries([...a]):a)),d=JSON.parse,D=(a,s,n)=>a+s+(t(n)==e?n:S(n)),m=(t,a,e)=>{const s=l(t);return n(a,t)?[a[s],(e?d:String)(o(a,s+1))]:void 0},P=(t,a)=>((t,a)=>t?.forEach(a))(t,((t,e)=>a(e,t))),R="hasStore",b=f(u(["Origin","Methods","Headers"],(t=>["Access-Control-Allow-"+t,"*"]))),x=async t=>await t.party.storage.get((t.config.storagePrefix??a)+R),C=async(t,e,r,c)=>{const o=t.party.storage,u=t.config.storagePrefix??a,g={[u+R]:1},f=[],p=[];await h(y(e[0],(async(a,e)=>i(a)?!r&&t.canDelTable(e,c)&&((t,...a)=>t.unshift(...a))(p,T(u,s,e)):t.canSetTable(e,r,c)&&await h(y(a,(async(a,n)=>i(a)?!r&&t.canDelRow(e,n,c)&&w(p,T(u,s,e,n)):t.canSetRow(e,n,r,c)&&await h(y(a,(async(a,l)=>{const h=[e,n,l],y=T(u,s,...h);i(a)?!r&&t.canDelCell(...h,c)&&w(f,y):t.canSetCell(...h,a,r,c,await o.get(y))&&(g[y]=a)}))))))))),await h(y(e[1],(async(a,e)=>{const s=u+"v"+e;i(a)?!r&&t.canDelValue(e,c)&&w(f,s):t.canSetValue(e,a,r,c,await o.get(s))&&(g[s]=a)}))),0!=l(p)&&P(await o.list(),(t=>p.every((a=>!n(t,a)||w(f,t)&&0)))),await o.delete(f),await o.put(g)},T=(t,a,...e)=>D(t,a,o(S(e),1,-1)),v=async(t,a,e=null)=>new Response(e,{status:a,headers:t.config.responseHeaders??b});class O{constructor(t){this.party=t,this.config={}}async onRequest(t){const e=this.config.storePath??"/store";if(new URL(t.url).pathname.endsWith(e)){const e=await x(this),n=await t.text();return"PUT"==t.method?e?v(this,205):(await C(this,d(n),!0,t),v(this,201)):v(this,200,e?S(await(async t=>{const e={},n={},r=t.config.storagePrefix??a;return P(await t.party.storage.list(),((t,a)=>c(m(r,t),(([t,r])=>{if(t==s){const[t,s,n]=d("["+r+"]");p(p(e,t,f),s,f)[n]=a}else"v"==t&&(n[r]=a)})))),[e,n]})(this)):a)}return v(this,404)}async onMessage(t,e){const s=this.config.messagePrefix??a;await c(m(s,t,1),(async([t,a])=>{"s"==t&&await x(this)&&(await C(this,a,!1,e),this.party.broadcast(D(s,"s",a)))}))}canSetTable(t,a,e){return!0}canDelTable(t,a){return!0}canSetRow(t,a,e,s){return!0}canDelRow(t,a,e){return!0}canSetCell(t,a,e,s,n,r,i){return!0}canDelCell(t,a,e,s){return!0}canSetValue(t,a,e,s,n){return!0}canDelValue(t,a){return!0}}export{O as TinyBasePartyKitServer};
1
+ const t=t=>typeof t,e=t(""),a="t",s=(t,e)=>t.startsWith(e),n=Promise,r=t=>null==t,i=(t,e,a)=>r(t)?a?.():e(t),c=(t,e,a)=>t.slice(e,a),o=t=>t.length,l=async t=>n.all(t),h=(t,e)=>t.map(e),g=(t,...e)=>t.push(...e),u=Object,w=(t=[])=>u.fromEntries(t),f=(t,e)=>h(u.entries(t),(([t,a])=>e(a,t))),y=(t,e,a)=>(((t,e)=>!r(((t,e)=>i(t,(t=>t[e])))(t,e)))(t,e)||(t[e]=a()),t[e]),p=t=>JSON.stringify(t,((t,e)=>e instanceof Map?u.fromEntries([...e]):e)),S=JSON.parse,d=(a,s,n)=>a+s+(t(n)==e?n:p(n)),P=(t,e,a)=>{const n=o(t);return s(e,t)?[e[n],(a?S:String)(c(e,n+1))]:void 0},m=(t,e)=>((t,e)=>t?.forEach(e))(t,((t,a)=>e(a,t))),x="hasStore",D=w(h(["Origin","Methods","Headers"],(t=>["Access-Control-Allow-"+t,"*"]))),R=async(t,e="")=>!!await t.get(e+x),b=async(t,e="")=>{const s={},n={};return m(await t.list(),((t,r)=>i(P(e,t),(([t,e])=>{if(t==a){const[t,a,n]=S("["+e+"]");y(y(s,t,w),a,w)[n]=r}else"v"==t&&(n[e]=r)})))),[s,n]},C=async(t,e,n,i)=>{const c=t.party.storage,h=t.config.storagePrefix,u={[h+x]:1},w=[],y=[];await l(f(e[0],(async(e,s)=>r(e)?!n&&t.canDelTable(s,i)&&((t,...e)=>t.unshift(...e))(y,T(h,a,s)):t.canSetTable(s,n,i)&&await l(f(e,(async(e,o)=>r(e)?!n&&t.canDelRow(s,o,i)&&g(y,T(h,a,s,o)):t.canSetRow(s,o,n,i)&&await l(f(e,(async(e,l)=>{const f=[s,o,l],y=T(h,a,...f);r(e)?!n&&t.canDelCell(...f,i)&&g(w,y):t.canSetCell(...f,e,n,i,await c.get(y))&&(u[y]=e)}))))))))),await l(f(e[1],(async(e,a)=>{const s=h+"v"+a;r(e)?!n&&t.canDelValue(a,i)&&g(w,s):t.canSetValue(a,e,n,i,await c.get(s))&&(u[s]=e)}))),0!=o(y)&&m(await c.list(),(t=>y.every((e=>!s(t,e)||g(w,t)&&0)))),await c.delete(w),await c.put(u)},T=(t,e,...a)=>d(t,e,c(p(a),1,-1)),v=async(t,e,a=null)=>new Response(a,{status:e,headers:t.config.responseHeaders});class O{constructor(t){this.party=t,this.config={},this.config.storePath??="/store",this.config.messagePrefix??="",this.config.storagePrefix??="",this.config.responseHeaders??=D}async onRequest(t){const{party:{storage:e},config:{storePath:a,storagePrefix:s}}=this;if(new URL(t.url).pathname.endsWith(a)){const a=await R(e,s),n=await t.text();return"PUT"==t.method?a?v(this,205):(await C(this,S(n),!0,t),v(this,201)):v(this,200,a?p(await b(e,s)):"")}return v(this,404)}async onMessage(t,e){const{party:{storage:a,broadcast:s},config:{messagePrefix:n,storagePrefix:r}}=this;await i(P(n,t,1),(async([t,i])=>{"s"==t&&await R(a,r)&&(await C(this,i,!1,e),s(d(n,"s",i),[e.id]))}))}canSetTable(t,e,a){return!0}canDelTable(t,e){return!0}canSetRow(t,e,a,s){return!0}canDelRow(t,e,a){return!0}canSetCell(t,e,a,s,n,r,i){return!0}canDelCell(t,e,a,s){return!0}canSetValue(t,e,a,s,n){return!0}canDelValue(t,e){return!0}}export{O as TinyBasePartyKitServer,R as hasStoreInStorage,b as loadStoreFromStorage};
@@ -18,8 +18,15 @@
18
18
  * @since 4.3.0
19
19
  */
20
20
 
21
- import {Cell, CellOrUndefined, Value, ValueOrUndefined} from '../store';
22
- import {Connection, Party, Request, Server} from 'partykit/server';
21
+ import {
22
+ Cell,
23
+ CellOrUndefined,
24
+ Tables,
25
+ Value,
26
+ ValueOrUndefined,
27
+ Values,
28
+ } from '../store';
29
+ import {Connection, Party, Request, Server, Storage} from 'partykit/server';
23
30
  import {Id} from '../common';
24
31
 
25
32
  /**
@@ -517,3 +524,52 @@ export class TinyBasePartyKitServer implements Server {
517
524
  */
518
525
  canDelValue(valueId: Id, connection: Connection): boolean;
519
526
  }
527
+
528
+ /**
529
+ * The hasStoreInStorage function returns a boolean indicating whether durable
530
+ * PartyKit storage contains a serialized Store.
531
+ *
532
+ * This is intended for specialist applications that require the ability to
533
+ * inspect or load a TinyBase Store from a server's storage outside of the
534
+ * normal context of a TinyBasePartyKitServer.
535
+ *
536
+ * The function is asynchronous, so you should use the `await` keyword or handle
537
+ * the result as a promise.
538
+ * @param storage A reference to the storage object, as would normally be
539
+ * accessible from the `TinyBasePartyKitServer.party` object.
540
+ * @param storagePrefix An optional prefix used before all the keys in the
541
+ * server's durable storage, to match the equivalent property in the server's
542
+ * TinyBasePartyKitServerConfig.
543
+ * @returns A promised boolean indicating whether a Store is present in the
544
+ * storage.
545
+ * @category Storage
546
+ * @since v4.4.1
547
+ */
548
+ export function hasStoreInStorage(
549
+ storage: Storage,
550
+ storagePrefix?: string,
551
+ ): Promise<boolean>;
552
+
553
+ /**
554
+ * The loadStoreFromStorage function returns the content of a Store from durable
555
+ * PartyKit storage.
556
+ *
557
+ * This is intended for specialist applications that require the ability to
558
+ * inspect or load a TinyBase Store from a server's storage outside of the
559
+ * normal context of a TinyBasePartyKitServer.
560
+ *
561
+ * The function is asynchronous, so you should use the `await` keyword or handle
562
+ * the result as a promise.
563
+ * @param storage A reference to the storage object, as would normally be
564
+ * accessible from the `TinyBasePartyKitServer.party` object.
565
+ * @param storagePrefix An optional prefix used before all the keys in the
566
+ * server's durable storage, to match the equivalent property in the server's
567
+ * TinyBasePartyKitServerConfig.
568
+ * @returns A promised array of a Tables object and a Values object.
569
+ * @category Storage
570
+ * @since v4.4.1
571
+ */
572
+ export function loadStoreFromStorage(
573
+ storage: Storage,
574
+ storagePrefix?: string,
575
+ ): Promise<[Tables, Values]>;
@@ -55,8 +55,13 @@ export interface RemotePersister extends Persister {
55
55
  * the `POST` method (to send the Store JSON to save) respectively.
56
56
  *
57
57
  * For when you choose to enable automatic loading for the Persister (with the
58
- * startAutoLoad method), it will poll the loadUrl for changes. The
58
+ * startAutoLoad method), it will poll the loadUrl for changes, using the
59
+ * `ETag` HTTP header to identify if things have changed. The
59
60
  * `autoLoadIntervalSeconds` method is used to indicate how often to do this.
61
+ *
62
+ * If you are implementing the server portion of this functionality yourself,
63
+ * remember to ensure that the `ETag` header changes every time the server's
64
+ * Store content does - otherwise changes will not be detected by the client.
60
65
  * @param store The Store to persist.
61
66
  * @param loadUrl The endpoint that supports a `GET` method to load JSON.
62
67
  * @param saveUrl The endpoint that supports a `POST` method to save JSON.
@@ -23,8 +23,11 @@ import {
23
23
  CellOrUndefined,
24
24
  NoTablesSchema,
25
25
  NoValuesSchema,
26
+ OptionalSchemas,
27
+ Tables,
26
28
  Value,
27
29
  ValueOrUndefined,
30
+ Values,
28
31
  } from '../store';
29
32
  import {Connection, Party, Request, Server} from 'partykit/server';
30
33
  import {Id} from '../common';
@@ -550,3 +553,61 @@ export class TinyBasePartyKitServer implements Server {
550
553
  */
551
554
  canDelValue(valueId: Id, connection: Connection): boolean;
552
555
  }
556
+
557
+ /**
558
+ * The hasStoreInStorage function returns a boolean indicating whether durable
559
+ * PartyKit storage contains a serialized Store.
560
+ *
561
+ * This is intended for specialist applications that require the ability to
562
+ * inspect or load a TinyBase Store from a server's storage outside of the
563
+ * normal context of a TinyBasePartyKitServer.
564
+ *
565
+ * The function is asynchronous, so you should use the `await` keyword or handle
566
+ * the result as a promise.
567
+ * @param storage A reference to the storage object, as would normally be
568
+ * accessible from the `TinyBasePartyKitServer.party` object.
569
+ * @param storagePrefix An optional prefix used before all the keys in the
570
+ * server's durable storage, to match the equivalent property in the server's
571
+ * TinyBasePartyKitServerConfig.
572
+ * @returns A promised boolean indicating whether a Store is present in the
573
+ * storage.
574
+ * @category Storage
575
+ * @since v4.4.1
576
+ */
577
+ export function hasStoreInStorage(
578
+ storage: Storage,
579
+ storagePrefix?: string,
580
+ ): Promise<boolean>;
581
+
582
+ /**
583
+ * The loadStoreFromStorage function returns the content of a Store from durable
584
+ * PartyKit storage.
585
+ *
586
+ * This has schema-based typing. The following is a simplified representation:
587
+ *
588
+ * ```ts override
589
+ * loadStoreFromStorage(
590
+ * storage: Storage,
591
+ * storagePrefix?: string,
592
+ * ): Promise<[Tables, Values]>;
593
+ * ```
594
+ *
595
+ * This is intended for specialist applications that require the ability to
596
+ * inspect or load a TinyBase Store from a server's storage outside of the
597
+ * normal context of a TinyBasePartyKitServer.
598
+ *
599
+ * The function is asynchronous, so you should use the `await` keyword or handle
600
+ * the result as a promise.
601
+ * @param storage A reference to the storage object, as would normally be
602
+ * accessible from the `TinyBasePartyKitServer.party` object.
603
+ * @param storagePrefix An optional prefix used before all the keys in the
604
+ * server's durable storage, to match the equivalent property in the server's
605
+ * TinyBasePartyKitServerConfig.
606
+ * @returns A promised array of a Tables object and a Values object.
607
+ * @category Storage
608
+ * @since v4.4.1
609
+ */
610
+ export function loadStoreFromStorage<Schemas extends OptionalSchemas>(
611
+ storage: Storage,
612
+ storagePrefix?: string,
613
+ ): Promise<[Tables<Schemas[0]>, Values<Schemas[1]>]>;
@@ -68,8 +68,13 @@ export interface RemotePersister<Schemas extends OptionalSchemas>
68
68
  * the `POST` method (to send the Store JSON to save) respectively.
69
69
  *
70
70
  * For when you choose to enable automatic loading for the Persister (with the
71
- * startAutoLoad method), it will poll the loadUrl for changes. The
71
+ * startAutoLoad method), it will poll the loadUrl for changes, using the
72
+ * `ETag` HTTP header to identify if things have changed. The
72
73
  * `autoLoadIntervalSeconds` method is used to indicate how often to do this.
74
+ *
75
+ * If you are implementing the server portion of this functionality yourself,
76
+ * remember to ensure that the `ETag` header changes every time the server's
77
+ * Store content does - otherwise changes will not be detected by the client.
73
78
  * @param store The Store to persist.
74
79
  * @param loadUrl The endpoint that supports a `GET` method to load JSON.
75
80
  * @param saveUrl The endpoint that supports a `POST` method to save JSON.
@@ -1 +1 @@
1
- var t,e;t=this,e=function(t){"use strict";const e=t=>typeof t,a="",s=e(a),n="t",i=(t,e)=>t.startsWith(e),r=Promise,o=t=>null==t,c=(t,e,a)=>o(t)?a?.():e(t),l=(t,e,a)=>t.slice(e,a),f=t=>t.length,u=async t=>r.all(t),h=(t,e)=>t.map(e),y=(t,...e)=>t.push(...e),w=Object,g=(t=[])=>w.fromEntries(t),p=(t,e)=>h(w.entries(t),(([t,a])=>e(a,t))),d=(t,e,a)=>(((t,e)=>!o(((t,e)=>c(t,(t=>t[e])))(t,e)))(t,e)||(t[e]=a()),t[e]),S=t=>JSON.stringify(t,((t,e)=>e instanceof Map?w.fromEntries([...e]):e)),P=JSON.parse,b=(t,a,n)=>t+a+(e(n)==s?n:S(n)),m=(t,e,a)=>{const s=f(t);return i(e,t)?[e[s],(a?P:String)(l(e,s+1))]:void 0},T=(t,e)=>((t,e)=>t?.forEach(e))(t,((t,a)=>e(a,t))),x="hasStore",D=g(h(["Origin","Methods","Headers"],(t=>["Access-Control-Allow-"+t,"*"]))),v=async t=>await t.party.storage.get((t.config.storagePrefix??a)+x),R=async(t,e,s,r)=>{const c=t.party.storage,l=t.config.storagePrefix??a,h={[l+x]:1},w=[],g=[];await u(p(e[0],(async(e,a)=>o(e)?!s&&t.canDelTable(a,r)&&((t,...e)=>t.unshift(...e))(g,C(l,n,a)):t.canSetTable(a,s,r)&&await u(p(e,(async(e,i)=>o(e)?!s&&t.canDelRow(a,i,r)&&y(g,C(l,n,a,i)):t.canSetRow(a,i,s,r)&&await u(p(e,(async(e,f)=>{const u=[a,i,f],g=C(l,n,...u);o(e)?!s&&t.canDelCell(...u,r)&&y(w,g):t.canSetCell(...u,e,s,r,await c.get(g))&&(h[g]=e)}))))))))),await u(p(e[1],(async(e,a)=>{const n=l+"v"+a;o(e)?!s&&t.canDelValue(a,r)&&y(w,n):t.canSetValue(a,e,s,r,await c.get(n))&&(h[n]=e)}))),0!=f(g)&&T(await c.list(),(t=>g.every((e=>!i(t,e)||y(w,t)&&0)))),await c.delete(w),await c.put(h)},C=(t,e,...a)=>b(t,e,l(S(a),1,-1)),O=async(t,e,a=null)=>new Response(a,{status:e,headers:t.config.responseHeaders??D});t.TinyBasePartyKitServer=class{constructor(t){this.party=t,this.config={}}async onRequest(t){const e=this.config.storePath??"/store";if(new URL(t.url).pathname.endsWith(e)){const e=await v(this),s=await t.text();return"PUT"==t.method?e?O(this,205):(await R(this,P(s),!0,t),O(this,201)):O(this,200,e?S(await(async t=>{const e={},s={},i=t.config.storagePrefix??a;return T(await t.party.storage.list(),((t,a)=>c(m(i,t),(([t,i])=>{if(t==n){const[t,s,n]=P("["+i+"]");d(d(e,t,g),s,g)[n]=a}else"v"==t&&(s[i]=a)})))),[e,s]})(this)):a)}return O(this,404)}async onMessage(t,e){const s=this.config.messagePrefix??a;await c(m(s,t,1),(async([t,a])=>{"s"==t&&await v(this)&&(await R(this,a,!1,e),this.party.broadcast(b(s,"s",a)))}))}canSetTable(t,e,a){return!0}canDelTable(t,e){return!0}canSetRow(t,e,a,s){return!0}canDelRow(t,e,a){return!0}canSetCell(t,e,a,s,n,i,r){return!0}canDelCell(t,e,a,s){return!0}canSetValue(t,e,a,s,n){return!0}canDelValue(t,e){return!0}}},"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).TinyBasePersisterPartyKitServer={});
1
+ var e,t;e=this,t=function(e){"use strict";const t=e=>typeof e,a=t(""),s="t",n=(e,t)=>e.startsWith(t),r=Promise,i=e=>null==e,o=(e,t,a)=>i(e)?a?.():t(e),c=(e,t,a)=>e.slice(t,a),l=e=>e.length,f=async e=>r.all(e),h=(e,t)=>e.map(t),u=(e,...t)=>e.push(...t),g=Object,y=(e=[])=>g.fromEntries(e),w=(e,t)=>h(g.entries(e),(([e,a])=>t(a,e))),d=(e,t,a)=>(((e,t)=>!i(((e,t)=>o(e,(e=>e[t])))(e,t)))(e,t)||(e[t]=a()),e[t]),p=e=>JSON.stringify(e,((e,t)=>t instanceof Map?g.fromEntries([...t]):t)),S=JSON.parse,P=(e,s,n)=>e+s+(t(n)==a?n:p(n)),m=(e,t,a)=>{const s=l(e);return n(t,e)?[t[s],(a?S:String)(c(t,s+1))]:void 0},x=(e,t)=>((e,t)=>e?.forEach(t))(e,((e,a)=>t(a,e))),b="hasStore",T=y(h(["Origin","Methods","Headers"],(e=>["Access-Control-Allow-"+e,"*"]))),D=async(e,t="")=>!!await e.get(t+b),v=async(e,t="")=>{const a={},n={};return x(await e.list(),((e,r)=>o(m(t,e),(([e,t])=>{if(e==s){const[e,s,n]=S("["+t+"]");d(d(a,e,y),s,y)[n]=r}else"v"==e&&(n[t]=r)})))),[a,n]},R=async(e,t,a,r)=>{const o=e.party.storage,c=e.config.storagePrefix,h={[c+b]:1},g=[],y=[];await f(w(t[0],(async(t,n)=>i(t)?!a&&e.canDelTable(n,r)&&((e,...t)=>e.unshift(...t))(y,C(c,s,n)):e.canSetTable(n,a,r)&&await f(w(t,(async(t,l)=>i(t)?!a&&e.canDelRow(n,l,r)&&u(y,C(c,s,n,l)):e.canSetRow(n,l,a,r)&&await f(w(t,(async(t,f)=>{const y=[n,l,f],w=C(c,s,...y);i(t)?!a&&e.canDelCell(...y,r)&&u(g,w):e.canSetCell(...y,t,a,r,await o.get(w))&&(h[w]=t)}))))))))),await f(w(t[1],(async(t,s)=>{const n=c+"v"+s;i(t)?!a&&e.canDelValue(s,r)&&u(g,n):e.canSetValue(s,t,a,r,await o.get(n))&&(h[n]=t)}))),0!=l(y)&&x(await o.list(),(e=>y.every((t=>!n(e,t)||u(g,e)&&0)))),await o.delete(g),await o.put(h)},C=(e,t,...a)=>P(e,t,c(p(a),1,-1)),O=async(e,t,a=null)=>new Response(a,{status:t,headers:e.config.responseHeaders});e.TinyBasePartyKitServer=class{constructor(e){this.party=e,this.config={},this.config.storePath??="/store",this.config.messagePrefix??="",this.config.storagePrefix??="",this.config.responseHeaders??=T}async onRequest(e){const{party:{storage:t},config:{storePath:a,storagePrefix:s}}=this;if(new URL(e.url).pathname.endsWith(a)){const a=await D(t,s),n=await e.text();return"PUT"==e.method?a?O(this,205):(await R(this,S(n),!0,e),O(this,201)):O(this,200,a?p(await v(t,s)):"")}return O(this,404)}async onMessage(e,t){const{party:{storage:a,broadcast:s},config:{messagePrefix:n,storagePrefix:r}}=this;await o(m(n,e,1),(async([e,i])=>{"s"==e&&await D(a,r)&&(await R(this,i,!1,t),s(P(n,"s",i),[t.id]))}))}canSetTable(e,t,a){return!0}canDelTable(e,t){return!0}canSetRow(e,t,a,s){return!0}canDelRow(e,t,a){return!0}canSetCell(e,t,a,s,n,r,i){return!0}canDelCell(e,t,a,s){return!0}canSetValue(e,t,a,s,n){return!0}canDelValue(e,t){return!0}},e.hasStoreInStorage=D,e.loadStoreFromStorage=v},"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).TinyBasePersisterPartyKitServer={});
@@ -1 +1 @@
1
- var e,t;e=this,t=function(e){"use strict";const t=e=>typeof e,n="",r=t(n),l="t",i=(e,t)=>e.startsWith(t),o=Promise,s=e=>null==e,a=(e,t,n)=>s(e)?null==n?void 0:n():t(e),u=(e,t,n)=>e.slice(t,n),c=e=>e.length,d=e=>{return t=function*(){return o.all(e)},new Promise(((e,n)=>{var r=e=>{try{i(t.next(e))}catch(e){n(e)}},l=e=>{try{i(t.throw(e))}catch(e){n(e)}},i=t=>t.done?e(t.value):Promise.resolve(t.value).then(r,l);i((t=t.apply(void 0,null)).next())}));var t},f=(e,t)=>e.map(t),h=(e,...t)=>e.push(...t),y=Object,v=(e=[])=>y.fromEntries(e),p=(e,t)=>f(y.entries(e),(([e,n])=>t(n,e))),g=(e,t,n)=>(((e,t)=>!s(((e,t)=>a(e,(e=>e[t])))(e,t)))(e,t)||(e[t]=n()),e[t]),P=e=>JSON.stringify(e,((e,t)=>t instanceof Map?y.fromEntries([...t]):t)),S=JSON.parse,m=(e,n,l)=>e+n+(t(l)==r?l:P(l)),x=(e,t,n)=>{const r=c(e);return i(t,e)?[t[r],(n?S:String)(u(t,r+1))]:void 0},w=(e,t)=>((e,t)=>null==e?void 0:e.forEach(t))(e,((e,n)=>t(n,e)));var b=(e,t,n)=>new Promise(((r,l)=>{var i=e=>{try{s(n.next(e))}catch(e){l(e)}},o=e=>{try{s(n.throw(e))}catch(e){l(e)}},s=e=>e.done?r(e.value):Promise.resolve(e.value).then(i,o);s((n=n.apply(e,t)).next())}));const T="hasStore",D=v(f(["Origin","Methods","Headers"],(e=>["Access-Control-Allow-"+e,"*"]))),R=e=>b(void 0,null,(function*(){var t;return yield e.party.storage.get((null!=(t=e.config.storagePrefix)?t:n)+T)})),C=(e,t,r,o)=>b(void 0,null,(function*(){var a;const u=e.party.storage,f=null!=(a=e.config.storagePrefix)?a:n,y={[f+T]:1},v=[],g=[];yield d(p(t[0],((t,n)=>b(void 0,null,(function*(){return s(t)?!r&&e.canDelTable(n,o)&&((e,...t)=>e.unshift(...t))(g,O(f,l,n)):e.canSetTable(n,r,o)&&(yield d(p(t,((t,i)=>b(void 0,null,(function*(){return s(t)?!r&&e.canDelRow(n,i,o)&&h(g,O(f,l,n,i)):e.canSetRow(n,i,r,o)&&(yield d(p(t,((t,a)=>b(void 0,null,(function*(){const c=[n,i,a],d=O(f,l,...c);s(t)?!r&&e.canDelCell(...c,o)&&h(v,d):e.canSetCell(...c,t,r,o,yield u.get(d))&&(y[d]=t)}))))))}))))))}))))),yield d(p(t[1],((t,n)=>b(void 0,null,(function*(){const l=f+"v"+n;s(t)?!r&&e.canDelValue(n,o)&&h(v,l):e.canSetValue(n,t,r,o,yield u.get(l))&&(y[l]=t)}))))),0!=c(g)&&w(yield u.list(),(e=>g.every((t=>!i(e,t)||h(v,e)&&0)))),yield u.delete(v),yield u.put(y)})),O=(e,t,...n)=>m(e,t,u(P(n),1,-1)),V=(e,t,n=null)=>b(void 0,null,(function*(){var r;return new Response(n,{status:t,headers:null!=(r=e.config.responseHeaders)?r:D})}));e.TinyBasePartyKitServer=class{constructor(e){this.party=e,this.config={}}onRequest(e){return b(this,null,(function*(){var t;const r=null!=(t=this.config.storePath)?t:"/store";if(new URL(e.url).pathname.endsWith(r)){const t=yield R(this),r=yield e.text();return"PUT"==e.method?t?V(this,205):(yield C(this,S(r),!0,e),V(this,201)):V(this,200,t?P(yield(i=this,b(void 0,null,(function*(){var e;const t={},r={},o=null!=(e=i.config.storagePrefix)?e:n;return w(yield i.party.storage.list(),((e,n)=>a(x(o,e),(([e,i])=>{if(e==l){const[e,r,l]=S("["+i+"]");g(g(t,e,v),r,v)[l]=n}else"v"==e&&(r[i]=n)})))),[t,r]})))):n)}var i;return V(this,404)}))}onMessage(e,t){return b(this,null,(function*(){var r;const l=null!=(r=this.config.messagePrefix)?r:n;yield a(x(l,e,1),(e=>b(this,[e],(function*([e,n]){"s"==e&&(yield R(this))&&(yield C(this,n,!1,t),this.party.broadcast(m(l,"s",n)))}))))}))}canSetTable(e,t,n){return!0}canDelTable(e,t){return!0}canSetRow(e,t,n,r){return!0}canDelRow(e,t,n){return!0}canSetCell(e,t,n,r,l,i,o){return!0}canDelCell(e,t,n,r){return!0}canSetValue(e,t,n,r,l){return!0}canDelValue(e,t){return!0}}},"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).TinyBasePersisterPartyKitServer={});
1
+ var e,t;e=this,t=function(e){"use strict";const t=e=>typeof e,n=t(""),r="t",i=(e,t)=>e.startsWith(t),o=Promise,s=e=>null==e,l=(e,t,n)=>s(e)?null==n?void 0:n():t(e),a=(e,t,n)=>e.slice(t,n),c=e=>e.length,u=e=>{return t=function*(){return o.all(e)},new Promise(((e,n)=>{var r=e=>{try{o(t.next(e))}catch(e){n(e)}},i=e=>{try{o(t.throw(e))}catch(e){n(e)}},o=t=>t.done?e(t.value):Promise.resolve(t.value).then(r,i);o((t=t.apply(void 0,null)).next())}));var t},d=(e,t)=>e.map(t),f=(e,...t)=>e.push(...t),h=Object,y=(e=[])=>h.fromEntries(e),g=(e,t)=>d(h.entries(e),(([e,n])=>t(n,e))),v=(e,t,n)=>(((e,t)=>!s(((e,t)=>l(e,(e=>e[t])))(e,t)))(e,t)||(e[t]=n()),e[t]),p=e=>JSON.stringify(e,((e,t)=>t instanceof Map?h.fromEntries([...t]):t)),P=JSON.parse,S=(e,r,i)=>e+r+(t(i)==n?i:p(i)),m=(e,t,n)=>{const r=c(e);return i(t,e)?[t[r],(n?P:String)(a(t,r+1))]:void 0},x=(e,t)=>((e,t)=>null==e?void 0:e.forEach(t))(e,((e,n)=>t(n,e)));var w=(e,t,n)=>new Promise(((r,i)=>{var o=e=>{try{l(n.next(e))}catch(e){i(e)}},s=e=>{try{l(n.throw(e))}catch(e){i(e)}},l=e=>e.done?r(e.value):Promise.resolve(e.value).then(o,s);l((n=n.apply(e,t)).next())}));const b="hasStore",T=y(d(["Origin","Methods","Headers"],(e=>["Access-Control-Allow-"+e,"*"]))),D=(e,...t)=>w(void 0,[e,...t],(function*(e,t=""){return!!(yield e.get(t+b))})),R=(e,...t)=>w(void 0,[e,...t],(function*(e,t=""){const n={},i={};return x(yield e.list(),((e,o)=>l(m(t,e),(([e,t])=>{if(e==r){const[e,r,i]=P("["+t+"]");v(v(n,e,y),r,y)[i]=o}else"v"==e&&(i[t]=o)})))),[n,i]})),C=(e,t,n,o)=>w(void 0,null,(function*(){const l=e.party.storage,a=e.config.storagePrefix,d={[a+b]:1},h=[],y=[];yield u(g(t[0],((t,i)=>w(void 0,null,(function*(){return s(t)?!n&&e.canDelTable(i,o)&&((e,...t)=>e.unshift(...t))(y,H(a,r,i)):e.canSetTable(i,n,o)&&(yield u(g(t,((t,c)=>w(void 0,null,(function*(){return s(t)?!n&&e.canDelRow(i,c,o)&&f(y,H(a,r,i,c)):e.canSetRow(i,c,n,o)&&(yield u(g(t,((t,u)=>w(void 0,null,(function*(){const y=[i,c,u],g=H(a,r,...y);s(t)?!n&&e.canDelCell(...y,o)&&f(h,g):e.canSetCell(...y,t,n,o,yield l.get(g))&&(d[g]=t)}))))))}))))))}))))),yield u(g(t[1],((t,r)=>w(void 0,null,(function*(){const i=a+"v"+r;s(t)?!n&&e.canDelValue(r,o)&&f(h,i):e.canSetValue(r,t,n,o,yield l.get(i))&&(d[i]=t)}))))),0!=c(y)&&x(yield l.list(),(e=>y.every((t=>!i(e,t)||f(h,e)&&0)))),yield l.delete(h),yield l.put(d)})),H=(e,t,...n)=>S(e,t,a(p(n),1,-1)),O=(e,t,n=null)=>w(void 0,null,(function*(){return new Response(n,{status:t,headers:e.config.responseHeaders})}));e.TinyBasePartyKitServer=class{constructor(e){var t,n,r,i;this.party=e,this.config={},null!=(t=this.config).storePath||(t.storePath="/store"),null!=(n=this.config).messagePrefix||(n.messagePrefix=""),null!=(r=this.config).storagePrefix||(r.storagePrefix=""),null!=(i=this.config).responseHeaders||(i.responseHeaders=T)}onRequest(e){return w(this,null,(function*(){const{party:{storage:t},config:{storePath:n,storagePrefix:r}}=this;if(new URL(e.url).pathname.endsWith(n)){const n=yield D(t,r),i=yield e.text();return"PUT"==e.method?n?O(this,205):(yield C(this,P(i),!0,e),O(this,201)):O(this,200,n?p(yield R(t,r)):"")}return O(this,404)}))}onMessage(e,t){return w(this,null,(function*(){const{party:{storage:n,broadcast:r},config:{messagePrefix:i,storagePrefix:o}}=this;yield l(m(i,e,1),(e=>w(this,[e],(function*([e,s]){"s"==e&&(yield D(n,o))&&(yield C(this,s,!1,t),r(S(i,"s",s),[t.id]))}))))}))}canSetTable(e,t,n){return!0}canDelTable(e,t){return!0}canSetRow(e,t,n,r){return!0}canDelRow(e,t,n){return!0}canSetCell(e,t,n,r,i,o,s){return!0}canDelCell(e,t,n,r){return!0}canSetValue(e,t,n,r,i){return!0}canDelValue(e,t){return!0}},e.hasStoreInStorage=D,e.loadStoreFromStorage=R},"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).TinyBasePersisterPartyKitServer={});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tinybase",
3
- "version": "4.4.0",
3
+ "version": "4.4.1",
4
4
  "author": "jamesgpearce",
5
5
  "repository": "github:tinyplex/tinybase",
6
6
  "license": "MIT",
package/readme.md CHANGED
@@ -1,4 +1,4 @@
1
- <section id="hero"><h2 id="the-reactive-data-store-for-local-first-apps">The <em>reactive</em> data store for <span>local-first apps</span>.</h2><p id="copy">Build blisteringly fast web apps that work both online and offline. Manage your state locally, synchronize it to the cloud when you need to, or even make it collaborative. But, most importantly... have fun building stuff again!</p></section><p><a href="https://tinybase.org/guides/releases/#v4-4"><em>NEW!</em> v4.4 release</a> <span id="one-with">&quot;The One With More Listeners&quot;</span></p><p><a class="start" href="https://tinybase.org/guides/the-basics/getting-started/">Get started</a></p><p><a href="https://tinybase.org/demos/">Try the demos</a></p><p><a href="https://tinybase.org/api/store/interfaces/store/store/">Read the docs</a></p><hr><ul><li>Manage <a href="#start-with-a-simple-key-value-store">key-value data</a>, <a href="#level-up-to-use-tabular-data">tabular data</a> - or both - with optional <a href="#apply-schemas-to-tables-values">schematization</a> to model your app&#x27;s data structures.</li><li><a href="#register-granular-listeners">Flexibly reactive</a> to reconciled updates, so you only spend rendering cycles on things that change.</li><li><a href="#build-complex-queries-with-tinyql">Powerful query engine</a> to select, join, filter, group, sort and paginate data - reactively - and without SQL.</li><li>Built-in <a href="#create-indexes-for-fast-lookups">indexing</a>, <a href="#define-metrics-and-aggregations">metric aggregation</a>, <a href="#model-table-relationships">tabular relationships</a> - and even an <a href="#set-checkpoints-for-an-undo-stack">undo stack</a> for your app state.</li><li>Create <a href="#type-definitions-orm-like-apis">type definitions &amp; ORM-like APIs</a>, from schema or inference. <a href="#an-inspector-for-your-data">Inspect your data</a> (<em>new!</em>) directly in the browser.</li><li>Easily <a href="#persist-to-storage-sqlite-crdts">sync your data</a> to browser <a href="https://tinybase.org/api/persister-browser">storage</a>, <a href="https://tinybase.org/api/persister-indexed-db/">IndexedDB</a>, <a href="https://tinybase.org/guides/schemas-and-persistence/database-persistence/">SQLite</a>, <a href="https://tinybase.org/guides/schemas-and-persistence/synchronizing-data/">CRDTs</a>, and (<em>new!</em>) <a href="https://tinybase.org/api/persister-partykit-client/">PartyKit</a>.</li><li>Optional <a href="#call-hooks-to-bind-to-data">bindings to React</a> and (<em>new!</em>) <a href="#pre-built-reactive-components">pre-built components</a> that let you easily build fully reactive user interfaces.</li><li>Tiny by name, tiny by nature: <a href="#did-we-say-tiny">5.0kB - 9.4kB</a>, zero dependencies. <a href="#well-tested-and-documented">100% tested</a>, <a href="https://tinybase.org/guides/the-basics/getting-started/">fully documented</a>, and of course, <a href="https://github.com/tinyplex/tinybase">open source</a>!</li></ul><hr><section id="friends"><h2 id="tinybase-works-great-on-its-own-but-also-plays-well-with-friends">Tinybase works great on its own, but also plays well with friends!</h2><div><a href="https://tinybase.org/guides/building-uis/getting-started-with-ui-react"><img width="48" src="https://tinybase.org/react.svg"> React</a></div><div><a href="https://tinybase.org/api/persister-partykit-client"><img width="48" src="https://tinybase.org/partykit.svg"> PartyKit</a></div><div><a href="https://tinybase.org/guides/schemas-and-persistence/database-persistence"><img width="48" src="https://tinybase.org/sqlite.svg"> SQLite</a></div><div><a href="https://tinybase.org/api/persister-indexed-db/functions/creation/createindexeddbpersister"><img width="48" src="https://tinybase.org/indexeddb.svg"> IndexedDB</a></div><div><a href="https://tinybase.org/api/persister-yjs/functions/creation/createyjspersister"><img width="48" src="https://tinybase.org/yjs.svg"> YJS</a></div><div><a href="https://tinybase.org/api/persister-cr-sqlite-wasm"><img width="48" src="https://tinybase.org/crsqlite.png"> CR-SQLite</a></div><div><a href="https://tinybase.org/api/persister-automerge"><img width="48" src="https://tinybase.org/automerge.svg"> Automerge</a></div></section><hr><section id="follow"><a href="https://github.com/tinyplex/tinybase" target="_blank"><img src="https://img.shields.io/github/stars/tinyplex/tinybase?style=for-the-badge&amp;logo=GitHub&amp;logoColor=%23fff&amp;label=GitHub&amp;labelColor=%23d81b60&amp;color=%23333"> </a><a href="https://discord.com/invite/mGz3mevwP8" target="_blank"><img src="https://img.shields.io/discord/1027918215323590676?style=for-the-badge&amp;logo=discord&amp;logoColor=%23fff&amp;label=Discord&amp;labelColor=%233131e8&amp;color=%23333"> </a><a href="https://twitter.com/tinybasejs" target="_blank"><img src="https://img.shields.io/twitter/follow/tinybasejs?style=for-the-badge&amp;logo=x&amp;logoColor=%23fff&amp;label=Twitter&amp;labelColor=%23333&amp;color=%23333"></a><br><a href="https://github.com/tinyplex/tinybase/discussions" target="_blank"><img src="https://img.shields.io/github/discussions/tinyplex/tinybase?style=for-the-badge&amp;logo=GitHub&amp;logoColor=%23fff&amp;label=Ideas&amp;labelColor=%23d81b60&amp;color=%23333"> </a><a href="https://github.com/tinyplex/tinybase/issues" target="_blank"><img src="https://img.shields.io/github/issues/tinyplex/tinybase?style=for-the-badge&amp;logo=GitHub&amp;logoColor=%23fff&amp;label=Issues&amp;labelColor=%23d81b60&amp;color=%23333"> </a><a href="#well-tested-and-documented"><img src="https://img.shields.io/badge/Tests-100%25-green?style=for-the-badge&amp;logo=jest&amp;logoColor=%23fff&amp;color=%23333&amp;labelColor=%2387c305"> </a><a href="https://www.npmjs.com/package/tinybase/v/4.4.0" target="_blank"><img src="https://img.shields.io/npm/v/tinybase?style=for-the-badge&amp;logo=npm&amp;logoColor=%23fff&amp;labelColor=%23bd0005&amp;color=%23333"></a></section><hr><section><h2 id="start-with-a-simple-key-value-store">Start with a simple key-value store.</h2><p>Creating a <a href="https://tinybase.org/api/store/interfaces/store/store/"><code>Store</code></a> requires just a simple call to the <a href="https://tinybase.org/api/store/functions/creation/createstore/"><code>createStore</code></a> function. Once you have one, you can easily set <a href="https://tinybase.org/api/store/type-aliases/store/values/"><code>Values</code></a> in it by unique <a href="https://tinybase.org/api/common/type-aliases/identity/id/"><code>Id</code></a>. And of course you can easily get them back out again.</p><p>Read more about using keyed value data in <a href="https://tinybase.org/guides/the-basics/">The Basics</a> guide.</p></section>
1
+ <section id="hero"><h2 id="the-reactive-data-store-for-local-first-apps">The <em>reactive</em> data store for <span>local-first apps</span>.</h2><p id="copy">Build blisteringly fast web apps that work both online and offline. Manage your state locally, synchronize it to the cloud when you need to, or even make it collaborative. But, most importantly... have fun building stuff again!</p></section><p><a href="https://tinybase.org/guides/releases/#v4-4"><em>NEW!</em> v4.4 release</a> <span id="one-with">&quot;The One With More Listeners&quot;</span></p><p><a class="start" href="https://tinybase.org/guides/the-basics/getting-started/">Get started</a></p><p><a href="https://tinybase.org/demos/">Try the demos</a></p><p><a href="https://tinybase.org/api/store/interfaces/store/store/">Read the docs</a></p><hr><ul><li>Manage <a href="#start-with-a-simple-key-value-store">key-value data</a>, <a href="#level-up-to-use-tabular-data">tabular data</a> - or both - with optional <a href="#apply-schemas-to-tables-values">schematization</a> to model your app&#x27;s data structures.</li><li><a href="#register-granular-listeners">Flexibly reactive</a> to reconciled updates, so you only spend rendering cycles on things that change.</li><li><a href="#build-complex-queries-with-tinyql">Powerful query engine</a> to select, join, filter, group, sort and paginate data - reactively - and without SQL.</li><li>Built-in <a href="#create-indexes-for-fast-lookups">indexing</a>, <a href="#define-metrics-and-aggregations">metric aggregation</a>, <a href="#model-table-relationships">tabular relationships</a> - and even an <a href="#set-checkpoints-for-an-undo-stack">undo stack</a> for your app state.</li><li>Create <a href="#type-definitions-orm-like-apis">type definitions &amp; ORM-like APIs</a>, from schema or inference. <a href="#an-inspector-for-your-data">Inspect your data</a> (<em>new!</em>) directly in the browser.</li><li>Easily <a href="#persist-to-storage-sqlite-crdts">sync your data</a> to browser <a href="https://tinybase.org/api/persister-browser">storage</a>, <a href="https://tinybase.org/api/persister-indexed-db/">IndexedDB</a>, <a href="https://tinybase.org/guides/schemas-and-persistence/database-persistence/">SQLite</a>, <a href="https://tinybase.org/guides/schemas-and-persistence/synchronizing-data/">CRDTs</a>, and (<em>new!</em>) <a href="https://tinybase.org/api/persister-partykit-client/">PartyKit</a>.</li><li>Optional <a href="#call-hooks-to-bind-to-data">bindings to React</a> and (<em>new!</em>) <a href="#pre-built-reactive-components">pre-built components</a> that let you easily build fully reactive user interfaces.</li><li>Tiny by name, tiny by nature: <a href="#did-we-say-tiny">5.0kB - 9.4kB</a>, zero dependencies. <a href="#well-tested-and-documented">100% tested</a>, <a href="https://tinybase.org/guides/the-basics/getting-started/">fully documented</a>, and of course, <a href="https://github.com/tinyplex/tinybase">open source</a>!</li></ul><hr><section id="friends"><h2 id="tinybase-works-great-on-its-own-but-also-plays-well-with-friends">Tinybase works great on its own, but also plays well with friends!</h2><div><a href="https://tinybase.org/guides/building-uis/getting-started-with-ui-react"><img width="48" src="https://tinybase.org/react.svg"> React</a></div><div><a href="https://tinybase.org/api/persister-partykit-client"><img width="48" src="https://tinybase.org/partykit.svg"> PartyKit</a></div><div><a href="https://tinybase.org/guides/schemas-and-persistence/database-persistence"><img width="48" src="https://tinybase.org/sqlite.svg"> SQLite</a></div><div><a href="https://tinybase.org/api/persister-indexed-db/functions/creation/createindexeddbpersister"><img width="48" src="https://tinybase.org/indexeddb.svg"> IndexedDB</a></div><div><a href="https://tinybase.org/api/persister-yjs/functions/creation/createyjspersister"><img width="48" src="https://tinybase.org/yjs.svg"> YJS</a></div><div><a href="https://tinybase.org/api/persister-cr-sqlite-wasm"><img width="48" src="https://tinybase.org/crsqlite.png"> CR-SQLite</a></div><div><a href="https://tinybase.org/api/persister-automerge"><img width="48" src="https://tinybase.org/automerge.svg"> Automerge</a></div></section><hr><section id="follow"><a href="https://github.com/tinyplex/tinybase" target="_blank"><img src="https://img.shields.io/github/stars/tinyplex/tinybase?style=for-the-badge&amp;logo=GitHub&amp;logoColor=%23fff&amp;label=GitHub&amp;labelColor=%23d81b60&amp;color=%23333"> </a><a href="https://discord.com/invite/mGz3mevwP8" target="_blank"><img src="https://img.shields.io/discord/1027918215323590676?style=for-the-badge&amp;logo=discord&amp;logoColor=%23fff&amp;label=Discord&amp;labelColor=%233131e8&amp;color=%23333"> </a><a href="https://twitter.com/tinybasejs" target="_blank"><img src="https://img.shields.io/twitter/follow/tinybasejs?style=for-the-badge&amp;logo=x&amp;logoColor=%23fff&amp;label=Twitter&amp;labelColor=%23333&amp;color=%23333"></a><br><a href="https://github.com/tinyplex/tinybase/discussions" target="_blank"><img src="https://img.shields.io/github/discussions/tinyplex/tinybase?style=for-the-badge&amp;logo=GitHub&amp;logoColor=%23fff&amp;label=Ideas&amp;labelColor=%23d81b60&amp;color=%23333"> </a><a href="https://github.com/tinyplex/tinybase/issues" target="_blank"><img src="https://img.shields.io/github/issues/tinyplex/tinybase?style=for-the-badge&amp;logo=GitHub&amp;logoColor=%23fff&amp;label=Issues&amp;labelColor=%23d81b60&amp;color=%23333"> </a><a href="#well-tested-and-documented"><img src="https://img.shields.io/badge/Tests-100%25-green?style=for-the-badge&amp;logo=jest&amp;logoColor=%23fff&amp;color=%23333&amp;labelColor=%2387c305"> </a><a href="https://www.npmjs.com/package/tinybase/v/4.4.1" target="_blank"><img src="https://img.shields.io/npm/v/tinybase?style=for-the-badge&amp;logo=npm&amp;logoColor=%23fff&amp;labelColor=%23bd0005&amp;color=%23333"></a></section><hr><section><h2 id="start-with-a-simple-key-value-store">Start with a simple key-value store.</h2><p>Creating a <a href="https://tinybase.org/api/store/interfaces/store/store/"><code>Store</code></a> requires just a simple call to the <a href="https://tinybase.org/api/store/functions/creation/createstore/"><code>createStore</code></a> function. Once you have one, you can easily set <a href="https://tinybase.org/api/store/type-aliases/store/values/"><code>Values</code></a> in it by unique <a href="https://tinybase.org/api/common/type-aliases/identity/id/"><code>Id</code></a>. And of course you can easily get them back out again.</p><p>Read more about using keyed value data in <a href="https://tinybase.org/guides/the-basics/">The Basics</a> guide.</p></section>
2
2
 
3
3
  ```js
4
4
  const store = createStore()