@codenotch/process 0.0.0-dev
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 +51 -0
- package/dist/activity.mjs +14 -0
- package/dist/template.mjs +21 -0
- package/dist/types/activity/index.d.ts +52 -0
- package/dist/types/lib/bindings.d.ts +48 -0
- package/dist/types/lib/canonical-json.d.ts +10 -0
- package/dist/types/lib/collections.d.ts +12 -0
- package/dist/types/lib/connectors.d.ts +299 -0
- package/dist/types/lib/context.d.ts +307 -0
- package/dist/types/lib/credits.d.ts +71 -0
- package/dist/types/lib/decimal.d.ts +72 -0
- package/dist/types/lib/duration.d.ts +40 -0
- package/dist/types/lib/encoding.d.ts +27 -0
- package/dist/types/lib/errors.d.ts +57 -0
- package/dist/types/lib/hash.d.ts +18 -0
- package/dist/types/lib/index.d.ts +25 -0
- package/dist/types/lib/instant.d.ts +41 -0
- package/dist/types/lib/meters.d.ts +61 -0
- package/dist/types/lib/registry.d.ts +131 -0
- package/dist/types/lib/renderings.d.ts +75 -0
- package/dist/types/lib/subscriptions.d.ts +61 -0
- package/dist/types/lib/types.d.ts +89 -0
- package/dist/types/lib/urls.d.ts +36 -0
- package/dist/types/lib/userTasks.d.ts +119 -0
- package/dist/types/lib/validators.d.ts +22 -0
- package/dist/types/lib/webPush.d.ts +30 -0
- package/dist/types/template/index.d.ts +17 -0
- package/dist/types/testing/api.d.ts +175 -0
- package/dist/types/testing/index.d.ts +2 -0
- package/dist/types/testing/main.d.ts +1 -0
- package/dist/types/testing/stdlib.d.ts +79 -0
- package/package.json +43 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Duration } from './duration';
|
|
2
|
+
import type { DurationLike } from './types';
|
|
3
|
+
export type InstantUnit = 'year' | 'month' | 'week' | 'day' | 'hour' | 'minute' | 'second';
|
|
4
|
+
/**
|
|
5
|
+
* An instant in time — a thin ISO-8601 UTC wrapper, culture-safe by construction.
|
|
6
|
+
* Immutable; serializes to an ISO string (toJSON) in records, queries and history.
|
|
7
|
+
* A datetime without a zone offset is read as UTC — never as the host's local time.
|
|
8
|
+
*/
|
|
9
|
+
export declare class Instant {
|
|
10
|
+
private readonly ms;
|
|
11
|
+
private constructor();
|
|
12
|
+
/** Parses an ISO-8601 string (or adopts an Instant / epoch milliseconds). */
|
|
13
|
+
static from(value: string | number | Instant): Instant;
|
|
14
|
+
/** An Instant from milliseconds since the Unix epoch (UTC). */
|
|
15
|
+
static fromEpochMillis(epochMillis: number): Instant;
|
|
16
|
+
/** Standard comparator: negative when a < b, 0 when equal, positive when a > b. */
|
|
17
|
+
static compare(a: Instant | string, b: Instant | string): number;
|
|
18
|
+
/** This instant shifted forward. Calendar components (months/years) clamp the day (Jan 31 + 1 month = Feb 28/29). */
|
|
19
|
+
plus(duration: DurationLike): Instant;
|
|
20
|
+
/** This instant shifted backward. */
|
|
21
|
+
minus(duration: DurationLike): Instant;
|
|
22
|
+
/** The exact elapsed time from `other` to this instant, as a fixed Duration. */
|
|
23
|
+
diff(other: Instant | string): Duration;
|
|
24
|
+
isBefore(other: Instant | string): boolean;
|
|
25
|
+
isAfter(other: Instant | string): boolean;
|
|
26
|
+
equals(other: Instant | string): boolean;
|
|
27
|
+
/** The start of the containing unit, in UTC. Weeks start on Monday (ISO-8601). */
|
|
28
|
+
startOf(unit: InstantUnit): Instant;
|
|
29
|
+
/** The last millisecond of the containing unit, in UTC. */
|
|
30
|
+
endOf(unit: InstantUnit): Instant;
|
|
31
|
+
/** Milliseconds since the Unix epoch. */
|
|
32
|
+
epochMillis(): number;
|
|
33
|
+
/** ISO-8601 UTC text with millisecond precision ('2026-01-31T09:30:00.000Z'). */
|
|
34
|
+
toISOString(): string;
|
|
35
|
+
toJSON(): string;
|
|
36
|
+
toString(): string;
|
|
37
|
+
/** Epoch milliseconds — makes <, >, <=, >= comparisons between Instants work. */
|
|
38
|
+
valueOf(): number;
|
|
39
|
+
}
|
|
40
|
+
/** @internal Instant factory used by the runtime (ctx.now). */
|
|
41
|
+
export declare const instantFromEpochMillis: (epochMillis: number) => Instant;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { ActivityOptions } from './context';
|
|
2
|
+
/** Who the units belong to: a user, or nobody ({} — still counted, recorded without a user). */
|
|
3
|
+
export type MeterTarget = {
|
|
4
|
+
user?: string;
|
|
5
|
+
};
|
|
6
|
+
export interface MeterAddOptions {
|
|
7
|
+
/**
|
|
8
|
+
* Optional dedup reference: the same reference never counts twice on this meter.
|
|
9
|
+
* Defaults to the activity execution's identity, so a crash re-run counts once anyway.
|
|
10
|
+
*/
|
|
11
|
+
reference?: string;
|
|
12
|
+
}
|
|
13
|
+
export interface MeterAddResult {
|
|
14
|
+
/** False = the meter's period total is at its configured cap; nothing was counted. */
|
|
15
|
+
applied: boolean;
|
|
16
|
+
/** True when the reference was already recorded. */
|
|
17
|
+
replayed: boolean;
|
|
18
|
+
/** The addressed total after the add (or as-is when refused/replayed). */
|
|
19
|
+
total: number;
|
|
20
|
+
}
|
|
21
|
+
export interface MeterTotalQuery {
|
|
22
|
+
user?: string;
|
|
23
|
+
/** 'YYYY-MM'; default the current period. */
|
|
24
|
+
period?: string;
|
|
25
|
+
}
|
|
26
|
+
export interface MeterBreakdownQuery {
|
|
27
|
+
/** 'YYYY-MM'; default the current period. */
|
|
28
|
+
period?: string;
|
|
29
|
+
/** Page size, default 100. */
|
|
30
|
+
limit?: number;
|
|
31
|
+
/** The previous page's `next` cursor. */
|
|
32
|
+
after?: string;
|
|
33
|
+
}
|
|
34
|
+
export interface MeterBreakdownRow {
|
|
35
|
+
/** Null = the usage recorded without a user. */
|
|
36
|
+
user: string | null;
|
|
37
|
+
total: number;
|
|
38
|
+
}
|
|
39
|
+
export interface MeterBreakdownPage {
|
|
40
|
+
items: MeterBreakdownRow[];
|
|
41
|
+
totalCount: number;
|
|
42
|
+
/** Pass as `after` to continue; null = no more rows. */
|
|
43
|
+
next: string | null;
|
|
44
|
+
}
|
|
45
|
+
export interface MeterHandle {
|
|
46
|
+
/**
|
|
47
|
+
* Count units on a custom meter (framework meters refuse with metering.reserved). A
|
|
48
|
+
* refusal ({applied: false}) means the configured cap is reached — a result to branch
|
|
49
|
+
* on, not an error.
|
|
50
|
+
*/
|
|
51
|
+
add(target: MeterTarget, amount: number, opts?: MeterAddOptions & ActivityOptions): Promise<MeterAddResult>;
|
|
52
|
+
/** The addressed total; 0 when nothing was recorded. Omit user for the no-user row. */
|
|
53
|
+
total(query?: MeterTotalQuery, opts?: ActivityOptions): Promise<number>;
|
|
54
|
+
/** Per-user totals of one period, cursor-paged — the billing loop's iterator. */
|
|
55
|
+
breakdown(query?: MeterBreakdownQuery, opts?: ActivityOptions): Promise<MeterBreakdownPage>;
|
|
56
|
+
}
|
|
57
|
+
/** @internal Same call channel as the connector handles. */
|
|
58
|
+
type MeterCall = <T>(name: string, input: Record<string, unknown>, opts: ActivityOptions | undefined) => Promise<T>;
|
|
59
|
+
/** @internal ctx.meter factory over the activity channel — bound by createContext. */
|
|
60
|
+
export declare const createMeterFactory: (call: MeterCall) => (name: string) => MeterHandle;
|
|
61
|
+
export {};
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import type { ConnectorKindEvents, ProjectConnectors } from './connectors';
|
|
2
|
+
import type { Context } from './context';
|
|
3
|
+
import type { DeliveryOptions, Json, Serializable } from './types';
|
|
4
|
+
/** Event ids a process can send to its caller, with their payload types. */
|
|
5
|
+
export type EventMap = Record<string, Serializable>;
|
|
6
|
+
/** Phantom carrier for the declared event map (the value exists only for type inference). */
|
|
7
|
+
export interface EventsDecl<E extends EventMap> {
|
|
8
|
+
readonly __events?: E;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Declares the events this process can send to its caller, typed on both sides:
|
|
12
|
+
* `events: events<{ 'sla.breach': { ticketId: string } }>()`.
|
|
13
|
+
*/
|
|
14
|
+
export declare const events: <E extends EventMap>() => EventsDecl<E>;
|
|
15
|
+
/** Opaque, typed handle to a defined process — what process() returns. */
|
|
16
|
+
export interface Process<In = unknown, Out = unknown, E extends EventMap = {}> {
|
|
17
|
+
readonly id: string;
|
|
18
|
+
/** @internal Phantom fields carrying the type parameters. */
|
|
19
|
+
readonly __types?: {
|
|
20
|
+
input: In;
|
|
21
|
+
output: Out;
|
|
22
|
+
events: E;
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
/** Auth scope of a process. Gates EXTERNAL starts only. */
|
|
26
|
+
export type AuthScope = 'public' | 'external' | 'internal' | 'system';
|
|
27
|
+
export interface ProcessAuth {
|
|
28
|
+
scope: AuthScope;
|
|
29
|
+
/** Required roles of the caller. */
|
|
30
|
+
roles?: string[];
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Start concurrency control. The key is GLOBAL to the project — any process claiming
|
|
34
|
+
* the same key conflicts; scope a key to one process by prefixing its name.
|
|
35
|
+
* limit > 1 turns the mutex into a semaphore.
|
|
36
|
+
*/
|
|
37
|
+
export interface Concurrency<In> {
|
|
38
|
+
key: (input: In) => string;
|
|
39
|
+
/** Max live instances holding this key. Default 1. */
|
|
40
|
+
limit?: number;
|
|
41
|
+
/**
|
|
42
|
+
* ignore = skip the new start; replace = supersede a holder (the replaced run is
|
|
43
|
+
* cancelled and unwinds its compensation chain); queue = run when a slot frees.
|
|
44
|
+
*/
|
|
45
|
+
onConflict: 'ignore' | 'replace' | 'queue';
|
|
46
|
+
}
|
|
47
|
+
export interface ProcessOptions {
|
|
48
|
+
logs?: 'on' | 'off';
|
|
49
|
+
}
|
|
50
|
+
/** Opaque start trigger. Triggers gate external starts; ctx.call bypasses them. */
|
|
51
|
+
export interface Trigger {
|
|
52
|
+
/** @internal */
|
|
53
|
+
readonly kind: string;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* API start triggers. Path/query params and body merge into the input. The HTTP
|
|
57
|
+
* response is what ctx.respond sent — or, when the process returns without
|
|
58
|
+
* responding, 200 with the output as JSON body. Uncaught errors map to defaults
|
|
59
|
+
* (NotFoundError → 404, ProcessError → 400 with its code, anything else → 500).
|
|
60
|
+
*/
|
|
61
|
+
export declare const api: Readonly<{
|
|
62
|
+
get: (route: string) => Trigger;
|
|
63
|
+
post: (route: string) => Trigger;
|
|
64
|
+
put: (route: string) => Trigger;
|
|
65
|
+
delete: (route: string) => Trigger;
|
|
66
|
+
}>;
|
|
67
|
+
/** Signal start trigger. Hierarchical refs: 'ticket' receives 'ticket.done.1'. */
|
|
68
|
+
export declare const signal: (ref: string, opts?: DeliveryOptions) => Trigger;
|
|
69
|
+
/**
|
|
70
|
+
* A connector binding name. The generated connectors.d.ts (augmenting ProjectConnectors)
|
|
71
|
+
* makes the project's binding names autocomplete; any string stays accepted.
|
|
72
|
+
*/
|
|
73
|
+
export type ConnectorBindingName = (keyof ProjectConnectors & string) | (string & {});
|
|
74
|
+
/**
|
|
75
|
+
* The event types a binding can receive: its kind's union when the binding and the kind
|
|
76
|
+
* are known (ProjectConnectors + ConnectorKindEvents), any string otherwise.
|
|
77
|
+
*/
|
|
78
|
+
export type ConnectorEventName<B> = B extends keyof ProjectConnectors ? ProjectConnectors[B] extends keyof ConnectorKindEvents ? ConnectorKindEvents[ProjectConnectors[B]] | (string & {}) : string : string;
|
|
79
|
+
/**
|
|
80
|
+
* Connector-event start trigger: starts the process when the connector bound to the
|
|
81
|
+
* named manifest parameter receives a verified provider event — a Slack message
|
|
82
|
+
* ('message'), a payment webhook ('payment.succeeded'), … The binding must be named (a
|
|
83
|
+
* trigger cannot infer the kind). Input = { type, eventRef, payload }. With a generated
|
|
84
|
+
* connectors.d.ts both arguments autocomplete; plain strings always compile.
|
|
85
|
+
*/
|
|
86
|
+
export declare const connectorEvent: <B extends ConnectorBindingName>(binding: B, event: ConnectorEventName<B>, opts?: DeliveryOptions) => Trigger;
|
|
87
|
+
/**
|
|
88
|
+
* Timer start trigger — a cron expression ('0 6 * * 1-5'), an ISO-8601 recurrence
|
|
89
|
+
* ('R/PT4H'), a one-shot ISO date, or any DurationLike repeat interval.
|
|
90
|
+
*/
|
|
91
|
+
export declare const timer: (schedule: string | number | object, opts?: DeliveryOptions) => Trigger;
|
|
92
|
+
/**
|
|
93
|
+
* Deploy-lifecycle start: runs after every successful install or version update of
|
|
94
|
+
* the project. install({ once: true }) runs on the first install only. A failure
|
|
95
|
+
* lands in the deployment report; it does not roll the deploy back.
|
|
96
|
+
*/
|
|
97
|
+
export declare const install: (opts?: {
|
|
98
|
+
once?: boolean;
|
|
99
|
+
}) => Trigger;
|
|
100
|
+
/** Manual start — started via API/portal only. This is the default when trigger is absent. */
|
|
101
|
+
export declare const manual: () => Trigger;
|
|
102
|
+
export interface ProcessDefinition<In, Out, E extends EventMap = {}> {
|
|
103
|
+
/** Unique per project. */
|
|
104
|
+
id: string;
|
|
105
|
+
description?: string;
|
|
106
|
+
/** MANDATORY start authorization. */
|
|
107
|
+
auth: ProcessAuth;
|
|
108
|
+
/**
|
|
109
|
+
* How the process can be started. Absent = manual(). An array makes one process
|
|
110
|
+
* reachable several ways: [signal('order.paid'), api.post('/orders/paid')].
|
|
111
|
+
*/
|
|
112
|
+
trigger?: Trigger | Trigger[];
|
|
113
|
+
concurrency?: Concurrency<In>;
|
|
114
|
+
/** Events this process can send to its caller (typed on both sides). */
|
|
115
|
+
events?: EventsDecl<E>;
|
|
116
|
+
options?: ProcessOptions;
|
|
117
|
+
run(ctx: Context<E>, input: In): Promise<Out>;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Declares a process. The definition is registered at bundle load; the returned
|
|
121
|
+
* handle is what ctx.call takes for a fully typed call.
|
|
122
|
+
*/
|
|
123
|
+
export declare const process: <In, Out, E extends EventMap = {}>(def: ProcessDefinition<In, Out, E>) => Process<In, Out, E>;
|
|
124
|
+
/** @internal Deploy-time metadata of every declared process. */
|
|
125
|
+
export declare const processMetadata: () => Json[];
|
|
126
|
+
/** @internal Lookup for the bootstrap (__start / __concurrencyKey). */
|
|
127
|
+
export declare const getProcessDefinition: (id: string | null | undefined) => ProcessDefinition<never, unknown, EventMap> | undefined;
|
|
128
|
+
/** @internal */
|
|
129
|
+
export declare const registrySize: () => number;
|
|
130
|
+
/** @internal The single declared process, when there is exactly one. */
|
|
131
|
+
export declare const singleProcessDefinition: () => ProcessDefinition<never, unknown, EventMap> | undefined;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { ActivityOptions } from './context';
|
|
2
|
+
import type { EmailHandle, EmailMessage } from './connectors';
|
|
3
|
+
import type { Serializable, Template } from './types';
|
|
4
|
+
/** What an email template's render function returns. */
|
|
5
|
+
export interface EmailTemplatePayload {
|
|
6
|
+
subject: string;
|
|
7
|
+
html: string;
|
|
8
|
+
/** Plain-text alternative. */
|
|
9
|
+
text?: string;
|
|
10
|
+
}
|
|
11
|
+
/** What a pdf template's render function returns: print HTML, turned into bytes server-side. */
|
|
12
|
+
export interface PdfTemplatePayload {
|
|
13
|
+
html: string;
|
|
14
|
+
}
|
|
15
|
+
/** Page setup a pdf template declares (template.pdf({ … }, render)). A template's own @page CSS wins. */
|
|
16
|
+
export interface PdfPageOptions {
|
|
17
|
+
/** Paper format; default A4. */
|
|
18
|
+
format?: 'A3' | 'A4' | 'A5' | 'Letter' | 'Legal';
|
|
19
|
+
/** Page margins: '20mm', '0.5in' or a bare number of millimeters. Default 20mm. */
|
|
20
|
+
margins?: string | number;
|
|
21
|
+
landscape?: boolean;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Metadata of a stored rendering. The bytes never travel through the process: consumers
|
|
25
|
+
* resolve them by id (attach to an email, or read through the renderings API).
|
|
26
|
+
*/
|
|
27
|
+
export interface StoredRendering {
|
|
28
|
+
renderingId: string;
|
|
29
|
+
filename: string;
|
|
30
|
+
contentType: string;
|
|
31
|
+
sizeBytes: number;
|
|
32
|
+
}
|
|
33
|
+
/** Options of ctx.render.email — plain activity options; email renderings are never stored. */
|
|
34
|
+
export type RenderOptions = ActivityOptions;
|
|
35
|
+
/** Options of ctx.render.pdf — the stored document's name and its listing metadata. */
|
|
36
|
+
export type PdfRenderOptions = ActivityOptions & {
|
|
37
|
+
/** Stored file name; default <template>.pdf. */
|
|
38
|
+
filename?: string;
|
|
39
|
+
/** The user the document is about (an invoice's customer) — opaque, filterable in the listing. */
|
|
40
|
+
user?: string;
|
|
41
|
+
/** Freeform labels ('invoice', 'pos_london', …) — filterable in the listing. No '|'. */
|
|
42
|
+
tags?: string[];
|
|
43
|
+
};
|
|
44
|
+
export type RenderSendOptions = ActivityOptions & {
|
|
45
|
+
/** The connector binding to send through; default: the project's only one of the kind. */
|
|
46
|
+
connector?: string;
|
|
47
|
+
};
|
|
48
|
+
/** A rendered email: the payload, plus send() through the project's email connector. */
|
|
49
|
+
export type RenderedEmail = EmailTemplatePayload & {
|
|
50
|
+
/**
|
|
51
|
+
* Send this rendering as one email: the rendered subject/html/text merged under the
|
|
52
|
+
* given message (the message wins on conflict). Same contract as ctx.email().send —
|
|
53
|
+
* at-least-once, single attempt by default.
|
|
54
|
+
*/
|
|
55
|
+
send(message: Omit<EmailMessage, 'subject' | 'html' | 'text'> & Partial<Pick<EmailMessage, 'subject' | 'html' | 'text'>>, opts?: RenderSendOptions): Promise<void>;
|
|
56
|
+
};
|
|
57
|
+
export interface RenderApi {
|
|
58
|
+
/** Render an email template. The template is the imported handle, or its manifest name. */
|
|
59
|
+
email<D>(template: Template<D, 'email'>, data: D, opts?: RenderOptions): Promise<RenderedEmail>;
|
|
60
|
+
email(template: string, data?: Serializable, opts?: RenderOptions): Promise<RenderedEmail>;
|
|
61
|
+
/**
|
|
62
|
+
* Render a pdf template. Always stored: the result is the rendering reference — pass it
|
|
63
|
+
* to email attachments, or hand the id to the renderings API. The bytes never enter the
|
|
64
|
+
* process. `user` and `tags` index the stored document for the listing.
|
|
65
|
+
*/
|
|
66
|
+
pdf<D>(template: Template<D, 'pdf'>, data: D, opts?: PdfRenderOptions): Promise<StoredRendering>;
|
|
67
|
+
pdf(template: string, data?: Serializable, opts?: PdfRenderOptions): Promise<StoredRendering>;
|
|
68
|
+
}
|
|
69
|
+
/** @internal How ctx.render reaches the activity channel and the connectors — bound by createContext. */
|
|
70
|
+
export interface RenderDeps {
|
|
71
|
+
call: <T>(name: string, input: Record<string, unknown>, opts: ActivityOptions | undefined) => Promise<T>;
|
|
72
|
+
email: (binding?: string) => EmailHandle;
|
|
73
|
+
}
|
|
74
|
+
/** @internal The ctx.render factory. */
|
|
75
|
+
export declare const createRenderFactory: (deps: RenderDeps) => RenderApi;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { ActivityOptions } from './context';
|
|
2
|
+
/** The subscriber: a subscription always belongs to a user. */
|
|
3
|
+
export type SubscriptionTarget = {
|
|
4
|
+
user: string;
|
|
5
|
+
};
|
|
6
|
+
/** A calendar period, applied host-side in UTC (months clamp to month ends). */
|
|
7
|
+
export interface SubscriptionPeriod {
|
|
8
|
+
months?: number;
|
|
9
|
+
weeks?: number;
|
|
10
|
+
days?: number;
|
|
11
|
+
hours?: number;
|
|
12
|
+
minutes?: number;
|
|
13
|
+
}
|
|
14
|
+
/** Exactly one of period (from now / from the current term end) and until (absolute ISO). */
|
|
15
|
+
export interface SubscriptionTerm {
|
|
16
|
+
period?: SubscriptionPeriod;
|
|
17
|
+
until?: string;
|
|
18
|
+
}
|
|
19
|
+
export type SubscriptionState = 'pending' | 'active' | 'grace' | 'expired';
|
|
20
|
+
export interface SubscriptionSnapshot {
|
|
21
|
+
id: string;
|
|
22
|
+
plan: string;
|
|
23
|
+
user: string;
|
|
24
|
+
state: SubscriptionState;
|
|
25
|
+
paidUntil: string;
|
|
26
|
+
graceUntil: string | null;
|
|
27
|
+
cancelAtPeriodEnd: boolean;
|
|
28
|
+
createdAt: string;
|
|
29
|
+
}
|
|
30
|
+
export interface SubscriptionHandle {
|
|
31
|
+
/**
|
|
32
|
+
* A new subscription — the dev grants after taking payment. Fails with
|
|
33
|
+
* subscription.alreadyActive when the user already holds a live one of this plan
|
|
34
|
+
* (extend instead).
|
|
35
|
+
*/
|
|
36
|
+
activate(target: SubscriptionTarget, term: SubscriptionTerm, opts?: ActivityOptions): Promise<SubscriptionSnapshot>;
|
|
37
|
+
/**
|
|
38
|
+
* Moves the live subscription's term: a period extends from max(paidUntil, now) — no
|
|
39
|
+
* proration, ever — and a grace subscription reactivates. Fails with
|
|
40
|
+
* subscription.notFound when there is no live one.
|
|
41
|
+
*/
|
|
42
|
+
extend(target: SubscriptionTarget, term: SubscriptionTerm, opts?: ActivityOptions): Promise<SubscriptionSnapshot>;
|
|
43
|
+
/** The live subscription, or null (terminal ones are history, not addressable here). */
|
|
44
|
+
get(target: SubscriptionTarget, opts?: ActivityOptions): Promise<SubscriptionSnapshot | null>;
|
|
45
|
+
/** The one entitlement query: true while active or in grace. */
|
|
46
|
+
isActive(target: SubscriptionTarget, opts?: ActivityOptions): Promise<boolean>;
|
|
47
|
+
/**
|
|
48
|
+
* atPeriodEnd keeps the paid term and stops it at paidUntil; without it the subscription
|
|
49
|
+
* expires immediately, remaining term forfeited (no proration).
|
|
50
|
+
*/
|
|
51
|
+
cancel(target: SubscriptionTarget, opts?: {
|
|
52
|
+
atPeriodEnd?: boolean;
|
|
53
|
+
} & ActivityOptions): Promise<SubscriptionSnapshot>;
|
|
54
|
+
}
|
|
55
|
+
/** @internal Same call channel as the connector handles. */
|
|
56
|
+
type SubscriptionCall = <T>(name: string, input: Record<string, unknown>, opts: ActivityOptions | undefined) => Promise<T>;
|
|
57
|
+
/** @internal ctx.subscription factory over the activity channel — bound by createContext. */
|
|
58
|
+
export declare const createSubscriptionFactory: (call: SubscriptionCall) => (plan: string, scope?: {
|
|
59
|
+
tenant?: boolean;
|
|
60
|
+
}) => SubscriptionHandle;
|
|
61
|
+
export {};
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/** JSON-compatible data. Payloads, databag values, history entries. */
|
|
2
|
+
export type Json = string | number | boolean | null | Json[] | {
|
|
3
|
+
[key: string]: Json;
|
|
4
|
+
};
|
|
5
|
+
/** What the engine can journal: JSON plus the engine value types (ISO-serialized). */
|
|
6
|
+
export type Serializable = Json | {
|
|
7
|
+
toJSON(): Json;
|
|
8
|
+
} | undefined | Serializable[] | {
|
|
9
|
+
[key: string]: Serializable;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Typed handle to a deployed activity — what a `*.activity` import looks like from
|
|
13
|
+
* workflow code (the deploy bundler rewrites the import to a reference stub; the
|
|
14
|
+
* Node implementation never enters the sandbox). Pass it to ctx.activity for a
|
|
15
|
+
* fully typed invocation.
|
|
16
|
+
*/
|
|
17
|
+
export type Activity<In = never, Out = unknown> = {
|
|
18
|
+
readonly __codenotchActivity: true;
|
|
19
|
+
/** @internal Phantom carrier of the type parameters. */
|
|
20
|
+
readonly __activityTypes?: {
|
|
21
|
+
input: In;
|
|
22
|
+
output: Out;
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
/** The kinds of output a rendering template produces. */
|
|
26
|
+
export type TemplateKind = 'email' | 'pdf';
|
|
27
|
+
/**
|
|
28
|
+
* Typed handle to a manifest-declared rendering template — what a template import looks
|
|
29
|
+
* like from workflow code (the deploy bundler rewrites the import to a reference stub;
|
|
30
|
+
* the template implementation never enters the sandbox). Pass it to ctx.render.* for a
|
|
31
|
+
* fully typed, kind-checked invocation.
|
|
32
|
+
*/
|
|
33
|
+
export type Template<Data = never, Kind extends TemplateKind = TemplateKind> = {
|
|
34
|
+
readonly __codenotchTemplate: true;
|
|
35
|
+
/** @internal Phantom carrier of the type parameters. */
|
|
36
|
+
readonly __templateTypes?: {
|
|
37
|
+
data: Data;
|
|
38
|
+
kind: Kind;
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
/** Duration as a components object. Calendar components (years/months) apply calendar arithmetic. */
|
|
42
|
+
export interface DurationParts {
|
|
43
|
+
years?: number;
|
|
44
|
+
months?: number;
|
|
45
|
+
weeks?: number;
|
|
46
|
+
days?: number;
|
|
47
|
+
hours?: number;
|
|
48
|
+
minutes?: number;
|
|
49
|
+
seconds?: number;
|
|
50
|
+
milliseconds?: number;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* A duration accepted everywhere a length of time is expected:
|
|
54
|
+
* 'P3D' / 'PT5M' ISO-8601
|
|
55
|
+
* 180000 raw milliseconds
|
|
56
|
+
* '3.00:00:00' .NET TimeSpan serialization
|
|
57
|
+
* { days: 3 } components object
|
|
58
|
+
* Duration.from(...) a Duration value
|
|
59
|
+
* Dates and datetimes are always ISO-8601.
|
|
60
|
+
*/
|
|
61
|
+
export type DurationLike = string | number | DurationParts | {
|
|
62
|
+
toMillis(): number;
|
|
63
|
+
};
|
|
64
|
+
/** Retry policy of an activity execution or a delivery. */
|
|
65
|
+
export interface RetryPolicy {
|
|
66
|
+
/** Total attempts (the first execution included). */
|
|
67
|
+
attempts: number;
|
|
68
|
+
/** Fixed delay between attempts. */
|
|
69
|
+
interval?: DurationLike;
|
|
70
|
+
/** Base delay for exponential backoff (alternative to interval). */
|
|
71
|
+
backoff?: DurationLike;
|
|
72
|
+
}
|
|
73
|
+
/** Receiver-side delivery policy of a signal wait or trigger. */
|
|
74
|
+
export interface DeliveryOptions {
|
|
75
|
+
/**
|
|
76
|
+
* 'delivery' (default): ack once accepted/started. 'end': ack only when the run
|
|
77
|
+
* finishes. v1 supports only 'delivery'; declaring 'end' is a deploy error.
|
|
78
|
+
*/
|
|
79
|
+
ack?: 'delivery' | 'end';
|
|
80
|
+
/** Dispatch retry: how the dispatcher retries reaching this instance. */
|
|
81
|
+
retry?: RetryPolicy;
|
|
82
|
+
}
|
|
83
|
+
/** One JSON-patch operation (RFC 6902) for the transformations merge can't express. */
|
|
84
|
+
export interface JsonPatchOp {
|
|
85
|
+
op: 'add' | 'remove' | 'replace' | 'move' | 'copy' | 'test';
|
|
86
|
+
path: string;
|
|
87
|
+
from?: string;
|
|
88
|
+
value?: Serializable;
|
|
89
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/** A query value: null renders the bare key, undefined omits it, arrays repeat the key. */
|
|
2
|
+
export type QueryValue = string | number | boolean | null | undefined | (string | number | boolean)[];
|
|
3
|
+
/** The parts of a parsed URL. */
|
|
4
|
+
export interface UrlParts {
|
|
5
|
+
/** Lowercase scheme without the colon ('https'). */
|
|
6
|
+
scheme: string;
|
|
7
|
+
/** Hostname, without port ('api.example.com'). */
|
|
8
|
+
host: string;
|
|
9
|
+
/** Port number, or null when the URL carries none. */
|
|
10
|
+
port: number | null;
|
|
11
|
+
/** Path including the leading slash ('/v1/users'); '/' when absent. */
|
|
12
|
+
path: string;
|
|
13
|
+
/** Decoded query parameters; repeated keys collect into arrays. */
|
|
14
|
+
query: Record<string, string | string[]>;
|
|
15
|
+
/** Fragment without the '#', or null. */
|
|
16
|
+
fragment: string | null;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Renders query parameters as a query string (no leading '?'). Arrays repeat the
|
|
20
|
+
* key, null renders the bare key, undefined entries are omitted.
|
|
21
|
+
*/
|
|
22
|
+
export declare const encodeQuery: (params: Record<string, QueryValue>) => string;
|
|
23
|
+
/** Parses a query string ('a=1&b=2', leading '?' tolerated). Repeated keys collect into arrays. */
|
|
24
|
+
export declare const parseQuery: (queryString: string) => Record<string, string | string[]>;
|
|
25
|
+
/**
|
|
26
|
+
* Builds a URL from a base and optional path/query/fragment, with correct escaping:
|
|
27
|
+
* url.build('https://api.example.com', { path: ['users', id], query: { page: 2 } }).
|
|
28
|
+
* Path segments are individually encoded; a string path is appended as-is.
|
|
29
|
+
*/
|
|
30
|
+
export declare const build: (base: string, options?: {
|
|
31
|
+
path?: string | (string | number)[];
|
|
32
|
+
query?: Record<string, QueryValue>;
|
|
33
|
+
fragment?: string;
|
|
34
|
+
}) => string;
|
|
35
|
+
/** Parses an absolute URL into its parts. Throws on anything unparseable. */
|
|
36
|
+
export declare const parse: (url: string) => UrlParts;
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { Instant } from './instant';
|
|
2
|
+
import type { DurationLike, Json, Serializable } from './types';
|
|
3
|
+
/** One field of the declarative form (see USER_TASKS.md for the full schema rules). */
|
|
4
|
+
export type UserTaskFormField = {
|
|
5
|
+
type: 'markdown';
|
|
6
|
+
content: string;
|
|
7
|
+
} | {
|
|
8
|
+
key: string;
|
|
9
|
+
type: 'text' | 'textarea' | 'number' | 'boolean' | 'select' | 'multiselect' | 'date' | 'datetime';
|
|
10
|
+
label?: string;
|
|
11
|
+
description?: string;
|
|
12
|
+
placeholder?: string;
|
|
13
|
+
required?: boolean;
|
|
14
|
+
readonly?: boolean;
|
|
15
|
+
default?: Json;
|
|
16
|
+
/** text/textarea */
|
|
17
|
+
minLength?: number;
|
|
18
|
+
maxLength?: number;
|
|
19
|
+
pattern?: string;
|
|
20
|
+
/** number / date ('yyyy-MM-dd') / datetime (ISO-8601) */
|
|
21
|
+
min?: number | string;
|
|
22
|
+
max?: number | string;
|
|
23
|
+
step?: number;
|
|
24
|
+
/** select/multiselect */
|
|
25
|
+
options?: {
|
|
26
|
+
value: string;
|
|
27
|
+
label?: string;
|
|
28
|
+
}[];
|
|
29
|
+
minItems?: number;
|
|
30
|
+
maxItems?: number;
|
|
31
|
+
};
|
|
32
|
+
export interface UserTaskForm {
|
|
33
|
+
kind?: 'schema';
|
|
34
|
+
schemaVersion?: number;
|
|
35
|
+
fields: UserTaskFormField[];
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Who is asked: one user, any holder of a project role (they claim from the queue), or —
|
|
39
|
+
* public — anyone holding the share link the handle exposes.
|
|
40
|
+
*/
|
|
41
|
+
export type UserTaskAssignment = {
|
|
42
|
+
user: string;
|
|
43
|
+
} | {
|
|
44
|
+
role: string;
|
|
45
|
+
} | {
|
|
46
|
+
public: true;
|
|
47
|
+
};
|
|
48
|
+
export interface UserTaskDefinition {
|
|
49
|
+
title: string;
|
|
50
|
+
description?: string;
|
|
51
|
+
form: UserTaskForm;
|
|
52
|
+
assign: UserTaskAssignment;
|
|
53
|
+
priority?: 'low' | 'normal' | 'high' | 'critical';
|
|
54
|
+
tags?: string[];
|
|
55
|
+
/** Informational absolute deadline (Instant or ISO-8601) — drives sorting/overdue badges, never a state change. */
|
|
56
|
+
due?: Instant | string;
|
|
57
|
+
/** The same deadline, relative to now. Use due or dueIn, not both. */
|
|
58
|
+
dueIn?: DurationLike;
|
|
59
|
+
/** Read-only data shown beside the form (order details, amounts, …). */
|
|
60
|
+
context?: Serializable;
|
|
61
|
+
}
|
|
62
|
+
export interface UserTaskOptions {
|
|
63
|
+
/**
|
|
64
|
+
* Keep the task alive when this run fails or is cancelled. Default false: the task is
|
|
65
|
+
* cancelled with the run's compensation, so no orphaned work lingers in anyone's queue.
|
|
66
|
+
*/
|
|
67
|
+
detached?: boolean;
|
|
68
|
+
}
|
|
69
|
+
/** What a completed task resolves with. */
|
|
70
|
+
export interface UserTaskOutcome {
|
|
71
|
+
/** The validated submission — exactly the editable fields' values. */
|
|
72
|
+
output: Json;
|
|
73
|
+
/** The completing user; absent when an anonymous share-link holder completed. */
|
|
74
|
+
completedBy?: string;
|
|
75
|
+
completedAt: Instant;
|
|
76
|
+
}
|
|
77
|
+
export interface UserTaskSnapshot {
|
|
78
|
+
id: string;
|
|
79
|
+
state: 'open' | 'inProgress' | 'completed' | 'cancelled';
|
|
80
|
+
mode: 'user' | 'role' | 'public';
|
|
81
|
+
title: string;
|
|
82
|
+
assignee?: string;
|
|
83
|
+
candidateRole?: string;
|
|
84
|
+
priority: 'low' | 'normal' | 'high' | 'critical';
|
|
85
|
+
tags: string[];
|
|
86
|
+
dueAt?: Instant;
|
|
87
|
+
output?: Json;
|
|
88
|
+
completedBy?: string;
|
|
89
|
+
completedAt?: Instant;
|
|
90
|
+
cancelledAt?: Instant;
|
|
91
|
+
}
|
|
92
|
+
export interface UserTaskHandle {
|
|
93
|
+
readonly id: string;
|
|
94
|
+
/** The portal share path/URL (Public tasks) — mail it, return it, put it in a chat message. */
|
|
95
|
+
readonly sharePath?: string;
|
|
96
|
+
readonly shareUrl?: string;
|
|
97
|
+
/**
|
|
98
|
+
* Parks until a human completes the task, then resolves with the validated output.
|
|
99
|
+
* Rejects with ProcessError 'userTask.cancelled' when the task is cancelled instead —
|
|
100
|
+
* race it with a timer for escalation: ctx.race({ done: task.outcome, late: ctx.wait('P2D') }).
|
|
101
|
+
*/
|
|
102
|
+
readonly outcome: Promise<UserTaskOutcome>;
|
|
103
|
+
/** The task's current state, polled from the store. Null when it does not exist (yet). */
|
|
104
|
+
get(): Promise<UserTaskSnapshot | null>;
|
|
105
|
+
/** Reassign to another user (User and claimed Role tasks). */
|
|
106
|
+
assign(userId: string): Promise<void>;
|
|
107
|
+
/** Cancel the task if it is still active; false when it already reached a terminal state. */
|
|
108
|
+
cancel(): Promise<boolean>;
|
|
109
|
+
}
|
|
110
|
+
/** @internal What the factory needs from the context runtime. */
|
|
111
|
+
export type UserTaskHooks = {
|
|
112
|
+
call: <T>(name: string, input: Record<string, unknown>) => Promise<T>;
|
|
113
|
+
park: <T>(kind: string, args: unknown) => Promise<T>;
|
|
114
|
+
nextUuid: () => string;
|
|
115
|
+
nowMs: () => number;
|
|
116
|
+
onCompensate: (fn: () => void | Promise<void>) => void;
|
|
117
|
+
};
|
|
118
|
+
/** @internal ctx.userTask over the runtime hooks — bound by createContext. */
|
|
119
|
+
export declare const createUserTaskFactory: (hooks: UserTaskHooks) => (definition: UserTaskDefinition, opts?: UserTaskOptions) => Promise<UserTaskHandle>;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* String validators: is.email(value), is.uuid(value), … Every check returns a boolean
|
|
3
|
+
* and never throws — non-string input is simply false.
|
|
4
|
+
*/
|
|
5
|
+
export declare const is: {
|
|
6
|
+
/** A plausible email address (practical single-address shape, not full RFC 5322). */
|
|
7
|
+
email: (value: unknown) => boolean;
|
|
8
|
+
/** A UUID in canonical hyphenated form, any version. */
|
|
9
|
+
uuid: (value: unknown) => boolean;
|
|
10
|
+
/** An absolute http(s) URL with a valid host. */
|
|
11
|
+
url: (value: unknown) => boolean;
|
|
12
|
+
/** An ISO-8601 calendar date ('2026-01-31'), month/day ranges checked. */
|
|
13
|
+
isoDate: (value: unknown) => boolean;
|
|
14
|
+
/** An ISO-8601 datetime ('2026-01-31T09:30:00Z', offset or zoneless). */
|
|
15
|
+
isoDateTime: (value: unknown) => boolean;
|
|
16
|
+
/** An ISO-8601 duration ('P3D', 'PT1H30M'). */
|
|
17
|
+
isoDuration: (value: unknown) => boolean;
|
|
18
|
+
/** A dotted-quad IPv4 address. */
|
|
19
|
+
ipv4: (value: unknown) => boolean;
|
|
20
|
+
/** An IPv6 address (compressed forms and IPv4-mapped tails included). */
|
|
21
|
+
ipv6: (value: unknown) => boolean;
|
|
22
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { ActivityOptions } from './context';
|
|
2
|
+
import type { ConnectorCall } from './connectors';
|
|
3
|
+
import type { Json } from './types';
|
|
4
|
+
export interface WebPushMessage {
|
|
5
|
+
/** The id of the user to notify. */
|
|
6
|
+
user: string;
|
|
7
|
+
/** The react application whose subscription receives it (its service worker shows the notification). */
|
|
8
|
+
app: string;
|
|
9
|
+
title: string;
|
|
10
|
+
body: string;
|
|
11
|
+
icon?: string;
|
|
12
|
+
/** Arbitrary payload handed to the app's service worker along with the notification. */
|
|
13
|
+
data?: Json;
|
|
14
|
+
}
|
|
15
|
+
export type WebPushDeviceStatus = 'success' | 'disconnected' | 'error';
|
|
16
|
+
export interface WebPushResult {
|
|
17
|
+
/** Devices delivered to. 0 when the user has no subscription — push is best-effort, not an error. */
|
|
18
|
+
sent: number;
|
|
19
|
+
/** Per-device outcome; 'disconnected' means the subscription was gone and has been removed. */
|
|
20
|
+
devices: Record<string, WebPushDeviceStatus>;
|
|
21
|
+
}
|
|
22
|
+
export interface WebPushApi {
|
|
23
|
+
/**
|
|
24
|
+
* Send one notification to every device the user subscribed. At-least-once and NOT
|
|
25
|
+
* retried by default (a retry could notify twice) — pass opts.retry to opt in.
|
|
26
|
+
*/
|
|
27
|
+
send(message: WebPushMessage, opts?: ActivityOptions): Promise<WebPushResult>;
|
|
28
|
+
}
|
|
29
|
+
/** @internal The ctx.webPush factory — same activity channel as the connector handles. */
|
|
30
|
+
export declare const createWebPushApi: (call: ConnectorCall) => WebPushApi;
|