@diagonal-hq/cli 0.0.0-stage → 0.2.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.
@@ -0,0 +1,22 @@
1
+ import './console';
2
+ export { HostError, WorkflowFailure, fail, validationError } from './errors';
3
+ export { invoke, setTransport, type HostTransport } from './transport';
4
+ export { transaction } from './transaction';
5
+ export { data } from './data';
6
+ export { mail, notification, sms } from './notifications';
7
+ export { InputField, InputSchema, v, type ContentDocument, type ContentNode, type ContentOptions, type FileVisibility, type Infer, type InferInput, type InputFieldKind, type InputFieldSpec, type InputShape, type Money, type RecordScope } from './input';
8
+ export { defineWorkflow, parseWorkflowInput, redirect, respond, validateWorkflowInput, type Identity, type InputErrors, type InputValidator, type RequestContext, type ScriptExecution, type WorkflowContext, type WorkflowDefinition, type WorkflowExecution } from './workflow';
9
+ export * from './generated/capabilities';
10
+ /** The embedding host selects its execution context through its supplied declarations. */
11
+ export interface ScriptHostContext {
12
+ }
13
+ /** Declaration-merged by the generated workspace module with one entry per resource. */
14
+ export interface WorkspaceResources {
15
+ }
16
+ /**
17
+ * Declaration-merged by the generated workspace module with one entry per notification type:
18
+ * its id, the unions of its template ids (all, email-enabled, and SMS-enabled), and the shape
19
+ * of its variables.
20
+ */
21
+ export interface WorkspaceNotificationTypes {
22
+ }
@@ -0,0 +1,168 @@
1
+ import type { ResourceKey } from './data';
2
+ /**
3
+ * A declared input field, as data. The compiler reads a workflow's input schema from its source
4
+ * and saves each field as a workflow input variable, so the platform validates a run's input
5
+ * against these rules before the script starts. The same data travels with the workflow at run
6
+ * time, where the SDK uses it to coerce string values to the declared types.
7
+ */
8
+ export interface InputFieldSpec {
9
+ readonly kind: InputFieldKind;
10
+ readonly optional: boolean;
11
+ readonly nullable: boolean;
12
+ /** Validation rules in the platform's rule syntax, such as `min:1` or `regex:/^[a-z]+$/`. */
13
+ readonly rules: readonly string[];
14
+ readonly label?: string;
15
+ readonly description?: string;
16
+ /** `select`: the allowed values. */
17
+ readonly options?: readonly (string | number)[];
18
+ /** `list`: the item field. */
19
+ readonly items?: InputFieldSpec;
20
+ /** `object`: the nested fields. */
21
+ readonly fields?: Readonly<Record<string, InputFieldSpec>>;
22
+ /** `record` and `recordReference`: the resource whose definitions or records the value must match. */
23
+ readonly resource?: string;
24
+ /** `recordReference`: the records the ID can name. The platform default is `active`. */
25
+ readonly scope?: RecordScope;
26
+ /** `file`: the accepted MIME types. */
27
+ readonly accept?: readonly string[];
28
+ /** `file`: the visibility of the file. */
29
+ readonly visibility?: FileVisibility;
30
+ /** `content`: the formatting the document may use. */
31
+ readonly content?: ContentOptions;
32
+ }
33
+ /**
34
+ * A money value as the platform stores it. It is a type alias, not an interface, so that it is
35
+ * assignable to the open money objects of capability inputs such as `money.format`.
36
+ */
37
+ export type Money = {
38
+ amount: number;
39
+ currency: string;
40
+ };
41
+ export type InputFieldKind = 'text' | 'email' | 'url' | 'uuid' | 'phone' | 'date' | 'datetime' | 'time' | 'timezone' | 'color' | 'number' | 'integer' | 'money' | 'boolean' | 'select' | 'list' | 'object' | 'record' | 'recordReference' | 'roleReference' | 'file' | 'content';
42
+ /** The records a record ID can name: `active`, `archived` (deleted), or `activeOrArchived` (all). */
43
+ export type RecordScope = 'active' | 'archived' | 'activeOrArchived';
44
+ export type FileVisibility = 'public' | 'private';
45
+ export interface ContentOptions {
46
+ /** `false` allows one paragraph only. The default is `true`. */
47
+ readonly multiLine?: boolean;
48
+ /** The marks the text may use. Each mark is allowed unless it is `false`. */
49
+ readonly extensions?: Readonly<Partial<Record<'bold' | 'italic' | 'strike' | 'underline' | 'link', boolean>>>;
50
+ /** The block nodes the document may use. Each node is allowed unless it is `false`. */
51
+ readonly allowedNodes?: Readonly<Partial<Record<'heading' | 'blockquote' | 'bulletList' | 'orderedList' | 'media', boolean>>>;
52
+ }
53
+ /** A rich text node in the platform's content format. */
54
+ export interface ContentNode {
55
+ type: string;
56
+ attrs?: Record<string, unknown>;
57
+ content?: ContentNode[];
58
+ marks?: {
59
+ type: string;
60
+ attrs?: Record<string, unknown>;
61
+ }[];
62
+ text?: string;
63
+ }
64
+ /** A rich text document in the platform's content format. */
65
+ export interface ContentDocument extends ContentNode {
66
+ type: 'doc';
67
+ }
68
+ /**
69
+ * One field of a workflow's input schema. `T` is the value type the handler receives; `O` is
70
+ * whether the key may be absent. Every modifier returns a new field, so a field can be shared.
71
+ */
72
+ export declare class InputField<T, O extends boolean = false> {
73
+ readonly $spec: InputFieldSpec;
74
+ /** Phantom members that carry the static types; they hold no value at run time. */
75
+ readonly $value: T;
76
+ readonly $optional: O;
77
+ constructor($spec: InputFieldSpec);
78
+ /** The key may be absent. */
79
+ optional(): InputField<T | undefined, true>;
80
+ /** The value may be `null`. */
81
+ nullable(): InputField<T | null, O>;
82
+ /** At least `value`: a number's value, a text's length, or a list's item count. */
83
+ min(value: number): InputField<T, O>;
84
+ /** At most `value`: a number's value, a text's length, or a list's item count. */
85
+ max(value: number): InputField<T, O>;
86
+ /** Between `min` and `max`, inclusive, measured as for `min` and `max`. */
87
+ between(min: number, max: number): InputField<T, O>;
88
+ /** Exactly `value`, measured as for `min` and `max`. */
89
+ size(value: number): InputField<T, O>;
90
+ /** The text matches the pattern. */
91
+ regex(pattern: RegExp): InputField<T, O>;
92
+ /** Any other platform validation rule, such as `starts_with:INV-` or `after:today`. */
93
+ rule(rule: string): InputField<T, O>;
94
+ label(label: string): InputField<T, O>;
95
+ description(description: string): InputField<T, O>;
96
+ }
97
+ export type InputShape = Record<string, InputField<unknown, boolean>>;
98
+ type Simplify<T> = {
99
+ [K in keyof T]: T[K];
100
+ } & {};
101
+ /** The value an input schema describes: optional fields become optional keys. */
102
+ export type InferInput<S extends InputShape> = Simplify<{
103
+ [K in keyof S as S[K]['$optional'] extends true ? never : K]: S[K]['$value'];
104
+ } & {
105
+ [K in keyof S as S[K]['$optional'] extends true ? K : never]?: S[K]['$value'];
106
+ }>;
107
+ /** The value a field or schema describes: `Infer<typeof Input>`. */
108
+ export type Infer<F extends InputField<unknown, boolean>> = F['$value'];
109
+ /** A workflow's input schema: the fields of an object. */
110
+ export declare class InputSchema<S extends InputShape> extends InputField<InferInput<S>> {
111
+ readonly shape: S;
112
+ constructor(shape: S);
113
+ }
114
+ /** Builders for a workflow's input schema. */
115
+ export declare const v: {
116
+ text: () => InputField<string, false>;
117
+ email: () => InputField<string, false>;
118
+ url: () => InputField<string, false>;
119
+ uuid: () => InputField<string, false>;
120
+ phone: () => InputField<string, false>;
121
+ /** A calendar date, `YYYY-MM-DD`. */
122
+ date: () => InputField<string, false>;
123
+ /** A date and time, ISO 8601. */
124
+ datetime: () => InputField<string, false>;
125
+ /** A time of day, `HH:MM`. */
126
+ time: () => InputField<string, false>;
127
+ timezone: () => InputField<string, false>;
128
+ color: () => InputField<string, false>;
129
+ number: () => InputField<number, false>;
130
+ integer: () => InputField<number, false>;
131
+ /** An amount of money: `amount` in the currency's minor units, such as cents, and the lowercase ISO 4217 `currency`. */
132
+ money: () => InputField<Money, false>;
133
+ boolean: () => InputField<boolean, false>;
134
+ /** One of `options`. */
135
+ select: <const V extends readonly (string | number)[]>(options: V) => InputField<V[number], false>;
136
+ /** A list whose items each satisfy `items`. */
137
+ list: <T>(items: InputField<T, boolean>) => InputField<Exclude<T, undefined>[], false>;
138
+ /** An object with the given fields. */
139
+ object: <S extends InputShape>(shape: S) => InputSchema<S>;
140
+ /**
141
+ * The field values of a record of `resource`, checked against that resource's field
142
+ * definitions: types, required fields, and each field's own rules.
143
+ */
144
+ record: <R extends ResourceKey>(resource: R) => InputField<Record<string, unknown>, false>;
145
+ /** The ID of an existing record of `resource`, in `scope`. */
146
+ recordReference: <R extends ResourceKey>(resource: R, options?: {
147
+ scope?: RecordScope;
148
+ }) => InputField<string, false>;
149
+ /** The ID of an existing role. */
150
+ roleReference: () => InputField<string, false>;
151
+ /**
152
+ * A file: the ID of an existing file, or the temporary path of an upload. A data call that
153
+ * writes the value to a file field of a record finalizes the upload.
154
+ */
155
+ file: (options?: {
156
+ accept?: readonly string[];
157
+ visibility?: FileVisibility;
158
+ }) => InputField<string, false>;
159
+ /** A rich text document with the formatting that `options` allows. */
160
+ content: (options?: ContentOptions) => InputField<ContentDocument, false>;
161
+ };
162
+ /**
163
+ * Converts the string forms a request carries (`"5"`, `"true"`) to the declared types. It runs
164
+ * after the platform validated the input against the same schema, so a value it cannot convert
165
+ * is left as it is.
166
+ */
167
+ export declare function coerceInput(spec: InputFieldSpec, value: unknown): unknown;
168
+ export {};
@@ -0,0 +1,47 @@
1
+ import type { ResourceKey } from './data';
2
+ import { type MailSendNotificationInput, type MailSendNotificationOutput, type NotificationSendBulkNotificationsInput, type NotificationSendBulkNotificationsOutput, type NotificationSendNotificationInput, type NotificationSetConsentInput, type NotificationSetConsentOutput, type SmsSendNotificationInput, type SmsSendNotificationOutput } from './generated/capabilities';
3
+ import type { WorkspaceNotificationTypes } from './index';
4
+ type Entry = WorkspaceNotificationTypes[keyof WorkspaceNotificationTypes];
5
+ type Declared = [keyof WorkspaceNotificationTypes] extends [never] ? false : true;
6
+ export type NotificationTypeKey = Declared extends true ? Entry extends {
7
+ id: infer I;
8
+ } ? I : never : string;
9
+ type EntryFor<T> = Extract<Entry, {
10
+ id: T;
11
+ }>;
12
+ /**
13
+ * The template ids of one notification type under `Channel`: every template, or only the
14
+ * templates with email or SMS on. Any string while the workspace declares no types.
15
+ */
16
+ export type NotificationTemplateKey<T, Channel extends 'template' | 'emailTemplate' | 'smsTemplate' = 'template'> = Declared extends true ? (EntryFor<T> extends Record<Channel, infer K> ? K : never) : string;
17
+ /** The variables one notification type declares; any dictionary while the workspace declares no types. */
18
+ export type NotificationVariables<T> = Declared extends true ? EntryFor<T> extends {
19
+ variables: infer V;
20
+ } ? V : never : Record<string, unknown>;
21
+ type NotificationInput<I, T, Channel extends 'template' | 'emailTemplate' | 'smsTemplate' = 'template'> = Omit<I, 'notificationType' | 'notificationTemplate' | 'variables'> & {
22
+ notificationType: T;
23
+ notificationTemplate?: NotificationTemplateKey<T, Channel>;
24
+ } & ({} extends NotificationVariables<T> ? {
25
+ variables?: NotificationVariables<T>;
26
+ } : {
27
+ variables: NotificationVariables<T>;
28
+ });
29
+ export declare const mail: {
30
+ sendNotification: <T extends NotificationTypeKey>(input: NotificationInput<MailSendNotificationInput, T, "emailTemplate">) => Promise<MailSendNotificationOutput>;
31
+ search: (input: import("./index").MailSearchInput) => Promise<import("./index").MailSearchOutput>;
32
+ send: (input: import("./index").MailSendInput) => Promise<import("./index").MailSendOutput>;
33
+ };
34
+ export declare const notification: {
35
+ sendNotification: <T extends NotificationTypeKey>(input: NotificationInput<NotificationSendNotificationInput, T>) => Promise<boolean>;
36
+ sendBulkNotifications: <T extends NotificationTypeKey, R extends ResourceKey>(input: Omit<NotificationInput<NotificationSendBulkNotificationsInput, T>, "resource"> & {
37
+ resource: R;
38
+ }) => Promise<NotificationSendBulkNotificationsOutput>;
39
+ setConsent: <R extends NotificationSetConsentInput["resource"]>(input: Omit<NotificationSetConsentInput, "resource"> & {
40
+ resource: R;
41
+ }) => Promise<NotificationSetConsentOutput>;
42
+ send: (input: import("./index").NotificationSendInput) => Promise<import("./index").NotificationSendOutput>;
43
+ };
44
+ export declare const sms: {
45
+ sendNotification: <T extends NotificationTypeKey>(input: NotificationInput<SmsSendNotificationInput, T, "smsTemplate">) => Promise<SmsSendNotificationOutput>;
46
+ };
47
+ export {};
@@ -0,0 +1,10 @@
1
+ import type { HostTransport } from './transport';
2
+ export declare class SerialTransactionTransport implements HostTransport {
3
+ private readonly transport;
4
+ private pending;
5
+ private settled;
6
+ private transactionOpen;
7
+ private closing;
8
+ constructor(transport: HostTransport);
9
+ call(name: string, input: Record<string, unknown>): Promise<unknown>;
10
+ }
@@ -0,0 +1,26 @@
1
+ import { type HostTransport } from '../transport';
2
+ import { type InputValidator, type WorkflowContext, type WorkflowDefinition } from '../workflow';
3
+ export declare class MockTransport implements HostTransport {
4
+ private readonly handlers;
5
+ readonly calls: Array<{
6
+ name: string;
7
+ input: unknown;
8
+ }>;
9
+ constructor(handlers: Partial<Record<string, (input: any) => unknown>>);
10
+ call(name: string, input: Record<string, unknown>): Promise<unknown>;
11
+ }
12
+ export declare function runWorkflow<I, O>(workflow: WorkflowDefinition<I, O>, input: I, options?: {
13
+ transport?: HostTransport;
14
+ context?: Omit<Partial<WorkflowContext>, 'execution'> & {
15
+ execution?: Partial<WorkflowContext['execution']>;
16
+ };
17
+ /** The module's `validateInput` export; it runs before the handler, as in the sandbox. */
18
+ validateInput?: InputValidator<I>;
19
+ }): Promise<O>;
20
+ export declare const fake: {
21
+ reset(): /*elided*/ any;
22
+ email: () => string;
23
+ name: () => string;
24
+ uuid: () => string;
25
+ number: (min?: number, max?: number) => number;
26
+ };
@@ -0,0 +1 @@
1
+ export declare function transaction<T>(fn: () => Promise<T>): Promise<T>;
@@ -0,0 +1,5 @@
1
+ export interface HostTransport {
2
+ call(name: string, input: Record<string, unknown>): Promise<unknown>;
3
+ }
4
+ export declare function setTransport(transport: HostTransport | null): void;
5
+ export declare function invoke<T>(name: string, input: object): Promise<T>;
@@ -0,0 +1,105 @@
1
+ import type { ResourceKeyWith } from './data';
2
+ import type { ScriptHostContext } from './index';
3
+ import { type InferInput, type InputFieldSpec, type InputSchema, type InputShape } from './input';
4
+ export interface Identity {
5
+ id: string;
6
+ /** The identity's resource has the Authenticatable capability. */
7
+ resourceId: ResourceKeyWith<'authenticatable'>;
8
+ email: string | null;
9
+ name: string | null;
10
+ permissions: string[];
11
+ }
12
+ export interface RequestContext {
13
+ method: string;
14
+ url: string;
15
+ params: Record<string, string>;
16
+ query: Record<string, unknown>;
17
+ body: Record<string, unknown>;
18
+ headers: Record<string, string>;
19
+ }
20
+ export interface ScriptExecution {
21
+ id: string;
22
+ kind: 'workflow' | 'embedded';
23
+ owner: {
24
+ type: string;
25
+ id: string;
26
+ };
27
+ }
28
+ export interface WorkflowExecution extends ScriptExecution {
29
+ kind: 'workflow';
30
+ workflowId: string;
31
+ }
32
+ export interface WorkflowContext {
33
+ user: Identity | null;
34
+ callingUser: Identity | null;
35
+ trigger: Record<string, unknown>;
36
+ request: RequestContext | null;
37
+ workspace: {
38
+ id: string;
39
+ timezone: string;
40
+ };
41
+ execution: ScriptHostContext extends {
42
+ embedded: true;
43
+ } ? ScriptExecution : WorkflowExecution;
44
+ now: string;
45
+ }
46
+ export interface WorkflowDefinition<I, O> {
47
+ readonly $diagonal: 'workflow';
48
+ /** The declared input schema, when the workflow has one. */
49
+ readonly input?: InputFieldSpec;
50
+ /** Runs the handler with input that {@link parseWorkflowInput} already converted. */
51
+ run(input: I, ctx: WorkflowContext): Promise<O>;
52
+ }
53
+ /**
54
+ * Converts a run's raw input to the declared types. A runner calls it once and passes the result
55
+ * to both `validateInput` and `run`, so the validator and the handler receive the same input.
56
+ */
57
+ export declare function parseWorkflowInput<I>(workflow: WorkflowDefinition<I, unknown>, input: unknown): I;
58
+ /**
59
+ * The errors an input validator reports, keyed by input field. A key outside the input's own fields
60
+ * (a nested path such as `tags.0`) is allowed; an empty or absent message means the field is valid.
61
+ */
62
+ export type InputErrors<I> = Partial<Record<keyof I & string, string | string[]>> & Record<string, string | string[] | undefined>;
63
+ /**
64
+ * The `validateInput` export a script may define beside its workflow. It runs before the handler
65
+ * with the same input and context; when it reports any error, the run ends with a 422 and those
66
+ * errors, and the handler does not run.
67
+ */
68
+ export type InputValidator<I> = (input: I, ctx: WorkflowContext) => InputErrors<I> | void | Promise<InputErrors<I> | void>;
69
+ /** Runs a script's input validator and ends the run with a validation error when it reports one. */
70
+ export declare function validateWorkflowInput<I>(validator: InputValidator<I>, input: I, ctx: WorkflowContext): Promise<void>;
71
+ type Handler<I, O> = (input: I, ctx: WorkflowContext) => Promise<O> | O;
72
+ /**
73
+ * Declares a workflow. With an input schema first, the platform validates every run's input
74
+ * against it before the script starts, and the handler's `input` type comes from the schema.
75
+ */
76
+ export declare function defineWorkflow<S extends InputShape, O = unknown>(input: InputSchema<S>, handler: Handler<InferInput<S>, O>): WorkflowDefinition<InferInput<S>, O>;
77
+ export declare function defineWorkflow<I = Record<string, unknown>, O = unknown>(handler: Handler<I, O>): WorkflowDefinition<I, O>;
78
+ export type ResponseOutput = {
79
+ $response_status: number;
80
+ $response_body: unknown;
81
+ $response_content_type: string;
82
+ };
83
+ export type RedirectType = 'internal' | 'external' | 'back';
84
+ export type RedirectOutput = {
85
+ redirect_url: string;
86
+ redirect_type: RedirectType;
87
+ permanent: boolean;
88
+ success?: string;
89
+ };
90
+ export declare function respond(body: unknown, options?: {
91
+ status?: number;
92
+ contentType?: string;
93
+ }): ResponseOutput;
94
+ /**
95
+ * Builds the output of a graph `http.redirect` step, with the same `redirect_type` and
96
+ * `permanent` keys. Without a `type`, a path such as `/done` redirects as `internal`. Any other
97
+ * URL, including a protocol-relative `//host`, redirects as `external`, because an Inertia
98
+ * request cannot follow a plain redirect to another origin. A `back` redirect is never permanent.
99
+ */
100
+ export declare function redirect(url: string, options?: {
101
+ success?: string;
102
+ type?: RedirectType;
103
+ permanent?: boolean;
104
+ }): RedirectOutput;
105
+ export {};
@@ -0,0 +1,3 @@
1
+ export declare const resources: Record<string, string>;
2
+ export declare const notificationTypes: Record<string, string>;
3
+ export declare const notificationTemplates: Record<string, Record<string, string>>;
package/package.json CHANGED
@@ -1,6 +1,43 @@
1
1
  {
2
2
  "name": "@diagonal-hq/cli",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.2.0",
4
+ "description": "The diagonal CLI and the configuration helpers for file-based Diagonal workspaces.",
5
+ "license": "SEE LICENSE IN LICENSE",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/Diagonal-HQ/diagonal.git",
9
+ "directory": "packages/cli"
10
+ },
11
+ "type": "module",
12
+ "types": "./dist/index.d.ts",
13
+ "exports": {
14
+ ".": {
15
+ "types": "./dist/index.d.ts",
16
+ "default": "./dist/index.js"
17
+ },
18
+ "./package.json": "./package.json"
19
+ },
20
+ "bin": {
21
+ "diagonal": "./bin/diagonal.mjs"
22
+ },
23
+ "engines": {
24
+ "node": ">=24"
25
+ },
26
+ "publishConfig": {
27
+ "access": "public"
28
+ },
29
+ "files": [
30
+ "bin/diagonal.mjs",
31
+ "dist/cli.mjs",
32
+ "dist/index.js",
33
+ "dist/fields.generated.js",
34
+ "dist/index.d.ts",
35
+ "dist/fields.generated.d.ts",
36
+ "dist/capabilities.generated.d.ts",
37
+ "dist/globals.d.ts",
38
+ "dist/sdk-contract.d.ts",
39
+ "dist/workflow",
40
+ "LICENSE",
41
+ "README.md"
42
+ ]
43
+ }