File
Blob: src/contracts/common.ts
| 1 | import { eg, type TypeFromCodec } from "@cloudflare/util-en-garde"; |
| 2 | |
| 3 | const DURABLE_ENTITY_ID_PATTERN = /^(usr|prj|run|whk)_[0-9A-Za-z]{22}$/u; |
| 4 | const SESSION_TOKEN_PATTERN = /^[A-Za-z0-9_-]{20,}$/u; |
| 5 | const ISO_DATE_TIME_PATTERN = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/u; |
| 6 | const SLUG_PATTERN = /^[A-Za-z0-9_-]+$/u; |
| 7 | const COMMIT_SHA_PATTERN = /^[A-Fa-f0-9]{7,64}$/u; |
| 8 | |
| 9 | const brandedString = <Name extends string>(name: Name, predicate: (value: string) => boolean) => |
| 10 | eg.brand(name, eg.string, predicate); |
| 11 | |
| 12 | const isIsoDateTime = (value: string): boolean => |
| 13 | ISO_DATE_TIME_PATTERN.test(value) && !Number.isNaN(Date.parse(value)) && new Date(value).toISOString() === value; |
| 14 | |
| 15 | const isNonEmptyString = (value: string): boolean => value.length > 0; |
| 16 | |
| 17 | export const EntityId = brandedString("DurableEntityId", (value) => DURABLE_ENTITY_ID_PATTERN.test(value)); |
| 18 | export type EntityId = TypeFromCodec<typeof EntityId>; |
| 19 | |
| 20 | export const OpaqueId = brandedString("OpaqueId", isNonEmptyString); |
| 21 | export type OpaqueId = TypeFromCodec<typeof OpaqueId>; |
| 22 | |
| 23 | export const UserId = brandedString("UserId", (value) => /^usr_[0-9A-Za-z]{22}$/u.test(value)); |
| 24 | export type UserId = TypeFromCodec<typeof UserId>; |
| 25 | |
| 26 | export const ProjectId = brandedString("ProjectId", (value) => /^prj_[0-9A-Za-z]{22}$/u.test(value)); |
| 27 | export type ProjectId = TypeFromCodec<typeof ProjectId>; |
| 28 | |
| 29 | export const RunId = brandedString("RunId", (value) => /^run_[0-9A-Za-z]{22}$/u.test(value)); |
| 30 | export type RunId = TypeFromCodec<typeof RunId>; |
| 31 | |
| 32 | export const WebhookId = brandedString("WebhookId", (value) => /^whk_[0-9A-Za-z]{22}$/u.test(value)); |
| 33 | export type WebhookId = TypeFromCodec<typeof WebhookId>; |
| 34 | |
| 35 | export const SessionId = brandedString("SessionId", (value) => SESSION_TOKEN_PATTERN.test(value)); |
| 36 | export type SessionId = TypeFromCodec<typeof SessionId>; |
| 37 | |
| 38 | export const IsoDateTime = brandedString("IsoDateTime", isIsoDateTime); |
| 39 | export type IsoDateTime = TypeFromCodec<typeof IsoDateTime>; |
| 40 | |
| 41 | export const UnixTimestampMs = eg.brand("UnixTimestampMs", eg.number, (value) => Number.isInteger(value) && value >= 0); |
| 42 | export type UnixTimestampMs = TypeFromCodec<typeof UnixTimestampMs>; |
| 43 | |
| 44 | export const OwnerSlug = brandedString("OwnerSlug", (value) => SLUG_PATTERN.test(value)); |
| 45 | export type OwnerSlug = TypeFromCodec<typeof OwnerSlug>; |
| 46 | |
| 47 | export const ProjectSlug = brandedString("ProjectSlug", (value) => SLUG_PATTERN.test(value)); |
| 48 | export type ProjectSlug = TypeFromCodec<typeof ProjectSlug>; |
| 49 | |
| 50 | export const BranchName = brandedString("BranchName", isNonEmptyString); |
| 51 | export type BranchName = TypeFromCodec<typeof BranchName>; |
| 52 | |
| 53 | export const CommitSha = brandedString("CommitSha", (value) => COMMIT_SHA_PATTERN.test(value)); |
| 54 | export type CommitSha = TypeFromCodec<typeof CommitSha>; |
| 55 | |
| 56 | export const TriggerType = eg.union([eg.literal("manual"), eg.literal("webhook")]); |
| 57 | export type TriggerType = TypeFromCodec<typeof TriggerType>; |
| 58 | |
| 59 | export const RunStatus = eg.union([ |
| 60 | eg.literal("queued"), |
| 61 | eg.literal("starting"), |
| 62 | eg.literal("running"), |
| 63 | eg.literal("cancel_requested"), |
| 64 | eg.literal("canceling"), |
| 65 | eg.literal("passed"), |
| 66 | eg.literal("failed"), |
| 67 | eg.literal("canceled"), |
| 68 | ]); |
| 69 | export type RunStatus = TypeFromCodec<typeof RunStatus>; |
| 70 | |
| 71 | export const isRunStatus = (value: unknown): value is RunStatus => RunStatus.is(value); |
| 72 | |
| 73 | export const toRunStatusOrNull = (value: string | undefined): RunStatus | null => |
| 74 | value && isRunStatus(value) ? value : null; |
| 75 | |
| 76 | export const WebhookProvider = eg.union([eg.literal("github"), eg.literal("gitlab"), eg.literal("gitea")]); |
| 77 | export type WebhookProvider = TypeFromCodec<typeof WebhookProvider>; |
| 78 | |
| 79 | export const UserSummary = eg.exactStrict( |
| 80 | eg.object({ |
| 81 | id: UserId, |
| 82 | slug: OwnerSlug, |
| 83 | email: eg.string, |
| 84 | displayName: eg.string, |
| 85 | createdAt: IsoDateTime, |
| 86 | disabledAt: eg.union([IsoDateTime, eg.null]), |
| 87 | }), |
| 88 | ); |
| 89 | export type UserSummary = TypeFromCodec<typeof UserSummary>; |