@qkitt/queue 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,531 @@
1
+ import { createMemorySnapshotStore, createWebSnapshotStore, createLocalStorageSnapshotStore, createSessionStorageSnapshotStore, createMemoryRowStore, createWebRowStore, createLocalStorageRowStore, createSessionStorageRowStore } from './chunk-SMQYICXZ.js';
2
+ import { buildQueue, withSnapshotPersist, withRowPersist, withWorker } from './chunk-653YYS22.js';
3
+ import { buildRouter } from './chunk-WIKDEC7T.js';
4
+
5
+ // src/config/config-freeze.util.ts
6
+ var freezeConfig = (config) => {
7
+ const queues = {};
8
+ for (const [name, queue] of Object.entries(config.queues)) {
9
+ queues[name] = Object.freeze({ ...queue });
10
+ }
11
+ const stores = {};
12
+ if (config.stores) {
13
+ for (const [name, store] of Object.entries(config.stores)) {
14
+ stores[name] = Object.freeze({ ...store });
15
+ }
16
+ }
17
+ const frozen = {
18
+ ...config,
19
+ ...Object.keys(stores).length > 0 ? { stores: Object.freeze(stores) } : {},
20
+ queues: Object.freeze(queues),
21
+ ...config.router ? {
22
+ router: Object.freeze({
23
+ ...config.router,
24
+ ...config.router.bindings ? {
25
+ bindings: Object.freeze(
26
+ config.router.bindings.map(
27
+ (b) => Object.freeze({ ...b })
28
+ )
29
+ )
30
+ } : {},
31
+ ...config.router.unmatchedQueue !== void 0 ? { unmatchedQueue: config.router.unmatchedQueue } : {}
32
+ })
33
+ } : {}
34
+ };
35
+ return Object.freeze(frozen);
36
+ };
37
+
38
+ // src/config/store-resolve.util.ts
39
+ var isWebAdapter = (adapter) => adapter === "localStorage" || adapter === "sessionStorage";
40
+ var isSnapshotStore = (value) => typeof value.load === "function" && typeof value.save === "function";
41
+ var isRowStore = (value) => {
42
+ const store = value;
43
+ return typeof store.loadAll === "function" && typeof store.insert === "function" && typeof store.remove === "function" && typeof store.clear === "function";
44
+ };
45
+ var createBuiltinSnapshot = (storeName, adapter, key, options) => {
46
+ if (adapter === "memory") {
47
+ return createMemorySnapshotStore();
48
+ }
49
+ if (key === void 0 || key.length === 0) {
50
+ throw new Error(
51
+ `config.stores.${storeName}.key is required when adapter is "${adapter}"`
52
+ );
53
+ }
54
+ if (options.storage) {
55
+ return createWebSnapshotStore({
56
+ key,
57
+ storage: options.storage
58
+ });
59
+ }
60
+ if (adapter === "localStorage") {
61
+ return createLocalStorageSnapshotStore(key);
62
+ }
63
+ return createSessionStorageSnapshotStore(key);
64
+ };
65
+ var createBuiltinRow = (storeName, adapter, key, options) => {
66
+ if (adapter === "memory") {
67
+ return createMemoryRowStore();
68
+ }
69
+ if (key === void 0 || key.length === 0) {
70
+ throw new Error(
71
+ `config.stores.${storeName}.key is required when adapter is "${adapter}"`
72
+ );
73
+ }
74
+ if (options.storage) {
75
+ return createWebRowStore({
76
+ key,
77
+ storage: options.storage
78
+ });
79
+ }
80
+ if (adapter === "localStorage") {
81
+ return createLocalStorageRowStore(key);
82
+ }
83
+ return createSessionStorageRowStore(key);
84
+ };
85
+ var resolveStore = (storeName, definition, options) => {
86
+ if ("impl" in definition) {
87
+ const impl = definition.impl;
88
+ if (definition.strategy === "snapshot" && !isSnapshotStore(impl)) {
89
+ throw new Error(
90
+ `config.stores.${storeName}.impl must be a SnapshotStore (strategy is "snapshot")`
91
+ );
92
+ }
93
+ if (definition.strategy === "row" && !isRowStore(impl)) {
94
+ throw new Error(
95
+ `config.stores.${storeName}.impl must be a RowStore (strategy is "row")`
96
+ );
97
+ }
98
+ return impl;
99
+ }
100
+ const { adapter, strategy } = definition;
101
+ const key = definition.key;
102
+ if (isWebAdapter(adapter) && (key === void 0 || key.length === 0)) {
103
+ throw new Error(
104
+ `config.stores.${storeName}.key is required when adapter is "${adapter}"`
105
+ );
106
+ }
107
+ if (strategy === "snapshot") {
108
+ return createBuiltinSnapshot(storeName, adapter, key, options);
109
+ }
110
+ return createBuiltinRow(storeName, adapter, key, options);
111
+ };
112
+ var resolveAllStores = (stores, options) => {
113
+ const resolved = {};
114
+ if (!stores) return resolved;
115
+ for (const [name, definition] of Object.entries(stores)) {
116
+ resolved[name] = resolveStore(name, definition, options);
117
+ }
118
+ return resolved;
119
+ };
120
+
121
+ // src/config/parse.util.ts
122
+ var BUILTIN_ADAPTERS = /* @__PURE__ */ new Set([
123
+ "memory",
124
+ "localStorage",
125
+ "sessionStorage"
126
+ ]);
127
+ var isPlainObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
128
+ var expectString = (value, path) => {
129
+ if (typeof value !== "string" || value.length === 0) {
130
+ throw new Error(`${path} must be a non-empty string`);
131
+ }
132
+ return value;
133
+ };
134
+ var expectBoolean = (value, path) => {
135
+ if (typeof value !== "boolean") {
136
+ throw new Error(`${path} must be a boolean`);
137
+ }
138
+ return value;
139
+ };
140
+ var expectPositiveFinite = (value, path) => {
141
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 1) {
142
+ throw new Error(`${path} must be a finite number >= 1`);
143
+ }
144
+ return value;
145
+ };
146
+ var parseAdapter = (value, path) => {
147
+ if (typeof value !== "string" || !BUILTIN_ADAPTERS.has(value)) {
148
+ throw new Error(
149
+ `${path} must be one of: memory, localStorage, sessionStorage`
150
+ );
151
+ }
152
+ return value;
153
+ };
154
+ var isSnapshotStoreLike = (value) => isPlainObject(value) && typeof value.load === "function" && typeof value.save === "function";
155
+ var isRowStoreLike = (value) => isPlainObject(value) && typeof value.loadAll === "function" && typeof value.insert === "function" && typeof value.remove === "function" && typeof value.clear === "function";
156
+ var parseStrategy = (value, path) => {
157
+ if (value !== "snapshot" && value !== "row") {
158
+ throw new Error(`${path} must be "snapshot" or "row"`);
159
+ }
160
+ return value;
161
+ };
162
+
163
+ // src/config/validate.ts
164
+ var parseStoreDefinition = (value, path, { allowJs }) => {
165
+ if (!isPlainObject(value)) {
166
+ throw new Error(`${path} must be an object`);
167
+ }
168
+ const strategy = parseStrategy(value.strategy, `${path}.strategy`);
169
+ if (value.impl !== void 0) {
170
+ if (!allowJs) {
171
+ throw new Error(
172
+ `${path}.impl is only valid in JS config (not JSON); implement SnapshotStore/RowStore and pass the instance from a module`
173
+ );
174
+ }
175
+ if (value.adapter !== void 0) {
176
+ throw new Error(
177
+ `${path} cannot set both "adapter" and "impl"`
178
+ );
179
+ }
180
+ if (strategy === "snapshot") {
181
+ if (!isSnapshotStoreLike(value.impl)) {
182
+ throw new Error(
183
+ `${path}.impl must be a SnapshotStore (load + save)`
184
+ );
185
+ }
186
+ return {
187
+ strategy: "snapshot",
188
+ impl: value.impl
189
+ };
190
+ }
191
+ if (!isRowStoreLike(value.impl)) {
192
+ throw new Error(
193
+ `${path}.impl must be a RowStore (loadAll + insert + remove + clear)`
194
+ );
195
+ }
196
+ return {
197
+ strategy: "row",
198
+ impl: value.impl
199
+ };
200
+ }
201
+ if (value.adapter === void 0) {
202
+ throw new Error(
203
+ `${path} must define "adapter" (built-in) or "impl" (custom store)`
204
+ );
205
+ }
206
+ const adapter = parseAdapter(value.adapter, `${path}.adapter`);
207
+ const key = value.key === void 0 ? void 0 : expectString(value.key, `${path}.key`);
208
+ if ((adapter === "localStorage" || adapter === "sessionStorage") && (key === void 0 || key.length === 0)) {
209
+ throw new Error(
210
+ `${path}.key is required when adapter is "${adapter}"`
211
+ );
212
+ }
213
+ if (strategy === "snapshot") {
214
+ return {
215
+ strategy: "snapshot",
216
+ adapter,
217
+ ...key !== void 0 ? { key } : {}
218
+ };
219
+ }
220
+ return {
221
+ strategy: "row",
222
+ adapter,
223
+ ...key !== void 0 ? { key } : {}
224
+ };
225
+ };
226
+ var parsePersistConfig = (value, path, storeNames) => {
227
+ if (!isPlainObject(value)) {
228
+ throw new Error(`${path} must be an object`);
229
+ }
230
+ const store = expectString(value.store, `${path}.store`);
231
+ if (!storeNames.has(store)) {
232
+ throw new Error(
233
+ `${path}.store "${store}" is not defined in config.stores`
234
+ );
235
+ }
236
+ const autoSave = value.autoSave === void 0 ? void 0 : expectBoolean(value.autoSave, `${path}.autoSave`);
237
+ return {
238
+ store,
239
+ ...autoSave !== void 0 ? { autoSave } : {}
240
+ };
241
+ };
242
+ var parseWorkerConfig = (value, path) => {
243
+ if (typeof value === "function") {
244
+ return value;
245
+ }
246
+ if (!isPlainObject(value)) {
247
+ throw new Error(
248
+ `${path} must be a function or { run, concurrency?, autoStart? }`
249
+ );
250
+ }
251
+ if (typeof value.run !== "function") {
252
+ throw new Error(`${path}.run must be a function`);
253
+ }
254
+ const concurrency = value.concurrency === void 0 ? void 0 : expectPositiveFinite(value.concurrency, `${path}.concurrency`);
255
+ const autoStart = value.autoStart === void 0 ? void 0 : expectBoolean(value.autoStart, `${path}.autoStart`);
256
+ return {
257
+ run: value.run,
258
+ ...concurrency !== void 0 ? { concurrency } : {},
259
+ ...autoStart !== void 0 ? { autoStart } : {}
260
+ };
261
+ };
262
+ var parseQueueConfig = (value, path, storeNames, { allowJs }) => {
263
+ if (!isPlainObject(value)) {
264
+ throw new Error(`${path} must be an object`);
265
+ }
266
+ const queue = {};
267
+ if (value.maxSize !== void 0) {
268
+ queue.maxSize = expectPositiveFinite(value.maxSize, `${path}.maxSize`);
269
+ }
270
+ if (value.persist !== void 0) {
271
+ queue.persist = parsePersistConfig(
272
+ value.persist,
273
+ `${path}.persist`,
274
+ storeNames
275
+ );
276
+ }
277
+ if (value.worker !== void 0) {
278
+ if (!allowJs) {
279
+ throw new Error(
280
+ `${path}.worker is only valid in JS config (functions cannot be expressed in JSON)`
281
+ );
282
+ }
283
+ queue.worker = parseWorkerConfig(value.worker, `${path}.worker`);
284
+ }
285
+ return queue;
286
+ };
287
+ var parseBindingConfig = (value, path) => {
288
+ if (!isPlainObject(value)) {
289
+ throw new Error(`${path} must be an object`);
290
+ }
291
+ return {
292
+ pattern: expectString(value.pattern, `${path}.pattern`),
293
+ queue: expectString(value.queue, `${path}.queue`)
294
+ };
295
+ };
296
+ var parseRouterConfig = (value, path) => {
297
+ if (!isPlainObject(value)) {
298
+ throw new Error(`${path} must be an object`);
299
+ }
300
+ const router = {};
301
+ if (value.bindings !== void 0) {
302
+ if (!Array.isArray(value.bindings)) {
303
+ throw new Error(`${path}.bindings must be an array`);
304
+ }
305
+ router.bindings = value.bindings.map(
306
+ (binding, index) => parseBindingConfig(binding, `${path}.bindings[${index}]`)
307
+ );
308
+ }
309
+ if (value.unmatchedQueue !== void 0) {
310
+ router.unmatchedQueue = expectString(
311
+ value.unmatchedQueue,
312
+ `${path}.unmatchedQueue`
313
+ );
314
+ }
315
+ return router;
316
+ };
317
+ var parseSystemConfigValue = (value, options) => {
318
+ if (!isPlainObject(value)) {
319
+ throw new Error("config must be an object");
320
+ }
321
+ if (!isPlainObject(value.queues)) {
322
+ throw new Error("config.queues must be an object");
323
+ }
324
+ const queueNames = Object.keys(value.queues);
325
+ if (queueNames.length === 0) {
326
+ throw new Error("config.queues must define at least one queue");
327
+ }
328
+ const stores = {};
329
+ if (value.stores !== void 0) {
330
+ if (!isPlainObject(value.stores)) {
331
+ throw new Error("config.stores must be an object");
332
+ }
333
+ for (const name of Object.keys(value.stores)) {
334
+ if (name.length === 0) {
335
+ throw new Error("config.stores keys must be non-empty strings");
336
+ }
337
+ stores[name] = parseStoreDefinition(
338
+ value.stores[name],
339
+ `config.stores.${name}`,
340
+ { allowJs: options.allowJs }
341
+ );
342
+ }
343
+ }
344
+ const storeNames = new Set(Object.keys(stores));
345
+ const queues = {};
346
+ for (const name of queueNames) {
347
+ if (name.length === 0) {
348
+ throw new Error("config.queues keys must be non-empty strings");
349
+ }
350
+ queues[name] = parseQueueConfig(
351
+ value.queues[name],
352
+ `config.queues.${name}`,
353
+ storeNames,
354
+ { allowJs: options.allowJs }
355
+ );
356
+ }
357
+ const config = { queues };
358
+ if (Object.keys(stores).length > 0) {
359
+ config.stores = stores;
360
+ }
361
+ if (value.router !== void 0) {
362
+ config.router = parseRouterConfig(value.router, "config.router");
363
+ }
364
+ if (value.hydrate !== void 0) {
365
+ config.hydrate = expectBoolean(value.hydrate, "config.hydrate");
366
+ }
367
+ if (config.router?.bindings) {
368
+ for (const [index, binding] of config.router.bindings.entries()) {
369
+ if (!(binding.queue in queues)) {
370
+ throw new Error(
371
+ `config.router.bindings[${index}].queue "${binding.queue}" is not defined in config.queues`
372
+ );
373
+ }
374
+ }
375
+ }
376
+ if (config.router?.unmatchedQueue !== void 0) {
377
+ if (!(config.router.unmatchedQueue in queues)) {
378
+ throw new Error(
379
+ `config.router.unmatchedQueue "${config.router.unmatchedQueue}" is not defined in config.queues`
380
+ );
381
+ }
382
+ }
383
+ return config;
384
+ };
385
+ var validateSystemConfig = (value) => parseSystemConfigValue(value, { allowJs: false });
386
+ var validateJsConfig = (value) => parseSystemConfigValue(value, { allowJs: true });
387
+ var defineConfig = (config) => validateJsConfig(config);
388
+ var parseSystemConfig = (json) => {
389
+ let parsed;
390
+ try {
391
+ parsed = JSON.parse(json);
392
+ } catch (error) {
393
+ const message = error instanceof Error ? error.message : "invalid JSON";
394
+ throw new Error(`config JSON is invalid: ${message}`);
395
+ }
396
+ return validateSystemConfig(parsed);
397
+ };
398
+
399
+ // src/config/from-config.ts
400
+ var resolveWorker = (worker) => {
401
+ if (typeof worker === "function") {
402
+ return { run: worker, options: {} };
403
+ }
404
+ const { run, concurrency, autoStart } = worker;
405
+ return {
406
+ run,
407
+ options: {
408
+ ...concurrency !== void 0 ? { concurrency } : {},
409
+ ...autoStart !== void 0 ? { autoStart } : {}
410
+ }
411
+ };
412
+ };
413
+ var buildQueueFromConfig = (queueName, queueConfig, storeDefs, resolvedStores) => {
414
+ let queue = buildQueue(
415
+ queueConfig.maxSize !== void 0 ? { maxSize: queueConfig.maxSize } : {}
416
+ );
417
+ if (queueConfig.persist) {
418
+ const storeName = queueConfig.persist.store;
419
+ const definition = storeDefs?.[storeName];
420
+ const store = resolvedStores[storeName];
421
+ if (!definition || !store) {
422
+ throw new Error(
423
+ `config.queues.${queueName}.persist.store "${storeName}" is not defined in config.stores`
424
+ );
425
+ }
426
+ if (definition.strategy === "snapshot") {
427
+ if (!isSnapshotStore(store)) {
428
+ throw new Error(
429
+ `config.stores.${storeName} is not a SnapshotStore`
430
+ );
431
+ }
432
+ queue = withSnapshotPersist(queue, store, {
433
+ autoSave: queueConfig.persist.autoSave
434
+ });
435
+ } else {
436
+ if (!isRowStore(store)) {
437
+ throw new Error(
438
+ `config.stores.${storeName} is not a RowStore`
439
+ );
440
+ }
441
+ queue = withRowPersist(queue, store);
442
+ }
443
+ }
444
+ if (queueConfig.worker) {
445
+ const { run, options: workerOptions } = resolveWorker(queueConfig.worker);
446
+ queue = withWorker(queue, run, workerOptions);
447
+ }
448
+ return queue;
449
+ };
450
+ var buildFromConfig = async (config, options = {}) => {
451
+ const validated = validateJsConfig(config);
452
+ const resolvedStores = resolveAllStores(validated.stores, options);
453
+ const queues = {};
454
+ for (const [name, queueConfig] of Object.entries(validated.queues)) {
455
+ queues[name] = buildQueueFromConfig(
456
+ name,
457
+ queueConfig,
458
+ validated.stores,
459
+ resolvedStores
460
+ );
461
+ }
462
+ let router;
463
+ if (validated.router) {
464
+ const queueMap = queues;
465
+ let unmatchedTarget;
466
+ if (validated.router.unmatchedQueue !== void 0) {
467
+ const sink = queueMap[validated.router.unmatchedQueue];
468
+ if (!sink) {
469
+ throw new Error(
470
+ `router unmatchedQueue "${validated.router.unmatchedQueue}" is not defined`
471
+ );
472
+ }
473
+ unmatchedTarget = sink;
474
+ }
475
+ const built = buildRouter(
476
+ unmatchedTarget !== void 0 ? { unmatchedTarget } : {}
477
+ );
478
+ for (const binding of validated.router.bindings ?? []) {
479
+ const target = queueMap[binding.queue];
480
+ if (!target) {
481
+ throw new Error(
482
+ `router binding queue "${binding.queue}" is not defined`
483
+ );
484
+ }
485
+ built.bind(binding.pattern, target);
486
+ }
487
+ router = built;
488
+ }
489
+ const hydrateAll = async () => {
490
+ const tasks = [];
491
+ for (const queue of Object.values(
492
+ queues
493
+ )) {
494
+ if (typeof queue.hydrate === "function") {
495
+ tasks.push(queue.hydrate());
496
+ }
497
+ }
498
+ await Promise.all(tasks);
499
+ };
500
+ const flushAll = async () => {
501
+ const tasks = [];
502
+ for (const queue of Object.values(
503
+ queues
504
+ )) {
505
+ if (typeof queue.flush === "function") {
506
+ tasks.push(queue.flush());
507
+ }
508
+ }
509
+ await Promise.all(tasks);
510
+ };
511
+ const shouldHydrate = validated.hydrate ?? Object.values(validated.queues).some((q) => q.persist !== void 0);
512
+ if (shouldHydrate) {
513
+ await hydrateAll();
514
+ }
515
+ return {
516
+ queues,
517
+ stores: resolvedStores,
518
+ router,
519
+ hydrateAll,
520
+ flushAll,
521
+ config: freezeConfig(validated)
522
+ };
523
+ };
524
+ var buildFromJson = async (json, options = {}) => {
525
+ const config = parseSystemConfig(json);
526
+ return buildFromConfig(config, options);
527
+ };
528
+
529
+ export { buildFromConfig, buildFromJson, defineConfig, parseSystemConfig, validateJsConfig, validateSystemConfig };
530
+ //# sourceMappingURL=chunk-R2ONNABJ.js.map
531
+ //# sourceMappingURL=chunk-R2ONNABJ.js.map