@vuoma/sdk 0.1.0 → 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.
- package/LICENSE +45 -0
- package/README.md +188 -24
- package/package.json +25 -6
- package/src/index.d.ts +64 -1
- package/src/index.js +87 -9
- package/src/member-contracts.js +98 -0
- package/src/organization-contracts.generated.d.ts +31 -0
- package/src/organization-contracts.generated.js +1343 -0
- package/src/organization.d.ts +23 -0
- package/src/organization.js +95 -0
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
// Reviewed public wire checks. No private package or server imports.
|
|
2
|
+
const object = value => value !== null && typeof value === 'object' && !Array.isArray(value)
|
|
3
|
+
const uuid = value => typeof value === 'string' && /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/iu.test(value)
|
|
4
|
+
const integer = (value, min = 0, max = Number.MAX_SAFE_INTEGER) => Number.isSafeInteger(value) && value >= min && value <= max
|
|
5
|
+
const text = (value, max, min = 0) => typeof value === 'string' && value.length >= min && value.length <= max
|
|
6
|
+
const timestamp = value => text(value, 64) && /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/u.test(value) && Number.isFinite(Date.parse(value)) && new Date(`${value.slice(0, 10)}T00:00:00Z`).toISOString().slice(0, 10) === value.slice(0, 10)
|
|
7
|
+
const exact = (value, allowed) => object(value) && Object.keys(value).every(key => allowed.includes(key))
|
|
8
|
+
const selectionKeys = ['service_offering_id', 'starts_at', 'ends_at', 'credit_cost']
|
|
9
|
+
const selection = value => object(value) && uuid(value.service_offering_id) && timestamp(value.starts_at) && timestamp(value.ends_at) && integer(value.credit_cost, 1, 100_000) && Date.parse(value.ends_at) > Date.parse(value.starts_at) && Date.parse(value.ends_at) - Date.parse(value.starts_at) <= 86_400_000
|
|
10
|
+
const check = (condition, message = 'Invalid member request.') => { if (!condition) throw new TypeError(message) }
|
|
11
|
+
|
|
12
|
+
export function bookingInput(value) {
|
|
13
|
+
const fields = ['service_session_id', 'entitlement_id', 'source_request_id', 'policy_version', 'policy_consent', 'payment_authorized', 'payment_authorization_id']
|
|
14
|
+
check(exact(value, fields))
|
|
15
|
+
const result = { ...value }
|
|
16
|
+
for (const key of ['service_session_id', 'source_request_id', 'entitlement_id']) {
|
|
17
|
+
if (key === 'entitlement_id' && result[key] == null) continue
|
|
18
|
+
check(typeof result[key] === 'string')
|
|
19
|
+
result[key] = result[key].trim().toLowerCase()
|
|
20
|
+
check(uuid(result[key]))
|
|
21
|
+
}
|
|
22
|
+
const policy = fields.slice(3)
|
|
23
|
+
if (policy.some(key => key in result)) check(uuid(result.policy_version) && result.policy_consent === true && result.payment_authorized === true && uuid(result.payment_authorization_id))
|
|
24
|
+
return result
|
|
25
|
+
}
|
|
26
|
+
export function visitInput(value) {
|
|
27
|
+
check(exact(value, ['source_request_id', 'appointments']) && uuid(value.source_request_id) && Array.isArray(value.appointments) && value.appointments.length >= 1 && value.appointments.length <= 10)
|
|
28
|
+
let previousEnd = null
|
|
29
|
+
const appointments = value.appointments.map(item => {
|
|
30
|
+
check(exact(item, selectionKeys) && selection(item) && (previousEnd === null || Date.parse(item.starts_at) >= previousEnd))
|
|
31
|
+
previousEnd = Date.parse(item.ends_at)
|
|
32
|
+
return { ...item }
|
|
33
|
+
})
|
|
34
|
+
return { source_request_id: value.source_request_id, appointments }
|
|
35
|
+
}
|
|
36
|
+
export function bookingId(value) { check(uuid(value), 'Use a valid booking ID.'); return value }
|
|
37
|
+
export function feeAmount(value) { check(integer(value), 'Use a nonnegative fee amount in minor currency units.'); return value }
|
|
38
|
+
export function validBookingResult(value) {
|
|
39
|
+
return object(value) && value.api_version === 1 && object(value.booking) && uuid(value.booking.id) && ['confirmed', 'waitlisted', 'cancelled', 'attended', 'no_show'].includes(value.booking.status) && object(value.redemption) && uuid(value.redemption.id) && ['waitlisted', 'reserved', 'consumed', 'released', 'reversed'].includes(value.redemption.status)
|
|
40
|
+
}
|
|
41
|
+
export function validQuote(quote) {
|
|
42
|
+
if (!object(quote) || !(quote.policy_version === null || uuid(quote.policy_version)) || !integer(quote.fee_amount) || !/^[a-z]{3}$/u.test(quote.currency ?? '') || !timestamp(quote.quoted_at) ||
|
|
43
|
+
!['can_cancel', 'late', 'grace_applies', 'demand_observed', 'waive_if_filled', 'credit_return'].every(key => typeof quote[key] === 'boolean') ||
|
|
44
|
+
!['late_cancellation_deadline', 'grace_until'].every(key => quote[key] === null || timestamp(quote[key])) ||
|
|
45
|
+
!['no_policy', 'fees_disabled', 'legacy_booking', 'on_time', 'waitlisted', 'booking_grace', 'quiet_class', 'late_fee_if_unfilled', 'seat_refilled', 'teacher_assignment', 'started'].includes(quote.reason)) return false
|
|
46
|
+
if (quote.fee_amount > 0 && (!quote.can_cancel || !quote.late || quote.grace_applies || !quote.policy_version || quote.reason !== 'late_fee_if_unfilled')) return false
|
|
47
|
+
return !quote.grace_applies || Boolean(quote.grace_until)
|
|
48
|
+
}
|
|
49
|
+
export function validVisitResult(value, request) {
|
|
50
|
+
if (!object(value) || value.api_version !== 1 || !uuid(value.visit_id) || value.source_request_id !== request.source_request_id || !integer(value.credit_cost, 1) || !Array.isArray(value.bookings) || value.bookings.length !== request.appointments.length) return false
|
|
51
|
+
const bookingIds = new Set(), sessionIds = new Set()
|
|
52
|
+
let total = 0
|
|
53
|
+
for (const [index, booking] of value.bookings.entries()) {
|
|
54
|
+
const expected = request.appointments[index]
|
|
55
|
+
if (!selection(booking) || !uuid(booking.booking_id) || !uuid(booking.service_session_id) || !['confirmed', 'cancelled', 'attended', 'no_show'].includes(booking.status) || bookingIds.has(booking.booking_id) || sessionIds.has(booking.service_session_id) || booking.service_offering_id !== expected.service_offering_id || Date.parse(booking.starts_at) !== Date.parse(expected.starts_at) || Date.parse(booking.ends_at) !== Date.parse(expected.ends_at) || booking.credit_cost !== expected.credit_cost) return false
|
|
56
|
+
bookingIds.add(booking.booking_id); sessionIds.add(booking.service_session_id); total += booking.credit_cost
|
|
57
|
+
}
|
|
58
|
+
return total === value.credit_cost
|
|
59
|
+
}
|
|
60
|
+
const location = value => object(value) && /^[a-z]{8}$/u.test(value.public_id ?? '') && text(value.name, 160, 1) && text(value.timezone, 100, 1) && ['city', 'region'].every(key => value[key] === null || text(value[key], 4000))
|
|
61
|
+
export function availabilityRange({ from, to }) {
|
|
62
|
+
check(timestamp(from) && timestamp(to) && Date.parse(to) > Date.parse(from) && Date.parse(to) - Date.parse(from) <= 14 * 86_400_000, 'Choose RFC 3339 timestamps spanning at most 14 days.')
|
|
63
|
+
return { from: new Date(from).toISOString(), to: new Date(to).toISOString() }
|
|
64
|
+
}
|
|
65
|
+
export function validAvailability(value, range) {
|
|
66
|
+
if (!object(value) || value.api_version !== 1 || !timestamp(value.generated_at) || !object(value.range) || value.range.from !== range.from || value.range.to !== range.to || typeof value.has_more !== 'boolean' || !Array.isArray(value.slots) || value.slots.length > 20_000) return false
|
|
67
|
+
const ids = new Set()
|
|
68
|
+
for (const slot of value.slots) {
|
|
69
|
+
if (!selection(slot) || !uuid(slot.service_id) || !integer(slot.duration_minutes, 1, 1440) || Date.parse(slot.ends_at) - Date.parse(slot.starts_at) !== slot.duration_minutes * 60_000 || !location(slot.location) || !object(slot.service) || slot.service.id !== slot.service_id || !text(slot.service.name, 160, 1) || !(slot.service.description === null || text(slot.service.description, 4000)) || slot.candidate_id !== `appointment:${slot.service_offering_id}:${slot.duration_minutes}:${Date.parse(slot.starts_at)}` || Date.parse(slot.starts_at) < Date.parse(range.from) || Date.parse(slot.starts_at) >= Date.parse(range.to) || ids.has(slot.candidate_id)) return false
|
|
70
|
+
ids.add(slot.candidate_id)
|
|
71
|
+
}
|
|
72
|
+
return true
|
|
73
|
+
}
|
|
74
|
+
export function validBookingCatalog(value) {
|
|
75
|
+
if (!object(value) || value.api_version !== 1 || !timestamp(value.generated_at) || !object(value.experience) || !['classes', 'appointments'].every(key => typeof value.experience[key] === 'boolean') || !['classes', 'appointments'].includes(value.experience.default_view) || !Array.isArray(value.locations) || value.locations.length > 1000 || !value.locations.every(location) || !Array.isArray(value.services) || value.services.length > 1000 || (value.cancellation_notice_minutes !== undefined && !integer(value.cancellation_notice_minutes, 0, 10080))) return false
|
|
76
|
+
const locations = new Set(value.locations.map(item => item.public_id)), services = new Set(), offerings = new Set()
|
|
77
|
+
if (locations.size !== value.locations.length || value.experience.appointments !== (value.services.length > 0) || (value.experience.classes || value.experience.appointments) && !value.experience[value.experience.default_view]) return false
|
|
78
|
+
for (const service of value.services) {
|
|
79
|
+
if (!object(service) || !uuid(service.id) || services.has(service.id) || !text(service.name, 160, 1) || !(service.description === null || text(service.description, 4000)) || !Array.isArray(service.offerings) || service.offerings.length < 1 || service.offerings.length > 1000) return false
|
|
80
|
+
services.add(service.id)
|
|
81
|
+
for (const offering of service.offerings) {
|
|
82
|
+
if (!object(offering) || !uuid(offering.id) || offerings.has(offering.id) || !integer(offering.duration_minutes, 1, 1440) || !locations.has(offering.location_public_id) || !(offering.credit_cost === null || integer(offering.credit_cost, 1, 100000))) return false
|
|
83
|
+
offerings.add(offering.id)
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return true
|
|
87
|
+
}
|
|
88
|
+
export function accountInput({ mode, email, signupIntent }) {
|
|
89
|
+
check(['signup', 'recovery'].includes(mode) && typeof email === 'string' && email.trim().includes('@') && email.length <= 320 && !/[\u0000-\u001f\u007f]/u.test(email), 'Provide a valid account mode and email address.')
|
|
90
|
+
if (mode === 'signup') check(['new', 'migrate', 'auto', 'activate'].includes(signupIntent), 'Choose a signup intent explicitly.')
|
|
91
|
+
}
|
|
92
|
+
export function displayName(value) { check(text(value, 160, 1) && Boolean(value.trim()) && !/[\u0000-\u001f\u007f]/u.test(value), 'Provide a display name.'); return value }
|
|
93
|
+
export function emailCode(value) { check(typeof value === 'string' && /^\d{6}$/u.test(value.trim()), 'Provide the six-digit email code.'); return value.trim() }
|
|
94
|
+
export function validCapability(value) { return typeof value === 'string' && /^[A-Za-z0-9_-]{43}$/u.test(value) }
|
|
95
|
+
export function accountResolution(value) {
|
|
96
|
+
check(object(value) && ['existing_account', 'existing_profile', 'new_profile'].includes(value.kind) && text(value.display_name, 160, 1) && Boolean(value.display_name.trim()) && !/[\u0000-\u001f\u007f]/u.test(value.display_name) && text(value.masked_email, 320) && /^[^@\s*]\*{3}@[^@\s*]\*{3}(?:\.[a-z0-9.-]+)?$/iu.test(value.masked_email) && (value.masked_phone === null || /^\*{3} \*{3} [0-9]{4}$/u.test(value.masked_phone ?? '')))
|
|
97
|
+
return { kind: value.kind, display_name: value.display_name, masked_email: value.masked_email, masked_phone: value.masked_phone }
|
|
98
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// Generated from reviewed API contracts. Run npm run api:generate; do not edit.
|
|
2
|
+
export interface OrganizationReadInputs {
|
|
3
|
+
"organization.read": Record<string, never>
|
|
4
|
+
"locations.read": Record<string, never>
|
|
5
|
+
"services.read": Record<string, never>
|
|
6
|
+
"schedule.read": { "week"?: string; "month"?: string }
|
|
7
|
+
"users.directory.read": { "q"?: string; "limit"?: number; "offset"?: number; "facet"?: "all" | "customers"; "customer_status"?: "active" | "inactive" | "archived" }
|
|
8
|
+
"task_lists.read": Record<string, never>
|
|
9
|
+
"inventory.read": Record<string, never>
|
|
10
|
+
}
|
|
11
|
+
export interface OrganizationReadResults {
|
|
12
|
+
"organization.read": { "api_version": 1; "data": { "organization": { "id": string; "slug": string; "name": string; "timezone": string; "currency_code": string; "brand_name"?: (string | null); [key: string]: unknown }; "access": { [key: string]: unknown } } }
|
|
13
|
+
"locations.read": { "api_version": 1; "data": { "locations": Array<{ "id": string; "name": string; "timezone": string; "status": string; "formatted_address"?: (string | null); [key: string]: unknown }> } }
|
|
14
|
+
"services.read": { "api_version": 1; "data": { "services": Array<{ "id": string; "name": string; "description"?: (string | null); "duration_minutes": number; "default_capacity"?: (number | null); "status": "active" | "inactive"; [key: string]: unknown }>; "past_session_variations": Array<{ [key: string]: unknown }> } }
|
|
15
|
+
"schedule.read": { "api_version": 1; "data": { "week_start": string; "sessions": Array<{ [key: string]: unknown }>; "staffing_windows": Array<{ [key: string]: unknown }>; "staffing_load_error": (string | null); "calendar": { "month": string; "days": Array<{ [key: string]: unknown }>; [key: string]: unknown } } }
|
|
16
|
+
"users.directory.read": { "api_version": 1; "data": { "users": Array<{ "id": string; "display_name": (string | null); "email"?: (string | null); "phone_e164"?: (string | null); "is_customer"?: boolean; "is_staff"?: boolean; "has_workspace_access"?: boolean; "person_status"?: "active" | "archived"; "customer_status"?: ("active" | "inactive" | "archived" | null) }>; "filters": { "facet": "all" | "customers"; "customer_status": ("active" | "inactive" | "archived" | null) }; "pagination": { "total": number; "limit": number; "offset": number; "has_more": boolean; "query": string } } }
|
|
17
|
+
"task_lists.read": { "api_version": 1; "data": { "task_lists": Array<{ [key: string]: unknown }>; "locations": Array<{ [key: string]: unknown }>; "can_manage_organization": boolean; [key: string]: unknown } }
|
|
18
|
+
"inventory.read": { "api_version": 1; "data": { "inventory": { "locations": Array<{ [key: string]: unknown }>; "stocks": Array<{ [key: string]: unknown }>; "alerts": Array<{ [key: string]: unknown }>; "movements": Array<{ [key: string]: unknown }>; "currency_code": string; [key: string]: unknown } } }
|
|
19
|
+
}
|
|
20
|
+
export type OrganizationReadOperationId = keyof OrganizationReadInputs
|
|
21
|
+
export interface OrganizationWriteInputs {
|
|
22
|
+
"messages.send": { "path"?: Record<string, never>; "body": { "channel_id": string; "body": string; "attachment_ids"?: Array<string>; "title"?: string; "preview"?: unknown; "action"?: unknown } }
|
|
23
|
+
"messages.read_state.update": { "path": { "channel_id": string }; "body": { "message_id": string } }
|
|
24
|
+
"messages.mute.update": { "path": { "channel_id": string }; "body": { "is_muted": boolean } }
|
|
25
|
+
}
|
|
26
|
+
export interface OrganizationWriteResults {
|
|
27
|
+
"messages.send": { "api_version": 1; "message": { "attachments"?: Array<{ "file_name": string; "content_type": "image/jpeg" | "image/png" | "image/webp" | "image/gif" | "image/heic" | "application/pdf" | "text/plain" | "text/csv" | "application/vnd.openxmlformats-officedocument.wordprocessingml.document" | "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" | "application/vnd.openxmlformats-officedocument.presentationml.presentation"; "byte_size": number; "id": string }>; "id": string; "body": string; "kind": "announcement" | "deleted" | "notification"; "notification": ({ "source_kind": "service_session_work_slot" | "service_session_provider" | "staffing_window_assignment" | "staffing_window"; "source_id": string; "session_id": (string | null); "session_starts_at": (string | null); "staffing_window_id": (string | null); "starts_at": string; "time_zone": string; "status": "open" | "claimed" | "withdrawn" | "cancelled"; "label": string } | null); "author_name": string; "is_mine": boolean; "created_at": string; "edited_at": (string | null); "deleted_at": (string | null) } }
|
|
28
|
+
"messages.read_state.update": { "api_version": 1; "state": { "channel_id": string; "message_id": string; "read_at": string } }
|
|
29
|
+
"messages.mute.update": { "api_version": 1; "state": { "channel_id": string; "is_muted": boolean } }
|
|
30
|
+
}
|
|
31
|
+
export type OrganizationWriteOperationId = keyof OrganizationWriteInputs
|