@tellescope/schema 1.250.2 → 1.252.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.
package/src/schema.ts CHANGED
@@ -79,6 +79,7 @@ import {
79
79
  DevelopHealthRunBenefitVerificationBaseArguments,
80
80
  WeeklyAvailability,
81
81
  CanvasCreateNoteAutomationAction,
82
+ CanvasCoding,
82
83
  StripeKeyDetail,
83
84
  MetriportIntegrationDetail,
84
85
  EnduserDevice,
@@ -90,7 +91,11 @@ import {
90
91
  AutomationAction,
91
92
  TimeTrackTimestamp,
92
93
  BelugaPharmacyMapping,
94
+ BelugaAutomationMappingEntry,
95
+ BelugaUpdateVisitPatientPreferenceItem,
93
96
  TimeTrack,
97
+ LinkedAccount,
98
+ LinkedAccountAccessEntry,
94
99
  } from "@tellescope/types-models"
95
100
 
96
101
  import {
@@ -261,6 +266,7 @@ import {
261
266
  exactMatchValidator,
262
267
  exactMatchValidatorOptional,
263
268
  listOfMongoIdStringValidatorOptionalOrEmptyOk,
269
+ linkedAccountAccessValidator,
264
270
  listOfStringsValidatorOptionalOrEmptyOk,
265
271
  stringValidatorOptionalEmptyOkay,
266
272
  analyticsQueryResultsValidator,
@@ -709,9 +715,9 @@ export type CustomActions = {
709
715
  >,
710
716
  file_download_URL: CustomAction<{ secureName: string, preferInBrowser?: boolean, }, { downloadURL: string, name: string }>,
711
717
  run_ocr: CustomAction<{ id: string, type: string }, { file: File }>,
712
- confirm_file_upload: CustomAction<{ id: string, syncToBeluga?: boolean, formResponseId?: string }, { }>,
718
+ confirm_file_upload: CustomAction<{ id: string, syncToBeluga?: boolean, syncToMDI?: boolean, formResponseId?: string }, { }>,
713
719
  send_fax: CustomAction<{ id: string, recipientFaxNumber: string }, { }>,
714
- push: CustomAction<{ id: string, destination: string, type?: string, typeId?: string }, { file?: File }>,
720
+ push: CustomAction<{ id: string, destination: string, type?: string, typeId?: string, canvasCategory?: string, canvasType?: CanvasCoding, canvasReviewMode?: string, canvasComment?: string }, { file?: File }>,
715
721
  },
716
722
  form_fields: {
717
723
  load_choices_from_database: CustomAction<{ fieldId: string, lastId?: string, limit?: number, databaseId?: string, search?: string }, { choices: DatabaseRecordClient[] }>,
@@ -934,6 +940,9 @@ export type CustomActions = {
934
940
  consent: CustomAction<{ termsVersion: string, }, { user: User, authToken: string }>,
935
941
  get_users_for_groups: CustomAction<{ groups: string[] }, { userIds: string[] }>,
936
942
  play_phone_message: CustomAction<{ userId: string, message: string, enduserId?: string, journeyContext?: JourneyContext }, { }>,
943
+ get_linked_accounts: CustomAction<{}, { linkedAccounts: LinkedAccount[] }>,
944
+ switch_account: CustomAction<{ targetUserId: string }, { authToken: string, user: User }>,
945
+ request_linked_account_access: CustomAction<{ targetEmail: string }, { }>,
937
946
  },
938
947
  chat_rooms: {
939
948
  join_room: CustomAction<{ id: string }, { room: ChatRoom }>,
@@ -3558,7 +3567,73 @@ export const schema: SchemaV1 = build_schema({
3558
3567
 
3559
3568
  return "User organizationIds are readonly"
3560
3569
  }
3561
- }
3570
+ },
3571
+ {
3572
+ explanation: "linkedAccountAccess mutations are constrained to the owner with allowlisted transitions",
3573
+ evaluate: (record, _, session, method, { updates, original }) => {
3574
+ if (!updates || !('linkedAccountAccess' in updates)) return
3575
+ if (method === 'create') {
3576
+ if (updates.linkedAccountAccess && (updates.linkedAccountAccess as any[]).length > 0) {
3577
+ return "linkedAccountAccess cannot be set on user creation"
3578
+ }
3579
+ return
3580
+ }
3581
+
3582
+ // Grant management is reserved to the actor's own session. From a switched session
3583
+ // (session.actorUserId set), even targeting the proxy identity's own record is rejected —
3584
+ // otherwise A-as-B could self-approve other pending requests on B, or delete B's existing
3585
+ // grants and silently revoke other grantees.
3586
+ if ((session as any).actorUserId) {
3587
+ return "Cannot update linkedAccountAccess from a switched session"
3588
+ }
3589
+
3590
+ // self-update only — record carries the post-merge updated document; original is the prior state.
3591
+ const ownerId = (record as any)._id?.toString() ?? (original as any)?._id?.toString() ?? (original as any)?.id
3592
+ if (!(ownerId && ownerId === session.id)) {
3593
+ return "Only the account owner can update linkedAccountAccess"
3594
+ }
3595
+
3596
+ const oldEntries: LinkedAccountAccessEntry[] = (original as any)?.linkedAccountAccess ?? []
3597
+ const newEntries: LinkedAccountAccessEntry[] = (updates.linkedAccountAccess as any) ?? []
3598
+
3599
+ for (const newEntry of newEntries) {
3600
+ const oldMatch = oldEntries.find(e => e.userId === newEntry.userId)
3601
+ if (!oldMatch) {
3602
+ return "Cannot add entries to linkedAccountAccess via direct update; use request_linked_account_access"
3603
+ }
3604
+
3605
+ if (newEntry.email !== oldMatch.email) return "linkedAccountAccess entry email is immutable"
3606
+ if ((newEntry.fname ?? null) !== (oldMatch.fname ?? null)) return "linkedAccountAccess entry fname is immutable"
3607
+ if ((newEntry.lname ?? null) !== (oldMatch.lname ?? null)) return "linkedAccountAccess entry lname is immutable"
3608
+ if ((newEntry.orgName ?? null) !== (oldMatch.orgName ?? null)) return "linkedAccountAccess entry orgName is immutable"
3609
+ if (new Date(newEntry.createdAt).getTime() !== new Date(oldMatch.createdAt).getTime()) return "linkedAccountAccess entry createdAt is immutable"
3610
+ if (new Date(newEntry.requestExpiresAt).getTime() !== new Date(oldMatch.requestExpiresAt).getTime()) return "linkedAccountAccess entry requestExpiresAt is immutable"
3611
+
3612
+ if (newEntry.status !== oldMatch.status) {
3613
+ if (!(oldMatch.status === 'pending' && newEntry.status === 'accepted')) {
3614
+ return "linkedAccountAccess status can only transition from pending to accepted"
3615
+ }
3616
+ // Reject approval of an expired pending request — owner must wait for the requester
3617
+ // to re-issue. requestExpiresAt is immutable per the rule above; the only way for a
3618
+ // pending entry to refresh is request_linked_account_access replacing the expired entry.
3619
+ if (new Date(oldMatch.requestExpiresAt).getTime() < Date.now()) {
3620
+ return "linkedAccountAccess request has expired and cannot be approved; requester must re-request"
3621
+ }
3622
+ }
3623
+ }
3624
+
3625
+ return
3626
+ }
3627
+ },
3628
+ {
3629
+ explanation: "Legacy accountAccessGrantedTo field is no longer accepted",
3630
+ evaluate: (_, __, ___, ____, { updates }) => {
3631
+ if (updates && 'accountAccessGrantedTo' in updates) {
3632
+ return "accountAccessGrantedTo has been replaced by linkedAccountAccess"
3633
+ }
3634
+ return
3635
+ }
3636
+ },
3562
3637
  ],
3563
3638
  },
3564
3639
  defaultActions: {
@@ -3731,7 +3806,7 @@ export const schema: SchemaV1 = build_schema({
3731
3806
  name: 'Play Phone Message',
3732
3807
  path: '/users/play-phone-message',
3733
3808
  description: "Calls the user and plays a recorded message",
3734
- parameters: {
3809
+ parameters: {
3735
3810
  userId: { validator: mongoIdStringValidator, required: true },
3736
3811
  message: { validator: stringValidator5000, required: true },
3737
3812
  enduserId: { validator: mongoIdStringValidator },
@@ -3739,6 +3814,39 @@ export const schema: SchemaV1 = build_schema({
3739
3814
  },
3740
3815
  returns: { },
3741
3816
  },
3817
+ get_linked_accounts: {
3818
+ op: "custom", access: 'read', method: "get",
3819
+ name: 'Get Linked Accounts',
3820
+ path: '/users/linked-accounts',
3821
+ description: "Returns accounts that have granted access to the caller",
3822
+ parameters: { },
3823
+ returns: {
3824
+ linkedAccounts: { validator: objectAnyFieldsAnyValuesValidator as any, required: true },
3825
+ },
3826
+ },
3827
+ switch_account: {
3828
+ op: "custom", access: 'update', method: "post",
3829
+ name: 'Switch Account',
3830
+ path: '/users/switch-account',
3831
+ description: "Switches the current session to a target account that has granted access",
3832
+ parameters: {
3833
+ targetUserId: { validator: mongoIdStringRequired, required: true },
3834
+ },
3835
+ returns: {
3836
+ authToken: { validator: stringValidator, required: true },
3837
+ user: { validator: 'user' as any, required: true },
3838
+ },
3839
+ },
3840
+ request_linked_account_access: {
3841
+ op: "custom", access: 'update', method: "post",
3842
+ name: 'Request Linked Account Access',
3843
+ path: '/users/request-linked-account-access',
3844
+ description: "Requests linked-account access from another user identified by email; the target user must accept before the requester can switch into the account",
3845
+ parameters: {
3846
+ targetEmail: { validator: emailValidator, required: true },
3847
+ },
3848
+ returns: { },
3849
+ },
3742
3850
  },
3743
3851
  publicActions: {
3744
3852
  begin_sso: {
@@ -3852,6 +3960,7 @@ export const schema: SchemaV1 = build_schema({
3852
3960
  email: {
3853
3961
  validator: emailValidator,
3854
3962
  required: true,
3963
+ updatesDisabled: true,
3855
3964
  examples: ['test@tellescope.com'],
3856
3965
  redactions: ['enduser'],
3857
3966
  },
@@ -3978,6 +4087,7 @@ export const schema: SchemaV1 = build_schema({
3978
4087
  dashboardView: { validator: customDashboardViewValidator },
3979
4088
  hideFromCalendarView: { validator: booleanValidator },
3980
4089
  requireSSO: { validator: listOfStringsValidatorUniqueOptionalOrEmptyOkay },
4090
+ linkedAccountAccess: { validator: linkedAccountAccessValidator },
3981
4091
  }
3982
4092
  },
3983
4093
  templates: {
@@ -4204,10 +4314,11 @@ export const schema: SchemaV1 = build_schema({
4204
4314
  op: "custom", access: 'create', method: "post",
4205
4315
  name: 'Confirm File Upload',
4206
4316
  path: '/files/confirm-upload',
4207
- description: "Triggers file create side effects / webhooks to be called after client-side upload is complete. Optionally syncs file to Beluga if syncToBeluga is true and formResponseId is provided.",
4317
+ description: "Triggers file create side effects / webhooks to be called after client-side upload is complete. Optionally syncs file to Beluga if syncToBeluga is true and formResponseId is provided, or to MD Integrations if syncToMDI is true and formResponseId is provided.",
4208
4318
  parameters: {
4209
4319
  id: { validator: mongoIdStringRequired, required: true },
4210
4320
  syncToBeluga: { validator: booleanValidator },
4321
+ syncToMDI: { validator: booleanValidator },
4211
4322
  formResponseId: { validator: mongoIdStringValidator },
4212
4323
  },
4213
4324
  returns: { },
@@ -4228,13 +4339,17 @@ export const schema: SchemaV1 = build_schema({
4228
4339
  name: 'Push File',
4229
4340
  path: '/files/push',
4230
4341
  description: "Sends a file to an integrated system (e.g. athenahealth)",
4231
- parameters: {
4342
+ parameters: {
4232
4343
  id: { validator: mongoIdStringRequired, required: true },
4233
4344
  destination: { validator: stringValidator, required: true },
4234
4345
  type: { validator: stringValidator },
4235
4346
  typeId: { validator: stringValidator },
4347
+ canvasCategory: { validator: stringValidator },
4348
+ canvasType: { validator: canvasCodingValidator },
4349
+ canvasReviewMode: { validator: stringValidator },
4350
+ canvasComment: { validator: stringValidator5000 },
4236
4351
  },
4237
- returns: {
4352
+ returns: {
4238
4353
  file: { validator: 'file' as any },
4239
4354
  },
4240
4355
  },
@@ -4755,8 +4870,9 @@ export const schema: SchemaV1 = build_schema({
4755
4870
  publicShowDownload: { validator: booleanValidator },
4756
4871
  canvasId: { validator: stringValidator100 },
4757
4872
  canvasQuestionId: { validator: stringValidator100 },
4758
- syncToOLH: { validator: booleanValidator },
4873
+ syncToOLH: { validator: booleanValidator },
4759
4874
  syncWithResponsesFromFormIds: { validator: listOfUniqueStringsValidatorEmptyOk },
4875
+ syncAnswersAsHtml: { validator: booleanValidator },
4760
4876
  scoresSync: {
4761
4877
  validator: listValidatorOptionalOrEmptyOk(objectValidator<{ score: string, externalId: string }>({
4762
4878
  score: stringValidator100,
@@ -5873,7 +5989,7 @@ export const schema: SchemaV1 = build_schema({
5873
5989
  canvasCoding: { validator: canvasCodingValidator },
5874
5990
  canvasReasonCoding: { validator: canvasCodingValidator },
5875
5991
  canvasLocationId: { validator: stringValidator100 },
5876
- references: { validator: listOfRelatedRecordsValidator, readonly: true },
5992
+ references: { validator: listOfRelatedRecordsValidator, updatesDisabled: true },
5877
5993
  completedAt: { validator: dateValidatorOptional },
5878
5994
  completedBy: { validator: stringValidator },
5879
5995
  confirmedAt: { validator: dateValidatorOptional },
@@ -7170,6 +7286,7 @@ export const schema: SchemaV1 = build_schema({
7170
7286
  canvasSyncPhoneConsent: { validator: booleanValidator },
7171
7287
  canvasStateToLocationId: { validator: objectAnyFieldsValidator(stringValidator100) },
7172
7288
  enforceMFA: { validator: booleanValidator },
7289
+ accountSwitchingEnabled: { validator: booleanValidator },
7173
7290
  replyToEnduserTransactionalEmails: { validator: emailValidator },
7174
7291
  customTermsOfService: { validator: stringValidator },
7175
7292
  customPrivacyPolicy: { validator: stringValidator },
@@ -7225,6 +7342,20 @@ export const schema: SchemaV1 = build_schema({
7225
7342
  summaryFormId: mongoIdStringOptional,
7226
7343
  }))
7227
7344
  },
7345
+ belugaAutomationMappings: {
7346
+ validator: listValidatorOptionalOrEmptyOk(objectValidator<BelugaAutomationMappingEntry>({
7347
+ enduserCondition: optionalAnyObjectValidator,
7348
+ patientPreferences: listValidator(objectValidator<BelugaUpdateVisitPatientPreferenceItem>({
7349
+ name: stringValidator,
7350
+ strength: stringValidator,
7351
+ refills: stringValidator,
7352
+ quantity: stringValidator,
7353
+ daysSupply: stringValidator,
7354
+ medId: stringValidator,
7355
+ })),
7356
+ pharmacyId: stringValidator,
7357
+ }))
7358
+ },
7228
7359
  },
7229
7360
  },
7230
7361
  databases: {
@@ -9734,7 +9865,7 @@ If a voicemail is left, it is indicated by recordingURI, transcription, or recor
9734
9865
  type: { validator: stringValidator100 }, // only used on creation
9735
9866
  maxTokens: { validator: positiveNumberValidator },
9736
9867
  conversationId: { validator: mongoIdStringValidator },
9737
- prompt: { validator: stringValidator25000 },
9868
+ prompt: { validator: stringValidator100000EmptyOkay },
9738
9869
  orchestrationId: { validator: stringValidatorOptional }, // optional ID to group multiple conversations as part of the same workflow
9739
9870
  },
9740
9871
  returns: {