tinybase 9.3.0 → 9.4.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/@types/queries/index.d.ts +141 -7
- package/@types/queries/with-schemas/index.d.ts +153 -7
- package/@types/ui-react-dom/index.d.ts +10 -10
- package/@types/ui-react-dom/with-schemas/index.d.ts +10 -10
- package/@types/ui-react-dom-charts/index.d.ts +7 -7
- package/@types/ui-react-dom-charts/with-schemas/index.d.ts +7 -7
- package/@types/ui-react-inspector/index.d.ts +1 -1
- package/@types/ui-react-inspector/with-schemas/index.d.ts +1 -1
- package/@types/ui-solid-dom/index.d.ts +10 -10
- package/@types/ui-solid-dom/with-schemas/index.d.ts +10 -10
- package/@types/ui-solid-inspector/index.d.ts +1 -1
- package/@types/ui-solid-inspector/with-schemas/index.d.ts +1 -1
- package/@types/ui-svelte-dom/index.d.ts +10 -10
- package/@types/ui-svelte-dom/with-schemas/index.d.ts +10 -10
- package/@types/ui-svelte-inspector/index.d.ts +1 -1
- package/@types/ui-svelte-inspector/with-schemas/index.d.ts +1 -1
- package/index.js +200 -24
- package/min/index.js +1 -1
- package/min/index.js.gz +0 -0
- package/min/omni/index.js +1 -1
- package/min/omni/index.js.gz +0 -0
- package/min/omni/with-schemas/index.js +1 -1
- package/min/omni/with-schemas/index.js.gz +0 -0
- package/min/queries/index.js +1 -1
- package/min/queries/index.js.gz +0 -0
- package/min/queries/with-schemas/index.js +1 -1
- package/min/queries/with-schemas/index.js.gz +0 -0
- package/min/with-schemas/index.js +1 -1
- package/min/with-schemas/index.js.gz +0 -0
- package/omni/index.js +200 -24
- package/omni/with-schemas/index.js +200 -24
- package/package.json +1 -1
- package/queries/index.js +205 -24
- package/queries/with-schemas/index.js +205 -24
- package/readme.md +1 -1
- package/releases.md +1 -1
- package/with-schemas/index.js +200 -24
|
@@ -53,6 +53,7 @@ const arrayEvery = (array, cb) => array.every(cb);
|
|
|
53
53
|
const arrayIsEqual = (array1, array2) =>
|
|
54
54
|
size(array1) === size(array2) &&
|
|
55
55
|
arrayEvery(array1, (value1, index) => array2[index] === value1);
|
|
56
|
+
const arraySort = (array, sorter) => array.sort(sorter);
|
|
56
57
|
const arrayForEach = (array, cb) => array.forEach(cb);
|
|
57
58
|
const arraySum = (array) => arrayReduce(array, (i, j) => i + j, 0);
|
|
58
59
|
const arrayMax = (array) =>
|
|
@@ -111,6 +112,10 @@ const objNew = (entries = []) => {
|
|
|
111
112
|
arrayForEach(entries, ([id, value]) => objSet(obj, id, value));
|
|
112
113
|
return obj;
|
|
113
114
|
};
|
|
115
|
+
const objDel = (obj, id) => {
|
|
116
|
+
delete obj[id];
|
|
117
|
+
return obj;
|
|
118
|
+
};
|
|
114
119
|
const objForEach = (obj, cb) =>
|
|
115
120
|
arrayForEach(objIds(obj), (id) => cb(obj[id], id));
|
|
116
121
|
const objSize = (obj) => size(objIds(obj));
|
|
@@ -464,6 +469,8 @@ const createQueries = getCreateFunction((store) => {
|
|
|
464
469
|
};
|
|
465
470
|
const preStoreListenerIds = mapNew();
|
|
466
471
|
const sourceStoreListenerIds = mapNew();
|
|
472
|
+
const selectAllListenerIds = mapNew();
|
|
473
|
+
const referencedQueryIds = mapNew();
|
|
467
474
|
const {
|
|
468
475
|
_: [, addListener, callListeners],
|
|
469
476
|
delListener: delListenerImpl,
|
|
@@ -537,11 +544,42 @@ const createQueries = getCreateFunction((store) => {
|
|
|
537
544
|
resetStoreListeners(mapGet(sourceStoreListenerIds, queryId));
|
|
538
545
|
mapSet(sourceStoreListenerIds, queryId);
|
|
539
546
|
};
|
|
540
|
-
const
|
|
547
|
+
const syncSelectAllListeners = (queryId, nextSources = mapNew()) => {
|
|
548
|
+
const listenerIds = mapEnsure(selectAllListenerIds, queryId, mapNew);
|
|
549
|
+
mapForEach(listenerIds, (sourceStore, listenerIdsByTableId) => {
|
|
550
|
+
mapForEach(listenerIdsByTableId, (sourceTableId, listenerId) =>
|
|
551
|
+
collHas(mapGet(nextSources, sourceStore), sourceTableId)
|
|
552
|
+
? 0
|
|
553
|
+
: (sourceStore.delListener(listenerId),
|
|
554
|
+
mapSet(listenerIdsByTableId, sourceTableId)),
|
|
555
|
+
);
|
|
556
|
+
if (collIsEmpty(listenerIdsByTableId)) {
|
|
557
|
+
mapSet(listenerIds, sourceStore);
|
|
558
|
+
}
|
|
559
|
+
});
|
|
560
|
+
mapForEach(nextSources, (sourceStore, tableIds) => {
|
|
561
|
+
const listenerIdsByTableId = mapEnsure(listenerIds, sourceStore, mapNew);
|
|
562
|
+
collForEach(tableIds, (sourceTableId) =>
|
|
563
|
+
mapEnsure(listenerIdsByTableId, sourceTableId, () =>
|
|
564
|
+
sourceStore.addTableCellIdsListener(sourceTableId, () =>
|
|
565
|
+
setQueryDefinitionImpl(queryId),
|
|
566
|
+
),
|
|
567
|
+
),
|
|
568
|
+
);
|
|
569
|
+
});
|
|
570
|
+
if (collIsEmpty(listenerIds)) {
|
|
571
|
+
mapSet(selectAllListenerIds, queryId);
|
|
572
|
+
}
|
|
573
|
+
};
|
|
574
|
+
const isResultStoreReferenced = (queryId, resultStore2) =>
|
|
541
575
|
!(
|
|
542
576
|
collEvery(routedResultListeners, ([, storeListenerIds]) =>
|
|
543
577
|
collEvery(storeListenerIds, ([store2]) => store2 !== resultStore2),
|
|
544
578
|
) &&
|
|
579
|
+
collEvery(
|
|
580
|
+
referencedQueryIds,
|
|
581
|
+
(queryIds) => !collHas(queryIds, queryId),
|
|
582
|
+
) &&
|
|
545
583
|
arrayEvery(
|
|
546
584
|
[preStoreListenerIds, sourceStoreListenerIds],
|
|
547
585
|
(storeListenerIds) =>
|
|
@@ -556,7 +594,7 @@ const createQueries = getCreateFunction((store) => {
|
|
|
556
594
|
hasQuery(queryId) ? 0 : mapSet(preStores, queryId),
|
|
557
595
|
);
|
|
558
596
|
mapForEach(resultStores, (queryId, resultStore2) =>
|
|
559
|
-
hasQuery(queryId) || isResultStoreReferenced(resultStore2)
|
|
597
|
+
hasQuery(queryId) || isResultStoreReferenced(queryId, resultStore2)
|
|
560
598
|
? 0
|
|
561
599
|
: mapSet(resultStores, queryId),
|
|
562
600
|
);
|
|
@@ -638,7 +676,13 @@ const createQueries = getCreateFunction((store) => {
|
|
|
638
676
|
const rootStore = asQuery ? getResultStore(tableId) : store;
|
|
639
677
|
const resultStore2 = getResultStore(queryId);
|
|
640
678
|
const paramValues = definition?.[3] ?? getParamValues(queryId);
|
|
641
|
-
const
|
|
679
|
+
const nextReferencedQueryIds = stagedDefinition[4];
|
|
680
|
+
if (asQuery) {
|
|
681
|
+
setAdd(nextReferencedQueryIds, tableId);
|
|
682
|
+
}
|
|
683
|
+
const selectionEntries = [];
|
|
684
|
+
let hasSelectAll = false;
|
|
685
|
+
let selectCount = 0;
|
|
642
686
|
const joinEntries = [
|
|
643
687
|
[void 0, [tableId, void 0, void 0, [], mapNew(), rootStore]],
|
|
644
688
|
];
|
|
@@ -650,21 +694,35 @@ const createQueries = getCreateFunction((store) => {
|
|
|
650
694
|
const joinedTableId = isTrue(arg1) ? arg2 : arg1;
|
|
651
695
|
const joinedCellId = isTrue(arg1) ? arg3 : arg2;
|
|
652
696
|
const selectEntry = isFunction(arg1)
|
|
653
|
-
? [
|
|
697
|
+
? [0, selectCount + EMPTY_STRING, arg1]
|
|
654
698
|
: isUndefined(joinedCellId)
|
|
655
|
-
? [arg1, (getTableCell) => getTableCell(arg1)]
|
|
699
|
+
? [0, arg1, (getTableCell) => getTableCell(arg1)]
|
|
656
700
|
: [
|
|
701
|
+
0,
|
|
657
702
|
joinedCellId,
|
|
658
703
|
(getTableCell) =>
|
|
659
704
|
isTrue(arg1)
|
|
660
705
|
? getTableCell(true, joinedTableId, joinedCellId)
|
|
661
706
|
: getTableCell(joinedTableId, joinedCellId),
|
|
662
707
|
];
|
|
663
|
-
|
|
708
|
+
selectCount++;
|
|
709
|
+
arrayPush(selectionEntries, selectEntry);
|
|
664
710
|
return {
|
|
665
|
-
as: (selectedCellId) => (selectEntry[
|
|
711
|
+
as: (selectedCellId) => (selectEntry[1] = selectedCellId),
|
|
666
712
|
};
|
|
667
713
|
};
|
|
714
|
+
const selectAll = (arg1, arg2, arg3) => {
|
|
715
|
+
hasSelectAll = true;
|
|
716
|
+
const joinedTableId = isTrue(arg1) ? arg2 : arg1;
|
|
717
|
+
const prefixOrMapper = isTrue(arg1) ? arg3 : arg2;
|
|
718
|
+
arrayPush(selectionEntries, [
|
|
719
|
+
1,
|
|
720
|
+
joinedTableId,
|
|
721
|
+
isFunction(prefixOrMapper)
|
|
722
|
+
? prefixOrMapper
|
|
723
|
+
: (cellId) => (prefixOrMapper ?? EMPTY_STRING) + cellId,
|
|
724
|
+
]);
|
|
725
|
+
};
|
|
668
726
|
const join = (arg1, arg2, arg3, arg4) => {
|
|
669
727
|
const joinedTableId = isTrue(arg1) ? arg2 : arg1;
|
|
670
728
|
const [fromJoinAlias, onArg] = isTrue(arg1)
|
|
@@ -674,6 +732,13 @@ const createQueries = getCreateFunction((store) => {
|
|
|
674
732
|
: isUndefined(arg3) || isFunction(arg2)
|
|
675
733
|
? [void 0, arg2]
|
|
676
734
|
: [arg2, arg3];
|
|
735
|
+
const sourceIsQuery = isTrue(arg1);
|
|
736
|
+
const sourceStore = sourceIsQuery
|
|
737
|
+
? getResultStore(joinedTableId)
|
|
738
|
+
: store;
|
|
739
|
+
if (sourceIsQuery) {
|
|
740
|
+
setAdd(nextReferencedQueryIds, joinedTableId);
|
|
741
|
+
}
|
|
677
742
|
const joinEntry = [
|
|
678
743
|
joinedTableId,
|
|
679
744
|
[
|
|
@@ -682,7 +747,7 @@ const createQueries = getCreateFunction((store) => {
|
|
|
682
747
|
isFunction(onArg) ? onArg : (getCell) => getCell(onArg),
|
|
683
748
|
[],
|
|
684
749
|
mapNew(),
|
|
685
|
-
|
|
750
|
+
sourceStore,
|
|
686
751
|
],
|
|
687
752
|
];
|
|
688
753
|
arrayPush(joinEntries, joinEntry);
|
|
@@ -728,10 +793,9 @@ const createQueries = getCreateFunction((store) => {
|
|
|
728
793
|
: (getSelectedOrGroupedCell) =>
|
|
729
794
|
getSelectedOrGroupedCell(arg1) === arg2,
|
|
730
795
|
);
|
|
731
|
-
build({select, join, where, group, having, param});
|
|
796
|
+
build({select, selectAll, join, where, group, having, param});
|
|
732
797
|
resultStore2.delTable(queryId);
|
|
733
|
-
|
|
734
|
-
if (collIsEmpty(selects)) {
|
|
798
|
+
if (isEmpty(selectionEntries)) {
|
|
735
799
|
commit?.();
|
|
736
800
|
return queries;
|
|
737
801
|
}
|
|
@@ -743,6 +807,55 @@ const createQueries = getCreateFunction((store) => {
|
|
|
743
807
|
);
|
|
744
808
|
const groups = mapNew(groupEntries);
|
|
745
809
|
const hasGroupsOrHavings = !collIsEmpty(groups) || !isEmpty(havings);
|
|
810
|
+
const selectEntries = [];
|
|
811
|
+
const selectAllSources = stagedDefinition[3];
|
|
812
|
+
const getSelectAllJoin = (joinedTableId) =>
|
|
813
|
+
mapGet(
|
|
814
|
+
joins,
|
|
815
|
+
isUndefined(joinedTableId) || joinedTableId === tableId
|
|
816
|
+
? void 0
|
|
817
|
+
: joinedTableId,
|
|
818
|
+
);
|
|
819
|
+
arrayForEach(
|
|
820
|
+
selectionEntries,
|
|
821
|
+
([selectAll2, selectedOrJoinedTableId, clauseOrMapper]) => {
|
|
822
|
+
if (selectAll2) {
|
|
823
|
+
if (hasGroupsOrHavings) {
|
|
824
|
+
ifNotUndefined(
|
|
825
|
+
getSelectAllJoin(selectedOrJoinedTableId),
|
|
826
|
+
([realTableId, , , , , sourceStore]) => {
|
|
827
|
+
setAdd(
|
|
828
|
+
mapEnsure(selectAllSources, sourceStore, setNew),
|
|
829
|
+
realTableId,
|
|
830
|
+
);
|
|
831
|
+
arrayForEach(
|
|
832
|
+
arraySort(sourceStore.getTableCellIds(realTableId)),
|
|
833
|
+
(cellId) =>
|
|
834
|
+
arrayPush(selectEntries, [
|
|
835
|
+
clauseOrMapper(cellId),
|
|
836
|
+
(getTableCell) =>
|
|
837
|
+
isUndefined(selectedOrJoinedTableId) ||
|
|
838
|
+
selectedOrJoinedTableId === tableId
|
|
839
|
+
? getTableCell(cellId)
|
|
840
|
+
: getTableCell(selectedOrJoinedTableId, cellId),
|
|
841
|
+
]),
|
|
842
|
+
);
|
|
843
|
+
},
|
|
844
|
+
);
|
|
845
|
+
}
|
|
846
|
+
} else {
|
|
847
|
+
arrayPush(selectEntries, [
|
|
848
|
+
selectedOrJoinedTableId,
|
|
849
|
+
clauseOrMapper,
|
|
850
|
+
]);
|
|
851
|
+
}
|
|
852
|
+
},
|
|
853
|
+
);
|
|
854
|
+
const selects = mapNew(selectEntries);
|
|
855
|
+
if (hasGroupsOrHavings && collIsEmpty(selects)) {
|
|
856
|
+
commit?.();
|
|
857
|
+
return queries;
|
|
858
|
+
}
|
|
746
859
|
const selectJoinWhereStore = hasGroupsOrHavings
|
|
747
860
|
? (stagedDefinition[2] = createStore())
|
|
748
861
|
: resultStore2;
|
|
@@ -921,18 +1034,78 @@ const createQueries = getCreateFunction((store) => {
|
|
|
921
1034
|
joinedCellId,
|
|
922
1035
|
);
|
|
923
1036
|
};
|
|
924
|
-
selectJoinWhereStore.transaction(() =>
|
|
925
|
-
arrayEvery(wheres, (where2) => where2(getJoinCell))
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
1037
|
+
selectJoinWhereStore.transaction(() => {
|
|
1038
|
+
if (!arrayEvery(wheres, (where2) => where2(getJoinCell))) {
|
|
1039
|
+
selectJoinWhereStore.delRow(queryId, rootRowId);
|
|
1040
|
+
} else if (hasGroupsOrHavings) {
|
|
1041
|
+
mapForEach(selects, (asCellId, tableCellGetter) =>
|
|
1042
|
+
selectJoinWhereStore._[5](
|
|
1043
|
+
queryId,
|
|
1044
|
+
rootRowId,
|
|
1045
|
+
asCellId,
|
|
1046
|
+
tableCellGetter(getJoinCell, rootRowId),
|
|
1047
|
+
),
|
|
1048
|
+
);
|
|
1049
|
+
} else if (hasSelectAll) {
|
|
1050
|
+
const resultRow = objNew();
|
|
1051
|
+
arrayForEach(
|
|
1052
|
+
selectionEntries,
|
|
1053
|
+
([selectAll2, selectedOrJoinedTableId, clauseOrMapper]) => {
|
|
1054
|
+
if (selectAll2) {
|
|
1055
|
+
ifNotUndefined(
|
|
1056
|
+
getSelectAllJoin(selectedOrJoinedTableId),
|
|
1057
|
+
([realTableId, , , , remoteIdPairs, sourceStore]) => {
|
|
1058
|
+
const sourceRowId =
|
|
1059
|
+
isUndefined(selectedOrJoinedTableId) ||
|
|
1060
|
+
selectedOrJoinedTableId === tableId
|
|
1061
|
+
? rootRowId
|
|
1062
|
+
: mapGet(remoteIdPairs, rootRowId)?.[0];
|
|
1063
|
+
ifNotUndefined(sourceRowId, (sourceRowId2) =>
|
|
1064
|
+
arrayForEach(
|
|
1065
|
+
arraySort(
|
|
1066
|
+
sourceStore.getCellIds(realTableId, sourceRowId2),
|
|
1067
|
+
),
|
|
1068
|
+
(cellId) =>
|
|
1069
|
+
objSet(
|
|
1070
|
+
resultRow,
|
|
1071
|
+
clauseOrMapper(cellId),
|
|
1072
|
+
sourceStore.getCell(
|
|
1073
|
+
realTableId,
|
|
1074
|
+
sourceRowId2,
|
|
1075
|
+
cellId,
|
|
1076
|
+
),
|
|
1077
|
+
),
|
|
1078
|
+
),
|
|
1079
|
+
);
|
|
1080
|
+
},
|
|
1081
|
+
);
|
|
1082
|
+
} else {
|
|
1083
|
+
const selectedCell = clauseOrMapper(getJoinCell, rootRowId);
|
|
1084
|
+
const selectedCellId = selectedOrJoinedTableId;
|
|
1085
|
+
if (isUndefined(selectedCell)) {
|
|
1086
|
+
objDel(resultRow, selectedCellId);
|
|
1087
|
+
} else {
|
|
1088
|
+
objSet(resultRow, selectedCellId, selectedCell);
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
},
|
|
1092
|
+
);
|
|
1093
|
+
if (objIsEmpty(resultRow)) {
|
|
1094
|
+
selectJoinWhereStore.delRow(queryId, rootRowId);
|
|
1095
|
+
} else {
|
|
1096
|
+
selectJoinWhereStore.setRow(queryId, rootRowId, resultRow);
|
|
1097
|
+
}
|
|
1098
|
+
} else {
|
|
1099
|
+
mapForEach(selects, (asCellId, tableCellGetter) =>
|
|
1100
|
+
selectJoinWhereStore._[5](
|
|
1101
|
+
queryId,
|
|
1102
|
+
rootRowId,
|
|
1103
|
+
asCellId,
|
|
1104
|
+
tableCellGetter(getJoinCell, rootRowId),
|
|
1105
|
+
),
|
|
1106
|
+
);
|
|
1107
|
+
}
|
|
1108
|
+
});
|
|
936
1109
|
};
|
|
937
1110
|
const listenToTable = (
|
|
938
1111
|
rootRowId,
|
|
@@ -1051,7 +1224,7 @@ const createQueries = getCreateFunction((store) => {
|
|
|
1051
1224
|
}),
|
|
1052
1225
|
);
|
|
1053
1226
|
const setQueryDefinitionImpl = (queryId, definition) => {
|
|
1054
|
-
const stagedDefinition = [mapNew(), mapNew()];
|
|
1227
|
+
const stagedDefinition = [mapNew(), mapNew(), void 0, mapNew(), setNew()];
|
|
1055
1228
|
let committed = false;
|
|
1056
1229
|
tryCatchSync(
|
|
1057
1230
|
() =>
|
|
@@ -1072,6 +1245,12 @@ const createQueries = getCreateFunction((store) => {
|
|
|
1072
1245
|
collIsEmpty(stagedDefinition[1]) ? void 0 : stagedDefinition[1],
|
|
1073
1246
|
);
|
|
1074
1247
|
mapSet(preStores, queryId, stagedDefinition[2]);
|
|
1248
|
+
syncSelectAllListeners(queryId, stagedDefinition[3]);
|
|
1249
|
+
mapSet(
|
|
1250
|
+
referencedQueryIds,
|
|
1251
|
+
queryId,
|
|
1252
|
+
collIsEmpty(stagedDefinition[4]) ? void 0 : stagedDefinition[4],
|
|
1253
|
+
);
|
|
1075
1254
|
committed = true;
|
|
1076
1255
|
}),
|
|
1077
1256
|
(error) => {
|
|
@@ -1121,6 +1300,8 @@ const createQueries = getCreateFunction((store) => {
|
|
|
1121
1300
|
paramStore.delRow(PARAMS_TABLE, queryId);
|
|
1122
1301
|
resetPreStores(queryId);
|
|
1123
1302
|
resetSourceStores(queryId);
|
|
1303
|
+
syncSelectAllListeners(queryId);
|
|
1304
|
+
mapSet(referencedQueryIds, queryId);
|
|
1124
1305
|
delDefinition(queryId);
|
|
1125
1306
|
cleanStores();
|
|
1126
1307
|
return queries;
|
package/readme.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
📦 Creating your project...
|
|
7
7
|
```
|
|
8
8
|
|
|
9
|
-
<hr><section><h2 id="it-s-reactive">It's <em>Reactive</em></h2><p>TinyBase lets you <a href="#register-granular-listeners">listen to changes</a> made to any part of your data. This means your app will be fast, since you only spend rendering cycles on things that change. The optional <a href="#call-hooks-to-bind-to-data">bindings to React</a>, <a href="#pre-built-reactive-components">pre-built components</a>, and <a href="#local-first-data-visualizations">charts</a> let you easily build fully reactive UIs on top of TinyBase. You even get a built-in <a href="#set-checkpoints-for-an-undo-stack">undo stack</a>, and <a href="#an-inspector-for-your-data">developer tools</a>!</p></section><section><h2 id="it-s-database-like">It's <em>Database-Like</em></h2><p>Consumer app? Enterprise app? Or even a game? Model <a href="#start-with-a-simple-key-value-store">key-value data</a> and <a href="#level-up-to-use-tabular-data">tabular data</a> with optional typed <a href="#apply-schemas-to-tables-values">schematization</a>, whatever its data structures. There are built-in <a href="#create-indexes-for-fast-lookups">indexing</a>, <a href="#define-metrics-and-aggregations">metric aggregation</a>, and tabular <a href="#model-table-relationships">relationships</a> APIs - and a powerful <a href="#build-complex-queries-with-tinyql">query engine</a> to select, join, filter, and group data (reactively!) without SQL.</p></section><section><h2 id="it-synchronizes">It <em>Synchronizes</em></h2><p>TinyBase has <a href="#synchronize-between-devices">native CRDT</a> support, meaning that you can deterministically <a href="https://beta.tinybase.org/guides/synchronization/">synchronize</a> and merge data across multiple sources, clients, and servers. And although TinyBase is an in-memory data store, you can easily <a href="#persist-to-storage-or-a-database">persist</a> your data to file, <a href="https://beta.tinybase.org/api/persister-browser">browser storage</a>, <a href="https://beta.tinybase.org/api/persister-indexed-db">IndexedDB</a>, <a href="https://beta.tinybase.org/guides/persistence/database-persistence/">SQLite or PostgreSQL databases</a>, and <a href="https://beta.tinybase.org/guides/persistence/third-party-crdt-persistence/">more</a>.</p></section><section><h2 id="it-s-built-for-a-local-first-world">It's Built For A <em>Local-First</em> World</h2><p>TinyBase works anywhere that JavaScript does, but it's especially great for local-first apps: where data is stored locally on the user's device and that can be run offline. It's tiny by name, tiny by nature: just <a href="#did-we-say-tiny">7.2kB - 15.6kB</a> and with no dependencies - yet <a href="#well-tested-and-documented">100% tested</a>, <a href="https://beta.tinybase.org/guides/the-basics/getting-started/">fully documented</a>, and of course, <a href="https://github.com/tinyplex/tinybase">open source</a>!</p></section><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><h3 id="ui-frameworks"><a href="https://beta.tinybase.org/guides/">UI Frameworks</a></h3><div><a href="https://beta.tinybase.org/guides/building-uis-with-react/getting-started-with-ui-react"><img src="https://beta.tinybase.org/react.svg?asImg" width="48"> React</a></div><div><a href="https://beta.tinybase.org/guides/building-uis-with-solid/getting-started-with-ui-solid"><img src="https://beta.tinybase.org/solid.svg?asImg" width="48"> Solid</a></div><div><a href="https://beta.tinybase.org/guides/building-uis-with-svelte/getting-started-with-ui-svelte"><img src="https://beta.tinybase.org/svelte.svg?asImg" width="48"> Svelte</a></div><h3 id="storage-sync"><a href="https://beta.tinybase.org/guides/persistence/">Storage & Sync</a></h3><div><a href="https://beta.tinybase.org/api/persister-indexed-db/functions/creation/createindexeddbpersister"><img src="https://beta.tinybase.org/indexeddb.svg?asImg" width="48"> IndexedDB</a></div><div><a href="https://beta.tinybase.org/api/persister-browser"><img src="https://beta.tinybase.org/browser.svg?asImg" width="48"> OPFS</a></div><div><a href="https://beta.tinybase.org/guides/integrations/cloudflare-durable-objects"><img src="https://beta.tinybase.org/cloudflare.svg?asImg" width="48"> Cloudflare</a></div><div><a href="https://beta.tinybase.org/guides/schemas-and-persistence/database-persistence"><img src="https://beta.tinybase.org/postgresql.svg?asImg" width="48"> PostgreSQL</a></div><div><a href="https://beta.tinybase.org/guides/schemas-and-persistence/database-persistence"><img src="https://beta.tinybase.org/pglite.svg?asImg" width="48"> PGlite</a></div><div><a href="https://beta.tinybase.org/guides/schemas-and-persistence/database-persistence"><img src="https://beta.tinybase.org/sqlite.svg?asImg" width="48"> SQLite</a></div><div><a href="https://beta.tinybase.org/guides/schemas-and-persistence/database-persistence"><img src="https://beta.tinybase.org/bun.svg?asImg" width="48"> Bun SQLite</a></div><div><a href="https://beta.tinybase.org/guides/schemas-and-persistence/database-persistence"><img src="https://beta.tinybase.org/expo.svg?asImg" width="48"> Expo SQLite</a></div><div><a href="https://beta.tinybase.org/guides/schemas-and-persistence/database-persistence"><img src="https://beta.tinybase.org/electric.svg?asImg" width="48"> ElectricSQL</a></div><div><a href="https://beta.tinybase.org/guides/schemas-and-persistence/database-persistence"><img src="https://beta.tinybase.org/turso.svg?asImg" width="48"> Turso</a></div><div><a href="https://beta.tinybase.org/guides/schemas-and-persistence/database-persistence"><img src="https://beta.tinybase.org/powersync.svg?asImg" width="48"> PowerSync</a></div><div><a href="https://beta.tinybase.org/api/persister-partykit-client"><img src="https://beta.tinybase.org/partykit.svg?asImg" width="48"> PartyKit</a></div><div><a href="https://beta.tinybase.org/api/persister-yjs/functions/creation/createyjspersister"><img src="https://beta.tinybase.org/yjs.svg?asImg" width="48"> YJS</a></div><div><a href="https://beta.tinybase.org/api/persister-cr-sqlite-wasm"><img src="https://beta.tinybase.org/crsqlite.png" width="48"> CR-SQLite</a></div><div><a href="https://beta.tinybase.org/api/persister-automerge"><img src="https://beta.tinybase.org/automerge.svg?asImg" width="48"> Automerge</a></div><h3 id="schema-systems"><a href="https://beta.tinybase.org/guides/schemas/using-schematizers/">Schema Systems</a></h3><div><a href="https://beta.tinybase.org/api/schematizer-zod/functions/creation/createzodschematizer"><img src="https://beta.tinybase.org/zod.svg?asImg" width="48"> Zod</a></div><div><a href="https://beta.tinybase.org/api/schematizer-typebox/functions/creation/createtypeboxschematizer"><img src="https://beta.tinybase.org/typebox.svg?asImg" width="48"> TypeBox</a></div><div><a href="https://beta.tinybase.org/api/schematizer-valibot/functions/creation/createvalibotschematizer"><img src="https://beta.tinybase.org/valibot.svg?asImg" width="48"> Valibot</a></div><div><a href="https://beta.tinybase.org/api/schematizer-arktype/functions/creation/createarktypeschematizer"><img src="https://beta.tinybase.org/arktype.svg?asImg" width="48"> ArkType</a></div><div><a href="https://beta.tinybase.org/api/schematizer-yup/functions/creation/createyupschematizer"><img src="https://beta.tinybase.org/yup.svg?asImg" width="48"> Yup</a></div><div><a href="https://beta.tinybase.org/api/schematizer-effect/functions/creation/createeffectschematizer"><img src="https://beta.tinybase.org/effect.svg?asImg" width="48"> Effect</a></div><p>(Baffled by all these logos? Check out our <a href="https://beta.tinybase.org/guides/the-basics/architectural-options">architectural options</a> guide to make sense of it all!)</p></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&logo=GitHub&logoColor=%23fff&label=GitHub&labelColor=%23d81b60&color=%23333"> </a><a href="https://bsky.app/profile/tinybase.bsky.social"><img src="https://img.shields.io/badge/Bluesky-Follow-blue?style=for-the-badge&logo=bluesky&logoColor=%23fff&color=%23333&labelColor=%230285FF"> </a><a href="https://x.com/tinybasejs" target="_blank"><img src="https://img.shields.io/badge/%2F%20Twitter-Follow-blue?style=for-the-badge&logo=x&logoColor=%23fff&color=%23333&labelColor=%23000"> </a><a href="https://discord.com/invite/mGz3mevwP8" target="_blank"><img src="https://img.shields.io/discord/1027918215323590676?style=for-the-badge&logo=discord&logoColor=%23fff&label=Discord&labelColor=%233131e8&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&logo=GitHub&logoColor=%23fff&label=Ideas&labelColor=%23d81b60&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&logo=GitHub&logoColor=%23fff&label=Issues&labelColor=%23d81b60&color=%23333"> </a><a href="#well-tested-and-documented"><img src="https://img.shields.io/badge/Tests-100%25-green?style=for-the-badge&logo=Vitest&logoColor=%23fff&color=%23333&labelColor=%2387c305"> </a><a href="https://www.npmjs.com/package/tinybase/v/9.3.0-beta.1" target="_blank"><img src="https://img.shields.io/npm/v/tinybase?style=for-the-badge&logo=npm&logoColor=%23fff&labelColor=%23bd0005&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://beta.tinybase.org/api/the-essentials/creating-stores/store/"><code>Store</code></a> requires just a simple call to the <a href="https://beta.tinybase.org/api/the-essentials/creating-stores/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/values/"><code>Values</code></a> in it by unique <a href="https://beta.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://beta.tinybase.org/guides/the-basics/">The Basics</a> guide.</p></section>
|
|
9
|
+
<hr><section><h2 id="it-s-reactive">It's <em>Reactive</em></h2><p>TinyBase lets you <a href="#register-granular-listeners">listen to changes</a> made to any part of your data. This means your app will be fast, since you only spend rendering cycles on things that change. The optional <a href="#call-hooks-to-bind-to-data">bindings to React</a>, <a href="#pre-built-reactive-components">pre-built components</a>, and <a href="#local-first-data-visualizations">charts</a> let you easily build fully reactive UIs on top of TinyBase. You even get a built-in <a href="#set-checkpoints-for-an-undo-stack">undo stack</a>, and <a href="#an-inspector-for-your-data">developer tools</a>!</p></section><section><h2 id="it-s-database-like">It's <em>Database-Like</em></h2><p>Consumer app? Enterprise app? Or even a game? Model <a href="#start-with-a-simple-key-value-store">key-value data</a> and <a href="#level-up-to-use-tabular-data">tabular data</a> with optional typed <a href="#apply-schemas-to-tables-values">schematization</a>, whatever its data structures. There are built-in <a href="#create-indexes-for-fast-lookups">indexing</a>, <a href="#define-metrics-and-aggregations">metric aggregation</a>, and tabular <a href="#model-table-relationships">relationships</a> APIs - and a powerful <a href="#build-complex-queries-with-tinyql">query engine</a> to select, join, filter, and group data (reactively!) without SQL.</p></section><section><h2 id="it-synchronizes">It <em>Synchronizes</em></h2><p>TinyBase has <a href="#synchronize-between-devices">native CRDT</a> support, meaning that you can deterministically <a href="https://beta.tinybase.org/guides/synchronization/">synchronize</a> and merge data across multiple sources, clients, and servers. And although TinyBase is an in-memory data store, you can easily <a href="#persist-to-storage-or-a-database">persist</a> your data to file, <a href="https://beta.tinybase.org/api/persister-browser">browser storage</a>, <a href="https://beta.tinybase.org/api/persister-indexed-db">IndexedDB</a>, <a href="https://beta.tinybase.org/guides/persistence/database-persistence/">SQLite or PostgreSQL databases</a>, and <a href="https://beta.tinybase.org/guides/persistence/third-party-crdt-persistence/">more</a>.</p></section><section><h2 id="it-s-built-for-a-local-first-world">It's Built For A <em>Local-First</em> World</h2><p>TinyBase works anywhere that JavaScript does, but it's especially great for local-first apps: where data is stored locally on the user's device and that can be run offline. It's tiny by name, tiny by nature: just <a href="#did-we-say-tiny">7.2kB - 15.6kB</a> and with no dependencies - yet <a href="#well-tested-and-documented">100% tested</a>, <a href="https://beta.tinybase.org/guides/the-basics/getting-started/">fully documented</a>, and of course, <a href="https://github.com/tinyplex/tinybase">open source</a>!</p></section><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><h3 id="ui-frameworks"><a href="https://beta.tinybase.org/guides/">UI Frameworks</a></h3><div><a href="https://beta.tinybase.org/guides/building-uis-with-react/getting-started-with-ui-react"><img src="https://beta.tinybase.org/react.svg?asImg" width="48"> React</a></div><div><a href="https://beta.tinybase.org/guides/building-uis-with-solid/getting-started-with-ui-solid"><img src="https://beta.tinybase.org/solid.svg?asImg" width="48"> Solid</a></div><div><a href="https://beta.tinybase.org/guides/building-uis-with-svelte/getting-started-with-ui-svelte"><img src="https://beta.tinybase.org/svelte.svg?asImg" width="48"> Svelte</a></div><h3 id="storage-sync"><a href="https://beta.tinybase.org/guides/persistence/">Storage & Sync</a></h3><div><a href="https://beta.tinybase.org/api/persister-indexed-db/functions/creation/createindexeddbpersister"><img src="https://beta.tinybase.org/indexeddb.svg?asImg" width="48"> IndexedDB</a></div><div><a href="https://beta.tinybase.org/api/persister-browser"><img src="https://beta.tinybase.org/browser.svg?asImg" width="48"> OPFS</a></div><div><a href="https://beta.tinybase.org/guides/integrations/cloudflare-durable-objects"><img src="https://beta.tinybase.org/cloudflare.svg?asImg" width="48"> Cloudflare</a></div><div><a href="https://beta.tinybase.org/guides/schemas-and-persistence/database-persistence"><img src="https://beta.tinybase.org/postgresql.svg?asImg" width="48"> PostgreSQL</a></div><div><a href="https://beta.tinybase.org/guides/schemas-and-persistence/database-persistence"><img src="https://beta.tinybase.org/pglite.svg?asImg" width="48"> PGlite</a></div><div><a href="https://beta.tinybase.org/guides/schemas-and-persistence/database-persistence"><img src="https://beta.tinybase.org/sqlite.svg?asImg" width="48"> SQLite</a></div><div><a href="https://beta.tinybase.org/guides/schemas-and-persistence/database-persistence"><img src="https://beta.tinybase.org/bun.svg?asImg" width="48"> Bun SQLite</a></div><div><a href="https://beta.tinybase.org/guides/schemas-and-persistence/database-persistence"><img src="https://beta.tinybase.org/expo.svg?asImg" width="48"> Expo SQLite</a></div><div><a href="https://beta.tinybase.org/guides/schemas-and-persistence/database-persistence"><img src="https://beta.tinybase.org/electric.svg?asImg" width="48"> ElectricSQL</a></div><div><a href="https://beta.tinybase.org/guides/schemas-and-persistence/database-persistence"><img src="https://beta.tinybase.org/turso.svg?asImg" width="48"> Turso</a></div><div><a href="https://beta.tinybase.org/guides/schemas-and-persistence/database-persistence"><img src="https://beta.tinybase.org/powersync.svg?asImg" width="48"> PowerSync</a></div><div><a href="https://beta.tinybase.org/api/persister-partykit-client"><img src="https://beta.tinybase.org/partykit.svg?asImg" width="48"> PartyKit</a></div><div><a href="https://beta.tinybase.org/api/persister-yjs/functions/creation/createyjspersister"><img src="https://beta.tinybase.org/yjs.svg?asImg" width="48"> YJS</a></div><div><a href="https://beta.tinybase.org/api/persister-cr-sqlite-wasm"><img src="https://beta.tinybase.org/crsqlite.png" width="48"> CR-SQLite</a></div><div><a href="https://beta.tinybase.org/api/persister-automerge"><img src="https://beta.tinybase.org/automerge.svg?asImg" width="48"> Automerge</a></div><h3 id="schema-systems"><a href="https://beta.tinybase.org/guides/schemas/using-schematizers/">Schema Systems</a></h3><div><a href="https://beta.tinybase.org/api/schematizer-zod/functions/creation/createzodschematizer"><img src="https://beta.tinybase.org/zod.svg?asImg" width="48"> Zod</a></div><div><a href="https://beta.tinybase.org/api/schematizer-typebox/functions/creation/createtypeboxschematizer"><img src="https://beta.tinybase.org/typebox.svg?asImg" width="48"> TypeBox</a></div><div><a href="https://beta.tinybase.org/api/schematizer-valibot/functions/creation/createvalibotschematizer"><img src="https://beta.tinybase.org/valibot.svg?asImg" width="48"> Valibot</a></div><div><a href="https://beta.tinybase.org/api/schematizer-arktype/functions/creation/createarktypeschematizer"><img src="https://beta.tinybase.org/arktype.svg?asImg" width="48"> ArkType</a></div><div><a href="https://beta.tinybase.org/api/schematizer-yup/functions/creation/createyupschematizer"><img src="https://beta.tinybase.org/yup.svg?asImg" width="48"> Yup</a></div><div><a href="https://beta.tinybase.org/api/schematizer-effect/functions/creation/createeffectschematizer"><img src="https://beta.tinybase.org/effect.svg?asImg" width="48"> Effect</a></div><p>(Baffled by all these logos? Check out our <a href="https://beta.tinybase.org/guides/the-basics/architectural-options">architectural options</a> guide to make sense of it all!)</p></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&logo=GitHub&logoColor=%23fff&label=GitHub&labelColor=%23d81b60&color=%23333"> </a><a href="https://bsky.app/profile/tinybase.bsky.social"><img src="https://img.shields.io/badge/Bluesky-Follow-blue?style=for-the-badge&logo=bluesky&logoColor=%23fff&color=%23333&labelColor=%230285FF"> </a><a href="https://x.com/tinybasejs" target="_blank"><img src="https://img.shields.io/badge/%2F%20Twitter-Follow-blue?style=for-the-badge&logo=x&logoColor=%23fff&color=%23333&labelColor=%23000"> </a><a href="https://discord.com/invite/mGz3mevwP8" target="_blank"><img src="https://img.shields.io/discord/1027918215323590676?style=for-the-badge&logo=discord&logoColor=%23fff&label=Discord&labelColor=%233131e8&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&logo=GitHub&logoColor=%23fff&label=Ideas&labelColor=%23d81b60&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&logo=GitHub&logoColor=%23fff&label=Issues&labelColor=%23d81b60&color=%23333"> </a><a href="#well-tested-and-documented"><img src="https://img.shields.io/badge/Tests-100%25-green?style=for-the-badge&logo=Vitest&logoColor=%23fff&color=%23333&labelColor=%2387c305"> </a><a href="https://www.npmjs.com/package/tinybase/v/9.4.0-beta.0" target="_blank"><img src="https://img.shields.io/npm/v/tinybase?style=for-the-badge&logo=npm&logoColor=%23fff&labelColor=%23bd0005&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://beta.tinybase.org/api/the-essentials/creating-stores/store/"><code>Store</code></a> requires just a simple call to the <a href="https://beta.tinybase.org/api/the-essentials/creating-stores/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/values/"><code>Values</code></a> in it by unique <a href="https://beta.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://beta.tinybase.org/guides/the-basics/">The Basics</a> guide.</p></section>
|
|
10
10
|
|
|
11
11
|
```js
|
|
12
12
|
import {createStore} from 'tinybase';
|
package/releases.md
CHANGED
|
@@ -35,7 +35,7 @@ await employeesSynchronizer.destroy();
|
|
|
35
35
|
await multistoreServer.destroy();
|
|
36
36
|
```
|
|
37
37
|
|
|
38
|
-
<p>Each channel extends the base URL path, so the example uses the logical paths <code>petShop/pets</code> and <code>petShop/employees</code>. Legacy clients can connect directly to those full paths, while omitting the channel <a href="https://beta.tinybase.org/api/common/type-aliases/identity/id/"><code>Id</code></a> retains the existing signature and wire protocol. Multiplexing is supported by <a href="https://beta.tinybase.org/api/synchronizer-ws-server/interfaces/server/wsserver/"><code>WsServer</code></a> and <a href="https://beta.tinybase.org/api/synchronizer-ws-server-simple/interfaces/server/wsserversimple/"><code>WsServerSimple</code></a>; <a href="https://beta.tinybase.org/api/the-essentials/synchronizing-stores/wsserverdurableobject/"><code>WsServerDurableObject</code></a> continues to use one URL path and Durable Object per WebSocket.</p><p>Channel <a href="https://beta.tinybase.org/api/common/type-aliases/identity/ids/"><code>Ids</code></a> are not an authorization boundary: a client accepted on a base path can subscribe to any valid descendant channel. Authenticate and isolate untrusted clients by base path, and do not treat client <a href="https://beta.tinybase.org/api/common/type-aliases/identity/ids/"><code>Ids</code></a> derived from <code>Sec-WebSocket-Key</code> as authenticated identities.</p><h2 id="reliability-and-hardening">Reliability And Hardening</h2><h3 id="core-data-and-apis">Core Data And APIs</h3><ul><li>Arbitrary <a href="https://beta.tinybase.org/api/common/type-aliases/identity/ids/"><code>Ids</code></a> such as <code>__proto__</code>, <code>constructor</code>, and <code>toString</code> are now safe throughout <a href="https://beta.tinybase.org/api/the-essentials/creating-stores/store/"><code>Store</code></a>, <a href="https://beta.tinybase.org/api/mergeable-store/interfaces/mergeable/mergeablestore/"><code>MergeableStore</code></a>, synchronization, and persistence.</li><li>Errors from transaction actions and pre-commit callbacks now roll back content, schemas, <a href="https://beta.tinybase.org/api/mergeable-store/interfaces/mergeable/mergeablestore/"><code>MergeableStore</code></a> stamps and hashes, and temporary state across <a href="https://beta.tinybase.org/api/the-essentials/creating-stores/store/"><code>Store</code></a>, <a href="https://beta.tinybase.org/api/mergeable-store/interfaces/mergeable/mergeablestore/"><code>MergeableStore</code></a>, and <a href="https://beta.tinybase.org/api/checkpoints/interfaces/checkpoints/checkpoints/"><code>Checkpoints</code></a>; nested failures roll back the shared outer transaction, while post-commit listener failures no longer strand the <a href="https://beta.tinybase.org/api/the-essentials/creating-stores/store/"><code>Store</code></a>.</li><li>Query definitions are staged before commit, and new <a href="https://beta.tinybase.org/api/indexes/type-aliases/concept/index/"><code>Index</code></a>, <a href="https://beta.tinybase.org/api/metrics/type-aliases/metric/metric/"><code>Metric</code></a>, Query, and <a href="https://beta.tinybase.org/api/relationships/type-aliases/concept/relationship/"><code>Relationship</code></a> definitions are discarded if their <a href="https://beta.tinybase.org/api/common/type-aliases/identity/ids/"><code>Ids</code></a> listener throws.</li><li>Deleting a Query definition now releases cached pre- and result Stores once nothing still references them.</li><li><a href="https://beta.tinybase.org/api/mergeable-store/interfaces/mergeable/mergeablestore/"><code>MergeableStore</code></a> now atomically rejects HLCs that are not exactly 16 characters or are over five minutes in the future, carries overflowing 24-bit counters into wall-clock time, and verifies local stamps without mutating rejected caller payloads.</li><li>String Cells, <a href="https://beta.tinybase.org/api/store/type-aliases/store/values/"><code>Values</code></a>, and schema defaults using TinyBase's reserved leading <code>U+FFFD</code> or exact <code>U+FFFC</code> encodings are rejected, while invalid or schema-incompatible persisted encodings are ignored safely.</li><li>Object and array Cells and <a href="https://beta.tinybase.org/api/store/type-aliases/store/values/"><code>Values</code></a> now have an explicit JSON-compatible content contract, reject unserializable data, and no longer modify caller-owned or frozen containers during bulk writes.</li><li><a href="https://beta.tinybase.org/api/queries/interfaces/queries/queries/"><code>Queries</code></a> and <a href="https://beta.tinybase.org/api/indexes/interfaces/indexes/indexes/"><code>Indexes</code></a> now group, sort, and index equivalent object and array values consistently without stale results; direct rich <a href="https://beta.tinybase.org/api/indexes/type-aliases/concept/index/"><code>Index</code></a> keys remain distinct from custom-function arrays that select multiple Slices.</li><li><a href="https://beta.tinybase.org/api/store/type-aliases/schema/tablesschema/"><code>TablesSchema</code></a> and <a href="https://beta.tinybase.org/api/store/type-aliases/schema/valuesschema/"><code>ValuesSchema</code></a> objects are cloned before normalization, making frozen schemas reusable and preserving the previous schema after an invalid replacement; schema JSON getters also preserve object and array defaults.</li><li><a href="https://beta.tinybase.org/api/middleware/interfaces/middleware/middleware/"><code>Middleware</code></a> receives cloned object and array values in public JavaScript form, with callback results validated and encoded only at the <a href="https://beta.tinybase.org/api/the-essentials/creating-stores/store/"><code>Store</code></a> boundary.</li><li><a href="https://beta.tinybase.org/api/middleware/interfaces/middleware/middleware/"><code>Middleware</code></a> and <a href="https://beta.tinybase.org/api/checkpoints/interfaces/checkpoints/checkpoints/"><code>Checkpoints</code></a> now clean up <a href="https://beta.tinybase.org/api/the-essentials/creating-stores/store/"><code>Store</code></a> registrations safely, avoid duplicate listeners after recreation, and skip phantom checkpoints for structurally unchanged rich content.</li></ul><h3 id="synchronization"><a href="https://beta.tinybase.org/guides/synchronization/">Synchronization</a></h3><ul><li><a href="https://beta.tinybase.org/api/synchronizer-ws-server/interfaces/server/wsserver/"><code>WsServer</code></a> and <a href="https://beta.tinybase.org/api/synchronizer-ws-server-simple/interfaces/server/wsserversimple/"><code>WsServerSimple</code></a> now share multiplex negotiation, decoding, channel-lifecycle, and cleanup behavior, and retain safe error listeners for their lifetimes.</li><li>Synchronizers reject pending requests on transport failure, remove built-in listeners when destroyed, and expose transport failures to custom register callbacks.</li><li><a href="https://beta.tinybase.org/api/synchronizer-broadcast-channel/interfaces/synchronizer/broadcastchannelsynchronizer/"><code>BroadcastChannelSynchronizer</code></a> validates message envelopes, while <a href="https://beta.tinybase.org/api/synchronizer-local/interfaces/synchronizer/localsynchronizer/"><code>LocalSynchronizer</code></a> snapshots scheduled recipients and cancels deliveries when destroyed.</li><li>WebSocket fragments now split by UTF-8 byte size at code-point boundaries and use at most 1,000 fragments.</li><li>Malformed WebSocket traffic reports error 14 and closes the offending peer with status <code>1007</code> before relay; complete messages and multiplex envelopes are limited to 16 MiB, with oversize input closing only its sender.</li><li>Offline and startup queues, pending requests, incomplete fragments, and socket buffering are bounded; queued traffic expires or coalesces, while overload reports error 15, closes peers with status <code>1013</code> where appropriate, and stops accepting new requests when the pending map is full.</li><li>Multiplexed channels keep independent timeout and error handling, clear failed subscriptions for retry, and settle immediately around closing or closed sockets; reconnect handshakes and queued replays cannot consume replacement connection state.</li><li><a href="https://beta.tinybase.org/api/synchronizer-ws-server/interfaces/server/wsserver/"><code>WsServer</code></a> setup, teardown, <a href="https://beta.tinybase.org/api/the-essentials/persisting-stores/persister/"><code>Persister</code></a> retries, path resubscription, and destruction now clean up deterministically; subscriptions are acknowledged before persisted startup, destroy closes clients and awaits the WebSocketServer, and stale path cleanup cannot remove replacements.</li><li>All path and client <a href="https://beta.tinybase.org/api/common/type-aliases/identity/id/"><code>Id</code></a> listeners still run when one throws, listener and ignored-error failures no longer strand server state, and <a href="https://beta.tinybase.org/api/synchronizer-ws-server-simple/interfaces/server/wsserversimple/"><code>WsServerSimple</code></a> applies the same outbound safety checks as <a href="https://beta.tinybase.org/api/synchronizer-ws-server/interfaces/server/wsserver/"><code>WsServer</code></a>.</li><li><a href="https://beta.tinybase.org/api/the-essentials/synchronizing-stores/wsserverdurableobject/"><code>WsServerDurableObject</code></a> reports an empty path before its first connection, correctly types synchronous errors and asynchronous namespace responses, and lets subclasses observe malformed traffic.</li><li>PartyKit now persists and broadcasts only authorized changes, leaves rooms uninitialized after fully rejected first writes, serializes writes, validates incoming content and stored keys, and accepts empty HTTP responses on the client.</li></ul><h3 id="persistence"><a href="https://beta.tinybase.org/guides/persistence/">Persistence</a></h3><ul><li>Remote Persisters preserve ETags until successful downloads, reject failed responses, serialize polling, and abort active polls when stopped or destroyed.</li><li><a href="https://beta.tinybase.org/api/the-essentials/persisting-stores/persister/"><code>Persister</code></a> operations now await queued work; shared schedulers attribute errors to the correct owner and survive failures in ignored-error handlers.</li><li>Repeated destruction shares one completion Promise, waits for active work, restores idle status despite cleanup failures, and releases shared scheduler state.</li><li>Concurrent automatic lifecycle calls support falsey handles, retain only the latest registration, wait for setup and cleanup during stop or destroy, and stop both auto-load and auto-save even if one cleanup fails.</li><li>Auto-load startup closes missed-change windows and preserves notification ordering; steady-state inline changes apply synchronously without redundant saves, while no-content notifications during a save coalesce into a trailing load.</li><li>Database transaction failures roll back; remote libSQL uses one transaction session while local file clients retain their connection, and tabular Persisters replace every table-name placeholder, enforce collision-safe <a href="https://beta.tinybase.org/api/store/type-aliases/store/row/"><code>Row</code></a> <a href="https://beta.tinybase.org/api/common/type-aliases/identity/id/"><code>Id</code></a> uniqueness, and reject configurations that cannot preserve merge metadata.</li><li>PostgreSQL auto-load shares notification resources until the final owner stops and releases reserved clients after setup failure.</li><li>SQLite auto-load establishes its baseline before use, defers update-hook work until the triggering write completes, drains pending work on stop, and allows asynchronous change-listener cleanup.</li><li>IndexedDB waits for transaction completion, reports aborts and blocked opens, uses read-only loads, and serializes and drains changed-content polling.</li><li>Durable Object KV persistence batches limit-safe writes transactionally and ignores unrelated or malformed keys; Durable Object SQL preserves empty <a href="https://beta.tinybase.org/api/common/type-aliases/identity/ids/"><code>Ids</code></a> and safely quotes generated table names.</li><li>React Native MMKV preserves deletion tombstones and contains malformed content and listener failures; Automerge and Yjs validate TinyBase document roots before loading or applying changes, while Automerge still allows unrelated root metadata.</li><li>Browser storage, IndexedDB, PowerSync, SQLite, PostgreSQL, Automerge, and Yjs notifications route parsing and listener failures through ignored-error handling.</li><li>File persistence atomically replaces data while preserving modes and symbolic links, and its auto-load follows replacements without duplicate self-loads; OPFS always closes successful writes and aborts failed ones.</li></ul><h3 id="ui-and-lifecycle">UI And Lifecycle</h3><ul><li>Solid primitives no longer register <a href="https://beta.tinybase.org/api/the-essentials/creating-stores/store/"><code>Store</code></a> listeners during server rendering.</li><li>React and Solid asynchronous resource hooks destroy stale or post-unmount results, hide resources during replacement, await destruction, contain failures, and serialize Solid replacements behind previous cleanup.</li><li>Solid and Svelte DOM and Inspector packages are explicitly client-only, so server builds fail cleanly during resolution.</li><li>Solid DOM table wrappers keep table, query, and slice metadata reactive, while Solid Inspector actions and custom <a href="https://beta.tinybase.org/api/store/type-aliases/store/cell/"><code>Cell</code></a> renderers react to editability and <a href="https://beta.tinybase.org/api/store/type-aliases/store/cell/"><code>Cell</code></a> <a href="https://beta.tinybase.org/api/common/type-aliases/identity/id/"><code>Id</code></a> changes.</li><li>Solid useUndoInformation and useRedoInformation now return Accessors for availability, checkpoint <a href="https://beta.tinybase.org/api/common/type-aliases/identity/id/"><code>Id</code></a>, and label values.</li><li>Object and array editors mark JSON with the wrong container type invalid, and React, Solid, and Svelte paginators normalize boundary offsets and clamp previous navigation to zero.</li><li>React and Solid paginators defer offset corrections until after rendering, and Provider registrations in all three UI modules preserve the correct resource when duplicate <a href="https://beta.tinybase.org/api/common/type-aliases/identity/id/"><code>Id</code></a> owners are removed.</li></ul><h3 id="packaging-and-performance">Packaging And Performance</h3><ul><li>TinyBase errors now use compact numeric codes documented in the <a href="https://beta.tinybase.org/guides/error-codes/">Error Codes</a> guide.</li><li>The build now classifies Svelte modules correctly and verifies generated JavaScript exports against declared public values.</li><li>Accidental React, Solid, DOM, and IndexedDB helper exports have been removed, while createDurableObjectSqlStoragePersister is exported from the omni module as declared.</li><li>The getTransactionMergeableChanges declaration now distinguishes its default unhashed result from explicitly requested hashed changes.</li><li>Internal iteration paths now avoid unnecessary allocations, while invalid <a href="https://beta.tinybase.org/api/the-essentials/creating-stores/store/"><code>Store</code></a> JSON setters complete synchronously without creating Promises or microtasks.</li><li><a href="https://beta.tinybase.org/api/metrics/type-aliases/metric/metric/"><code>Metric</code></a> extrema and Cartesian chart summaries now handle large datasets without variadic argument limits or quadratic category de-duplication.</li></ul><hr><h1 id="v9-2">v9.2</h1><p>TinyBase v9.2 makes the library easier for coding agents and AI systems to discover, evaluate, understand, and use correctly.</p><p>Although there are no changes to TinyBase source code, this is more than just an AI-specific documentation pass. The package and website metadata now describes TinyBase using the concrete problems it solves: reactive in-memory data, offline and local-first applications, persistence, CRDTs, and synchronization. This helps search and retrieval systems connect an application requirement to TinyBase before the library name is already known.</p><h2 id="agent-documentation-and-retrieval">Agent <a href="https://beta.tinybase.org/guides/how-tinybase-is-built/documentation/">Documentation</a> And Retrieval</h2><p>The website now publishes a concise <a href="https://tinybase.org/llms.txt"><code>llms.txt</code></a> orientation file and a fuller <a href="https://tinybase.org/llms-full.txt"><code>llms-full.txt</code></a> agent guide. A new <a href="https://beta.tinybase.org/guides/the-basics/why-tinybase/">Why TinyBase?</a> guide explains when TinyBase is a good architectural fit, when it is not, and which <a href="https://beta.tinybase.org/api/the-essentials/creating-stores/store/"><code>Store</code></a>, persistence, synchronization, and UI pieces to start with.</p><p>The repository also includes configuration for current documentation to be indexed by <a href="https://context7.com/">Context7</a>, as well as machine-readable structured metadata for ordinary search and retrieval systems.</p><h2 id="an-official-tinybase-skill">An Official TinyBase Skill</h2><p>The repository now contains an official <a href="https://tinybase.org/skills/build-with-tinybase/SKILL.md"><code>build-with-tinybase</code></a> agent skill. It guides coding agents through architecture selection, scaffolding, safe extension of generated applications, and behavioral verification.</p><p>The skill treats persistence and synchronization as behaviors to prove, not just code that compiles. It instructs agents to verify persisted state across a real reload and synchronized state between multiple clients.</p><h2 id="agent-friendly-scaffolding">Agent-Friendly Scaffolding</h2><p>The <a href="https://github.com/tinyplex/create-tinybase"><code>create-tinybase</code></a> scaffolding tool now exposes its current options as JSON, documents its non-interactive mode, and generates an architecture-specific <code>AGENTS.md</code> in every new project. This gives agents a deterministic path from application requirements to a complete, working TinyBase app.</p><p>Agents can inspect the current generator contract with:</p>
|
|
38
|
+
<p>Each channel extends the base URL path, so the example uses the logical paths <code>petShop/pets</code> and <code>petShop/employees</code>. Legacy clients can connect directly to those full paths, while omitting the channel <a href="https://beta.tinybase.org/api/common/type-aliases/identity/id/"><code>Id</code></a> retains the existing signature and wire protocol. Multiplexing is supported by <a href="https://beta.tinybase.org/api/synchronizer-ws-server/interfaces/server/wsserver/"><code>WsServer</code></a> and <a href="https://beta.tinybase.org/api/synchronizer-ws-server-simple/interfaces/server/wsserversimple/"><code>WsServerSimple</code></a>; <a href="https://beta.tinybase.org/api/the-essentials/synchronizing-stores/wsserverdurableobject/"><code>WsServerDurableObject</code></a> continues to use one URL path and Durable Object per WebSocket.</p><p>Channel <a href="https://beta.tinybase.org/api/common/type-aliases/identity/ids/"><code>Ids</code></a> are not an authorization boundary: a client accepted on a base path can subscribe to any valid descendant channel. Authenticate and isolate untrusted clients by base path, and do not treat client <a href="https://beta.tinybase.org/api/common/type-aliases/identity/ids/"><code>Ids</code></a> derived from <code>Sec-WebSocket-Key</code> as authenticated identities.</p><h2 id="reliability-and-hardening">Reliability And Hardening</h2><h3 id="breaking-ish-changes">Breaking-ish <a href="https://beta.tinybase.org/api/store/type-aliases/transaction/changes/"><code>Changes</code></a></h3><ul><li>Errors use compact numeric codes.</li><li>Failed transactions now rollback.</li><li>Certain previously accepted/reserved or unserializable values are rejected.</li><li>Client-only Solid and Svelte exports now correctly fail server resolution.</li><li>Solid's <a href="https://beta.tinybase.org/api/ui-react/type-aliases/checkpoints/undoorredoinformation/"><code>UndoOrRedoInformation</code></a> changed from values to accessors.</li><li>Over-eager runtime exports disappeared from the Svelte package.</li></ul><p>The full explanations for these and many other changes are below:</p><h3 id="core-data-and-apis">Core Data And APIs</h3><ul><li>Arbitrary <a href="https://beta.tinybase.org/api/common/type-aliases/identity/ids/"><code>Ids</code></a> such as <code>__proto__</code>, <code>constructor</code>, and <code>toString</code> are now safe throughout <a href="https://beta.tinybase.org/api/the-essentials/creating-stores/store/"><code>Store</code></a>, <a href="https://beta.tinybase.org/api/mergeable-store/interfaces/mergeable/mergeablestore/"><code>MergeableStore</code></a>, synchronization, and persistence.</li><li>Errors from transaction actions and pre-commit callbacks now roll back content, schemas, <a href="https://beta.tinybase.org/api/mergeable-store/interfaces/mergeable/mergeablestore/"><code>MergeableStore</code></a> stamps and hashes, and temporary state across <a href="https://beta.tinybase.org/api/the-essentials/creating-stores/store/"><code>Store</code></a>, <a href="https://beta.tinybase.org/api/mergeable-store/interfaces/mergeable/mergeablestore/"><code>MergeableStore</code></a>, and <a href="https://beta.tinybase.org/api/checkpoints/interfaces/checkpoints/checkpoints/"><code>Checkpoints</code></a>; nested failures roll back the shared outer transaction, while post-commit listener failures no longer strand the <a href="https://beta.tinybase.org/api/the-essentials/creating-stores/store/"><code>Store</code></a>.</li><li>Query definitions are staged before commit, and new <a href="https://beta.tinybase.org/api/indexes/type-aliases/concept/index/"><code>Index</code></a>, <a href="https://beta.tinybase.org/api/metrics/type-aliases/metric/metric/"><code>Metric</code></a>, Query, and <a href="https://beta.tinybase.org/api/relationships/type-aliases/concept/relationship/"><code>Relationship</code></a> definitions are discarded if their <a href="https://beta.tinybase.org/api/common/type-aliases/identity/ids/"><code>Ids</code></a> listener throws.</li><li>Deleting a Query definition now releases cached pre- and result Stores once nothing still references them.</li><li><a href="https://beta.tinybase.org/api/mergeable-store/interfaces/mergeable/mergeablestore/"><code>MergeableStore</code></a> now atomically rejects HLCs that are not exactly 16 characters or are over five minutes in the future, carries overflowing 24-bit counters into wall-clock time, and verifies local stamps without mutating rejected caller payloads.</li><li>String Cells, <a href="https://beta.tinybase.org/api/store/type-aliases/store/values/"><code>Values</code></a>, and schema defaults using TinyBase's reserved leading <code>U+FFFD</code> or exact <code>U+FFFC</code> encodings are rejected, while invalid or schema-incompatible persisted encodings are ignored safely.</li><li>Object and array Cells and <a href="https://beta.tinybase.org/api/store/type-aliases/store/values/"><code>Values</code></a> now have an explicit JSON-compatible content contract, reject unserializable data, and no longer modify caller-owned or frozen containers during bulk writes.</li><li><a href="https://beta.tinybase.org/api/queries/interfaces/queries/queries/"><code>Queries</code></a> and <a href="https://beta.tinybase.org/api/indexes/interfaces/indexes/indexes/"><code>Indexes</code></a> now group, sort, and index equivalent object and array values consistently without stale results; direct rich <a href="https://beta.tinybase.org/api/indexes/type-aliases/concept/index/"><code>Index</code></a> keys remain distinct from custom-function arrays that select multiple Slices.</li><li><a href="https://beta.tinybase.org/api/store/type-aliases/schema/tablesschema/"><code>TablesSchema</code></a> and <a href="https://beta.tinybase.org/api/store/type-aliases/schema/valuesschema/"><code>ValuesSchema</code></a> objects are cloned before normalization, making frozen schemas reusable and preserving the previous schema after an invalid replacement; schema JSON getters also preserve object and array defaults.</li><li><a href="https://beta.tinybase.org/api/middleware/interfaces/middleware/middleware/"><code>Middleware</code></a> receives cloned object and array values in public JavaScript form, with callback results validated and encoded only at the <a href="https://beta.tinybase.org/api/the-essentials/creating-stores/store/"><code>Store</code></a> boundary.</li><li><a href="https://beta.tinybase.org/api/middleware/interfaces/middleware/middleware/"><code>Middleware</code></a> and <a href="https://beta.tinybase.org/api/checkpoints/interfaces/checkpoints/checkpoints/"><code>Checkpoints</code></a> now clean up <a href="https://beta.tinybase.org/api/the-essentials/creating-stores/store/"><code>Store</code></a> registrations safely, avoid duplicate listeners after recreation, and skip phantom checkpoints for structurally unchanged rich content.</li></ul><h3 id="synchronization"><a href="https://beta.tinybase.org/guides/synchronization/">Synchronization</a></h3><ul><li><a href="https://beta.tinybase.org/api/synchronizer-ws-server/interfaces/server/wsserver/"><code>WsServer</code></a> and <a href="https://beta.tinybase.org/api/synchronizer-ws-server-simple/interfaces/server/wsserversimple/"><code>WsServerSimple</code></a> now share multiplex negotiation, decoding, channel-lifecycle, and cleanup behavior, and retain safe error listeners for their lifetimes.</li><li>Synchronizers reject pending requests on transport failure, remove built-in listeners when destroyed, and expose transport failures to custom register callbacks.</li><li><a href="https://beta.tinybase.org/api/synchronizer-broadcast-channel/interfaces/synchronizer/broadcastchannelsynchronizer/"><code>BroadcastChannelSynchronizer</code></a> validates message envelopes, while <a href="https://beta.tinybase.org/api/synchronizer-local/interfaces/synchronizer/localsynchronizer/"><code>LocalSynchronizer</code></a> snapshots scheduled recipients and cancels deliveries when destroyed.</li><li>WebSocket fragments now split by UTF-8 byte size at code-point boundaries and use at most 1,000 fragments.</li><li>Malformed WebSocket traffic reports error 14 and closes the offending peer with status <code>1007</code> before relay; complete messages and multiplex envelopes are limited to 16 MiB, with oversize input closing only its sender.</li><li>Offline and startup queues, pending requests, incomplete fragments, and socket buffering are bounded; queued traffic expires or coalesces, while overload reports error 15, closes peers with status <code>1013</code> where appropriate, and stops accepting new requests when the pending map is full.</li><li>Multiplexed channels keep independent timeout and error handling, clear failed subscriptions for retry, and settle immediately around closing or closed sockets; reconnect handshakes and queued replays cannot consume replacement connection state.</li><li><a href="https://beta.tinybase.org/api/synchronizer-ws-server/interfaces/server/wsserver/"><code>WsServer</code></a> setup, teardown, <a href="https://beta.tinybase.org/api/the-essentials/persisting-stores/persister/"><code>Persister</code></a> retries, path resubscription, and destruction now clean up deterministically; subscriptions are acknowledged before persisted startup, destroy closes clients and awaits the WebSocketServer, and stale path cleanup cannot remove replacements.</li><li>All path and client <a href="https://beta.tinybase.org/api/common/type-aliases/identity/id/"><code>Id</code></a> listeners still run when one throws, listener and ignored-error failures no longer strand server state, and <a href="https://beta.tinybase.org/api/synchronizer-ws-server-simple/interfaces/server/wsserversimple/"><code>WsServerSimple</code></a> applies the same outbound safety checks as <a href="https://beta.tinybase.org/api/synchronizer-ws-server/interfaces/server/wsserver/"><code>WsServer</code></a>.</li><li><a href="https://beta.tinybase.org/api/the-essentials/synchronizing-stores/wsserverdurableobject/"><code>WsServerDurableObject</code></a> reports an empty path before its first connection, correctly types synchronous errors and asynchronous namespace responses, and lets subclasses observe malformed traffic.</li><li>PartyKit now persists and broadcasts only authorized changes, leaves rooms uninitialized after fully rejected first writes, serializes writes, validates incoming content and stored keys, and accepts empty HTTP responses on the client.</li></ul><h3 id="persistence"><a href="https://beta.tinybase.org/guides/persistence/">Persistence</a></h3><ul><li>Remote Persisters preserve ETags until successful downloads, reject failed responses, serialize polling, and abort active polls when stopped or destroyed.</li><li><a href="https://beta.tinybase.org/api/the-essentials/persisting-stores/persister/"><code>Persister</code></a> operations now await queued work; shared schedulers attribute errors to the correct owner and survive failures in ignored-error handlers.</li><li>Repeated destruction shares one completion Promise, waits for active work, restores idle status despite cleanup failures, and releases shared scheduler state.</li><li>Concurrent automatic lifecycle calls support falsey handles, retain only the latest registration, wait for setup and cleanup during stop or destroy, and stop both auto-load and auto-save even if one cleanup fails.</li><li>Auto-load startup closes missed-change windows and preserves notification ordering; steady-state inline changes apply synchronously without redundant saves, while no-content notifications during a save coalesce into a trailing load.</li><li>Database transaction failures roll back; remote libSQL uses one transaction session while local file clients retain their connection, and tabular Persisters replace every table-name placeholder, enforce collision-safe <a href="https://beta.tinybase.org/api/store/type-aliases/store/row/"><code>Row</code></a> <a href="https://beta.tinybase.org/api/common/type-aliases/identity/id/"><code>Id</code></a> uniqueness, and reject configurations that cannot preserve merge metadata.</li><li>PostgreSQL auto-load shares notification resources until the final owner stops and releases reserved clients after setup failure.</li><li>SQLite auto-load establishes its baseline before use, defers update-hook work until the triggering write completes, drains pending work on stop, and allows asynchronous change-listener cleanup.</li><li>IndexedDB waits for transaction completion, reports aborts and blocked opens, uses read-only loads, and serializes and drains changed-content polling.</li><li>Durable Object KV persistence batches limit-safe writes transactionally and ignores unrelated or malformed keys; Durable Object SQL preserves empty <a href="https://beta.tinybase.org/api/common/type-aliases/identity/ids/"><code>Ids</code></a> and safely quotes generated table names.</li><li>React Native MMKV preserves deletion tombstones and contains malformed content and listener failures; Automerge and Yjs validate TinyBase document roots before loading or applying changes, while Automerge still allows unrelated root metadata.</li><li>Browser storage, IndexedDB, PowerSync, SQLite, PostgreSQL, Automerge, and Yjs notifications route parsing and listener failures through ignored-error handling.</li><li>File persistence atomically replaces data while preserving modes and symbolic links, and its auto-load follows replacements without duplicate self-loads; OPFS always closes successful writes and aborts failed ones.</li></ul><h3 id="ui-and-lifecycle">UI And Lifecycle</h3><ul><li>Solid primitives no longer register <a href="https://beta.tinybase.org/api/the-essentials/creating-stores/store/"><code>Store</code></a> listeners during server rendering.</li><li>React and Solid asynchronous resource hooks destroy stale or post-unmount results, hide resources during replacement, await destruction, contain failures, and serialize Solid replacements behind previous cleanup.</li><li>Solid and Svelte DOM and Inspector packages are explicitly client-only, so server builds fail cleanly during resolution.</li><li>Solid DOM table wrappers keep table, query, and slice metadata reactive, while Solid Inspector actions and custom <a href="https://beta.tinybase.org/api/store/type-aliases/store/cell/"><code>Cell</code></a> renderers react to editability and <a href="https://beta.tinybase.org/api/store/type-aliases/store/cell/"><code>Cell</code></a> <a href="https://beta.tinybase.org/api/common/type-aliases/identity/id/"><code>Id</code></a> changes.</li><li>Solid useUndoInformation and useRedoInformation now return Accessors for availability, checkpoint <a href="https://beta.tinybase.org/api/common/type-aliases/identity/id/"><code>Id</code></a>, and label values.</li><li>Object and array editors mark JSON with the wrong container type invalid, and React, Solid, and Svelte paginators normalize boundary offsets and clamp previous navigation to zero.</li><li>React and Solid paginators defer offset corrections until after rendering, and Provider registrations in all three UI modules preserve the correct resource when duplicate <a href="https://beta.tinybase.org/api/common/type-aliases/identity/id/"><code>Id</code></a> owners are removed.</li></ul><h3 id="packaging-and-performance">Packaging And Performance</h3><ul><li>TinyBase errors now use compact numeric codes documented in the <a href="https://beta.tinybase.org/guides/error-codes/">Error Codes</a> guide.</li><li>The build now classifies Svelte modules correctly and verifies generated JavaScript exports against declared public values.</li><li>Accidental React, Solid, DOM, and IndexedDB helper exports have been removed, while createDurableObjectSqlStoragePersister is exported from the omni module as declared.</li><li>The getTransactionMergeableChanges declaration now distinguishes its default unhashed result from explicitly requested hashed changes.</li><li>Internal iteration paths now avoid unnecessary allocations, while invalid <a href="https://beta.tinybase.org/api/the-essentials/creating-stores/store/"><code>Store</code></a> JSON setters complete synchronously without creating Promises or microtasks.</li><li><a href="https://beta.tinybase.org/api/metrics/type-aliases/metric/metric/"><code>Metric</code></a> extrema and Cartesian chart summaries now handle large datasets without variadic argument limits or quadratic category de-duplication.</li></ul><hr><h1 id="v9-2">v9.2</h1><p>TinyBase v9.2 makes the library easier for coding agents and AI systems to discover, evaluate, understand, and use correctly.</p><p>Although there are no changes to TinyBase source code, this is more than just an AI-specific documentation pass. The package and website metadata now describes TinyBase using the concrete problems it solves: reactive in-memory data, offline and local-first applications, persistence, CRDTs, and synchronization. This helps search and retrieval systems connect an application requirement to TinyBase before the library name is already known.</p><h2 id="agent-documentation-and-retrieval">Agent <a href="https://beta.tinybase.org/guides/how-tinybase-is-built/documentation/">Documentation</a> And Retrieval</h2><p>The website now publishes a concise <a href="https://tinybase.org/llms.txt"><code>llms.txt</code></a> orientation file and a fuller <a href="https://tinybase.org/llms-full.txt"><code>llms-full.txt</code></a> agent guide. A new <a href="https://beta.tinybase.org/guides/the-basics/why-tinybase/">Why TinyBase?</a> guide explains when TinyBase is a good architectural fit, when it is not, and which <a href="https://beta.tinybase.org/api/the-essentials/creating-stores/store/"><code>Store</code></a>, persistence, synchronization, and UI pieces to start with.</p><p>The repository also includes configuration for current documentation to be indexed by <a href="https://context7.com/">Context7</a>, as well as machine-readable structured metadata for ordinary search and retrieval systems.</p><h2 id="an-official-tinybase-skill">An Official TinyBase Skill</h2><p>The repository now contains an official <a href="https://tinybase.org/skills/build-with-tinybase/SKILL.md"><code>build-with-tinybase</code></a> agent skill. It guides coding agents through architecture selection, scaffolding, safe extension of generated applications, and behavioral verification.</p><p>The skill treats persistence and synchronization as behaviors to prove, not just code that compiles. It instructs agents to verify persisted state across a real reload and synchronized state between multiple clients.</p><h2 id="agent-friendly-scaffolding">Agent-Friendly Scaffolding</h2><p>The <a href="https://github.com/tinyplex/create-tinybase"><code>create-tinybase</code></a> scaffolding tool now exposes its current options as JSON, documents its non-interactive mode, and generates an architecture-specific <code>AGENTS.md</code> in every new project. This gives agents a deterministic path from application requirements to a complete, working TinyBase app.</p><p>Agents can inspect the current generator contract with:</p>
|
|
39
39
|
|
|
40
40
|
```sh
|
|
41
41
|
npm create tinybase@latest -- --list-options
|