@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,203 @@
1
+ // src/persist/memory.ts
2
+ var createMemorySnapshotStore = (initial = []) => {
3
+ const data = [...initial];
4
+ return {
5
+ get data() {
6
+ return data;
7
+ },
8
+ load: () => data.slice(),
9
+ save: (items) => {
10
+ data.length = 0;
11
+ data.push(...items);
12
+ }
13
+ };
14
+ };
15
+ var createMemoryRowStore = (initial = []) => {
16
+ const rows = initial.map((row) => ({
17
+ id: row.id,
18
+ item: row.item
19
+ }));
20
+ return {
21
+ get rows() {
22
+ return rows;
23
+ },
24
+ loadAll: () => rows.map((row) => ({ id: row.id, item: row.item })),
25
+ insert: (record) => {
26
+ const index = rows.findIndex((row) => row.id === record.id);
27
+ const next = { id: record.id, item: record.item };
28
+ if (index >= 0) {
29
+ rows[index] = next;
30
+ } else {
31
+ rows.push(next);
32
+ }
33
+ },
34
+ remove: (id) => {
35
+ const index = rows.findIndex((row) => row.id === id);
36
+ if (index >= 0) {
37
+ rows.splice(index, 1);
38
+ }
39
+ },
40
+ clear: () => {
41
+ rows.length = 0;
42
+ }
43
+ };
44
+ };
45
+
46
+ // src/persist/json-codec.util.ts
47
+ var StorageCodecError = class extends Error {
48
+ name = "StorageCodecError";
49
+ cause;
50
+ constructor(message, cause) {
51
+ super(message, cause !== void 0 ? { cause } : void 0);
52
+ this.cause = cause;
53
+ }
54
+ };
55
+ var defaultJsonCodec = () => ({
56
+ serialize: (value) => JSON.stringify(value),
57
+ deserialize: (raw) => JSON.parse(raw)
58
+ });
59
+ var decodeWithCodec = (label, raw, deserialize) => {
60
+ try {
61
+ return deserialize(raw);
62
+ } catch (error) {
63
+ throw new StorageCodecError(
64
+ `Failed to deserialize ${label}: ${error instanceof Error ? error.message : String(error)}`,
65
+ error
66
+ );
67
+ }
68
+ };
69
+
70
+ // src/persist/web-storage-access.util.ts
71
+ var getGlobalStorage = (name) => {
72
+ const storage = globalThis[name];
73
+ if (!storage) {
74
+ throw new Error(
75
+ `${name} is not available; pass an explicit \`storage\` option`
76
+ );
77
+ }
78
+ return storage;
79
+ };
80
+ var lazyGlobalStorage = (name) => ({
81
+ getItem: (key) => getGlobalStorage(name).getItem(key),
82
+ setItem: (key, value) => getGlobalStorage(name).setItem(key, value),
83
+ removeItem: (key) => getGlobalStorage(name).removeItem(key)
84
+ });
85
+ var resolveStorage = (storage) => {
86
+ if (storage) return storage;
87
+ return lazyGlobalStorage("localStorage");
88
+ };
89
+
90
+ // src/persist/web-storage.ts
91
+ var createWebSnapshotStore = (options) => {
92
+ const storage = () => resolveStorage(options.storage);
93
+ const codec = options.codec ?? defaultJsonCodec();
94
+ return {
95
+ load: () => {
96
+ const raw = storage().getItem(options.key);
97
+ if (raw === null || raw === "") return [];
98
+ const items = decodeWithCodec(
99
+ `snapshot "${options.key}"`,
100
+ raw,
101
+ codec.deserialize
102
+ );
103
+ return Array.isArray(items) ? items : [];
104
+ },
105
+ save: (items) => {
106
+ storage().setItem(options.key, codec.serialize([...items]));
107
+ }
108
+ };
109
+ };
110
+ var orderCodec = {
111
+ serialize: (ids) => JSON.stringify(ids),
112
+ deserialize: (raw) => {
113
+ const ids = JSON.parse(raw);
114
+ if (!Array.isArray(ids)) return [];
115
+ return ids.filter((id) => typeof id === "string");
116
+ }
117
+ };
118
+ var createWebRowStore = (options) => {
119
+ const storage = () => resolveStorage(options.storage);
120
+ const itemCodec = options.itemCodec ?? defaultJsonCodec();
121
+ const orderKey = `${options.key}:order`;
122
+ const rowKey = (id) => `${options.key}:row:${id}`;
123
+ const readOrder = () => {
124
+ const raw = storage().getItem(orderKey);
125
+ if (raw === null || raw === "") return [];
126
+ return decodeWithCodec(
127
+ `row order "${orderKey}"`,
128
+ raw,
129
+ orderCodec.deserialize
130
+ );
131
+ };
132
+ const writeOrder = (ids) => {
133
+ if (ids.length === 0) {
134
+ storage().removeItem(orderKey);
135
+ return;
136
+ }
137
+ storage().setItem(orderKey, orderCodec.serialize(ids));
138
+ };
139
+ return {
140
+ loadAll: () => {
141
+ const ids = readOrder();
142
+ const rows = [];
143
+ for (const id of ids) {
144
+ const raw = storage().getItem(rowKey(id));
145
+ if (raw === null) continue;
146
+ rows.push({
147
+ id,
148
+ item: decodeWithCodec(
149
+ `row "${rowKey(id)}"`,
150
+ raw,
151
+ itemCodec.deserialize
152
+ )
153
+ });
154
+ }
155
+ return rows;
156
+ },
157
+ insert: (record) => {
158
+ const store = storage();
159
+ store.setItem(rowKey(record.id), itemCodec.serialize(record.item));
160
+ const ids = readOrder();
161
+ if (!ids.includes(record.id)) {
162
+ ids.push(record.id);
163
+ writeOrder(ids);
164
+ }
165
+ },
166
+ remove: (id) => {
167
+ const store = storage();
168
+ store.removeItem(rowKey(id));
169
+ writeOrder(readOrder().filter((entry) => entry !== id));
170
+ },
171
+ clear: () => {
172
+ const store = storage();
173
+ for (const id of readOrder()) {
174
+ store.removeItem(rowKey(id));
175
+ }
176
+ store.removeItem(orderKey);
177
+ }
178
+ };
179
+ };
180
+ var createLocalStorageSnapshotStore = (key, options = {}) => createWebSnapshotStore({
181
+ ...options,
182
+ key,
183
+ storage: lazyGlobalStorage("localStorage")
184
+ });
185
+ var createLocalStorageRowStore = (key, options = {}) => createWebRowStore({
186
+ ...options,
187
+ key,
188
+ storage: lazyGlobalStorage("localStorage")
189
+ });
190
+ var createSessionStorageSnapshotStore = (key, options = {}) => createWebSnapshotStore({
191
+ ...options,
192
+ key,
193
+ storage: lazyGlobalStorage("sessionStorage")
194
+ });
195
+ var createSessionStorageRowStore = (key, options = {}) => createWebRowStore({
196
+ ...options,
197
+ key,
198
+ storage: lazyGlobalStorage("sessionStorage")
199
+ });
200
+
201
+ export { StorageCodecError, createLocalStorageRowStore, createLocalStorageSnapshotStore, createMemoryRowStore, createMemorySnapshotStore, createSessionStorageRowStore, createSessionStorageSnapshotStore, createWebRowStore, createWebSnapshotStore };
202
+ //# sourceMappingURL=chunk-SMQYICXZ.js.map
203
+ //# sourceMappingURL=chunk-SMQYICXZ.js.map
@@ -0,0 +1,75 @@
1
+ // src/events/index.ts
2
+ var createTypedEmit = (emit) => {
3
+ return (eventName, data) => {
4
+ emit(eventName, data);
5
+ };
6
+ };
7
+ var buildEventEmitter = () => {
8
+ const listenersByEvent = /* @__PURE__ */ new Map();
9
+ const on = (eventName, callback) => {
10
+ const listeners = listenersByEvent.get(eventName);
11
+ if (listeners) {
12
+ listeners.push(callback);
13
+ } else {
14
+ listenersByEvent.set(eventName, [
15
+ callback
16
+ ]);
17
+ }
18
+ return () => off(eventName, callback);
19
+ };
20
+ const once = (eventName, callback) => {
21
+ const wrapper = (data) => {
22
+ off(eventName, wrapper);
23
+ callback(data);
24
+ };
25
+ return on(eventName, wrapper);
26
+ };
27
+ const off = (eventName, callback) => {
28
+ const listeners = listenersByEvent.get(eventName);
29
+ if (!listeners) return;
30
+ if (!callback) {
31
+ listenersByEvent.delete(eventName);
32
+ return;
33
+ }
34
+ const next = listeners.filter((cb) => cb !== callback);
35
+ if (next.length === 0) {
36
+ listenersByEvent.delete(eventName);
37
+ } else {
38
+ listenersByEvent.set(eventName, next);
39
+ }
40
+ };
41
+ const emit = (eventName, data) => {
42
+ const listeners = listenersByEvent.get(eventName);
43
+ if (!listeners?.length) return;
44
+ for (const callback of [...listeners]) {
45
+ try {
46
+ callback(data);
47
+ } catch {
48
+ }
49
+ }
50
+ };
51
+ const clear = () => {
52
+ listenersByEvent.clear();
53
+ };
54
+ const listenerCount = (eventName) => {
55
+ return listenersByEvent.get(eventName)?.length ?? 0;
56
+ };
57
+ const eventNames = () => {
58
+ return [...listenersByEvent.keys()];
59
+ };
60
+ const api = {
61
+ on,
62
+ once,
63
+ off,
64
+ emit,
65
+ clear,
66
+ listenerCount,
67
+ eventNames,
68
+ expand: () => api
69
+ };
70
+ return api;
71
+ };
72
+
73
+ export { buildEventEmitter, createTypedEmit };
74
+ //# sourceMappingURL=chunk-TX6HHN5M.js.map
75
+ //# sourceMappingURL=chunk-TX6HHN5M.js.map
@@ -0,0 +1,63 @@
1
+ // src/worker/retry.ts
2
+ var RetryExhaustedError = class extends Error {
3
+ name = "RetryExhaustedError";
4
+ attempts;
5
+ cause;
6
+ constructor(attempts, cause) {
7
+ super(`Retry exhausted after ${attempts} attempt(s)`, { cause });
8
+ this.attempts = attempts;
9
+ this.cause = cause;
10
+ }
11
+ };
12
+ var sleep = (ms) => new Promise((resolve) => {
13
+ const schedule = globalThis.setTimeout;
14
+ schedule(() => resolve(), ms);
15
+ });
16
+ var resolveDelay = (delay, failedAttempt, error) => {
17
+ if (delay === void 0) return 0;
18
+ if (typeof delay === "function") return Math.max(0, delay(failedAttempt, error));
19
+ return Math.max(0, delay);
20
+ };
21
+ var withRetry = (worker, options) => {
22
+ const opts = typeof options === "number" ? { retries: options } : options;
23
+ const maxRetries = Math.max(0, opts.retries);
24
+ const shouldRetry = opts.shouldRetry ?? (() => true);
25
+ return async (item) => {
26
+ let lastError;
27
+ for (let attempt = 1; attempt <= maxRetries + 1; attempt += 1) {
28
+ try {
29
+ return await worker(item);
30
+ } catch (error) {
31
+ lastError = error;
32
+ const failedAttempt = attempt;
33
+ const retriesLeft = maxRetries + 1 - attempt;
34
+ if (retriesLeft <= 0 || !shouldRetry(error, failedAttempt)) {
35
+ throw new RetryExhaustedError(failedAttempt, error);
36
+ }
37
+ const wait = resolveDelay(opts.delay, failedAttempt, error);
38
+ if (wait > 0) {
39
+ await sleep(wait);
40
+ }
41
+ }
42
+ }
43
+ throw lastError;
44
+ };
45
+ };
46
+
47
+ // src/worker/pipeline.ts
48
+ function pipeline(...steps) {
49
+ if (steps.length === 0) {
50
+ throw new Error("pipeline requires at least one step");
51
+ }
52
+ return async (input) => {
53
+ let value = input;
54
+ for (const step of steps) {
55
+ value = await step(value);
56
+ }
57
+ return value;
58
+ };
59
+ }
60
+
61
+ export { RetryExhaustedError, pipeline, withRetry };
62
+ //# sourceMappingURL=chunk-W3FXQZCX.js.map
63
+ //# sourceMappingURL=chunk-W3FXQZCX.js.map
@@ -0,0 +1,178 @@
1
+ import { buildEventEmitter, createTypedEmit } from './chunk-TX6HHN5M.js';
2
+
3
+ // src/router/match.util.ts
4
+ var TOPIC_SEPARATOR = ".";
5
+ var SINGLE_WILDCARD = "*";
6
+ var MULTI_WILDCARD = "#";
7
+ var isEmptySegment = (segment) => segment.length === 0;
8
+ var isValidTopic = (topic) => {
9
+ if (topic.length === 0) return false;
10
+ if (topic.includes(SINGLE_WILDCARD) || topic.includes(MULTI_WILDCARD)) {
11
+ return false;
12
+ }
13
+ const segments = topic.split(TOPIC_SEPARATOR);
14
+ return segments.length > 0 && !segments.some(isEmptySegment);
15
+ };
16
+ var isValidPattern = (pattern) => {
17
+ if (pattern.length === 0) return false;
18
+ const segments = pattern.split(TOPIC_SEPARATOR);
19
+ if (segments.some(isEmptySegment)) return false;
20
+ for (let i = 0; i < segments.length; i += 1) {
21
+ const segment = segments[i];
22
+ if (segment === MULTI_WILDCARD) {
23
+ return i === segments.length - 1;
24
+ }
25
+ }
26
+ return true;
27
+ };
28
+ var matchTopic = (pattern, topic) => {
29
+ if (!isValidPattern(pattern) || !isValidTopic(topic)) return false;
30
+ const patternParts = pattern.split(TOPIC_SEPARATOR);
31
+ const topicParts = topic.split(TOPIC_SEPARATOR);
32
+ let pi = 0;
33
+ let ti = 0;
34
+ while (pi < patternParts.length && ti < topicParts.length) {
35
+ const token = patternParts[pi];
36
+ if (token === MULTI_WILDCARD) {
37
+ return true;
38
+ }
39
+ if (token === SINGLE_WILDCARD || token === topicParts[ti]) {
40
+ pi += 1;
41
+ ti += 1;
42
+ continue;
43
+ }
44
+ return false;
45
+ }
46
+ if (pi === patternParts.length - 1 && patternParts[pi] === MULTI_WILDCARD) {
47
+ return true;
48
+ }
49
+ return pi === patternParts.length && ti === topicParts.length;
50
+ };
51
+
52
+ // src/router/router.ts
53
+ var buildRouter = (options = {}) => {
54
+ const events = buildEventEmitter();
55
+ const emitRouter = createTypedEmit(
56
+ events.emit
57
+ );
58
+ const routes = [];
59
+ let unmatchedTarget = options.unmatchedTarget;
60
+ let unmatchedTotal = 0;
61
+ let lastUnmatchedRecord;
62
+ const bind = (pattern, target) => {
63
+ if (!isValidPattern(pattern)) {
64
+ const error = new Error(`Invalid route pattern: ${pattern}`);
65
+ emitRouter("router:error", { operation: "bind", error, pattern });
66
+ throw error;
67
+ }
68
+ const binding = {
69
+ pattern,
70
+ target
71
+ };
72
+ routes.push(binding);
73
+ emitRouter("router:bound", { pattern });
74
+ return () => {
75
+ unbind(pattern, target);
76
+ };
77
+ };
78
+ const unbind = (pattern, target) => {
79
+ let removed = 0;
80
+ for (let i = routes.length - 1; i >= 0; i -= 1) {
81
+ const route = routes[i];
82
+ if (route.pattern !== pattern) continue;
83
+ if (target !== void 0 && route.target !== target) continue;
84
+ routes.splice(i, 1);
85
+ removed += 1;
86
+ }
87
+ if (removed > 0) {
88
+ emitRouter("router:unbound", { pattern, removed });
89
+ }
90
+ };
91
+ const deliverUnmatched = (topic, data) => {
92
+ if (unmatchedTarget === void 0) {
93
+ return false;
94
+ }
95
+ try {
96
+ unmatchedTarget.enqueue({ topic, data });
97
+ return true;
98
+ } catch (error) {
99
+ emitRouter("router:error", {
100
+ operation: "unmatched",
101
+ error,
102
+ topic
103
+ });
104
+ return false;
105
+ }
106
+ };
107
+ const publish = (topic, data) => {
108
+ if (!isValidTopic(topic)) {
109
+ const error = new Error(`Invalid publish topic: ${topic}`);
110
+ emitRouter("router:error", { operation: "publish", error, topic });
111
+ throw error;
112
+ }
113
+ const message = { topic, data };
114
+ let matched = 0;
115
+ for (const route of [...routes]) {
116
+ if (!matchTopic(route.pattern, topic)) continue;
117
+ try {
118
+ route.target.enqueue(message);
119
+ matched += 1;
120
+ } catch (error) {
121
+ emitRouter("router:error", {
122
+ operation: "publish",
123
+ error,
124
+ topic,
125
+ pattern: route.pattern
126
+ });
127
+ }
128
+ }
129
+ if (matched === 0) {
130
+ unmatchedTotal += 1;
131
+ lastUnmatchedRecord = { topic, data };
132
+ const delivered = deliverUnmatched(topic, data);
133
+ emitRouter("router:unmatched", { topic, data, delivered });
134
+ } else {
135
+ emitRouter("router:published", { topic, data, matched });
136
+ }
137
+ return matched;
138
+ };
139
+ const bindings = () => routes.map((route) => ({
140
+ pattern: route.pattern,
141
+ target: route.target
142
+ }));
143
+ const clear = () => {
144
+ routes.length = 0;
145
+ };
146
+ const setUnmatchedTarget = (target) => {
147
+ unmatchedTarget = target;
148
+ };
149
+ const getUnmatchedTarget = () => unmatchedTarget;
150
+ const unmatchedCount = () => unmatchedTotal;
151
+ const lastUnmatched = () => lastUnmatchedRecord;
152
+ const clearUnmatched = () => {
153
+ unmatchedTotal = 0;
154
+ lastUnmatchedRecord = void 0;
155
+ };
156
+ const api = {
157
+ bind,
158
+ unbind,
159
+ publish,
160
+ bindings,
161
+ clear,
162
+ setUnmatchedTarget,
163
+ getUnmatchedTarget,
164
+ unmatchedCount,
165
+ lastUnmatched,
166
+ clearUnmatched,
167
+ on: events.on,
168
+ once: events.once,
169
+ off: events.off,
170
+ emit: events.emit,
171
+ expand: () => api
172
+ };
173
+ return api;
174
+ };
175
+
176
+ export { MULTI_WILDCARD, SINGLE_WILDCARD, TOPIC_SEPARATOR, buildRouter, isValidPattern, isValidTopic, matchTopic };
177
+ //# sourceMappingURL=chunk-WIKDEC7T.js.map
178
+ //# sourceMappingURL=chunk-WIKDEC7T.js.map
@@ -0,0 +1,7 @@
1
+ export { buildFromConfig, buildFromJson, defineConfig, parseSystemConfig, validateJsConfig, validateSystemConfig } from '../chunk-R2ONNABJ.js';
2
+ import '../chunk-SMQYICXZ.js';
3
+ import '../chunk-653YYS22.js';
4
+ import '../chunk-WIKDEC7T.js';
5
+ import '../chunk-TX6HHN5M.js';
6
+ //# sourceMappingURL=index.js.map
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,3 @@
1
+ export { buildEventEmitter, createTypedEmit } from '../chunk-TX6HHN5M.js';
2
+ //# sourceMappingURL=index.js.map
3
+ //# sourceMappingURL=index.js.map