tinybase 7.3.2 → 7.3.3

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.
Files changed (135) hide show
  1. package/min/omni/index.js +1 -1
  2. package/min/omni/index.js.gz +0 -0
  3. package/min/omni/with-schemas/index.js +1 -1
  4. package/min/omni/with-schemas/index.js.gz +0 -0
  5. package/min/persisters/index.js +1 -1
  6. package/min/persisters/index.js.gz +0 -0
  7. package/min/persisters/persister-browser/index.js +1 -1
  8. package/min/persisters/persister-browser/index.js.gz +0 -0
  9. package/min/persisters/persister-browser/with-schemas/index.js +1 -1
  10. package/min/persisters/persister-browser/with-schemas/index.js.gz +0 -0
  11. package/min/persisters/persister-cr-sqlite-wasm/index.js +1 -1
  12. package/min/persisters/persister-cr-sqlite-wasm/index.js.gz +0 -0
  13. package/min/persisters/persister-cr-sqlite-wasm/with-schemas/index.js +1 -1
  14. package/min/persisters/persister-cr-sqlite-wasm/with-schemas/index.js.gz +0 -0
  15. package/min/persisters/persister-durable-object-sql-storage/index.js +1 -1
  16. package/min/persisters/persister-durable-object-sql-storage/index.js.gz +0 -0
  17. package/min/persisters/persister-durable-object-sql-storage/with-schemas/index.js +1 -1
  18. package/min/persisters/persister-durable-object-sql-storage/with-schemas/index.js.gz +0 -0
  19. package/min/persisters/persister-electric-sql/index.js +1 -1
  20. package/min/persisters/persister-electric-sql/index.js.gz +0 -0
  21. package/min/persisters/persister-electric-sql/with-schemas/index.js +1 -1
  22. package/min/persisters/persister-electric-sql/with-schemas/index.js.gz +0 -0
  23. package/min/persisters/persister-expo-sqlite/index.js +1 -1
  24. package/min/persisters/persister-expo-sqlite/index.js.gz +0 -0
  25. package/min/persisters/persister-expo-sqlite/with-schemas/index.js +1 -1
  26. package/min/persisters/persister-expo-sqlite/with-schemas/index.js.gz +0 -0
  27. package/min/persisters/persister-file/index.js +1 -1
  28. package/min/persisters/persister-file/index.js.gz +0 -0
  29. package/min/persisters/persister-file/with-schemas/index.js +1 -1
  30. package/min/persisters/persister-file/with-schemas/index.js.gz +0 -0
  31. package/min/persisters/persister-libsql/index.js +1 -1
  32. package/min/persisters/persister-libsql/index.js.gz +0 -0
  33. package/min/persisters/persister-libsql/with-schemas/index.js +1 -1
  34. package/min/persisters/persister-libsql/with-schemas/index.js.gz +0 -0
  35. package/min/persisters/persister-partykit-client/index.js +1 -1
  36. package/min/persisters/persister-partykit-client/index.js.gz +0 -0
  37. package/min/persisters/persister-partykit-client/with-schemas/index.js +1 -1
  38. package/min/persisters/persister-partykit-client/with-schemas/index.js.gz +0 -0
  39. package/min/persisters/persister-partykit-server/index.js +1 -1
  40. package/min/persisters/persister-partykit-server/index.js.gz +0 -0
  41. package/min/persisters/persister-partykit-server/with-schemas/index.js +1 -1
  42. package/min/persisters/persister-partykit-server/with-schemas/index.js.gz +0 -0
  43. package/min/persisters/persister-pglite/index.js +1 -1
  44. package/min/persisters/persister-pglite/index.js.gz +0 -0
  45. package/min/persisters/persister-pglite/with-schemas/index.js +1 -1
  46. package/min/persisters/persister-pglite/with-schemas/index.js.gz +0 -0
  47. package/min/persisters/persister-postgres/index.js +1 -1
  48. package/min/persisters/persister-postgres/index.js.gz +0 -0
  49. package/min/persisters/persister-postgres/with-schemas/index.js +1 -1
  50. package/min/persisters/persister-postgres/with-schemas/index.js.gz +0 -0
  51. package/min/persisters/persister-powersync/index.js +1 -1
  52. package/min/persisters/persister-powersync/index.js.gz +0 -0
  53. package/min/persisters/persister-powersync/with-schemas/index.js +1 -1
  54. package/min/persisters/persister-powersync/with-schemas/index.js.gz +0 -0
  55. package/min/persisters/persister-react-native-sqlite/index.js +1 -1
  56. package/min/persisters/persister-react-native-sqlite/index.js.gz +0 -0
  57. package/min/persisters/persister-react-native-sqlite/with-schemas/index.js +1 -1
  58. package/min/persisters/persister-react-native-sqlite/with-schemas/index.js.gz +0 -0
  59. package/min/persisters/persister-sqlite-bun/index.js +1 -1
  60. package/min/persisters/persister-sqlite-bun/index.js.gz +0 -0
  61. package/min/persisters/persister-sqlite-bun/with-schemas/index.js +1 -1
  62. package/min/persisters/persister-sqlite-bun/with-schemas/index.js.gz +0 -0
  63. package/min/persisters/persister-sqlite-wasm/index.js +1 -1
  64. package/min/persisters/persister-sqlite-wasm/index.js.gz +0 -0
  65. package/min/persisters/persister-sqlite-wasm/with-schemas/index.js +1 -1
  66. package/min/persisters/persister-sqlite-wasm/with-schemas/index.js.gz +0 -0
  67. package/min/persisters/persister-sqlite3/index.js +1 -1
  68. package/min/persisters/persister-sqlite3/index.js.gz +0 -0
  69. package/min/persisters/persister-sqlite3/with-schemas/index.js +1 -1
  70. package/min/persisters/persister-sqlite3/with-schemas/index.js.gz +0 -0
  71. package/min/persisters/with-schemas/index.js +1 -1
  72. package/min/persisters/with-schemas/index.js.gz +0 -0
  73. package/min/synchronizers/synchronizer-ws-client/index.js +1 -1
  74. package/min/synchronizers/synchronizer-ws-client/index.js.gz +0 -0
  75. package/min/synchronizers/synchronizer-ws-client/with-schemas/index.js +1 -1
  76. package/min/synchronizers/synchronizer-ws-client/with-schemas/index.js.gz +0 -0
  77. package/min/synchronizers/synchronizer-ws-server/index.js +1 -1
  78. package/min/synchronizers/synchronizer-ws-server/index.js.gz +0 -0
  79. package/min/synchronizers/synchronizer-ws-server/with-schemas/index.js +1 -1
  80. package/min/synchronizers/synchronizer-ws-server/with-schemas/index.js.gz +0 -0
  81. package/min/synchronizers/synchronizer-ws-server-durable-object/index.js +1 -1
  82. package/min/synchronizers/synchronizer-ws-server-durable-object/index.js.gz +0 -0
  83. package/min/synchronizers/synchronizer-ws-server-durable-object/with-schemas/index.js +1 -1
  84. package/min/synchronizers/synchronizer-ws-server-durable-object/with-schemas/index.js.gz +0 -0
  85. package/min/ui-react-inspector/index.js +1 -1
  86. package/min/ui-react-inspector/index.js.gz +0 -0
  87. package/min/ui-react-inspector/with-schemas/index.js +1 -1
  88. package/min/ui-react-inspector/with-schemas/index.js.gz +0 -0
  89. package/omni/index.js +16 -3
  90. package/omni/with-schemas/index.js +16 -3
  91. package/package.json +13 -13
  92. package/persisters/index.js +10 -1
  93. package/persisters/persister-browser/index.js +17 -1
  94. package/persisters/persister-browser/with-schemas/index.js +17 -1
  95. package/persisters/persister-cr-sqlite-wasm/index.js +10 -1
  96. package/persisters/persister-cr-sqlite-wasm/with-schemas/index.js +10 -1
  97. package/persisters/persister-durable-object-sql-storage/index.js +10 -1
  98. package/persisters/persister-durable-object-sql-storage/with-schemas/index.js +10 -1
  99. package/persisters/persister-electric-sql/index.js +10 -1
  100. package/persisters/persister-electric-sql/with-schemas/index.js +10 -1
  101. package/persisters/persister-expo-sqlite/index.js +10 -1
  102. package/persisters/persister-expo-sqlite/with-schemas/index.js +10 -1
  103. package/persisters/persister-file/index.js +17 -1
  104. package/persisters/persister-file/with-schemas/index.js +17 -1
  105. package/persisters/persister-libsql/index.js +10 -1
  106. package/persisters/persister-libsql/with-schemas/index.js +10 -1
  107. package/persisters/persister-partykit-client/index.js +27 -2
  108. package/persisters/persister-partykit-client/with-schemas/index.js +27 -2
  109. package/persisters/persister-partykit-server/index.js +37 -2
  110. package/persisters/persister-partykit-server/with-schemas/index.js +37 -2
  111. package/persisters/persister-pglite/index.js +10 -1
  112. package/persisters/persister-pglite/with-schemas/index.js +10 -1
  113. package/persisters/persister-postgres/index.js +10 -1
  114. package/persisters/persister-postgres/with-schemas/index.js +10 -1
  115. package/persisters/persister-powersync/index.js +10 -1
  116. package/persisters/persister-powersync/with-schemas/index.js +10 -1
  117. package/persisters/persister-react-native-sqlite/index.js +10 -1
  118. package/persisters/persister-react-native-sqlite/with-schemas/index.js +10 -1
  119. package/persisters/persister-sqlite-bun/index.js +10 -1
  120. package/persisters/persister-sqlite-bun/with-schemas/index.js +10 -1
  121. package/persisters/persister-sqlite-wasm/index.js +10 -1
  122. package/persisters/persister-sqlite-wasm/with-schemas/index.js +10 -1
  123. package/persisters/persister-sqlite3/index.js +10 -1
  124. package/persisters/persister-sqlite3/with-schemas/index.js +10 -1
  125. package/persisters/with-schemas/index.js +10 -1
  126. package/readme.md +9 -1
  127. package/releases.md +1 -1
  128. package/synchronizers/synchronizer-ws-client/index.js +14 -1
  129. package/synchronizers/synchronizer-ws-client/with-schemas/index.js +14 -1
  130. package/synchronizers/synchronizer-ws-server/index.js +14 -1
  131. package/synchronizers/synchronizer-ws-server/with-schemas/index.js +14 -1
  132. package/synchronizers/synchronizer-ws-server-durable-object/index.js +14 -1
  133. package/synchronizers/synchronizer-ws-server-durable-object/with-schemas/index.js +14 -1
  134. package/ui-react-inspector/index.js +10 -1
  135. package/ui-react-inspector/with-schemas/index.js +10 -1
@@ -3,15 +3,19 @@ const EMPTY_STRING = '';
3
3
  const STRING = getTypeOf(EMPTY_STRING);
4
4
  const T = 't';
5
5
  const V = 'v';
6
+ const UNDEFINED = '\uFFFC';
6
7
  const strStartsWith = (str, prefix) => str.startsWith(prefix);
7
8
 
8
9
  const promise = Promise;
9
10
  const getIfNotFunction = (predicate) => (value, then, otherwise) =>
10
11
  predicate(value) ? otherwise?.() : then(value);
11
12
  const isInstanceOf = (thing, cls) => thing instanceof cls;
13
+ const isNullish = (thing) => thing == null;
12
14
  const isUndefined = (thing) => thing === void 0;
15
+ const ifNotNullish = getIfNotFunction(isNullish);
13
16
  const ifNotUndefined = getIfNotFunction(isUndefined);
14
17
  const isString = (thing) => getTypeOf(thing) == STRING;
18
+ const isArray = (thing) => Array.isArray(thing);
15
19
  const slice = (arrayOrString, start, end) => arrayOrString.slice(start, end);
16
20
  const size = (arrayOrString) => arrayOrString.length;
17
21
  const promiseAll = async (promises) => promise.all(promises);
@@ -23,11 +27,25 @@ const arrayPush = (array, ...values) => array.push(...values);
23
27
  const arrayUnshift = (array, ...values) => array.unshift(...values);
24
28
 
25
29
  const object = Object;
30
+ const getPrototypeOf = (obj) => object.getPrototypeOf(obj);
26
31
  const objEntries = object.entries;
32
+ const isObject = (obj) =>
33
+ !isNullish(obj) &&
34
+ ifNotNullish(
35
+ getPrototypeOf(obj),
36
+ (objPrototype) =>
37
+ objPrototype == object.prototype ||
38
+ isNullish(getPrototypeOf(objPrototype)),
39
+
40
+ /* istanbul ignore next */
41
+ () => true,
42
+ );
27
43
  const objNew = (entries = []) => object.fromEntries(entries);
28
44
  const objHas = (obj, id) => id in obj;
29
45
  const objToArray = (obj, cb) =>
30
46
  arrayMap(objEntries(obj), ([id, value]) => cb(value, id));
47
+ const objMap = (obj, cb) =>
48
+ objNew(objToArray(obj, (value, id) => [id, cb(value, id)]));
31
49
  const objEnsure = (obj, id, getDefaultValue) => {
32
50
  if (!objHas(obj, id)) {
33
51
  obj[id] = getDefaultValue();
@@ -41,6 +59,19 @@ const jsonStringWithMap = (obj) =>
41
59
  jsonString(obj, (_key, value) =>
42
60
  isInstanceOf(value, Map) ? object.fromEntries([...value]) : value,
43
61
  );
62
+ const jsonStringWithUndefined = (obj) =>
63
+ jsonString(obj, (_key, value) => (isUndefined(value) ? UNDEFINED : value));
64
+ const jsonParseWithUndefined = (str) =>
65
+ // JSON.parse reviver removes properties with undefined values
66
+ replaceUndefinedString(jsonParse(str));
67
+ const replaceUndefinedString = (obj) =>
68
+ obj === UNDEFINED
69
+ ? void 0
70
+ : isArray(obj)
71
+ ? arrayMap(obj, replaceUndefinedString)
72
+ : isObject(obj)
73
+ ? objMap(obj, replaceUndefinedString)
74
+ : obj;
44
75
 
45
76
  const collForEach = (coll, cb) => coll?.forEach(cb);
46
77
 
@@ -51,13 +82,17 @@ const SET_CHANGES = 's';
51
82
  const STORE_PATH = '/store';
52
83
  const PUT = 'PUT';
53
84
  const construct = (prefix, type, payload) =>
54
- prefix + type + (isString(payload) ? payload : jsonStringWithMap(payload));
85
+ prefix +
86
+ type +
87
+ (isString(payload) ? payload : jsonStringWithUndefined(payload));
55
88
  const deconstruct = (prefix, message, stringified) => {
56
89
  const prefixSize = size(prefix);
57
90
  return strStartsWith(message, prefix)
58
91
  ? [
59
92
  message[prefixSize],
60
- (stringified ? jsonParse : String)(slice(message, prefixSize + 1)),
93
+ (stringified ? jsonParseWithUndefined : String)(
94
+ slice(message, prefixSize + 1),
95
+ ),
61
96
  ]
62
97
  : void 0;
63
98
  };
@@ -93,7 +93,16 @@ const jsonParse = JSON.parse;
93
93
  const jsonStringWithUndefined = (obj) =>
94
94
  jsonString(obj, (_key, value) => (isUndefined(value) ? UNDEFINED : value));
95
95
  const jsonParseWithUndefined = (str) =>
96
- jsonParse(str, (_key, value) => (value === UNDEFINED ? void 0 : value));
96
+ // JSON.parse reviver removes properties with undefined values
97
+ replaceUndefinedString(jsonParse(str));
98
+ const replaceUndefinedString = (obj) =>
99
+ obj === UNDEFINED
100
+ ? void 0
101
+ : isArray(obj)
102
+ ? arrayMap(obj, replaceUndefinedString)
103
+ : isObject(obj)
104
+ ? objMap(obj, replaceUndefinedString)
105
+ : obj;
97
106
 
98
107
  const textEncoder = /* @__PURE__ */ new GLOBAL.TextEncoder();
99
108
  const getHash = (string) => {
@@ -93,7 +93,16 @@ const jsonParse = JSON.parse;
93
93
  const jsonStringWithUndefined = (obj) =>
94
94
  jsonString(obj, (_key, value) => (isUndefined(value) ? UNDEFINED : value));
95
95
  const jsonParseWithUndefined = (str) =>
96
- jsonParse(str, (_key, value) => (value === UNDEFINED ? void 0 : value));
96
+ // JSON.parse reviver removes properties with undefined values
97
+ replaceUndefinedString(jsonParse(str));
98
+ const replaceUndefinedString = (obj) =>
99
+ obj === UNDEFINED
100
+ ? void 0
101
+ : isArray(obj)
102
+ ? arrayMap(obj, replaceUndefinedString)
103
+ : isObject(obj)
104
+ ? objMap(obj, replaceUndefinedString)
105
+ : obj;
97
106
 
98
107
  const textEncoder = /* @__PURE__ */ new GLOBAL.TextEncoder();
99
108
  const getHash = (string) => {
@@ -92,7 +92,16 @@ const jsonParse = JSON.parse;
92
92
  const jsonStringWithUndefined = (obj) =>
93
93
  jsonString(obj, (_key, value) => (isUndefined(value) ? UNDEFINED : value));
94
94
  const jsonParseWithUndefined = (str) =>
95
- jsonParse(str, (_key, value) => (value === UNDEFINED ? void 0 : value));
95
+ // JSON.parse reviver removes properties with undefined values
96
+ replaceUndefinedString(jsonParse(str));
97
+ const replaceUndefinedString = (obj) =>
98
+ obj === UNDEFINED
99
+ ? void 0
100
+ : isArray(obj)
101
+ ? arrayMap(obj, replaceUndefinedString)
102
+ : isObject(obj)
103
+ ? objMap(obj, replaceUndefinedString)
104
+ : obj;
96
105
 
97
106
  const textEncoder = /* @__PURE__ */ new GLOBAL.TextEncoder();
98
107
  const getHash = (string) => {
@@ -92,7 +92,16 @@ const jsonParse = JSON.parse;
92
92
  const jsonStringWithUndefined = (obj) =>
93
93
  jsonString(obj, (_key, value) => (isUndefined(value) ? UNDEFINED : value));
94
94
  const jsonParseWithUndefined = (str) =>
95
- jsonParse(str, (_key, value) => (value === UNDEFINED ? void 0 : value));
95
+ // JSON.parse reviver removes properties with undefined values
96
+ replaceUndefinedString(jsonParse(str));
97
+ const replaceUndefinedString = (obj) =>
98
+ obj === UNDEFINED
99
+ ? void 0
100
+ : isArray(obj)
101
+ ? arrayMap(obj, replaceUndefinedString)
102
+ : isObject(obj)
103
+ ? objMap(obj, replaceUndefinedString)
104
+ : obj;
96
105
 
97
106
  const textEncoder = /* @__PURE__ */ new GLOBAL.TextEncoder();
98
107
  const getHash = (string) => {
@@ -305,7 +305,16 @@ const jsonParse = JSON.parse;
305
305
  const jsonStringWithUndefined = (obj) =>
306
306
  jsonString(obj, (_key, value) => (isUndefined(value) ? UNDEFINED : value));
307
307
  const jsonParseWithUndefined = (str) =>
308
- jsonParse(str, (_key, value) => (value === UNDEFINED ? void 0 : value));
308
+ // JSON.parse reviver removes properties with undefined values
309
+ replaceUndefinedString(jsonParse(str));
310
+ const replaceUndefinedString = (obj) =>
311
+ obj === UNDEFINED
312
+ ? void 0
313
+ : isArray(obj)
314
+ ? arrayMap(obj, replaceUndefinedString)
315
+ : isObject(obj)
316
+ ? objMap(obj, replaceUndefinedString)
317
+ : obj;
309
318
 
310
319
  const INTEGER = /^\d+$/;
311
320
  const getPoolFunctions = () => {
@@ -305,7 +305,16 @@ const jsonParse = JSON.parse;
305
305
  const jsonStringWithUndefined = (obj) =>
306
306
  jsonString(obj, (_key, value) => (isUndefined(value) ? UNDEFINED : value));
307
307
  const jsonParseWithUndefined = (str) =>
308
- jsonParse(str, (_key, value) => (value === UNDEFINED ? void 0 : value));
308
+ // JSON.parse reviver removes properties with undefined values
309
+ replaceUndefinedString(jsonParse(str));
310
+ const replaceUndefinedString = (obj) =>
311
+ obj === UNDEFINED
312
+ ? void 0
313
+ : isArray(obj)
314
+ ? arrayMap(obj, replaceUndefinedString)
315
+ : isObject(obj)
316
+ ? objMap(obj, replaceUndefinedString)
317
+ : obj;
309
318
 
310
319
  const INTEGER = /^\d+$/;
311
320
  const getPoolFunctions = () => {
@@ -304,7 +304,16 @@ const jsonParse = JSON.parse;
304
304
  const jsonStringWithUndefined = (obj) =>
305
305
  jsonString(obj, (_key, value) => (isUndefined(value) ? UNDEFINED : value));
306
306
  const jsonParseWithUndefined = (str) =>
307
- jsonParse(str, (_key, value) => (value === UNDEFINED ? void 0 : value));
307
+ // JSON.parse reviver removes properties with undefined values
308
+ replaceUndefinedString(jsonParse(str));
309
+ const replaceUndefinedString = (obj) =>
310
+ obj === UNDEFINED
311
+ ? void 0
312
+ : isArray(obj)
313
+ ? arrayMap(obj, replaceUndefinedString)
314
+ : isObject(obj)
315
+ ? objMap(obj, replaceUndefinedString)
316
+ : obj;
308
317
 
309
318
  const INTEGER = /^\d+$/;
310
319
  const getPoolFunctions = () => {
@@ -304,7 +304,16 @@ const jsonParse = JSON.parse;
304
304
  const jsonStringWithUndefined = (obj) =>
305
305
  jsonString(obj, (_key, value) => (isUndefined(value) ? UNDEFINED : value));
306
306
  const jsonParseWithUndefined = (str) =>
307
- jsonParse(str, (_key, value) => (value === UNDEFINED ? void 0 : value));
307
+ // JSON.parse reviver removes properties with undefined values
308
+ replaceUndefinedString(jsonParse(str));
309
+ const replaceUndefinedString = (obj) =>
310
+ obj === UNDEFINED
311
+ ? void 0
312
+ : isArray(obj)
313
+ ? arrayMap(obj, replaceUndefinedString)
314
+ : isObject(obj)
315
+ ? objMap(obj, replaceUndefinedString)
316
+ : obj;
308
317
 
309
318
  const INTEGER = /^\d+$/;
310
319
  const getPoolFunctions = () => {
@@ -304,7 +304,16 @@ const jsonParse = JSON.parse;
304
304
  const jsonStringWithUndefined = (obj) =>
305
305
  jsonString(obj, (_key, value) => (isUndefined(value) ? UNDEFINED : value));
306
306
  const jsonParseWithUndefined = (str) =>
307
- jsonParse(str, (_key, value) => (value === UNDEFINED ? void 0 : value));
307
+ // JSON.parse reviver removes properties with undefined values
308
+ replaceUndefinedString(jsonParse(str));
309
+ const replaceUndefinedString = (obj) =>
310
+ obj === UNDEFINED
311
+ ? void 0
312
+ : isArray(obj)
313
+ ? arrayMap(obj, replaceUndefinedString)
314
+ : isObject(obj)
315
+ ? objMap(obj, replaceUndefinedString)
316
+ : obj;
308
317
 
309
318
  const INTEGER = /^\d+$/;
310
319
  const getPoolFunctions = () => {
@@ -304,7 +304,16 @@ const jsonParse = JSON.parse;
304
304
  const jsonStringWithUndefined = (obj) =>
305
305
  jsonString(obj, (_key, value) => (isUndefined(value) ? UNDEFINED : value));
306
306
  const jsonParseWithUndefined = (str) =>
307
- jsonParse(str, (_key, value) => (value === UNDEFINED ? void 0 : value));
307
+ // JSON.parse reviver removes properties with undefined values
308
+ replaceUndefinedString(jsonParse(str));
309
+ const replaceUndefinedString = (obj) =>
310
+ obj === UNDEFINED
311
+ ? void 0
312
+ : isArray(obj)
313
+ ? arrayMap(obj, replaceUndefinedString)
314
+ : isObject(obj)
315
+ ? objMap(obj, replaceUndefinedString)
316
+ : obj;
308
317
 
309
318
  const INTEGER = /^\d+$/;
310
319
  const getPoolFunctions = () => {
@@ -304,7 +304,16 @@ const jsonParse = JSON.parse;
304
304
  const jsonStringWithUndefined = (obj) =>
305
305
  jsonString(obj, (_key, value) => (isUndefined(value) ? UNDEFINED : value));
306
306
  const jsonParseWithUndefined = (str) =>
307
- jsonParse(str, (_key, value) => (value === UNDEFINED ? void 0 : value));
307
+ // JSON.parse reviver removes properties with undefined values
308
+ replaceUndefinedString(jsonParse(str));
309
+ const replaceUndefinedString = (obj) =>
310
+ obj === UNDEFINED
311
+ ? void 0
312
+ : isArray(obj)
313
+ ? arrayMap(obj, replaceUndefinedString)
314
+ : isObject(obj)
315
+ ? objMap(obj, replaceUndefinedString)
316
+ : obj;
308
317
 
309
318
  const INTEGER = /^\d+$/;
310
319
  const getPoolFunctions = () => {
@@ -304,7 +304,16 @@ const jsonParse = JSON.parse;
304
304
  const jsonStringWithUndefined = (obj) =>
305
305
  jsonString(obj, (_key, value) => (isUndefined(value) ? UNDEFINED : value));
306
306
  const jsonParseWithUndefined = (str) =>
307
- jsonParse(str, (_key, value) => (value === UNDEFINED ? void 0 : value));
307
+ // JSON.parse reviver removes properties with undefined values
308
+ replaceUndefinedString(jsonParse(str));
309
+ const replaceUndefinedString = (obj) =>
310
+ obj === UNDEFINED
311
+ ? void 0
312
+ : isArray(obj)
313
+ ? arrayMap(obj, replaceUndefinedString)
314
+ : isObject(obj)
315
+ ? objMap(obj, replaceUndefinedString)
316
+ : obj;
308
317
 
309
318
  const INTEGER = /^\d+$/;
310
319
  const getPoolFunctions = () => {
@@ -305,7 +305,16 @@ const jsonParse = JSON.parse;
305
305
  const jsonStringWithUndefined = (obj) =>
306
306
  jsonString(obj, (_key, value) => (isUndefined(value) ? UNDEFINED : value));
307
307
  const jsonParseWithUndefined = (str) =>
308
- jsonParse(str, (_key, value) => (value === UNDEFINED ? void 0 : value));
308
+ // JSON.parse reviver removes properties with undefined values
309
+ replaceUndefinedString(jsonParse(str));
310
+ const replaceUndefinedString = (obj) =>
311
+ obj === UNDEFINED
312
+ ? void 0
313
+ : isArray(obj)
314
+ ? arrayMap(obj, replaceUndefinedString)
315
+ : isObject(obj)
316
+ ? objMap(obj, replaceUndefinedString)
317
+ : obj;
309
318
 
310
319
  const INTEGER = /^\d+$/;
311
320
  const getPoolFunctions = () => {
@@ -305,7 +305,16 @@ const jsonParse = JSON.parse;
305
305
  const jsonStringWithUndefined = (obj) =>
306
306
  jsonString(obj, (_key, value) => (isUndefined(value) ? UNDEFINED : value));
307
307
  const jsonParseWithUndefined = (str) =>
308
- jsonParse(str, (_key, value) => (value === UNDEFINED ? void 0 : value));
308
+ // JSON.parse reviver removes properties with undefined values
309
+ replaceUndefinedString(jsonParse(str));
310
+ const replaceUndefinedString = (obj) =>
311
+ obj === UNDEFINED
312
+ ? void 0
313
+ : isArray(obj)
314
+ ? arrayMap(obj, replaceUndefinedString)
315
+ : isObject(obj)
316
+ ? objMap(obj, replaceUndefinedString)
317
+ : obj;
309
318
 
310
319
  const INTEGER = /^\d+$/;
311
320
  const getPoolFunctions = () => {
@@ -479,7 +479,16 @@ const jsonParse = JSON.parse;
479
479
  const jsonStringWithUndefined = (obj) =>
480
480
  jsonString(obj, (_key, value) => (isUndefined(value) ? UNDEFINED : value));
481
481
  const jsonParseWithUndefined = (str) =>
482
- jsonParse(str, (_key, value) => (value === UNDEFINED ? void 0 : value));
482
+ // JSON.parse reviver removes properties with undefined values
483
+ replaceUndefinedString(jsonParse(str));
484
+ const replaceUndefinedString = (obj) =>
485
+ obj === UNDEFINED
486
+ ? void 0
487
+ : isArray(obj)
488
+ ? arrayMap(obj, replaceUndefinedString)
489
+ : isObject(obj)
490
+ ? objMap(obj, replaceUndefinedString)
491
+ : obj;
483
492
 
484
493
  const textEncoder = /* @__PURE__ */ new GLOBAL.TextEncoder();
485
494
  const getHash = (string) => {
package/readme.md CHANGED
@@ -1,4 +1,12 @@
1
- <link rel="preload" as="image" href="https://tinybase.org/react.svg?asImg"><link rel="preload" as="image" href="https://tinybase.org/indexeddb.svg?asImg"><link rel="preload" as="image" href="https://tinybase.org/browser.svg?asImg"><link rel="preload" as="image" href="https://tinybase.org/cloudflare.svg?asImg"><link rel="preload" as="image" href="https://tinybase.org/postgresql.svg?asImg"><link rel="preload" as="image" href="https://tinybase.org/pglite.svg?asImg"><link rel="preload" as="image" href="https://tinybase.org/sqlite.svg?asImg"><link rel="preload" as="image" href="https://tinybase.org/bun.svg?asImg"><link rel="preload" as="image" href="https://tinybase.org/expo.svg?asImg"><link rel="preload" as="image" href="https://tinybase.org/electric.svg?asImg"><link rel="preload" as="image" href="https://tinybase.org/turso.svg?asImg"><link rel="preload" as="image" href="https://tinybase.org/powersync.svg?asImg"><link rel="preload" as="image" href="https://tinybase.org/partykit.svg?asImg"><link rel="preload" as="image" href="https://tinybase.org/yjs.svg?asImg"><link rel="preload" as="image" href="https://tinybase.org/crsqlite.png"><link rel="preload" as="image" href="https://tinybase.org/automerge.svg?asImg"><link rel="preload" as="image" href="https://tinybase.org/zod.svg?asImg"><link rel="preload" as="image" href="https://tinybase.org/typebox.svg?asImg"><link rel="preload" as="image" href="https://tinybase.org/valibot.svg?asImg"><link rel="preload" as="image" href="https://tinybase.org/arktype.svg?asImg"><link rel="preload" as="image" href="https://tinybase.org/yup.svg?asImg"><link rel="preload" as="image" href="https://tinybase.org/effect.svg?asImg"><link rel="preload" as="image" href="https://img.shields.io/github/stars/tinyplex/tinybase?style=for-the-badge&amp;logo=GitHub&amp;logoColor=%23fff&amp;label=GitHub&amp;labelColor=%23d81b60&amp;color=%23333"><link rel="preload" as="image" href="https://img.shields.io/badge/Bluesky-Follow-blue?style=for-the-badge&amp;logo=bluesky&amp;logoColor=%23fff&amp;color=%23333&amp;labelColor=%230285FF"><link rel="preload" as="image" href="https://img.shields.io/badge/%2F%20Twitter-Follow-blue?style=for-the-badge&amp;logo=x&amp;logoColor=%23fff&amp;color=%23333&amp;labelColor=%23000"><link rel="preload" as="image" href="https://img.shields.io/discord/1027918215323590676?style=for-the-badge&amp;logo=discord&amp;logoColor=%23fff&amp;label=Discord&amp;labelColor=%233131e8&amp;color=%23333"><link rel="preload" as="image" href="https://img.shields.io/github/discussions/tinyplex/tinybase?style=for-the-badge&amp;logo=GitHub&amp;logoColor=%23fff&amp;label=Ideas&amp;labelColor=%23d81b60&amp;color=%23333"><link rel="preload" as="image" href="https://img.shields.io/github/issues/tinyplex/tinybase?style=for-the-badge&amp;logo=GitHub&amp;logoColor=%23fff&amp;label=Issues&amp;labelColor=%23d81b60&amp;color=%23333"><link rel="preload" as="image" href="https://img.shields.io/badge/Tests-100%25-green?style=for-the-badge&amp;logo=Vitest&amp;logoColor=%23fff&amp;color=%23333&amp;labelColor=%2387c305"><link rel="preload" as="image" href="https://img.shields.io/npm/v/tinybase?style=for-the-badge&amp;logo=npm&amp;logoColor=%23fff&amp;labelColor=%23bd0005&amp;color=%23333"><link rel="preload" as="image" href="https://tinybase.org/ui-react-dom.webp"><link rel="preload" as="image" href="https://tinybase.org/inspector.webp"><link rel="preload" as="image" href="https://github.com/fastrepl.png?size=48"><link rel="preload" as="image" href="https://github.com/expo.png?size=48"><link rel="preload" as="image" href="https://github.com/dylmye.png?size=48"><link rel="preload" as="image" href="https://github.com/ComputelessComputer.png?size=48"><link rel="preload" as="image" href="https://github.com/cancelself.png?size=48"><link rel="preload" as="image" href="https://github.com/cpojer.png?size=48"><link rel="preload" as="image" href="https://github.com/beekeeb.png?size=48"><link rel="preload" as="image" href="https://github.com/WonderPanda.png?size=48"><link rel="preload" as="image" href="https://github.com/arpitBhalla.png?size=48"><link rel="preload" as="image" href="https://github.com/behrends.png?size=48"><link rel="preload" as="image" href="https://github.com/betomoedano.png?size=48"><link rel="preload" as="image" href="https://github.com/brentvatne.png?size=48"><link rel="preload" as="image" href="https://github.com/byCedric.png?size=48"><link rel="preload" as="image" href="https://github.com/circadian-risk.png?size=48"><link rel="preload" as="image" href="https://github.com/cubecull.png?size=48"><link rel="preload" as="image" href="https://github.com/erwinkn.png?size=48"><link rel="preload" as="image" href="https://github.com/ezra-en.png?size=48"><link rel="preload" as="image" href="https://github.com/feychenie.png?size=48"><link rel="preload" as="image" href="https://github.com/flaming-codes.png?size=48"><link rel="preload" as="image" href="https://github.com/fostertheweb.png?size=48"><link rel="preload" as="image" href="https://github.com/Giulio987.png?size=48"><link rel="preload" as="image" href="https://github.com/hi-ogawa.png?size=48"><link rel="preload" as="image" href="https://github.com/itsdevcoffee.png?size=48"><link rel="preload" as="image" href="https://github.com/jbolda.png?size=48"><link rel="preload" as="image" href="https://github.com/Kayoo-asso.png?size=48"><link rel="preload" as="image" href="https://github.com/kotofurumiya.png?size=48"><link rel="preload" as="image" href="https://github.com/Kudo.png?size=48"><link rel="preload" as="image" href="https://github.com/learn-anything.png?size=48"><link rel="preload" as="image" href="https://github.com/lluc.png?size=48"><link rel="preload" as="image" href="https://github.com/marksteve.png?size=48"><link rel="preload" as="image" href="https://github.com/miking-the-viking.png?size=48"><link rel="preload" as="image" href="https://github.com/mjamesderocher.png?size=48"><link rel="preload" as="image" href="https://github.com/mouktardev.png?size=48"><link rel="preload" as="image" href="https://github.com/nickmessing.png?size=48"><link rel="preload" as="image" href="https://github.com/nikitavoloboev.png?size=48"><link rel="preload" as="image" href="https://github.com/nkzw-tech.png?size=48"><link rel="preload" as="image" href="https://github.com/palerdot.png?size=48"><link rel="preload" as="image" href="https://github.com/PorcoRosso85.png?size=48"><link rel="preload" as="image" href="https://github.com/primodiumxyz.png?size=48"><link rel="preload" as="image" href="https://github.com/shaneosullivan.png?size=48"><link rel="preload" as="image" href="https://github.com/sudo-self.png?size=48"><link rel="preload" as="image" href="https://github.com/SuperSonicHub1.png?size=48"><link rel="preload" as="image" href="https://github.com/threepointone.png?size=48"><link rel="preload" as="image" href="https://github.com/uptonking.png?size=48"><link rel="preload" as="image" href="https://github.com/ViktorZhurbin.png?size=48"><link rel="preload" as="image" href="https://github.com/wilkerlucio.png?size=48"><link rel="preload" as="image" href="https://synclets.org/favicon.svg?asImg"><link rel="preload" as="image" href="https://tinywidgets.org/favicon.svg?asImg"><link rel="preload" as="image" href="https://tinytick.org/favicon.svg?asImg"><link rel="preload" as="image" href="https://tinybase.org/youtube.webp"><section id="hero"><h2 id="a-reactive-data-store-sync-engine">A <em>reactive</em> data store &amp; <span><em>sync</em> engine</span></h2></section><p><a href="https://tinybase.org/guides/releases/#v7-3"><em>NEW!</em> v7.3 release</a></p><p><span id="one-with">&quot;The one with state hooks!&quot;</span></p><p><a class="start" href="https://tinybase.org/guides/the-basics/getting-started/">Get started</a></p><p><a href="https://tinybase.org/demos/">Try the demos</a></p><p><a href="https://tinybase.org/api/the-essentials/creating-stores/store/">Read the docs</a></p><hr><section><h2 id="it-s-reactive">It&#x27;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> and <a href="#pre-built-reactive-components">pre-built components</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&#x27;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://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-databases-more">persist</a> your data to file, <a href="https://tinybase.org/api/persister-browser">browser storage</a>, <a href="https://tinybase.org/api/persister-indexed-db">IndexedDB</a>, <a href="https://tinybase.org/guides/persistence/database-persistence/">SQLite or PostgreSQL databases</a>, and <a href="https://tinybase.org/guides/persistence/third-party-crdt-persistence/">more</a>.</p></section><section><h2 id="it-s-built-for-a-local-first-world">It&#x27;s Built For A <em>Local-First</em> World</h2><p>TinyBase works anywhere that JavaScript does, but it&#x27;s especially great for local-first apps: where data is stored locally on the user&#x27;s device and that can be run offline. It&#x27;s tiny by name, tiny by nature: just <a href="#did-we-say-tiny">5.4kB - 12.1kB</a> and with no dependencies - yet <a href="#well-tested-and-documented">100% tested</a>, <a href="https://tinybase.org/guides/the-basics/getting-started/">fully documented</a>, and of course, <a href="https://github.com/tinyplex/tinybase">open source</a>!</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><div><a href="https://tinybase.org/guides/building-uis/getting-started-with-ui-react"><img src="https://tinybase.org/react.svg?asImg" width="48"> React</a></div><div><a href="https://tinybase.org/api/persister-indexed-db/functions/creation/createindexeddbpersister"><img src="https://tinybase.org/indexeddb.svg?asImg" width="48"> IndexedDB</a></div><div><a href="https://tinybase.org/api/persister-browser"><img src="https://tinybase.org/browser.svg?asImg" width="48"> OPFS</a></div><div><a href="https://tinybase.org/guides/integrations/cloudflare-durable-objects"><img src="https://tinybase.org/cloudflare.svg?asImg" width="48"> Cloudflare</a></div><div><a href="https://tinybase.org/guides/schemas-and-persistence/database-persistence"><img src="https://tinybase.org/postgresql.svg?asImg" width="48"> PostgreSQL</a></div><div><a href="https://tinybase.org/guides/schemas-and-persistence/database-persistence"><img src="https://tinybase.org/pglite.svg?asImg" width="48"> PGlite</a></div><div><a href="https://tinybase.org/guides/schemas-and-persistence/database-persistence"><img src="https://tinybase.org/sqlite.svg?asImg" width="48"> SQLite</a></div><div><a href="https://tinybase.org/guides/schemas-and-persistence/database-persistence"><img src="https://tinybase.org/bun.svg?asImg" width="48"> Bun SQLite</a></div><div><a href="https://tinybase.org/guides/schemas-and-persistence/database-persistence"><img src="https://tinybase.org/expo.svg?asImg" width="48"> Expo SQLite</a></div><div><a href="https://tinybase.org/guides/schemas-and-persistence/database-persistence"><img src="https://tinybase.org/electric.svg?asImg" width="48"> ElectricSQL</a></div><div><a href="https://tinybase.org/guides/schemas-and-persistence/database-persistence"><img src="https://tinybase.org/turso.svg?asImg" width="48"> Turso</a></div><div><a href="https://tinybase.org/guides/schemas-and-persistence/database-persistence"><img src="https://tinybase.org/powersync.svg?asImg" width="48"> PowerSync</a></div><div><a href="https://tinybase.org/api/persister-partykit-client"><img src="https://tinybase.org/partykit.svg?asImg" width="48"> PartyKit</a></div><div><a href="https://tinybase.org/api/persister-yjs/functions/creation/createyjspersister"><img src="https://tinybase.org/yjs.svg?asImg" width="48"> YJS</a></div><div><a href="https://tinybase.org/api/persister-cr-sqlite-wasm"><img src="https://tinybase.org/crsqlite.png" width="48"> CR-SQLite</a></div><div><a href="https://tinybase.org/api/persister-automerge"><img src="https://tinybase.org/automerge.svg?asImg" width="48"> Automerge</a></div><div><a href="https://tinybase.org/api/schematizer-zod/functions/creation/createzodschematizer"><img src="https://tinybase.org/zod.svg?asImg" width="48"> Zod</a></div><div><a href="https://tinybase.org/api/schematizer-typebox/functions/creation/createtypeboxschematizer"><img src="https://tinybase.org/typebox.svg?asImg" width="48"> TypeBox</a></div><div><a href="https://tinybase.org/api/schematizer-valibot/functions/creation/createvalibotschematizer"><img src="https://tinybase.org/valibot.svg?asImg" width="48"> Valibot</a></div><div><a href="https://tinybase.org/api/schematizer-arktype/functions/creation/createarktypeschematizer"><img src="https://tinybase.org/arktype.svg?asImg" width="48"> ArkType</a></div><div><a href="https://tinybase.org/api/schematizer-yup/functions/creation/createyupschematizer"><img src="https://tinybase.org/yup.svg?asImg" width="48"> Yup</a></div><div><a href="https://tinybase.org/api/schematizer-effect/functions/creation/createeffectschematizer"><img src="https://tinybase.org/effect.svg?asImg" width="48"> Effect</a></div><p>(Baffled by all these logos? Check out our <a href="https://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&amp;logo=GitHub&amp;logoColor=%23fff&amp;label=GitHub&amp;labelColor=%23d81b60&amp;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&amp;logo=bluesky&amp;logoColor=%23fff&amp;color=%23333&amp;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&amp;logo=x&amp;logoColor=%23fff&amp;color=%23333&amp;labelColor=%23000"> </a><a href="https://discord.com/invite/mGz3mevwP8" target="_blank"><img src="https://img.shields.io/discord/1027918215323590676?style=for-the-badge&amp;logo=discord&amp;logoColor=%23fff&amp;label=Discord&amp;labelColor=%233131e8&amp;color=%23333"></a><br><a href="https://github.com/tinyplex/tinybase/discussions" target="_blank"><img src="https://img.shields.io/github/discussions/tinyplex/tinybase?style=for-the-badge&amp;logo=GitHub&amp;logoColor=%23fff&amp;label=Ideas&amp;labelColor=%23d81b60&amp;color=%23333"> </a><a href="https://github.com/tinyplex/tinybase/issues" target="_blank"><img src="https://img.shields.io/github/issues/tinyplex/tinybase?style=for-the-badge&amp;logo=GitHub&amp;logoColor=%23fff&amp;label=Issues&amp;labelColor=%23d81b60&amp;color=%23333"> </a><a href="#well-tested-and-documented"><img src="https://img.shields.io/badge/Tests-100%25-green?style=for-the-badge&amp;logo=Vitest&amp;logoColor=%23fff&amp;color=%23333&amp;labelColor=%2387c305"> </a><a href="https://www.npmjs.com/package/tinybase/v/7.3.1" target="_blank"><img src="https://img.shields.io/npm/v/tinybase?style=for-the-badge&amp;logo=npm&amp;logoColor=%23fff&amp;labelColor=%23bd0005&amp;color=%23333"></a></section><hr><section><h2 id="start-with-a-simple-key-value-store">Start with a simple key-value store.</h2><p>Creating a <a href="https://tinybase.org/api/the-essentials/creating-stores/store/"><code>Store</code></a> requires just a simple call to the <a href="https://tinybase.org/api/the-essentials/creating-stores/createstore/"><code>createStore</code></a> function. Once you have one, you can easily set <a href="https://tinybase.org/api/store/type-aliases/store/values/"><code>Values</code></a> in it by unique <a href="https://tinybase.org/api/common/type-aliases/identity/id/"><code>Id</code></a>. And of course you can easily get them back out again.</p><p>Read more about using keyed value data in <a href="https://tinybase.org/guides/the-basics/">The Basics</a> guide.</p></section>
1
+ <link rel="preload" as="image" href="https://tinybase.org/react.svg?asImg"><link rel="preload" as="image" href="https://tinybase.org/indexeddb.svg?asImg"><link rel="preload" as="image" href="https://tinybase.org/browser.svg?asImg"><link rel="preload" as="image" href="https://tinybase.org/cloudflare.svg?asImg"><link rel="preload" as="image" href="https://tinybase.org/postgresql.svg?asImg"><link rel="preload" as="image" href="https://tinybase.org/pglite.svg?asImg"><link rel="preload" as="image" href="https://tinybase.org/sqlite.svg?asImg"><link rel="preload" as="image" href="https://tinybase.org/bun.svg?asImg"><link rel="preload" as="image" href="https://tinybase.org/expo.svg?asImg"><link rel="preload" as="image" href="https://tinybase.org/electric.svg?asImg"><link rel="preload" as="image" href="https://tinybase.org/turso.svg?asImg"><link rel="preload" as="image" href="https://tinybase.org/powersync.svg?asImg"><link rel="preload" as="image" href="https://tinybase.org/partykit.svg?asImg"><link rel="preload" as="image" href="https://tinybase.org/yjs.svg?asImg"><link rel="preload" as="image" href="https://tinybase.org/crsqlite.png"><link rel="preload" as="image" href="https://tinybase.org/automerge.svg?asImg"><link rel="preload" as="image" href="https://tinybase.org/zod.svg?asImg"><link rel="preload" as="image" href="https://tinybase.org/typebox.svg?asImg"><link rel="preload" as="image" href="https://tinybase.org/valibot.svg?asImg"><link rel="preload" as="image" href="https://tinybase.org/arktype.svg?asImg"><link rel="preload" as="image" href="https://tinybase.org/yup.svg?asImg"><link rel="preload" as="image" href="https://tinybase.org/effect.svg?asImg"><link rel="preload" as="image" href="https://img.shields.io/github/stars/tinyplex/tinybase?style=for-the-badge&amp;logo=GitHub&amp;logoColor=%23fff&amp;label=GitHub&amp;labelColor=%23d81b60&amp;color=%23333"><link rel="preload" as="image" href="https://img.shields.io/badge/Bluesky-Follow-blue?style=for-the-badge&amp;logo=bluesky&amp;logoColor=%23fff&amp;color=%23333&amp;labelColor=%230285FF"><link rel="preload" as="image" href="https://img.shields.io/badge/%2F%20Twitter-Follow-blue?style=for-the-badge&amp;logo=x&amp;logoColor=%23fff&amp;color=%23333&amp;labelColor=%23000"><link rel="preload" as="image" href="https://img.shields.io/discord/1027918215323590676?style=for-the-badge&amp;logo=discord&amp;logoColor=%23fff&amp;label=Discord&amp;labelColor=%233131e8&amp;color=%23333"><link rel="preload" as="image" href="https://img.shields.io/github/discussions/tinyplex/tinybase?style=for-the-badge&amp;logo=GitHub&amp;logoColor=%23fff&amp;label=Ideas&amp;labelColor=%23d81b60&amp;color=%23333"><link rel="preload" as="image" href="https://img.shields.io/github/issues/tinyplex/tinybase?style=for-the-badge&amp;logo=GitHub&amp;logoColor=%23fff&amp;label=Issues&amp;labelColor=%23d81b60&amp;color=%23333"><link rel="preload" as="image" href="https://img.shields.io/badge/Tests-100%25-green?style=for-the-badge&amp;logo=Vitest&amp;logoColor=%23fff&amp;color=%23333&amp;labelColor=%2387c305"><link rel="preload" as="image" href="https://img.shields.io/npm/v/tinybase?style=for-the-badge&amp;logo=npm&amp;logoColor=%23fff&amp;labelColor=%23bd0005&amp;color=%23333"><link rel="preload" as="image" href="https://tinybase.org/ui-react-dom.webp"><link rel="preload" as="image" href="https://tinybase.org/inspector.webp"><link rel="preload" as="image" href="https://github.com/fastrepl.png?size=48"><link rel="preload" as="image" href="https://github.com/expo.png?size=48"><link rel="preload" as="image" href="https://github.com/dylmye.png?size=48"><link rel="preload" as="image" href="https://github.com/ComputelessComputer.png?size=48"><link rel="preload" as="image" href="https://github.com/cancelself.png?size=48"><link rel="preload" as="image" href="https://github.com/cpojer.png?size=48"><link rel="preload" as="image" href="https://github.com/beekeeb.png?size=48"><link rel="preload" as="image" href="https://github.com/WonderPanda.png?size=48"><link rel="preload" as="image" href="https://github.com/arpitBhalla.png?size=48"><link rel="preload" as="image" href="https://github.com/behrends.png?size=48"><link rel="preload" as="image" href="https://github.com/betomoedano.png?size=48"><link rel="preload" as="image" href="https://github.com/brentvatne.png?size=48"><link rel="preload" as="image" href="https://github.com/byCedric.png?size=48"><link rel="preload" as="image" href="https://github.com/circadian-risk.png?size=48"><link rel="preload" as="image" href="https://github.com/cubecull.png?size=48"><link rel="preload" as="image" href="https://github.com/erwinkn.png?size=48"><link rel="preload" as="image" href="https://github.com/ezra-en.png?size=48"><link rel="preload" as="image" href="https://github.com/feychenie.png?size=48"><link rel="preload" as="image" href="https://github.com/flaming-codes.png?size=48"><link rel="preload" as="image" href="https://github.com/fostertheweb.png?size=48"><link rel="preload" as="image" href="https://github.com/Giulio987.png?size=48"><link rel="preload" as="image" href="https://github.com/hi-ogawa.png?size=48"><link rel="preload" as="image" href="https://github.com/itsdevcoffee.png?size=48"><link rel="preload" as="image" href="https://github.com/jbolda.png?size=48"><link rel="preload" as="image" href="https://github.com/Kayoo-asso.png?size=48"><link rel="preload" as="image" href="https://github.com/kotofurumiya.png?size=48"><link rel="preload" as="image" href="https://github.com/Kudo.png?size=48"><link rel="preload" as="image" href="https://github.com/learn-anything.png?size=48"><link rel="preload" as="image" href="https://github.com/lluc.png?size=48"><link rel="preload" as="image" href="https://github.com/marksteve.png?size=48"><link rel="preload" as="image" href="https://github.com/miking-the-viking.png?size=48"><link rel="preload" as="image" href="https://github.com/mjamesderocher.png?size=48"><link rel="preload" as="image" href="https://github.com/mouktardev.png?size=48"><link rel="preload" as="image" href="https://github.com/nickmessing.png?size=48"><link rel="preload" as="image" href="https://github.com/nikitavoloboev.png?size=48"><link rel="preload" as="image" href="https://github.com/nkzw-tech.png?size=48"><link rel="preload" as="image" href="https://github.com/palerdot.png?size=48"><link rel="preload" as="image" href="https://github.com/PorcoRosso85.png?size=48"><link rel="preload" as="image" href="https://github.com/primodiumxyz.png?size=48"><link rel="preload" as="image" href="https://github.com/shaneosullivan.png?size=48"><link rel="preload" as="image" href="https://github.com/sudo-self.png?size=48"><link rel="preload" as="image" href="https://github.com/SuperSonicHub1.png?size=48"><link rel="preload" as="image" href="https://github.com/threepointone.png?size=48"><link rel="preload" as="image" href="https://github.com/uptonking.png?size=48"><link rel="preload" as="image" href="https://github.com/ViktorZhurbin.png?size=48"><link rel="preload" as="image" href="https://github.com/wilkerlucio.png?size=48"><link rel="preload" as="image" href="https://synclets.org/favicon.svg?asImg"><link rel="preload" as="image" href="https://tinywidgets.org/favicon.svg?asImg"><link rel="preload" as="image" href="https://tinytick.org/favicon.svg?asImg"><link rel="preload" as="image" href="https://tinybase.org/youtube.webp"><section id="hero"><h2 id="a-reactive-data-store-sync-engine">A <em>reactive</em> data store &amp; <span><em>sync</em> engine</span></h2></section><p><a href="https://tinybase.org/guides/releases/#v7-3"><em>NEW!</em> v7.3 release</a></p><p><span id="one-with">&quot;The one with state hooks!&quot;</span></p><p><a class="start" href="https://tinybase.org/guides/the-basics/getting-started/">Get started</a></p><p><a href="https://tinybase.org/demos/">Try the demos</a></p><p><a href="https://tinybase.org/api/the-essentials/creating-stores/store/">Read the docs</a></p><hr><section><h2 id="let-s-build-local-first-apps">Let&#x27;s build <em>local-first</em> apps</h2><p>Create a todo list, a chat app, a drawing tool, or a tic-tac-toe game - with sync &amp; persistence! - in less than 60 seconds.</p></section>
2
+
3
+ ```bash
4
+ > npm create tinybase@latest
5
+
6
+ 📦 Creating your project...
7
+ ```
8
+
9
+ <hr><section><h2 id="it-s-reactive">It&#x27;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> and <a href="#pre-built-reactive-components">pre-built components</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&#x27;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://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-databases-more">persist</a> your data to file, <a href="https://tinybase.org/api/persister-browser">browser storage</a>, <a href="https://tinybase.org/api/persister-indexed-db">IndexedDB</a>, <a href="https://tinybase.org/guides/persistence/database-persistence/">SQLite or PostgreSQL databases</a>, and <a href="https://tinybase.org/guides/persistence/third-party-crdt-persistence/">more</a>.</p></section><section><h2 id="it-s-built-for-a-local-first-world">It&#x27;s Built For A <em>Local-First</em> World</h2><p>TinyBase works anywhere that JavaScript does, but it&#x27;s especially great for local-first apps: where data is stored locally on the user&#x27;s device and that can be run offline. It&#x27;s tiny by name, tiny by nature: just <a href="#did-we-say-tiny">5.4kB - 12.1kB</a> and with no dependencies - yet <a href="#well-tested-and-documented">100% tested</a>, <a href="https://tinybase.org/guides/the-basics/getting-started/">fully documented</a>, and of course, <a href="https://github.com/tinyplex/tinybase">open source</a>!</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><div><a href="https://tinybase.org/guides/building-uis/getting-started-with-ui-react"><img src="https://tinybase.org/react.svg?asImg" width="48"> React</a></div><div><a href="https://tinybase.org/api/persister-indexed-db/functions/creation/createindexeddbpersister"><img src="https://tinybase.org/indexeddb.svg?asImg" width="48"> IndexedDB</a></div><div><a href="https://tinybase.org/api/persister-browser"><img src="https://tinybase.org/browser.svg?asImg" width="48"> OPFS</a></div><div><a href="https://tinybase.org/guides/integrations/cloudflare-durable-objects"><img src="https://tinybase.org/cloudflare.svg?asImg" width="48"> Cloudflare</a></div><div><a href="https://tinybase.org/guides/schemas-and-persistence/database-persistence"><img src="https://tinybase.org/postgresql.svg?asImg" width="48"> PostgreSQL</a></div><div><a href="https://tinybase.org/guides/schemas-and-persistence/database-persistence"><img src="https://tinybase.org/pglite.svg?asImg" width="48"> PGlite</a></div><div><a href="https://tinybase.org/guides/schemas-and-persistence/database-persistence"><img src="https://tinybase.org/sqlite.svg?asImg" width="48"> SQLite</a></div><div><a href="https://tinybase.org/guides/schemas-and-persistence/database-persistence"><img src="https://tinybase.org/bun.svg?asImg" width="48"> Bun SQLite</a></div><div><a href="https://tinybase.org/guides/schemas-and-persistence/database-persistence"><img src="https://tinybase.org/expo.svg?asImg" width="48"> Expo SQLite</a></div><div><a href="https://tinybase.org/guides/schemas-and-persistence/database-persistence"><img src="https://tinybase.org/electric.svg?asImg" width="48"> ElectricSQL</a></div><div><a href="https://tinybase.org/guides/schemas-and-persistence/database-persistence"><img src="https://tinybase.org/turso.svg?asImg" width="48"> Turso</a></div><div><a href="https://tinybase.org/guides/schemas-and-persistence/database-persistence"><img src="https://tinybase.org/powersync.svg?asImg" width="48"> PowerSync</a></div><div><a href="https://tinybase.org/api/persister-partykit-client"><img src="https://tinybase.org/partykit.svg?asImg" width="48"> PartyKit</a></div><div><a href="https://tinybase.org/api/persister-yjs/functions/creation/createyjspersister"><img src="https://tinybase.org/yjs.svg?asImg" width="48"> YJS</a></div><div><a href="https://tinybase.org/api/persister-cr-sqlite-wasm"><img src="https://tinybase.org/crsqlite.png" width="48"> CR-SQLite</a></div><div><a href="https://tinybase.org/api/persister-automerge"><img src="https://tinybase.org/automerge.svg?asImg" width="48"> Automerge</a></div><div><a href="https://tinybase.org/api/schematizer-zod/functions/creation/createzodschematizer"><img src="https://tinybase.org/zod.svg?asImg" width="48"> Zod</a></div><div><a href="https://tinybase.org/api/schematizer-typebox/functions/creation/createtypeboxschematizer"><img src="https://tinybase.org/typebox.svg?asImg" width="48"> TypeBox</a></div><div><a href="https://tinybase.org/api/schematizer-valibot/functions/creation/createvalibotschematizer"><img src="https://tinybase.org/valibot.svg?asImg" width="48"> Valibot</a></div><div><a href="https://tinybase.org/api/schematizer-arktype/functions/creation/createarktypeschematizer"><img src="https://tinybase.org/arktype.svg?asImg" width="48"> ArkType</a></div><div><a href="https://tinybase.org/api/schematizer-yup/functions/creation/createyupschematizer"><img src="https://tinybase.org/yup.svg?asImg" width="48"> Yup</a></div><div><a href="https://tinybase.org/api/schematizer-effect/functions/creation/createeffectschematizer"><img src="https://tinybase.org/effect.svg?asImg" width="48"> Effect</a></div><p>(Baffled by all these logos? Check out our <a href="https://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&amp;logo=GitHub&amp;logoColor=%23fff&amp;label=GitHub&amp;labelColor=%23d81b60&amp;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&amp;logo=bluesky&amp;logoColor=%23fff&amp;color=%23333&amp;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&amp;logo=x&amp;logoColor=%23fff&amp;color=%23333&amp;labelColor=%23000"> </a><a href="https://discord.com/invite/mGz3mevwP8" target="_blank"><img src="https://img.shields.io/discord/1027918215323590676?style=for-the-badge&amp;logo=discord&amp;logoColor=%23fff&amp;label=Discord&amp;labelColor=%233131e8&amp;color=%23333"></a><br><a href="https://github.com/tinyplex/tinybase/discussions" target="_blank"><img src="https://img.shields.io/github/discussions/tinyplex/tinybase?style=for-the-badge&amp;logo=GitHub&amp;logoColor=%23fff&amp;label=Ideas&amp;labelColor=%23d81b60&amp;color=%23333"> </a><a href="https://github.com/tinyplex/tinybase/issues" target="_blank"><img src="https://img.shields.io/github/issues/tinyplex/tinybase?style=for-the-badge&amp;logo=GitHub&amp;logoColor=%23fff&amp;label=Issues&amp;labelColor=%23d81b60&amp;color=%23333"> </a><a href="#well-tested-and-documented"><img src="https://img.shields.io/badge/Tests-100%25-green?style=for-the-badge&amp;logo=Vitest&amp;logoColor=%23fff&amp;color=%23333&amp;labelColor=%2387c305"> </a><a href="https://www.npmjs.com/package/tinybase/v/7.3.2" target="_blank"><img src="https://img.shields.io/npm/v/tinybase?style=for-the-badge&amp;logo=npm&amp;logoColor=%23fff&amp;labelColor=%23bd0005&amp;color=%23333"></a></section><hr><section><h2 id="start-with-a-simple-key-value-store">Start with a simple key-value store.</h2><p>Creating a <a href="https://tinybase.org/api/the-essentials/creating-stores/store/"><code>Store</code></a> requires just a simple call to the <a href="https://tinybase.org/api/the-essentials/creating-stores/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
10
 
3
11
  ```js
4
12
  import {createStore} from 'tinybase';
package/releases.md CHANGED
@@ -339,7 +339,7 @@ export class MyDurableObject extends WsServerDurableObject {
339
339
  }
340
340
  ```
341
341
 
342
- <p>You can get started quickly with this architecture using the <a href="https://github.com/tinyplex/vite-tinybase-ts-react-sync-durable-object">new Vite template</a> that accompanies this release.</p><h2 id="server-reference-implementation">Server Reference Implementation</h2><p>Unrelated to Durable Objects, this release also includes the new <a href="https://tinybase.org/api/synchronizer-ws-server-simple/"><code>synchronizer-ws-server-simple</code></a> module that contains a simple server implementation called <a href="https://tinybase.org/api/synchronizer-ws-server-simple/interfaces/server/wsserversimple/"><code>WsServerSimple</code></a>. Without the complications of listeners, persistence, or statistics, this is more suitable to be used as a reference implementation for other server environments.</p><h2 id="architectural-guide">Architectural Guide</h2><p>To go with this release, we have added new documentation on ways in which you can use TinyBase in an app architecture. Check it out in the new <a href="https://tinybase.org/guides/the-basics/architectural-options/">Architectural Options</a> guide.</p><p>We&#x27;ve also started a new section of documentation for describing integrations, of which the <a href="https://tinybase.org/guides/integrations/cloudflare-durable-objects/">Cloudflare Durable Objects</a> guide, of course, is the first new entry!</p><hr><h1 id="v5-3">v5.3</h1><p>This release is focussed on a few API improvements and quality-of-life changes. These include:</p><h2 id="react-ssr-support">React SSR support</h2><p>Thanks to contributor <a href="https://github.com/muhajirdev">Muhammad Muhajir</a> for ensuring that TinyBase runs in server-side rendering environments!</p><h2 id="in-the-persisters-module">In the <a href="https://tinybase.org/api/persisters/"><code>persisters</code></a> module...</h2><p>All <a href="https://tinybase.org/api/the-essentials/persisting-stores/persister/"><code>Persister</code></a> objects now expose information about whether they are loading or saving. To access this <a href="https://tinybase.org/api/persisters/enumerations/lifecycle/status/"><code>Status</code></a>, use:</p><ul><li>The <a href="https://tinybase.org/api/persisters/interfaces/persister/persister/methods/lifecycle/getstatus/"><code>getStatus</code></a> method, which will return 0 when it is idle, 1 when it is loading, and 2 when it is saving.</li><li>The <a href="https://tinybase.org/api/persisters/interfaces/persister/persister/methods/listener/addstatuslistener/"><code>addStatusListener</code></a> method, which lets you add a <a href="https://tinybase.org/api/persisters/type-aliases/listener/statuslistener/"><code>StatusListener</code></a> function and which is called whenever the status changes.</li></ul><p>These make it possible to track background load and save activities, so that, for example, you can show a status-bar spinner of asynchronous persistence activity.</p><h2 id="in-the-synchronizers-module">In the <a href="https://tinybase.org/api/synchronizers/"><code>synchronizers</code></a> module...</h2><p>Synchronizers are a sub-class of <a href="https://tinybase.org/api/the-essentials/persisting-stores/persister/"><code>Persister</code></a>, so all <a href="https://tinybase.org/api/the-essentials/synchronizing-stores/synchronizer/"><code>Synchronizer</code></a> objects now also have:</p><ul><li>The <a href="https://tinybase.org/api/persisters/interfaces/persister/persister/methods/lifecycle/getstatus/"><code>getStatus</code></a> method, which will return 0 when it is idle, 1 when it is &#x27;loading&#x27; (ie inbound syncing), and 2 when it is &#x27;saving&#x27; (ie outbound syncing).</li><li>The <a href="https://tinybase.org/api/persisters/interfaces/persister/persister/methods/listener/addstatuslistener/"><code>addStatusListener</code></a> method, which lets you add a <a href="https://tinybase.org/api/persisters/type-aliases/listener/statuslistener/"><code>StatusListener</code></a> function and which is called whenever the status changes.</li></ul><h2 id="in-the-ui-react-module">In the <a href="https://tinybase.org/api/ui-react/"><code>ui-react</code></a> module...</h2><p>There are corresponding hooks so that you can build these status changes into a React UI easily:</p><ul><li>The <a href="https://tinybase.org/api/ui-react/functions/persister-hooks/usepersisterstatus/"><code>usePersisterStatus</code></a> hook, which will return the status for an explicitly provided, or context-derived <a href="https://tinybase.org/api/the-essentials/persisting-stores/persister/"><code>Persister</code></a>.</li><li>The <a href="https://tinybase.org/api/ui-react/functions/persister-hooks/usepersisterstatuslistener/"><code>usePersisterStatusListener</code></a> hook, which lets you add your own <a href="https://tinybase.org/api/persisters/type-aliases/listener/statuslistener/"><code>StatusListener</code></a> function to a <a href="https://tinybase.org/api/the-essentials/persisting-stores/persister/"><code>Persister</code></a>.</li><li>The <a href="https://tinybase.org/api/ui-react/functions/persister-hooks/usepersister/"><code>usePersister</code></a> hook, which lets you get direct access to a <a href="https://tinybase.org/api/the-essentials/persisting-stores/persister/"><code>Persister</code></a> from within your UI.</li></ul><p>And correspondingly for Synchronizers:</p><ul><li>The <a href="https://tinybase.org/api/ui-react/functions/synchronizer-hooks/usesynchronizerstatus/"><code>useSynchronizerStatus</code></a> hook, which will return the status for an explicitly provided, or context-derived <a href="https://tinybase.org/api/the-essentials/synchronizing-stores/synchronizer/"><code>Synchronizer</code></a>.</li><li>The <a href="https://tinybase.org/api/ui-react/functions/synchronizer-hooks/usesynchronizerstatuslistener/"><code>useSynchronizerStatusListener</code></a> hook, which lets you add your own <a href="https://tinybase.org/api/persisters/type-aliases/listener/statuslistener/"><code>StatusListener</code></a> function to a <a href="https://tinybase.org/api/the-essentials/synchronizing-stores/synchronizer/"><code>Synchronizer</code></a>.</li><li>The <a href="https://tinybase.org/api/ui-react/functions/synchronizer-hooks/usesynchronizer/"><code>useSynchronizer</code></a> hook, which lets you get direct access to a <a href="https://tinybase.org/api/the-essentials/synchronizing-stores/synchronizer/"><code>Synchronizer</code></a> from within your UI.</li></ul><p>In addition, this module also now includes hooks for injecting objects into the Provider context scope imperatively, much like the existing <a href="https://tinybase.org/api/ui-react/functions/store-hooks/useprovidestore/"><code>useProvideStore</code></a> hook:</p><ul><li>The <a href="https://tinybase.org/api/ui-react/functions/metrics-hooks/useprovidemetrics/"><code>useProvideMetrics</code></a> hook, which lets you imperatively register <a href="https://tinybase.org/api/metrics/interfaces/metrics/metrics/"><code>Metrics</code></a> objects.</li><li>The <a href="https://tinybase.org/api/ui-react/functions/indexes-hooks/useprovideindexes/"><code>useProvideIndexes</code></a> hook, which lets you register <a href="https://tinybase.org/api/indexes/interfaces/indexes/indexes/"><code>Indexes</code></a> objects.</li><li>The <a href="https://tinybase.org/api/ui-react/functions/relationships-hooks/useproviderelationships/"><code>useProvideRelationships</code></a> hook, which lets you register <a href="https://tinybase.org/api/relationships/interfaces/relationships/relationships/"><code>Relationships</code></a> objects.</li><li>The <a href="https://tinybase.org/api/ui-react/functions/queries-hooks/useprovidequeries/"><code>useProvideQueries</code></a> hook, which lets you register <a href="https://tinybase.org/api/queries/interfaces/queries/queries/"><code>Queries</code></a> objects.</li><li>The <a href="https://tinybase.org/api/ui-react/functions/checkpoints-hooks/useprovidecheckpoints/"><code>useProvideCheckpoints</code></a> hook, which lets you register <a href="https://tinybase.org/api/checkpoints/interfaces/checkpoints/checkpoints/"><code>Checkpoints</code></a> objects.</li><li>The <a href="https://tinybase.org/api/ui-react/functions/persister-hooks/useprovidepersister/"><code>useProvidePersister</code></a> hook, which lets you register <a href="https://tinybase.org/api/the-essentials/persisting-stores/persister/"><code>Persister</code></a> objects.</li><li>The <a href="https://tinybase.org/api/ui-react/functions/synchronizer-hooks/useprovidesynchronizer/"><code>useProvideSynchronizer</code></a> hook, which lets you register <a href="https://tinybase.org/api/the-essentials/synchronizing-stores/synchronizer/"><code>Synchronizer</code></a> objects.</li></ul><p>All of these new methods have extensive documentation, each with examples to show how to use them.</p><p>Please provide feedback on this new release on GitHub!</p><hr><h1 id="v5-2">v5.2</h1><p>This release introduces new Persisters for... PostgreSQL! TinyBase now has two new <a href="https://tinybase.org/api/the-essentials/persisting-stores/persister/"><code>Persister</code></a> modules:</p><ul><li>The <a href="https://tinybase.org/api/persister-postgres/"><code>persister-postgres</code></a> module provides the <a href="https://tinybase.org/api/persister-postgres/interfaces/persister/postgrespersister/"><code>PostgresPersister</code></a>, which uses the excellent <a href="https://github.com/porsager/postgres"><code>postgres</code></a> module to bind to regular PostgreSQL databases, generally on a server.</li><li>The <a href="https://tinybase.org/api/persister-pglite/"><code>persister-pglite</code></a> module provides the <a href="https://tinybase.org/api/persister-pglite/interfaces/persister/pglitepersister/"><code>PglitePersister</code></a>, which uses the new and exciting <a href="https://github.com/electric-sql/pglite"><code>pglite</code></a> module for running PostgreSQL... in a browser!</li></ul><p>Conceptually, things behave in the same way as they do for the various SQLite persisters. Simply use the <a href="https://tinybase.org/api/persister-postgres/functions/creation/createpostgrespersister/"><code>createPostgresPersister</code></a> function (or the similar <a href="https://tinybase.org/api/the-essentials/persisting-stores/createpglitepersister/"><code>createPglitePersister</code></a> function) to persist your TinyBase data:</p>
342
+ <p>You can get started quickly with this architecture using the Durable Objects option in the <a href="https://github.com/tinyplex/create-tinybase"><code>create-tinybase</code> tool</a>.</p><h2 id="server-reference-implementation">Server Reference Implementation</h2><p>Unrelated to Durable Objects, this release also includes the new <a href="https://tinybase.org/api/synchronizer-ws-server-simple/"><code>synchronizer-ws-server-simple</code></a> module that contains a simple server implementation called <a href="https://tinybase.org/api/synchronizer-ws-server-simple/interfaces/server/wsserversimple/"><code>WsServerSimple</code></a>. Without the complications of listeners, persistence, or statistics, this is more suitable to be used as a reference implementation for other server environments.</p><h2 id="architectural-guide">Architectural Guide</h2><p>To go with this release, we have added new documentation on ways in which you can use TinyBase in an app architecture. Check it out in the new <a href="https://tinybase.org/guides/the-basics/architectural-options/">Architectural Options</a> guide.</p><p>We&#x27;ve also started a new section of documentation for describing integrations, of which the <a href="https://tinybase.org/guides/integrations/cloudflare-durable-objects/">Cloudflare Durable Objects</a> guide, of course, is the first new entry!</p><hr><h1 id="v5-3">v5.3</h1><p>This release is focussed on a few API improvements and quality-of-life changes. These include:</p><h2 id="react-ssr-support">React SSR support</h2><p>Thanks to contributor <a href="https://github.com/muhajirdev">Muhammad Muhajir</a> for ensuring that TinyBase runs in server-side rendering environments!</p><h2 id="in-the-persisters-module">In the <a href="https://tinybase.org/api/persisters/"><code>persisters</code></a> module...</h2><p>All <a href="https://tinybase.org/api/the-essentials/persisting-stores/persister/"><code>Persister</code></a> objects now expose information about whether they are loading or saving. To access this <a href="https://tinybase.org/api/persisters/enumerations/lifecycle/status/"><code>Status</code></a>, use:</p><ul><li>The <a href="https://tinybase.org/api/persisters/interfaces/persister/persister/methods/lifecycle/getstatus/"><code>getStatus</code></a> method, which will return 0 when it is idle, 1 when it is loading, and 2 when it is saving.</li><li>The <a href="https://tinybase.org/api/persisters/interfaces/persister/persister/methods/listener/addstatuslistener/"><code>addStatusListener</code></a> method, which lets you add a <a href="https://tinybase.org/api/persisters/type-aliases/listener/statuslistener/"><code>StatusListener</code></a> function and which is called whenever the status changes.</li></ul><p>These make it possible to track background load and save activities, so that, for example, you can show a status-bar spinner of asynchronous persistence activity.</p><h2 id="in-the-synchronizers-module">In the <a href="https://tinybase.org/api/synchronizers/"><code>synchronizers</code></a> module...</h2><p>Synchronizers are a sub-class of <a href="https://tinybase.org/api/the-essentials/persisting-stores/persister/"><code>Persister</code></a>, so all <a href="https://tinybase.org/api/the-essentials/synchronizing-stores/synchronizer/"><code>Synchronizer</code></a> objects now also have:</p><ul><li>The <a href="https://tinybase.org/api/persisters/interfaces/persister/persister/methods/lifecycle/getstatus/"><code>getStatus</code></a> method, which will return 0 when it is idle, 1 when it is &#x27;loading&#x27; (ie inbound syncing), and 2 when it is &#x27;saving&#x27; (ie outbound syncing).</li><li>The <a href="https://tinybase.org/api/persisters/interfaces/persister/persister/methods/listener/addstatuslistener/"><code>addStatusListener</code></a> method, which lets you add a <a href="https://tinybase.org/api/persisters/type-aliases/listener/statuslistener/"><code>StatusListener</code></a> function and which is called whenever the status changes.</li></ul><h2 id="in-the-ui-react-module">In the <a href="https://tinybase.org/api/ui-react/"><code>ui-react</code></a> module...</h2><p>There are corresponding hooks so that you can build these status changes into a React UI easily:</p><ul><li>The <a href="https://tinybase.org/api/ui-react/functions/persister-hooks/usepersisterstatus/"><code>usePersisterStatus</code></a> hook, which will return the status for an explicitly provided, or context-derived <a href="https://tinybase.org/api/the-essentials/persisting-stores/persister/"><code>Persister</code></a>.</li><li>The <a href="https://tinybase.org/api/ui-react/functions/persister-hooks/usepersisterstatuslistener/"><code>usePersisterStatusListener</code></a> hook, which lets you add your own <a href="https://tinybase.org/api/persisters/type-aliases/listener/statuslistener/"><code>StatusListener</code></a> function to a <a href="https://tinybase.org/api/the-essentials/persisting-stores/persister/"><code>Persister</code></a>.</li><li>The <a href="https://tinybase.org/api/ui-react/functions/persister-hooks/usepersister/"><code>usePersister</code></a> hook, which lets you get direct access to a <a href="https://tinybase.org/api/the-essentials/persisting-stores/persister/"><code>Persister</code></a> from within your UI.</li></ul><p>And correspondingly for Synchronizers:</p><ul><li>The <a href="https://tinybase.org/api/ui-react/functions/synchronizer-hooks/usesynchronizerstatus/"><code>useSynchronizerStatus</code></a> hook, which will return the status for an explicitly provided, or context-derived <a href="https://tinybase.org/api/the-essentials/synchronizing-stores/synchronizer/"><code>Synchronizer</code></a>.</li><li>The <a href="https://tinybase.org/api/ui-react/functions/synchronizer-hooks/usesynchronizerstatuslistener/"><code>useSynchronizerStatusListener</code></a> hook, which lets you add your own <a href="https://tinybase.org/api/persisters/type-aliases/listener/statuslistener/"><code>StatusListener</code></a> function to a <a href="https://tinybase.org/api/the-essentials/synchronizing-stores/synchronizer/"><code>Synchronizer</code></a>.</li><li>The <a href="https://tinybase.org/api/ui-react/functions/synchronizer-hooks/usesynchronizer/"><code>useSynchronizer</code></a> hook, which lets you get direct access to a <a href="https://tinybase.org/api/the-essentials/synchronizing-stores/synchronizer/"><code>Synchronizer</code></a> from within your UI.</li></ul><p>In addition, this module also now includes hooks for injecting objects into the Provider context scope imperatively, much like the existing <a href="https://tinybase.org/api/ui-react/functions/store-hooks/useprovidestore/"><code>useProvideStore</code></a> hook:</p><ul><li>The <a href="https://tinybase.org/api/ui-react/functions/metrics-hooks/useprovidemetrics/"><code>useProvideMetrics</code></a> hook, which lets you imperatively register <a href="https://tinybase.org/api/metrics/interfaces/metrics/metrics/"><code>Metrics</code></a> objects.</li><li>The <a href="https://tinybase.org/api/ui-react/functions/indexes-hooks/useprovideindexes/"><code>useProvideIndexes</code></a> hook, which lets you register <a href="https://tinybase.org/api/indexes/interfaces/indexes/indexes/"><code>Indexes</code></a> objects.</li><li>The <a href="https://tinybase.org/api/ui-react/functions/relationships-hooks/useproviderelationships/"><code>useProvideRelationships</code></a> hook, which lets you register <a href="https://tinybase.org/api/relationships/interfaces/relationships/relationships/"><code>Relationships</code></a> objects.</li><li>The <a href="https://tinybase.org/api/ui-react/functions/queries-hooks/useprovidequeries/"><code>useProvideQueries</code></a> hook, which lets you register <a href="https://tinybase.org/api/queries/interfaces/queries/queries/"><code>Queries</code></a> objects.</li><li>The <a href="https://tinybase.org/api/ui-react/functions/checkpoints-hooks/useprovidecheckpoints/"><code>useProvideCheckpoints</code></a> hook, which lets you register <a href="https://tinybase.org/api/checkpoints/interfaces/checkpoints/checkpoints/"><code>Checkpoints</code></a> objects.</li><li>The <a href="https://tinybase.org/api/ui-react/functions/persister-hooks/useprovidepersister/"><code>useProvidePersister</code></a> hook, which lets you register <a href="https://tinybase.org/api/the-essentials/persisting-stores/persister/"><code>Persister</code></a> objects.</li><li>The <a href="https://tinybase.org/api/ui-react/functions/synchronizer-hooks/useprovidesynchronizer/"><code>useProvideSynchronizer</code></a> hook, which lets you register <a href="https://tinybase.org/api/the-essentials/synchronizing-stores/synchronizer/"><code>Synchronizer</code></a> objects.</li></ul><p>All of these new methods have extensive documentation, each with examples to show how to use them.</p><p>Please provide feedback on this new release on GitHub!</p><hr><h1 id="v5-2">v5.2</h1><p>This release introduces new Persisters for... PostgreSQL! TinyBase now has two new <a href="https://tinybase.org/api/the-essentials/persisting-stores/persister/"><code>Persister</code></a> modules:</p><ul><li>The <a href="https://tinybase.org/api/persister-postgres/"><code>persister-postgres</code></a> module provides the <a href="https://tinybase.org/api/persister-postgres/interfaces/persister/postgrespersister/"><code>PostgresPersister</code></a>, which uses the excellent <a href="https://github.com/porsager/postgres"><code>postgres</code></a> module to bind to regular PostgreSQL databases, generally on a server.</li><li>The <a href="https://tinybase.org/api/persister-pglite/"><code>persister-pglite</code></a> module provides the <a href="https://tinybase.org/api/persister-pglite/interfaces/persister/pglitepersister/"><code>PglitePersister</code></a>, which uses the new and exciting <a href="https://github.com/electric-sql/pglite"><code>pglite</code></a> module for running PostgreSQL... in a browser!</li></ul><p>Conceptually, things behave in the same way as they do for the various SQLite persisters. Simply use the <a href="https://tinybase.org/api/persister-postgres/functions/creation/createpostgrespersister/"><code>createPostgresPersister</code></a> function (or the similar <a href="https://tinybase.org/api/the-essentials/persisting-stores/createpglitepersister/"><code>createPglitePersister</code></a> function) to persist your TinyBase data:</p>
343
343
 
344
344
  ```js
345
345
  import postgres from 'postgres';
@@ -66,6 +66,10 @@ const objNew = (entries = []) => object.fromEntries(entries);
66
66
  const objHas = (obj, id) => id in obj;
67
67
  const objForEach = (obj, cb) =>
68
68
  arrayForEach(objEntries(obj), ([id, value]) => cb(value, id));
69
+ const objToArray = (obj, cb) =>
70
+ arrayMap(objEntries(obj), ([id, value]) => cb(value, id));
71
+ const objMap = (obj, cb) =>
72
+ objNew(objToArray(obj, (value, id) => [id, cb(value, id)]));
69
73
  const objSize = (obj) => size(objIds(obj));
70
74
  const objIsEmpty = (obj) => isObject(obj) && objSize(obj) == 0;
71
75
  const objEnsure = (obj, id, getDefaultValue) => {
@@ -80,7 +84,16 @@ const jsonParse = JSON.parse;
80
84
  const jsonStringWithUndefined = (obj) =>
81
85
  jsonString(obj, (_key, value) => (isUndefined(value) ? UNDEFINED : value));
82
86
  const jsonParseWithUndefined = (str) =>
83
- jsonParse(str, (_key, value) => (value === UNDEFINED ? void 0 : value));
87
+ // JSON.parse reviver removes properties with undefined values
88
+ replaceUndefinedString(jsonParse(str));
89
+ const replaceUndefinedString = (obj) =>
90
+ obj === UNDEFINED
91
+ ? void 0
92
+ : isArray(obj)
93
+ ? arrayMap(obj, replaceUndefinedString)
94
+ : isObject(obj)
95
+ ? objMap(obj, replaceUndefinedString)
96
+ : obj;
84
97
 
85
98
  const MESSAGE_SEPARATOR = '\n';
86
99
  const ifPayloadValid = (payload, then) => {