zodbus 1.1.3 → 2.0.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.cjs CHANGED
@@ -1,2 +1,208 @@
1
- "use strict";var h=Object.defineProperty;var $=Object.getOwnPropertyDescriptor;var k=Object.getOwnPropertyNames;var I=Object.prototype.hasOwnProperty;var O=(i,s)=>{for(var u in s)h(i,u,{get:s[u],enumerable:!0})},E=(i,s,u,a)=>{if(s&&typeof s=="object"||typeof s=="function")for(let t of k(s))!I.call(i,t)&&t!==u&&h(i,t,{get:()=>s[t],enumerable:!(a=$(s,t))||a.enumerable});return i};var R=i=>E(h({},"__esModule",{value:!0}),i);var N={};O(N,{create:()=>B});module.exports=R(N);var v=require("zod");var y="[zodbus] ";var l=class extends Error{constructor(s){super(`${y}${s}`),this.name="ValidationError"}},K=class extends Error{constructor(s){super(`${y}${s}`),this.name="RuntimeError"}};var L=(i,s="")=>{let u=[];for(let[a,t]of Object.entries(i)){let p=s?`${s}.${a}`:a;typeof t.parse=="function"?u.push(p):u.push(...L(t,p))}return u},m=(i,s="")=>{let u=[];for(let[a,t]of Object.entries(i)){let p=s?`${s}.${a}`:a,d=s?`${s}.*`:"*";typeof t.parse=="function"?u.push(p,d):u.push(...m(t,p),...m(t,d))}return u},w=i=>{let s={},u=L(i),a=m(i);for(let t of a){let p=t.split(".");s[t]=u.filter(d=>{let b=d.split(".");if(b.length!==p.length)return!1;for(let[f,P]of b.entries())if(p[f]!=="*"&&P!==p[f])return!1;return!0})}return s};function B({schema:i,validate:s=!0}){let u=w(i),a=Array.from(new Set(Object.values(u).flat())),t=new Map,p=e=>{if(e==="*")return Array.from(t.values());let n=[],o=u[e];if(!o)throw new l(`Invalid event: "${e}"`);for(let r of o){let c=t.get(r);c&&n.push(c)}return n},d=(e,n)=>{let o=e.split("."),r=i;for(let c of o)if(r=r[c],!r)throw new l(`Invalid event: "${e}". Could not resolve "${c}" fragment.`);if(r instanceof v.ZodType)r.parse(n);else throw new l(`Reached invalid payload schema for: "${e}"`)},b=(e,n)=>{let o=p(e);for(let r of o)n===void 0?r.clear():r.delete(n)},f=(e,n)=>{var r;if(typeof n!="function")throw new l(`Invalid listener for event: "${e}". Expected function, got ${typeof n}`);let o=e==="*"?a:u[e];if(!o)throw new l(`Invalid event: "${e}"`);for(let c of o){let T=(r=t.get(c))!=null?r:new Set;T.add(n),t.set(c,T)}return{event:e,listener:n,unsubscribe:()=>b(e,n)}};return{publish:(e,n)=>{if(s&&d(e,n),!!t.has(e))for(let o of t.get(e))o(n,e)},subscribe:f,subscribeOnce:(e,n)=>{let o=(r,c)=>{n(r,c),b(e,o)};return f(e,o)},unsubscribe:b,getEventNames:()=>a,getListeners:e=>{let n=[],o=e?p(e):Array.from(t.values());for(let r of o)for(let c of r)n.push(c);return n},waitFor:(e,n={})=>{let{timeout:o=5e3,filter:r}=n;return new Promise((c,T)=>{let g,S=x=>{r&&!r(x)||(b(e,S),c(x),clearTimeout(g))};f(e,S),o&&(g=setTimeout(()=>{b(e,S),T(new K(`Timeout waiting for event: "${e}"`))},o))})}}}
2
- //# sourceMappingURL=index.cjs.map
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ let zod = require("zod");
3
+ //#region src/constants.ts
4
+ const errorPrefix = "[zodbus] ";
5
+ //#endregion
6
+ //#region src/errors.ts
7
+ var ValidationError = class extends Error {
8
+ constructor(message) {
9
+ super(`${errorPrefix}${message}`);
10
+ this.name = "ValidationError";
11
+ }
12
+ };
13
+ var RuntimeError = class extends Error {
14
+ constructor(message) {
15
+ super(`${errorPrefix}${message}`);
16
+ this.name = "RuntimeError";
17
+ }
18
+ };
19
+ //#endregion
20
+ //#region src/utils/schema.ts
21
+ const subPubPathMapCache = /* @__PURE__ */ new WeakMap();
22
+ const getPublishPaths = (schema, prefix = "") => {
23
+ const paths = [];
24
+ for (const [k, v] of Object.entries(schema)) {
25
+ const newPrefix = prefix ? `${prefix}.${k}` : k;
26
+ if (typeof v.parse === "function") paths.push(newPrefix);
27
+ else paths.push(...getPublishPaths(v, newPrefix));
28
+ }
29
+ return paths;
30
+ };
31
+ const getSubscribePaths = (schema, prefix = "") => {
32
+ const paths = [];
33
+ for (const [k, v] of Object.entries(schema)) {
34
+ const newPrefix = prefix ? `${prefix}.${k}` : k;
35
+ const newWildcardPrefix = prefix ? `${prefix}.*` : "*";
36
+ if (typeof v.parse === "function") paths.push(newPrefix, newWildcardPrefix);
37
+ else paths.push(...getSubscribePaths(v, newPrefix), ...getSubscribePaths(v, newWildcardPrefix));
38
+ }
39
+ return paths;
40
+ };
41
+ const getSubPubPathMap = (schema) => {
42
+ const cached = subPubPathMapCache.get(schema);
43
+ if (cached) return cached;
44
+ const map = {};
45
+ const publishPaths = getPublishPaths(schema);
46
+ const subscribePaths = getSubscribePaths(schema);
47
+ for (const subscribePath of subscribePaths) {
48
+ const subscribeParts = subscribePath.split(".");
49
+ map[subscribePath] = publishPaths.filter((publishPath) => {
50
+ const publishParts = publishPath.split(".");
51
+ if (publishParts.length !== subscribeParts.length) return false;
52
+ for (const [i, publishPart] of publishParts.entries()) if (subscribeParts[i] !== "*" && publishPart !== subscribeParts[i]) return false;
53
+ return true;
54
+ });
55
+ }
56
+ subPubPathMapCache.set(schema, map);
57
+ return map;
58
+ };
59
+ //#endregion
60
+ //#region src/bus.ts
61
+ function create({ schema, validate = true }) {
62
+ const subPubPathMap = getSubPubPathMap(schema);
63
+ const eventNames = Array.from(new Set(Object.values(subPubPathMap).flat()));
64
+ const listeners = /* @__PURE__ */ new Map();
65
+ const validationSchemaCache = /* @__PURE__ */ new Map();
66
+ const getListenerSetsForSubscriptionKey = (event) => {
67
+ if (event === "*") return Array.from(listeners.values());
68
+ const listenerSets = [];
69
+ const publishPaths = subPubPathMap[event];
70
+ if (!publishPaths) throw new ValidationError(`Invalid event: "${event}"`);
71
+ for (const publishPath of publishPaths) {
72
+ const listenerSet = listeners.get(publishPath);
73
+ if (listenerSet) listenerSets.push(listenerSet);
74
+ }
75
+ return listenerSets;
76
+ };
77
+ const validatePayloadOrPanic = (event, data) => {
78
+ const zodSchema = validationSchemaCache.get(event);
79
+ if (zodSchema) {
80
+ zodSchema.parse(data);
81
+ return;
82
+ }
83
+ const pathFragments = event.split(".");
84
+ let currentSchema = schema;
85
+ for (const fragment of pathFragments) {
86
+ const nextSchema = currentSchema[fragment];
87
+ if (!nextSchema) throw new ValidationError(`Invalid event: "${event}". Could not resolve "${fragment}" fragment.`);
88
+ currentSchema = nextSchema;
89
+ }
90
+ if (currentSchema instanceof zod.ZodType) {
91
+ validationSchemaCache.set(event, currentSchema);
92
+ currentSchema.parse(data);
93
+ } else throw new ValidationError(`Reached invalid payload schema for: "${event}"`);
94
+ };
95
+ /** Unsubscribe from an event. If no listener is provided, all listeners for the event will be removed.
96
+ * @param event The event to unsubscribe from. Wildcards `foo.*.bar.*` are supported.
97
+ * Using `*` will match any event on any level.
98
+ * More specific wildcard patterns like `*.*` will only match events on that level.
99
+ * @param listener The listener to remove. If no listener is provided, all listeners for the event will be removed.
100
+ *
101
+ * Examples:
102
+ * - `unsubscribe("foo.bar", listener)` will unsubscribe the listener from `foo.bar`
103
+ * - `unsubscribe("foo.*", listener)` will unsubscribe the listener from all events under `foo`
104
+ * - `unsubscribe("*", listener)` will unsubscribe the listener from all events
105
+ * - `unsubscribe("*")` will unsubscribe all listeners from all events
106
+ * - `unsubscribe("*.*")` will unsubscribe all listeners from all events on the second level
107
+ */
108
+ const unsubscribe = (event, listener) => {
109
+ const eventListeners = getListenerSetsForSubscriptionKey(event);
110
+ for (const listenerSet of eventListeners) if (listener === void 0) listenerSet.clear();
111
+ else listenerSet.delete(listener);
112
+ };
113
+ /** Subscribe to an event. Returns an object with the event name, listener, and an unsubscribe function.
114
+ * @param event The event to subscribe to. Wildcards `foo.*.bar.*` are supported.
115
+ * Using `*` will match any event on any level.
116
+ * More specific wildcard patterns like `*.*` will only match events on that level.
117
+ * @param listener The listener to call when the event is published.
118
+ * @returns A subscription object with the event name, listener, and an unsubscribe function.
119
+ */
120
+ const subscribe = (event, listener) => {
121
+ if (typeof listener !== "function") throw new ValidationError(`Invalid listener for event: "${event}". Expected function, got ${typeof listener}`);
122
+ const publishPaths = event === "*" ? eventNames : subPubPathMap[event];
123
+ if (!publishPaths) throw new ValidationError(`Invalid event: "${event}"`);
124
+ for (const publishPath of publishPaths) {
125
+ let listenerSet = listeners.get(publishPath);
126
+ if (!listenerSet) {
127
+ listenerSet = /* @__PURE__ */ new Set();
128
+ listeners.set(publishPath, listenerSet);
129
+ }
130
+ listenerSet.add(listener);
131
+ }
132
+ return {
133
+ event,
134
+ listener,
135
+ unsubscribe: () => unsubscribe(event, listener)
136
+ };
137
+ };
138
+ /** Subscribe to an event once. The listener will be unsubscribed after the first time it is called.
139
+ * You cannot cancel this subscription using unsubscribe() with the original listener.
140
+ * Use the returned unsubscribe function / listener instead.
141
+ * @param event The event to subscribe to. Wildcards `foo.*.bar.*` are supported.
142
+ * Using `*` will match any event on any level.
143
+ * More specific wildcard patterns like `*.*` will only match events on that level.
144
+ * @param listener The listener to call when the event is published.
145
+ * @returns A subscription object with the event name, listener, and an unsubscribe function.
146
+ * */
147
+ const subscribeOnce = (event, listener) => {
148
+ const wrappedListener = (data, eventName) => {
149
+ listener(data, eventName);
150
+ unsubscribe(event, wrappedListener);
151
+ };
152
+ return subscribe(event, wrappedListener);
153
+ };
154
+ /** Publish an event. All listeners for the event will be called with the provided data.
155
+ * @param event The event to publish.
156
+ * @param data The data to pass to the listeners.*/
157
+ const publish = (event, data) => {
158
+ if (validate) validatePayloadOrPanic(event, data);
159
+ if (!listeners.has(event)) return;
160
+ for (const listener of listeners.get(event)) listener(data, event);
161
+ };
162
+ /** Returns a list of all the event names.
163
+ * @returns An array of event names. */
164
+ const getEventNames = () => eventNames;
165
+ /** Get the list of listeners for an event or all listeners if no event is provided.
166
+ * @param event The event to get listeners for.
167
+ * @returns An array of listeners for the event. */
168
+ const getListeners = (event) => {
169
+ const targetListeners = [];
170
+ const targetListenerSets = event ? getListenerSetsForSubscriptionKey(event) : Array.from(listeners.values());
171
+ for (const listenerSet of targetListenerSets) for (const listener of listenerSet) targetListeners.push(listener);
172
+ return targetListeners;
173
+ };
174
+ /** Waits for an event to be published.
175
+ * Returns a promise that resolves when the event is published.
176
+ * @param event The event to wait for.
177
+ * @param options.timeout The timeout in milliseconds. Defaults to 5000.
178
+ * @param options.filter A function that returns true if the event should be accepted.
179
+ * @returns A promise that resolves when the event is published with the data passed to the listener. */
180
+ const waitFor = (event, options = {}) => {
181
+ const { timeout = 5e3, filter } = options;
182
+ return new Promise((resolve, reject) => {
183
+ let timeoutId;
184
+ const listener = (data) => {
185
+ if (filter && !filter(data)) return;
186
+ unsubscribe(event, listener);
187
+ resolve(data);
188
+ clearTimeout(timeoutId);
189
+ };
190
+ subscribe(event, listener);
191
+ if (timeout) timeoutId = setTimeout(() => {
192
+ unsubscribe(event, listener);
193
+ reject(new RuntimeError(`Timeout waiting for event: "${event}"`));
194
+ }, timeout);
195
+ });
196
+ };
197
+ return {
198
+ publish,
199
+ subscribe,
200
+ subscribeOnce,
201
+ unsubscribe,
202
+ getEventNames,
203
+ getListeners,
204
+ waitFor
205
+ };
206
+ }
207
+ //#endregion
208
+ exports.create = create;
@@ -0,0 +1,63 @@
1
+ import { ZodType } from "zod";
2
+
3
+ //#region src/types.d.ts
4
+ type IsZodType<T> = T extends ZodType ? true : false;
5
+ type IsSchema<T> = T extends Schema ? true : false;
6
+ type Listener<K extends string = string, V = unknown> = (data: V, event: K) => void;
7
+ interface Schema {
8
+ [key: string]: Schema | ZodType;
9
+ }
10
+ type HasWildcard<T extends string> = T extends `*.${string}` | `*` | `${string}.*.${string}` | `${string}.*` ? true : false;
11
+ type InferSubscriptionListener<T, K extends string, P extends string = ""> = K extends `${infer Left}.${infer Right}` ? HasWildcard<Left & string> extends true ? Listener : Left extends keyof T ? T[Left] extends Schema ? InferSubscriptionListener<T[Left], Right, `${P}${Left}.`> : never : never : K extends keyof T ? T[K] extends ZodType<infer O, unknown> ? Listener<`${P}${K}`, O> : never : HasWildcard<K & string> extends true ? Listener : never;
12
+ type InferSubscriptionListenerPayload<T, K extends string, P extends string = ""> = K extends `${infer Left}.${infer Right}` ? HasWildcard<Left & string> extends true ? unknown : Left extends keyof T ? T[Left] extends Schema ? InferSubscriptionListenerPayload<T[Left], Right, `${P}${Left}.`> : never : never : K extends keyof T ? T[K] extends ZodType<infer O, unknown> ? O : never : HasWildcard<K & string> extends true ? unknown : never;
13
+ type ZodTypePath<T, K extends keyof T = keyof T> = K extends string ? IsZodType<T[K]> extends true ? K : never : never;
14
+ type NamespacePath<T, K extends keyof T = keyof T> = K extends string ? IsSchema<T[K]> extends true ? `${K}.${SchemaPath<T[K], keyof T[K]>}` : never : never;
15
+ type SchemaPath<T, K extends keyof T = keyof T> = NamespacePath<T, K> | ZodTypePath<T, K>;
16
+ type WildcardPath<T extends string> = T extends `${infer L}.${infer R}` ? `*.${WildcardPath<R>}` | `${L}.*` | `${L}.${WildcardPath<R>}` : T | "*";
17
+ type ExcludeDirectlyNestedKeys<T extends string> = T extends `${infer L}.${infer R}` ? ExcludeDirectlyNestedKeys<R> | L : never;
18
+ type Subscription<T extends Schema> = { [K in Exclude<WildcardPath<SchemaPath<T>>, ExcludeDirectlyNestedKeys<SchemaPath<T>>> | "*"]: {
19
+ listener: InferSubscriptionListener<T, K>;
20
+ payload: InferSubscriptionListenerPayload<T, K>;
21
+ } };
22
+ type Subscriptions<T extends Schema> = { [K in keyof Subscription<T>]: Subscription<T>[K] };
23
+ type SubscriptionListeners<T extends Schema> = { [K in keyof Subscriptions<T>]: Subscriptions<T>[K]["listener"] };
24
+ type SubscriptionListenerPayloads<T extends Schema> = { [K in keyof Subscriptions<T>]: Subscriptions<T>[K]["payload"] };
25
+ type SubscriptionKey<T extends Schema> = Extract<keyof SubscriptionListeners<T>, string>;
26
+ type PublishKey<T extends Schema> = Extract<SubscriptionKey<T>, SchemaPath<T>>;
27
+ //#endregion
28
+ //#region src/bus.d.ts
29
+ type BusOptions<T extends Schema> = {
30
+ schema: T;
31
+ validate?: boolean;
32
+ };
33
+ interface Bus<T extends Schema> {
34
+ publish: <K extends PublishKey<T>>(event: K, data: SubscriptionListenerPayloads<T>[K]) => void;
35
+ subscribe: <K extends SubscriptionKey<T>>(event: K, listener: SubscriptionListeners<T>[K]) => {
36
+ event: K;
37
+ listener: SubscriptionListeners<T>[K];
38
+ unsubscribe: () => void;
39
+ };
40
+ subscribeOnce: <K extends SubscriptionKey<T>>(event: K, listener: SubscriptionListeners<T>[K]) => {
41
+ event: K;
42
+ listener: SubscriptionListeners<T>[K];
43
+ unsubscribe: () => void;
44
+ };
45
+ unsubscribe: <K extends SubscriptionKey<T>>(event: K, listener?: SubscriptionListeners<T>[K]) => void;
46
+ getEventNames: () => PublishKey<T>[];
47
+ getListeners: <K extends SubscriptionKey<T>>(event?: K) => ((data: unknown, eventName: string) => void)[];
48
+ waitFor: <K extends SubscriptionKey<T>>(event: K, options?: {
49
+ timeout?: number;
50
+ filter?: (data: SubscriptionListenerPayloads<T>[K]) => boolean;
51
+ }) => Promise<SubscriptionListenerPayloads<T>[K]>;
52
+ }
53
+ declare function create<T extends Schema>({
54
+ schema,
55
+ validate
56
+ }: BusOptions<T>): Bus<T>;
57
+ type InferBusType<T extends Schema> = ReturnType<typeof create<T>>;
58
+ type InferPublishHandler<T extends Schema> = InferBusType<T>["publish"];
59
+ type InferSubscribeHandler<T extends Schema> = InferBusType<T>["subscribe"];
60
+ type InferSubscriptionKey<T extends Schema> = SubscriptionKey<T>;
61
+ type InferPublishKey<T extends Schema> = PublishKey<T>;
62
+ //#endregion
63
+ export { InferBusType, InferPublishHandler, InferPublishKey, InferSubscribeHandler, InferSubscriptionKey, create };
package/dist/index.d.ts CHANGED
@@ -1 +1,63 @@
1
- export * from "./bus";
1
+ import { ZodType } from "zod";
2
+
3
+ //#region src/types.d.ts
4
+ type IsZodType<T> = T extends ZodType ? true : false;
5
+ type IsSchema<T> = T extends Schema ? true : false;
6
+ type Listener<K extends string = string, V = unknown> = (data: V, event: K) => void;
7
+ interface Schema {
8
+ [key: string]: Schema | ZodType;
9
+ }
10
+ type HasWildcard<T extends string> = T extends `*.${string}` | `*` | `${string}.*.${string}` | `${string}.*` ? true : false;
11
+ type InferSubscriptionListener<T, K extends string, P extends string = ""> = K extends `${infer Left}.${infer Right}` ? HasWildcard<Left & string> extends true ? Listener : Left extends keyof T ? T[Left] extends Schema ? InferSubscriptionListener<T[Left], Right, `${P}${Left}.`> : never : never : K extends keyof T ? T[K] extends ZodType<infer O, unknown> ? Listener<`${P}${K}`, O> : never : HasWildcard<K & string> extends true ? Listener : never;
12
+ type InferSubscriptionListenerPayload<T, K extends string, P extends string = ""> = K extends `${infer Left}.${infer Right}` ? HasWildcard<Left & string> extends true ? unknown : Left extends keyof T ? T[Left] extends Schema ? InferSubscriptionListenerPayload<T[Left], Right, `${P}${Left}.`> : never : never : K extends keyof T ? T[K] extends ZodType<infer O, unknown> ? O : never : HasWildcard<K & string> extends true ? unknown : never;
13
+ type ZodTypePath<T, K extends keyof T = keyof T> = K extends string ? IsZodType<T[K]> extends true ? K : never : never;
14
+ type NamespacePath<T, K extends keyof T = keyof T> = K extends string ? IsSchema<T[K]> extends true ? `${K}.${SchemaPath<T[K], keyof T[K]>}` : never : never;
15
+ type SchemaPath<T, K extends keyof T = keyof T> = NamespacePath<T, K> | ZodTypePath<T, K>;
16
+ type WildcardPath<T extends string> = T extends `${infer L}.${infer R}` ? `*.${WildcardPath<R>}` | `${L}.*` | `${L}.${WildcardPath<R>}` : T | "*";
17
+ type ExcludeDirectlyNestedKeys<T extends string> = T extends `${infer L}.${infer R}` ? ExcludeDirectlyNestedKeys<R> | L : never;
18
+ type Subscription<T extends Schema> = { [K in Exclude<WildcardPath<SchemaPath<T>>, ExcludeDirectlyNestedKeys<SchemaPath<T>>> | "*"]: {
19
+ listener: InferSubscriptionListener<T, K>;
20
+ payload: InferSubscriptionListenerPayload<T, K>;
21
+ } };
22
+ type Subscriptions<T extends Schema> = { [K in keyof Subscription<T>]: Subscription<T>[K] };
23
+ type SubscriptionListeners<T extends Schema> = { [K in keyof Subscriptions<T>]: Subscriptions<T>[K]["listener"] };
24
+ type SubscriptionListenerPayloads<T extends Schema> = { [K in keyof Subscriptions<T>]: Subscriptions<T>[K]["payload"] };
25
+ type SubscriptionKey<T extends Schema> = Extract<keyof SubscriptionListeners<T>, string>;
26
+ type PublishKey<T extends Schema> = Extract<SubscriptionKey<T>, SchemaPath<T>>;
27
+ //#endregion
28
+ //#region src/bus.d.ts
29
+ type BusOptions<T extends Schema> = {
30
+ schema: T;
31
+ validate?: boolean;
32
+ };
33
+ interface Bus<T extends Schema> {
34
+ publish: <K extends PublishKey<T>>(event: K, data: SubscriptionListenerPayloads<T>[K]) => void;
35
+ subscribe: <K extends SubscriptionKey<T>>(event: K, listener: SubscriptionListeners<T>[K]) => {
36
+ event: K;
37
+ listener: SubscriptionListeners<T>[K];
38
+ unsubscribe: () => void;
39
+ };
40
+ subscribeOnce: <K extends SubscriptionKey<T>>(event: K, listener: SubscriptionListeners<T>[K]) => {
41
+ event: K;
42
+ listener: SubscriptionListeners<T>[K];
43
+ unsubscribe: () => void;
44
+ };
45
+ unsubscribe: <K extends SubscriptionKey<T>>(event: K, listener?: SubscriptionListeners<T>[K]) => void;
46
+ getEventNames: () => PublishKey<T>[];
47
+ getListeners: <K extends SubscriptionKey<T>>(event?: K) => ((data: unknown, eventName: string) => void)[];
48
+ waitFor: <K extends SubscriptionKey<T>>(event: K, options?: {
49
+ timeout?: number;
50
+ filter?: (data: SubscriptionListenerPayloads<T>[K]) => boolean;
51
+ }) => Promise<SubscriptionListenerPayloads<T>[K]>;
52
+ }
53
+ declare function create<T extends Schema>({
54
+ schema,
55
+ validate
56
+ }: BusOptions<T>): Bus<T>;
57
+ type InferBusType<T extends Schema> = ReturnType<typeof create<T>>;
58
+ type InferPublishHandler<T extends Schema> = InferBusType<T>["publish"];
59
+ type InferSubscribeHandler<T extends Schema> = InferBusType<T>["subscribe"];
60
+ type InferSubscriptionKey<T extends Schema> = SubscriptionKey<T>;
61
+ type InferPublishKey<T extends Schema> = PublishKey<T>;
62
+ //#endregion
63
+ export { InferBusType, InferPublishHandler, InferPublishKey, InferSubscribeHandler, InferSubscriptionKey, create };
package/dist/index.js ADDED
@@ -0,0 +1,207 @@
1
+ import { ZodType } from "zod";
2
+ //#region src/constants.ts
3
+ const errorPrefix = "[zodbus] ";
4
+ //#endregion
5
+ //#region src/errors.ts
6
+ var ValidationError = class extends Error {
7
+ constructor(message) {
8
+ super(`${errorPrefix}${message}`);
9
+ this.name = "ValidationError";
10
+ }
11
+ };
12
+ var RuntimeError = class extends Error {
13
+ constructor(message) {
14
+ super(`${errorPrefix}${message}`);
15
+ this.name = "RuntimeError";
16
+ }
17
+ };
18
+ //#endregion
19
+ //#region src/utils/schema.ts
20
+ const subPubPathMapCache = /* @__PURE__ */ new WeakMap();
21
+ const getPublishPaths = (schema, prefix = "") => {
22
+ const paths = [];
23
+ for (const [k, v] of Object.entries(schema)) {
24
+ const newPrefix = prefix ? `${prefix}.${k}` : k;
25
+ if (typeof v.parse === "function") paths.push(newPrefix);
26
+ else paths.push(...getPublishPaths(v, newPrefix));
27
+ }
28
+ return paths;
29
+ };
30
+ const getSubscribePaths = (schema, prefix = "") => {
31
+ const paths = [];
32
+ for (const [k, v] of Object.entries(schema)) {
33
+ const newPrefix = prefix ? `${prefix}.${k}` : k;
34
+ const newWildcardPrefix = prefix ? `${prefix}.*` : "*";
35
+ if (typeof v.parse === "function") paths.push(newPrefix, newWildcardPrefix);
36
+ else paths.push(...getSubscribePaths(v, newPrefix), ...getSubscribePaths(v, newWildcardPrefix));
37
+ }
38
+ return paths;
39
+ };
40
+ const getSubPubPathMap = (schema) => {
41
+ const cached = subPubPathMapCache.get(schema);
42
+ if (cached) return cached;
43
+ const map = {};
44
+ const publishPaths = getPublishPaths(schema);
45
+ const subscribePaths = getSubscribePaths(schema);
46
+ for (const subscribePath of subscribePaths) {
47
+ const subscribeParts = subscribePath.split(".");
48
+ map[subscribePath] = publishPaths.filter((publishPath) => {
49
+ const publishParts = publishPath.split(".");
50
+ if (publishParts.length !== subscribeParts.length) return false;
51
+ for (const [i, publishPart] of publishParts.entries()) if (subscribeParts[i] !== "*" && publishPart !== subscribeParts[i]) return false;
52
+ return true;
53
+ });
54
+ }
55
+ subPubPathMapCache.set(schema, map);
56
+ return map;
57
+ };
58
+ //#endregion
59
+ //#region src/bus.ts
60
+ function create({ schema, validate = true }) {
61
+ const subPubPathMap = getSubPubPathMap(schema);
62
+ const eventNames = Array.from(new Set(Object.values(subPubPathMap).flat()));
63
+ const listeners = /* @__PURE__ */ new Map();
64
+ const validationSchemaCache = /* @__PURE__ */ new Map();
65
+ const getListenerSetsForSubscriptionKey = (event) => {
66
+ if (event === "*") return Array.from(listeners.values());
67
+ const listenerSets = [];
68
+ const publishPaths = subPubPathMap[event];
69
+ if (!publishPaths) throw new ValidationError(`Invalid event: "${event}"`);
70
+ for (const publishPath of publishPaths) {
71
+ const listenerSet = listeners.get(publishPath);
72
+ if (listenerSet) listenerSets.push(listenerSet);
73
+ }
74
+ return listenerSets;
75
+ };
76
+ const validatePayloadOrPanic = (event, data) => {
77
+ const zodSchema = validationSchemaCache.get(event);
78
+ if (zodSchema) {
79
+ zodSchema.parse(data);
80
+ return;
81
+ }
82
+ const pathFragments = event.split(".");
83
+ let currentSchema = schema;
84
+ for (const fragment of pathFragments) {
85
+ const nextSchema = currentSchema[fragment];
86
+ if (!nextSchema) throw new ValidationError(`Invalid event: "${event}". Could not resolve "${fragment}" fragment.`);
87
+ currentSchema = nextSchema;
88
+ }
89
+ if (currentSchema instanceof ZodType) {
90
+ validationSchemaCache.set(event, currentSchema);
91
+ currentSchema.parse(data);
92
+ } else throw new ValidationError(`Reached invalid payload schema for: "${event}"`);
93
+ };
94
+ /** Unsubscribe from an event. If no listener is provided, all listeners for the event will be removed.
95
+ * @param event The event to unsubscribe from. Wildcards `foo.*.bar.*` are supported.
96
+ * Using `*` will match any event on any level.
97
+ * More specific wildcard patterns like `*.*` will only match events on that level.
98
+ * @param listener The listener to remove. If no listener is provided, all listeners for the event will be removed.
99
+ *
100
+ * Examples:
101
+ * - `unsubscribe("foo.bar", listener)` will unsubscribe the listener from `foo.bar`
102
+ * - `unsubscribe("foo.*", listener)` will unsubscribe the listener from all events under `foo`
103
+ * - `unsubscribe("*", listener)` will unsubscribe the listener from all events
104
+ * - `unsubscribe("*")` will unsubscribe all listeners from all events
105
+ * - `unsubscribe("*.*")` will unsubscribe all listeners from all events on the second level
106
+ */
107
+ const unsubscribe = (event, listener) => {
108
+ const eventListeners = getListenerSetsForSubscriptionKey(event);
109
+ for (const listenerSet of eventListeners) if (listener === void 0) listenerSet.clear();
110
+ else listenerSet.delete(listener);
111
+ };
112
+ /** Subscribe to an event. Returns an object with the event name, listener, and an unsubscribe function.
113
+ * @param event The event to subscribe to. Wildcards `foo.*.bar.*` are supported.
114
+ * Using `*` will match any event on any level.
115
+ * More specific wildcard patterns like `*.*` will only match events on that level.
116
+ * @param listener The listener to call when the event is published.
117
+ * @returns A subscription object with the event name, listener, and an unsubscribe function.
118
+ */
119
+ const subscribe = (event, listener) => {
120
+ if (typeof listener !== "function") throw new ValidationError(`Invalid listener for event: "${event}". Expected function, got ${typeof listener}`);
121
+ const publishPaths = event === "*" ? eventNames : subPubPathMap[event];
122
+ if (!publishPaths) throw new ValidationError(`Invalid event: "${event}"`);
123
+ for (const publishPath of publishPaths) {
124
+ let listenerSet = listeners.get(publishPath);
125
+ if (!listenerSet) {
126
+ listenerSet = /* @__PURE__ */ new Set();
127
+ listeners.set(publishPath, listenerSet);
128
+ }
129
+ listenerSet.add(listener);
130
+ }
131
+ return {
132
+ event,
133
+ listener,
134
+ unsubscribe: () => unsubscribe(event, listener)
135
+ };
136
+ };
137
+ /** Subscribe to an event once. The listener will be unsubscribed after the first time it is called.
138
+ * You cannot cancel this subscription using unsubscribe() with the original listener.
139
+ * Use the returned unsubscribe function / listener instead.
140
+ * @param event The event to subscribe to. Wildcards `foo.*.bar.*` are supported.
141
+ * Using `*` will match any event on any level.
142
+ * More specific wildcard patterns like `*.*` will only match events on that level.
143
+ * @param listener The listener to call when the event is published.
144
+ * @returns A subscription object with the event name, listener, and an unsubscribe function.
145
+ * */
146
+ const subscribeOnce = (event, listener) => {
147
+ const wrappedListener = (data, eventName) => {
148
+ listener(data, eventName);
149
+ unsubscribe(event, wrappedListener);
150
+ };
151
+ return subscribe(event, wrappedListener);
152
+ };
153
+ /** Publish an event. All listeners for the event will be called with the provided data.
154
+ * @param event The event to publish.
155
+ * @param data The data to pass to the listeners.*/
156
+ const publish = (event, data) => {
157
+ if (validate) validatePayloadOrPanic(event, data);
158
+ if (!listeners.has(event)) return;
159
+ for (const listener of listeners.get(event)) listener(data, event);
160
+ };
161
+ /** Returns a list of all the event names.
162
+ * @returns An array of event names. */
163
+ const getEventNames = () => eventNames;
164
+ /** Get the list of listeners for an event or all listeners if no event is provided.
165
+ * @param event The event to get listeners for.
166
+ * @returns An array of listeners for the event. */
167
+ const getListeners = (event) => {
168
+ const targetListeners = [];
169
+ const targetListenerSets = event ? getListenerSetsForSubscriptionKey(event) : Array.from(listeners.values());
170
+ for (const listenerSet of targetListenerSets) for (const listener of listenerSet) targetListeners.push(listener);
171
+ return targetListeners;
172
+ };
173
+ /** Waits for an event to be published.
174
+ * Returns a promise that resolves when the event is published.
175
+ * @param event The event to wait for.
176
+ * @param options.timeout The timeout in milliseconds. Defaults to 5000.
177
+ * @param options.filter A function that returns true if the event should be accepted.
178
+ * @returns A promise that resolves when the event is published with the data passed to the listener. */
179
+ const waitFor = (event, options = {}) => {
180
+ const { timeout = 5e3, filter } = options;
181
+ return new Promise((resolve, reject) => {
182
+ let timeoutId;
183
+ const listener = (data) => {
184
+ if (filter && !filter(data)) return;
185
+ unsubscribe(event, listener);
186
+ resolve(data);
187
+ clearTimeout(timeoutId);
188
+ };
189
+ subscribe(event, listener);
190
+ if (timeout) timeoutId = setTimeout(() => {
191
+ unsubscribe(event, listener);
192
+ reject(new RuntimeError(`Timeout waiting for event: "${event}"`));
193
+ }, timeout);
194
+ });
195
+ };
196
+ return {
197
+ publish,
198
+ subscribe,
199
+ subscribeOnce,
200
+ unsubscribe,
201
+ getEventNames,
202
+ getListeners,
203
+ waitFor
204
+ };
205
+ }
206
+ //#endregion
207
+ export { create };
package/package.json CHANGED
@@ -1,7 +1,10 @@
1
1
  {
2
2
  "name": "zodbus",
3
- "version": "1.1.3",
4
- "repository": "https://github.com/3rd/zodbus",
3
+ "version": "2.0.0",
4
+ "repository": {
5
+ "type": "git",
6
+ "url": "git+https://github.com/3rd/zodbus.git"
7
+ },
5
8
  "description": "Type-safe event bus built around Zod.",
6
9
  "keywords": [
7
10
  "event bus",
@@ -13,32 +16,43 @@
13
16
  "email": "3rd@users.noreply.github.com"
14
17
  },
15
18
  "license": "MIT",
19
+ "engines": {
20
+ "node": ">=20",
21
+ "bun": ">=1.1.0"
22
+ },
23
+ "packageManager": "bun@1.3.11",
16
24
  "files": [
17
25
  "dist"
18
26
  ],
19
27
  "type": "module",
20
28
  "types": "dist/index.d.ts",
21
- "main": "dist/index.mjs",
29
+ "main": "./dist/index.cjs",
30
+ "module": "./dist/index.js",
31
+ "sideEffects": false,
22
32
  "exports": {
23
33
  ".": {
24
34
  "import": {
25
- "default": "./dist/index.mjs",
26
- "types": "./dist/index.d.ts"
35
+ "types": "./dist/index.d.ts",
36
+ "default": "./dist/index.js"
27
37
  },
28
38
  "require": {
29
- "default": "./dist/index.cjs",
30
- "types": "./dist/index.d.ts"
39
+ "types": "./dist/index.d.cts",
40
+ "default": "./dist/index.cjs"
31
41
  }
32
42
  }
33
43
  },
34
44
  "scripts": {
35
- "build": "npm run clean && node src/scripts/build.mjs && tsc -p tsconfig.build.json",
36
- "test": "jest",
37
- "tsc": "tsc",
45
+ "test": "bun test",
46
+ "typecheck": "tsc --noEmit",
47
+ "build": "tsdown",
48
+ "publint": "publint --level=warning",
49
+ "attw": "attw --pack .",
50
+ "verify": "bun run test && bun run typecheck && bun run build && bun run publint && bun run attw",
51
+ "tsc": "bun run typecheck",
38
52
  "benchmark": "cd benchmark && npm run benchmark",
39
- "prepare": "husky install",
40
- "prepublishOnly": "pnpm run build && pnpm run tsc && pnpm run test",
41
- "clean": "rm -rf dist"
53
+ "release": "GIT_CONFIG_KEY_0=\"url.$(git remote get-url --push origin).insteadOf\" GIT_CONFIG_VALUE_0=\"https://github.com/3rd/zodbus.git\" GIT_CONFIG_COUNT=1 semantic-release --no-ci",
54
+ "prepare": "husky",
55
+ "prepublishOnly": "bun run verify"
42
56
  },
43
57
  "lint-staged": {
44
58
  "*": "prettier --ignore-unknown --write"
@@ -46,7 +60,6 @@
46
60
  "prettier": {
47
61
  "arrowParens": "always",
48
62
  "bracketSpacing": true,
49
- "jsxBracketSameLine": false,
50
63
  "printWidth": 120,
51
64
  "quoteProps": "as-needed",
52
65
  "semi": true,
@@ -56,22 +69,22 @@
56
69
  "useTabs": false
57
70
  },
58
71
  "devDependencies": {
59
- "@commitlint/cli": "^19.3.0",
60
- "@commitlint/config-conventional": "^19.2.2",
72
+ "@arethetypeswrong/cli": "^0.18.4",
73
+ "@commitlint/cli": "^21.1.0",
74
+ "@commitlint/config-conventional": "^21.1.0",
61
75
  "@semantic-release/changelog": "^6.0.3",
62
76
  "@semantic-release/git": "^10.0.1",
63
- "@types/jest": "^29.5.12",
64
- "@types/node": "^20.12.8",
65
- "esbuild": "^0.20.2",
66
- "husky": "^9.0.11",
67
- "jest": "^29.7.0",
68
- "lint-staged": "^15.2.2",
69
- "prettier": "^3.2.5",
70
- "semantic-release": "^23.0.8",
71
- "ts-jest": "^29.1.2",
72
- "typescript": "^5.4.5"
77
+ "@types/bun": "^1.3.14",
78
+ "@types/node": "^22.20.0",
79
+ "husky": "^9.1.7",
80
+ "lint-staged": "^17.0.8",
81
+ "prettier": "^3.9.1",
82
+ "publint": "^0.3.21",
83
+ "semantic-release": "^25.0.5",
84
+ "tsdown": "^0.22.3",
85
+ "typescript": "^6.0.3"
73
86
  },
74
87
  "peerDependencies": {
75
- "zod": "^3.21.4"
88
+ "zod": "^4.1.11"
76
89
  }
77
90
  }
package/dist/bus.d.ts DELETED
@@ -1,32 +0,0 @@
1
- import type { PublishKey, Schema, SubscriptionKey, SubscriptionListenerPayloads, SubscriptionListeners } from "./types";
2
- type BusOptions<T extends Schema> = {
3
- schema: T;
4
- validate?: boolean;
5
- };
6
- interface Bus<T extends Schema> {
7
- publish: <K extends PublishKey<T>>(event: K, data: SubscriptionListenerPayloads<T>[K]) => void;
8
- subscribe: <K extends SubscriptionKey<T>>(event: K, listener: SubscriptionListeners<T>[K]) => {
9
- event: K;
10
- listener: SubscriptionListeners<T>[K];
11
- unsubscribe: () => void;
12
- };
13
- subscribeOnce: <K extends SubscriptionKey<T>>(event: K, listener: SubscriptionListeners<T>[K]) => {
14
- event: K;
15
- listener: SubscriptionListeners<T>[K];
16
- unsubscribe: () => void;
17
- };
18
- unsubscribe: <K extends SubscriptionKey<T>>(event: K, listener?: SubscriptionListeners<T>[K]) => void;
19
- getEventNames: () => PublishKey<T>[];
20
- getListeners: <K extends SubscriptionKey<T>>(event?: K) => ((data: unknown, eventName: string) => void)[];
21
- waitFor: <K extends SubscriptionKey<T>>(event: K, options?: {
22
- timeout?: number;
23
- filter?: (data: SubscriptionListenerPayloads<T>[K]) => boolean;
24
- }) => Promise<SubscriptionListenerPayloads<T>[K]>;
25
- }
26
- declare function create<T extends Schema>({ schema, validate }: BusOptions<T>): Bus<T>;
27
- export type InferBusType<T extends Schema> = ReturnType<typeof create<T>>;
28
- export type InferPublishHandler<T extends Schema> = InferBusType<T>["publish"];
29
- export type InferSubscribeHandler<T extends Schema> = InferBusType<T>["subscribe"];
30
- export type InferSubscriptionKey<T extends Schema> = SubscriptionKey<T>;
31
- export type InferPublishKey<T extends Schema> = PublishKey<T>;
32
- export { create };
@@ -1 +0,0 @@
1
- export declare const errorPrefix = "[zodbus] ";
package/dist/errors.d.ts DELETED
@@ -1,6 +0,0 @@
1
- export declare class ValidationError extends Error {
2
- constructor(message: string);
3
- }
4
- export declare class RuntimeError extends Error {
5
- constructor(message: string);
6
- }
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../src/index.ts", "../src/bus.ts", "../src/constants.ts", "../src/errors.ts", "../src/utils/schema.ts"],
4
- "sourcesContent": ["export * from \"./bus\";\n", "import { ZodType } from \"zod\";\nimport { RuntimeError, ValidationError } from \"./errors\";\nimport type { PublishKey, Schema, SubscriptionKey, SubscriptionListenerPayloads, SubscriptionListeners } from \"./types\";\nimport { getSubPubPathMap } from \"./utils/schema\";\n\ntype BusOptions<T extends Schema> = {\n schema: T;\n validate?: boolean;\n};\n\ninterface Bus<T extends Schema> {\n publish: <K extends PublishKey<T>>(event: K, data: SubscriptionListenerPayloads<T>[K]) => void;\n subscribe: <K extends SubscriptionKey<T>>(\n event: K,\n listener: SubscriptionListeners<T>[K]\n ) => { event: K; listener: SubscriptionListeners<T>[K]; unsubscribe: () => void };\n subscribeOnce: <K extends SubscriptionKey<T>>(\n event: K,\n listener: SubscriptionListeners<T>[K]\n ) => { event: K; listener: SubscriptionListeners<T>[K]; unsubscribe: () => void };\n unsubscribe: <K extends SubscriptionKey<T>>(event: K, listener?: SubscriptionListeners<T>[K]) => void;\n getEventNames: () => PublishKey<T>[];\n getListeners: <K extends SubscriptionKey<T>>(event?: K) => ((data: unknown, eventName: string) => void)[];\n waitFor: <K extends SubscriptionKey<T>>(\n event: K,\n options?: { timeout?: number; filter?: (data: SubscriptionListenerPayloads<T>[K]) => boolean }\n ) => Promise<SubscriptionListenerPayloads<T>[K]>;\n}\n\nfunction create<T extends Schema>({ schema, validate = true }: BusOptions<T>): Bus<T> {\n const subPubPathMap = getSubPubPathMap(schema) as Record<SubscriptionKey<T>, PublishKey<T>[]>;\n const eventNames = Array.from(new Set(Object.values(subPubPathMap).flat())) as PublishKey<T>[];\n const listeners = new Map<PublishKey<T>, Set<SubscriptionListeners<T>[PublishKey<T>]>>();\n\n const getListenerSetsForSubscriptionKey = (event: SubscriptionKey<T>): Set<unknown>[] => {\n if (event === \"*\") return Array.from(listeners.values());\n const listenerSets: Set<SubscriptionListeners<T>[PublishKey<T>]>[] = [];\n const publishPaths = subPubPathMap[event];\n if (!publishPaths) throw new ValidationError(`Invalid event: \"${event}\"`);\n for (const publishPath of publishPaths) {\n const listenerSet = listeners.get(publishPath);\n if (listenerSet) listenerSets.push(listenerSet);\n }\n return listenerSets;\n };\n\n const validatePayloadOrPanic = (event: string, data: unknown): void => {\n const pathFragments = event.split(\".\");\n let currentSchema: Schema | ZodType = schema;\n for (const fragment of pathFragments) {\n currentSchema = (currentSchema as Record<string, Schema | ZodType>)[fragment];\n if (!currentSchema) {\n throw new ValidationError(`Invalid event: \"${event}\". Could not resolve \"${fragment}\" fragment.`);\n }\n }\n if (currentSchema instanceof ZodType) {\n currentSchema.parse(data);\n } else {\n throw new ValidationError(`Reached invalid payload schema for: \"${event}\"`);\n }\n };\n\n /** Unsubscribe from an event. If no listener is provided, all listeners for the event will be removed.\n * @param event The event to unsubscribe from. Wildcards `foo.*.bar.*` are supported.\n * Using `*` will match any event on any level.\n * More specific wildcard patterns like `*.*` will only match events on that level.\n * @param listener The listener to remove. If no listener is provided, all listeners for the event will be removed.\n *\n * Examples:\n * - `unsubscribe(\"foo.bar\", listener)` will unsubscribe the listener from `foo.bar`\n * - `unsubscribe(\"foo.*\", listener)` will unsubscribe the listener from all events under `foo`\n * - `unsubscribe(\"*\", listener)` will unsubscribe the listener from all events\n * - `unsubscribe(\"*\")` will unsubscribe all listeners from all events\n * - `unsubscribe(\"*.*\")` will unsubscribe all listeners from all events on the second level\n */\n const unsubscribe = <K extends SubscriptionKey<T>>(event: K, listener?: SubscriptionListeners<T>[K]): void => {\n const eventListeners = getListenerSetsForSubscriptionKey(event);\n for (const listenerSet of eventListeners) {\n if (listener === undefined) {\n listenerSet.clear();\n } else {\n listenerSet.delete(listener);\n }\n }\n };\n\n /** Subscribe to an event. Returns an object with the event name, listener, and an unsubscribe function.\n * @param event The event to subscribe to. Wildcards `foo.*.bar.*` are supported.\n * Using `*` will match any event on any level.\n * More specific wildcard patterns like `*.*` will only match events on that level.\n * @param listener The listener to call when the event is published.\n * @returns A subscription object with the event name, listener, and an unsubscribe function.\n */\n const subscribe = <K extends SubscriptionKey<T>>(\n event: K,\n listener: SubscriptionListeners<T>[K]\n ): {\n event: K;\n listener: SubscriptionListeners<T>[K];\n unsubscribe: () => void;\n } => {\n if (typeof listener !== \"function\") {\n throw new ValidationError(`Invalid listener for event: \"${event}\". Expected function, got ${typeof listener}`);\n }\n const publishPaths = event === \"*\" ? eventNames : subPubPathMap[event];\n if (!publishPaths) throw new ValidationError(`Invalid event: \"${event}\"`);\n for (const publishPath of publishPaths) {\n const listenerSet = listeners.get(publishPath) ?? new Set();\n listenerSet.add(listener as SubscriptionListeners<T>[PublishKey<T>]);\n listeners.set(publishPath, listenerSet);\n }\n return { event, listener, unsubscribe: () => unsubscribe(event, listener) };\n };\n\n /** Subscribe to an event once. The listener will be unsubscribed after the first time it is called.\n * You cannot cancel this subscription using unsubscribe() with the original listener.\n * Use the returned unsubscribe function / listener instead.\n * @param event The event to subscribe to. Wildcards `foo.*.bar.*` are supported.\n * Using `*` will match any event on any level.\n * More specific wildcard patterns like `*.*` will only match events on that level.\n * @param listener The listener to call when the event is published.\n * @returns A subscription object with the event name, listener, and an unsubscribe function.\n * */\n const subscribeOnce = <K extends SubscriptionKey<T>>(event: K, listener: SubscriptionListeners<T>[K]) => {\n const wrappedListener = (data: unknown, eventName: string) => {\n listener(data, eventName);\n unsubscribe(event, wrappedListener as unknown as SubscriptionListeners<T>[K]);\n };\n return subscribe(event, wrappedListener as unknown as SubscriptionListeners<T>[K]);\n };\n\n /** Publish an event. All listeners for the event will be called with the provided data.\n * @param event The event to publish.\n * @param data The data to pass to the listeners.*/\n const publish = <K extends PublishKey<T>>(event: K, data: SubscriptionListenerPayloads<T>[K]) => {\n if (validate) validatePayloadOrPanic(event, data);\n if (!listeners.has(event)) return;\n for (const listener of listeners.get(event)!) {\n (listener as SubscriptionListeners<T>[K])(data, event);\n }\n };\n\n /** Returns a list of all the event names.\n * @returns An array of event names. */\n const getEventNames = () => eventNames;\n\n /** Get the list of listeners for an event or all listeners if no event is provided.\n * @param event The event to get listeners for.\n * @returns An array of listeners for the event. */\n const getListeners = <K extends SubscriptionKey<T>>(event?: K) => {\n const targetListeners: ((data: unknown, eventName: string) => void)[] = [];\n const targetListenerSets = event ? getListenerSetsForSubscriptionKey(event) : Array.from(listeners.values());\n for (const listenerSet of targetListenerSets) {\n for (const listener of listenerSet) {\n targetListeners.push(listener as (data: unknown, eventName: string) => void);\n }\n }\n return targetListeners;\n };\n\n /** Waits for an event to be published.\n * Returns a promise that resolves when the event is published.\n * @param event The event to wait for.\n * @param options.timeout The timeout in milliseconds. Defaults to 10000.\n * @param options.filter A function that returns true if the event should be accepted.\n * @returns A promise that resolves when the event is published with the data passed to the listener. */\n const waitFor = <K extends SubscriptionKey<T>>(\n event: K,\n options: { timeout?: number; filter?: (data: SubscriptionListenerPayloads<T>[K]) => boolean } = {}\n ) => {\n const { timeout = 5000, filter } = options;\n return new Promise<SubscriptionListenerPayloads<T>[K]>((resolve, reject) => {\n let timeoutId: ReturnType<typeof setTimeout>;\n const listener = (data: unknown) => {\n if (filter && !filter(data as SubscriptionListenerPayloads<T>[K])) return;\n unsubscribe(event, listener as unknown as SubscriptionListeners<T>[K]);\n resolve(data as SubscriptionListenerPayloads<T>[K]);\n clearTimeout(timeoutId);\n };\n subscribe(event, listener as unknown as SubscriptionListeners<T>[K]);\n if (timeout) {\n timeoutId = setTimeout(() => {\n unsubscribe(event, listener as unknown as SubscriptionListeners<T>[K]);\n reject(new RuntimeError(`Timeout waiting for event: \"${event}\"`));\n }, timeout);\n }\n });\n };\n\n return {\n publish,\n subscribe,\n subscribeOnce,\n unsubscribe,\n getEventNames,\n getListeners,\n waitFor,\n };\n}\n\nexport type InferBusType<T extends Schema> = ReturnType<typeof create<T>>;\nexport type InferPublishHandler<T extends Schema> = InferBusType<T>[\"publish\"];\nexport type InferSubscribeHandler<T extends Schema> = InferBusType<T>[\"subscribe\"];\nexport type InferSubscriptionKey<T extends Schema> = SubscriptionKey<T>;\nexport type InferPublishKey<T extends Schema> = PublishKey<T>;\n\nexport { create };\n", "export const errorPrefix = \"[zodbus] \";\n", "import { errorPrefix } from \"./constants\";\n\nexport class ValidationError extends Error {\n constructor(message: string) {\n super(`${errorPrefix}${message}`);\n this.name = \"ValidationError\";\n }\n}\n\nexport class RuntimeError extends Error {\n constructor(message: string) {\n super(`${errorPrefix}${message}`);\n this.name = \"RuntimeError\";\n }\n}\n", "import { ZodType } from \"zod\";\nimport { Schema } from \"../types\";\n\nexport const hasWildcard = (path: string): boolean => path.split(\".\").includes(\"*\");\n\nexport const getPublishPaths = (schema: Schema | ZodType, prefix = \"\"): string[] => {\n const paths: string[] = [];\n for (const [k, v] of Object.entries(schema)) {\n const newPrefix = prefix ? `${prefix}.${k}` : k;\n if (typeof v.parse === \"function\") {\n paths.push(newPrefix);\n } else {\n paths.push(...getPublishPaths(v, newPrefix));\n }\n }\n return paths;\n};\n\nexport const getSubscribePaths = (schema: Schema | ZodType, prefix = \"\"): string[] => {\n const paths: string[] = [];\n for (const [k, v] of Object.entries(schema)) {\n const newPrefix = prefix ? `${prefix}.${k}` : k;\n const newWildcardPrefix = prefix ? `${prefix}.*` : \"*\";\n if (typeof v.parse === \"function\") {\n paths.push(newPrefix, newWildcardPrefix);\n } else {\n paths.push(...getSubscribePaths(v, newPrefix), ...getSubscribePaths(v, newWildcardPrefix));\n }\n }\n return paths;\n};\n\nexport const getSubPubPathMap = (schema: Schema | ZodType): Record<string, string[]> => {\n const map: Record<string, string[]> = {};\n const publishPaths = getPublishPaths(schema);\n const subscribePaths = getSubscribePaths(schema);\n\n for (const subscribePath of subscribePaths) {\n const subscribeParts = subscribePath.split(\".\");\n map[subscribePath] = publishPaths.filter((publishPath) => {\n const publishParts = publishPath.split(\".\");\n if (publishParts.length !== subscribeParts.length) return false;\n for (const [i, publishPart] of publishParts.entries()) {\n if (subscribeParts[i] !== \"*\" && publishPart !== subscribeParts[i]) return false;\n }\n return true;\n });\n }\n\n return map;\n};\n"],
5
- "mappings": "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",
6
- "names": ["src_exports", "__export", "create", "__toCommonJS", "import_zod", "errorPrefix", "ValidationError", "message", "errorPrefix", "RuntimeError", "getPublishPaths", "schema", "prefix", "paths", "k", "v", "newPrefix", "getSubscribePaths", "newWildcardPrefix", "getSubPubPathMap", "map", "publishPaths", "subscribePaths", "subscribePath", "subscribeParts", "publishPath", "publishParts", "i", "publishPart", "create", "schema", "validate", "subPubPathMap", "getSubPubPathMap", "eventNames", "listeners", "getListenerSetsForSubscriptionKey", "event", "listenerSets", "publishPaths", "ValidationError", "publishPath", "listenerSet", "validatePayloadOrPanic", "data", "pathFragments", "currentSchema", "fragment", "unsubscribe", "listener", "eventListeners", "subscribe", "_a", "wrappedListener", "eventName", "targetListeners", "targetListenerSets", "options", "timeout", "filter", "resolve", "reject", "timeoutId", "RuntimeError"]
7
- }
package/dist/index.mjs DELETED
@@ -1,2 +0,0 @@
1
- import{ZodType as w}from"zod";var h="[zodbus] ";var l=class extends Error{constructor(i){super(`${h}${i}`),this.name="ValidationError"}},K=class extends Error{constructor(i){super(`${h}${i}`),this.name="RuntimeError"}};var x=(a,i="")=>{let u=[];for(let[p,r]of Object.entries(a)){let c=i?`${i}.${p}`:p;typeof r.parse=="function"?u.push(c):u.push(...x(r,c))}return u},y=(a,i="")=>{let u=[];for(let[p,r]of Object.entries(a)){let c=i?`${i}.${p}`:p,d=i?`${i}.*`:"*";typeof r.parse=="function"?u.push(c,d):u.push(...y(r,c),...y(r,d))}return u},L=a=>{let i={},u=x(a),p=y(a);for(let r of p){let c=r.split(".");i[r]=u.filter(d=>{let b=d.split(".");if(b.length!==c.length)return!1;for(let[f,m]of b.entries())if(c[f]!=="*"&&m!==c[f])return!1;return!0})}return i};function M({schema:a,validate:i=!0}){let u=L(a),p=Array.from(new Set(Object.values(u).flat())),r=new Map,c=e=>{if(e==="*")return Array.from(r.values());let s=[],n=u[e];if(!n)throw new l(`Invalid event: "${e}"`);for(let t of n){let o=r.get(t);o&&s.push(o)}return s},d=(e,s)=>{let n=e.split("."),t=a;for(let o of n)if(t=t[o],!t)throw new l(`Invalid event: "${e}". Could not resolve "${o}" fragment.`);if(t instanceof w)t.parse(s);else throw new l(`Reached invalid payload schema for: "${e}"`)},b=(e,s)=>{let n=c(e);for(let t of n)s===void 0?t.clear():t.delete(s)},f=(e,s)=>{var t;if(typeof s!="function")throw new l(`Invalid listener for event: "${e}". Expected function, got ${typeof s}`);let n=e==="*"?p:u[e];if(!n)throw new l(`Invalid event: "${e}"`);for(let o of n){let T=(t=r.get(o))!=null?t:new Set;T.add(s),r.set(o,T)}return{event:e,listener:s,unsubscribe:()=>b(e,s)}};return{publish:(e,s)=>{if(i&&d(e,s),!!r.has(e))for(let n of r.get(e))n(s,e)},subscribe:f,subscribeOnce:(e,s)=>{let n=(t,o)=>{s(t,o),b(e,n)};return f(e,n)},unsubscribe:b,getEventNames:()=>p,getListeners:e=>{let s=[],n=e?c(e):Array.from(r.values());for(let t of n)for(let o of t)s.push(o);return s},waitFor:(e,s={})=>{let{timeout:n=5e3,filter:t}=s;return new Promise((o,T)=>{let P,S=g=>{t&&!t(g)||(b(e,S),o(g),clearTimeout(P))};f(e,S),n&&(P=setTimeout(()=>{b(e,S),T(new K(`Timeout waiting for event: "${e}"`))},n))})}}}export{M as create};
2
- //# sourceMappingURL=index.mjs.map
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../src/bus.ts", "../src/constants.ts", "../src/errors.ts", "../src/utils/schema.ts"],
4
- "sourcesContent": ["import { ZodType } from \"zod\";\nimport { RuntimeError, ValidationError } from \"./errors\";\nimport type { PublishKey, Schema, SubscriptionKey, SubscriptionListenerPayloads, SubscriptionListeners } from \"./types\";\nimport { getSubPubPathMap } from \"./utils/schema\";\n\ntype BusOptions<T extends Schema> = {\n schema: T;\n validate?: boolean;\n};\n\ninterface Bus<T extends Schema> {\n publish: <K extends PublishKey<T>>(event: K, data: SubscriptionListenerPayloads<T>[K]) => void;\n subscribe: <K extends SubscriptionKey<T>>(\n event: K,\n listener: SubscriptionListeners<T>[K]\n ) => { event: K; listener: SubscriptionListeners<T>[K]; unsubscribe: () => void };\n subscribeOnce: <K extends SubscriptionKey<T>>(\n event: K,\n listener: SubscriptionListeners<T>[K]\n ) => { event: K; listener: SubscriptionListeners<T>[K]; unsubscribe: () => void };\n unsubscribe: <K extends SubscriptionKey<T>>(event: K, listener?: SubscriptionListeners<T>[K]) => void;\n getEventNames: () => PublishKey<T>[];\n getListeners: <K extends SubscriptionKey<T>>(event?: K) => ((data: unknown, eventName: string) => void)[];\n waitFor: <K extends SubscriptionKey<T>>(\n event: K,\n options?: { timeout?: number; filter?: (data: SubscriptionListenerPayloads<T>[K]) => boolean }\n ) => Promise<SubscriptionListenerPayloads<T>[K]>;\n}\n\nfunction create<T extends Schema>({ schema, validate = true }: BusOptions<T>): Bus<T> {\n const subPubPathMap = getSubPubPathMap(schema) as Record<SubscriptionKey<T>, PublishKey<T>[]>;\n const eventNames = Array.from(new Set(Object.values(subPubPathMap).flat())) as PublishKey<T>[];\n const listeners = new Map<PublishKey<T>, Set<SubscriptionListeners<T>[PublishKey<T>]>>();\n\n const getListenerSetsForSubscriptionKey = (event: SubscriptionKey<T>): Set<unknown>[] => {\n if (event === \"*\") return Array.from(listeners.values());\n const listenerSets: Set<SubscriptionListeners<T>[PublishKey<T>]>[] = [];\n const publishPaths = subPubPathMap[event];\n if (!publishPaths) throw new ValidationError(`Invalid event: \"${event}\"`);\n for (const publishPath of publishPaths) {\n const listenerSet = listeners.get(publishPath);\n if (listenerSet) listenerSets.push(listenerSet);\n }\n return listenerSets;\n };\n\n const validatePayloadOrPanic = (event: string, data: unknown): void => {\n const pathFragments = event.split(\".\");\n let currentSchema: Schema | ZodType = schema;\n for (const fragment of pathFragments) {\n currentSchema = (currentSchema as Record<string, Schema | ZodType>)[fragment];\n if (!currentSchema) {\n throw new ValidationError(`Invalid event: \"${event}\". Could not resolve \"${fragment}\" fragment.`);\n }\n }\n if (currentSchema instanceof ZodType) {\n currentSchema.parse(data);\n } else {\n throw new ValidationError(`Reached invalid payload schema for: \"${event}\"`);\n }\n };\n\n /** Unsubscribe from an event. If no listener is provided, all listeners for the event will be removed.\n * @param event The event to unsubscribe from. Wildcards `foo.*.bar.*` are supported.\n * Using `*` will match any event on any level.\n * More specific wildcard patterns like `*.*` will only match events on that level.\n * @param listener The listener to remove. If no listener is provided, all listeners for the event will be removed.\n *\n * Examples:\n * - `unsubscribe(\"foo.bar\", listener)` will unsubscribe the listener from `foo.bar`\n * - `unsubscribe(\"foo.*\", listener)` will unsubscribe the listener from all events under `foo`\n * - `unsubscribe(\"*\", listener)` will unsubscribe the listener from all events\n * - `unsubscribe(\"*\")` will unsubscribe all listeners from all events\n * - `unsubscribe(\"*.*\")` will unsubscribe all listeners from all events on the second level\n */\n const unsubscribe = <K extends SubscriptionKey<T>>(event: K, listener?: SubscriptionListeners<T>[K]): void => {\n const eventListeners = getListenerSetsForSubscriptionKey(event);\n for (const listenerSet of eventListeners) {\n if (listener === undefined) {\n listenerSet.clear();\n } else {\n listenerSet.delete(listener);\n }\n }\n };\n\n /** Subscribe to an event. Returns an object with the event name, listener, and an unsubscribe function.\n * @param event The event to subscribe to. Wildcards `foo.*.bar.*` are supported.\n * Using `*` will match any event on any level.\n * More specific wildcard patterns like `*.*` will only match events on that level.\n * @param listener The listener to call when the event is published.\n * @returns A subscription object with the event name, listener, and an unsubscribe function.\n */\n const subscribe = <K extends SubscriptionKey<T>>(\n event: K,\n listener: SubscriptionListeners<T>[K]\n ): {\n event: K;\n listener: SubscriptionListeners<T>[K];\n unsubscribe: () => void;\n } => {\n if (typeof listener !== \"function\") {\n throw new ValidationError(`Invalid listener for event: \"${event}\". Expected function, got ${typeof listener}`);\n }\n const publishPaths = event === \"*\" ? eventNames : subPubPathMap[event];\n if (!publishPaths) throw new ValidationError(`Invalid event: \"${event}\"`);\n for (const publishPath of publishPaths) {\n const listenerSet = listeners.get(publishPath) ?? new Set();\n listenerSet.add(listener as SubscriptionListeners<T>[PublishKey<T>]);\n listeners.set(publishPath, listenerSet);\n }\n return { event, listener, unsubscribe: () => unsubscribe(event, listener) };\n };\n\n /** Subscribe to an event once. The listener will be unsubscribed after the first time it is called.\n * You cannot cancel this subscription using unsubscribe() with the original listener.\n * Use the returned unsubscribe function / listener instead.\n * @param event The event to subscribe to. Wildcards `foo.*.bar.*` are supported.\n * Using `*` will match any event on any level.\n * More specific wildcard patterns like `*.*` will only match events on that level.\n * @param listener The listener to call when the event is published.\n * @returns A subscription object with the event name, listener, and an unsubscribe function.\n * */\n const subscribeOnce = <K extends SubscriptionKey<T>>(event: K, listener: SubscriptionListeners<T>[K]) => {\n const wrappedListener = (data: unknown, eventName: string) => {\n listener(data, eventName);\n unsubscribe(event, wrappedListener as unknown as SubscriptionListeners<T>[K]);\n };\n return subscribe(event, wrappedListener as unknown as SubscriptionListeners<T>[K]);\n };\n\n /** Publish an event. All listeners for the event will be called with the provided data.\n * @param event The event to publish.\n * @param data The data to pass to the listeners.*/\n const publish = <K extends PublishKey<T>>(event: K, data: SubscriptionListenerPayloads<T>[K]) => {\n if (validate) validatePayloadOrPanic(event, data);\n if (!listeners.has(event)) return;\n for (const listener of listeners.get(event)!) {\n (listener as SubscriptionListeners<T>[K])(data, event);\n }\n };\n\n /** Returns a list of all the event names.\n * @returns An array of event names. */\n const getEventNames = () => eventNames;\n\n /** Get the list of listeners for an event or all listeners if no event is provided.\n * @param event The event to get listeners for.\n * @returns An array of listeners for the event. */\n const getListeners = <K extends SubscriptionKey<T>>(event?: K) => {\n const targetListeners: ((data: unknown, eventName: string) => void)[] = [];\n const targetListenerSets = event ? getListenerSetsForSubscriptionKey(event) : Array.from(listeners.values());\n for (const listenerSet of targetListenerSets) {\n for (const listener of listenerSet) {\n targetListeners.push(listener as (data: unknown, eventName: string) => void);\n }\n }\n return targetListeners;\n };\n\n /** Waits for an event to be published.\n * Returns a promise that resolves when the event is published.\n * @param event The event to wait for.\n * @param options.timeout The timeout in milliseconds. Defaults to 10000.\n * @param options.filter A function that returns true if the event should be accepted.\n * @returns A promise that resolves when the event is published with the data passed to the listener. */\n const waitFor = <K extends SubscriptionKey<T>>(\n event: K,\n options: { timeout?: number; filter?: (data: SubscriptionListenerPayloads<T>[K]) => boolean } = {}\n ) => {\n const { timeout = 5000, filter } = options;\n return new Promise<SubscriptionListenerPayloads<T>[K]>((resolve, reject) => {\n let timeoutId: ReturnType<typeof setTimeout>;\n const listener = (data: unknown) => {\n if (filter && !filter(data as SubscriptionListenerPayloads<T>[K])) return;\n unsubscribe(event, listener as unknown as SubscriptionListeners<T>[K]);\n resolve(data as SubscriptionListenerPayloads<T>[K]);\n clearTimeout(timeoutId);\n };\n subscribe(event, listener as unknown as SubscriptionListeners<T>[K]);\n if (timeout) {\n timeoutId = setTimeout(() => {\n unsubscribe(event, listener as unknown as SubscriptionListeners<T>[K]);\n reject(new RuntimeError(`Timeout waiting for event: \"${event}\"`));\n }, timeout);\n }\n });\n };\n\n return {\n publish,\n subscribe,\n subscribeOnce,\n unsubscribe,\n getEventNames,\n getListeners,\n waitFor,\n };\n}\n\nexport type InferBusType<T extends Schema> = ReturnType<typeof create<T>>;\nexport type InferPublishHandler<T extends Schema> = InferBusType<T>[\"publish\"];\nexport type InferSubscribeHandler<T extends Schema> = InferBusType<T>[\"subscribe\"];\nexport type InferSubscriptionKey<T extends Schema> = SubscriptionKey<T>;\nexport type InferPublishKey<T extends Schema> = PublishKey<T>;\n\nexport { create };\n", "export const errorPrefix = \"[zodbus] \";\n", "import { errorPrefix } from \"./constants\";\n\nexport class ValidationError extends Error {\n constructor(message: string) {\n super(`${errorPrefix}${message}`);\n this.name = \"ValidationError\";\n }\n}\n\nexport class RuntimeError extends Error {\n constructor(message: string) {\n super(`${errorPrefix}${message}`);\n this.name = \"RuntimeError\";\n }\n}\n", "import { ZodType } from \"zod\";\nimport { Schema } from \"../types\";\n\nexport const hasWildcard = (path: string): boolean => path.split(\".\").includes(\"*\");\n\nexport const getPublishPaths = (schema: Schema | ZodType, prefix = \"\"): string[] => {\n const paths: string[] = [];\n for (const [k, v] of Object.entries(schema)) {\n const newPrefix = prefix ? `${prefix}.${k}` : k;\n if (typeof v.parse === \"function\") {\n paths.push(newPrefix);\n } else {\n paths.push(...getPublishPaths(v, newPrefix));\n }\n }\n return paths;\n};\n\nexport const getSubscribePaths = (schema: Schema | ZodType, prefix = \"\"): string[] => {\n const paths: string[] = [];\n for (const [k, v] of Object.entries(schema)) {\n const newPrefix = prefix ? `${prefix}.${k}` : k;\n const newWildcardPrefix = prefix ? `${prefix}.*` : \"*\";\n if (typeof v.parse === \"function\") {\n paths.push(newPrefix, newWildcardPrefix);\n } else {\n paths.push(...getSubscribePaths(v, newPrefix), ...getSubscribePaths(v, newWildcardPrefix));\n }\n }\n return paths;\n};\n\nexport const getSubPubPathMap = (schema: Schema | ZodType): Record<string, string[]> => {\n const map: Record<string, string[]> = {};\n const publishPaths = getPublishPaths(schema);\n const subscribePaths = getSubscribePaths(schema);\n\n for (const subscribePath of subscribePaths) {\n const subscribeParts = subscribePath.split(\".\");\n map[subscribePath] = publishPaths.filter((publishPath) => {\n const publishParts = publishPath.split(\".\");\n if (publishParts.length !== subscribeParts.length) return false;\n for (const [i, publishPart] of publishParts.entries()) {\n if (subscribeParts[i] !== \"*\" && publishPart !== subscribeParts[i]) return false;\n }\n return true;\n });\n }\n\n return map;\n};\n"],
5
- "mappings": "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",
6
- "names": ["ZodType", "errorPrefix", "ValidationError", "message", "errorPrefix", "RuntimeError", "getPublishPaths", "schema", "prefix", "paths", "k", "v", "newPrefix", "getSubscribePaths", "newWildcardPrefix", "getSubPubPathMap", "map", "publishPaths", "subscribePaths", "subscribePath", "subscribeParts", "publishPath", "publishParts", "i", "publishPart", "create", "schema", "validate", "subPubPathMap", "getSubPubPathMap", "eventNames", "listeners", "getListenerSetsForSubscriptionKey", "event", "listenerSets", "publishPaths", "ValidationError", "publishPath", "listenerSet", "validatePayloadOrPanic", "data", "pathFragments", "currentSchema", "fragment", "ZodType", "unsubscribe", "listener", "eventListeners", "subscribe", "_a", "wrappedListener", "eventName", "targetListeners", "targetListenerSets", "options", "timeout", "filter", "resolve", "reject", "timeoutId", "RuntimeError"]
7
- }
package/dist/types.d.ts DELETED
@@ -1,32 +0,0 @@
1
- import { ZodType } from "zod";
2
- export type IsZodType<T> = T extends ZodType ? true : false;
3
- export type IsSchema<T> = T extends Schema ? true : false;
4
- export type Listener<K extends string = string, V = unknown> = (data: V, event: K) => void;
5
- export interface Schema {
6
- [key: string]: ZodType | Schema;
7
- }
8
- export type HasWildcard<T extends string> = T extends `*` | `*.${string}` | `${string}.*.${string}` | `${string}.*` ? true : false;
9
- export type InferSubscriptionListener<T, K extends string, P extends string = ""> = K extends `${infer Left}.${infer Right}` ? HasWildcard<Left & string> extends true ? Listener : Left extends keyof T ? T[Left] extends Schema ? InferSubscriptionListener<T[Left], Right, `${P}${Left}.`> : never : never : K extends keyof T ? T[K] extends ZodType<infer O, any, any> ? Listener<`${P}${K}`, O> : never : HasWildcard<K & string> extends true ? Listener : never;
10
- export type InferSubscriptionListenerPayload<T, K extends string, P extends string = ""> = K extends `${infer Left}.${infer Right}` ? HasWildcard<Left & string> extends true ? unknown : Left extends keyof T ? T[Left] extends Schema ? InferSubscriptionListenerPayload<T[Left], Right, `${P}${Left}.`> : never : never : K extends keyof T ? T[K] extends ZodType<infer O, any, any> ? O : never : HasWildcard<K & string> extends true ? unknown : never;
11
- export type ZodTypePath<T, K extends keyof T = keyof T> = K extends string ? IsZodType<T[K]> extends true ? K : never : never;
12
- export type NamespacePath<T, K extends keyof T = keyof T> = K extends string ? IsSchema<T[K]> extends true ? `${K}.${SchemaPath<T[K], keyof T[K]>}` : never : never;
13
- export type SchemaPath<T, K extends keyof T = keyof T> = ZodTypePath<T, K> | NamespacePath<T, K>;
14
- export type WildcardPath<T extends string> = T extends `${infer L}.${infer R}` ? `${L}.${WildcardPath<R>}` | `${L}.*` | `*.${WildcardPath<R>}` : T | "*";
15
- export type ExcludeDirectlyNestedKeys<T extends string> = T extends `${infer L}.${infer R}` ? L | ExcludeDirectlyNestedKeys<R> : never;
16
- export type Subscription<T extends Schema> = {
17
- [K in Exclude<WildcardPath<SchemaPath<T>>, ExcludeDirectlyNestedKeys<SchemaPath<T>>> | "*"]: {
18
- listener: InferSubscriptionListener<T, K>;
19
- payload: InferSubscriptionListenerPayload<T, K>;
20
- };
21
- };
22
- export type Subscriptions<T extends Schema> = {
23
- [K in keyof Subscription<T>]: Subscription<T>[K];
24
- };
25
- export type SubscriptionListeners<T extends Schema> = {
26
- [K in keyof Subscriptions<T>]: Subscriptions<T>[K]["listener"];
27
- };
28
- export type SubscriptionListenerPayloads<T extends Schema> = {
29
- [K in keyof Subscriptions<T>]: Subscriptions<T>[K]["payload"];
30
- };
31
- export type SubscriptionKey<T extends Schema> = Extract<keyof SubscriptionListeners<T>, string>;
32
- export type PublishKey<T extends Schema> = Extract<SubscriptionKey<T>, SchemaPath<T>>;
@@ -1,6 +0,0 @@
1
- import { ZodType } from "zod";
2
- import { Schema } from "../types";
3
- export declare const hasWildcard: (path: string) => boolean;
4
- export declare const getPublishPaths: (schema: Schema | ZodType, prefix?: string) => string[];
5
- export declare const getSubscribePaths: (schema: Schema | ZodType, prefix?: string) => string[];
6
- export declare const getSubPubPathMap: (schema: Schema | ZodType) => Record<string, string[]>;