@quark-fw/core 0.0.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,4 @@
1
+ import { EventMap, TypedEventEmitter } from "./types.js";
2
+ export default function createEventIterator<Events extends EventMap, EventName extends keyof Events>(ee: TypedEventEmitter<Events>, eventName: EventName, signal: AbortSignal): {
3
+ [Symbol.asyncIterator](): AsyncGenerator<Parameters<Events[EventName]>[0], void, unknown>;
4
+ };
@@ -0,0 +1,36 @@
1
+ export default function createEventIterator(ee, eventName, signal) {
2
+ return {
3
+ async *[Symbol.asyncIterator]() {
4
+ const queue = [];
5
+ let resolve;
6
+ const handler = (data) => {
7
+ queue.push(data);
8
+ if (resolve) {
9
+ resolve();
10
+ resolve = undefined;
11
+ }
12
+ };
13
+ ee.on(eventName, handler);
14
+ // будим ожидающий цикл, чтобы он увидел signal.aborted и завершился
15
+ signal.addEventListener("abort", () => {
16
+ if (resolve) {
17
+ resolve();
18
+ resolve = undefined;
19
+ }
20
+ }, { once: true });
21
+ try {
22
+ while (!signal.aborted) {
23
+ if (!queue.length) {
24
+ await new Promise((r) => (resolve = r));
25
+ }
26
+ while (queue.length) {
27
+ yield queue.shift();
28
+ }
29
+ }
30
+ }
31
+ finally {
32
+ ee.off(eventName, handler);
33
+ }
34
+ },
35
+ };
36
+ }
@@ -0,0 +1,4 @@
1
+ import { EventMapFromArgs, TypedEventEmitter } from "./types.js";
2
+ export declare function createTypedEventEmitter<T extends Record<string, unknown[]>>({ debug, }?: {
3
+ debug?: boolean;
4
+ }): TypedEventEmitter<EventMapFromArgs<T>>;
@@ -0,0 +1,40 @@
1
+ import { EventEmitter } from "node:events";
2
+ export function createTypedEventEmitter({ debug, } = {
3
+ debug: false,
4
+ }) {
5
+ const ee = new EventEmitter();
6
+ if (!debug)
7
+ return ee;
8
+ // Оборачиваем в Proxy для логирования
9
+ return new Proxy(ee, {
10
+ get(target, prop, receiver) {
11
+ const original = Reflect.get(target, prop, receiver);
12
+ // Логируем emit
13
+ if (prop === "emit") {
14
+ return (event, ...args) => {
15
+ console.log(`[emit] ${event}`, ...args);
16
+ return original.call(target, event, ...args);
17
+ };
18
+ }
19
+ // Логируем подписки
20
+ if (prop === "on" ||
21
+ prop === "addListener" ||
22
+ prop === "once" ||
23
+ prop === "prependListener" ||
24
+ prop === "prependOnceListener") {
25
+ return (event, listener) => {
26
+ console.log(`[on] ${event}`, listener.toString());
27
+ return original.call(target, event, listener);
28
+ };
29
+ }
30
+ // Логируем удаление подписок
31
+ if (prop === "off" || prop === "removeListener") {
32
+ return (event, listener) => {
33
+ console.log(`[off] ${event}`, listener.toString());
34
+ return original.call(target, event, listener);
35
+ };
36
+ }
37
+ return original;
38
+ },
39
+ });
40
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,34 @@
1
+ export type ArgsToFn<T> = T extends unknown[] ? (...args: T) => void : never;
2
+ /**
3
+ * Ограничение для карты событий.
4
+ *
5
+ * Здесь `any` обязателен: EventMapFromArgs строит обработчики с конкретными
6
+ * кортежами аргументов, и удовлетворить ограничение они могут только через
7
+ * двусторонне совместимый `any`. С `unknown[]` не проходит присваивание
8
+ * параметров, с `never[]` — компилятор не сводит обобщённый `T[K]`.
9
+ * Конкретные типы событий задаются в EventMapFromArgs, здесь их нет.
10
+ */
11
+ export type EventMap = {
12
+ [K in string]: (...args: any[]) => void;
13
+ };
14
+ export type EventMapFromArgs<T extends Record<string, unknown[]>> = {
15
+ [K in keyof T]: (...args: T[K]) => void;
16
+ };
17
+ export interface TypedEventEmitter<Events extends EventMap> {
18
+ addListener<E extends keyof Events>(event: E, listener: Events[E]): this;
19
+ on<E extends keyof Events>(event: E, listener: Events[E]): this;
20
+ once<E extends keyof Events>(event: E, listener: Events[E]): this;
21
+ prependListener<E extends keyof Events>(event: E, listener: Events[E]): this;
22
+ prependOnceListener<E extends keyof Events>(event: E, listener: Events[E]): this;
23
+ off<E extends keyof Events>(event: E, listener: Events[E]): this;
24
+ removeAllListeners<E extends keyof Events>(event?: E): this;
25
+ removeListener<E extends keyof Events>(event: E, listener: Events[E]): this;
26
+ emit<E extends keyof Events>(event: E, ...args: Parameters<Events[E]>): boolean;
27
+ eventNames(): (keyof Events | string | symbol)[];
28
+ rawListeners<E extends keyof Events>(event: E): Events[E][];
29
+ listeners<E extends keyof Events>(event: E): Events[E][];
30
+ listenerCount<E extends keyof Events>(event: E): number;
31
+ getMaxListeners(): number;
32
+ setMaxListeners(maxListeners: number): this;
33
+ }
34
+ export type PickEvent<Events extends EventMap, E extends keyof Events> = Events[E];
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,27 @@
1
+ import { TypedEventEmitter, EventMapFromArgs } from "./ee/types.js";
2
+ import { Atom } from "@quark-fw/atom";
3
+ import { QuarkPlugin } from "@quark-fw/plugin";
4
+
5
+ export {};
6
+
7
+ declare global {
8
+ interface Events {
9
+ "lifecycle:init": [string];
10
+ "plugin:init": [QuarkPlugin];
11
+ "atom:update": [{ key: string; value: unknown }];
12
+ }
13
+
14
+ type EventsMap = EventMapFromArgs<Events>;
15
+
16
+ type QuarkEE = TypedEventEmitter<EventsMap>;
17
+
18
+ interface Quark {
19
+ ee: Atom<QuarkEE>;
20
+ plugins: QuarkPlugin[];
21
+ registerPlugin: (plugin: QuarkPlugin) => Promise<void>;
22
+ ready: () => Promise<void>;
23
+ destroy: () => Promise<void>;
24
+ }
25
+
26
+ var quark: Quark;
27
+ }
@@ -0,0 +1,2 @@
1
+ /// <reference path="global.d.ts" />
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,71 @@
1
+ import atom from "@quark-fw/atom";
2
+ import { createTypedEventEmitter } from "./ee/createTypedEventEmitter.js";
3
+ // создаём emitter
4
+ const eeAtom = atom("ee", createTypedEventEmitter());
5
+ const loaded = new Set();
6
+ // Регистрации сериализуются: при top-level await соседние модули
7
+ // выполняются конкурентно, и без очереди проверка dependsOn гонится
8
+ // с ещё не завершённой регистрацией предыдущего плагина.
9
+ let registrationQueue = Promise.resolve();
10
+ const register = async (plugin) => {
11
+ if (loaded.has(plugin.name))
12
+ return;
13
+ // зависимости: должны быть зарегистрированы раньше
14
+ if (plugin.dependsOn) {
15
+ for (const dep of plugin.dependsOn) {
16
+ if (!loaded.has(dep))
17
+ throw new Error(`Plugin ${plugin.name} depends on plugin ${dep}, which is not registered yet — import it first`);
18
+ }
19
+ }
20
+ // Атомы. Имена приходят от плагина в рантайме, а их объявление живёт в
21
+ // аугментации интерфейса Quark — статически связать одно с другим нельзя.
22
+ if (plugin.atoms) {
23
+ const target = quark;
24
+ for (const [key, atom] of Object.entries(plugin.atoms)) {
25
+ target[key] = atom;
26
+ }
27
+ }
28
+ // методы
29
+ plugin.extend?.(quark);
30
+ // подписки на события
31
+ if (plugin.onEvent) {
32
+ for (const [event, handler] of Object.entries(plugin.onEvent)) {
33
+ // имя события — строка из объекта плагина, соответствие ключам
34
+ // EventsMap проверить статически здесь нельзя
35
+ quark.ee.value.on(event, handler);
36
+ }
37
+ }
38
+ // lifecycle init
39
+ await plugin.onInit?.(quark);
40
+ quark.ee.value.emit("plugin:init", plugin);
41
+ loaded.add(plugin.name);
42
+ quark.plugins.push(plugin);
43
+ await plugin.onReady?.(quark);
44
+ };
45
+ // создаём глобальный quark
46
+ global.quark =
47
+ global.quark ||
48
+ {
49
+ ee: eeAtom,
50
+ plugins: [],
51
+ registerPlugin: (plugin) => {
52
+ const task = registrationQueue.then(() => register(plugin));
53
+ // ошибка одной регистрации не должна блокировать очередь
54
+ registrationQueue = task.catch(() => { });
55
+ return task;
56
+ },
57
+ // дождаться завершения всех уже поставленных в очередь регистраций —
58
+ // нужно модулям приложения, выполняющимся конкурентно с плагинами
59
+ ready: () => registrationQueue,
60
+ destroy: async () => {
61
+ // в обратном порядке регистрации
62
+ for (const plugin of [...quark.plugins].reverse()) {
63
+ await plugin.onDestroy?.(quark);
64
+ }
65
+ quark.plugins.length = 0;
66
+ loaded.clear();
67
+ },
68
+ // Quark дополняется плагинами через declaration merging,
69
+ // поэтому базовый объект заведомо неполон
70
+ };
71
+ quark.ee.value.emit("lifecycle:init", "app started");
@@ -0,0 +1,3 @@
1
+ export declare const systemEventEmitter: import("./ee/types.js").TypedEventEmitter<import("./ee/types.js").EventMapFromArgs<{
2
+ asd: [boolean];
3
+ }>>;
@@ -0,0 +1,2 @@
1
+ import { createTypedEventEmitter } from "./ee/createTypedEventEmitter.js";
2
+ export const systemEventEmitter = createTypedEventEmitter();
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "@quark-fw/core",
3
+ "version": "0.0.1",
4
+ "description": "Core runtime of the Quark framework: global quark object, lifecycle and typed event emitter",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "default": "./dist/index.js"
12
+ },
13
+ "./package.json": "./package.json",
14
+ "./src/*": "./dist/*",
15
+ "./*": "./dist/*"
16
+ },
17
+ "files": ["dist"],
18
+ "scripts": {
19
+ "dev": "tsx watch src/index.ts",
20
+ "build": "tsc -p tsconfig.build.json && node ../../scripts/postbuild.mjs"
21
+ },
22
+ "keywords": ["quark"],
23
+ "author": "",
24
+ "license": "ISC",
25
+ "publishConfig": {
26
+ "access": "public"
27
+ },
28
+ "dependencies": {
29
+ "@quark-fw/atom": "0.0.1",
30
+ "@quark-fw/plugin": "0.0.1"
31
+ }
32
+ }