@valkyriestudios/utils 12.32.0 → 12.33.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/README.md CHANGED
@@ -1010,6 +1010,97 @@ These functions are the following:
1010
1010
  - **Is.Equal**
1011
1011
  - **Is.Eq**
1012
1012
 
1013
+ ### modules/PubSub
1014
+ A lightweight fire-and-forget publish–subscribe utility that supports both synchronous and asynchronous subscribers, with built‑in support for storing the last published data for each event.
1015
+
1016
+ This feature lets new subscribers immediately receive the most recent value for specific events.
1017
+
1018
+ The default storage behavior is configurable via the constructor or can be overridden on a per‑publish basis.
1019
+
1020
+ ##### Usage
1021
+ Simple fire-and-foreget without storage:
1022
+ ```typescript
1023
+ import { PubSub } from '@valkyriestudios/utils/modules/PubSub';
1024
+
1025
+ const relay = new PubSub({ name: 'MyPubSub', store: true });
1026
+
1027
+ /* Subscribe to events. */
1028
+ relay.subscribe({
1029
+ onDataUpdate: (data) => ...,
1030
+ onModalOpen: (...) => ...,
1031
+ }, 'client1');
1032
+
1033
+ /* Subscribe to events. */
1034
+ const client2 = relay.subscribe({
1035
+ onUserUpdate: (...) => ...,
1036
+ }); /* If not passing a client id a client id will be generated and returned */
1037
+
1038
+ /* Publish events. Since storage is enabled, the data is saved */
1039
+ relay.publish('onDataUpdate', {foo: 'bar'});
1040
+ relay.publish('onUserUpdate', {language: 'nl'});
1041
+
1042
+ /* When not necessary anymore */
1043
+ relay.unsubscribe(client2);
1044
+ ```
1045
+
1046
+ In-Memory storage included:
1047
+ ```typescript
1048
+ import { PubSub } from '@valkyriestudios/utils/modules/PubSub';
1049
+
1050
+ /* Create a PubSub instance that stores published data by default */
1051
+ const relay = new PubSub({ name: 'MyPubSub', store: true });
1052
+
1053
+ /* Subscribe to events. */
1054
+ relay.subscribe({
1055
+ dataUpdate: (data) => console.log('Subscriber 1 received dataUpdate:', data),
1056
+ /* This event will automatically unsubscribe after the first time its called */
1057
+ onceEvent: {
1058
+ run: (data) => console.log('This runs only once:', data),
1059
+ once: true,
1060
+ },
1061
+ }, 'client1');
1062
+
1063
+ /* Publish events. Since storage is enabled, the data is saved */
1064
+ relay.publish('dataUpdate', { foo: 'bar' });
1065
+ relay.publish('onceEvent', 'only once');
1066
+
1067
+ /* Later, a new subscriber immediately gets the last published value */
1068
+ relay.subscribe({
1069
+ dataUpdate: (data) => console.log('Late subscriber received dataUpdate:', data)
1070
+ }, 'client2');
1071
+ ```
1072
+
1073
+ ##### API Overview
1074
+ - **new PubSub(options?: { logger?: LogFn; name?: string; store?: boolean })**
1075
+ Creates a new PubSub instance.
1076
+ -- **logger (optional)**: Custom logging function to capture errors.
1077
+ -- **name (optional)**: A non‑empty string to name the instance.
1078
+ -- **store (optional)**: A boolean to set the default behavior for storing published data (default is false).
1079
+ - **publish(event: string, data?: unknown, store?: boolean): void**
1080
+ Publishes data for a specific event.
1081
+ -- If storage is enabled (via the default or the store override), the published data is stored for that event.
1082
+ -- All subscribers for the event are invoked.
1083
+ -- Supports both synchronous and asynchronous subscriber functions. Asynchronous errors are caught and logged.
1084
+ - **subscribe(events: RawEvents, client_id?: string, override?: boolean): string | null**
1085
+ Subscribes a client to one or more events.
1086
+ -- Returns a client ID (either provided or generated) which can later be used to unsubscribe.
1087
+ -- If stored data exists for an event, the new subscriber is immediately invoked with that data.
1088
+ -- The override flag (defaults to true) determines whether existing subscriptions for the same client are replaced.
1089
+ - **unsubscribe(client_id: string, event?: string | string[]): void**
1090
+ Unsubscribes a client from the specified event(s) or all events if none are provided.
1091
+ - **clientIds(event?: string | string[]): Set<string>**
1092
+ Retrieves a set of client IDs subscribed to one or more events.
1093
+ - **clear(event?: string | string[]): void**
1094
+ Clears subscriptions for specific event(s) or all events if no event is specified.
1095
+
1096
+ ##### Notes:
1097
+ - **Data Storage:**
1098
+ When storage is enabled, the last published value is tied to an event’s subscription entry. If there are no subscribers left, the stored value is automatically cleaned up.
1099
+ - **Error Handling:**
1100
+ Both synchronous and asynchronous errors are logged via the custom logger (if provided). The utility checks for thenables by verifying that the returned object has both then and catch methods.
1101
+ - **Flexibility:**
1102
+ The storage behavior can be controlled globally through the constructor or overridden for individual publish calls.
1103
+
1013
1104
  ### number/is(val:unknown)
1014
1105
  Check if a variable is a number
1015
1106
  ```typescript
package/deep/get.js CHANGED
@@ -32,21 +32,17 @@ function deepGet(obj, path, get_parent = false) {
32
32
  const extracted = [];
33
33
  for (let y = 0; y < node.length; y++) {
34
34
  const el = deepGet(node[y], key);
35
- if (el !== undefined)
36
- extracted.push(el);
35
+ if (el !== undefined) {
36
+ extracted.push(...Array.isArray(el) ? el : [el]);
37
+ }
37
38
  }
38
- node = extracted.length ? extracted.flat() : undefined;
39
+ node = extracted;
39
40
  nodes.push(node);
40
41
  }
41
42
  }
42
43
  else if (typeof node === 'object' && node !== null) {
43
44
  node = node[key];
44
45
  nodes.push(node);
45
- if (node === undefined)
46
- return undefined;
47
- }
48
- else {
49
- return undefined;
50
46
  }
51
47
  key = '';
52
48
  break;
@@ -67,9 +63,9 @@ function deepGet(obj, path, get_parent = false) {
67
63
  else {
68
64
  const extracted = [];
69
65
  for (let i = 0; i < node.length; i++) {
70
- const el = node[i];
71
- if (el?.[key] !== undefined)
72
- extracted.push(el?.[key]);
66
+ const val = node[i]?.[key];
67
+ if (val !== undefined)
68
+ extracted.push(...Array.isArray(val) ? val : [val]);
73
69
  }
74
70
  node = extracted.length ? extracted : undefined;
75
71
  nodes.push(node);
package/index.d.ts CHANGED
@@ -644,3 +644,40 @@ declare module "hash/index" {
644
644
  import { guid } from "hash/guid";
645
645
  export { fnv1A, guid };
646
646
  }
647
+ declare module "modules/PubSub" {
648
+ type SyncFn = (data: unknown) => void;
649
+ type AsyncFn = (data: unknown) => Promise<void>;
650
+ type Fn = SyncFn | AsyncFn;
651
+ export type RawEvents = {
652
+ [key: string]: Fn | {
653
+ run: Fn;
654
+ once?: boolean;
655
+ };
656
+ };
657
+ export type LogObject = {
658
+ name: string;
659
+ event: string;
660
+ client_id: string;
661
+ msg: string;
662
+ on: Date;
663
+ data: unknown;
664
+ err: Error;
665
+ };
666
+ export type LogFn = (log: LogObject) => void;
667
+ export type PubSubOptions = {
668
+ logger?: LogFn;
669
+ name?: string;
670
+ store?: boolean;
671
+ };
672
+ class PubSub {
673
+ #private;
674
+ constructor(options?: PubSubOptions);
675
+ get name(): string;
676
+ publish(event: string, data?: unknown, store?: boolean): void;
677
+ subscribe(events: RawEvents, client_id?: string, override?: boolean): string | null;
678
+ unsubscribe(client_id: string, event?: string | string[]): void;
679
+ clientIds(event?: string | string[]): Set<string>;
680
+ clear(event?: string | string[]): void;
681
+ }
682
+ export { PubSub, PubSub as default };
683
+ }
@@ -0,0 +1,90 @@
1
+ type SyncFn = (data: unknown) => void;
2
+ type AsyncFn = (data: unknown) => Promise<void>;
3
+ type Fn = SyncFn | AsyncFn;
4
+ export type RawEvents = {
5
+ [key: string]: Fn | {
6
+ run: Fn;
7
+ once?: boolean;
8
+ };
9
+ };
10
+ /**
11
+ * Types of Log Object shapes
12
+ */
13
+ export type LogObject = {
14
+ name: string;
15
+ event: string;
16
+ client_id: string;
17
+ msg: string;
18
+ on: Date;
19
+ data: unknown;
20
+ err: Error;
21
+ };
22
+ /**
23
+ * Logger Function type
24
+ */
25
+ export type LogFn = (log: LogObject) => void;
26
+ export type PubSubOptions = {
27
+ /**
28
+ * Custom logger function, will receive an instance of LogObject when an error is thrown
29
+ */
30
+ logger?: LogFn;
31
+ /**
32
+ * Name of the pubsub (used in logs as well)
33
+ */
34
+ name?: string;
35
+ /**
36
+ * Whether or not the default is for the last provided value to be stored. (if not passed = false)
37
+ */
38
+ store?: boolean;
39
+ };
40
+ declare class PubSub {
41
+ #private;
42
+ constructor(options?: PubSubOptions);
43
+ /**
44
+ * Getter returning the name of the pubsub.
45
+ */
46
+ get name(): string;
47
+ /**
48
+ * Publish data for a specific event.
49
+ *
50
+ * An optional third parameter allows the producer to override the default storage behavior.
51
+ * If storage is enabled, the published data is saved so that new subscribers receive it immediately.
52
+ *
53
+ * @param {string} event - Name of the event.
54
+ * @param {unknown} data - (Optional) Data to publish.
55
+ * @param {boolean?} store - (Optional) Override for storing this event’s data.
56
+ */
57
+ publish(event: string, data?: unknown, store?: boolean): void;
58
+ /**
59
+ * Subscribes to one or more events as a client.
60
+ *
61
+ * After adding the subscription, if there is stored data for an event,
62
+ * the new subscriber's callback is immediately invoked with that data.
63
+ *
64
+ * @param {RawEvents} events - Raw Events object to subscribe to.
65
+ * @param {string?} client_id - Optional client id. If not provided one will be generated.
66
+ * @param {boolean?} override - Defaults to true. If false, existing subscriptions for the same event are preserved.
67
+ * @returns Client id if successful, or null for invalid input.
68
+ */
69
+ subscribe(events: RawEvents, client_id?: string, override?: boolean): string | null;
70
+ /**
71
+ * Unsubscribes a client from specified event(s) or from all events if none are specified.
72
+ *
73
+ * @param {string} client_id - Client ID to unsubscribe.
74
+ * @param {string|string[]?} event - (Optional) Unsubscribe only from these event(s).
75
+ */
76
+ unsubscribe(client_id: string, event?: string | string[]): void;
77
+ /**
78
+ * Retrieve the client IDs subscribed to a single, multiple, or all events.
79
+ *
80
+ * @param {string|string[]?} event - Optional event or events array.
81
+ */
82
+ clientIds(event?: string | string[]): Set<string>;
83
+ /**
84
+ * Clear subscriptions for specific event(s) or all events.
85
+ *
86
+ * @param {string|string[]?} event - Events to clear.
87
+ */
88
+ clear(event?: string | string[]): void;
89
+ }
90
+ export { PubSub, PubSub as default };
@@ -0,0 +1,191 @@
1
+ "use strict";
2
+ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
3
+ if (kind === "m") throw new TypeError("Private method is not writable");
4
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
5
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
6
+ return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
7
+ };
8
+ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
9
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
10
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
11
+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
12
+ };
13
+ var _PubSub_instances, _PubSub_subscriptions, _PubSub_name, _PubSub_log, _PubSub_store_by_default, _PubSub_publishToSubscriber;
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.default = exports.PubSub = void 0;
16
+ const is_1 = require("../array/is");
17
+ const is_2 = require("../function/is");
18
+ const noop_1 = require("../function/noop");
19
+ const guid_1 = require("../hash/guid");
20
+ const is_3 = require("../object/is");
21
+ const isNotEmpty_1 = require("../object/isNotEmpty");
22
+ const isNotEmpty_2 = require("../string/isNotEmpty");
23
+ class PubSub {
24
+ constructor(options = {}) {
25
+ _PubSub_instances.add(this);
26
+ _PubSub_subscriptions.set(this, new Map());
27
+ _PubSub_name.set(this, 'PubSub');
28
+ _PubSub_log.set(this, noop_1.noop);
29
+ _PubSub_store_by_default.set(this, false);
30
+ if (!(0, is_3.isObject)(options))
31
+ throw new Error('PubSub@ctor: options should be an object');
32
+ if ('logger' in options) {
33
+ if (!(0, is_2.isFunction)(options.logger))
34
+ throw new Error('PubSub@ctor: logger should be a function');
35
+ __classPrivateFieldSet(this, _PubSub_log, options.logger, "f");
36
+ }
37
+ if ('name' in options) {
38
+ if (!(0, isNotEmpty_2.isNotEmptyString)(options.name))
39
+ throw new Error('PubSub@ctor: name should be a non-empty string');
40
+ __classPrivateFieldSet(this, _PubSub_name, options.name.trim(), "f");
41
+ }
42
+ if ('store' in options) {
43
+ if (options.store !== true && options.store !== false)
44
+ throw new Error('PubSub@ctor: store should be a boolean');
45
+ __classPrivateFieldSet(this, _PubSub_store_by_default, !!options.store, "f");
46
+ }
47
+ }
48
+ get name() {
49
+ return __classPrivateFieldGet(this, _PubSub_name, "f");
50
+ }
51
+ publish(event, data, store) {
52
+ if (!(0, isNotEmpty_2.isNotEmptyString)(event))
53
+ return;
54
+ let entry = __classPrivateFieldGet(this, _PubSub_subscriptions, "f").get(event);
55
+ const shouldStore = store !== undefined ? store : __classPrivateFieldGet(this, _PubSub_store_by_default, "f");
56
+ if (!entry) {
57
+ if (!shouldStore)
58
+ return;
59
+ entry = { value: data, subscribers: new Map() };
60
+ __classPrivateFieldGet(this, _PubSub_subscriptions, "f").set(event, entry);
61
+ }
62
+ else if (shouldStore) {
63
+ entry.value = data;
64
+ }
65
+ const entriesArray = [...entry.subscribers.entries()];
66
+ for (const [client_id, client_handler] of entriesArray) {
67
+ __classPrivateFieldGet(this, _PubSub_instances, "m", _PubSub_publishToSubscriber).call(this, event, entry, client_id, client_handler, data);
68
+ }
69
+ if (!shouldStore && !entry.subscribers.size)
70
+ __classPrivateFieldGet(this, _PubSub_subscriptions, "f").delete(event);
71
+ }
72
+ subscribe(events, client_id, override = true) {
73
+ if (!(0, isNotEmpty_1.isNotEmptyObject)(events))
74
+ return null;
75
+ const uid = (0, isNotEmpty_2.isNotEmptyString)(client_id) ? client_id : (0, guid_1.guid)();
76
+ for (const event of Object.keys(events)) {
77
+ const raw_payload = events[event];
78
+ let normalized_payload = null;
79
+ if ((0, is_2.isFunction)(raw_payload)) {
80
+ normalized_payload = { run: raw_payload, once: false };
81
+ }
82
+ else if ((0, is_2.isFunction)(raw_payload?.run)) {
83
+ normalized_payload = {
84
+ run: raw_payload.run,
85
+ once: raw_payload.once === true,
86
+ };
87
+ }
88
+ if (!normalized_payload)
89
+ continue;
90
+ let entry = __classPrivateFieldGet(this, _PubSub_subscriptions, "f").get(event);
91
+ if (!entry) {
92
+ entry = { subscribers: new Map(), value: undefined };
93
+ __classPrivateFieldGet(this, _PubSub_subscriptions, "f").set(event, entry);
94
+ }
95
+ if (override === false && entry.subscribers.has(uid))
96
+ continue;
97
+ entry.subscribers.set(uid, normalized_payload);
98
+ if (entry.value !== undefined) {
99
+ __classPrivateFieldGet(this, _PubSub_instances, "m", _PubSub_publishToSubscriber).call(this, event, entry, uid, normalized_payload, entry.value);
100
+ }
101
+ }
102
+ return uid;
103
+ }
104
+ unsubscribe(client_id, event) {
105
+ if (!(0, isNotEmpty_2.isNotEmptyString)(client_id))
106
+ return;
107
+ const events = (0, isNotEmpty_2.isNotEmptyString)(event)
108
+ ? [event]
109
+ : (0, is_1.isArray)(event)
110
+ ? event
111
+ : [...__classPrivateFieldGet(this, _PubSub_subscriptions, "f").keys()];
112
+ for (let i = 0; i < events.length; i++) {
113
+ const ev = events[i];
114
+ if (!(0, isNotEmpty_2.isNotEmptyString)(ev))
115
+ continue;
116
+ const entry = __classPrivateFieldGet(this, _PubSub_subscriptions, "f").get(ev);
117
+ if (entry) {
118
+ entry.subscribers.delete(client_id);
119
+ if (!entry.subscribers.size) {
120
+ __classPrivateFieldGet(this, _PubSub_subscriptions, "f").delete(ev);
121
+ }
122
+ }
123
+ }
124
+ }
125
+ clientIds(event) {
126
+ const events = (0, isNotEmpty_2.isNotEmptyString)(event)
127
+ ? [event]
128
+ : (0, is_1.isArray)(event)
129
+ ? event
130
+ : [...__classPrivateFieldGet(this, _PubSub_subscriptions, "f").keys()];
131
+ const result = new Set();
132
+ for (let i = 0; i < events.length; i++) {
133
+ const entry = __classPrivateFieldGet(this, _PubSub_subscriptions, "f").get(events[i]);
134
+ if (entry) {
135
+ for (const key of entry.subscribers.keys()) {
136
+ result.add(key);
137
+ }
138
+ }
139
+ }
140
+ return result;
141
+ }
142
+ clear(event) {
143
+ if ((0, isNotEmpty_2.isNotEmptyString)(event)) {
144
+ __classPrivateFieldGet(this, _PubSub_subscriptions, "f").delete(event);
145
+ }
146
+ else if ((0, is_1.isArray)(event)) {
147
+ for (let i = 0; i < event.length; i++) {
148
+ const ev = event[i];
149
+ if ((0, isNotEmpty_2.isNotEmptyString)(ev)) {
150
+ __classPrivateFieldGet(this, _PubSub_subscriptions, "f").delete(ev);
151
+ }
152
+ }
153
+ }
154
+ else {
155
+ __classPrivateFieldSet(this, _PubSub_subscriptions, new Map(), "f");
156
+ }
157
+ }
158
+ }
159
+ exports.PubSub = PubSub;
160
+ exports.default = PubSub;
161
+ _PubSub_subscriptions = new WeakMap(), _PubSub_name = new WeakMap(), _PubSub_log = new WeakMap(), _PubSub_store_by_default = new WeakMap(), _PubSub_instances = new WeakSet(), _PubSub_publishToSubscriber = function _PubSub_publishToSubscriber(event, map, client_id, sub, data) {
162
+ try {
163
+ const out = sub.run(data);
164
+ if (sub.once)
165
+ map.subscribers.delete(client_id);
166
+ if (out && (0, is_2.isFunction)(out?.catch) && (0, is_2.isFunction)(out?.then)) {
167
+ Promise.resolve(out).catch(err => {
168
+ __classPrivateFieldGet(this, _PubSub_log, "f").call(this, {
169
+ name: __classPrivateFieldGet(this, _PubSub_name, "f"),
170
+ event,
171
+ client_id,
172
+ msg: '[async] ' + __classPrivateFieldGet(this, _PubSub_name, "f") + '@publish: ' + (err?.message || 'Unknown Error'),
173
+ on: new Date(),
174
+ data,
175
+ err: err,
176
+ });
177
+ });
178
+ }
179
+ }
180
+ catch (err) {
181
+ __classPrivateFieldGet(this, _PubSub_log, "f").call(this, {
182
+ name: __classPrivateFieldGet(this, _PubSub_name, "f"),
183
+ event,
184
+ client_id,
185
+ msg: '[sync] ' + __classPrivateFieldGet(this, _PubSub_name, "f") + '@publish: ' + (err?.message || 'Unknown Error'),
186
+ on: new Date(),
187
+ data,
188
+ err: err,
189
+ });
190
+ }
191
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@valkyriestudios/utils",
3
- "version": "12.32.0",
3
+ "version": "12.33.0",
4
4
  "description": "A collection of single-function utilities for common tasks",
5
5
  "author": {
6
6
  "name": "Peter Vermeulen",