Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions src/client/journeys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,14 @@ import {
IterableSuccessResponseSchema,
} from "../types/common.js";
import {
GetJourneyDslGraphParams,
GetJourneyDslGraphResponse,
GetJourneyDslGraphResponseSchema,
GetJourneysParams,
GetJourneysResponse,
GetJourneysResponseSchema,
JourneyDslTileTypeManifest,
JourneyDslTileTypeManifestSchema,
TriggerJourneyParams,
} from "../types/journeys.js";
import type { BaseIterableClient, Constructor } from "./base.js";
Expand Down Expand Up @@ -51,5 +56,20 @@ export function Journeys<T extends Constructor<BaseIterableClient>>(Base: T) {
const response = await this.client.get(url);
return validateResponse(response, GetJourneysResponseSchema);
}

async getJourneyDslSchema(): Promise<JourneyDslTileTypeManifest> {
const response = await this.client.get("/api/journeys/dsl/schema");
return validateResponse(response, JourneyDslTileTypeManifestSchema);
}

async getJourneyDslGraph(
params: GetJourneyDslGraphParams
): Promise<GetJourneyDslGraphResponse> {
const response = await this.client.post(
"/api/journeys/dsl/graph",
params
);
return validateResponse(response, GetJourneyDslGraphResponseSchema);
}
};
}
287 changes: 286 additions & 1 deletion src/types/journeys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,14 @@ import {
* Journey (workflow) management schemas and types
*/

export const JourneyDraftDetailsSchema = z.object({
id: z.number(),
createdAt: UnixTimestampSchema,
updatedAt: UnixTimestampSchema,
name: z.string(),
creatorUserId: z.string(),
});

export const JourneySchema = z.object({
id: z.number(),
name: z.string(),
Expand All @@ -24,6 +32,7 @@ export const JourneySchema = z.object({
createdAt: UnixTimestampSchema, // API docs: "format": "int32"
updatedAt: UnixTimestampSchema, // API docs: "format": "int32"
creatorUserId: z.string().optional(),
draft: JourneyDraftDetailsSchema.nullable().optional(),
});

export const GetJourneysResponseSchema = z.object({
Expand All @@ -33,6 +42,7 @@ export const GetJourneysResponseSchema = z.object({
previousPageUrl: z.string().optional(),
});

export type JourneyDraftDetails = z.infer<typeof JourneyDraftDetailsSchema>;
export type Journey = z.infer<typeof JourneySchema>;
export type GetJourneysResponse = z.infer<typeof GetJourneysResponseSchema>;

Expand Down Expand Up @@ -83,6 +93,281 @@ export const TriggerJourneyParamsSchema = z.object({
.describe("Data fields for the journey"),
});

// Type exports
const MAX_JOURNEY_DSL_SELECTIONS = 50;

/**
* Opaque/pre-1.0 JSON object. Nested values are not interpreted so extra keys
* and evolving blobs such as tileData are not stripped.
*/
const JsonObjectSchema = z.record(z.string(), z.unknown());

export const JourneyWorkflowTypeSchema = z
.enum(["Draft", "Published"])
.describe("Whether to retrieve the draft or published workflow");

export const JourneyDslSelectionSchema = z
.object({
workflowId: z
.number()
.int()
.positive()
.describe(
"Workflow ID from get_journeys: use journey.id for a Published journey, journey.draft.id for its nested draft, or journey.id when journeyType is Draft"
),
workflowType: JourneyWorkflowTypeSchema.default("Published").describe(
"Use Draft with a draft workflow ID; use Published (the default) with a published workflow ID"
),
})
.describe("A draft or published workflow to include in a DSL graph request");

function uniqueJourneyDslSelections(
journeys: Array<{ workflowId: number; workflowType: "Draft" | "Published" }>,
ctx: z.RefinementCtx
): void {
const uniqueKeys = new Set(
journeys.map(
({ workflowId, workflowType }) => `${workflowType}:${workflowId}`
)
);
if (uniqueKeys.size !== journeys.length) {
ctx.addIssue({
code: "custom",
message:
"journeys contains duplicate (workflowType, workflowId) selections",
});
}
}

export const GetJourneyDslGraphParamsSchema = z
.object({
journeys: z
.array(JourneyDslSelectionSchema)
.min(1)
.max(MAX_JOURNEY_DSL_SELECTIONS)
.superRefine(uniqueJourneyDslSelections)
.describe("Workflows to retrieve"),
})
.describe("Parameters for retrieving Journey DSL graphs");

export const JourneyDslSchemaManifestSchema = z
.object({
version: z.string(),
schemaEndpoint: z.string(),
})
.passthrough();

export const JourneyDslEntityRefSchema = z
.object({
field: z.string(),
entityType: z.string(),
id: z.number(),
path: z.string().nullable().optional(),
})
.passthrough();

export const JourneyDslValidationIssueSchema = z
.object({
code: z.string(),
message: z.string(),
nodeId: z.number().nullable().optional(),
})
.passthrough();

export const JourneyDslCompletenessSchema = z
.object({
requiredFieldsPresent: z.boolean(),
issues: z.array(JourneyDslValidationIssueSchema),
})
.passthrough();

export const JourneyDslNodeSchema = z
.object({
nodeId: z.number(),
nodeType: z.string(),
title: z.string(),
tileData: JsonObjectSchema,
entityRefs: z.array(JourneyDslEntityRefSchema),
createdAt: z.string().nullable().optional(),
updatedAt: z.string().nullable().optional(),
})
.passthrough();

export const JourneyDslEdgeSchema = z
.object({
srcNodeId: z.number(),
destNodeId: z.number(),
outputIndex: z.number().int(),
label: z.string().nullable().optional(),
})
.passthrough();

export type JourneyDslTriggerRule = {
ruleType: string;
eventName?: string | null;
searchQuery?: Record<string, unknown> | null;
subRules?: JourneyDslTriggerRule[] | null;
};

export const JourneyDslTriggerRuleSchema: z.ZodType<JourneyDslTriggerRule> =
z.lazy(() =>
z
.object({
ruleType: z.string(),
eventName: z.string().nullable().optional(),
searchQuery: JsonObjectSchema.nullable().optional(),
subRules: z.array(JourneyDslTriggerRuleSchema).nullable().optional(),
})
.passthrough()
);

export const JourneyDslExitRuleSchema = z
.object({
id: z.string(),
status: z.string(),
source: z.string(),
triggerRule: JourneyDslTriggerRuleSchema,
sendToJourneyId: z.number().nullable().optional(),
entityRefs: z.array(JourneyDslEntityRefSchema),
})
.passthrough();

export const JourneyDslCustomConversionSchema = z
.object({
eventName: z.string(),
attributionMode: z.string(),
attributionPeriodHours: z.number().int().nullable().optional(),
})
.passthrough();

export const JourneyDslConversionGoalSchema = z
.object({
id: z.number(),
isActive: z.boolean(),
conversions: z.array(JourneyDslCustomConversionSchema),
})
.passthrough();

export const JourneyDslJourneySchema = z
.object({
journeyId: z.number(),
name: z.string(),
description: z.string(),
status: z.string(),
journeyType: JourneyWorkflowTypeSchema,
createdAt: z.string().nullable().optional(),
updatedAt: z.string().nullable().optional(),
simultaneousLimit: z.number().nullable().optional(),
lifetimeLimit: z.number().nullable().optional(),
triggerEventNames: z.array(z.string()),
exitRules: z.array(JourneyDslExitRuleSchema),
conversionGoals: z.array(JourneyDslConversionGoalSchema),
labelIds: z.array(z.number()),
isArchived: z.boolean(),
})
.passthrough();

export const JourneyDslDocumentSchema = z
.object({
schemaVersion: z.string(),
journey: JourneyDslJourneySchema,
startNodeId: z.number(),
nodes: z.array(JourneyDslNodeSchema),
edges: z.array(JourneyDslEdgeSchema),
completeness: JourneyDslCompletenessSchema,
schema: JourneyDslSchemaManifestSchema,
})
.passthrough();

export const JourneyDslJourneyErrorSchema = z
.object({
workflowId: z.number(),
workflowType: JourneyWorkflowTypeSchema,
error: z.string(),
message: z.string(),
})
.passthrough();

export const GetJourneyDslGraphResponseSchema = z
.object({
journeys: z.array(JourneyDslDocumentSchema),
errors: z.array(JourneyDslJourneyErrorSchema),
schema: JourneyDslSchemaManifestSchema,
})
.passthrough();

export const JourneyDslOutputCountSchema = z.union([
z.number().int().nonnegative(),
z.literal("dynamic"),
]);

export const JourneyDslTileTypeSchema = z
.object({
tileType: z.string(),
category: z.string(),
numOutputs: JourneyDslOutputCountSchema,
description: z.string(),
tileDataSchema: JsonObjectSchema,
outputs: z
.array(
z
.object({
index: z.number().int(),
label: z.string(),
description: z.string(),
})
.passthrough()
)
.nullable()
.optional(),
})
.passthrough();

export const JourneyDslTileTypeManifestSchema = z
.object({
kind: z.string(),
version: z.string(),
tileSchemas: z.array(JourneyDslTileTypeSchema),
entityTypes: z.array(z.object({ entityType: z.string() }).passthrough()),
})
.passthrough();

export type JourneyWorkflowType = z.infer<typeof JourneyWorkflowTypeSchema>;
export type JourneyDslSelection = z.input<typeof JourneyDslSelectionSchema>;
export type GetJourneyDslGraphParams = z.input<
typeof GetJourneyDslGraphParamsSchema
>;
export type JourneyDslSchemaManifest = z.infer<
typeof JourneyDslSchemaManifestSchema
>;
export type JourneyDslEntityRef = z.infer<typeof JourneyDslEntityRefSchema>;
export type JourneyDslValidationIssue = z.infer<
typeof JourneyDslValidationIssueSchema
>;
export type JourneyDslCompleteness = z.infer<
typeof JourneyDslCompletenessSchema
>;
export type JourneyDslNode = z.infer<typeof JourneyDslNodeSchema>;
export type JourneyDslEdge = z.infer<typeof JourneyDslEdgeSchema>;
export type JourneyDslExitRule = z.infer<typeof JourneyDslExitRuleSchema>;
export type JourneyDslCustomConversion = z.infer<
typeof JourneyDslCustomConversionSchema
>;
export type JourneyDslConversionGoal = z.infer<
typeof JourneyDslConversionGoalSchema
>;
export type JourneyDslJourney = z.infer<typeof JourneyDslJourneySchema>;
export type JourneyDslDocument = z.infer<typeof JourneyDslDocumentSchema>;
export type JourneyDslJourneyError = z.infer<
typeof JourneyDslJourneyErrorSchema
>;
export type GetJourneyDslGraphResponse = z.infer<
typeof GetJourneyDslGraphResponseSchema
>;
export type JourneyDslOutputCount = z.infer<typeof JourneyDslOutputCountSchema>;
export type JourneyDslTileType = z.infer<typeof JourneyDslTileTypeSchema>;
export type JourneyDslTileTypeManifest = z.infer<
typeof JourneyDslTileTypeManifestSchema
>;

export type GetJourneysParams = z.infer<typeof GetJourneysParamsSchema>;
export type TriggerJourneyParams = z.infer<typeof TriggerJourneyParamsSchema>;
Loading
Loading