Set up project structure, source files, and architecture docs

- Copy core source from alkhub_ts/packages/core/pubsub/ with import path fixups
  (typed_event_target.ts → types.ts, .ts → .js extensions)
- Make PubSubPublishArgsByKey exported (was private type, needed by barrel)
- Add package.json with sub-path exports and optional peer deps (ioredis)
- Add tsup.config.ts with multi-entry + splitting for tree-shaking
- Add tsconfig.json, vitest.config.ts, .gitignore
- Add AGENTS.md with project conventions and adapter checklist
- Add architecture docs following taskgraph/alkhub pattern:
  docs/architecture/README.md, api-surface.md, event-targets.md,
  iroh-transport.md, build-distribution.md
- Add ADRs: 001-graphql-yoga-fork, 002-tree-shake-pattern
- Copy migration research doc to docs/research/migration.md
- Dual-license MIT OR Apache-2.0 (matching taskgraph)
This commit is contained in:
2026-04-30 10:20:41 +00:00
parent a5d128629e
commit 8c025c3433
23 changed files with 4747 additions and 0 deletions

108
src/create_pubsub.ts Normal file
View File

@@ -0,0 +1,108 @@
/*
* Adapted from @graphql-yoga/subscription
* Original source: https://github.com/graphql-hive/graphql-yoga
* License: MIT
*
* Copyright (c) 2024 The Guild, GraphQL Yoga Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
import { Repeater } from "@repeaterjs/repeater";
import type { TypedEventTarget, TypedEvent } from "./types.js";
export type PubSubPublishArgsByKey = {
[key: string]: [] | [unknown] | [number | string, unknown];
};
export type PubSubEvent<
TPubSubPublishArgsByKey extends PubSubPublishArgsByKey,
TKey extends Extract<keyof TPubSubPublishArgsByKey, string>,
> = TypedEvent<
TKey,
TPubSubPublishArgsByKey[TKey][1] extends undefined
? TPubSubPublishArgsByKey[TKey][0]
: TPubSubPublishArgsByKey[TKey][1]
>;
export type PubSubEventTarget<TPubSubPublishArgsByKey extends PubSubPublishArgsByKey> =
TypedEventTarget<
PubSubEvent<TPubSubPublishArgsByKey, Extract<keyof TPubSubPublishArgsByKey, string>>
>;
export type PubSubConfig<TPubSubPublishArgsByKey extends PubSubPublishArgsByKey> = {
eventTarget?: PubSubEventTarget<TPubSubPublishArgsByKey>;
};
export type PubSub<TPubSubPublishArgsByKey extends PubSubPublishArgsByKey> = {
publish<TKey extends Extract<keyof TPubSubPublishArgsByKey, string>>(
routingKey: TKey,
...args: TPubSubPublishArgsByKey[TKey]
): void;
subscribe<TKey extends Extract<keyof TPubSubPublishArgsByKey, string>>(
...[routingKey, id]: TPubSubPublishArgsByKey[TKey][1] extends undefined
? [TKey]
: [TKey, TPubSubPublishArgsByKey[TKey][0]]
): Repeater<unknown>;
};
export function createPubSub<TPubSubPublishArgsByKey extends PubSubPublishArgsByKey>(
config?: PubSubConfig<TPubSubPublishArgsByKey>,
): PubSub<TPubSubPublishArgsByKey> {
const target =
config?.eventTarget ?? (new EventTarget() as PubSubEventTarget<TPubSubPublishArgsByKey>);
return {
publish<TKey extends Extract<keyof TPubSubPublishArgsByKey, string>>(
routingKey: TKey,
...args: TPubSubPublishArgsByKey[TKey]
) {
const payload = args[1] ?? args[0] ?? null;
const topic = args[1] === undefined ? routingKey : `${routingKey}:${args[0] as number}`;
const event = new CustomEvent(topic, { detail: payload }) as PubSubEvent<
TPubSubPublishArgsByKey,
TKey
>;
target.dispatchEvent(event);
},
subscribe<TKey extends Extract<keyof TPubSubPublishArgsByKey, string>>(
...[routingKey, id]: TPubSubPublishArgsByKey[TKey][1] extends undefined
? [TKey]
: [TKey, TPubSubPublishArgsByKey[TKey][0]]
): Repeater<unknown> {
const topic: TKey = (id === undefined ? routingKey : `${routingKey}:${id as number}`) as TKey;
return new Repeater(function subscriptionRepeater(
next: (value: unknown) => Promise<void>,
stop: Promise<void>,
) {
function pubsubEventListener(event: CustomEvent) {
next(event.detail);
}
stop.then(function subscriptionRepeaterStopHandler() {
target.removeEventListener(topic, pubsubEventListener as EventListener);
});
target.addEventListener(topic, pubsubEventListener as EventListener, undefined);
});
},
};
}