octaflow 0.24.1 → 0.26.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/README.md +64 -0
- package/dist/ai/providers/index.d.ts +63 -1
- package/dist/ai/providers/index.js +87 -1
- package/dist/drizzle/index.d.ts +173 -0
- package/dist/drizzle/index.js +199 -0
- package/dist/index.d.ts +1 -1
- package/dist/{observability-CR9dEdLW.d.ts → observability-3wVqt3tZ.d.ts} +1 -1
- package/dist/postgres/index.d.ts +1 -1
- package/package.json +26 -15
package/README.md
CHANGED
|
@@ -230,6 +230,9 @@ what the layers you import need:
|
|
|
230
230
|
# Postgres store / gate / event sink
|
|
231
231
|
pnpm add pg
|
|
232
232
|
|
|
233
|
+
# the same tables as Drizzle column sets, for a host that lets drizzle-kit own its migrations
|
|
234
|
+
pnpm add drizzle-orm
|
|
235
|
+
|
|
233
236
|
# pg-boss dispatcher, workers, cron scheduler
|
|
234
237
|
pnpm add pg-boss
|
|
235
238
|
|
|
@@ -240,6 +243,67 @@ pnpm add ai @ai-sdk/provider
|
|
|
240
243
|
> Pure in-memory usage (great for tests and single-process apps) needs **nothing** beyond
|
|
241
244
|
> `zod` — the engine, `defineStep`/`buildWorkflow`, and the in-memory store are all in the core.
|
|
242
245
|
|
|
246
|
+
### The Postgres tables
|
|
247
|
+
|
|
248
|
+
`octaflow/postgres` owns five tables and ships their DDL: `flowStoreDdl()` (`flow_workflow`,
|
|
249
|
+
`flow_workflow_step`), `flowEventDdl()` (`flow_step_event`) and `flowGateDdl()`
|
|
250
|
+
(`flow_rate_bucket`, `flow_step_lease`). Each is idempotent `CREATE TABLE IF NOT EXISTS`, takes
|
|
251
|
+
a `schema`, and is meant to be pasted into your own migration system; `applySchema(pool, ddl)`
|
|
252
|
+
is the dev and test shortcut.
|
|
253
|
+
|
|
254
|
+
#### The Drizzle columns
|
|
255
|
+
|
|
256
|
+
The rule for every table-owning capability on the platform: `./postgres` ships the store over
|
|
257
|
+
the structural executor plus its `xDdl()`, and `./drizzle` ships the same table as a spreadable
|
|
258
|
+
column set, so a host on Drizzle declares it in its own schema and drizzle-kit owns the
|
|
259
|
+
migration. Where the store's queries are one SQL statement by nature — every one of octaflow's
|
|
260
|
+
is — `./drizzle` ships the columns only, and the store stays the SQL one. So
|
|
261
|
+
`octaflow/drizzle` exports `flowWorkflowColumns`, `flowWorkflowStepColumns`,
|
|
262
|
+
`flowStepEventColumns`, `flowRateBucketColumns` and `flowStepLeaseColumns` (`drizzle-orm` an
|
|
263
|
+
optional peer) and nothing that runs a query. The foreign keys, the composite keys, the unique
|
|
264
|
+
constraint and the indexes are yours to declare on the `pgTable`; each column set's doc lists
|
|
265
|
+
the ones its DDL creates, and two of them the store relies on:
|
|
266
|
+
|
|
267
|
+
```ts
|
|
268
|
+
import { sql } from 'drizzle-orm';
|
|
269
|
+
import { foreignKey, index, pgTable, primaryKey, unique, uniqueIndex } from 'drizzle-orm/pg-core';
|
|
270
|
+
import { flowWorkflowColumns, flowWorkflowStepColumns, flowStepLeaseColumns } from 'octaflow/drizzle';
|
|
271
|
+
|
|
272
|
+
export const flowWorkflow = pgTable('flow_workflow', flowWorkflowColumns, (t) => [
|
|
273
|
+
index('flow_workflow_partition_status_idx').on(t.partitionKey, t.status),
|
|
274
|
+
index('flow_workflow_partition_type_idx').on(t.partitionKey, t.type),
|
|
275
|
+
index('flow_workflow_partition_entity_idx').on(t.partitionKey, t.entityRef),
|
|
276
|
+
index('flow_workflow_parent_idx').on(t.parentWorkflowId),
|
|
277
|
+
// The deadline sweep reads only live runs that have one.
|
|
278
|
+
index('flow_workflow_deadline_idx').on(t.deadlineAt).where(sql`deadline_at IS NOT NULL`),
|
|
279
|
+
// `start` with an idempotency key upserts against this index: without it a repeated start is a second run.
|
|
280
|
+
uniqueIndex('flow_workflow_idempotency_idx').on(t.partitionKey, t.idempotencyKey).where(sql`idempotency_key IS NOT NULL`),
|
|
281
|
+
]);
|
|
282
|
+
|
|
283
|
+
export const flowWorkflowStep = pgTable('flow_workflow_step', flowWorkflowStepColumns, (t) => [
|
|
284
|
+
foreignKey({ columns: [t.workflowId], foreignColumns: [flowWorkflow.id] }).onDelete('cascade'),
|
|
285
|
+
foreignKey({ columns: [t.parentStepId], foreignColumns: [t.id] }).onDelete('cascade'),
|
|
286
|
+
unique('flow_workflow_step_workflow_id_key_unique').on(t.workflowId, t.key),
|
|
287
|
+
index('flow_workflow_step_workflow_idx').on(t.workflowId),
|
|
288
|
+
index('flow_workflow_step_status_idx').on(t.workflowId, t.status),
|
|
289
|
+
index('flow_workflow_step_parent_idx').on(t.parentStepId),
|
|
290
|
+
]);
|
|
291
|
+
|
|
292
|
+
export const flowStepLease = pgTable('flow_step_lease', flowStepLeaseColumns, (t) => [
|
|
293
|
+
primaryKey({ columns: [t.partitionKey, t.stepType, t.stepId] }),
|
|
294
|
+
index('flow_step_lease_active_idx').on(t.partitionKey, t.stepType, t.expiresAt),
|
|
295
|
+
]);
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
The names are yours: the store's `ON CONFLICT` clauses target column lists, never a
|
|
299
|
+
constraint by name, so what must match is each index's columns and its partial `WHERE`,
|
|
300
|
+
not what you call it.
|
|
301
|
+
|
|
302
|
+
The store stays plain SQL, bound to the `schema` you hand `createPgWorkflowStore`,
|
|
303
|
+
`createPgStepGate` and `createPgEventSink` — and, for the gate, to the `tables` names — so
|
|
304
|
+
declare the tables under the same schema and names. A test in the package holds every column
|
|
305
|
+
set and its DDL equal, column for column: a column added to one side alone fails the build.
|
|
306
|
+
|
|
243
307
|
---
|
|
244
308
|
|
|
245
309
|
---
|
|
@@ -66,7 +66,7 @@ export interface LazyEmbeddingModelMeta {
|
|
|
66
66
|
* next call rather than cached.
|
|
67
67
|
*/
|
|
68
68
|
export declare function createLazyEmbeddingModel(meta: LazyEmbeddingModelMeta, load: () => Promise<EmbeddingModelV4>): EmbeddingModelV4;
|
|
69
|
-
export type AiProviderConfigErrorKey = 'ai_residency_undeclared' | 'ai_residency_invalid' | 'ai_base_url_invalid' | 'ai_model_required' | 'ai_embedding_dimensions_invalid';
|
|
69
|
+
export type AiProviderConfigErrorKey = 'ai_residency_undeclared' | 'ai_residency_invalid' | 'ai_base_url_invalid' | 'ai_model_required' | 'ai_model_undeclared' | 'ai_embedding_dimensions_invalid';
|
|
70
70
|
/**
|
|
71
71
|
* A provider configuration that cannot work. Thrown before any SDK is loaded
|
|
72
72
|
* or any request is made: it is a deployment mistake, found at wiring time.
|
|
@@ -143,4 +143,66 @@ export interface OpenAiCompatibleEmbeddingProvider {
|
|
|
143
143
|
export declare function createOpenAiCompatibleModelProvider(config: OpenAiCompatibleModelProviderConfig): Promise<OpenAiCompatibleModelProvider>;
|
|
144
144
|
/** An embedding model on an OpenAI-compatible endpoint, with its declared width. */
|
|
145
145
|
export declare function createOpenAiCompatibleEmbeddingProvider(config: OpenAiCompatibleEmbeddingProviderConfig): Promise<OpenAiCompatibleEmbeddingProvider>;
|
|
146
|
+
export interface OpenAiCompatibleGatewayModel {
|
|
147
|
+
/** Where the gateway sends this alias. Validated like an endpoint's residency. */
|
|
148
|
+
residency: AiEndpointResidency;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* `residency` is where the gateway itself runs, usually `{ region: 'self-hosted' }`.
|
|
152
|
+
* It decides whether plain http is acceptable for the hop to the gateway; where a
|
|
153
|
+
* call ends up is the alias's residency in `models`.
|
|
154
|
+
*/
|
|
155
|
+
export interface OpenAiCompatibleGatewayConfig extends OpenAiCompatibleEndpointConfig {
|
|
156
|
+
/** Every alias this deployment may call, keyed by the model name the gateway knows. */
|
|
157
|
+
models: Record<string, OpenAiCompatibleGatewayModel>;
|
|
158
|
+
/** As for {@link OpenAiCompatibleModelProviderConfig.structuredOutputs}. */
|
|
159
|
+
structuredOutputs?: boolean;
|
|
160
|
+
/** As for {@link OpenAiCompatibleModelProviderConfig.includeUsage}. */
|
|
161
|
+
includeUsage?: boolean;
|
|
162
|
+
}
|
|
163
|
+
export interface GatewayCallOptions {
|
|
164
|
+
/**
|
|
165
|
+
* Sent as the request body's `metadata`, which LiteLLM keeps in its spend log,
|
|
166
|
+
* so a call there can be matched to the host's own record: a workflow id, a
|
|
167
|
+
* scope key. It leaves the house as written and is never redacted: put ids
|
|
168
|
+
* here, never content. Added innermost, so a disclosure gate wrapped around the
|
|
169
|
+
* model neither digests nor redacts it.
|
|
170
|
+
*/
|
|
171
|
+
metadata?: Record<string, string>;
|
|
172
|
+
}
|
|
173
|
+
export interface OpenAiCompatibleGatewayProvider {
|
|
174
|
+
providerId: OpenAiCompatibleProviderId;
|
|
175
|
+
/** Where the gateway itself runs. */
|
|
176
|
+
residency: AiEndpointResidency;
|
|
177
|
+
/** Every declared alias with its validated residency: what a settings page or a subprocessor annex lists. */
|
|
178
|
+
models: Readonly<Record<string, AiEndpointResidency>>;
|
|
179
|
+
/** The residency of one alias. Throws `ai_model_undeclared` for an alias the declaration does not name. */
|
|
180
|
+
residencyOf(alias: string): AiEndpointResidency;
|
|
181
|
+
/** A Chat Completions model for a declared alias, wrapped for refusal detection. */
|
|
182
|
+
languageModel(alias: string, options?: GatewayCallOptions): LanguageModelV4;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* A model gateway — LiteLLM, or any router with the OpenAI-compatible API — as
|
|
186
|
+
* refusal-aware models, one per declared alias, each with the residency of
|
|
187
|
+
* where the gateway sends it.
|
|
188
|
+
*
|
|
189
|
+
* ```ts
|
|
190
|
+
* const gateway = await createOpenAiCompatibleGatewayProvider({
|
|
191
|
+
* baseUrl: 'http://litellm.ai.svc:4000/v1',
|
|
192
|
+
* apiKey: process.env.LITELLM_KEY,
|
|
193
|
+
* residency: { region: 'self-hosted' },
|
|
194
|
+
* models: {
|
|
195
|
+
* 'approval-confidential': { residency: { region: 'self-hosted' } },
|
|
196
|
+
* 'approval-public': { residency: { region: 'eu', legalName: 'Example Inference GmbH', country: 'DE' } },
|
|
197
|
+
* },
|
|
198
|
+
* });
|
|
199
|
+
* const model = gateway.languageModel('approval-confidential', { metadata: { workflowId } });
|
|
200
|
+
* ```
|
|
201
|
+
*
|
|
202
|
+
* The declaration is what the gateway is configured to do, stated by the
|
|
203
|
+
* deployment: this provider cannot see the gateway's routing, and a fallback
|
|
204
|
+
* the gateway adds on its own breaks the declaration silently. Keep the two
|
|
205
|
+
* in the same review.
|
|
206
|
+
*/
|
|
207
|
+
export declare function createOpenAiCompatibleGatewayProvider(config: OpenAiCompatibleGatewayConfig): Promise<OpenAiCompatibleGatewayProvider>;
|
|
146
208
|
//#endregion
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { r as createRefusalAwareModel } from "../../refusal-BsL2HPDb.js";
|
|
2
2
|
import { a as EMBEDDING_PROVIDERS, c as OPENAI_COMPATIBLE_PROVIDER_ID, d as resolveEndpointResidency, t as AI_PROVIDERS } from "../../provider-registry-BRwjC6_3.js";
|
|
3
|
+
import { wrapLanguageModel } from "ai";
|
|
3
4
|
//#region src/ai/providers/index.ts
|
|
4
5
|
const LLM_LOADERS = {
|
|
5
6
|
anthropic: async (apiKey, baseURL) => {
|
|
@@ -186,5 +187,90 @@ async function createOpenAiCompatibleEmbeddingProvider(config) {
|
|
|
186
187
|
embeddingModel: () => provider.embeddingModel(modelId)
|
|
187
188
|
};
|
|
188
189
|
}
|
|
190
|
+
/** Puts `metadata` into the request body under the provider's own options key, merged over any the call carries. */
|
|
191
|
+
function withGatewayMetadata(model, optionsKey, metadata) {
|
|
192
|
+
const fixed = { ...metadata };
|
|
193
|
+
return wrapLanguageModel({
|
|
194
|
+
model,
|
|
195
|
+
middleware: {
|
|
196
|
+
specificationVersion: "v4",
|
|
197
|
+
transformParams: async ({ params }) => {
|
|
198
|
+
const own = params.providerOptions?.[optionsKey] ?? {};
|
|
199
|
+
const existing = own.metadata !== null && typeof own.metadata === "object" && !Array.isArray(own.metadata) ? own.metadata : {};
|
|
200
|
+
const merged = {
|
|
201
|
+
...own,
|
|
202
|
+
metadata: {
|
|
203
|
+
...existing,
|
|
204
|
+
...fixed
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
return {
|
|
208
|
+
...params,
|
|
209
|
+
providerOptions: {
|
|
210
|
+
...params.providerOptions,
|
|
211
|
+
[optionsKey]: merged
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* A model gateway — LiteLLM, or any router with the OpenAI-compatible API — as
|
|
220
|
+
* refusal-aware models, one per declared alias, each with the residency of
|
|
221
|
+
* where the gateway sends it.
|
|
222
|
+
*
|
|
223
|
+
* ```ts
|
|
224
|
+
* const gateway = await createOpenAiCompatibleGatewayProvider({
|
|
225
|
+
* baseUrl: 'http://litellm.ai.svc:4000/v1',
|
|
226
|
+
* apiKey: process.env.LITELLM_KEY,
|
|
227
|
+
* residency: { region: 'self-hosted' },
|
|
228
|
+
* models: {
|
|
229
|
+
* 'approval-confidential': { residency: { region: 'self-hosted' } },
|
|
230
|
+
* 'approval-public': { residency: { region: 'eu', legalName: 'Example Inference GmbH', country: 'DE' } },
|
|
231
|
+
* },
|
|
232
|
+
* });
|
|
233
|
+
* const model = gateway.languageModel('approval-confidential', { metadata: { workflowId } });
|
|
234
|
+
* ```
|
|
235
|
+
*
|
|
236
|
+
* The declaration is what the gateway is configured to do, stated by the
|
|
237
|
+
* deployment: this provider cannot see the gateway's routing, and a fallback
|
|
238
|
+
* the gateway adds on its own breaks the declaration silently. Keep the two
|
|
239
|
+
* in the same review.
|
|
240
|
+
*/
|
|
241
|
+
async function createOpenAiCompatibleGatewayProvider(config) {
|
|
242
|
+
const residency = checkEndpoint(config);
|
|
243
|
+
const declared = config.models;
|
|
244
|
+
if (declared === null || typeof declared !== "object" || Object.keys(declared).length === 0) throw new AiProviderConfigError("ai_model_required", "a gateway needs at least one declared model alias in models");
|
|
245
|
+
const models = {};
|
|
246
|
+
for (const [alias, model] of Object.entries(declared)) {
|
|
247
|
+
requireModel(alias, "a model alias");
|
|
248
|
+
const resolved = resolveEndpointResidency(model?.residency);
|
|
249
|
+
if (!resolved.ok) throw new AiProviderConfigError(resolved.error.key, `model ${JSON.stringify(alias)}: ${resolved.error.message}`);
|
|
250
|
+
models[alias] = resolved.value;
|
|
251
|
+
}
|
|
252
|
+
Object.freeze(models);
|
|
253
|
+
const provider = await loadOpenAiCompatible(config, {
|
|
254
|
+
structuredOutputs: config.structuredOutputs,
|
|
255
|
+
includeUsage: config.includeUsage
|
|
256
|
+
});
|
|
257
|
+
const optionsKey = config.name ?? "openai-compatible";
|
|
258
|
+
const residencyOf = (alias) => {
|
|
259
|
+
const found = Object.hasOwn(models, alias) ? models[alias] : void 0;
|
|
260
|
+
if (found === void 0) throw new AiProviderConfigError("ai_model_undeclared", `model ${JSON.stringify(alias)} is not declared for this gateway; declared: ${Object.keys(models).join(", ")}`);
|
|
261
|
+
return found;
|
|
262
|
+
};
|
|
263
|
+
return {
|
|
264
|
+
providerId: OPENAI_COMPATIBLE_PROVIDER_ID,
|
|
265
|
+
residency,
|
|
266
|
+
models,
|
|
267
|
+
residencyOf,
|
|
268
|
+
languageModel: (alias, options) => {
|
|
269
|
+
residencyOf(alias);
|
|
270
|
+
const chat = provider.chatModel(alias);
|
|
271
|
+
return createRefusalAwareModel(options?.metadata ? withGatewayMetadata(chat, optionsKey, options.metadata) : chat);
|
|
272
|
+
}
|
|
273
|
+
};
|
|
274
|
+
}
|
|
189
275
|
//#endregion
|
|
190
|
-
export { AiProviderConfigError, createEmbeddingModelFactory, createEmbeddingModelProvider, createLanguageModelFactory, createLanguageModelProvider, createLazyEmbeddingModel, createOpenAiCompatibleEmbeddingProvider, createOpenAiCompatibleModelProvider };
|
|
276
|
+
export { AiProviderConfigError, createEmbeddingModelFactory, createEmbeddingModelProvider, createLanguageModelFactory, createLanguageModelProvider, createLazyEmbeddingModel, createOpenAiCompatibleEmbeddingProvider, createOpenAiCompatibleGatewayProvider, createOpenAiCompatibleModelProvider };
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
//#region src/drizzle/index.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* `flow_workflow` — one row per run. `partition_key` is the engine's scoping
|
|
4
|
+
* column: the store is bound to one partition and every query filters by it.
|
|
5
|
+
*
|
|
6
|
+
* The DDL adds, and the host declares:
|
|
7
|
+
*
|
|
8
|
+
* ```ts
|
|
9
|
+
* import { index, pgTable, uniqueIndex } from 'drizzle-orm/pg-core';
|
|
10
|
+
* import { sql } from 'drizzle-orm';
|
|
11
|
+
* import { flowWorkflowColumns } from 'octaflow/drizzle';
|
|
12
|
+
*
|
|
13
|
+
* export const flowWorkflow = pgTable('flow_workflow', flowWorkflowColumns, (t) => [
|
|
14
|
+
* index('flow_workflow_partition_status_idx').on(t.partitionKey, t.status),
|
|
15
|
+
* index('flow_workflow_parent_idx').on(t.parentWorkflowId),
|
|
16
|
+
* index('flow_workflow_partition_type_idx').on(t.partitionKey, t.type),
|
|
17
|
+
* index('flow_workflow_partition_entity_idx').on(t.partitionKey, t.entityRef),
|
|
18
|
+
* // Load-bearing: the deadline sweep reads only live runs that have one.
|
|
19
|
+
* index('flow_workflow_deadline_idx').on(t.deadlineAt).where(sql`deadline_at IS NOT NULL`),
|
|
20
|
+
* // Load-bearing: `start` with an idempotency key relies on this unique index
|
|
21
|
+
* // to make a repeated start return the existing run instead of a second one.
|
|
22
|
+
* uniqueIndex('flow_workflow_idempotency_idx').on(t.partitionKey, t.idempotencyKey).where(sql`idempotency_key IS NOT NULL`),
|
|
23
|
+
* ]);
|
|
24
|
+
* ```
|
|
25
|
+
*
|
|
26
|
+
* `parent_workflow_id` and `parent_step_id` carry no foreign key in the DDL
|
|
27
|
+
* (a parent may outlive or predate its child in a migration); add one if you
|
|
28
|
+
* want the constraint.
|
|
29
|
+
*/
|
|
30
|
+
export declare const flowWorkflowColumns: {
|
|
31
|
+
id: import("drizzle-orm").IsPrimaryKey<import("drizzle-orm").NotNull<import("drizzle-orm/pg-core").PgBigSerial53BuilderInitial<"id">>>;
|
|
32
|
+
partitionKey: import("drizzle-orm").NotNull<import("drizzle-orm/pg-core").PgTextBuilderInitial<"partition_key", [string, ...string[]]>>;
|
|
33
|
+
/** The workflow definition's name. */
|
|
34
|
+
type: import("drizzle-orm").NotNull<import("drizzle-orm/pg-core").PgTextBuilderInitial<"type", [string, ...string[]]>>;
|
|
35
|
+
status: import("drizzle-orm").NotNull<import("drizzle-orm/pg-core").PgTextBuilderInitial<"status", [string, ...string[]]>>;
|
|
36
|
+
input: import("drizzle-orm").HasDefault<import("drizzle-orm").NotNull<import("drizzle-orm/pg-core").PgJsonbBuilderInitial<"input">>>;
|
|
37
|
+
output: import("drizzle-orm/pg-core").PgJsonbBuilderInitial<"output">;
|
|
38
|
+
error: import("drizzle-orm/pg-core").PgTextBuilderInitial<"error", [string, ...string[]]>;
|
|
39
|
+
/** The host's handle for what the run is about (`'record:88'`), for lookups by entity. */
|
|
40
|
+
entityRef: import("drizzle-orm/pg-core").PgTextBuilderInitial<"entity_ref", [string, ...string[]]>;
|
|
41
|
+
totalSteps: import("drizzle-orm").NotNull<import("drizzle-orm/pg-core").PgIntegerBuilderInitial<"total_steps">>;
|
|
42
|
+
completedSteps: import("drizzle-orm").HasDefault<import("drizzle-orm").NotNull<import("drizzle-orm/pg-core").PgIntegerBuilderInitial<"completed_steps">>>;
|
|
43
|
+
failedSteps: import("drizzle-orm").HasDefault<import("drizzle-orm").NotNull<import("drizzle-orm/pg-core").PgIntegerBuilderInitial<"failed_steps">>>;
|
|
44
|
+
metadata: import("drizzle-orm/pg-core").PgJsonbBuilderInitial<"metadata">;
|
|
45
|
+
/** Set on a `start` that asked for one; unique per partition where present. */
|
|
46
|
+
idempotencyKey: import("drizzle-orm/pg-core").PgTextBuilderInitial<"idempotency_key", [string, ...string[]]>;
|
|
47
|
+
/** The run's deadline, when the definition set one; the sweep reads it. */
|
|
48
|
+
deadlineAt: import("drizzle-orm/pg-core").PgTimestampBuilderInitial<string>;
|
|
49
|
+
/** For a sub-workflow: the run that started it, and the step in that run. */
|
|
50
|
+
parentWorkflowId: import("drizzle-orm/pg-core").PgBigInt53BuilderInitial<"parent_workflow_id">;
|
|
51
|
+
parentStepId: import("drizzle-orm/pg-core").PgBigInt53BuilderInitial<"parent_step_id">;
|
|
52
|
+
createdAt: import("drizzle-orm").HasDefault<import("drizzle-orm").NotNull<import("drizzle-orm/pg-core").PgTimestampBuilderInitial<string>>>;
|
|
53
|
+
startedAt: import("drizzle-orm/pg-core").PgTimestampBuilderInitial<string>;
|
|
54
|
+
completedAt: import("drizzle-orm/pg-core").PgTimestampBuilderInitial<string>;
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* `flow_workflow_step` — one row per step of a run, including the children a
|
|
58
|
+
* fan-out creates at runtime.
|
|
59
|
+
*
|
|
60
|
+
* The DDL adds, and the host declares:
|
|
61
|
+
*
|
|
62
|
+
* ```ts
|
|
63
|
+
* import { foreignKey, index, pgTable, unique, type AnyPgColumn } from 'drizzle-orm/pg-core';
|
|
64
|
+
* import { flowWorkflowStepColumns } from 'octaflow/drizzle';
|
|
65
|
+
*
|
|
66
|
+
* export const flowWorkflowStep = pgTable('flow_workflow_step', flowWorkflowStepColumns, (t) => [
|
|
67
|
+
* foreignKey({ columns: [t.workflowId], foreignColumns: [flowWorkflow.id] }).onDelete('cascade'),
|
|
68
|
+
* foreignKey({ columns: [t.parentStepId], foreignColumns: [t.id] }).onDelete('cascade'),
|
|
69
|
+
* // `(workflow_id, key)` is the step's identity within a run; the constraint
|
|
70
|
+
* // is what refuses a second step under the same key.
|
|
71
|
+
* unique('flow_workflow_step_workflow_id_key_unique').on(t.workflowId, t.key),
|
|
72
|
+
* index('flow_workflow_step_workflow_idx').on(t.workflowId),
|
|
73
|
+
* index('flow_workflow_step_status_idx').on(t.workflowId, t.status),
|
|
74
|
+
* index('flow_workflow_step_parent_idx').on(t.parentStepId),
|
|
75
|
+
* ]);
|
|
76
|
+
* ```
|
|
77
|
+
*/
|
|
78
|
+
export declare const flowWorkflowStepColumns: {
|
|
79
|
+
id: import("drizzle-orm").IsPrimaryKey<import("drizzle-orm").NotNull<import("drizzle-orm/pg-core").PgBigSerial53BuilderInitial<"id">>>;
|
|
80
|
+
workflowId: import("drizzle-orm").NotNull<import("drizzle-orm/pg-core").PgBigInt53BuilderInitial<"workflow_id">>;
|
|
81
|
+
partitionKey: import("drizzle-orm").NotNull<import("drizzle-orm/pg-core").PgTextBuilderInitial<"partition_key", [string, ...string[]]>>;
|
|
82
|
+
/** The step's key within its workflow — unique per run. */
|
|
83
|
+
key: import("drizzle-orm").NotNull<import("drizzle-orm/pg-core").PgTextBuilderInitial<"key", [string, ...string[]]>>;
|
|
84
|
+
/** The step handler's name. */
|
|
85
|
+
type: import("drizzle-orm").NotNull<import("drizzle-orm/pg-core").PgTextBuilderInitial<"type", [string, ...string[]]>>;
|
|
86
|
+
status: import("drizzle-orm").NotNull<import("drizzle-orm/pg-core").PgTextBuilderInitial<"status", [string, ...string[]]>>;
|
|
87
|
+
/** The keys of the steps this one waits for. */
|
|
88
|
+
dependencies: import("drizzle-orm").HasDefault<import("drizzle-orm").NotNull<import("drizzle-orm/pg-core").PgJsonbBuilderInitial<"dependencies">>>;
|
|
89
|
+
input: import("drizzle-orm/pg-core").PgJsonbBuilderInitial<"input">;
|
|
90
|
+
output: import("drizzle-orm/pg-core").PgJsonbBuilderInitial<"output">;
|
|
91
|
+
error: import("drizzle-orm/pg-core").PgTextBuilderInitial<"error", [string, ...string[]]>;
|
|
92
|
+
metadata: import("drizzle-orm/pg-core").PgJsonbBuilderInitial<"metadata">;
|
|
93
|
+
attempts: import("drizzle-orm").HasDefault<import("drizzle-orm").NotNull<import("drizzle-orm/pg-core").PgIntegerBuilderInitial<"attempts">>>;
|
|
94
|
+
/** For a fan-out child: the step that spawned it. */
|
|
95
|
+
parentStepId: import("drizzle-orm/pg-core").PgBigInt53BuilderInitial<"parent_step_id">;
|
|
96
|
+
/** The last heartbeat a long step sent; the sweep uses it to tell stalled from slow. */
|
|
97
|
+
heartbeatAt: import("drizzle-orm/pg-core").PgTimestampBuilderInitial<string>;
|
|
98
|
+
startedAt: import("drizzle-orm/pg-core").PgTimestampBuilderInitial<string>;
|
|
99
|
+
completedAt: import("drizzle-orm/pg-core").PgTimestampBuilderInitial<string>;
|
|
100
|
+
};
|
|
101
|
+
/**
|
|
102
|
+
* `flow_step_event` — the append-only run history `createPgEventSink` writes,
|
|
103
|
+
* one row per engine event.
|
|
104
|
+
*
|
|
105
|
+
* The DDL adds, and the host declares:
|
|
106
|
+
*
|
|
107
|
+
* ```ts
|
|
108
|
+
* export const flowStepEvent = pgTable('flow_step_event', flowStepEventColumns, (t) => [
|
|
109
|
+
* index('flow_step_event_workflow_idx').on(t.partitionKey, t.workflowId, t.id),
|
|
110
|
+
* ]);
|
|
111
|
+
* ```
|
|
112
|
+
*
|
|
113
|
+
* `workflow_id` carries no foreign key on purpose: the history outlives the run.
|
|
114
|
+
*/
|
|
115
|
+
export declare const flowStepEventColumns: {
|
|
116
|
+
id: import("drizzle-orm").IsPrimaryKey<import("drizzle-orm").NotNull<import("drizzle-orm/pg-core").PgBigSerial53BuilderInitial<"id">>>;
|
|
117
|
+
partitionKey: import("drizzle-orm").NotNull<import("drizzle-orm/pg-core").PgTextBuilderInitial<"partition_key", [string, ...string[]]>>;
|
|
118
|
+
workflowId: import("drizzle-orm").NotNull<import("drizzle-orm/pg-core").PgBigInt53BuilderInitial<"workflow_id">>;
|
|
119
|
+
workflowType: import("drizzle-orm/pg-core").PgTextBuilderInitial<"workflow_type", [string, ...string[]]>;
|
|
120
|
+
/** Null for a workflow-level event. */
|
|
121
|
+
stepId: import("drizzle-orm/pg-core").PgBigInt53BuilderInitial<"step_id">;
|
|
122
|
+
stepKey: import("drizzle-orm/pg-core").PgTextBuilderInitial<"step_key", [string, ...string[]]>;
|
|
123
|
+
stepType: import("drizzle-orm/pg-core").PgTextBuilderInitial<"step_type", [string, ...string[]]>;
|
|
124
|
+
eventType: import("drizzle-orm").NotNull<import("drizzle-orm/pg-core").PgTextBuilderInitial<"event_type", [string, ...string[]]>>;
|
|
125
|
+
attempt: import("drizzle-orm/pg-core").PgIntegerBuilderInitial<"attempt">;
|
|
126
|
+
durationMs: import("drizzle-orm/pg-core").PgIntegerBuilderInitial<"duration_ms">;
|
|
127
|
+
error: import("drizzle-orm/pg-core").PgTextBuilderInitial<"error", [string, ...string[]]>;
|
|
128
|
+
at: import("drizzle-orm").NotNull<import("drizzle-orm/pg-core").PgTimestampBuilderInitial<string>>;
|
|
129
|
+
};
|
|
130
|
+
/**
|
|
131
|
+
* `flow_rate_bucket` — one token bucket per `(partition_key, step_type)` for
|
|
132
|
+
* `createPgStepGate`'s rate limits. The table name is the DDL's default and
|
|
133
|
+
* the gate's `tables.rateBucket` option; the two must agree.
|
|
134
|
+
*
|
|
135
|
+
* The DDL adds a composite primary key, and the host declares it:
|
|
136
|
+
*
|
|
137
|
+
* ```ts
|
|
138
|
+
* export const flowRateBucket = pgTable('flow_rate_bucket', flowRateBucketColumns, (t) => [
|
|
139
|
+
* primaryKey({ columns: [t.partitionKey, t.stepType] }),
|
|
140
|
+
* ]);
|
|
141
|
+
* ```
|
|
142
|
+
*/
|
|
143
|
+
export declare const flowRateBucketColumns: {
|
|
144
|
+
partitionKey: import("drizzle-orm").NotNull<import("drizzle-orm/pg-core").PgTextBuilderInitial<"partition_key", [string, ...string[]]>>;
|
|
145
|
+
stepType: import("drizzle-orm").NotNull<import("drizzle-orm/pg-core").PgTextBuilderInitial<"step_type", [string, ...string[]]>>;
|
|
146
|
+
/** The bucket's fill, refilled from `updated_at` on every read. */
|
|
147
|
+
tokens: import("drizzle-orm").NotNull<import("drizzle-orm/pg-core").PgDoublePrecisionBuilderInitial<"tokens">>;
|
|
148
|
+
updatedAt: import("drizzle-orm").HasDefault<import("drizzle-orm").NotNull<import("drizzle-orm/pg-core").PgTimestampBuilderInitial<string>>>;
|
|
149
|
+
};
|
|
150
|
+
/**
|
|
151
|
+
* `flow_step_lease` — one row per running step under a concurrency cap, for
|
|
152
|
+
* `createPgStepGate`'s `maxConcurrent`. The table name is the DDL's default
|
|
153
|
+
* and the gate's `tables.lease` option; the two must agree.
|
|
154
|
+
*
|
|
155
|
+
* The DDL adds a composite primary key and one index, and the host declares
|
|
156
|
+
* them — the index name is derived from the table name in the DDL:
|
|
157
|
+
*
|
|
158
|
+
* ```ts
|
|
159
|
+
* export const flowStepLease = pgTable('flow_step_lease', flowStepLeaseColumns, (t) => [
|
|
160
|
+
* primaryKey({ columns: [t.partitionKey, t.stepType, t.stepId] }),
|
|
161
|
+
* index('flow_step_lease_active_idx').on(t.partitionKey, t.stepType, t.expiresAt),
|
|
162
|
+
* ]);
|
|
163
|
+
* ```
|
|
164
|
+
*/
|
|
165
|
+
export declare const flowStepLeaseColumns: {
|
|
166
|
+
partitionKey: import("drizzle-orm").NotNull<import("drizzle-orm/pg-core").PgTextBuilderInitial<"partition_key", [string, ...string[]]>>;
|
|
167
|
+
stepType: import("drizzle-orm").NotNull<import("drizzle-orm/pg-core").PgTextBuilderInitial<"step_type", [string, ...string[]]>>;
|
|
168
|
+
stepId: import("drizzle-orm").NotNull<import("drizzle-orm/pg-core").PgBigInt53BuilderInitial<"step_id">>;
|
|
169
|
+
acquiredAt: import("drizzle-orm").HasDefault<import("drizzle-orm").NotNull<import("drizzle-orm/pg-core").PgTimestampBuilderInitial<string>>>;
|
|
170
|
+
/** A lease past this instant is reaped on the next acquire. */
|
|
171
|
+
expiresAt: import("drizzle-orm").NotNull<import("drizzle-orm/pg-core").PgTimestampBuilderInitial<string>>;
|
|
172
|
+
};
|
|
173
|
+
//#endregion
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import { bigint, bigserial, doublePrecision, integer, jsonb, text, timestamp } from "drizzle-orm/pg-core";
|
|
2
|
+
//#region src/drizzle/index.ts
|
|
3
|
+
/**
|
|
4
|
+
* `octaflow/drizzle` — the engine's tables as Drizzle column sets.
|
|
5
|
+
*
|
|
6
|
+
* One spreadable column set per table the Postgres adapters create: the
|
|
7
|
+
* workflow store's two (`flowStoreDdl`), the event sink's one (`flowEventDdl`)
|
|
8
|
+
* and the gate's two (`flowGateDdl`). A host on Drizzle declares the tables in
|
|
9
|
+
* its own schema from these columns and lets drizzle-kit own the migrations;
|
|
10
|
+
* the stores themselves stay plain SQL over the `schema` (and, for the gate,
|
|
11
|
+
* the table names) the host hands them. Nothing here runs a query: no store
|
|
12
|
+
* is rewritten on the ORM, because every one of the engine's writes is one
|
|
13
|
+
* SQL statement by nature.
|
|
14
|
+
*
|
|
15
|
+
* What the DDL declares beyond the columns is the host's to mirror in its
|
|
16
|
+
* `pgTable(...)` call — the foreign keys, the composite keys, the unique
|
|
17
|
+
* constraint and the indexes. Each set's doc lists them; two of the indexes
|
|
18
|
+
* are load-bearing for the store, not an optimisation, and are marked so.
|
|
19
|
+
* A test holds these sets and the DDL equal column for column.
|
|
20
|
+
*
|
|
21
|
+
* `drizzle-orm` is an optional peer, loaded by this subpath alone.
|
|
22
|
+
*/
|
|
23
|
+
const timestamptz = (name) => timestamp(name, {
|
|
24
|
+
withTimezone: true,
|
|
25
|
+
mode: "date"
|
|
26
|
+
});
|
|
27
|
+
/**
|
|
28
|
+
* `flow_workflow` — one row per run. `partition_key` is the engine's scoping
|
|
29
|
+
* column: the store is bound to one partition and every query filters by it.
|
|
30
|
+
*
|
|
31
|
+
* The DDL adds, and the host declares:
|
|
32
|
+
*
|
|
33
|
+
* ```ts
|
|
34
|
+
* import { index, pgTable, uniqueIndex } from 'drizzle-orm/pg-core';
|
|
35
|
+
* import { sql } from 'drizzle-orm';
|
|
36
|
+
* import { flowWorkflowColumns } from 'octaflow/drizzle';
|
|
37
|
+
*
|
|
38
|
+
* export const flowWorkflow = pgTable('flow_workflow', flowWorkflowColumns, (t) => [
|
|
39
|
+
* index('flow_workflow_partition_status_idx').on(t.partitionKey, t.status),
|
|
40
|
+
* index('flow_workflow_parent_idx').on(t.parentWorkflowId),
|
|
41
|
+
* index('flow_workflow_partition_type_idx').on(t.partitionKey, t.type),
|
|
42
|
+
* index('flow_workflow_partition_entity_idx').on(t.partitionKey, t.entityRef),
|
|
43
|
+
* // Load-bearing: the deadline sweep reads only live runs that have one.
|
|
44
|
+
* index('flow_workflow_deadline_idx').on(t.deadlineAt).where(sql`deadline_at IS NOT NULL`),
|
|
45
|
+
* // Load-bearing: `start` with an idempotency key relies on this unique index
|
|
46
|
+
* // to make a repeated start return the existing run instead of a second one.
|
|
47
|
+
* uniqueIndex('flow_workflow_idempotency_idx').on(t.partitionKey, t.idempotencyKey).where(sql`idempotency_key IS NOT NULL`),
|
|
48
|
+
* ]);
|
|
49
|
+
* ```
|
|
50
|
+
*
|
|
51
|
+
* `parent_workflow_id` and `parent_step_id` carry no foreign key in the DDL
|
|
52
|
+
* (a parent may outlive or predate its child in a migration); add one if you
|
|
53
|
+
* want the constraint.
|
|
54
|
+
*/
|
|
55
|
+
const flowWorkflowColumns = {
|
|
56
|
+
id: bigserial("id", { mode: "number" }).primaryKey(),
|
|
57
|
+
partitionKey: text("partition_key").notNull(),
|
|
58
|
+
/** The workflow definition's name. */
|
|
59
|
+
type: text("type").notNull(),
|
|
60
|
+
status: text("status").notNull(),
|
|
61
|
+
input: jsonb("input").notNull().default({}),
|
|
62
|
+
output: jsonb("output"),
|
|
63
|
+
error: text("error"),
|
|
64
|
+
/** The host's handle for what the run is about (`'record:88'`), for lookups by entity. */
|
|
65
|
+
entityRef: text("entity_ref"),
|
|
66
|
+
totalSteps: integer("total_steps").notNull(),
|
|
67
|
+
completedSteps: integer("completed_steps").notNull().default(0),
|
|
68
|
+
failedSteps: integer("failed_steps").notNull().default(0),
|
|
69
|
+
metadata: jsonb("metadata"),
|
|
70
|
+
/** Set on a `start` that asked for one; unique per partition where present. */
|
|
71
|
+
idempotencyKey: text("idempotency_key"),
|
|
72
|
+
/** The run's deadline, when the definition set one; the sweep reads it. */
|
|
73
|
+
deadlineAt: timestamptz("deadline_at"),
|
|
74
|
+
/** For a sub-workflow: the run that started it, and the step in that run. */
|
|
75
|
+
parentWorkflowId: bigint("parent_workflow_id", { mode: "number" }),
|
|
76
|
+
parentStepId: bigint("parent_step_id", { mode: "number" }),
|
|
77
|
+
createdAt: timestamptz("created_at").notNull().defaultNow(),
|
|
78
|
+
startedAt: timestamptz("started_at"),
|
|
79
|
+
completedAt: timestamptz("completed_at")
|
|
80
|
+
};
|
|
81
|
+
/**
|
|
82
|
+
* `flow_workflow_step` — one row per step of a run, including the children a
|
|
83
|
+
* fan-out creates at runtime.
|
|
84
|
+
*
|
|
85
|
+
* The DDL adds, and the host declares:
|
|
86
|
+
*
|
|
87
|
+
* ```ts
|
|
88
|
+
* import { foreignKey, index, pgTable, unique, type AnyPgColumn } from 'drizzle-orm/pg-core';
|
|
89
|
+
* import { flowWorkflowStepColumns } from 'octaflow/drizzle';
|
|
90
|
+
*
|
|
91
|
+
* export const flowWorkflowStep = pgTable('flow_workflow_step', flowWorkflowStepColumns, (t) => [
|
|
92
|
+
* foreignKey({ columns: [t.workflowId], foreignColumns: [flowWorkflow.id] }).onDelete('cascade'),
|
|
93
|
+
* foreignKey({ columns: [t.parentStepId], foreignColumns: [t.id] }).onDelete('cascade'),
|
|
94
|
+
* // `(workflow_id, key)` is the step's identity within a run; the constraint
|
|
95
|
+
* // is what refuses a second step under the same key.
|
|
96
|
+
* unique('flow_workflow_step_workflow_id_key_unique').on(t.workflowId, t.key),
|
|
97
|
+
* index('flow_workflow_step_workflow_idx').on(t.workflowId),
|
|
98
|
+
* index('flow_workflow_step_status_idx').on(t.workflowId, t.status),
|
|
99
|
+
* index('flow_workflow_step_parent_idx').on(t.parentStepId),
|
|
100
|
+
* ]);
|
|
101
|
+
* ```
|
|
102
|
+
*/
|
|
103
|
+
const flowWorkflowStepColumns = {
|
|
104
|
+
id: bigserial("id", { mode: "number" }).primaryKey(),
|
|
105
|
+
workflowId: bigint("workflow_id", { mode: "number" }).notNull(),
|
|
106
|
+
partitionKey: text("partition_key").notNull(),
|
|
107
|
+
/** The step's key within its workflow — unique per run. */
|
|
108
|
+
key: text("key").notNull(),
|
|
109
|
+
/** The step handler's name. */
|
|
110
|
+
type: text("type").notNull(),
|
|
111
|
+
status: text("status").notNull(),
|
|
112
|
+
/** The keys of the steps this one waits for. */
|
|
113
|
+
dependencies: jsonb("dependencies").notNull().default([]),
|
|
114
|
+
input: jsonb("input"),
|
|
115
|
+
output: jsonb("output"),
|
|
116
|
+
error: text("error"),
|
|
117
|
+
metadata: jsonb("metadata"),
|
|
118
|
+
attempts: integer("attempts").notNull().default(0),
|
|
119
|
+
/** For a fan-out child: the step that spawned it. */
|
|
120
|
+
parentStepId: bigint("parent_step_id", { mode: "number" }),
|
|
121
|
+
/** The last heartbeat a long step sent; the sweep uses it to tell stalled from slow. */
|
|
122
|
+
heartbeatAt: timestamptz("heartbeat_at"),
|
|
123
|
+
startedAt: timestamptz("started_at"),
|
|
124
|
+
completedAt: timestamptz("completed_at")
|
|
125
|
+
};
|
|
126
|
+
/**
|
|
127
|
+
* `flow_step_event` — the append-only run history `createPgEventSink` writes,
|
|
128
|
+
* one row per engine event.
|
|
129
|
+
*
|
|
130
|
+
* The DDL adds, and the host declares:
|
|
131
|
+
*
|
|
132
|
+
* ```ts
|
|
133
|
+
* export const flowStepEvent = pgTable('flow_step_event', flowStepEventColumns, (t) => [
|
|
134
|
+
* index('flow_step_event_workflow_idx').on(t.partitionKey, t.workflowId, t.id),
|
|
135
|
+
* ]);
|
|
136
|
+
* ```
|
|
137
|
+
*
|
|
138
|
+
* `workflow_id` carries no foreign key on purpose: the history outlives the run.
|
|
139
|
+
*/
|
|
140
|
+
const flowStepEventColumns = {
|
|
141
|
+
id: bigserial("id", { mode: "number" }).primaryKey(),
|
|
142
|
+
partitionKey: text("partition_key").notNull(),
|
|
143
|
+
workflowId: bigint("workflow_id", { mode: "number" }).notNull(),
|
|
144
|
+
workflowType: text("workflow_type"),
|
|
145
|
+
/** Null for a workflow-level event. */
|
|
146
|
+
stepId: bigint("step_id", { mode: "number" }),
|
|
147
|
+
stepKey: text("step_key"),
|
|
148
|
+
stepType: text("step_type"),
|
|
149
|
+
eventType: text("event_type").notNull(),
|
|
150
|
+
attempt: integer("attempt"),
|
|
151
|
+
durationMs: integer("duration_ms"),
|
|
152
|
+
error: text("error"),
|
|
153
|
+
at: timestamptz("at").notNull()
|
|
154
|
+
};
|
|
155
|
+
/**
|
|
156
|
+
* `flow_rate_bucket` — one token bucket per `(partition_key, step_type)` for
|
|
157
|
+
* `createPgStepGate`'s rate limits. The table name is the DDL's default and
|
|
158
|
+
* the gate's `tables.rateBucket` option; the two must agree.
|
|
159
|
+
*
|
|
160
|
+
* The DDL adds a composite primary key, and the host declares it:
|
|
161
|
+
*
|
|
162
|
+
* ```ts
|
|
163
|
+
* export const flowRateBucket = pgTable('flow_rate_bucket', flowRateBucketColumns, (t) => [
|
|
164
|
+
* primaryKey({ columns: [t.partitionKey, t.stepType] }),
|
|
165
|
+
* ]);
|
|
166
|
+
* ```
|
|
167
|
+
*/
|
|
168
|
+
const flowRateBucketColumns = {
|
|
169
|
+
partitionKey: text("partition_key").notNull(),
|
|
170
|
+
stepType: text("step_type").notNull(),
|
|
171
|
+
/** The bucket's fill, refilled from `updated_at` on every read. */
|
|
172
|
+
tokens: doublePrecision("tokens").notNull(),
|
|
173
|
+
updatedAt: timestamptz("updated_at").notNull().defaultNow()
|
|
174
|
+
};
|
|
175
|
+
/**
|
|
176
|
+
* `flow_step_lease` — one row per running step under a concurrency cap, for
|
|
177
|
+
* `createPgStepGate`'s `maxConcurrent`. The table name is the DDL's default
|
|
178
|
+
* and the gate's `tables.lease` option; the two must agree.
|
|
179
|
+
*
|
|
180
|
+
* The DDL adds a composite primary key and one index, and the host declares
|
|
181
|
+
* them — the index name is derived from the table name in the DDL:
|
|
182
|
+
*
|
|
183
|
+
* ```ts
|
|
184
|
+
* export const flowStepLease = pgTable('flow_step_lease', flowStepLeaseColumns, (t) => [
|
|
185
|
+
* primaryKey({ columns: [t.partitionKey, t.stepType, t.stepId] }),
|
|
186
|
+
* index('flow_step_lease_active_idx').on(t.partitionKey, t.stepType, t.expiresAt),
|
|
187
|
+
* ]);
|
|
188
|
+
* ```
|
|
189
|
+
*/
|
|
190
|
+
const flowStepLeaseColumns = {
|
|
191
|
+
partitionKey: text("partition_key").notNull(),
|
|
192
|
+
stepType: text("step_type").notNull(),
|
|
193
|
+
stepId: bigint("step_id", { mode: "number" }).notNull(),
|
|
194
|
+
acquiredAt: timestamptz("acquired_at").notNull().defaultNow(),
|
|
195
|
+
/** A lease past this instant is reaped on the next acquire. */
|
|
196
|
+
expiresAt: timestamptz("expires_at").notNull()
|
|
197
|
+
};
|
|
198
|
+
//#endregion
|
|
199
|
+
export { flowRateBucketColumns, flowStepEventColumns, flowStepLeaseColumns, flowWorkflowColumns, flowWorkflowStepColumns };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { $ as computeReadiness, A as StepConditionHandler, B as StepStatus, C as defineWaitStep, D as StartOptions, E as RetryPolicy, F as StepHandlerNotFoundError, G as WorkflowNotFoundError, H as WorkflowCreatedResult, I as StepHandlerRegistry, J as WorkflowStatus, K as WorkflowNotRetryableError, L as StepId, M as StepError, N as StepExecutionContext, O as StepCompensateHandler, P as StepHandler, Q as ReadinessPlan, R as StepRecord, S as defineSubWorkflowStep, T as InvalidWorkflowDefinitionError, U as WorkflowDefinition, V as WaitTimeoutPolicy, W as WorkflowId, X as JoinRule, Y as WorkflowWithSteps, Z as PlannedSkip, _ as WorkflowOutput, a as STEP_DISPLAY_STATUS, b as defineSleepStep, c as WORKFLOW_STATUS_SCHEMA, d as toPublicWorkflow, et as isTerminalStepStatus, f as StepDeps, g as TypedWorkflow, h as TypedStepContext, i as PublicWorkflowStep, it as ok, j as StepDefinition, k as StepCompensationContext, l as toDisplayStepStatus, m as TypedStep, n as PUBLIC_WORKFLOW_STEP_SCHEMA, nt as Result, o as STEP_DISPLAY_STATUS_SCHEMA, p as StepOutput, q as WorkflowRecord, r as PublicWorkflow, rt as err, s as StepDisplayStatus, t as PUBLIC_WORKFLOW_SCHEMA, tt as FlowErrorShape, u as toPublicStep, v as buildWorkflow, w as FlowError, x as defineStep, y as defineMapStep, z as StepRegistration } from "./index-BURAYZui.js";
|
|
2
2
|
import { n as Logger, r as noopLogger, t as LogAttributes } from "./logger-BCus3AOa.js";
|
|
3
|
-
import { A as ListWorkflowsFilters, C as AddChildStep, D as CreatedWorkflow, E as CreateWorkflowStep, M as TransactionalScope, N as WorkflowStore, O as FailStepParams, S as createInMemoryStepGate, T as CreateWorkflowParams, _ as InMemoryStepGateConfig, a as FlowSpan, b as StepGateDecision, c as RecordingObserver, d as createMeterObserver, f as createRecordingObserver, g as ConcurrencyRule, h as noopTracer, i as FlowObserver, j as ReopenWorkflowParams, k as FinishWorkflowParams, l as RecordingTracer, m as noopObserver, n as FlowEventType, o as FlowTracer, p as createRecordingTracer, r as FlowMeter, s as RecordedSpan, t as FlowEvent, u as combineObservers, v as RateRule, w as CompleteStepParams, x as StepGateRequest, y as StepGate } from "./observability-
|
|
3
|
+
import { A as ListWorkflowsFilters, C as AddChildStep, D as CreatedWorkflow, E as CreateWorkflowStep, M as TransactionalScope, N as WorkflowStore, O as FailStepParams, S as createInMemoryStepGate, T as CreateWorkflowParams, _ as InMemoryStepGateConfig, a as FlowSpan, b as StepGateDecision, c as RecordingObserver, d as createMeterObserver, f as createRecordingObserver, g as ConcurrencyRule, h as noopTracer, i as FlowObserver, j as ReopenWorkflowParams, k as FinishWorkflowParams, l as RecordingTracer, m as noopObserver, n as FlowEventType, o as FlowTracer, p as createRecordingTracer, r as FlowMeter, s as RecordedSpan, t as FlowEvent, u as combineObservers, v as RateRule, w as CompleteStepParams, x as StepGateRequest, y as StepGate } from "./observability-3wVqt3tZ.js";
|
|
4
4
|
import { i as EnqueueOptions, n as DispatchStepPayload, r as Dispatcher, t as DispatchKind } from "./dispatcher-g7Ct0J6w.js";
|
|
5
5
|
import { a as WorkflowCompletedArgs, i as BuildStepContextArgs, n as BeforeStartArgs, o as WorkflowHooks, r as BeforeStartResult, t as AfterStepArgs } from "./hooks-BdxIcc92.js";
|
|
6
6
|
//#region src/core/registry.d.ts
|
|
@@ -320,7 +320,7 @@ declare function createRecordingObserver(): RecordingObserver;
|
|
|
320
320
|
* The slice of a metrics API the engine's meter observer needs. Structural:
|
|
321
321
|
* `@octabits-io/telemetry`'s meter and an OpenTelemetry `Meter`
|
|
322
322
|
* both fit (the SDK's `createCounter`/`createHistogram` need a two-line
|
|
323
|
-
* adapter
|
|
323
|
+
* adapter).
|
|
324
324
|
*/
|
|
325
325
|
interface FlowMeter {
|
|
326
326
|
counter(name: string, options?: {
|
package/dist/postgres/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { W as WorkflowId } from "../index-BURAYZui.js";
|
|
2
|
-
import { N as WorkflowStore, g as ConcurrencyRule, i as FlowObserver, t as FlowEvent, v as RateRule, y as StepGate } from "../observability-
|
|
2
|
+
import { N as WorkflowStore, g as ConcurrencyRule, i as FlowObserver, t as FlowEvent, v as RateRule, y as StepGate } from "../observability-3wVqt3tZ.js";
|
|
3
3
|
import { Pool } from "pg";
|
|
4
4
|
//#region src/postgres/ddl.d.ts
|
|
5
5
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "octaflow",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.26.0",
|
|
4
4
|
"description": "Durable DAG workflow engine: Zod-typed steps over Postgres + pg-boss, with an optional AI add-on (token/cost/quota). Layered single package.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -20,6 +20,11 @@
|
|
|
20
20
|
"import": "./dist/postgres/index.js",
|
|
21
21
|
"default": "./dist/postgres/index.js"
|
|
22
22
|
},
|
|
23
|
+
"./drizzle": {
|
|
24
|
+
"types": "./dist/drizzle/index.d.ts",
|
|
25
|
+
"import": "./dist/drizzle/index.js",
|
|
26
|
+
"default": "./dist/drizzle/index.js"
|
|
27
|
+
},
|
|
23
28
|
"./pg-boss": {
|
|
24
29
|
"types": "./dist/pg-boss/index.d.ts",
|
|
25
30
|
"import": "./dist/pg-boss/index.js",
|
|
@@ -65,7 +70,8 @@
|
|
|
65
70
|
"llm",
|
|
66
71
|
"pg-boss",
|
|
67
72
|
"postgres",
|
|
68
|
-
"zod"
|
|
73
|
+
"zod",
|
|
74
|
+
"drizzle"
|
|
69
75
|
],
|
|
70
76
|
"peerDependencies": {
|
|
71
77
|
"@ai-sdk/anthropic": "^4.0.0",
|
|
@@ -74,6 +80,7 @@
|
|
|
74
80
|
"@ai-sdk/openai-compatible": "^3.0.0",
|
|
75
81
|
"@ai-sdk/provider": "^4.0.0",
|
|
76
82
|
"ai": "^7.0.0",
|
|
83
|
+
"drizzle-orm": ">=0.36.0 <1",
|
|
77
84
|
"pg": "^8.0.0",
|
|
78
85
|
"pg-boss": "^12.21.0",
|
|
79
86
|
"typescript": "^5 || ^6 || ^7",
|
|
@@ -98,6 +105,9 @@
|
|
|
98
105
|
"ai": {
|
|
99
106
|
"optional": true
|
|
100
107
|
},
|
|
108
|
+
"drizzle-orm": {
|
|
109
|
+
"optional": true
|
|
110
|
+
},
|
|
101
111
|
"pg": {
|
|
102
112
|
"optional": true
|
|
103
113
|
},
|
|
@@ -106,22 +116,23 @@
|
|
|
106
116
|
}
|
|
107
117
|
},
|
|
108
118
|
"devDependencies": {
|
|
109
|
-
"@ai-sdk/anthropic": "^4.0.
|
|
110
|
-
"@ai-sdk/mistral": "^4.0.
|
|
111
|
-
"@ai-sdk/openai": "^4.0.
|
|
112
|
-
"@ai-sdk/openai-compatible": "^3.0.
|
|
113
|
-
"@ai-sdk/provider": "^4.0.
|
|
114
|
-
"@testcontainers/postgresql": "^12.
|
|
115
|
-
"@types/node": "^26.
|
|
119
|
+
"@ai-sdk/anthropic": "^4.0.69",
|
|
120
|
+
"@ai-sdk/mistral": "^4.0.55",
|
|
121
|
+
"@ai-sdk/openai": "^4.0.82",
|
|
122
|
+
"@ai-sdk/openai-compatible": "^3.0.60",
|
|
123
|
+
"@ai-sdk/provider": "^4.0.20",
|
|
124
|
+
"@testcontainers/postgresql": "^12.2.0",
|
|
125
|
+
"@types/node": "^26.6.3",
|
|
116
126
|
"@types/pg": "^8.23.1",
|
|
117
|
-
"ai": "^7.0.
|
|
118
|
-
"
|
|
119
|
-
"pg
|
|
120
|
-
"
|
|
127
|
+
"ai": "^7.0.123",
|
|
128
|
+
"drizzle-orm": "^0.45.3",
|
|
129
|
+
"pg": "^8.23.1",
|
|
130
|
+
"pg-boss": "^12.35.0",
|
|
131
|
+
"testcontainers": "^12.2.0",
|
|
121
132
|
"tsdown": "^0.23.0",
|
|
122
133
|
"typescript": "^7.0.2",
|
|
123
|
-
"vitest": "^5.0.
|
|
124
|
-
"zod": "^4.6.
|
|
134
|
+
"vitest": "^5.0.3",
|
|
135
|
+
"zod": "^4.6.5"
|
|
125
136
|
},
|
|
126
137
|
"scripts": {
|
|
127
138
|
"build": "tsdown",
|