EdgeAIG's picture
download
raw
6.25 kB
/**
* Durable workflow queues delegate work to persisted background workers and
* resume the waiting workflow with the worker result.
*
* A workflow calls `process` to encode a payload, offer it to a named
* `PersistedQueue`, attach a `DurableDeferred` token, and suspend. A worker
* created with `makeWorker` or `worker` takes the item, runs the handler, and
* records the handler's `Exit` through that token so the original workflow can
* continue with the typed success or error.
*
* @since 4.0.0
*/
import * as Effect from "../../Effect.js";
import * as Layer from "../../Layer.js";
import * as Schedule from "../../Schedule.js";
import * as Schema from "../../Schema.js";
import * as Tracer from "../../Tracer.js";
import * as PersistedQueue from "../persistence/PersistedQueue.js";
import * as Activity from "./Activity.js";
import * as DurableDeferred from "./DurableDeferred.js";
/**
* Runtime identifier attached to `DurableQueue` values.
*
* @category type IDs
* @since 4.0.0
*/
export const TypeId = "~effect/workflow/DurableQueue";
/**
* Creates a `DurableQueue` that waits for persisted items to finish processing
* using a `DurableDeferred`.
*
* **Example** (Defining a durable queue with workers)
*
* ```ts
* import { Effect, Schema } from "effect"
* import { DurableQueue, Workflow } from "effect/unstable/workflow"
*
* // Define a DurableQueue that can be used to derive workers and offer items for
* // processing.
* const ApiQueue = DurableQueue.make({
* name: "ApiQueue",
* payload: {
* id: Schema.String
* },
* success: Schema.Void,
* error: Schema.Never,
* idempotencyKey(payload) {
* return payload.id
* }
* })
*
* const MyWorkflow = Workflow.make("MyWorkflow", {
* payload: {
* id: Schema.String
* },
* idempotencyKey: ({ id }) => id
* })
*
* const MyWorkflowLayer = MyWorkflow.toLayer(
* Effect.fnUntraced(function*() {
* // Add an item to the DurableQueue defined above.
* //
* // When the worker has finished processing the item, the workflow will
* // resume.
* //
* yield* DurableQueue.process(ApiQueue, { id: "api-call-1" })
*
* yield* Effect.log("Workflow succeeded!")
* })
* )
*
* // Define a worker layer that can process items from the DurableQueue.
* const ApiWorker = DurableQueue.worker(
* ApiQueue,
* Effect.fnUntraced(function*({ id }) {
* yield* Effect.log(`Worker processing API call with id: ${id}`)
* }),
* { concurrency: 5 } // Process up to 5 items concurrently
* )
* ```
*
* @category constructors
* @since 4.0.0
*/
export const make = options => ({
[TypeId]: TypeId,
name: options.name,
payloadSchema: Schema.isSchema(options.payload) ? options.payload : Schema.Struct(options.payload),
idempotencyKey: options.idempotencyKey,
deferred: DurableDeferred.make(`DurableQueue/${options.name}`, {
success: options.success,
error: options.error
})
});
const queueSchemas = /*#__PURE__*/new WeakMap();
const getQueueSchema = payload => {
let schema = queueSchemas.get(payload);
if (!schema) {
schema = Schema.Struct({
token: DurableDeferred.Token,
traceId: Schema.String,
spanId: Schema.String,
sampled: Schema.Boolean,
payload
});
queueSchemas.set(payload, schema);
}
return schema;
};
/**
* Adds an item to the queue and wait for a worker to process it.
*
* @category Processing
* @since 4.0.0
*/
export const process = /*#__PURE__*/Effect.fnUntraced(function* (self, fields, options) {
const payload = self.payloadSchema.make(fields);
const queueName = `DurableQueue/${self.name}`;
const queue = yield* PersistedQueue.make({
name: queueName,
schema: getQueueSchema(self.payloadSchema)
});
const id = yield* Activity.idempotencyKey(`${queueName}/${self.idempotencyKey(payload)}`);
const deferred = DurableDeferred.make(`${self.deferred.name}/${id}`, {
success: self.deferred.successSchema,
error: self.deferred.errorSchema
});
const token = yield* DurableDeferred.token(deferred);
yield* Effect.useSpan(`DurableQueue/${self.name}/process`, {
attributes: {
id
}
}, span => queue.offer({
token,
payload,
traceId: span.traceId,
spanId: span.spanId,
sampled: span.sampled
}, {
id
}).pipe(Effect.tapCause(Effect.logWarning), Effect.catchTag("SchemaError", Effect.die), Effect.retry(options?.retrySchedule ?? defaultRetrySchedule), Effect.orDie, Effect.annotateLogs({
package: "effect",
module: "DurableQueue",
fiber: "process",
queueName: self.name
})));
return yield* DurableDeferred.await(deferred);
});
const defaultRetrySchedule = /*#__PURE__*/Schedule.exponential(500, 1.5).pipe(/*#__PURE__*/Schedule.either(/*#__PURE__*/Schedule.spaced("1 minute")));
/**
* Create a worker effect that processes items from the durable queue.
*
* @category Worker
* @since 4.0.0
*/
export const makeWorker = /*#__PURE__*/Effect.fnUntraced(function* (self, f, options) {
const queue = yield* PersistedQueue.make({
name: `DurableQueue/${self.name}`,
schema: getQueueSchema(self.payloadSchema)
});
const concurrency = options?.concurrency ?? 1;
const worker = queue.take(item_ => {
const item = item_;
return Effect.withSpan(f(item.payload).pipe(Effect.exit, Effect.flatMap(exit => DurableDeferred.done(self.deferred, {
token: item.token,
exit
})), Effect.asVoid), `DurableQueue/${self.name}/worker`, {
captureStackTrace: false,
parent: Tracer.externalSpan({
traceId: item.traceId,
spanId: item.spanId,
sampled: item.sampled
})
});
}).pipe(Effect.catchCause(Effect.logWarning), Effect.forever, Effect.annotateLogs({
package: "effect",
module: "DurableQueue",
fiber: "worker",
queueName: self.name
}));
yield* Effect.replicateEffect(worker, concurrency, {
concurrency,
discard: true
});
return yield* Effect.never;
});
/**
* Create a layer that runs workers for the durable queue.
*
* @category Worker
* @since 4.0.0
*/
export const worker = (self, f, options) => Layer.effectDiscard(Effect.forkScoped(makeWorker(self, f, options)));
//# sourceMappingURL=DurableQueue.js.map

Xet Storage Details

Size:
6.25 kB
·
Xet hash:
66a20c5626800999d42c03caea6959f56911aceadff946fd625627cec6a8b145

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.