@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.
@@ -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