Remove native classes from TS runtime

This commit is contained in:
elpresidank 2026-06-01 20:26:47 -05:00
parent 952daf325d
commit dca2786828
79 changed files with 7622 additions and 6703 deletions

View file

@ -57,183 +57,30 @@ export interface FlowRequestor<TReq, TRes> {
readonly stop: () => Promise<void>;
}
export class Flow<Requirements = never> {
private producers = new Map<string, EffectProducer<unknown>>();
private consumers = new Map<string, EffectConsumer>();
private requestors = new Map<string, EffectRequestResponse<unknown, unknown>>();
private parameters = new Map<string, unknown>();
private compatibilityScope: Scope.Closeable | null = null;
public readonly name: string;
public readonly processorId: string;
private readonly pubsub: PubSubBackend;
private readonly definition: FlowDefinition;
private readonly specifications: ReadonlyArray<Spec<Requirements>>;
export function makeFlow<Requirements = never>(
name: string,
processorId: string,
pubsub: PubSubBackend,
definition: FlowDefinition,
specifications: ReadonlyArray<Spec<Requirements>>,
) {
const producers = new Map<string, EffectProducer<unknown>>();
const consumers = new Map<string, EffectConsumer>();
const requestors = new Map<string, EffectRequestResponse<unknown, unknown>>();
const parameters = new Map<string, unknown>();
let compatibilityScope: Scope.Closeable | null = null;
constructor(
name: string,
processorId: string,
pubsub: PubSubBackend,
definition: FlowDefinition,
specifications: ReadonlyArray<Spec<Requirements>>,
) {
this.name = name;
this.processorId = processorId;
this.pubsub = pubsub;
this.definition = definition;
this.specifications = specifications;
}
startEffect(): Effect.Effect<void, PubSubError, SpecRuntimeRequirements | Requirements> {
const flow = this;
return Effect.gen(function* () {
for (const spec of flow.specifications) {
yield* spec.addEffect(flow, flow.definition);
}
});
}
async start(): Promise<void> {
if (this.compatibilityScope !== null) {
await this.stop();
const ensureCompatibilityScope = async (): Promise<Scope.Closeable> => {
if (compatibilityScope !== null) {
return compatibilityScope;
}
await this.runInCompatibilityScope(
this.startEffect() as Effect.Effect<void, PubSubError, SpecRuntimeRequirements>,
this.pubsub,
);
}
compatibilityScope = await Effect.runPromise(Scope.make());
return compatibilityScope;
};
async stop(): Promise<void> {
const scope = this.compatibilityScope;
this.compatibilityScope = null;
if (scope !== null) {
await Effect.runPromise(Scope.close(scope, Exit.void));
}
this.clearResources();
}
async runInCompatibilityScope<A, E>(
effect: Effect.Effect<A, E, SpecRuntimeRequirements>,
pubsub: PubSubBackend,
): Promise<A> {
const scope = await this.ensureCompatibilityScope();
const pubsubService = makePubSubService(pubsub);
const messagingConfig = await Effect.runPromise(loadMessagingRuntimeConfig());
return await Effect.runPromise(
effect.pipe(
Effect.provideService(ProducerFactory, ProducerFactory.of(makeProducerFactoryService(pubsubService))),
Effect.provideService(ConsumerFactory, ConsumerFactory.of(makeConsumerFactoryService(pubsubService, messagingConfig))),
Effect.provideService(
RequestResponseFactory,
RequestResponseFactory.of(makeRequestResponseFactoryService(pubsubService, messagingConfig)),
),
Scope.provide(scope),
),
);
}
clearResources(): void {
this.producers.clear();
this.consumers.clear();
this.requestors.clear();
this.parameters.clear();
}
registerProducer(name: string, producer: EffectProducer<unknown>): void {
this.producers.set(name, producer);
}
registerConsumer(name: string, consumer: EffectConsumer): void {
this.consumers.set(name, consumer);
}
registerRequestor(name: string, rr: EffectRequestResponse<unknown, unknown>): void {
this.requestors.set(name, rr);
}
setParameter(name: string, value: unknown): void {
this.parameters.set(name, value);
}
producerEffect<T>(name: string): Effect.Effect<EffectProducer<T>, FlowResourceNotFoundError> {
const p = this.producers.get(name);
return p === undefined
? Effect.fail(flowResourceNotFoundError(this.name, "producer", name))
: Effect.succeed(p as EffectProducer<T>);
}
consumerEffect(name: string): Effect.Effect<EffectConsumer, FlowResourceNotFoundError> {
const c = this.consumers.get(name);
return c === undefined
? Effect.fail(flowResourceNotFoundError(this.name, "consumer", name))
: Effect.succeed(c);
}
requestorEffect<TReq, TRes>(
name: string,
): Effect.Effect<EffectRequestResponse<TReq, TRes>, FlowResourceNotFoundError> {
const rr = this.requestors.get(name);
return rr === undefined
? Effect.fail(flowResourceNotFoundError(this.name, "requestor", name))
: Effect.succeed(rr as EffectRequestResponse<TReq, TRes>);
}
parameterEffect<T>(name: string): Effect.Effect<T, FlowResourceNotFoundError> {
const v = this.parameters.get(name);
return v === undefined
? Effect.fail(flowResourceNotFoundError(this.name, "parameter", name))
: Effect.succeed(v as T);
}
producer<T>(name: string): FlowProducer<T> {
const p = this.producers.get(name);
if (p === undefined) throw flowResourceNotFoundError(this.name, "producer", name);
return {
send: (id, message) => Effect.runPromise((p as EffectProducer<T>).send(id, message)),
flush: () => Effect.runPromise(p.flush),
stop: () => Effect.runPromise(p.flush.pipe(Effect.flatMap(() => p.close))),
};
}
consumer(name: string): FlowConsumer {
const c = this.consumers.get(name);
if (c === undefined) throw flowResourceNotFoundError(this.name, "consumer", name);
return {
stop: () => Effect.runPromise(c.stop),
};
}
requestor<TReq, TRes>(name: string): FlowRequestor<TReq, TRes> {
const rr = this.requestors.get(name);
if (rr === undefined) throw flowResourceNotFoundError(this.name, "requestor", name);
return {
request: (request, options) =>
Effect.runPromise(
(rr as EffectRequestResponse<TReq, TRes>).request(
request,
this.toEffectRequestOptions(options),
),
),
stop: () => Effect.runPromise(rr.stop),
};
}
parameter<T>(name: string): T {
const v = this.parameters.get(name);
if (v === undefined) throw flowResourceNotFoundError(this.name, "parameter", name);
return v as T;
}
private async ensureCompatibilityScope(): Promise<Scope.Closeable> {
if (this.compatibilityScope !== null) {
return this.compatibilityScope;
}
this.compatibilityScope = await Effect.runPromise(Scope.make());
return this.compatibilityScope;
}
private toEffectRequestOptions<TRes>(
const toEffectRequestOptions = <TRes>(
options: FlowRequestOptions<TRes> | undefined,
): EffectRequestOptions<TRes> | undefined {
): EffectRequestOptions<TRes> | undefined => {
if (options === undefined) {
return undefined;
}
@ -246,5 +93,153 @@ export class Flow<Requirements = never> {
recipient: (response: TRes) => Effect.promise(() => recipient(response)),
}),
};
}
};
const flow = {
name,
processorId,
startEffect(): Effect.Effect<void, PubSubError, SpecRuntimeRequirements | Requirements> {
return Effect.gen(function* () {
for (const spec of specifications) {
yield* spec.addEffect(flow, definition);
}
});
},
async start(): Promise<void> {
if (compatibilityScope !== null) {
await flow.stop();
}
await flow.runInCompatibilityScope(
flow.startEffect() as Effect.Effect<void, PubSubError, SpecRuntimeRequirements>,
pubsub,
);
},
async stop(): Promise<void> {
const scope = compatibilityScope;
compatibilityScope = null;
if (scope !== null) {
await Effect.runPromise(Scope.close(scope, Exit.void));
}
flow.clearResources();
},
async runInCompatibilityScope<A, E>(
effect: Effect.Effect<A, E, SpecRuntimeRequirements>,
runtimePubsub: PubSubBackend,
): Promise<A> {
const scope = await ensureCompatibilityScope();
const pubsubService = makePubSubService(runtimePubsub);
const messagingConfig = await Effect.runPromise(loadMessagingRuntimeConfig());
return await Effect.runPromise(
effect.pipe(
Effect.provideService(ProducerFactory, ProducerFactory.of(makeProducerFactoryService(pubsubService))),
Effect.provideService(ConsumerFactory, ConsumerFactory.of(makeConsumerFactoryService(pubsubService, messagingConfig))),
Effect.provideService(
RequestResponseFactory,
RequestResponseFactory.of(makeRequestResponseFactoryService(pubsubService, messagingConfig)),
),
Scope.provide(scope),
),
);
},
clearResources(): void {
producers.clear();
consumers.clear();
requestors.clear();
parameters.clear();
},
registerProducer(registerName: string, producer: EffectProducer<unknown>): void {
producers.set(registerName, producer);
},
registerConsumer(registerName: string, consumer: EffectConsumer): void {
consumers.set(registerName, consumer);
},
registerRequestor(registerName: string, rr: EffectRequestResponse<unknown, unknown>): void {
requestors.set(registerName, rr);
},
setParameter(parameterName: string, value: unknown): void {
parameters.set(parameterName, value);
},
producerEffect<T>(producerName: string): Effect.Effect<EffectProducer<T>, FlowResourceNotFoundError> {
const p = producers.get(producerName);
return p === undefined
? Effect.fail(flowResourceNotFoundError(name, "producer", producerName))
: Effect.succeed(p as EffectProducer<T>);
},
consumerEffect(consumerName: string): Effect.Effect<EffectConsumer, FlowResourceNotFoundError> {
const c = consumers.get(consumerName);
return c === undefined
? Effect.fail(flowResourceNotFoundError(name, "consumer", consumerName))
: Effect.succeed(c);
},
requestorEffect<TReq, TRes>(
requestorName: string,
): Effect.Effect<EffectRequestResponse<TReq, TRes>, FlowResourceNotFoundError> {
const rr = requestors.get(requestorName);
return rr === undefined
? Effect.fail(flowResourceNotFoundError(name, "requestor", requestorName))
: Effect.succeed(rr as EffectRequestResponse<TReq, TRes>);
},
parameterEffect<T>(parameterName: string): Effect.Effect<T, FlowResourceNotFoundError> {
const v = parameters.get(parameterName);
return v === undefined
? Effect.fail(flowResourceNotFoundError(name, "parameter", parameterName))
: Effect.succeed(v as T);
},
producer<T>(producerName: string): FlowProducer<T> {
const p = producers.get(producerName);
if (p === undefined) throw flowResourceNotFoundError(name, "producer", producerName);
return {
send: (id, message) => Effect.runPromise((p as EffectProducer<T>).send(id, message)),
flush: () => Effect.runPromise(p.flush),
stop: () => Effect.runPromise(p.flush.pipe(Effect.flatMap(() => p.close))),
};
},
consumer(consumerName: string): FlowConsumer {
const c = consumers.get(consumerName);
if (c === undefined) throw flowResourceNotFoundError(name, "consumer", consumerName);
return {
stop: () => Effect.runPromise(c.stop),
};
},
requestor<TReq, TRes>(requestorName: string): FlowRequestor<TReq, TRes> {
const rr = requestors.get(requestorName);
if (rr === undefined) throw flowResourceNotFoundError(name, "requestor", requestorName);
return {
request: (request, options) =>
Effect.runPromise(
(rr as EffectRequestResponse<TReq, TRes>).request(
request,
toEffectRequestOptions(options),
),
),
stop: () => Effect.runPromise(rr.stop),
};
},
parameter<T>(parameterName: string): T {
const v = parameters.get(parameterName);
if (v === undefined) throw flowResourceNotFoundError(name, "parameter", parameterName);
return v as T;
},
};
return flow;
}
export type Flow<Requirements = never> = ReturnType<typeof makeFlow<Requirements>>;
export const Flow = makeFlow as unknown as {
new <Requirements = never>(
name: string,
processorId: string,
pubsub: PubSubBackend,
definition: FlowDefinition,
specifications: ReadonlyArray<Spec<Requirements>>,
): Flow<Requirements>;
<Requirements = never>(
name: string,
processorId: string,
pubsub: PubSubBackend,
definition: FlowDefinition,
specifications: ReadonlyArray<Spec<Requirements>>,
): Flow<Requirements>;
};