@procedures/store 0.52.1 → 0.52.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.
@@ -1 +1 @@
1
- {"version":3,"file":"queue.js","sourceRoot":"","sources":["../../src/storage/queue.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"queue.js","sourceRoot":"","sources":["../../src/storage/queue.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package",
3
3
  "name": "@procedures/store",
4
- "version": "0.52.1",
4
+ "version": "0.52.3",
5
5
  "description": "A program store for procedure-as-data: save the programs that procedure.define makes, load them back after a restart, in memory, a JSON file, IndexedDB or a remote collection storage.",
6
6
  "keywords": [
7
7
  "program-store",
@@ -78,12 +78,12 @@
78
78
  },
79
79
  "dependencies": {
80
80
  "@mark1russell7/service": "^0.6.0",
81
- "@procedures/client": "0.52.1",
82
- "@procedures/collections": "0.52.1",
81
+ "@procedures/client": "0.52.3",
82
+ "@procedures/collections": "0.52.3",
83
83
  "zod": "^4.6.5"
84
84
  },
85
85
  "devDependencies": {
86
- "@procedures/fs": "0.52.1",
86
+ "@procedures/fs": "0.52.3",
87
87
  "@mark1russell7/cue": "^2.1.1",
88
88
  "@types/node": "^22.0.0",
89
89
  "fake-indexeddb": "^6.2.5",
package/src/browser.ts CHANGED
@@ -36,6 +36,7 @@ export type {
36
36
  HybridStorageOptions,
37
37
  HybridMode,
38
38
  LocalCopyStorage,
39
+ PushBatch,
39
40
  QueueRefusal,
40
41
  RemoteApplyCause,
41
42
  RemoteApplyContext,
@@ -70,7 +70,7 @@ import {
70
70
  } from "@procedures/client/query";
71
71
  import { optionalMs, realClock, type Cancel, type Clock, type Duration } from "@procedures/client/browser";
72
72
  import { backoffDelay } from "@mark1russell7/service/backoff";
73
- import { MemoryQueueStore, idsOf, singlesOf, withoutId, type QueuedOperation, type QueueStore } from "./queue.js";
73
+ import { MemoryQueueStore, chunksOf, idsOf, singlesOf, withoutId, type QueuedOperation, type QueueStore } from "./queue.js";
74
74
  import type { SyncError, SyncStatus } from "./types.js";
75
75
  import type { RemoteChangeFeed, RemoteChangeHint, RemoteChanges } from "./feed.js";
76
76
  import { isSyncState, type SyncStateStore } from "./sync-state.js";
@@ -259,6 +259,33 @@ export interface HybridStorageOptions {
259
259
  */
260
260
  isTooLarge?: (error: unknown) => boolean;
261
261
 
262
+ /**
263
+ * This function reads the capacity of a send from an error of `isTooLarge` (roadmap CLI-121). It gives the units of
264
+ * the refused send and the capacity of the remote limit, or undefined. Only `pushBatch` reads it. The default reads
265
+ * the numbers `units` and `capacity` of the metadata of the error. A capacity refusal of a limit of the server gives
266
+ * these numbers.
267
+ */
268
+ capacityOf?: (error: unknown) => { readonly units: number; readonly capacity: number } | undefined;
269
+
270
+ /**
271
+ * The merge of queued writes into one batch for each send (roadmap CLI-121). Without it, the push sends each queued
272
+ * write alone. With it, the push sends the run of sets at the head of the queue as one `setBatch`. It sends the run
273
+ * of deletes as one `deleteBatch`. Thus a first sync of many writes makes few remote calls.
274
+ *
275
+ * - A run stops at a write of another kind and at an id that the run already has. It also stops at a write that
276
+ * waits for the queue store, and at the caps of `PushBatch`. So the order of the writes of each id stays.
277
+ * - A delete of one id with a base version does not join a run, because `deleteBatch` sends no base versions.
278
+ * - A queued batch over the caps goes as batches within the caps. Set the caps within the capacity of a remote limit
279
+ * for each call. Then no send of a flush is larger than that capacity. A limit with a cost for each item is an example.
280
+ * - The writes leave the queue only after the remote took the batch.
281
+ * - When the remote refuses a merged batch or finds a conflict in it, the push sends each write of the run again
282
+ * alone. Thus `onRefused`, `onDropped` and the conflict policy get the same writes as without the merge.
283
+ * - A send of n ids with an error of `isTooLarge` lowers the cap of ids of each later send. With a capacity of
284
+ * `capacityOf`, the cap is n × capacity / units. Without it, the cap is half of n. The push then sends the same
285
+ * writes in smaller sends. Only a write of one id with this error leaves the queue, with the reason `too-large`.
286
+ */
287
+ pushBatch?: PushBatch | undefined;
288
+
262
289
  /**
263
290
  * This hook gets each queued write that the remote refused (roadmap O67). The local copy of its
264
291
  * ids gets the remote values again, except for an id that a later queued write changes. An
@@ -786,9 +813,54 @@ const defaultIsRefusal = (error: unknown): boolean => {
786
813
  return typeof code === "string" && REFUSAL_CODES.has(code);
787
814
  };
788
815
 
816
+ /** The caps of a merged send of the queue (roadmap CLI-121). */
817
+ export interface PushBatch {
818
+ /** The most ids in one merged send. The default is 100. */
819
+ readonly maxItems?: number | undefined;
820
+ /** The most characters of the JSON text of the writes in one merged send. The default is 1048576 (1 MiB). */
821
+ readonly maxBytes?: number | undefined;
822
+ }
823
+
824
+ /** This function gives the caps of a merged send, with the defaults. It throws a `RangeError` for a cap that is not a whole number of 1 or more. */
825
+ function pushBatchOf(batch: PushBatch): { maxItems: number; maxBytes: number } {
826
+ const cap = (value: number | undefined, fallback: number, name: string): number => {
827
+ if (value === undefined) return fallback;
828
+ if (!Number.isInteger(value) || value < 1) throw new RangeError(`pushBatch.${name} must be a whole number of 1 or more, not ${String(value)}`);
829
+ return value;
830
+ };
831
+ return { maxItems: cap(batch.maxItems, 100, "maxItems"), maxBytes: cap(batch.maxBytes, 1_048_576, "maxBytes") };
832
+ }
833
+
834
+ /** This function gives the kind of a write in a merged send: `set` for a set or a set batch, `delete` for a delete or its batch. */
835
+ function mergeKind(op: QueuedOperation): "set" | "delete" | undefined {
836
+ if (op.type === "set" || op.type === "setBatch") return "set";
837
+ if (op.type === "delete" || op.type === "deleteBatch") return "delete";
838
+ return undefined;
839
+ }
840
+
841
+ /** This function gives one batch with the writes of a run, in their order (roadmap CLI-121). */
842
+ function mergedOperation(run: readonly QueuedOperation[]): QueuedOperation {
843
+ const timestamp = run[0]!.timestamp;
844
+ if (mergeKind(run[0]!) === "delete") return { type: "deleteBatch", data: { ids: run.flatMap(idsOf) }, timestamp };
845
+ const items = run.flatMap((op): Array<[string, unknown]> => {
846
+ const data = op.data as { id: string; value: unknown; items: Array<[string, unknown]> };
847
+ return op.type === "set" ? [[data.id, data.value]] : data.items;
848
+ });
849
+ return { type: "setBatch", data: { items }, timestamp };
850
+ }
851
+
789
852
  /** The default of `isTooLarge`: an error with the code `PAYLOAD_TOO_LARGE` (roadmap CLI-108). */
790
853
  const defaultIsTooLarge = (error: unknown): boolean => (error as { code?: unknown } | null | undefined)?.code === "PAYLOAD_TOO_LARGE";
791
854
 
855
+ /** The default of `capacityOf`: the numbers `units` and `capacity` of the metadata of the error, with units over the capacity. */
856
+ const defaultCapacityOf = (error: unknown): { units: number; capacity: number } | undefined => {
857
+ const metadata = (error as { metadata?: { units?: unknown; capacity?: unknown } | null } | null | undefined)?.metadata;
858
+ const units = metadata?.units;
859
+ const capacity = metadata?.capacity;
860
+ if (typeof units !== "number" || typeof capacity !== "number" || !Number.isFinite(units) || !Number.isFinite(capacity)) return undefined;
861
+ return capacity >= 1 && units > capacity ? { units, capacity } : undefined;
862
+ };
863
+
792
864
  /** True when a remote does not know a procedure: an older server without `changes`. */
793
865
  const isNotFound = (error: unknown): boolean =>
794
866
  typeof error === "object" && error !== null && (error as { code?: unknown }).code === "NOT_FOUND";
@@ -1066,6 +1138,8 @@ export class HybridStorage<T> implements CollectionStorage<T> {
1066
1138
  isUnavailable: (error: unknown) => boolean;
1067
1139
  isRefusal: (error: unknown) => boolean;
1068
1140
  isTooLarge: (error: unknown) => boolean;
1141
+ capacityOf: (error: unknown) => { readonly units: number; readonly capacity: number } | undefined;
1142
+ pushBatch?: { maxItems: number; maxBytes: number };
1069
1143
  onRefused?: (refusal: QueueRefusal) => void | Promise<void>;
1070
1144
  onDropped?: (drop: DroppedWrite) => void | Promise<void>;
1071
1145
  };
@@ -1073,6 +1147,12 @@ export class HybridStorage<T> implements CollectionStorage<T> {
1073
1147
  private offlineOps: QueuedOperation[] = [];
1074
1148
  /** The operations in `offlineOps` that the queue store does not have yet */
1075
1149
  private readonly unpersisted = new Set<QueuedOperation>();
1150
+ /** The count of operations at the head of the queue that the push sends at this time: more than 1 for a merged send (roadmap CLI-121) */
1151
+ private sendingCount = 0;
1152
+ /** The length of the JSON text of each queued operation, for the byte cap of a merged send */
1153
+ private readonly opSizes = new WeakMap<QueuedOperation, number>();
1154
+ /** The cap of ids of one send that an error of `isTooLarge` set, below `pushBatch.maxItems` (roadmap CLI-121) */
1155
+ private itemCap: number | undefined;
1076
1156
  /** True when the queue store can differ from `offlineOps`: a failed write, or sent operations */
1077
1157
  private storeDirty = false;
1078
1158
  /** The calls of the queue store, one at a time */
@@ -1224,6 +1304,8 @@ export class HybridStorage<T> implements CollectionStorage<T> {
1224
1304
  isUnavailable: options.isUnavailable ?? defaultIsUnavailable,
1225
1305
  isRefusal: options.isRefusal ?? defaultIsRefusal,
1226
1306
  isTooLarge: options.isTooLarge ?? defaultIsTooLarge,
1307
+ capacityOf: options.capacityOf ?? defaultCapacityOf,
1308
+ ...(options.pushBatch !== undefined && { pushBatch: pushBatchOf(options.pushBatch) }),
1227
1309
  ...(options.onRefused !== undefined && { onRefused: options.onRefused }),
1228
1310
  ...(options.onDropped !== undefined && { onDropped: options.onDropped }),
1229
1311
  };
@@ -3094,6 +3176,8 @@ export class HybridStorage<T> implements CollectionStorage<T> {
3094
3176
  let split = false;
3095
3177
  // The operations whose conflicts this push resolved once (roadmap CLI-16)
3096
3178
  const resolved = new Set<QueuedOperation>();
3179
+ // The operations that go alone: a merged send of them failed (roadmap CLI-121)
3180
+ const alone = new Set<QueuedOperation>();
3097
3181
  try {
3098
3182
  while (this.offlineOps.length > 0) {
3099
3183
  const op = this.offlineOps[0]!;
@@ -3102,6 +3186,51 @@ export class HybridStorage<T> implements CollectionStorage<T> {
3102
3186
  await this.storeChain;
3103
3187
  continue;
3104
3188
  }
3189
+ const chunks = this.chunksOfHead(op);
3190
+ if (chunks.length > 1) {
3191
+ // A batch over the caps goes as batches within the caps (roadmap CLI-121)
3192
+ if (alone.has(op)) for (const chunk of chunks) alone.add(chunk);
3193
+ this.offlineOps.splice(0, 1, ...chunks);
3194
+ split = true;
3195
+ this.storeDirty = true;
3196
+ continue;
3197
+ }
3198
+ const run = this.pushRun(alone);
3199
+ if (run.length > 1) {
3200
+ this.sendingCount = run.length;
3201
+ try {
3202
+ for (const queued of run) this.echoSent(queued);
3203
+ await this.executeOperation(mergedOperation(run));
3204
+ } catch (error) {
3205
+ const conflicts = this.sendsBaseVersions() ? versionConflictsOf(error) : undefined;
3206
+ const refusal = this.refusalOf(error, conflicts);
3207
+ if (refusal === "too-large" && this.lowerItemCap(run.reduce((sum, queued) => sum + idsOf(queued).length, 0), error)) {
3208
+ // The same writes go again in smaller sends
3209
+ this.isOnline = true;
3210
+ continue;
3211
+ }
3212
+ if ((conflicts !== undefined && conflicts.length > 0) || refusal !== undefined) {
3213
+ // Each write of the run goes again alone, so the policy and the hooks get each write as without the merge
3214
+ this.isOnline = true;
3215
+ for (const queued of run) alone.add(queued);
3216
+ continue;
3217
+ }
3218
+ // The run and the later operations stay queued
3219
+ if (conflicts === undefined) this.isOnline = false;
3220
+ throw error;
3221
+ } finally {
3222
+ this.sendingCount = 0;
3223
+ }
3224
+ for (const queued of run) this.noteRemoteWrite(queued);
3225
+ // Remove the operations only after they reached the remote
3226
+ for (const queued of run) {
3227
+ if (this.offlineOps[0] !== queued) break;
3228
+ this.offlineOps.shift();
3229
+ sent++;
3230
+ this.storeDirty = true;
3231
+ }
3232
+ continue;
3233
+ }
3105
3234
  try {
3106
3235
  this.echoSent(op);
3107
3236
  await this.executeOperation(op);
@@ -3121,13 +3250,18 @@ export class HybridStorage<T> implements CollectionStorage<T> {
3121
3250
  }
3122
3251
  continue;
3123
3252
  }
3124
- const tooLarge = conflicts === undefined && !(error instanceof QueueStoreError) && this.options.isTooLarge(error);
3125
- const refused = tooLarge || (conflicts === undefined && !(error instanceof QueueStoreError) && !this.options.isUnavailable(error) && this.options.isRefusal(error));
3126
- if (refused) {
3253
+ const refusal = this.refusalOf(error, conflicts);
3254
+ if (refusal === "too-large" && this.lowerItemCap(idsOf(op).length, error)) {
3255
+ // The batch goes again in smaller batches, not as one write for each id (roadmap CLI-121)
3256
+ this.isOnline = true;
3257
+ continue;
3258
+ }
3259
+ if (refusal !== undefined) {
3127
3260
  this.isOnline = true;
3128
3261
  const singles = singlesOf(op);
3129
3262
  if (singles.length > 1 && this.offlineOps[0] === op) {
3130
3263
  // A batch goes again as one write for each id, so only the bad write leaves the queue (roadmap CLI-108)
3264
+ if (alone.has(op)) for (const single of singles) alone.add(single);
3131
3265
  this.offlineOps.splice(0, 1, ...singles);
3132
3266
  split = true;
3133
3267
  this.storeDirty = true;
@@ -3139,7 +3273,7 @@ export class HybridStorage<T> implements CollectionStorage<T> {
3139
3273
  sent++;
3140
3274
  this.storeDirty = true;
3141
3275
  }
3142
- await this.refuseQueued(op, error, tooLarge ? "too-large" : "refused");
3276
+ await this.refuseQueued(op, error, refusal);
3143
3277
  continue;
3144
3278
  }
3145
3279
  // This operation and the later ones stay queued
@@ -3165,6 +3299,89 @@ export class HybridStorage<T> implements CollectionStorage<T> {
3165
3299
  }
3166
3300
  }
3167
3301
 
3302
+ /**
3303
+ * This method tells if the remote refused a write: `too-large`, `refused`, or undefined for another error. A conflict
3304
+ * and an error of the queue store are not a refusal.
3305
+ */
3306
+ private refusalOf(error: unknown, conflicts: readonly VersionConflict[] | undefined): "too-large" | "refused" | undefined {
3307
+ if (conflicts !== undefined || error instanceof QueueStoreError) return undefined;
3308
+ if (this.options.isTooLarge(error)) return "too-large";
3309
+ return !this.options.isUnavailable(error) && this.options.isRefusal(error) ? "refused" : undefined;
3310
+ }
3311
+
3312
+ /**
3313
+ * This method gives the run of queued operations at the head of the queue that one send takes (roadmap CLI-121). The
3314
+ * run is the head operation alone when `pushBatch` is not set, or when the head cannot join a merged send.
3315
+ */
3316
+ private pushRun(alone: ReadonlySet<QueuedOperation>): QueuedOperation[] {
3317
+ const head = this.offlineOps[0]!;
3318
+ const caps = this.options.pushBatch;
3319
+ const kind = mergeKind(head);
3320
+ if (caps === undefined || kind === undefined || alone.has(head) || !this.canJoinRun(head)) return [head];
3321
+ const run: QueuedOperation[] = [];
3322
+ const ids = new Set<string>();
3323
+ let items = 0;
3324
+ let bytes = 0;
3325
+ for (const op of this.offlineOps) {
3326
+ if (mergeKind(op) !== kind || alone.has(op) || this.unpersisted.has(op) || !this.canJoinRun(op)) break;
3327
+ const opIds = idsOf(op);
3328
+ // A second write of an id goes in a later send, so the order of the writes of the id stays
3329
+ if (opIds.some((id) => ids.has(id))) break;
3330
+ const size = this.sizeOf(op);
3331
+ if (run.length > 0 && (items + opIds.length > this.maxItemsOf(caps) || bytes + size > caps.maxBytes)) break;
3332
+ run.push(op);
3333
+ for (const id of opIds) ids.add(id);
3334
+ items += opIds.length;
3335
+ bytes += size;
3336
+ }
3337
+ return run;
3338
+ }
3339
+
3340
+ /** This method gives the batches within the caps of `pushBatch` of the head operation, or the head alone. */
3341
+ private chunksOfHead(op: QueuedOperation): QueuedOperation[] {
3342
+ const caps = this.options.pushBatch;
3343
+ if (caps === undefined || mergeKind(op) === undefined) return [op];
3344
+ const maxItems = this.maxItemsOf(caps);
3345
+ if (idsOf(op).length <= maxItems && this.sizeOf(op) <= caps.maxBytes) return [op];
3346
+ return chunksOf(op, maxItems, caps.maxBytes);
3347
+ }
3348
+
3349
+ /** This method gives the cap of ids of one send: `pushBatch.maxItems`, or the lower cap that an error of `isTooLarge` set. */
3350
+ private maxItemsOf(caps: { maxItems: number }): number {
3351
+ return this.itemCap === undefined ? caps.maxItems : Math.min(caps.maxItems, this.itemCap);
3352
+ }
3353
+
3354
+ /**
3355
+ * This method lowers the cap of ids of each later send after an error of `isTooLarge` for a send of `ids` ids (roadmap
3356
+ * CLI-121). With a capacity of `capacityOf`, the cap is ids × capacity / units, else half of the ids. It is false when
3357
+ * `pushBatch` is not set or the send had one id: then the write leaves the queue as before.
3358
+ */
3359
+ private lowerItemCap(ids: number, error: unknown): boolean {
3360
+ if (this.options.pushBatch === undefined || ids <= 1) return false;
3361
+ const capacity = this.options.capacityOf(error);
3362
+ const next = capacity === undefined ? Math.floor(ids / 2) : Math.floor((ids * capacity.capacity) / capacity.units);
3363
+ // Each lower cap is smaller than the refused send, thus the push ends
3364
+ const cap = Math.max(1, Math.min(ids - 1, next));
3365
+ this.itemCap = this.itemCap === undefined ? cap : Math.min(this.itemCap, cap);
3366
+ return true;
3367
+ }
3368
+
3369
+ /** True when an operation can join a merged send. A delete of one id with a base version cannot: `deleteBatch` sends no base versions. */
3370
+ private canJoinRun(op: QueuedOperation): boolean {
3371
+ if (op.type !== "delete" || !this.sendsBaseVersions()) return true;
3372
+ return this.bases.get((op.data as { id: string }).id) === undefined;
3373
+ }
3374
+
3375
+ /** The length of the JSON text of the data of an operation. */
3376
+ private sizeOf(op: QueuedOperation): number {
3377
+ let size = this.opSizes.get(op);
3378
+ if (size === undefined) {
3379
+ size = JSON.stringify(op.data)?.length ?? 0;
3380
+ this.opSizes.set(op, size);
3381
+ }
3382
+ return size;
3383
+ }
3384
+
3168
3385
  /**
3169
3386
  * This method ends a queued write that the remote refused (roadmap O67). The local copy of its ids
3170
3387
  * gets the remote values, and the hook `onRefused` gets the refusal. A refused clear asks for a
@@ -3576,8 +3793,8 @@ export class HybridStorage<T> implements CollectionStorage<T> {
3576
3793
  const queued = this.offlineOps[index]!;
3577
3794
  const ids = idsOf(queued);
3578
3795
  if (!ids.includes(id) && !ids.includes("*")) continue;
3579
- // pushQueue can be sending the first operation now: do not change it
3580
- const sending = index === 0 && this.pushing;
3796
+ // pushQueue can be sending the first operations at this time: do not change them
3797
+ const sending = index < Math.max(1, this.sendingCount) && this.pushing;
3581
3798
  return !sending && (queued.type === "set" || queued.type === "delete") ? index : -1;
3582
3799
  }
3583
3800
  return -1;
@@ -55,6 +55,7 @@ export type {
55
55
  HybridStorageOptions,
56
56
  HybridMode,
57
57
  LocalCopyStorage,
58
+ PushBatch,
58
59
  QueueRefusal,
59
60
  RemoteApplyCause,
60
61
  RemoteApplyContext,
@@ -143,6 +143,44 @@ export function singlesOf(op: QueuedOperation): QueuedOperation[] {
143
143
  }
144
144
  }
145
145
 
146
+ /**
147
+ * This function cuts a batch into batches of consecutive ids within the caps, in its order, with the base version of
148
+ * each id (roadmap CLI-121). The size of an entry is the length of its JSON text. An entry over `maxBytes` goes in a
149
+ * chunk alone. A set, a delete and a clear give an array of the operation itself.
150
+ */
151
+ export function chunksOf(op: QueuedOperation, maxItems: number, maxBytes: number): QueuedOperation[] {
152
+ if (op.type !== "setBatch" && op.type !== "deleteBatch") return [op];
153
+ const data = op.data as { ids?: string[]; items?: Array<[string, unknown]> };
154
+ const entries: Array<string | [string, unknown]> = op.type === "setBatch" ? (data.items ?? []) : (data.ids ?? []);
155
+ const groups: Array<Array<string | [string, unknown]>> = [];
156
+ let group: Array<string | [string, unknown]> = [];
157
+ let bytes = 0;
158
+ for (const entry of entries) {
159
+ const size = JSON.stringify(entry)?.length ?? 0;
160
+ if (group.length > 0 && (group.length + 1 > maxItems || bytes + size > maxBytes)) {
161
+ groups.push(group);
162
+ group = [];
163
+ bytes = 0;
164
+ }
165
+ group.push(entry);
166
+ bytes += size;
167
+ }
168
+ if (group.length > 0) groups.push(group);
169
+ if (groups.length <= 1) return [op];
170
+ return groups.map((chunk) => {
171
+ const ids = new Set(chunk.map((entry) => (typeof entry === "string" ? entry : entry[0])));
172
+ const base = op.base?.filter(([id]) => ids.has(id));
173
+ const baseValues = op.baseValues?.filter(([id]) => ids.has(id));
174
+ return {
175
+ type: op.type,
176
+ data: op.type === "setBatch" ? { items: chunk as Array<[string, unknown]> } : { ids: chunk as string[] },
177
+ timestamp: op.timestamp,
178
+ ...(base !== undefined && base.length > 0 && { base }),
179
+ ...(baseValues !== undefined && baseValues.length > 0 && { baseValues }),
180
+ };
181
+ });
182
+ }
183
+
146
184
  /** True when a value has the shape of a queued operation. A loaded file can hold anything. */
147
185
  export function isQueuedOperation(value: unknown): value is QueuedOperation {
148
186
  if (typeof value !== "object" || value === null) return false;