@diagonal-hq/cli 0.2.0 → 0.4.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.
@@ -371,7 +371,7 @@ export type RelationInverse = {
371
371
  settings?: Omit<RelationFieldSettings, 'inverse'> & { "max_connections"?: number | null };
372
372
  };
373
373
 
374
- /** A relation names its target resource by id or slug. The type defaults to `belongs_to`. */
374
+ /** A relation names its target resource by slug, as `Resources.<Name>`. The type defaults to `belongs_to`. */
375
375
  export type RelationFieldOptions = Omit<BaseFieldOptions<RelationFieldSettings>, 'settings'> & { resource: string } & (
376
376
  | { type?: "belongs_to"; settings?: RelationFieldSettings }
377
377
  | { type: "belongs_to_many"; settings?: RelationFieldSettings & { "max_connections"?: number | null } }
package/dist/index.d.ts CHANGED
@@ -76,13 +76,8 @@ export type ResourceDataApi = {
76
76
  export type ResourceDefinition<
77
77
  TFields extends Record<string, FieldDefinition> = Record<string, FieldDefinition>
78
78
  > = {
79
- /**
80
- * A lowercase UUID v7, for example '0199f000-0000-7000-8000-000000000001'.
81
- * `diagonal check` suggests a new one when the id is not v7. Keep it
82
- * stable: records and workflows refer to it.
83
- */
84
- id: string;
85
- slug: string;
79
+ /** Only a platform resource reference sets `platform`. */
80
+ platform?: never;
86
81
  name: string;
87
82
  singularName: string;
88
83
  displayField?: Extract<keyof TFields, string>;
@@ -112,16 +107,22 @@ export type ResourceDefinition<
112
107
  capabilities?: ResourceCapabilities<TFields>;
113
108
  };
114
109
 
110
+ /**
111
+ * A resource that the platform provisions and owns, such as the roles. The
112
+ * file gives it a `Resources` entry. The deploy does not change it.
113
+ */
114
+ export type PlatformResourceDefinition = { platform: true };
115
+
115
116
  export declare function defineResource<const TFields extends Record<string, FieldDefinition>>(
116
- resource: ResourceDefinition<TFields>
117
- ): ResourceDefinition<TFields>;
117
+ resource: ResourceDefinition<TFields> | PlatformResourceDefinition
118
+ ): ResourceDefinition<TFields> | PlatformResourceDefinition;
118
119
 
119
120
  /** A TipTap document whose mentions resolve against the page's bound records. */
120
121
  export type PageMetaResolver = { readonly type: string; readonly [key: string]: unknown };
121
122
 
122
123
  export type PageRouteParam = {
123
- /** The slug or id of the resource whose record the parameter names. */
124
- resource: string;
124
+ /** The resource whose record the parameter names, as `Resources.<Name>`. */
125
+ resource: ResourceReference;
125
126
  /** The field the parameter matches. Defaults to the record id. */
126
127
  lookupField?: string | null;
127
128
  /** Relation fields to load with the record. */
@@ -133,8 +134,8 @@ export type PageAuthorizer =
133
134
  | null
134
135
  | 'authenticated'
135
136
  | {
136
- /** The slug or id of the resource whose signed-in records may view the page. */
137
- resource?: string | null;
137
+ /** The resource whose signed-in records may view the page, as `Resources.<Name>`. */
138
+ resource?: ResourceReference | null;
138
139
  /** Permission ids. A viewer needs any one of them. */
139
140
  permissions?: readonly string[];
140
141
  };
@@ -180,7 +181,11 @@ export type NavigationDefinition = { id: string; items: readonly NavigationItem[
180
181
 
181
182
  export declare function defineNavigation<const T extends NavigationDefinition>(navigation: T): T;
182
183
 
183
- /** The slug, name, or id of a resource. */
184
+ /**
185
+ * The slug of a resource: its file name in `resources/`. Use `Resources.<Name>`
186
+ * from `@app/generated/resources`, which `diagonal check` writes, so the editor
187
+ * and the build catch a wrong name. A slug string builds with a warning.
188
+ */
184
189
  export type ResourceReference = string;
185
190
 
186
191
  export type WorkflowRunAs =
@@ -2296,9 +2296,15 @@ type SdkSendResolvedNotificationHandle = Omit<SdkSendEmailHandle, 'send' | 'data
2296
2296
 
2297
2297
  type SdkUpcomingNotification = {
2298
2298
  id: string;
2299
+ revision: number;
2300
+ idempotency_key: string | null;
2301
+ computed_at: string | null;
2302
+ scheduled_at_override: string | null;
2303
+ changed_by_resource_id: string | null;
2304
+ changed_by_record_id: string | null;
2299
2305
  notification_type_name: string;
2300
- scheduled_at: string;
2301
- status: 'scheduled' | 'failed';
2306
+ scheduled_at: string | null;
2307
+ status: 'draft' | 'scheduled' | 'failed';
2302
2308
  is_one_off: boolean;
2303
2309
  is_paused: boolean;
2304
2310
  /** Held occurrences do not send until they are resumed. */
@@ -2306,6 +2312,8 @@ type SdkUpcomingNotification = {
2306
2312
  can_cancel: boolean;
2307
2313
  can_edit: boolean;
2308
2314
  can_reschedule: boolean;
2315
+ can_save: boolean;
2316
+ can_schedule: boolean;
2309
2317
  can_send_now: boolean;
2310
2318
  can_hold: boolean;
2311
2319
  can_resume: boolean;
@@ -2318,7 +2326,7 @@ type SdkScheduledNotificationContent = {
2318
2326
  };
2319
2327
 
2320
2328
  type SdkScheduledNotificationDetail = Omit<SdkUpcomingNotification, 'status'> & {
2321
- status: 'scheduled' | 'failed' | 'queued' | 'cancelled' | 'expired';
2329
+ status: 'draft' | 'scheduled' | 'failed' | 'queued' | 'cancelled' | 'expired';
2322
2330
  notification_type_id: string;
2323
2331
  notification_template_id: string;
2324
2332
  recipient: SdkNotificationRecipient;
@@ -2328,13 +2336,16 @@ type SdkScheduledNotificationDetail = Omit<SdkUpcomingNotification, 'status'> &
2328
2336
  };
2329
2337
 
2330
2338
  type SdkScheduledNotificationEdits = {
2339
+ expected_revision: number;
2331
2340
  recipient?: SdkNotificationRecipient;
2332
2341
  /** Omit to retain content. Null restores the shared template. Use raw content from ScheduledNotificationComposer, not resolved NotificationComposer output. */
2333
2342
  content_override?: SdkScheduledNotificationContent | null;
2334
2343
  };
2335
2344
 
2336
2345
  type SdkUpdateScheduledNotificationInput = SdkScheduledNotificationEdits & {
2337
- /** One-offs only. Future ISO datetime with Z or an explicit offset. */
2346
+ /** Required with scheduled_at to release a saved draft. */
2347
+ transition?: 'schedule';
2348
+ /** Explicit send-time override. Future ISO datetime with Z or an explicit offset. */
2338
2349
  scheduled_at?: string;
2339
2350
  };
2340
2351
 
@@ -2351,14 +2362,15 @@ type SdkUpdateScheduledNotificationHandle = {
2351
2362
 
2352
2363
  type SdkScheduledNotificationResult = {
2353
2364
  id: string;
2354
- status: 'scheduled';
2365
+ revision: number;
2366
+ status: 'draft' | 'scheduled' | 'failed' | 'cancelled' | 'queued' | 'expired';
2355
2367
  };
2356
2368
 
2357
2369
  type SdkSendScheduledNotificationNowHandle = {
2358
2370
  /** Applies edits and queues this occurrence once. Queued does not mean delivered. Never retries automatically. */
2359
2371
  sendNow: (
2360
2372
  id: string,
2361
- input?: SdkScheduledNotificationEdits
2373
+ input: SdkScheduledNotificationEdits
2362
2374
  ) => Promise<{ id: string; status: 'queued' }>;
2363
2375
  isPending: boolean;
2364
2376
  error: ApiError | null;
@@ -2526,8 +2538,8 @@ type SdkNotificationHistoryItem = {
2526
2538
  /** Saved name for sent rows; the current name, or a deleted placeholder, otherwise. */
2527
2539
  notification_type_name: string;
2528
2540
  notification_template_name: string;
2529
- /** Send time for scheduled rows; preparation time for sent rows. ISO 8601. */
2530
- occurred_at: string;
2541
+ /** Send time for scheduled rows; preparation time for sent rows. ISO 8601, or null for unscheduled drafts. */
2542
+ occurred_at: string | null;
2531
2543
  source_kind: SdkNotificationSourceKind;
2532
2544
  /** Schedule or workflow name. Null without the `manage_notifications` permission. */
2533
2545
  source_name: string | null;
@@ -2652,22 +2664,29 @@ type SdkUpcomingNotificationsOptions = {
2652
2664
  enabled?: boolean;
2653
2665
  };
2654
2666
 
2655
- type SdkScheduleNotificationInput = {
2656
- notification_type_id: string;
2657
- notification_template_id: string;
2658
- recipient: SdkNotificationRecipient;
2659
- variables: Record<string, any>;
2660
- /** Future ISO 8601 timestamp with Z or an explicit UTC offset. */
2661
- scheduled_at: string;
2662
- /**
2663
- * A repeated key resolves with the notification that first used it. At send time, the key
2664
- * also stops delivery when a send or bulk item already used it.
2665
- */
2666
- idempotency_key?: string;
2667
- };
2667
+ type SdkScheduleNotificationInput =
2668
+ | ({
2669
+ notification_type_id: string;
2670
+ notification_template_id: string;
2671
+ recipient: SdkNotificationRecipient;
2672
+ variables: Record<string, any>;
2673
+ content_override?: SdkScheduledNotificationContent | null;
2674
+ idempotency_key?: string;
2675
+ } & (
2676
+ | { status: 'draft'; idempotency_key: string; scheduled_at?: never }
2677
+ | { status?: 'scheduled'; scheduled_at: string }
2678
+ ))
2679
+ | {
2680
+ notification_schedule_id: string;
2681
+ source_record_id: string;
2682
+ stage_id: string;
2683
+ status: 'draft';
2684
+ content_override?: SdkScheduledNotificationContent | null;
2685
+ };
2668
2686
 
2669
2687
  type SdkCancelledNotificationResult = {
2670
2688
  id: string;
2689
+ revision: number;
2671
2690
  status: 'cancelled';
2672
2691
  };
2673
2692
 
@@ -2681,7 +2700,7 @@ type SdkScheduleNotificationHandle = {
2681
2700
 
2682
2701
  type SdkCancelScheduledNotificationHandle = {
2683
2702
  /** Rejects with ApiError on failure, including 409 once delivery has started. */
2684
- cancel: (id: string) => Promise<SdkCancelledNotificationResult>;
2703
+ cancel: (id: string, expectedRevision: number) => Promise<SdkCancelledNotificationResult>;
2685
2704
  isPending: boolean;
2686
2705
  error: ApiError | null;
2687
2706
  reset: () => void;
@@ -2689,6 +2708,7 @@ type SdkCancelScheduledNotificationHandle = {
2689
2708
 
2690
2709
  type SdkHeldNotificationResult = {
2691
2710
  id: string;
2711
+ revision: number;
2692
2712
  status: 'scheduled' | 'failed';
2693
2713
  is_held: boolean;
2694
2714
  scheduled_at: string;
@@ -2696,7 +2716,7 @@ type SdkHeldNotificationResult = {
2696
2716
 
2697
2717
  type SdkHoldScheduledNotificationHandle = {
2698
2718
  /** Rejects with ApiError on failure, including 409 once delivery has started. */
2699
- hold: (id: string) => Promise<SdkHeldNotificationResult>;
2719
+ hold: (id: string, expectedRevision: number) => Promise<SdkHeldNotificationResult>;
2700
2720
  isPending: boolean;
2701
2721
  error: ApiError | null;
2702
2722
  reset: () => void;
@@ -2707,7 +2727,11 @@ type SdkResumeScheduledNotificationHandle = {
2707
2727
  * Pass a future ISO `scheduledAt` with Z or an offset to send at a new time. It is required
2708
2728
  * when the saved time passed during the hold. Rejects with ApiError on failure.
2709
2729
  */
2710
- resume: (id: string, scheduledAt?: string) => Promise<SdkHeldNotificationResult>;
2730
+ resume: (
2731
+ id: string,
2732
+ expectedRevision: number,
2733
+ scheduledAt?: string
2734
+ ) => Promise<SdkHeldNotificationResult>;
2711
2735
  isPending: boolean;
2712
2736
  error: ApiError | null;
2713
2737
  reset: () => void;
@@ -7190,6 +7214,9 @@ type SdkHooks = {
7190
7214
  * scheduled_at must be a future ISO timestamp with Z or an offset; convert local input
7191
7215
  * with new Date(value).toISOString(). Template values resolve again at send preparation.
7192
7216
  * Success refreshes upcoming-notification queries. The promise rejects with ApiError.
7217
+ * Use status: "draft" without scheduled_at to prepare an unscheduled draft. One-off drafts
7218
+ * require idempotency_key. Generated drafts use notification_schedule_id, source_record_id,
7219
+ * and stage_id. Saving a draft never releases it; use Send or transition: "schedule".
7193
7220
  * An `idempotency_key` makes a repeated schedule call return the first scheduled
7194
7221
  * notification unchanged. At send time the key also stops delivery when a send, bulk item,
7195
7222
  * or workflow already used it, so a manual send and a scheduled send of one notice reach the
@@ -7207,7 +7234,7 @@ type SdkHooks = {
7207
7234
  * or expired items return 409. Success refreshes upcoming lists. Rejects with ApiError.
7208
7235
  * @example
7209
7236
  * const cancellation = Sdk.hooks.useCancelScheduledNotification();
7210
- * await cancellation.cancel(notification.id);
7237
+ * await cancellation.cancel(notification.id, notification.revision);
7211
7238
  */
7212
7239
  useCancelScheduledNotification(): SdkCancelScheduledNotificationHandle;
7213
7240
 
@@ -7217,17 +7244,17 @@ type SdkHooks = {
7217
7244
  * as cancel. Repeated holds succeed. Success refreshes upcoming lists. Rejects with ApiError.
7218
7245
  * @example
7219
7246
  * const hold = Sdk.hooks.useHoldScheduledNotification();
7220
- * await hold.hold(notification.id);
7247
+ * await hold.hold(notification.id, notification.revision);
7221
7248
  */
7222
7249
  useHoldScheduledNotification(): SdkHoldScheduledNotificationHandle;
7223
7250
 
7224
7251
  /**
7225
7252
  * Resume a held occurrence so it sends once at its time. When that time passed during the
7226
7253
  * hold, pass a new future time; the old time never sends late. A generated occurrence keeps
7227
- * the new time until its schedule date changes. Rejects with ApiError.
7254
+ * the explicit time across schedule date changes. Rejects with ApiError.
7228
7255
  * @example
7229
7256
  * const resume = Sdk.hooks.useResumeScheduledNotification();
7230
- * await resume.resume(notification.id, new Date(value).toISOString());
7257
+ * await resume.resume(notification.id, notification.revision, new Date(value).toISOString());
7231
7258
  */
7232
7259
  useResumeScheduledNotification(): SdkResumeScheduledNotificationHandle;
7233
7260
 
@@ -7243,22 +7270,22 @@ type SdkHooks = {
7243
7270
  ): SdkApiHandle<SdkScheduledNotificationDetail>;
7244
7271
 
7245
7272
  /**
7246
- * Edit a scheduled or failed occurrence. Only one-offs can change scheduled_at.
7273
+ * Edit a draft, scheduled, or failed occurrence. Pass its expected_revision.
7247
7274
  * A null content_override restores the template. Failed preparation recovers only
7248
7275
  * after successful resolution. Refreshes detail and all upcoming record lists.
7249
7276
  * @example
7250
7277
  * const editing = Sdk.hooks.useUpdateScheduledNotification();
7251
- * await editing.update(id, { content_override: null });
7278
+ * await editing.update(id, { expected_revision: notification.revision, content_override: null });
7252
7279
  */
7253
7280
  useUpdateScheduledNotification(): SdkUpdateScheduledNotificationHandle;
7254
7281
 
7255
7282
  /**
7256
- * Apply edits and send the same scheduled or failed occurrence now, including
7283
+ * Apply edits and send the same draft, scheduled, or failed occurrence now, including
7257
7284
  * paused occurrences. The original time cannot send it again. Rejects terminal
7258
7285
  * items with 409. Refreshes detail, upcoming lists, and email history.
7259
7286
  * @example
7260
7287
  * const sending = Sdk.hooks.useSendScheduledNotificationNow();
7261
- * await sending.sendNow(id);
7288
+ * await sending.sendNow(id, { expected_revision: notification.revision });
7262
7289
  */
7263
7290
  useSendScheduledNotificationNow(): SdkSendScheduledNotificationNowHandle;
7264
7291
 
@@ -12698,7 +12725,7 @@ type ScheduledNotificationComposerProps = {
12698
12725
  /** The occurrence to open, such as an id from useUpcomingNotifications. */
12699
12726
  scheduledNotificationId: string;
12700
12727
  /** Edited raw content, ready to save as content_override. Null while nothing changed, and when the occurrence cannot be edited. */
12701
- onChange: (content: SdkScheduledNotificationContent | null) => void;
12728
+ onChange: (content: SdkScheduledNotificationContent | null, expectedRevision?: number) => void;
12702
12729
  /** Preview for an unsaved recipient change instead of the saved recipient. */
12703
12730
  recipient?: SdkNotificationRecipient;
12704
12731
  /** Omit to allow every text layout field. An empty array locks every layout field. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diagonal-hq/cli",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "The diagonal CLI and the configuration helpers for file-based Diagonal workspaces.",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "repository": {