@byearlybird/starling 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -14,19 +14,20 @@ declare namespace value_d_exports {
14
14
  export { EncodedValue, decode$3 as decode, encode$3 as encode, isEncoded, merge$2 as merge };
15
15
  }
16
16
  type EncodedValue<T$1> = {
17
- __value: T$1;
18
- __eventstamp: string;
17
+ "~value": T$1;
18
+ "~eventstamp": string;
19
19
  };
20
20
  declare const encode$3: <T>(value: T, eventstamp: string) => EncodedValue<T>;
21
21
  declare const decode$3: <T>(value: EncodedValue<T>) => T;
22
22
  declare const merge$2: <T>(into: EncodedValue<T>, from: EncodedValue<T>) => EncodedValue<T>;
23
23
  declare const isEncoded: (value: unknown) => boolean;
24
24
  declare namespace record_d_exports {
25
- export { EncodedRecord, decode$2 as decode, encode$2 as encode, merge$1 as merge };
25
+ export { EncodedRecord, decode$2 as decode, encode$2 as encode, isObject, merge$1 as merge };
26
26
  }
27
27
  type EncodedRecord = {
28
28
  [key: string]: EncodedValue<unknown> | EncodedRecord;
29
29
  };
30
+ declare const isObject: (value: unknown) => boolean;
30
31
  declare const encode$2: <T extends Record<string, unknown>>(obj: T, eventstamp: string) => EncodedRecord;
31
32
  declare const decode$2: <T extends Record<string, unknown>>(obj: EncodedRecord) => T;
32
33
  declare const merge$1: (into: EncodedRecord, from: EncodedRecord) => EncodedRecord;
@@ -35,11 +36,11 @@ declare namespace document_d_exports {
35
36
  }
36
37
  type EncodedDocument = {
37
38
  "~id": string;
38
- "~data": EncodedRecord;
39
+ "~data": EncodedValue<unknown> | EncodedRecord;
39
40
  "~deletedAt": string | null;
40
41
  };
41
- declare const encode$1: <T extends record_d_exports<string, unknown>>(id: string, obj: T, eventstamp: string, deletedAt?: string | null) => EncodedDocument;
42
- declare const decode$1: <T extends record_d_exports<string, unknown>>(doc: EncodedDocument) => {
42
+ declare const encode$1: <T>(id: string, obj: T, eventstamp: string, deletedAt?: string | null) => EncodedDocument;
43
+ declare const decode$1: <T>(doc: EncodedDocument) => {
43
44
  "~id": string;
44
45
  "~data": T;
45
46
  "~deletedAt": string | null;
@@ -74,86 +75,70 @@ declare const create$1: (iterable?: Iterable<readonly [string, EncodedDocument]>
74
75
  };
75
76
  };
76
77
  declare namespace store_d_exports {
77
- export { Plugin, PluginHandle, PluginMethods, Store as StarlingStore, StoreHooks, StoreOnBeforeDelete, StoreOnBeforePatch, StoreOnBeforePut, StoreOnDelete, StoreOnPatch, StoreOnPut, StoreTransaction, create };
78
+ export { DeepPartial, NotPromise, Plugin, PluginMethods, Store as StarlingStore, StoreHooks, StoreOnDelete, StoreOnPatch, StoreOnPut, StorePutOptions, StoreSetTransaction, create };
78
79
  }
79
80
  type DeepPartial<T$1> = T$1 extends object ? { [P in keyof T$1]?: DeepPartial<T$1[P]> } : T$1;
81
+ /**
82
+ * Type constraint to prevent Promise returns from set callbacks.
83
+ * Transactions must be synchronous operations.
84
+ */
85
+ type NotPromise<T$1> = T$1 extends Promise<any> ? never : T$1;
80
86
  /**
81
87
  * Called once per commit with all put operations accumulated as decoded entries.
82
88
  * Only fires if at least one put occurred.
83
89
  */
84
- type StoreOnPut<T$1 extends Record<string, unknown>> = (entries: ReadonlyArray<readonly [string, T$1]>) => void;
90
+ type StoreOnPut<T$1> = (entries: ReadonlyArray<readonly [string, T$1]>) => void;
85
91
  /**
86
92
  * Called once per commit with all patch operations accumulated as decoded entries.
87
93
  * Only fires if at least one patch occurred.
88
94
  */
89
- type StoreOnPatch<T$1 extends Record<string, unknown>> = (entries: ReadonlyArray<readonly [string, T$1]>) => void;
95
+ type StoreOnPatch<T$1> = (entries: ReadonlyArray<readonly [string, T$1]>) => void;
90
96
  /**
91
97
  * Called once per commit with all deleted keys (IDs).
92
98
  * Only fires if at least one delete occurred.
93
99
  */
94
100
  type StoreOnDelete = (keys: ReadonlyArray<string>) => void;
95
- /**
96
- * Called before a put operation is applied.
97
- * Throws to reject the operation.
98
- */
99
- type StoreOnBeforePut<T$1 extends Record<string, unknown>> = (key: string, value: T$1) => void;
100
- /**
101
- * Called before a patch operation is applied.
102
- * Throws to reject the operation.
103
- */
104
- type StoreOnBeforePatch<T$1 extends Record<string, unknown>> = (key: string, value: DeepPartial<T$1>) => void;
105
- /**
106
- * Called before a delete operation is applied.
107
- * Throws to reject the operation.
108
- */
109
- type StoreOnBeforeDelete = (key: string) => void;
110
101
  /**
111
102
  * Hook callbacks that receive batches of decoded entries.
112
103
  * Hooks fire on commit only, never during staged operations.
113
104
  * Arrays are readonly to prevent external mutation.
114
105
  */
115
- type StoreHooks<T$1 extends Record<string, unknown>> = {
116
- onBeforePut?: StoreOnBeforePut<T$1>;
117
- onBeforePatch?: StoreOnBeforePatch<T$1>;
118
- onBeforeDelete?: StoreOnBeforeDelete;
106
+ type StoreHooks<T$1> = {
119
107
  onPut?: StoreOnPut<T$1>;
120
108
  onPatch?: StoreOnPatch<T$1>;
121
109
  onDelete?: StoreOnDelete;
122
110
  };
123
- type StoreTransaction<T$1 extends Record<string, unknown>> = {
124
- put: (key: string, value: T$1) => void;
111
+ type StorePutOptions = {
112
+ withId?: string;
113
+ };
114
+ type StoreSetTransaction<T$1> = {
115
+ put: (value: T$1, options?: StorePutOptions) => string;
125
116
  patch: (key: string, value: DeepPartial<T$1>) => void;
126
117
  merge: (doc: EncodedDocument) => void;
127
118
  del: (key: string) => void;
128
- has: (key: string) => boolean;
129
- commit: (opts?: {
130
- silent: boolean;
131
- }) => void;
119
+ get: (key: string) => T$1 | null;
132
120
  rollback: () => void;
133
121
  };
134
122
  type PluginMethods = Record<string, (...args: any[]) => any>;
135
- type PluginHandle<T$1 extends Record<string, unknown>, M$1 extends PluginMethods = {}> = {
136
- init: () => Promise<void> | void;
123
+ type Plugin<T$1, M$1 extends PluginMethods = {}> = {
124
+ init: (store: Store<T$1>) => Promise<void> | void;
137
125
  dispose: () => Promise<void> | void;
138
126
  hooks?: StoreHooks<T$1>;
139
127
  methods?: M$1;
140
128
  };
141
- type Plugin<T$1 extends Record<string, unknown>, M$1 extends PluginMethods = {}> = (store: Store<T$1, any>) => PluginHandle<T$1, M$1>;
142
- type Store<T$1 extends Record<string, unknown>, Extended = {}> = {
129
+ type Store<T$1, Extended = {}> = {
143
130
  get: (key: string) => T$1 | null;
144
- has: (key: string) => boolean;
145
- readonly size: number;
146
- values: () => IterableIterator<T$1>;
131
+ set: <R = void>(callback: (tx: StoreSetTransaction<T$1>) => NotPromise<R>, opts?: {
132
+ silent?: boolean;
133
+ }) => NotPromise<R>;
147
134
  entries: () => IterableIterator<readonly [string, T$1]>;
148
135
  snapshot: () => EncodedDocument[];
149
- put: (key: string, value: T$1) => void;
150
- patch: (key: string, value: DeepPartial<T$1>) => void;
151
- del: (key: string) => void;
152
- begin: () => StoreTransaction<T$1>;
153
136
  use: <M extends PluginMethods>(plugin: Plugin<T$1, M>) => Store<T$1, Extended & M>;
154
137
  init: () => Promise<Store<T$1, Extended>>;
155
138
  dispose: () => Promise<void>;
156
139
  } & Extended;
157
- declare const create: <T extends Record<string, unknown>>() => Store<T, {}>;
140
+ declare const create: <T>(config?: {
141
+ getId?: () => string;
142
+ }) => Store<T, {}>;
158
143
  //#endregion
159
144
  export { type clock_d_exports as Clock, document_d_exports as Document, eventstamp_d_exports as Eventstamp, kv_d_exports as KV, record_d_exports as Record, store_d_exports as Store, value_d_exports as Value };
package/dist/index.js CHANGED
@@ -20,8 +20,8 @@ const decode$3 = (eventstamp) => {
20
20
 
21
21
  //#endregion
22
22
  //#region src/clock.ts
23
- var clock_exports = /* @__PURE__ */ __export({ create: () => create });
24
- const create = () => {
23
+ var clock_exports = /* @__PURE__ */ __export({ create: () => create$2 });
24
+ const create$2 = () => {
25
25
  let counter = 0;
26
26
  let lastMs = Date.now();
27
27
  return {
@@ -56,21 +56,22 @@ var value_exports = /* @__PURE__ */ __export({
56
56
  merge: () => merge$2
57
57
  });
58
58
  const encode$2 = (value, eventstamp) => ({
59
- __value: value,
60
- __eventstamp: eventstamp
59
+ "~value": value,
60
+ "~eventstamp": eventstamp
61
61
  });
62
- const decode$2 = (value) => value.__value;
62
+ const decode$2 = (value) => value["~value"];
63
63
  const merge$2 = (into, from) => ({
64
- __value: into.__eventstamp > from.__eventstamp ? into.__value : from.__value,
65
- __eventstamp: into.__eventstamp > from.__eventstamp ? into.__eventstamp : from.__eventstamp
64
+ "~value": into["~eventstamp"] > from["~eventstamp"] ? into["~value"] : from["~value"],
65
+ "~eventstamp": into["~eventstamp"] > from["~eventstamp"] ? into["~eventstamp"] : from["~eventstamp"]
66
66
  });
67
- const isEncoded = (value) => !!(typeof value === "object" && value !== null && "__value" in value && "__eventstamp" in value);
67
+ const isEncoded = (value) => !!(typeof value === "object" && value !== null && "~value" in value && "~eventstamp" in value);
68
68
 
69
69
  //#endregion
70
70
  //#region src/record.ts
71
71
  var record_exports = /* @__PURE__ */ __export({
72
72
  decode: () => decode$1,
73
73
  encode: () => encode$1,
74
+ isObject: () => isObject,
74
75
  merge: () => merge$1
75
76
  });
76
77
  const isObject = (value) => !!(value != null && typeof value === "object" && !Array.isArray(value) && Object.getPrototypeOf(value) === Object.prototype);
@@ -139,19 +140,26 @@ var document_exports = /* @__PURE__ */ __export({
139
140
  });
140
141
  const encode = (id, obj, eventstamp, deletedAt = null) => ({
141
142
  "~id": id,
142
- "~data": encode$1(obj, eventstamp),
143
+ "~data": isObject(obj) ? encode$1(obj, eventstamp) : encode$2(obj, eventstamp),
143
144
  "~deletedAt": deletedAt
144
145
  });
145
146
  const decode = (doc) => ({
146
147
  "~id": doc["~id"],
147
- "~data": decode$1(doc["~data"]),
148
+ "~data": isEncoded(doc["~data"]) ? decode$2(doc["~data"]) : decode$1(doc["~data"]),
148
149
  "~deletedAt": doc["~deletedAt"]
149
150
  });
150
- const merge = (into, from) => ({
151
- "~id": into["~id"],
152
- "~data": merge$1(into["~data"], from["~data"]),
153
- "~deletedAt": into["~deletedAt"] && from["~deletedAt"] ? into["~deletedAt"] > from["~deletedAt"] ? into["~deletedAt"] : from["~deletedAt"] : into["~deletedAt"] || from["~deletedAt"] || null
154
- });
151
+ const merge = (into, from) => {
152
+ const intoIsValue = isEncoded(into["~data"]);
153
+ const fromIsValue = isEncoded(from["~data"]);
154
+ if (intoIsValue !== fromIsValue) throw new Error(`Cannot merge documents with incompatible types: ${intoIsValue ? "primitive" : "object"} vs ${fromIsValue ? "primitive" : "object"}`);
155
+ const mergedData = intoIsValue && fromIsValue ? merge$2(into["~data"], from["~data"]) : merge$1(into["~data"], from["~data"]);
156
+ const mergedDeletedAt = into["~deletedAt"] && from["~deletedAt"] ? into["~deletedAt"] > from["~deletedAt"] ? into["~deletedAt"] : from["~deletedAt"] : into["~deletedAt"] || from["~deletedAt"] || null;
157
+ return {
158
+ "~id": into["~id"],
159
+ "~data": mergedData,
160
+ "~deletedAt": mergedDeletedAt
161
+ };
162
+ };
155
163
  const del = (doc, eventstamp) => ({
156
164
  ...doc,
157
165
  "~deletedAt": eventstamp
@@ -159,8 +167,8 @@ const del = (doc, eventstamp) => ({
159
167
 
160
168
  //#endregion
161
169
  //#region src/kv.ts
162
- var kv_exports = /* @__PURE__ */ __export({ create: () => create$2 });
163
- const create$2 = (iterable) => {
170
+ var kv_exports = /* @__PURE__ */ __export({ create: () => create$1 });
171
+ const create$1 = (iterable) => {
164
172
  let readMap = new Map(iterable);
165
173
  function cloneMap(src) {
166
174
  return new Map(src);
@@ -217,21 +225,19 @@ const create$2 = (iterable) => {
217
225
 
218
226
  //#endregion
219
227
  //#region src/store.ts
220
- var store_exports = /* @__PURE__ */ __export({ create: () => create$1 });
221
- const create$1 = () => {
222
- const clock = create();
228
+ var store_exports = /* @__PURE__ */ __export({ create: () => create });
229
+ const create = (config = {}) => {
230
+ const kv = create$1();
231
+ const clock = create$2();
232
+ const initializers = /* @__PURE__ */ new Set();
233
+ const disposers = /* @__PURE__ */ new Set();
234
+ const getId = config.getId ?? (() => crypto.randomUUID());
223
235
  const encodeValue = (key, value) => encode(key, value, clock.now());
224
- const kv = create$2();
225
236
  const listeners = {
226
- beforePut: /* @__PURE__ */ new Set(),
227
- beforePatch: /* @__PURE__ */ new Set(),
228
- beforeDel: /* @__PURE__ */ new Set(),
229
237
  put: /* @__PURE__ */ new Set(),
230
238
  patch: /* @__PURE__ */ new Set(),
231
239
  del: /* @__PURE__ */ new Set()
232
240
  };
233
- const initializers = /* @__PURE__ */ new Set();
234
- const disposers = /* @__PURE__ */ new Set();
235
241
  const decodeActive = (doc) => {
236
242
  if (!doc || doc["~deletedAt"]) return null;
237
243
  return decode(doc)["~data"];
@@ -240,23 +246,11 @@ const create$1 = () => {
240
246
  get(key) {
241
247
  return decodeActive(kv.get(key));
242
248
  },
243
- has(key) {
244
- return decodeActive(kv.get(key)) !== null;
245
- },
246
- values() {
247
- function* iterator() {
248
- for (const doc of kv.values()) {
249
- const data = decodeActive(doc);
250
- if (data) yield data;
251
- }
252
- }
253
- return iterator();
254
- },
255
249
  entries() {
256
250
  function* iterator() {
257
251
  for (const [key, doc] of kv.entries()) {
258
252
  const data = decodeActive(doc);
259
- if (data) yield [key, data];
253
+ if (data !== null) yield [key, data];
260
254
  }
261
255
  }
262
256
  return iterator();
@@ -264,45 +258,27 @@ const create$1 = () => {
264
258
  snapshot() {
265
259
  return Array.from(kv.values());
266
260
  },
267
- get size() {
268
- let count = 0;
269
- for (const doc of kv.values()) if (doc && !doc["~deletedAt"]) count++;
270
- return count;
271
- },
272
- put(key, value) {
273
- const tx = this.begin();
274
- tx.put(key, value);
275
- tx.commit();
276
- },
277
- patch(key, value) {
278
- const tx = this.begin();
279
- tx.patch(key, value);
280
- tx.commit();
281
- },
282
- del(key) {
283
- const tx = this.begin();
284
- tx.del(key);
285
- tx.commit();
286
- },
287
- begin() {
261
+ set(callback, opts) {
288
262
  const tx = kv.begin();
263
+ const silent = opts?.silent ?? false;
289
264
  const putKeyValues = [];
290
265
  const patchKeyValues = [];
291
266
  const deleteKeys = [];
292
- return {
293
- put(key, value) {
294
- for (const fn of listeners.beforePut) fn(key, value);
267
+ let rolledBack = false;
268
+ const setTransaction = {
269
+ put(value, options) {
270
+ const key = options?.withId ?? getId();
295
271
  tx.put(key, encodeValue(key, value));
296
272
  putKeyValues.push([key, value]);
273
+ return key;
297
274
  },
298
275
  patch(key, value) {
299
- for (const fn of listeners.beforePatch) fn(key, value);
300
276
  tx.patch(key, encode(key, value, clock.now()));
301
277
  const merged = decodeActive(tx.get(key));
302
278
  if (merged) patchKeyValues.push([key, merged]);
303
279
  },
304
280
  merge(doc) {
305
- if (tx.has(doc["~id"])) tx.patch(doc["~id"], doc);
281
+ if (tx.get(doc["~id"])) tx.patch(doc["~id"], doc);
306
282
  else tx.put(doc["~id"], doc);
307
283
  const currentDoc = tx.get(doc["~id"]);
308
284
  if (currentDoc && !currentDoc["~deletedAt"]) {
@@ -311,50 +287,36 @@ const create$1 = () => {
311
287
  }
312
288
  },
313
289
  del(key) {
314
- for (const fn of listeners.beforeDel) fn(key);
315
290
  if (!tx.get(key)) return;
316
291
  tx.del(key, clock.now());
317
292
  deleteKeys.push(key);
318
293
  },
319
- has(key) {
320
- return tx.has(key);
321
- },
322
- commit(opts = { silent: false }) {
323
- tx.commit();
324
- if (opts.silent) return;
325
- if (putKeyValues.length > 0) for (const fn of listeners.put) fn(Object.freeze([...putKeyValues]));
326
- if (patchKeyValues.length > 0) for (const fn of listeners.patch) fn(Object.freeze([...patchKeyValues]));
327
- if (deleteKeys.length > 0) for (const fn of listeners.del) fn(Object.freeze([...deleteKeys]));
294
+ get(key) {
295
+ return decodeActive(tx.get(key));
328
296
  },
329
297
  rollback() {
298
+ rolledBack = true;
330
299
  tx.rollback();
331
300
  }
332
301
  };
302
+ try {
303
+ const result = callback(setTransaction);
304
+ if (rolledBack) return result;
305
+ tx.commit();
306
+ if (!silent) {
307
+ if (putKeyValues.length > 0) for (const fn of listeners.put) fn(Object.freeze([...putKeyValues]));
308
+ if (patchKeyValues.length > 0) for (const fn of listeners.patch) fn(Object.freeze([...patchKeyValues]));
309
+ if (deleteKeys.length > 0) for (const fn of listeners.del) fn(Object.freeze([...deleteKeys]));
310
+ }
311
+ return result;
312
+ } catch (error) {
313
+ tx.rollback();
314
+ throw error;
315
+ }
333
316
  },
334
317
  use(plugin) {
335
- const { hooks: pluginHooks, init, dispose, methods } = plugin(this);
318
+ const { hooks: pluginHooks, init, dispose, methods } = plugin;
336
319
  if (pluginHooks) {
337
- if (pluginHooks.onBeforePut) {
338
- const callback = pluginHooks.onBeforePut;
339
- listeners.beforePut.add(callback);
340
- disposers.add(() => {
341
- listeners.beforePut.delete(callback);
342
- });
343
- }
344
- if (pluginHooks.onBeforePatch) {
345
- const callback = pluginHooks.onBeforePatch;
346
- listeners.beforePatch.add(callback);
347
- disposers.add(() => {
348
- listeners.beforePatch.delete(callback);
349
- });
350
- }
351
- if (pluginHooks.onBeforeDelete) {
352
- const callback = pluginHooks.onBeforeDelete;
353
- listeners.beforeDel.add(callback);
354
- disposers.add(() => {
355
- listeners.beforeDel.delete(callback);
356
- });
357
- }
358
320
  if (pluginHooks.onPut) {
359
321
  const callback = pluginHooks.onPut;
360
322
  listeners.put.add(callback);
@@ -383,7 +345,7 @@ const create$1 = () => {
383
345
  return this;
384
346
  },
385
347
  async init() {
386
- for (const fn of initializers) await fn();
348
+ for (const fn of initializers) await fn(this);
387
349
  return this;
388
350
  },
389
351
  async dispose() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@byearlybird/starling",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "main": "./dist/index.js",