tinybase 3.0.0-beta.1 → 3.0.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.
@@ -1 +1 @@
1
- var e,a;e=this,a=function(e){"use strict";const a=e=>typeof e,l="",t=a(l),o=a(!0),n=a(0),s="type",r="default",d=(e,a)=>e.every(a),$=(e,a)=>e.sort(a),i=(e,a)=>e.forEach(a),u=(e,a)=>e.map(a),I=e=>e.length,c=e=>0==I(e),b=(e,...a)=>e.push(...a),w=e=>e.pop(),h=(e,...a)=>e.unshift(...a),g=e=>e.shift(),C=JSON.parse,f=isFinite,T=(e,a)=>e instanceof a,V=e=>null==e,v=e=>e==t||e==o,p=e=>a(e)==t,R=e=>Array.isArray(e),m=e=>{const l=a(e);return v(l)||l==n&&f(e)?l:void 0},y=(e,a)=>{var l;return null!=(l=null==e?void 0:e.has(a))&&l},L=e=>{var a;return[...null!=(a=null==e?void 0:e.values())?a:[]]},S=(e,a)=>null==e?void 0:e.forEach(a),k=(e,a)=>null==e?void 0:e.delete(a),J=e=>new Map(e),E=(e,a)=>null==e?void 0:e.get(a),O=(e,a)=>S(e,((e,l)=>a(l,e))),j=(e,a)=>{var l;return u([...null!=(l=null==e?void 0:e.entries())?l:[]],(([e,l])=>a(l,e)))},x=(e,a,l)=>V(l)?(k(e,a),e):null==e?void 0:e.set(a,l),A=(e,a,l)=>(y(e,a)||x(e,a,l()),E(e,a)),P=e=>new Set(R(e)||V(e)?e:[e]),D=(e,a)=>null==e?void 0:e.add(a),N=e=>[e,e],G=()=>[J(),J()],z=/[^A-Za-z]+/,F=/[^A-Za-z0-9]+/,W=/^( *)\/\*\* *(.*?) *\*\/$/gm,M=(e,a,l)=>e.substring(a,l),Z=e=>e.toUpperCase(),_=e=>e.toLowerCase(),B=(e,a,l,t=1)=>{const o=`${a}${1==t?"":t}`;return y(e,o)?B(e,a,l,t+1):(x(e,o,l),o)},Q=e=>e.replace(W,((e,a,l)=>{const t=77-U(a);return`${a}/**\n${l.replace(RegExp(`([^\\n]{1,${t}})(\\s|$)`,"g"),a+" * $1\n")}${a} */`})),U=e=>e.length,q=(e,a=l)=>e.join(a),H=e=>e.flat(1e3),K=(e,a=0)=>q(u(e.split(F),((e,l)=>(l>0||a?Z:_)(M(e,0,1))+M(e,1)))),X=e=>Z(q((e&&!z.test(e[0])?e:" "+e).split(F),"_")),Y=e=>`/** ${e}. */`,ee=()=>{const e=G(),a=J(),t=J(),o=J();return[(...e)=>q(H(e),"\n"),(a,l,...t)=>i(t,(t=>D(A(e[a],l,P),t))),(e,l,t)=>B(a,e,[l,t]),(e,a,l,o,n,s="")=>B(t,e,[a,l,o,n,s]),(e,a,l)=>B(o,e,R(l)?[`(${a}) => {`,l,"}"]:[`(${a}) => ${l}`]),(e,a)=>E(o,e)===a?e:B(o,e,a),a=>[...$(j(e[a],((e,a)=>`import {${q($(L(e)),", ")}} from '${a}';`))),l],()=>j(a,(([e,a],t)=>[Y(a),`export type ${t} = ${e};`,l])),e=>j(t,(([a,t,o,n,s],r)=>{const d=e?[`${r}: ${s}(${a}): ${t} => ${o},`]:[`${r}${s}(${a}): ${t};`];return e||h(d,Y(n)),b(d,l),d})),()=>j(o,((e,a)=>(e=R(e)?e:[e],b(e,w(e)+";"),[`const ${a} = ${g(e)}`,e,l])))]},ae=Object,le=ae.keys,te=ae.freeze,oe=e=>T(e,ae)&&e.constructor==ae,ne=(e,a)=>u(ae.entries(e),(([e,l])=>a(l,e))),se=e=>oe(e)&&c(le(e)),re=e=>{const a=new WeakMap;return l=>(a.has(l)||a.set(l,e(l)),a.get(l))},de="the Store",$e="A function for",ie="export",ue="listener",Ie=" | undefined",ce=`Registers a ${ue} that will be called`,be="Represents",we=" => void",he="the end of the transaction",ge="the specified Row",Ce="a string serialization of",fe=(e=0,a=0)=>`the ${Ne[e]}content of${a?" "+de:""}`,Te=(e,a=0,l=0)=>`${Pe[e]} ${fe(a,1)}${l?" when set":""}`,Ve=(e,a=0)=>`${be} a Row when ${a?"s":"g"}etting ${fe()} the '${e}' Table`,ve=(e,a,l=0)=>`Gets ${l?"sorted, paginated":"the"} Ids of the ${e}s in ${a}`,pe=(e,a)=>`Calls a function for each ${e} in ${a}`,Re=(e,a=de)=>`Gets whether ${e} exists in ${a}`,me=e=>"A function that takes "+e,ye=(e,a=0)=>`${$e} listening to changes to ${De[e]} in ${De[a]}`,Le=(e,a,t=0)=>`${ce} whenever ${e} in ${a} change`+(t?l:"s"),Se=e=>`the '${e}' Table`,ke=e=>`${ge} in ${Se(e)}`,Je=e=>`the '${e}' Cell`,Ee=e=>`the '${e}' Value`,Oe=(e,a=0)=>`${Pe[a]} ${fe()} ${Se(e)}`,je=(e,a=0)=>`${Pe[a]} ${fe()} ${ke(e)}`,xe=(e,a,l=0)=>`${Pe[l]} ${Je(a)} for ${ke(e)}`,Ae=(e,a=0)=>`${Pe[a]} ${Ee(e)}`,Pe=["Gets","Sets","Sets part of","Deletes",be,"Gets "+Ce,"Sets "+Ce,ce+" whenever"],De=[de,"Tables","Table Ids","a Table","Row Ids","a Row","Cell Ids","a Cell","invalid Cell changes","Values","Value Ids","a Value","invalid Value changes"],Ne=["","tabular ","keyed value "],Ge=["DoRollback","Id","IdOrNull","Ids","Json","Store"],ze=(e,a=l,t=l)=>`store.${e}(${a})${t?" as "+t:l}`,Fe=(e,a=l)=>`fluent(() => ${ze(e,a)})`,We=(e,a=l,t=l)=>`store.${e}(${a?a+", ":l}proxy(${ue})${t?", "+t:l})`,Me=(e,a,t)=>{if(se(e)&&se(a))return N(l);const[n,d,$,i,u,I,c,w,h,g]=ee(),C=`./${K(t)}.d`,f=K(t,1),T=K(f),v=[],R=J(),m=a=>ne(e,((e,t)=>a(t,A(R,t,(()=>{const e=K(t,1);return[$(e+"Table",`{[rowId: Id]: ${e}Row}`,`${be} the '${t}' Table`),$(e+"Row",`{${q(y(t,((e,a,t)=>`'${e}'${V(t)?"?":l}: ${a};`))," ")}}`,Ve(t)),$(e+"RowWhenSet",`{${q(y(t,((e,a)=>`'${e}'?: ${a};`))," ")}}`,Ve(t,1)),$(e+"CellId",q(y(t,(e=>`'${e}'`))," | "),`A Cell Id for the '${t}' Table`),$(e+"CellCallback",`(...[cellId, cell]: ${q(y(t,((e,a)=>`[cellId: '${e}', cell: ${a}]`))," | ")})${we}`,me(`a Cell Id and value from a Row in the '${t}' Table`)),$(e+"RowCallback",`(rowId: Id, forEachCell: (cellCallback: ${e}CellCallback)${we})${we}`,me(`a Row Id from the '${t}' Table, and a Cell iterator`))]})),K(t,1),I(X(t),`'${t}'`)))),y=(a,l)=>ne(e[a],((e,a)=>l(a,e[s],e[r],I(X(a),`'${a}'`),K(a,1)))),S=e=>ne(a,((a,l)=>e(l,a[s],a[r],I(X(l),`'${l}'`),K(l,1))));if(d(1,C,f,`create${f} as create${f}Decl`),!se(e)){const e=$("Tables",`{${q(m(((e,a)=>`'${e}'?: ${a[0]};`))," ")}}`,Te(4,1)),a=$("TableId",q(m((e=>`'${e}'`))," | "),"A Table Id in "+de),t=$("TableCallback",`(...[tableId, rowCallback]: ${q(m(((e,a)=>`[tableId: '${e}', forEachRow: (rowCallback: ${a[5]})${we}]`))," | ")})${we}`,me("a Table Id, and a Row iterator")),n=$("GetCellChange",`(...[tableId, rowId, cellId]: ${q(m(((e,a)=>`[tableId: '${e}', rowId: Id, cellId: ${a[3]}]`))," | ")}) => CellChange`,$e+" returning information about any Cell's changes during a transaction"),u=$("TablesListener",`(${T}: ${f}, getCellChange: ${n}${Ie})${we}`,ye(1)),c=$("TableIdsListener",`(${T}: ${f})${we}`,ye(2)),w=$("TableListener",`(${T}: ${f}, tableId: ${a}, getCellChange: ${n}${Ie})${we}`,ye(3)),h=$("RowIdsListener",`(${T}: ${f}, tableId: ${a})`+we,ye(4,3)),g=$("RowListener",`(${T}: ${f}, tableId: ${a}, rowId: Id, getCellChange: ${n}${Ie})${we}`,ye(5,3)),R=$("CellIdsListener",`(${T}: ${f}, tableId: ${a}, rowId: Id)`+we,ye(6,5)),S=$("CellListener",`(...[${T}, tableId, rowId, cellId, newCell, oldCell, getCellChange]: ${q(H(m((e=>y(e,((a,l)=>`[${T}: ${f}, tableId: '${e}', rowId: Id, cellId: '${a}', newCell: ${l}${Ie}, oldCell: ${l}${Ie}, getCellChange: ${n} | undefined]`)))))," | ")})${we}`,ye(7,5)),k=$("InvalidCellListener",`(${T}: ${f}, tableId: Id, rowId: Id, cellId: Id, invalidCells: any[])${we}`,ye(8));i("hasTables",l,o,ze("hasTables"),Re("any Table")),i("getTables",l,e,ze("getTables"),Te(0,1)),i("setTables","tables: "+e,f,Fe("setTables","tables"),Te(1,1)),i("delTables",l,f,Fe("delTables"),Te(3,1)),i("getTableIds",l,a+"[]",ze("getTableIds",l,a+"[]"),ve("Table",de)),i("forEachTable","tableCallback: "+t,"void",ze("forEachTable","tableCallback as any"),pe("Table",de));const E=J();m(((e,[a,t,n,s,r,$],u,I)=>{d(1,C,a,t,n,s,r,$),i(`has${u}Table`,l,o,ze("hasTable",I),Re(Se(e))),i(`get${u}Table`,l,a,ze("getTable",I,a),Oe(e)),i(`set${u}Table`,"table: "+a,f,Fe("setTable",I+", table"),Oe(e,1)),i(`del${u}Table`,l,f,Fe("delTable",I),Oe(e,3)),i(`get${u}RowIds`,l,"Ids",ze("getRowIds",I),ve("Row",Se(e))),i(`get${u}SortedRowIds`,`cellId?: ${s}, descending?: boolean, offset?: number, limit?: number`,"Ids",ze("getSortedRowIds",I+", cellId, descending, offset, limit"),ve("Row",Se(e),1)),i(`forEach${u}Row`,"rowCallback: "+$,"void",ze("forEachRow",I+", rowCallback as any"),pe("Row",Se(e))),i(`has${u}Row`,"rowId: Id",o,ze("hasRow",I+", rowId"),Re(ge,Se(e))),i(`get${u}Row`,"rowId: Id",t,ze("getRow",I+", rowId",t),je(e)),i(`set${u}Row`,"rowId: Id, row: "+n,f,Fe("setRow",I+", rowId, row"),je(e,1)),i(`add${u}Row`,"row: "+n,"Id"+Ie,ze("addRow",I+", row"),"Adds a new Row to "+Se(e)),i(`set${u}PartialRow`,"rowId: Id, partialRow: "+n,f,Fe("setPartialRow",I+", rowId, partialRow"),je(e,2)),i(`del${u}Row`,"rowId: Id",f,Fe("delRow",I+", rowId"),je(e,3)),i(`get${u}CellIds`,"rowId: Id",s+"[]",ze("getCellIds",I+", rowId",s+"[]"),ve("Cell",ke(e))),i(`forEach${u}Cell`,"rowId: Id, cellCallback: "+r,"void",ze("forEachCell",I+", rowId, cellCallback as any"),pe("Cell",ke(e))),y(e,((a,t,n,s,r)=>{const d="Map"+K(t,1);x(E,t,d),i(`has${u}${r}Cell`,"rowId: Id",o,ze("hasCell",`${I}, rowId, ${s}`),Re(Je(a),ke(e)));const $=`${t}${V(n)?Ie:l}`;i(`get${u}${r}Cell`,"rowId: Id",$,ze("getCell",`${I}, rowId, ${s}`,$),xe(e,a)),i(`set${u}${r}Cell`,`rowId: Id, cell: ${t} | ${d}`,f,Fe("setCell",`${I}, rowId, ${s}, cell as any`),xe(e,a,1)),i(`del${u}${r}Cell`,"rowId: Id",f,Fe("delCell",`${I}, rowId, ${s}`),xe(e,a,3))}))})),i("getTablesJson",l,"Json",ze("getTablesJson"),Te(5,1)),i("setTablesJson","tablesJson: Json",f,Fe("setTablesJson","tablesJson"),Te(6,1)),i("addTablesListener",`${ue}: ${u}, mutator?: boolean`,"Id",We("addTablesListener",l,"mutator"),Te(7,1)+" changes"),i("addTableIdsListener",`${ue}: ${c}, mutator?: boolean`,"Id",We("addTableIdsListener",l,"mutator"),Le("the Table Ids",de,1)),i("addTableListener",`tableId: ${a} | null, ${ue}: ${w}, mutator?: boolean`,"Id",We("addTableListener","tableId","mutator"),Le("a Table",de)),i("addRowIdsListener",`tableId: ${a} | null, ${ue}: ${h}, mutator?: boolean`,"Id",We("addRowIdsListener","tableId","mutator"),Le("the Row Ids","a Table",1)),i("addRowListener",`tableId: ${a} | null, rowId: IdOrNull, ${ue}: ${g}, mutator?: boolean`,"Id",We("addRowListener","tableId, rowId","mutator"),Le("a Row","a Table")),i("addCellIdsListener",`tableId: ${a} | null, rowId: IdOrNull, ${ue}: ${R}, mutator?: boolean`,"Id",We("addCellIdsListener","tableId, rowId","mutator"),Le("the Cell Ids","a Row",1)),i("addCellListener",`tableId: ${a} | null, rowId: IdOrNull, cellId: ${q(m(((e,a)=>a[3]))," | ")} | null, ${ue}: ${S}, mutator?: boolean`,"Id",We("addCellListener","tableId, rowId, cellId","mutator"),Le("a Cell","a Row")),i("addInvalidCellListener",`tableId: IdOrNull, rowId: IdOrNull, cellId: IdOrNull, ${ue}: ${k}, mutator?: boolean`,"Id",We("addInvalidCellListener","tableId, rowId, cellId","mutator"),ce+" whenever an invalid Cell change was attempted"),O(E,((e,a)=>$(a,`(cell: ${e}${Ie}) => ${e}`,`Takes a ${e} Cell value and returns another`))),d(1,C,e,a,t,u,c,w,h,g,R,S,k,...L(E)),d(0,"tinybase","CellChange"),b(v,".setTablesSchema({",H(m(((e,a,t,o)=>[`[${o}]: {`,...y(e,((e,a,t,o)=>`[${o}]: {[${I(X(s),`'${s}'`)}]: ${I(X(a),`'${a}'`)}${V(t)?l:`, [${I(X(r),`'${r}'`)}]: ${p(t)?I(X(t),`'${t}'`):t}`}},`)),"},"]))),"})")}if(!se(a)){const e=$("Values",`{${q(S(((e,a,t)=>`'${e}'${V(t)?"?":l}: ${a};`))," ")}}`,Te(4,2)),a=$("ValuesWhenSet",`{${q(S(((e,a)=>`'${e}'?: ${a};`))," ")}}`,Te(4,2,1)),t=$("ValueId",q(S((e=>`'${e}'`))," | "),"A Value Id in "+de),n=$("ValueCallback",`(...[valueId, rowCallback]: ${q(S(((e,a)=>`[valueId: '${e}', value: ${a}]`))," | ")})${we}`,me("a Value Id, and value")),u=$("GetValueChange",`(valueId: ${t}) => ValueChange`,$e+" returning information about any Value's changes during a transaction"),c=$("ValuesListener",`(${T}: ${f}, getValueChange: ${u}${Ie})`+we,ye(9)),w=$("ValueIdsListener",`(${T}: ${f})${we}`,ye(10)),h=$("ValueListener",`(...[${T}, valueId, newValue, oldValue, getValueChange]: ${q(S(((e,a)=>`[${T}: ${f}, valueId: '${e}', newValue: ${a}${Ie}, oldValue: ${a}${Ie}, getValueChange: ${u} | undefined]`))," | ")})${we}`,ye(11)),g=$("InvalidValueListener",`(${T}: ${f}, valueId: Id, invalidValues: any[])${we}`,ye(12));i("hasValues",l,o,ze("hasValues"),Re("any Value")),i("getValues",l,e,ze("getValues"),Te(0,2)),i("setValues","values: "+a,f,Fe("setValues","values"),Te(1,2)),i("setPartialValues","partialValues: "+a,f,Fe("setPartialValues","partialValues"),Te(2,2)),i("delValues",l,f,Fe("delValues"),Te(3,2)),i("getValueIds",l,t+"[]",ze("getValueIds",l,t+"[]"),ve("Value",de)),i("forEachValue","valueCallback: "+n,"void",ze("forEachValue","valueCallback as any"),pe("Value",de)),S(((e,a,t,n,s)=>{i(`has${s}Value`,l,o,ze("hasValue",n),Re(Ee(e))),i(`get${s}Value`,l,a,ze("getValue",n,a),Ae(e)),i(`set${s}Value`,"value: "+a,f,Fe("setValue",n+", value"),Ae(e,1)),i(`del${s}Value`,l,f,Fe("delValue",n),Ae(e,3))})),i("getValuesJson",l,"Json",ze("getValuesJson"),Te(5,2)),i("setValuesJson","valuesJson: Json",f,Fe("setValuesJson","valuesJson"),Te(6,2)),i("addValuesListener",`${ue}: ${c}, mutator?: boolean`,"Id",We("addValuesListener",l,"mutator"),Te(7,2)+" changes"),i("addValueIdsListener",`${ue}: ${w}, mutator?: boolean`,"Id",We("addValueIdsListener",l,"mutator"),Le("the Value Ids",de,1)),i("addValueListener",`valueId: ${t} | null, ${ue}: ${h}, mutator?: boolean`,"Id",We("addValueListener","valueId","mutator"),Le("a Value",de)),i("addInvalidValueListener",`valueId: IdOrNull, ${ue}: ${g}, mutator?: boolean`,"Id",We("addInvalidValueListener","valueId","mutator"),ce+" whenever an invalid Cell change was attempted"),d(1,C,e,t,n,c,w,h,g),d(0,"tinybase","ValueChange"),b(v,".setValuesSchema({",S(((e,a,t,o)=>[`[${o}]: {[${I(X(s),`'${s}'`)}]: ${I(X(a),`'${a}'`)}${V(t)?l:`, [${I(X(r),`'${r}'`)}]: ${p(t)?I(X(t),`'${t}'`):t}`}},`])),"})")}d(0,"tinybase",...Ge);const k=$("TransactionListener",`(${T}: ${f}, cellsTouched: boolean, valuesTouched: boolean)${we}`,$e+" listening to the completion of a transaction");return i("getJson",l,"Json",ze("getJson"),Te(5)),i("setJson","json: Json",f,Fe("setJson","json"),Te(6)),i("transaction","actions: () => Return, doRollback?: DoRollback","Return",ze("transaction","actions, doRollback"),"Execute a transaction to make multiple mutations","<Return>"),i("startTransaction",l,f,Fe("startTransaction"),"Explicitly starts a transaction"),i("finishTransaction","doRollback?: DoRollback,",f,Fe("finishTransaction","doRollback"),"Explicitly finishes a transaction"),i("addWillFinishTransactionListener",`${ue}: ${k}`,"Id",We("addWillFinishTransactionListener"),`${ce} just before ${he}`),i("addDidFinishTransactionListener",`${ue}: ${k}`,"Id",We("addDidFinishTransactionListener"),`${ce} just after ${he}`),i("callListener",ue+"Id: Id",f,Fe("callListener",ue+"Id"),`Manually provoke a ${ue} to be called`),i("delListener",ue+"Id: Id",f,Fe("delListener",ue+"Id"),`Remove a ${ue} that was previously added to ${de}`),i("getStore",l,"Store","store",Pe[0]+" the underlying Store object"),d(1,"tinybase","createStore",...Ge),d(1,C,f,`create${f} as create${f}Decl`,k),I("store",["createStore()",...v]),u("fluent","actions: () => Store",["actions();",`return ${T};`]),u("proxy",ue+": any",`(_: Store, ...args: any[]) => ${ue}(${T}, ...args)`),I(T,["{",...h(1),"}"]),[n(...c(0),...w(),`${ie} interface ${f} {`,...h(0),"}",l,Y(`Creates a ${f} object`),`${ie} function create${f}(): ${f};`),n(...c(1),`${ie} const create${f}: typeof create${f}Decl = () => {`,...g(),`return Object.freeze(${T});`,"};")]};var Ze=Object.defineProperty,_e=Object.getOwnPropertySymbols,Be=Object.prototype.hasOwnProperty,Qe=Object.prototype.propertyIsEnumerable,Ue=(e,a,l)=>a in e?Ze(e,a,{enumerable:!0,configurable:!0,writable:!0,value:l}):e[a]=l,qe=(e,a)=>{for(var l in a||(a={}))Be.call(a,l)&&Ue(e,l,a[l]);if(_e)for(var l of _e(a))Qe.call(a,l)&&Ue(e,l,a[l]);return e},He=(e,a,l)=>new Promise(((t,o)=>{var n=e=>{try{r(l.next(e))}catch(e){o(e)}},s=e=>{try{r(l.throw(e))}catch(e){o(e)}},r=e=>e.done?t(e.value):Promise.resolve(e.value).then(n,s);r((l=l.apply(e,a)).next())}));const Ke={parser:"typescript",singleQuote:!0,trailingComma:"all",bracketSpacing:!1,jsdocSingleLineComment:!1},Xe=re((e=>{const a=()=>{const a=C(e.getTablesSchemaJson());return!se(a)||d(e.getTableIds(),(l=>{const t=e.getRowIds(l),o=J();if(d(t,(a=>d(e.getCellIds(l,a),(t=>{const n=e.getCell(l,a,t),s=A(o,t,(()=>[m(n),J(),[0],0])),[r,d,[$]]=s,i=A(d,n,(()=>0))+1;return i>$&&(s[2]=[i,n]),x(d,n,i),s[3]++,r==m(n)})))))return a[l]={},S(o,(([e,,[,o],n],d)=>{a[l][d]=qe({[s]:e},n==I(t)?{[r]:o}:{})})),1}))?a:{}},l=()=>{const a=C(e.getValuesSchemaJson());return se(a)&&e.forEachValue(((e,l)=>{a[e]={[s]:m(l)}})),a},t=e=>Me(a(),l(),e),o=e=>He(void 0,null,(function*(){let a;try{a=(yield import("prettier")).format}catch(e){a=e=>e}return u(t(e),(e=>Q(a(e,Ke))))}));return te({getStoreStats:a=>{let l=0,t=0,o=0;const n={};return e.forEachTable(((e,s)=>{l++;let r=0,d=0;const $={};s(((e,l)=>{r++;let t=0;l((()=>t++)),d+=t,a&&($[e]={rowCells:t})})),t+=r,o+=d,a&&(n[e]={tableRows:r,tableCells:d,rows:$})})),qe({totalTables:l,totalRows:t,totalCells:o,totalValues:I(e.getValueIds()),jsonLength:U(e.getJson())},a?{detail:{tables:n}}:{})},getStoreTablesSchema:a,getStoreValuesSchema:l,getStoreApi:t,getPrettyStoreApi:o})}));e.createTools=Xe},"object"==typeof exports&&"undefined"!=typeof module?a(exports):"function"==typeof define&&define.amd?define(["exports"],a):a((e="undefined"!=typeof globalThis?globalThis:e||self).TinyBaseTools={});
1
+ var e,a;e=this,a=function(e){"use strict";const a=e=>typeof e,l="",t=a(l),o=a(!0),n=a(0),s="type",r="default",d=(e,a)=>e.every(a),$=(e,a)=>e.sort(a),i=(e,a)=>e.forEach(a),u=(e,a)=>e.map(a),I=e=>e.length,c=e=>0==I(e),b=(e,...a)=>e.push(...a),w=e=>e.pop(),h=(e,...a)=>e.unshift(...a),g=e=>e.shift(),C=JSON.parse,f=isFinite,T=(e,a)=>e instanceof a,V=e=>null==e,v=e=>e==t||e==o,p=e=>a(e)==t,R=e=>Array.isArray(e),m=e=>{const l=a(e);return v(l)||l==n&&f(e)?l:void 0},y=(e,a)=>{var l;return null!=(l=null==e?void 0:e.has(a))&&l},L=e=>{var a;return[...null!=(a=null==e?void 0:e.values())?a:[]]},S=(e,a)=>null==e?void 0:e.forEach(a),k=(e,a)=>null==e?void 0:e.delete(a),J=e=>new Map(e),E=(e,a)=>null==e?void 0:e.get(a),O=(e,a)=>S(e,((e,l)=>a(l,e))),j=(e,a)=>{var l;return u([...null!=(l=null==e?void 0:e.entries())?l:[]],(([e,l])=>a(l,e)))},x=(e,a,l)=>V(l)?(k(e,a),e):null==e?void 0:e.set(a,l),A=(e,a,l)=>(y(e,a)||x(e,a,l()),E(e,a)),P=e=>new Set(R(e)||V(e)?e:[e]),D=(e,a)=>null==e?void 0:e.add(a),N=e=>[e,e],G=()=>[J(),J()],z=/[^A-Za-z]+/,F=/[^A-Za-z0-9]+/,W=/^( *)\/\*\* *(.*?) *\*\/$/gm,M=(e,a,l)=>e.substring(a,l),Z=e=>e.toUpperCase(),_=e=>e.toLowerCase(),B=(e,a,l,t=1)=>{const o=`${a}${1==t?"":t}`;return y(e,o)?B(e,a,l,t+1):(x(e,o,l),o)},Q=e=>e.replace(W,((e,a,l)=>{const t=77-U(a);return`${a}/**\n${l.replace(RegExp(`([^\\n]{1,${t}})(\\s|$)`,"g"),a+" * $1\n")}${a} */`})),U=e=>e.length,q=(e,a=l)=>e.join(a),H=e=>e.flat(1e3),K=(e,a=0)=>q(u(e.split(F),((e,l)=>(l>0||a?Z:_)(M(e,0,1))+M(e,1)))),X=e=>Z(q((e&&!z.test(e[0])?e:" "+e).split(F),"_")),Y=e=>`/** ${e}. */`,ee=()=>{const e=G(),a=J(),t=J(),o=J();return[(...e)=>q(H(e),"\n"),(a,l,...t)=>i(t,(t=>D(A(e[a],l,P),t))),(e,l,t)=>B(a,e,[l,t]),(e,a,l,o,n,s="")=>B(t,e,[a,l,o,n,s]),(e,a,l)=>B(o,e,R(l)?[`(${a}) => {`,l,"}"]:[`(${a}) => ${l}`]),(e,a)=>E(o,e)===a?e:B(o,e,a),a=>[...$(j(e[a],((e,a)=>`import {${q($(L(e)),", ")}} from '${a}';`))),l],()=>j(a,(([e,a],t)=>[Y(a),`export type ${t} = ${e};`,l])),e=>j(t,(([a,t,o,n,s],r)=>{const d=e?[`${r}: ${s}(${a}): ${t} => ${o},`]:[`${r}${s}(${a}): ${t};`];return e||h(d,Y(n)),b(d,l),d})),()=>j(o,((e,a)=>(e=R(e)?e:[e],b(e,w(e)+";"),[`const ${a} = ${g(e)}`,e,l])))]},ae=Object,le=ae.keys,te=ae.freeze,oe=e=>T(e,ae)&&e.constructor==ae,ne=(e,a)=>u(ae.entries(e),(([e,l])=>a(l,e))),se=e=>oe(e)&&c(le(e)),re=e=>{const a=new WeakMap;return l=>(a.has(l)||a.set(l,e(l)),a.get(l))},de="the Store",$e="A function for",ie="export",ue="listener",Ie=" | undefined",ce=`Registers a ${ue} that will be called`,be="Represents",we=" => void",he="the end of the transaction",ge="the specified Row",Ce="a string serialization of",fe=(e=0,a=0)=>`the ${Ne[e]}content of${a?" "+de:""}`,Te=(e,a=0,l=0)=>`${Pe[e]} ${fe(a,1)}${l?" when set":""}`,Ve=(e,a=0)=>`${be} a Row when ${a?"s":"g"}etting ${fe()} the '${e}' Table`,ve=(e,a,l=0)=>`Gets ${l?"sorted, paginated":"the"} Ids of the ${e}s in ${a}`,pe=(e,a)=>`Calls a function for each ${e} in ${a}`,Re=(e,a=de)=>`Gets whether ${e} exists in ${a}`,me=e=>"A function that takes "+e,ye=(e,a=0)=>`${$e} listening to changes to ${De[e]} in ${De[a]}`,Le=(e,a,t=0)=>`${ce} whenever ${e} in ${a} change`+(t?l:"s"),Se=e=>`the '${e}' Table`,ke=e=>`${ge} in ${Se(e)}`,Je=e=>`the '${e}' Cell`,Ee=e=>`the '${e}' Value`,Oe=(e,a=0)=>`${Pe[a]} ${fe()} ${Se(e)}`,je=(e,a=0)=>`${Pe[a]} ${fe()} ${ke(e)}`,xe=(e,a,l=0)=>`${Pe[l]} ${Je(a)} for ${ke(e)}`,Ae=(e,a=0)=>`${Pe[a]} ${Ee(e)}`,Pe=["Gets","Sets","Sets part of","Deletes",be,"Gets "+Ce,"Sets "+Ce,ce+" whenever"],De=[de,"Tables","Table Ids","a Table","Row Ids","a Row","Cell Ids","a Cell","invalid Cell changes","Values","Value Ids","a Value","invalid Value changes"],Ne=["","tabular ","keyed value "],Ge=["DoRollback","Id","IdOrNull","Ids","Json","Store"],ze=(e,a=l,t=l)=>`store.${e}(${a})${t?" as "+t:l}`,Fe=(e,a=l)=>`fluent(() => ${ze(e,a)})`,We=(e,a=l,t=l)=>`store.${e}(${a?a+", ":l}proxy(${ue})${t?", "+t:l})`,Me=(e,a,t)=>{if(se(e)&&se(a))return N(l);const[n,d,$,i,u,I,c,w,h,g]=ee(),C=`./${K(t)}.d`,f=K(t,1),T=K(f),v=[],R=J(),m=a=>ne(e,((e,t)=>a(t,A(R,t,(()=>{const e=K(t,1);return[$(e+"Table",`{[rowId: Id]: ${e}Row}`,`${be} the '${t}' Table`),$(e+"Row",`{${q(y(t,((e,a,t)=>`'${e}'${V(t)?"?":l}: ${a};`))," ")}}`,Ve(t)),$(e+"RowWhenSet",`{${q(y(t,((e,a)=>`'${e}'?: ${a};`))," ")}}`,Ve(t,1)),$(e+"CellId",q(y(t,(e=>`'${e}'`))," | "),`A Cell Id for the '${t}' Table`),$(e+"CellCallback",`(...[cellId, cell]: ${q(y(t,((e,a)=>`[cellId: '${e}', cell: ${a}]`))," | ")})${we}`,me(`a Cell Id and value from a Row in the '${t}' Table`)),$(e+"RowCallback",`(rowId: Id, forEachCell: (cellCallback: ${e}CellCallback)${we})${we}`,me(`a Row Id from the '${t}' Table, and a Cell iterator`))]})),K(t,1),I(X(t),`'${t}'`)))),y=(a,l)=>ne(e[a],((e,a)=>l(a,e[s],e[r],I(X(a),`'${a}'`),K(a,1)))),S=e=>ne(a,((a,l)=>e(l,a[s],a[r],I(X(l),`'${l}'`),K(l,1))));if(d(1,C,f,`create${f} as create${f}Decl`),!se(e)){const e=$("Tables",`{${q(m(((e,a)=>`'${e}'?: ${a[0]};`))," ")}}`,Te(4,1)),a=$("TableId",q(m((e=>`'${e}'`))," | "),"A Table Id in "+de),t=$("TableCallback",`(...[tableId, rowCallback]: ${q(m(((e,a)=>`[tableId: '${e}', forEachRow: (rowCallback: ${a[5]})${we}]`))," | ")})${we}`,me("a Table Id, and a Row iterator")),n=$("GetCellChange",`(...[tableId, rowId, cellId]: ${q(m(((e,a)=>`[tableId: '${e}', rowId: Id, cellId: ${a[3]}]`))," | ")}) => CellChange`,$e+" returning information about any Cell's changes during a transaction"),u=$("TablesListener",`(${T}: ${f}, getCellChange: ${n}${Ie})${we}`,ye(1)),c=$("TableIdsListener",`(${T}: ${f})${we}`,ye(2)),w=$("TableListener",`(${T}: ${f}, tableId: ${a}, getCellChange: ${n}${Ie})${we}`,ye(3)),h=$("RowIdsListener",`(${T}: ${f}, tableId: ${a})`+we,ye(4,3)),g=$("RowListener",`(${T}: ${f}, tableId: ${a}, rowId: Id, getCellChange: ${n}${Ie})${we}`,ye(5,3)),R=$("CellIdsListener",`(${T}: ${f}, tableId: ${a}, rowId: Id)`+we,ye(6,5)),S=$("CellListener",`(...[${T}, tableId, rowId, cellId, newCell, oldCell, getCellChange]: ${q(H(m((e=>y(e,((a,l)=>`[${T}: ${f}, tableId: '${e}', rowId: Id, cellId: '${a}', newCell: ${l}${Ie}, oldCell: ${l}${Ie}, getCellChange: ${n} | undefined]`)))))," | ")})${we}`,ye(7,5)),k=$("InvalidCellListener",`(${T}: ${f}, tableId: Id, rowId: Id, cellId: Id, invalidCells: any[])${we}`,ye(8));i("hasTables",l,o,ze("hasTables"),Re("any Table")),i("getTables",l,e,ze("getTables"),Te(0,1)),i("setTables","tables: "+e,f,Fe("setTables","tables"),Te(1,1)),i("delTables",l,f,Fe("delTables"),Te(3,1)),i("getTableIds",l,a+"[]",ze("getTableIds",l,a+"[]"),ve("Table",de)),i("forEachTable","tableCallback: "+t,"void",ze("forEachTable","tableCallback as any"),pe("Table",de));const E=J();m(((e,[a,t,n,s,r,$],u,I)=>{d(1,C,a,t,n,s,r,$),i(`has${u}Table`,l,o,ze("hasTable",I),Re(Se(e))),i(`get${u}Table`,l,a,ze("getTable",I,a),Oe(e)),i(`set${u}Table`,"table: "+a,f,Fe("setTable",I+", table"),Oe(e,1)),i(`del${u}Table`,l,f,Fe("delTable",I),Oe(e,3)),i(`get${u}RowIds`,l,"Ids",ze("getRowIds",I),ve("Row",Se(e))),i(`get${u}SortedRowIds`,`cellId?: ${s}, descending?: boolean, offset?: number, limit?: number`,"Ids",ze("getSortedRowIds",I+", cellId, descending, offset, limit"),ve("Row",Se(e),1)),i(`forEach${u}Row`,"rowCallback: "+$,"void",ze("forEachRow",I+", rowCallback as any"),pe("Row",Se(e))),i(`has${u}Row`,"rowId: Id",o,ze("hasRow",I+", rowId"),Re(ge,Se(e))),i(`get${u}Row`,"rowId: Id",t,ze("getRow",I+", rowId",t),je(e)),i(`set${u}Row`,"rowId: Id, row: "+n,f,Fe("setRow",I+", rowId, row"),je(e,1)),i(`add${u}Row`,"row: "+n,"Id"+Ie,ze("addRow",I+", row"),"Adds a new Row to "+Se(e)),i(`set${u}PartialRow`,"rowId: Id, partialRow: "+n,f,Fe("setPartialRow",I+", rowId, partialRow"),je(e,2)),i(`del${u}Row`,"rowId: Id",f,Fe("delRow",I+", rowId"),je(e,3)),i(`get${u}CellIds`,"rowId: Id",s+"[]",ze("getCellIds",I+", rowId",s+"[]"),ve("Cell",ke(e))),i(`forEach${u}Cell`,"rowId: Id, cellCallback: "+r,"void",ze("forEachCell",I+", rowId, cellCallback as any"),pe("Cell",ke(e))),y(e,((a,t,n,s,r)=>{const d="Map"+K(t,1);x(E,t,d),i(`has${u}${r}Cell`,"rowId: Id",o,ze("hasCell",`${I}, rowId, ${s}`),Re(Je(a),ke(e)));const $=`${t}${V(n)?Ie:l}`;i(`get${u}${r}Cell`,"rowId: Id",$,ze("getCell",`${I}, rowId, ${s}`,$),xe(e,a)),i(`set${u}${r}Cell`,`rowId: Id, cell: ${t} | ${d}`,f,Fe("setCell",`${I}, rowId, ${s}, cell as any`),xe(e,a,1)),i(`del${u}${r}Cell`,"rowId: Id",f,Fe("delCell",`${I}, rowId, ${s}`),xe(e,a,3))}))})),i("getTablesJson",l,"Json",ze("getTablesJson"),Te(5,1)),i("setTablesJson","tablesJson: Json",f,Fe("setTablesJson","tablesJson"),Te(6,1)),i("addTablesListener",`${ue}: ${u}, mutator?: boolean`,"Id",We("addTablesListener",l,"mutator"),Te(7,1)+" changes"),i("addTableIdsListener",`${ue}: ${c}, mutator?: boolean`,"Id",We("addTableIdsListener",l,"mutator"),Le("the Table Ids",de,1)),i("addTableListener",`tableId: ${a} | null, ${ue}: ${w}, mutator?: boolean`,"Id",We("addTableListener","tableId","mutator"),Le("a Table",de)),i("addRowIdsListener",`tableId: ${a} | null, ${ue}: ${h}, mutator?: boolean`,"Id",We("addRowIdsListener","tableId","mutator"),Le("the Row Ids","a Table",1)),i("addRowListener",`tableId: ${a} | null, rowId: IdOrNull, ${ue}: ${g}, mutator?: boolean`,"Id",We("addRowListener","tableId, rowId","mutator"),Le("a Row","a Table")),i("addCellIdsListener",`tableId: ${a} | null, rowId: IdOrNull, ${ue}: ${R}, mutator?: boolean`,"Id",We("addCellIdsListener","tableId, rowId","mutator"),Le("the Cell Ids","a Row",1)),i("addCellListener",`tableId: ${a} | null, rowId: IdOrNull, cellId: ${q(m(((e,a)=>a[3]))," | ")} | null, ${ue}: ${S}, mutator?: boolean`,"Id",We("addCellListener","tableId, rowId, cellId","mutator"),Le("a Cell","a Row")),i("addInvalidCellListener",`tableId: IdOrNull, rowId: IdOrNull, cellId: IdOrNull, ${ue}: ${k}, mutator?: boolean`,"Id",We("addInvalidCellListener","tableId, rowId, cellId","mutator"),ce+" whenever an invalid Cell change was attempted"),O(E,((e,a)=>$(a,`(cell: ${e}${Ie}) => ${e}`,`Takes a ${e} Cell value and returns another`))),d(1,C,e,a,t,u,c,w,h,g,R,S,k,...L(E)),d(0,"tinybase","CellChange"),b(v,".setTablesSchema({",H(m(((e,a,t,o)=>[`[${o}]: {`,...y(e,((e,a,t,o)=>`[${o}]: {[${I(X(s),`'${s}'`)}]: ${I(X(a),`'${a}'`)}${V(t)?l:`, [${I(X(r),`'${r}'`)}]: ${p(t)?I(X(t),`'${t}'`):t}`}},`)),"},"]))),"})")}if(!se(a)){const e=$("Values",`{${q(S(((e,a,t)=>`'${e}'${V(t)?"?":l}: ${a};`))," ")}}`,Te(4,2)),a=$("ValuesWhenSet",`{${q(S(((e,a)=>`'${e}'?: ${a};`))," ")}}`,Te(4,2,1)),t=$("ValueId",q(S((e=>`'${e}'`))," | "),"A Value Id in "+de),n=$("ValueCallback",`(...[valueId, rowCallback]: ${q(S(((e,a)=>`[valueId: '${e}', value: ${a}]`))," | ")})${we}`,me("a Value Id, and value")),u=$("GetValueChange",`(valueId: ${t}) => ValueChange`,$e+" returning information about any Value's changes during a transaction"),c=$("ValuesListener",`(${T}: ${f}, getValueChange: ${u}${Ie})`+we,ye(9)),w=$("ValueIdsListener",`(${T}: ${f})${we}`,ye(10)),h=$("ValueListener",`(...[${T}, valueId, newValue, oldValue, getValueChange]: ${q(S(((e,a)=>`[${T}: ${f}, valueId: '${e}', newValue: ${a}${Ie}, oldValue: ${a}${Ie}, getValueChange: ${u} | undefined]`))," | ")})${we}`,ye(11)),g=$("InvalidValueListener",`(${T}: ${f}, valueId: Id, invalidValues: any[])${we}`,ye(12));i("hasValues",l,o,ze("hasValues"),Re("any Value")),i("getValues",l,e,ze("getValues"),Te(0,2)),i("setValues","values: "+a,f,Fe("setValues","values"),Te(1,2)),i("setPartialValues","partialValues: "+a,f,Fe("setPartialValues","partialValues"),Te(2,2)),i("delValues",l,f,Fe("delValues"),Te(3,2)),i("getValueIds",l,t+"[]",ze("getValueIds",l,t+"[]"),ve("Value",de)),i("forEachValue","valueCallback: "+n,"void",ze("forEachValue","valueCallback as any"),pe("Value",de)),S(((e,a,t,n,s)=>{i(`has${s}Value`,l,o,ze("hasValue",n),Re(Ee(e))),i(`get${s}Value`,l,a,ze("getValue",n,a),Ae(e)),i(`set${s}Value`,"value: "+a,f,Fe("setValue",n+", value"),Ae(e,1)),i(`del${s}Value`,l,f,Fe("delValue",n),Ae(e,3))})),i("getValuesJson",l,"Json",ze("getValuesJson"),Te(5,2)),i("setValuesJson","valuesJson: Json",f,Fe("setValuesJson","valuesJson"),Te(6,2)),i("addValuesListener",`${ue}: ${c}, mutator?: boolean`,"Id",We("addValuesListener",l,"mutator"),Te(7,2)+" changes"),i("addValueIdsListener",`${ue}: ${w}, mutator?: boolean`,"Id",We("addValueIdsListener",l,"mutator"),Le("the Value Ids",de,1)),i("addValueListener",`valueId: ${t} | null, ${ue}: ${h}, mutator?: boolean`,"Id",We("addValueListener","valueId","mutator"),Le("a Value",de)),i("addInvalidValueListener",`valueId: IdOrNull, ${ue}: ${g}, mutator?: boolean`,"Id",We("addInvalidValueListener","valueId","mutator"),ce+" whenever an invalid Cell change was attempted"),d(1,C,e,t,n,c,w,h,g),d(0,"tinybase","ValueChange"),b(v,".setValuesSchema({",S(((e,a,t,o)=>[`[${o}]: {[${I(X(s),`'${s}'`)}]: ${I(X(a),`'${a}'`)}${V(t)?l:`, [${I(X(r),`'${r}'`)}]: ${p(t)?I(X(t),`'${t}'`):t}`}},`])),"})")}d(0,"tinybase",...Ge);const k=$("TransactionListener",`(${T}: ${f}, cellsTouched: boolean, valuesTouched: boolean)${we}`,$e+" listening to the completion of a transaction");return i("getJson",l,"Json",ze("getJson"),Te(5)),i("setJson","json: Json",f,Fe("setJson","json"),Te(6)),i("transaction","actions: () => Return, doRollback?: DoRollback","Return",ze("transaction","actions, doRollback"),"Execute a transaction to make multiple mutations","<Return>"),i("startTransaction",l,f,Fe("startTransaction"),"Explicitly starts a transaction"),i("finishTransaction","doRollback?: DoRollback,",f,Fe("finishTransaction","doRollback"),"Explicitly finishes a transaction"),i("addWillFinishTransactionListener",`${ue}: ${k}`,"Id",We("addWillFinishTransactionListener"),`${ce} just before ${he}`),i("addDidFinishTransactionListener",`${ue}: ${k}`,"Id",We("addDidFinishTransactionListener"),`${ce} just after ${he}`),i("callListener",ue+"Id: Id",f,Fe("callListener",ue+"Id"),`Manually provoke a ${ue} to be called`),i("delListener",ue+"Id: Id",f,Fe("delListener",ue+"Id"),`Remove a ${ue} that was previously added to ${de}`),i("getStore",l,"Store","store",Pe[0]+" the underlying Store object"),d(1,"tinybase","createStore",...Ge),d(1,C,f,`create${f} as create${f}Decl`,k),I("store",["createStore()",...v]),u("fluent","actions: () => Store",["actions();",`return ${T};`]),u("proxy",ue+": any",`(_: Store, ...args: any[]) => ${ue}(${T}, ...args)`),I(T,["{",...h(1),"}"]),[n(...c(0),...w(),`${ie} interface ${f} {`,...h(0),"}",l,Y(`Creates a ${f} object`),`${ie} function create${f}(): ${f};`),n(...c(1),`${ie} const create${f}: typeof create${f}Decl = () => {`,...g(),`return Object.freeze(${T});`,"};")]};var Ze=Object.defineProperty,_e=Object.getOwnPropertySymbols,Be=Object.prototype.hasOwnProperty,Qe=Object.prototype.propertyIsEnumerable,Ue=(e,a,l)=>a in e?Ze(e,a,{enumerable:!0,configurable:!0,writable:!0,value:l}):e[a]=l,qe=(e,a)=>{for(var l in a||(a={}))Be.call(a,l)&&Ue(e,l,a[l]);if(_e)for(var l of _e(a))Qe.call(a,l)&&Ue(e,l,a[l]);return e},He=(e,a,l)=>new Promise(((t,o)=>{var n=e=>{try{r(l.next(e))}catch(e){o(e)}},s=e=>{try{r(l.throw(e))}catch(e){o(e)}},r=e=>e.done?t(e.value):Promise.resolve(e.value).then(n,s);r((l=l.apply(e,a)).next())}));const Ke={parser:"typescript",singleQuote:!0,trailingComma:"all",bracketSpacing:!1,jsdocSingleLineComment:!1},Xe=re((e=>{const a=()=>{const a=C(e.getTablesSchemaJson());return!se(a)||d(e.getTableIds(),(l=>{const t=e.getRowIds(l),o=J();if(d(t,(a=>d(e.getCellIds(l,a),(t=>{const n=e.getCell(l,a,t),s=A(o,t,(()=>[m(n),J(),[0],0])),[r,d,[$]]=s,i=A(d,n,(()=>0))+1;return i>$&&(s[2]=[i,n]),x(d,n,i),s[3]++,r==m(n)})))))return a[l]={},S(o,(([e,,[,o],n],d)=>{a[l][d]=qe({[s]:e},n==I(t)?{[r]:o}:{})})),1}))?a:{}},l=()=>{const a=C(e.getValuesSchemaJson());return se(a)&&e.forEachValue(((e,l)=>{a[e]={[s]:m(l)}})),a},t=e=>Me(a(),l(),e),o=e=>He(void 0,null,(function*(){let a;try{a=(yield import("prettier")).format}catch(e){a=e=>e}return u(t(e),(e=>Q(a(e,Ke))))}));return te({getStoreStats:a=>{let l=0,t=0,o=0;const n={};return e.forEachTable(((e,s)=>{l++;let r=0,d=0;const $={};s(((e,l)=>{r++;let t=0;l((()=>t++)),d+=t,a&&($[e]={rowCells:t})})),t+=r,o+=d,a&&(n[e]={tableRows:r,tableCells:d,rows:$})})),qe({totalTables:l,totalRows:t,totalCells:o,totalValues:I(e.getValueIds()),jsonLength:U(e.getJson())},a?{detail:{tables:n}}:{})},getStoreTablesSchema:a,getStoreValuesSchema:l,getStoreApi:t,getPrettyStoreApi:o,getStore:()=>e})}));e.createTools=Xe},"object"==typeof exports&&"undefined"!=typeof module?a(exports):"function"==typeof define&&define.amd?define(["exports"],a):a((e="undefined"!=typeof globalThis?globalThis:e||self).TinyBaseTools={});
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tinybase",
3
- "version": "3.0.0-beta.1",
3
+ "version": "3.0.0",
4
4
  "author": "jamesgpearce",
5
5
  "repository": "github:tinyplex/tinybase",
6
6
  "license": "MIT",
@@ -69,11 +69,11 @@
69
69
  "@babel/preset-react": "^7.18.6",
70
70
  "@babel/preset-typescript": "^7.18.6",
71
71
  "@rollup/plugin-replace": "^5.0.2",
72
- "@rollup/plugin-terser": "^0.3.0",
72
+ "@rollup/plugin-terser": "^0.4.0",
73
73
  "@types/asciichart": "^1.5.6",
74
74
  "@types/expect-puppeteer": "^5.0.2",
75
75
  "@types/http-server": "^0.12.1",
76
- "@types/jest": "^29.2.6",
76
+ "@types/jest": "^29.4.0",
77
77
  "@types/jest-environment-puppeteer": "^5.0.3",
78
78
  "@types/less": "^3.0.3",
79
79
  "@types/node": "^18.11.18",
@@ -82,27 +82,27 @@
82
82
  "@types/react-dom": "^18.0.10",
83
83
  "@types/react-test-renderer": "^18.0.0",
84
84
  "@types/tmp": "^0.2.3",
85
- "@typescript-eslint/eslint-plugin": "^5.48.2",
86
- "@typescript-eslint/parser": "^5.48.2",
85
+ "@typescript-eslint/eslint-plugin": "^5.49.0",
86
+ "@typescript-eslint/parser": "^5.49.0",
87
87
  "asciichart": "^1.5.25",
88
88
  "babel-eslint": "^10.1.0",
89
- "babel-jest": "^29.3.1",
89
+ "babel-jest": "^29.4.1",
90
90
  "babel-preset-minify": "^0.5.2",
91
91
  "buffer-replace": "^1.0.0",
92
92
  "country-flag-emoji-json": "^2.0.0",
93
93
  "cspell": "^6.19.2",
94
- "esbuild": "^0.17.4",
95
- "eslint": "^8.32.0",
94
+ "esbuild": "^0.17.5",
95
+ "eslint": "^8.33.0",
96
96
  "eslint-config-prettier": "^8.6.0",
97
97
  "eslint-plugin-jest": "^27.2.1",
98
- "eslint-plugin-jsdoc": "^39.6.7",
99
- "eslint-plugin-react": "^7.32.1",
98
+ "eslint-plugin-jsdoc": "^39.7.4",
99
+ "eslint-plugin-react": "^7.32.2",
100
100
  "eslint-plugin-react-hooks": "^4.6.0",
101
101
  "gulp": "^4.0.2",
102
102
  "gulp-gzip": "^1.4.2",
103
103
  "http-server": "^14.1.1",
104
- "jest": "^29.3.1",
105
- "jest-environment-jsdom": "^29.3.1",
104
+ "jest": "^29.4.1",
105
+ "jest-environment-jsdom": "^29.4.1",
106
106
  "jest-fetch-mock": "^3.0.3",
107
107
  "jest-puppeteer": "^6.2.0",
108
108
  "less": "^4.1.3",
@@ -111,7 +111,7 @@
111
111
  "react": "^18.2.0",
112
112
  "react-dom": "^18.2.0",
113
113
  "react-test-renderer": "^18.2.0",
114
- "rollup": "^3.10.1",
114
+ "rollup": "^3.12.0",
115
115
  "rollup-plugin-esbuild": "^5.0.0",
116
116
  "rollup-plugin-gzip": "^3.1.0",
117
117
  "rollup-plugin-preserve-shebang": "^1.0.1",
package/readme.md CHANGED
@@ -1,7 +1,18 @@
1
- <section id="hero"><h2 id="the-reactive-data-store-for-local-first-apps">The <em>reactive</em> data store for localfirst apps.</h2><p>Modern apps deserve better. Why trade reactive user experiences to be able to use relational data? Why sacrifice store features for bundle size? And why should the cloud do all the work <a href="https://www.inkandswitch.com/local-first/" target="_blank">anyway</a>?</p><p><em>TinyBase is a smart new way to structure your local app data:</em></p><ul><li>Familiar concepts of <a href="#set-and-get-tables-rows-and-cells">tables, rows, and cells</a>, and <a href="#apply-schemas-to-tables">schematization</a> to model your data domain.</li><li><a href="#register-listeners-at-any-granularity">Flexibly reactive</a> to reconciled updates, so your UI only spends cycles on the data that changes.</li><li><a href="#build-complex-queries-with-tinyql">Powerful query engine</a> to select, join, filter, group, sort and paginate data - reactively.</li><li><a href="#create-indexes-for-fast-lookups">Indexing</a>, <a href="#define-metrics-and-aggregations">metrics</a>, <a href="#configure-relationships-between-tables">relationships</a> - and even an <a href="#use-checkpoints-for-an-easy-undo-stack">undo stack</a> for your app state! - out of the box.</li><li>Easily <a href="#persist-data-to-browser-file-or-server">sync your data</a> to local or remote storage, and use <a href="#call-hooks-to-bind-to-data">idiomatic bindings</a> to your UI.</li><li><em>NEW!</em> <a href="#generate-orm-like-apis">Generate ORM-like APIs</a> in TypeScript, based on your schema or inferred from actual data.</li></ul><p><em>Tiny by name, tiny by nature</em>, TinyBase only costs <a href="#did-we-say-tiny">4.2kB - 8.7kB</a> when compressed, and has zero dependencies. And of course it&#x27;s <a href="#well-tested-and-documented">well tested</a>, <a href="https://beta.tinybase.org/guides/the-basics/getting-started/">fully documented</a>, and <a href="https://github.com/tinyplex/tinybase">open source</a>. Other <a href="https://beta.tinybase.org/guides/faq/">FAQs</a>?</p></section><p><a id="new" href="https://beta.tinybase.org/guides/releases/#v2-2"><em>NEW!</em> v2.2 release</a></p><hr><p><a class="start" href="https://beta.tinybase.org/guides/the-basics/getting-started/">Get started</a></p><p><a href="https://beta.tinybase.org/demos/">Try the demos</a></p><p><a href="https://beta.tinybase.org/api/store/interfaces/store/store/">Read the docs</a></p><hr><section><h2 id="set-and-get-tables-rows-and-cells">Set and get tables, rows, and cells.</h2><p>Creating a <a href="https://beta.tinybase.org/api/store/interfaces/store/store/"><code>Store</code></a> requires just a simple call to the <a href="https://beta.tinybase.org/api/store/functions/creation/createstore/"><code>createStore</code></a> function. Once you have one, you can easily set <a href="https://beta.tinybase.org/api/store/type-aliases/store/table/"><code>Table</code></a>, <a href="https://beta.tinybase.org/api/store/type-aliases/store/row/"><code>Row</code></a>, or <a href="https://beta.tinybase.org/api/store/type-aliases/store/cell/"><code>Cell</code></a> values by their <a href="https://beta.tinybase.org/api/common/type-aliases/identity/id/"><code>Id</code></a>. And of course you can easily get the values back out again.</p><p>Read more about setting and changing data in <a href="https://beta.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>Modern apps deserve better. Why trade reactive user experiences to be able to use relational data? Why sacrifice store features for bundle size? And why should the cloud do all the work <a href="https://www.inkandswitch.com/local-first/" target="_blank">anyway</a>?</p><p><em>TinyBase is a smart new way to structure your local app data:</em></p><ul><li>Manage <a href="#start-with-a-simple-key-value-store">key-value data</a> (<em>new!</em>), <a href="#level-up-to-use-tabular-data">tabular data</a> - or both - with optional <a href="#apply-schemas-to-tables-and-values">schematization</a> to model it.</li><li><a href="#register-listeners-at-any-granularity">Flexibly reactive</a> to reconciled updates, so your UI only spends cycles on the data that changes.</li><li><a href="#build-complex-queries-with-tinyql">Powerful query engine</a> to select, join, filter, group, sort and paginate data - reactively.</li><li><a href="#create-indexes-for-fast-lookups">Indexing</a>, <a href="#define-metrics-and-aggregations">metrics</a>, <a href="#configure-relationships-between-tables">relationships</a> - and even an <a href="#use-checkpoints-for-an-easy-undo-stack">undo stack</a> for your app state! - out of the box.</li><li>Easily <a href="#persist-data-to-browser-file-or-server">sync your data</a> to local or remote storage, and use <a href="#call-hooks-to-bind-to-data">idiomatic bindings</a> to your UI.</li><li><a href="#generate-orm-like-apis">Generate ORM-like APIs</a> (<em>new!</em>) in TypeScript, based on a schema or inferred from actual data.</li></ul><p><em>Tiny by name, tiny by nature</em>, TinyBase only costs <a href="#did-we-say-tiny">4.2kB - 8.7kB</a> when compressed, and has zero dependencies. And of course it&#x27;s <a href="#well-tested-and-documented">well tested</a>, <a href="https://tinybase.org/guides/the-basics/getting-started/">fully documented</a>, and <a href="https://github.com/tinyplex/tinybase">open source</a>. Other <a href="https://tinybase.org/guides/faq/">FAQs</a>?</p></section><p><a id="new" href="https://tinybase.org/guides/releases/#v3-0"><em>NEW!</em> v3.0 release</a></p><hr><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><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()
5
+ .setValues({employees: 3})
6
+ .setValue('open', true);
7
+
8
+ console.log(store.getValues());
9
+ // -> {employees: 3, open: true}
10
+ ```
11
+
12
+ <section><h2 id="level-up-to-use-tabular-data">Level up to use tabular data.</h2><p>For other types of data applications, a tabular data structure is more useful. TinyBase lets you set and get nested <a href="https://tinybase.org/api/store/type-aliases/store/table/"><code>Table</code></a>, <a href="https://tinybase.org/api/store/type-aliases/store/row/"><code>Row</code></a>, or <a href="https://tinybase.org/api/store/type-aliases/store/cell/"><code>Cell</code></a> data, by unique <a href="https://tinybase.org/api/common/type-aliases/identity/id/"><code>Id</code></a> and in the same <a href="https://tinybase.org/api/store/interfaces/store/store/"><code>Store</code></a> as the keyed values.</p><p>Read more about setting and changing data in <a href="https://tinybase.org/guides/the-basics/">The Basics</a> guide.</p></section>
13
+
14
+ ```js
15
+ store
5
16
  .setTable('pets', {fido: {species: 'dog'}})
6
17
  .setCell('pets', 'fido', 'color', 'brown');
7
18
 
@@ -9,7 +20,7 @@ console.log(store.getRow('pets', 'fido'));
9
20
  // -> {species: 'dog', color: 'brown'}
10
21
  ```
11
22
 
12
- <section><h2 id="register-listeners-at-any-granularity">Register listeners at any granularity.</h2><p>The magic starts to happen when you register listeners on a <a href="https://beta.tinybase.org/api/store/interfaces/store/store/"><code>Store</code></a>, <a href="https://beta.tinybase.org/api/store/type-aliases/store/table/"><code>Table</code></a>, <a href="https://beta.tinybase.org/api/store/type-aliases/store/row/"><code>Row</code></a>, or <a href="https://beta.tinybase.org/api/store/type-aliases/store/cell/"><code>Cell</code></a>. They get called when any part of that object changes. You can also use wildcards - useful when you don&#x27;t know the <a href="https://beta.tinybase.org/api/common/type-aliases/identity/id/"><code>Id</code></a> of the objects that might change.</p><p>Read more about listeners in the <a href="https://beta.tinybase.org/guides/the-basics/listening-to-stores/">Listening To Stores</a> guide.</p></section>
23
+ <section><h2 id="register-listeners-at-any-granularity">Register listeners at any granularity.</h2><p>The magic starts to happen when you register listeners on a <a href="https://tinybase.org/api/store/type-aliases/store/value/"><code>Value</code></a>, <a href="https://tinybase.org/api/store/type-aliases/store/table/"><code>Table</code></a>, <a href="https://tinybase.org/api/store/type-aliases/store/row/"><code>Row</code></a>, or <a href="https://tinybase.org/api/store/type-aliases/store/cell/"><code>Cell</code></a>. They get called when any part of that object changes. You can also use wildcards - useful when you don&#x27;t know the <a href="https://tinybase.org/api/common/type-aliases/identity/id/"><code>Id</code></a> of the objects that might change.</p><p>Read more about listeners in the <a href="https://tinybase.org/guides/the-basics/listening-to-stores/">Listening To Stores</a> guide.</p></section>
13
24
 
14
25
  ```js
15
26
  const listenerId = store.addTableListener('pets', () =>
@@ -22,7 +33,7 @@ store.setCell('pets', 'fido', 'sold', false);
22
33
  store.delListener(listenerId);
23
34
  ```
24
35
 
25
- <section><h2 id="call-hooks-to-bind-to-data">Call hooks to bind to data.</h2><p>If you&#x27;re using React in your application, the optional <a href="https://beta.tinybase.org/api/ui-react/"><code>ui-react</code></a> module provides hooks to bind to the data in a <a href="https://beta.tinybase.org/api/store/interfaces/store/store/"><code>Store</code></a>.</p><p>More magic! The <a href="https://beta.tinybase.org/api/ui-react/functions/store-hooks/usecell/"><code>useCell</code></a> hook in this example fetches the dog&#x27;s color. But it also registers a listener on that cell that will fire and re-render the component whenever the value changes.</p><p>Basically you simply describe what data you want in your user interface and TinyBase will take care of the whole lifecycle of updating it for you.</p><p>Read more about the using hooks in the <a href="https://beta.tinybase.org/guides/building-uis/using-react-hooks/">Using React Hooks</a> guide.</p></section>
36
+ <section><h2 id="call-hooks-to-bind-to-data">Call hooks to bind to data.</h2><p>If you&#x27;re using React in your application, the optional <a href="https://tinybase.org/api/ui-react/"><code>ui-react</code></a> module provides hooks to bind to the data in a <a href="https://tinybase.org/api/store/interfaces/store/store/"><code>Store</code></a>.</p><p>More magic! The <a href="https://tinybase.org/api/ui-react/functions/store-hooks/usecell/"><code>useCell</code></a> hook in this example fetches the dog&#x27;s color. But it also registers a listener on that cell that will fire and re-render the component whenever the value changes.</p><p>Basically you simply describe what data you want in your user interface and TinyBase will take care of the whole lifecycle of updating it for you.</p><p>Read more about the using hooks in the <a href="https://tinybase.org/guides/building-uis/using-react-hooks/">Using React Hooks</a> guide.</p></section>
26
37
 
27
38
  ```jsx
28
39
  const App1 = () => {
@@ -41,7 +52,7 @@ console.log(app.innerHTML);
41
52
  // -> 'Color: walnut'
42
53
  ```
43
54
 
44
- <section><h2 id="use-components-to-make-reactive-apps">Use components to make reactive apps.</h2><p>The react module provides simple React components with bindings that make it easy to create a fully reactive user interface based on a <a href="https://beta.tinybase.org/api/store/interfaces/store/store/"><code>Store</code></a>.</p><p>In this example, the library&#x27;s <a href="https://beta.tinybase.org/api/ui-react/functions/store-components/rowview/"><code>RowView</code></a> component just needs a reference to the <a href="https://beta.tinybase.org/api/store/interfaces/store/store/"><code>Store</code></a>, the <code>tableId</code>, and the <code>rowId</code> in order to render the contents of that row. An optional <code>cellComponent</code> prop lets you override how you want each <a href="https://beta.tinybase.org/api/store/type-aliases/store/cell/"><code>Cell</code></a> rendered. Again, all the listeners and updates are taken care of for you.</p><p>The module also includes a context Provider that sets up default for an entire app to use, reducing the need to drill all your props down into your app&#x27;s hierarchy.</p><p>Most of the demos showcase the use of these React hooks and components. Take a look at <a href="https://beta.tinybase.org/demos/todo-app/todo-app-v1-the-basics/">Todo App v1 (the basics)</a> to see these user interface binding patterns in action.</p><p>Read more about the <a href="https://beta.tinybase.org/api/ui-react/"><code>ui-react</code></a> module in the <a href="https://beta.tinybase.org/guides/building-uis/">Building UIs</a> guides.</p></section>
55
+ <section><h2 id="use-components-for-reactive-apps">Use components for reactive apps.</h2><p>The react module provides simple React components with bindings that make it easy to create a fully reactive user interface based on a <a href="https://tinybase.org/api/store/interfaces/store/store/"><code>Store</code></a>.</p><p>In this example, the library&#x27;s <a href="https://tinybase.org/api/ui-react/functions/store-components/rowview/"><code>RowView</code></a> component just needs a reference to the <a href="https://tinybase.org/api/store/interfaces/store/store/"><code>Store</code></a>, the <code>tableId</code>, and the <code>rowId</code> in order to render the contents of that row. An optional <code>cellComponent</code> prop lets you override how you want each <a href="https://tinybase.org/api/store/type-aliases/store/cell/"><code>Cell</code></a> rendered. Again, all the listeners and updates are taken care of for you.</p><p>The module also includes a context Provider that sets up default for an entire app to use, reducing the need to drill all your props down into your app&#x27;s hierarchy.</p><p>Most of the demos showcase the use of these React hooks and components. Take a look at <a href="https://tinybase.org/demos/todo-app/todo-app-v1-the-basics/">Todo App v1 (the basics)</a> to see these user interface binding patterns in action.</p><p>Read more about the <a href="https://tinybase.org/api/ui-react/"><code>ui-react</code></a> module in the <a href="https://tinybase.org/guides/building-uis/">Building UIs</a> guides.</p></section>
45
56
 
46
57
  ```jsx
47
58
  const MyCellView = (props) => (
@@ -71,7 +82,7 @@ console.log(app.innerHTML);
71
82
  root.unmount();
72
83
  ```
73
84
 
74
- <section><h2 id="apply-schemas-to-tables">Apply schemas to tables.</h2><p>By default, a <a href="https://beta.tinybase.org/api/store/type-aliases/store/row/"><code>Row</code></a> can contain any arbitrary <a href="https://beta.tinybase.org/api/store/type-aliases/store/cell/"><code>Cell</code></a>. But you can add a schema to a <a href="https://beta.tinybase.org/api/store/interfaces/store/store/"><code>Store</code></a> to ensure that the values are always what you expect. For example, you can limit their types, and provide defaults. You can also create mutating listeners that can programmatically enforce a schema.</p><p>In this example, we set a second <a href="https://beta.tinybase.org/api/store/type-aliases/store/row/"><code>Row</code></a> without the <code>sold</code> <a href="https://beta.tinybase.org/api/store/type-aliases/store/cell/"><code>Cell</code></a> in it. The schema ensures it&#x27;s present with default of <code>false</code>.</p><p>Read more about schemas in the <a href="https://beta.tinybase.org/guides/schemas-and-persistence/using-schemas/">Using Schemas</a> guide.</p></section>
85
+ <section><h2 id="apply-schemas-to-tables-and-values">Apply schemas to tables and values.</h2><p>By default, a <a href="https://tinybase.org/api/store/interfaces/store/store/"><code>Store</code></a> can contain any arbitrary <a href="https://tinybase.org/api/store/type-aliases/store/value/"><code>Value</code></a>, and a <a href="https://tinybase.org/api/store/type-aliases/store/row/"><code>Row</code></a> can contain any arbitrary <a href="https://tinybase.org/api/store/type-aliases/store/cell/"><code>Cell</code></a>. But you can add a <a href="https://tinybase.org/api/store/type-aliases/schema/valuesschema/"><code>ValuesSchema</code></a> or a <a href="https://tinybase.org/api/store/type-aliases/schema/tablesschema/"><code>TablesSchema</code></a> to a <a href="https://tinybase.org/api/store/interfaces/store/store/"><code>Store</code></a> to ensure that the values are always what you expect: constraining their types, and providing defaults.</p><p>In this example, we set a second <a href="https://tinybase.org/api/store/type-aliases/store/row/"><code>Row</code></a> without the <code>sold</code> <a href="https://tinybase.org/api/store/type-aliases/store/cell/"><code>Cell</code></a> in it. The schema ensures it&#x27;s present with default of <code>false</code>.</p><p>Read more about schemas in the <a href="https://tinybase.org/guides/schemas-and-persistence/using-schemas/">Using Schemas</a> guide.</p></section>
75
86
 
76
87
  ```js
77
88
  store.setTablesSchema({
@@ -89,20 +100,20 @@ console.log(store.getRow('pets', 'felix'));
89
100
  store.delTablesSchema();
90
101
  ```
91
102
 
92
- <section><h2 id="persist-data-to-browser-file-or-server">Persist data to browser, file, or server.</h2><p>You can easily persist a <a href="https://beta.tinybase.org/api/store/interfaces/store/store/"><code>Store</code></a> between browser page reloads or sessions. You can also synchronize it with a web endpoint, or (if you&#x27;re using TinyBase in an appropriate environment), load and save it to a file.</p><p>Read more about persisters in the <a href="https://beta.tinybase.org/guides/schemas-and-persistence/persisting-data/">Persisting Data</a> guide.</p></section>
103
+ <section><h2 id="persist-data-to-browser-file-or-server">Persist data to browser, file, or server.</h2><p>You can easily persist a <a href="https://tinybase.org/api/store/interfaces/store/store/"><code>Store</code></a> between browser page reloads or sessions. You can also synchronize it with a web endpoint, or (if you&#x27;re using TinyBase in an appropriate environment), load and save it to a file.</p><p>Read more about persisters in the <a href="https://tinybase.org/guides/schemas-and-persistence/persisting-data/">Persisting Data</a> guide.</p></section>
93
104
 
94
105
  ```js
95
106
  const persister = createSessionPersister(store, 'demo');
96
107
  await persister.save();
97
108
 
98
109
  console.log(sessionStorage.getItem('demo'));
99
- // -> '[{"pets":{"fido":{"species":"dog","color":"walnut","sold":true},"felix":{"species":"cat","sold":false}}},{}]'
110
+ // -> '[{"pets":{"fido":{"species":"dog","color":"walnut","sold":true},"felix":{"species":"cat","sold":false}}},{"employees":3,"open":true}]'
100
111
 
101
112
  persister.destroy();
102
113
  sessionStorage.clear();
103
114
  ```
104
115
 
105
- <section><h2 id="build-complex-queries-with-tinyql">Build complex queries with <a href="https://beta.tinybase.org/guides/making-queries/tinyql/">TinyQL</a>.</h2><p>The <a href="https://beta.tinybase.org/api/queries/interfaces/queries/queries/"><code>Queries</code></a> object lets you query data across tables, with filtering and aggregation - using a SQL-adjacent syntax called <a href="https://beta.tinybase.org/guides/making-queries/tinyql/">TinyQL</a>.</p><p>Accessors and listeners let you sort and paginate the results efficiently, making building rich tabular interfaces easier than ever.</p><p>In this example, we have two tables: of pets and their owners. They are joined together by the pet&#x27;s ownerId <a href="https://beta.tinybase.org/api/store/type-aliases/store/cell/"><code>Cell</code></a>. We select the pet&#x27;s species, and the owner&#x27;s state, and then aggregate the prices for the combinations.</p><p>We access the results by descending price, essentially answering the question: &quot;which is the highest-priced species, and in which state?&quot;</p><p>Needless to say, the results are reactive too! You can add listeners to queries just as easily as you do to raw tables.</p><p>Read more about <a href="https://beta.tinybase.org/api/queries/interfaces/queries/queries/"><code>Queries</code></a> in the <a href="https://beta.tinybase.org/guides/releases/#v2-0">v2.0 Release Notes</a>, the <a href="https://beta.tinybase.org/guides/making-queries/">Making Queries</a> guide, and the <a href="https://beta.tinybase.org/demos/car-analysis/">Car Analysis</a> demo and <a href="https://beta.tinybase.org/demos/movie-database/">Movie Database</a> demo.</p></section>
116
+ <section><h2 id="build-complex-queries-with-tinyql">Build complex queries with <a href="https://tinybase.org/guides/making-queries/tinyql/">TinyQL</a>.</h2><p>The <a href="https://tinybase.org/api/queries/interfaces/queries/queries/"><code>Queries</code></a> object lets you query data across tables, with filtering and aggregation - using a SQL-adjacent syntax called <a href="https://tinybase.org/guides/making-queries/tinyql/">TinyQL</a>.</p><p>Accessors and listeners let you sort and paginate the results efficiently, making building rich tabular interfaces easier than ever.</p><p>In this example, we have two tables: of pets and their owners. They are joined together by the pet&#x27;s ownerId <a href="https://tinybase.org/api/store/type-aliases/store/cell/"><code>Cell</code></a>. We select the pet&#x27;s species, and the owner&#x27;s state, and then aggregate the prices for the combinations.</p><p>We access the results by descending price, essentially answering the question: &quot;which is the highest-priced species, and in which state?&quot;</p><p>Needless to say, the results are reactive too! You can add listeners to queries just as easily as you do to raw tables.</p><p>Read more about <a href="https://tinybase.org/api/queries/interfaces/queries/queries/"><code>Queries</code></a> in the <a href="https://tinybase.org/guides/releases/#v2-0">v2.0 Release Notes</a>, the <a href="https://tinybase.org/guides/making-queries/">Making Queries</a> guide, and the <a href="https://tinybase.org/demos/car-analysis/">Car Analysis</a> demo and <a href="https://tinybase.org/demos/movie-database/">Movie Database</a> demo.</p></section>
106
117
 
107
118
  ```js
108
119
  store
@@ -143,7 +154,7 @@ queries
143
154
  queries.destroy();
144
155
  ```
145
156
 
146
- <section><h2 id="define-metrics-and-aggregations">Define metrics and aggregations.</h2><p>A <a href="https://beta.tinybase.org/api/metrics/interfaces/metrics/metrics/"><code>Metrics</code></a> object makes it easy to keep a running aggregation of <a href="https://beta.tinybase.org/api/store/type-aliases/store/cell/"><code>Cell</code></a> values in each <a href="https://beta.tinybase.org/api/store/type-aliases/store/row/"><code>Row</code></a> of a <a href="https://beta.tinybase.org/api/store/type-aliases/store/table/"><code>Table</code></a>. This is useful for counting rows, but also supports averages, ranges of values, or arbitrary aggregations.</p><p>In this example, we create a new table of the pet species, and keep a track of which is most expensive. When we add horses to our pet store, the listener detects that the highest price has changed.</p><p>Read more about <a href="https://beta.tinybase.org/api/metrics/interfaces/metrics/metrics/"><code>Metrics</code></a> in the <a href="https://beta.tinybase.org/guides/metrics-and-indexes/using-metrics/">Using Metrics</a> guide.</p></section>
157
+ <section><h2 id="define-metrics-and-aggregations">Define metrics and aggregations.</h2><p>A <a href="https://tinybase.org/api/metrics/interfaces/metrics/metrics/"><code>Metrics</code></a> object makes it easy to keep a running aggregation of <a href="https://tinybase.org/api/store/type-aliases/store/cell/"><code>Cell</code></a> values in each <a href="https://tinybase.org/api/store/type-aliases/store/row/"><code>Row</code></a> of a <a href="https://tinybase.org/api/store/type-aliases/store/table/"><code>Table</code></a>. This is useful for counting rows, but also supports averages, ranges of values, or arbitrary aggregations.</p><p>In this example, we create a new table of the pet species, and keep a track of which is most expensive. When we add horses to our pet store, the listener detects that the highest price has changed.</p><p>Read more about <a href="https://tinybase.org/api/metrics/interfaces/metrics/metrics/"><code>Metrics</code></a> in the <a href="https://tinybase.org/guides/metrics-and-indexes/using-metrics/">Using Metrics</a> guide.</p></section>
147
158
 
148
159
  ```js
149
160
  store.setTable('species', {
@@ -172,7 +183,7 @@ store.setCell('species', 'horse', 'price', 20);
172
183
  metrics.destroy();
173
184
  ```
174
185
 
175
- <section><h2 id="create-indexes-for-fast-lookups">Create indexes for fast lookups.</h2><p>An <a href="https://beta.tinybase.org/api/indexes/interfaces/indexes/indexes/"><code>Indexes</code></a> object makes it easy to look up all the <a href="https://beta.tinybase.org/api/store/type-aliases/store/row/"><code>Row</code></a> objects that have a certain value in a <a href="https://beta.tinybase.org/api/store/type-aliases/store/cell/"><code>Cell</code></a>.</p><p>In this example, we create an index on the <code>species</code> <a href="https://beta.tinybase.org/api/store/type-aliases/store/cell/"><code>Cell</code></a> values. We can then get the the list of distinct <a href="https://beta.tinybase.org/api/store/type-aliases/store/cell/"><code>Cell</code></a> value present for that index (known as &#x27;slices&#x27;), and the set of <a href="https://beta.tinybase.org/api/store/type-aliases/store/row/"><code>Row</code></a> objects that match each value.</p><p><a href="https://beta.tinybase.org/api/indexes/interfaces/indexes/indexes/"><code>Indexes</code></a> objects are reactive too. So you can set listeners on them just as you do for the data in the underlying <a href="https://beta.tinybase.org/api/store/interfaces/store/store/"><code>Store</code></a>.</p><p>Read more about <a href="https://beta.tinybase.org/api/indexes/interfaces/indexes/indexes/"><code>Indexes</code></a> in the <a href="https://beta.tinybase.org/guides/metrics-and-indexes/using-indexes/">Using Indexes</a> guide.</p></section>
186
+ <section><h2 id="create-indexes-for-fast-lookups">Create indexes for fast lookups.</h2><p>An <a href="https://tinybase.org/api/indexes/interfaces/indexes/indexes/"><code>Indexes</code></a> object makes it easy to look up all the <a href="https://tinybase.org/api/store/type-aliases/store/row/"><code>Row</code></a> objects that have a certain value in a <a href="https://tinybase.org/api/store/type-aliases/store/cell/"><code>Cell</code></a>.</p><p>In this example, we create an index on the <code>species</code> <a href="https://tinybase.org/api/store/type-aliases/store/cell/"><code>Cell</code></a> values. We can then get the the list of distinct <a href="https://tinybase.org/api/store/type-aliases/store/cell/"><code>Cell</code></a> value present for that index (known as &#x27;slices&#x27;), and the set of <a href="https://tinybase.org/api/store/type-aliases/store/row/"><code>Row</code></a> objects that match each value.</p><p><a href="https://tinybase.org/api/indexes/interfaces/indexes/indexes/"><code>Indexes</code></a> objects are reactive too. So you can set listeners on them just as you do for the data in the underlying <a href="https://tinybase.org/api/store/interfaces/store/store/"><code>Store</code></a>.</p><p>Read more about <a href="https://tinybase.org/api/indexes/interfaces/indexes/indexes/"><code>Indexes</code></a> in the <a href="https://tinybase.org/guides/metrics-and-indexes/using-indexes/">Using Indexes</a> guide.</p></section>
176
187
 
177
188
  ```js
178
189
  const indexes = createIndexes(store);
@@ -196,7 +207,7 @@ store.setRow('pets', 'lowly', {species: 'worm'});
196
207
  indexes.destroy();
197
208
  ```
198
209
 
199
- <section><h2 id="model-relationships-between-tables">Model relationships between tables.</h2><p>A <a href="https://beta.tinybase.org/api/relationships/interfaces/relationships/relationships/"><code>Relationships</code></a> object lets you associate a <a href="https://beta.tinybase.org/api/store/type-aliases/store/row/"><code>Row</code></a> in a local <a href="https://beta.tinybase.org/api/store/type-aliases/store/table/"><code>Table</code></a> with the <a href="https://beta.tinybase.org/api/common/type-aliases/identity/id/"><code>Id</code></a> of a <a href="https://beta.tinybase.org/api/store/type-aliases/store/row/"><code>Row</code></a> in a remote <a href="https://beta.tinybase.org/api/store/type-aliases/store/table/"><code>Table</code></a>. You can also reference a table to itself to create linked lists.</p><p>In this example, the <code>species</code> <a href="https://beta.tinybase.org/api/store/type-aliases/store/cell/"><code>Cell</code></a> of the <code>pets</code> <a href="https://beta.tinybase.org/api/store/type-aliases/store/table/"><code>Table</code></a> is used to create a relationship to the <code>species</code> <a href="https://beta.tinybase.org/api/store/type-aliases/store/table/"><code>Table</code></a>, so that we can access the price of a given pet.</p><p>Like everything else, you can set listeners on <a href="https://beta.tinybase.org/api/relationships/interfaces/relationships/relationships/"><code>Relationships</code></a> too.</p><p>Read more about <a href="https://beta.tinybase.org/api/relationships/interfaces/relationships/relationships/"><code>Relationships</code></a> in the <a href="https://beta.tinybase.org/guides/relationships-and-checkpoints/using-relationships/">Using Relationships</a> guide.</p></section>
210
+ <section><h2 id="model-relationships-between-tables">Model relationships between tables.</h2><p>A <a href="https://tinybase.org/api/relationships/interfaces/relationships/relationships/"><code>Relationships</code></a> object lets you associate a <a href="https://tinybase.org/api/store/type-aliases/store/row/"><code>Row</code></a> in a local <a href="https://tinybase.org/api/store/type-aliases/store/table/"><code>Table</code></a> with the <a href="https://tinybase.org/api/common/type-aliases/identity/id/"><code>Id</code></a> of a <a href="https://tinybase.org/api/store/type-aliases/store/row/"><code>Row</code></a> in a remote <a href="https://tinybase.org/api/store/type-aliases/store/table/"><code>Table</code></a>. You can also reference a table to itself to create linked lists.</p><p>In this example, the <code>species</code> <a href="https://tinybase.org/api/store/type-aliases/store/cell/"><code>Cell</code></a> of the <code>pets</code> <a href="https://tinybase.org/api/store/type-aliases/store/table/"><code>Table</code></a> is used to create a relationship to the <code>species</code> <a href="https://tinybase.org/api/store/type-aliases/store/table/"><code>Table</code></a>, so that we can access the price of a given pet.</p><p>Like everything else, you can set listeners on <a href="https://tinybase.org/api/relationships/interfaces/relationships/relationships/"><code>Relationships</code></a> too.</p><p>Read more about <a href="https://tinybase.org/api/relationships/interfaces/relationships/relationships/"><code>Relationships</code></a> in the <a href="https://tinybase.org/guides/relationships-and-checkpoints/using-relationships/">Using Relationships</a> guide.</p></section>
200
211
 
201
212
  ```js
202
213
  const relationships = createRelationships(store);
@@ -219,7 +230,7 @@ console.log(
219
230
  relationships.destroy();
220
231
  ```
221
232
 
222
- <section><h2 id="set-checkpoints-for-an-undo-stack">Set checkpoints for an undo stack.</h2><p>A <a href="https://beta.tinybase.org/api/checkpoints/interfaces/checkpoints/checkpoints/"><code>Checkpoints</code></a> object lets you set checkpoints on a <a href="https://beta.tinybase.org/api/store/interfaces/store/store/"><code>Store</code></a>. Move forward and backward through them to create undo and redo functions.</p><p>In this example, we set a checkpoint, then sell one of the pets. Later, the pet is brought back to the shop, and we go back to that checkpoint to revert the store to its previous state.</p><p>Read more about <a href="https://beta.tinybase.org/api/checkpoints/interfaces/checkpoints/checkpoints/"><code>Checkpoints</code></a> in the <a href="https://beta.tinybase.org/guides/relationships-and-checkpoints/using-checkpoints/">Using Checkpoints</a> guide.</p></section>
233
+ <section><h2 id="set-checkpoints-for-an-undo-stack">Set checkpoints for an undo stack.</h2><p>A <a href="https://tinybase.org/api/checkpoints/interfaces/checkpoints/checkpoints/"><code>Checkpoints</code></a> object lets you set checkpoints on a <a href="https://tinybase.org/api/store/interfaces/store/store/"><code>Store</code></a>. Move forward and backward through them to create undo and redo functions.</p><p>In this example, we set a checkpoint, then sell one of the pets. Later, the pet is brought back to the shop, and we go back to that checkpoint to revert the store to its previous state.</p><p>Read more about <a href="https://tinybase.org/api/checkpoints/interfaces/checkpoints/checkpoints/"><code>Checkpoints</code></a> in the <a href="https://tinybase.org/guides/relationships-and-checkpoints/using-checkpoints/">Using Checkpoints</a> guide.</p></section>
223
234
 
224
235
  ```js
225
236
  const checkpoints = createCheckpoints(store);
@@ -236,7 +247,7 @@ console.log(store.getCell('pets', 'felix', 'sold'));
236
247
  // -> false
237
248
  ```
238
249
 
239
- <section><h2 id="generate-orm-like-apis">Generate ORM-like APIs</h2><p>You can easily create TypeScript <code>.d.ts</code> definitions that model your data and encourage type-safety when reading and writing data - as well as <code>.ts</code> implementations that provide ORM-like methods for your named tables.</p><p>Read more about TinyBase&#x27;s tools and CLI in the <a href="https://beta.tinybase.org/guides/developer-tools/">Developer Tools</a> guide.</p></section>
250
+ <section><h2 id="generate-orm-like-apis">Generate ORM-like APIs</h2><p>You can easily create TypeScript <code>.d.ts</code> definitions that model your data and encourage type-safety when reading and writing data - as well as <code>.ts</code> implementations that provide ORM-like methods for your named tables.</p><p>Read more about TinyBase&#x27;s tools and CLI in the <a href="https://tinybase.org/guides/developer-tools/">Developer Tools</a> guide.</p></section>
240
251
 
241
252
  ```js yolo
242
253
  const tools = createTools(store);
@@ -255,4 +266,4 @@ export const createShop: typeof createShopDecl = () => {
255
266
  };
256
267
  ```
257
268
 
258
- <section><h2 id="did-we-say-tiny">Did we say tiny?</h2><p>If you use the basic <a href="https://beta.tinybase.org/api/store/"><code>store</code></a> module alone, you&#x27;ll only add a gzipped <em>4.2kB</em> to your app. You can incrementally add the other modules as you need more functionality, or get it all for <em>8.7kB</em>.</p><p>The <code>ui-react</code> adaptor is just another <em>3.4kB</em>, the developer <a href="https://beta.tinybase.org/api/tools/"><code>tools</code></a> module is <em>5.4kB</em>, and everything is fast. Life&#x27;s easy when you have zero dependencies!</p><p>Read more about how TinyBase is structured in the <a href="https://beta.tinybase.org/guides/how-tinybase-is-built/architecture/">Architecture</a> guide.</p></section><div class="table"><table class="fixed"><tbody><tr><th width="30%"> </th><th>.js.gz</th><th>.js</th><th>debug.js</th><th>.d.ts</th></tr><tr><th class="right"><a href="https://beta.tinybase.org/api/store/">store</a></th><td>4.2kB</td><td>10.0kB</td><td>43.8kB</td><td>183.8kB</td></tr><tr><th class="right"><a href="https://beta.tinybase.org/api/metrics/">metrics</a></th><td>1.8kB</td><td>3.6kB</td><td>14.8kB</td><td>29.1kB</td></tr><tr><th class="right"><a href="https://beta.tinybase.org/api/indexes/">indexes</a></th><td>1.9kB</td><td>3.7kB</td><td>16.6kB</td><td>33.9kB</td></tr><tr><th class="right"><a href="https://beta.tinybase.org/api/relationships/">relationships</a></th><td>1.8kB</td><td>3.6kB</td><td>16.8kB</td><td>42.1kB</td></tr><tr><th class="right"><a href="https://beta.tinybase.org/api/queries/">queries</a></th><td>2.6kB</td><td>5.5kB</td><td>24.9kB</td><td>106.8kB</td></tr><tr><th class="right"><a href="https://beta.tinybase.org/api/checkpoints/">checkpoints</a></th><td>1.5kB</td><td>3.0kB</td><td>12.5kB</td><td>33.4kB</td></tr><tr><th class="right"><a href="https://beta.tinybase.org/api/persisters/">persisters</a></th><td>0.8kB</td><td>1.7kB</td><td>5.2kB</td><td>27.2kB</td></tr><tr><th class="right"><a href="https://beta.tinybase.org/api/common/">common</a></th><td>0.1kB</td><td>0.1kB</td><td>0.1kB</td><td>3.5kB</td></tr><tr><th class="right">tinybase (all)</th><td>8.7kB</td><td>21.0kB</td><td>92.7kB</td><td>0.3kB</td></tr></tbody></table></div><section><h2 id="well-tested-and-documented">Well tested and documented.</h2><p>TinyBase has <em>100.0%</em> test coverage, including the code throughout the documentation - even on this page! The guides, demos, and API examples are designed to make it as easy as possible to get up and running.</p><p>Read more about how TinyBase is tested in the Unit <a href="https://beta.tinybase.org/guides/how-tinybase-is-built/testing/">Testing</a> guide.</p></section><div class="table"><table class="fixed"><tbody><tr><th width="30%"> </th><th>Total</th><th>Tested</th><th>Coverage</th></tr><tr><th class="right">Lines</th><td>1,790</td><td>1,790</td><td>100.0%</td></tr><tr><th class="right">Statements</th><td>1,928</td><td>1,928</td><td>100.0%</td></tr><tr><th class="right">Functions</th><td>766</td><td>766</td><td>100.0%</td></tr><tr><th class="right">Branches</th><td>628</td><td>628</td><td>100.0%</td></tr><tr><th class="right">Tests</th><td colspan="3">2,579</td></tr><tr><th class="right">Assertions</th><td colspan="3">12,399</td></tr></tbody></table></div><hr><p><a class="start" href="https://beta.tinybase.org/guides/the-basics/getting-started/">Get started</a></p><p><a href="https://beta.tinybase.org/demos/">Try the demos</a></p><p><a href="https://beta.tinybase.org/api/store/interfaces/store/store/">Read the docs</a></p><hr><section><h2 id="follow">Follow</h2><ul><li>News and updates on <a href="https://twitter.com/tinybasejs">Twitter</a> and <a href="https://facebook.com/tinybasejs">Facebook</a>.</li><li><a href="https://beta.tinybase.org/guides/releases/">Release notes</a> for each version.</li><li>Packages on <a href="https://www.npmjs.com/package/tinybase/v/3.0.0-beta.1">NPM</a>.</li><li><a href="https://github.com/tinyplex/tinybase/issues">Issues</a> on <a href="https://github.com/tinyplex/tinybase">GitHub</a>.</li></ul></section><section><h2 id="about">About</h2><p>Building TinyBase was originally an interesting exercise for <a rel="me" href="https://hachyderm.io/@jamesgpearce">me</a> in API design, minification, and documentation. It could not have been built without these great <a href="https://beta.tinybase.org/guides/how-tinybase-is-built/credits/#giants">projects</a> and <a href="https://beta.tinybase.org/guides/how-tinybase-is-built/credits/#and-friends">friends</a>, and I hope you enjoy using it!</p></section>
269
+ <section><h2 id="did-we-say-tiny">Did we say tiny?</h2><p>If you use the basic <a href="https://tinybase.org/api/store/"><code>store</code></a> module alone, you&#x27;ll only add a gzipped <em>4.2kB</em> to your app. You can incrementally add the other modules as you need more functionality, or get it all for <em>8.7kB</em>.</p><p>The <a href="https://tinybase.org/api/ui-react/"><code>ui-react</code></a> module is just another <em>3.4kB</em>, the <a href="https://tinybase.org/api/tools/"><code>tools</code></a> module is <em>5.4kB</em>, and everything is fast. Life&#x27;s easy when you have zero dependencies!</p><p>Read more about how TinyBase is structured in the <a href="https://tinybase.org/guides/how-tinybase-is-built/architecture/">Architecture</a> guide.</p></section><div class="table"><table class="fixed"><tbody><tr><th width="30%"> </th><th>.js.gz</th><th>.js</th><th>debug.js</th><th>.d.ts</th></tr><tr><th class="right"><a href="https://tinybase.org/api/store/">store</a></th><td>4.2kB</td><td>10.0kB</td><td>43.9kB</td><td>184.6kB</td></tr><tr><th class="right"><a href="https://tinybase.org/api/metrics/">metrics</a></th><td>1.8kB</td><td>3.6kB</td><td>14.8kB</td><td>29.1kB</td></tr><tr><th class="right"><a href="https://tinybase.org/api/indexes/">indexes</a></th><td>1.9kB</td><td>3.7kB</td><td>16.6kB</td><td>33.9kB</td></tr><tr><th class="right"><a href="https://tinybase.org/api/relationships/">relationships</a></th><td>1.8kB</td><td>3.6kB</td><td>16.8kB</td><td>42.1kB</td></tr><tr><th class="right"><a href="https://tinybase.org/api/queries/">queries</a></th><td>2.6kB</td><td>5.5kB</td><td>24.9kB</td><td>106.8kB</td></tr><tr><th class="right"><a href="https://tinybase.org/api/checkpoints/">checkpoints</a></th><td>1.5kB</td><td>3.0kB</td><td>12.5kB</td><td>33.4kB</td></tr><tr><th class="right"><a href="https://tinybase.org/api/persisters/">persisters</a></th><td>0.8kB</td><td>1.7kB</td><td>5.2kB</td><td>27.2kB</td></tr><tr><th class="right"><a href="https://tinybase.org/api/common/">common</a></th><td>0.1kB</td><td>0.1kB</td><td>0.1kB</td><td>3.5kB</td></tr><tr><th class="right">tinybase (all)</th><td>8.7kB</td><td>21.1kB</td><td>92.8kB</td><td>0.3kB</td></tr></tbody></table></div><section><h2 id="well-tested-and-documented">Well tested and documented.</h2><p>TinyBase has <em>100.0%</em> test coverage, including the code throughout the documentation - even on this page! The guides, demos, and API examples are designed to make it as easy as possible to get up and running.</p><p>Read more about how TinyBase is tested in the Unit <a href="https://tinybase.org/guides/how-tinybase-is-built/testing/">Testing</a> guide.</p></section><div class="table"><table class="fixed"><tbody><tr><th width="30%"> </th><th>Total</th><th>Tested</th><th>Coverage</th></tr><tr><th class="right">Lines</th><td>1,794</td><td>1,794</td><td>100.0%</td></tr><tr><th class="right">Statements</th><td>1,932</td><td>1,932</td><td>100.0%</td></tr><tr><th class="right">Functions</th><td>768</td><td>768</td><td>100.0%</td></tr><tr><th class="right">Branches</th><td>628</td><td>628</td><td>100.0%</td></tr><tr><th class="right">Tests</th><td colspan="3">2,581</td></tr><tr><th class="right">Assertions</th><td colspan="3">12,421</td></tr></tbody></table></div><hr><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><section><h2 id="follow">Follow</h2><ul><li>News and updates on <a href="https://twitter.com/tinybasejs">Twitter</a>, <a href="https://discord.com/invite/mGz3mevwP8">Discord</a>, and <a href="https://facebook.com/tinybasejs">Facebook</a>.</li><li><a href="https://github.com/tinyplex/tinybase/discussion">Discussions</a> and <a href="https://github.com/tinyplex/tinybase/issues">issues</a> on <a href="https://github.com/tinyplex/tinybase">GitHub</a>.</li><li><a href="https://tinybase.org/guides/releases/">Release notes</a> for each version.</li><li>Packages on <a href="https://www.npmjs.com/package/tinybase/v/3.0.0">NPM</a>.</li></ul></section><section><h2 id="about">About</h2><p>Building TinyBase was originally an interesting exercise for <a rel="me" href="https://hachyderm.io/@jamesgpearce">me</a> in API design, minification, and documentation. It could not have been built without these great <a href="https://tinybase.org/guides/how-tinybase-is-built/credits/#giants">projects</a> and <a href="https://tinybase.org/guides/how-tinybase-is-built/credits/#and-friends">friends</a>, and I hope you enjoy using it!</p></section>