@happyvertical/smrt-bookings 0.60.7 → 0.60.9

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/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["value"],"sources":["../src/errors.ts","../src/migration.ts","../src/windows.ts","../src/service.ts"],"sourcesContent":["/** A service rejection suitable for mapping to a host response. */\nexport class BookingError extends Error {\n constructor(\n readonly code:\n | 'invalid'\n | 'forbidden'\n | 'not-found'\n | 'conflict'\n | 'unavailable'\n | 'duplicate-project-window',\n message: string,\n ) {\n super(message);\n this.name = 'BookingError';\n }\n}\n","import type { DatabaseInterface } from '@happyvertical/sql';\n\nconst statements = [\n `CREATE TABLE smrt_booking_offerings (\n tenant_id uuid NOT NULL,\n id uuid NOT NULL,\n kind text NOT NULL CHECK (kind IN ('developer-hour', 'supervision-day')),\n enabled boolean NOT NULL,\n PRIMARY KEY (tenant_id, id)\n )`,\n `CREATE TABLE smrt_booking_resources (\n tenant_id uuid NOT NULL,\n id uuid NOT NULL,\n windows jsonb NOT NULL,\n PRIMARY KEY (tenant_id, id)\n )`,\n `CREATE TABLE smrt_bookings (\n tenant_id uuid NOT NULL,\n id uuid NOT NULL,\n offering_id uuid NOT NULL,\n contact_id uuid NOT NULL,\n project_id uuid,\n kind text NOT NULL CHECK (kind IN ('developer-hour', 'supervision-day')),\n status text NOT NULL CHECK (status IN ('requested', 'proposed', 'confirmed', 'declined', 'cancelled')),\n proposal_id uuid,\n assented_proposal_id uuid,\n confirmed_proposal_id uuid,\n PRIMARY KEY (tenant_id, id),\n FOREIGN KEY (tenant_id, offering_id) REFERENCES smrt_booking_offerings (tenant_id, id),\n CHECK (kind <> 'supervision-day' OR project_id IS NOT NULL),\n CHECK (status <> 'confirmed' OR confirmed_proposal_id IS NOT NULL),\n CHECK (assented_proposal_id IS NULL OR assented_proposal_id IS NOT DISTINCT FROM proposal_id),\n CHECK (confirmed_proposal_id IS NULL OR confirmed_proposal_id IS NOT DISTINCT FROM assented_proposal_id)\n )`,\n `CREATE TABLE smrt_booking_proposals (\n tenant_id uuid NOT NULL,\n booking_id uuid NOT NULL,\n id uuid NOT NULL,\n provider_id uuid NOT NULL,\n coverage jsonb NOT NULL,\n terms jsonb NOT NULL,\n approved_plan jsonb,\n PRIMARY KEY (tenant_id, booking_id, id),\n FOREIGN KEY (tenant_id, booking_id) REFERENCES smrt_bookings (tenant_id, id),\n FOREIGN KEY (tenant_id, provider_id) REFERENCES smrt_booking_resources (tenant_id, id)\n )`,\n `ALTER TABLE smrt_bookings\n ADD FOREIGN KEY (tenant_id, id, proposal_id) REFERENCES smrt_booking_proposals (tenant_id, booking_id, id)`,\n `CREATE TABLE smrt_booking_allocations (\n tenant_id uuid NOT NULL,\n booking_id uuid NOT NULL,\n proposal_id uuid NOT NULL,\n provider_id uuid NOT NULL,\n project_id uuid,\n exclusive boolean NOT NULL,\n start_at timestamptz NOT NULL,\n end_at timestamptz NOT NULL CHECK (end_at > start_at),\n released_at timestamptz,\n PRIMARY KEY (tenant_id, booking_id),\n FOREIGN KEY (tenant_id, booking_id, proposal_id)\n REFERENCES smrt_booking_proposals (tenant_id, booking_id, id),\n FOREIGN KEY (tenant_id, provider_id) REFERENCES smrt_booking_resources (tenant_id, id),\n CHECK (exclusive = (project_id IS NULL))\n )`,\n `CREATE INDEX smrt_booking_active_provider ON smrt_booking_allocations (tenant_id, provider_id, start_at)\n WHERE released_at IS NULL`,\n `CREATE UNIQUE INDEX smrt_booking_active_project_window\n ON smrt_booking_allocations (tenant_id, project_id, start_at, end_at)\n WHERE released_at IS NULL AND project_id IS NOT NULL`,\n `CREATE TABLE smrt_booking_commands (\n sequence bigint GENERATED ALWAYS AS IDENTITY UNIQUE,\n tenant_id uuid NOT NULL,\n operation text NOT NULL,\n key_hash text NOT NULL,\n actor_id text NOT NULL,\n payload_hash text NOT NULL,\n booking_id uuid,\n result jsonb,\n reason text,\n at timestamptz NOT NULL DEFAULT clock_timestamp(),\n PRIMARY KEY (tenant_id, operation, key_hash),\n FOREIGN KEY (tenant_id, booking_id) REFERENCES smrt_bookings (tenant_id, id)\n )`,\n `CREATE INDEX smrt_booking_history ON smrt_booking_commands (tenant_id, booking_id, sequence)`,\n];\n\n/** Initial PostgreSQL schema. Register once in the host deployment's migration ledger. */\nexport async function migrateBookingsV1(db: DatabaseInterface): Promise<void> {\n if (!db.transaction)\n throw new TypeError('Bookings requires a PostgreSQL transaction');\n await db.transaction(async (tx) => {\n for (const statement of statements) await tx.query(statement);\n });\n}\n","import { BookingError } from './errors.js';\nimport type {\n AvailabilityWindow,\n BookingWindow,\n DeclaredAvailabilityWindow,\n ResolvedBookingWindow,\n} from './types.js';\n\nconst WALL = /^(\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2})([+-]\\d{2}:\\d{2})$/;\nconst MINUTE_MS = 60_000;\n\nfunction formatter(zone: string): Intl.DateTimeFormat {\n // Intl also accepts offsets as time zone names. These are not IANA zones.\n if (typeof zone !== 'string' || !/^[A-Za-z][A-Za-z0-9_+/-]*$/.test(zone)) {\n throw new BookingError('invalid', 'An IANA time zone is required');\n }\n try {\n return new Intl.DateTimeFormat('en-GB', {\n timeZone: zone,\n calendar: 'iso8601',\n numberingSystem: 'latn',\n year: 'numeric',\n month: '2-digit',\n day: '2-digit',\n hour: '2-digit',\n minute: '2-digit',\n second: '2-digit',\n hourCycle: 'h23',\n });\n } catch {\n throw new BookingError('invalid', 'Unknown IANA time zone');\n }\n}\n\nfunction instant(value: string, format: Intl.DateTimeFormat): string {\n const match = typeof value === 'string' ? WALL.exec(value) : null;\n if (!match || match[2] === '-00:00') {\n throw new BookingError(\n 'invalid',\n 'Expected local minute with explicit UTC offset',\n );\n }\n const time = Date.parse(value);\n if (!Number.isFinite(time)) {\n throw new BookingError('invalid', 'Invalid local timestamp');\n }\n const parts = Object.fromEntries(\n format.formatToParts(time).map(({ type, value }) => [type, value]),\n );\n const local = `${parts.year.padStart(4, '0')}-${parts.month}-${parts.day}T${parts.hour}:${parts.minute}`;\n if (local !== match[1] || parts.second !== '00') {\n throw new BookingError(\n 'invalid',\n 'Local timestamp and offset do not exist in this zone',\n );\n }\n return new Date(time).toISOString();\n}\n\n/** Resolve half-open wall time; a DST fold must name the intended offset. */\nexport function resolveBookingWindow(\n window: BookingWindow,\n): ResolvedBookingWindow {\n const format = formatter(window.zone);\n const startUtc = instant(window.start, format);\n const endUtc = instant(window.end, format);\n if (startUtc >= endUtc) {\n throw new BookingError(\n 'invalid',\n 'A booking window must have positive duration',\n );\n }\n return {\n zone: format.resolvedOptions().timeZone,\n start: window.start,\n end: window.end,\n startUtc,\n endUtc,\n };\n}\n\nexport function elapsedHour(window: ResolvedBookingWindow): boolean {\n return (\n Date.parse(window.endUtc) - Date.parse(window.startUtc) === 60 * MINUTE_MS\n );\n}\n\nfunction oneLocalDay(window: ResolvedBookingWindow): boolean {\n const startDate = window.start.slice(0, 10);\n const endDate = window.end.slice(0, 10);\n if (startDate === endDate) return true;\n if (!window.end.slice(10).startsWith('T00:00')) return false;\n const tomorrow = new Date(`${startDate}T00:00:00Z`);\n tomorrow.setUTCDate(tomorrow.getUTCDate() + 1);\n return tomorrow.toISOString().slice(0, 10) === endDate;\n}\n\nexport function declareWindows(\n zone: string,\n windows: readonly AvailabilityWindow[],\n): DeclaredAvailabilityWindow[] {\n formatter(zone);\n if (!Array.isArray(windows))\n throw new BookingError('invalid', 'Expected availability windows');\n const days = new Set<string>();\n const normalized = windows\n .map(({ start, end, capacity, supervision }) => {\n if (\n !Number.isSafeInteger(capacity) ||\n capacity < 1 ||\n typeof supervision !== 'boolean'\n ) {\n throw new BookingError(\n 'invalid',\n 'Availability requires positive integer capacity and a supervision flag',\n );\n }\n const window = resolveBookingWindow({ start, end, zone });\n const day = start.slice(0, 10);\n if (supervision) {\n if (!oneLocalDay(window) || days.has(day)) {\n throw new BookingError(\n 'invalid',\n 'Declare at most one supervision window per local day',\n );\n }\n days.add(day);\n }\n return { ...window, capacity, supervision };\n })\n .sort((a, b) => a.startUtc.localeCompare(b.startUtc));\n if (\n normalized.some(\n (window, i) => i > 0 && normalized[i - 1].endUtc > window.startUtc,\n )\n ) {\n throw new BookingError('invalid', 'Availability windows must not overlap');\n }\n return normalized;\n}\n\nexport interface Reservation {\n startUtc: string;\n endUtc: string;\n exclusive: boolean;\n}\n\nexport function supervisionDeclared(\n candidate: ResolvedBookingWindow,\n windows: readonly DeclaredAvailabilityWindow[],\n): boolean {\n return windows.some(\n (window) =>\n window.supervision &&\n candidate.start === window.start &&\n candidate.end === window.end &&\n candidate.zone === window.zone,\n );\n}\n\n/** Check every segment; endpoints belonging to ending windows are excluded. */\nexport function fitsCapacity(\n candidate: Reservation,\n declared: readonly DeclaredAvailabilityWindow[],\n occupied: readonly Reservation[],\n): boolean {\n type Delta = { capacity: number; shared: number; exclusive: number };\n const boundaries = new Map<string, Delta>();\n const add = (at: string, key: keyof Delta, delta: number) => {\n const event = boundaries.get(at) ?? {\n capacity: 0,\n shared: 0,\n exclusive: 0,\n };\n event[key] += delta;\n boundaries.set(at, event);\n };\n const sweep = (\n window: Reservation | DeclaredAvailabilityWindow,\n key: keyof Delta,\n units: number,\n ) => {\n if (\n window.startUtc >= candidate.endUtc ||\n window.endUtc <= candidate.startUtc\n )\n return;\n add(\n window.startUtc < candidate.startUtc\n ? candidate.startUtc\n : window.startUtc,\n key,\n units,\n );\n if (window.endUtc < candidate.endUtc) add(window.endUtc, key, -units);\n };\n\n // Seed the first segment even when there is no declared coverage there.\n add(candidate.startUtc, 'capacity', 0);\n for (const window of declared) sweep(window, 'capacity', window.capacity);\n for (const reservation of occupied)\n sweep(reservation, reservation.exclusive ? 'exclusive' : 'shared', 1);\n\n let capacity = 0;\n let shared = 0;\n let exclusive = 0;\n for (const at of [...boundaries.keys()].sort()) {\n const delta = boundaries.get(at);\n if (!delta) continue;\n // Apply ends and starts together before checking the segment beginning here.\n capacity += delta.capacity;\n shared += delta.shared;\n exclusive += delta.exclusive;\n if (\n capacity < 1 ||\n exclusive > 0 ||\n (candidate.exclusive ? shared > 0 : shared >= capacity)\n )\n return false;\n }\n return true;\n}\n","import { createHash, randomUUID } from 'node:crypto';\nimport type { DatabaseInterface } from '@happyvertical/sql';\nimport { BookingError } from './errors.js';\nimport type {\n AvailabilityInput,\n BookingAccess,\n BookingAuthority,\n BookingContext,\n BookingHistoryEvent,\n BookingKind,\n BookingOffering,\n BookingOperation,\n BookingPage,\n BookingPageOptions,\n BookingProposal,\n BookingReceipt,\n BookingReferences,\n BookingStatus,\n BookingView,\n DeclaredAvailabilityWindow,\n ListBookingsOptions,\n ProposeBookingInput,\n ProviderAvailability,\n RequestBookingInput,\n ResolvedBookingWindow,\n} from './types.js';\nimport {\n declareWindows,\n elapsedHour,\n fitsCapacity,\n type Reservation,\n resolveBookingWindow,\n supervisionDeclared,\n} from './windows.js';\n\ntype WriteOperation = Exclude<BookingOperation, 'read' | 'history'>;\n\ninterface BookingRow {\n id: string;\n offering_id: string;\n contact_id: string;\n project_id: string | null;\n kind: BookingKind;\n status: BookingStatus;\n proposal_id: string | null;\n assented_proposal_id: string | null;\n confirmed_proposal_id: string | null;\n}\n\ninterface ProposalRow {\n provider_id: string;\n coverage: string;\n terms: string;\n approved_plan: string | null;\n}\n\ninterface Allocation extends Reservation {\n window: ResolvedBookingWindow;\n}\n\nconst UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n\nfunction id(value: string): string {\n if (typeof value !== 'string' || !UUID.test(value)) {\n throw new BookingError('invalid', 'Expected a UUID reference');\n }\n return value.toLowerCase();\n}\n\nfunction pageBounds({ afterId, limit = 50 }: BookingPageOptions) {\n if (!Number.isInteger(limit) || limit < 1 || limit > 100) {\n throw new BookingError('invalid', 'Page limit must be between 1 and 100');\n }\n return { after: afterId === undefined ? null : id(afterId), limit };\n}\n\nfunction page<T>(\n rows: T[],\n limit: number,\n cursor: (item: T) => string,\n): BookingPage<T> {\n const items = rows.slice(0, limit);\n return {\n items,\n nextCursor: rows.length > limit ? cursor(rows[limit - 1]) : null,\n };\n}\n\nfunction bookingView(\n row: BookingRow,\n proposal: BookingProposal | null,\n): BookingView {\n return {\n id: row.id,\n offeringId: row.offering_id,\n contactId: row.contact_id,\n projectId: row.project_id,\n kind: row.kind,\n status: row.status,\n proposal,\n assentedProposalId: row.assented_proposal_id,\n confirmedProposalId: row.confirmed_proposal_id,\n };\n}\n\nfunction proposalView(proposalId: string, row: ProposalRow): BookingProposal {\n return {\n id: proposalId,\n providerId: row.provider_id,\n window: JSON.parse(row.coverage),\n terms: JSON.parse(row.terms),\n approvedPlan:\n row.approved_plan === null ? null : JSON.parse(row.approved_plan),\n };\n}\n\nfunction required(value: string, max = 200): string {\n if (\n typeof value !== 'string' ||\n value.trim().length === 0 ||\n value.length > max ||\n value.includes('\\0')\n ) {\n throw new BookingError(\n 'invalid',\n 'Expected a nonempty text value within the length limit',\n );\n }\n return value;\n}\n\nfunction identity(input: BookingContext): Readonly<BookingContext> {\n return Object.freeze({\n tenantId: id(input.tenantId),\n actorId: required(input.actorId),\n });\n}\n\nfunction digest(value: string): string {\n return createHash('sha256').update(value).digest('hex');\n}\n\nfunction operator(access: BookingAccess): void {\n if (!access.operator)\n throw new BookingError('forbidden', 'Operator access required');\n}\n\nfunction contact(access: BookingAccess, contactId: string): void {\n if (access.contactId !== contactId) {\n throw new BookingError(\n 'forbidden',\n 'Only the named customer contact may act for this booking',\n );\n }\n}\n\nfunction visible(access: BookingAccess, row: BookingRow): void {\n if (!access.operator && access.contactId !== row.contact_id) {\n throw new BookingError('not-found', 'Booking does not exist');\n }\n}\n\nfunction pending(row: BookingRow): void {\n if (row.status !== 'requested' && row.status !== 'proposed') {\n throw new BookingError('conflict', 'Booking is no longer pending');\n }\n}\n\nfunction declared(\n kind: BookingKind,\n window: ResolvedBookingWindow,\n availability: readonly DeclaredAvailabilityWindow[],\n): void {\n if (\n (kind === 'supervision-day' &&\n !supervisionDeclared(window, availability)) ||\n (kind === 'developer-hour' &&\n !availability.some((entry) => entry.zone === window.zone)) ||\n !fitsCapacity(\n { ...window, exclusive: kind === 'developer-hour' },\n availability,\n [],\n )\n ) {\n throw new BookingError(\n 'unavailable',\n 'The proposed window is outside declared availability',\n );\n }\n}\n\n/** Transactional PostgreSQL service. The application supplies trusted identity and authorization. */\nexport class BookingService {\n readonly #db: DatabaseInterface;\n readonly #authority: BookingAuthority;\n\n constructor(db: DatabaseInterface, authority: BookingAuthority) {\n this.#db = db;\n this.#authority = authority;\n }\n\n async configureOffering(\n context: BookingContext,\n key: string,\n input: { offeringId: string; kind: BookingKind; enabled: boolean },\n ): Promise<BookingReceipt> {\n const offeringId = id(input.offeringId);\n const { kind, enabled } = input;\n if (\n (kind !== 'developer-hour' && kind !== 'supervision-day') ||\n typeof enabled !== 'boolean'\n ) {\n throw new BookingError(\n 'invalid',\n 'Invalid offering kind or enabled flag',\n );\n }\n return this.#write(\n context,\n 'configure-offering',\n key,\n { offeringId, kind, enabled },\n operator,\n async (tx, ctx) => {\n const saved = await tx.query(\n `INSERT INTO smrt_booking_offerings (tenant_id, id, kind, enabled) VALUES (?, ?, ?, ?)\n ON CONFLICT (tenant_id, id) DO UPDATE SET enabled = EXCLUDED.enabled\n WHERE smrt_booking_offerings.kind = EXCLUDED.kind RETURNING id`,\n ctx.tenantId,\n offeringId,\n kind,\n enabled,\n );\n if (!saved.rows.length)\n throw new BookingError('conflict', 'An offering kind cannot change');\n return { id: offeringId };\n },\n );\n }\n\n async declareAvailability(\n context: BookingContext,\n key: string,\n input: AvailabilityInput,\n ): Promise<BookingReceipt> {\n const providerId = id(input.providerId);\n const windows = declareWindows(input.zone, input.windows);\n return this.#write(\n context,\n 'declare-availability',\n key,\n { providerId, windows },\n operator,\n async (tx, ctx) => {\n await tx.query(\n `INSERT INTO smrt_booking_resources (tenant_id, id, windows) VALUES (?, ?, '[]')\n ON CONFLICT (tenant_id, id) DO NOTHING`,\n ctx.tenantId,\n providerId,\n );\n await this.#resource(tx, ctx, providerId, true);\n operator(await this.#access(ctx, 'declare-availability'));\n await this.#verify(ctx, { providerId });\n const allocated = await this.#allocations(tx, ctx, providerId);\n if (\n allocated.some(\n (item, i) =>\n (!item.exclusive && !supervisionDeclared(item.window, windows)) ||\n !fitsCapacity(\n item,\n windows,\n allocated.filter((_, j) => j !== i),\n ),\n )\n ) {\n throw new BookingError(\n 'unavailable',\n 'Availability change would undercut confirmed reservations',\n );\n }\n await tx.query(\n 'UPDATE smrt_booking_resources SET windows = ? WHERE tenant_id = ? AND id = ?',\n JSON.stringify(windows),\n ctx.tenantId,\n providerId,\n );\n return { id: providerId };\n },\n );\n }\n\n async request(\n context: BookingContext,\n key: string,\n input: RequestBookingInput,\n ): Promise<BookingReceipt> {\n const offeringId = id(input.offeringId);\n const contactId = id(input.contactId);\n const projectId =\n input.projectId === undefined ? null : id(input.projectId);\n return this.#write(\n context,\n 'request',\n key,\n { offeringId, contactId, projectId },\n (access) => {\n if (!access.operator) contact(access, contactId);\n },\n async (tx, ctx) => {\n const { rows } = await tx.query(\n 'SELECT kind, enabled FROM smrt_booking_offerings WHERE tenant_id = ? AND id = ? FOR SHARE',\n ctx.tenantId,\n offeringId,\n );\n if (!rows[0]?.enabled)\n throw new BookingError('not-found', 'Offering is not available');\n const kind = rows[0].kind as BookingKind;\n if (kind === 'supervision-day' && !projectId) {\n throw new BookingError(\n 'invalid',\n 'A supervision request requires a project',\n );\n }\n await this.#verify(ctx, {\n contactId,\n projectId: projectId ?? undefined,\n });\n const bookingId = randomUUID();\n await tx.query(\n `INSERT INTO smrt_bookings (tenant_id, id, offering_id, contact_id, project_id, kind, status)\n VALUES (?, ?, ?, ?, ?, ?, 'requested')`,\n ctx.tenantId,\n bookingId,\n offeringId,\n contactId,\n projectId,\n kind,\n );\n return { id: bookingId };\n },\n { createsBooking: true },\n );\n }\n\n async propose(\n context: BookingContext,\n key: string,\n input: ProposeBookingInput,\n ): Promise<BookingReceipt> {\n const bookingId = id(input.bookingId);\n const providerId = id(input.providerId);\n const window = resolveBookingWindow(input.window);\n const terms = {\n revision: required(input.terms.revision),\n text: required(input.terms.text, 65_536),\n };\n const plan = input.approvedPlan;\n const approvedPlan =\n plan === undefined\n ? null\n : { id: id(plan.id), version: required(plan.version) };\n return this.#write(\n context,\n 'propose',\n key,\n { bookingId, providerId, window, terms, approvedPlan },\n operator,\n async (tx, ctx) => {\n const row = await this.#booking(tx, ctx, bookingId, true);\n pending(row);\n if (\n row.kind === 'supervision-day'\n ? !approvedPlan\n : approvedPlan || !elapsedHour(window)\n ) {\n throw new BookingError(\n 'invalid',\n 'Supervision requires a plan version; dedicated time requires one elapsed hour',\n );\n }\n await this.#verify(ctx, {\n ...this.#refs(row),\n providerId,\n approvedPlan: approvedPlan ?? undefined,\n });\n declared(\n row.kind,\n window,\n await this.#resource(tx, ctx, providerId, false),\n );\n const proposalId = randomUUID();\n await tx.query(\n `INSERT INTO smrt_booking_proposals (tenant_id, booking_id, id, provider_id, coverage, terms, approved_plan)\n VALUES (?, ?, ?, ?, ?, ?, ?)`,\n ctx.tenantId,\n bookingId,\n proposalId,\n providerId,\n JSON.stringify(window),\n JSON.stringify(terms),\n approvedPlan && JSON.stringify(approvedPlan),\n );\n await tx.query(\n `UPDATE smrt_bookings SET status = 'proposed', proposal_id = ?, assented_proposal_id = NULL\n WHERE tenant_id = ? AND id = ?`,\n proposalId,\n ctx.tenantId,\n bookingId,\n );\n return { id: bookingId, proposalId };\n },\n { bookingId },\n );\n }\n\n async assent(\n context: BookingContext,\n key: string,\n input: { bookingId: string; proposalId: string },\n ): Promise<BookingReceipt> {\n const bookingId = id(input.bookingId);\n const proposalId = id(input.proposalId);\n return this.#write(\n context,\n 'assent',\n key,\n { bookingId, proposalId },\n async (access, tx, ctx) => {\n // Check contact ownership before exposing the existence of a replay.\n const row = await this.#booking(tx, ctx, bookingId, false);\n visible(access, row);\n contact(access, row.contact_id);\n },\n async (tx, ctx) => {\n const row = await this.#booking(tx, ctx, bookingId, true);\n contact(await this.#access(ctx, 'assent'), row.contact_id);\n this.#current(row, proposalId);\n if (row.assented_proposal_id)\n throw new BookingError(\n 'conflict',\n 'Customer assent is already recorded',\n );\n await this.#verify(ctx, this.#refs(row));\n await tx.query(\n 'UPDATE smrt_bookings SET assented_proposal_id = ? WHERE tenant_id = ? AND id = ?',\n proposalId,\n ctx.tenantId,\n bookingId,\n );\n return { id: bookingId, proposalId };\n },\n { bookingId },\n );\n }\n\n async confirm(\n context: BookingContext,\n key: string,\n input: { bookingId: string; proposalId: string },\n ): Promise<BookingReceipt> {\n const bookingId = id(input.bookingId);\n const proposalId = id(input.proposalId);\n return this.#write(\n context,\n 'confirm',\n key,\n { bookingId, proposalId },\n operator,\n async (tx, ctx) => {\n const row = await this.#booking(tx, ctx, bookingId, true);\n this.#current(row, proposalId);\n if (row.assented_proposal_id !== proposalId) {\n throw new BookingError(\n 'conflict',\n 'Exact proposal does not have customer assent',\n );\n }\n const proposal = await this.#proposal(tx, ctx, row, proposalId);\n const availability = await this.#resource(\n tx,\n ctx,\n proposal.providerId,\n true,\n );\n // Refresh after waiting for contested capacity; this does not lock external authorities.\n operator(await this.#access(ctx, 'confirm'));\n await this.#verify(ctx, {\n ...this.#refs(row),\n providerId: proposal.providerId,\n approvedPlan: proposal.approvedPlan ?? undefined,\n });\n const exclusive = row.kind === 'developer-hour';\n const window = proposal.window;\n declared(row.kind, window, availability);\n if (\n !fitsCapacity(\n { ...window, exclusive },\n availability,\n await this.#allocations(tx, ctx, proposal.providerId, window),\n )\n ) {\n throw new BookingError(\n 'unavailable',\n 'The proposed window has insufficient capacity',\n );\n }\n const inserted = await tx.query(\n `INSERT INTO smrt_booking_allocations\n (tenant_id, booking_id, proposal_id, provider_id, project_id, exclusive, start_at, end_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?)\n ON CONFLICT (tenant_id, project_id, start_at, end_at)\n WHERE released_at IS NULL AND project_id IS NOT NULL DO NOTHING RETURNING booking_id`,\n ctx.tenantId,\n bookingId,\n proposalId,\n proposal.providerId,\n exclusive ? null : row.project_id,\n exclusive,\n window.startUtc,\n window.endUtc,\n );\n if (!inserted.rows.length) {\n throw new BookingError(\n 'duplicate-project-window',\n 'Project already has a reservation for this window',\n );\n }\n await tx.query(\n `UPDATE smrt_bookings SET status = 'confirmed', confirmed_proposal_id = ?\n WHERE tenant_id = ? AND id = ?`,\n proposalId,\n ctx.tenantId,\n bookingId,\n );\n return { id: bookingId, proposalId };\n },\n { bookingId },\n );\n }\n\n decline(\n context: BookingContext,\n key: string,\n input: { bookingId: string; reason: string },\n ): Promise<BookingReceipt> {\n return this.#close(context, 'decline', key, input);\n }\n\n cancel(\n context: BookingContext,\n key: string,\n input: { bookingId: string; reason: string },\n ): Promise<BookingReceipt> {\n return this.#close(context, 'cancel', key, input);\n }\n\n async get(context: BookingContext, bookingId: string): Promise<BookingView> {\n const ctx = identity(context);\n const access = await this.#access(ctx, 'read');\n const row = await this.#booking(this.#db, ctx, id(bookingId), false);\n visible(access, row);\n return bookingView(\n row,\n row.proposal_id\n ? await this.#proposal(this.#db, ctx, row, row.proposal_id)\n : null,\n );\n }\n\n /** Lists the tenant's bookings, restricted to the current contact for nonoperators. */\n async listBookings(\n context: BookingContext,\n options: ListBookingsOptions = {},\n ): Promise<BookingPage<BookingView>> {\n const ctx = identity(context);\n const access = await this.#discover(ctx);\n const { after, limit } = pageBounds(options);\n const projectId =\n options.projectId === undefined ? null : id(options.projectId);\n const result = await this.#db.query(\n `SELECT b.id, b.offering_id, b.contact_id, b.project_id, b.kind, b.status,\n b.proposal_id, b.assented_proposal_id, b.confirmed_proposal_id,\n p.provider_id, p.coverage::text, p.terms::text, p.approved_plan::text\n FROM smrt_bookings b\n LEFT JOIN smrt_booking_proposals p\n ON p.tenant_id = b.tenant_id AND p.booking_id = b.id AND p.id = b.proposal_id\n WHERE b.tenant_id = ? AND (?::boolean OR b.contact_id = ?)\n AND (?::uuid IS NULL OR b.project_id = ?)\n AND (?::uuid IS NULL OR b.id > ?)\n ORDER BY b.id LIMIT ?`,\n ctx.tenantId,\n access.operator,\n access.contactId ?? null,\n projectId,\n projectId,\n after,\n after,\n limit + 1,\n );\n const rows = result.rows as Array<BookingRow & ProposalRow>;\n const selected = page(rows, limit, (row) => row.id);\n return {\n items: selected.items.map((row) =>\n bookingView(\n row,\n row.proposal_id ? proposalView(row.proposal_id, row) : null,\n ),\n ),\n nextCursor: selected.nextCursor,\n };\n }\n\n /** Lists configured offerings. Contacts only see enabled offerings. */\n async listOfferings(\n context: BookingContext,\n options: BookingPageOptions = {},\n ): Promise<BookingPage<BookingOffering>> {\n const ctx = identity(context);\n const access = await this.#discover(ctx);\n const { after, limit } = pageBounds(options);\n const { rows } = await this.#db.query(\n `SELECT id, kind, enabled FROM smrt_booking_offerings\n WHERE tenant_id = ? AND (?::boolean OR enabled)\n AND (?::uuid IS NULL OR id > ?)\n ORDER BY id LIMIT ?`,\n ctx.tenantId,\n access.operator,\n after,\n after,\n limit + 1,\n );\n return page(rows as BookingOffering[], limit, (row) => row.id);\n }\n\n /** Lists provider schedules so operators can inspect or replace existing windows. */\n async listAvailability(\n context: BookingContext,\n options: BookingPageOptions = {},\n ): Promise<BookingPage<ProviderAvailability>> {\n const ctx = identity(context);\n operator(await this.#access(ctx, 'read'));\n const { after, limit } = pageBounds(options);\n const { rows } = await this.#db.query(\n `SELECT id, windows::text FROM smrt_booking_resources\n WHERE tenant_id = ? AND (?::uuid IS NULL OR id > ?)\n ORDER BY id LIMIT ?`,\n ctx.tenantId,\n after,\n after,\n limit + 1,\n );\n const selected = page(\n rows as Array<{ id: string; windows: string }>,\n limit,\n (row) => row.id,\n );\n return {\n items: selected.items.map((row) => ({\n providerId: row.id,\n windows: JSON.parse(row.windows),\n })),\n nextCursor: selected.nextCursor,\n };\n }\n\n async getProposal(\n context: BookingContext,\n bookingId: string,\n proposalId: string,\n ): Promise<BookingProposal> {\n const ctx = identity(context);\n const access = await this.#access(ctx, 'read');\n const row = await this.#booking(this.#db, ctx, id(bookingId), false);\n visible(access, row);\n return this.#proposal(this.#db, ctx, row, id(proposalId));\n }\n\n async history(\n context: BookingContext,\n bookingId: string,\n ): Promise<BookingHistoryEvent[]> {\n const ctx = identity(context);\n operator(await this.#access(ctx, 'history'));\n await this.#booking(this.#db, ctx, id(bookingId), false);\n const { rows } = await this.#db.query(\n `SELECT operation, actor_id, result->>'proposalId' AS proposal_id, reason, at\n FROM smrt_booking_commands WHERE tenant_id = ? AND booking_id = ? ORDER BY at, sequence`,\n ctx.tenantId,\n id(bookingId),\n );\n return rows.map((row) => ({\n operation: row.operation as BookingOperation,\n actorId: row.actor_id,\n proposalId: row.proposal_id,\n reason: row.reason,\n at: new Date(row.at).toISOString(),\n }));\n }\n\n async #close(\n context: BookingContext,\n operation: 'decline' | 'cancel',\n key: string,\n input: { bookingId: string; reason: string },\n ): Promise<BookingReceipt> {\n const bookingId = id(input.bookingId);\n const reason = required(input.reason, 2_048);\n return this.#write(\n context,\n operation,\n key,\n { bookingId, reason },\n operator,\n async (tx, ctx) => {\n const row = await this.#booking(tx, ctx, bookingId, true);\n if (\n operation === 'cancel' &&\n row.confirmed_proposal_id &&\n row.status === 'confirmed'\n ) {\n const proposal = await this.#proposal(\n tx,\n ctx,\n row,\n row.confirmed_proposal_id,\n );\n await this.#resource(tx, ctx, proposal.providerId, true);\n operator(await this.#access(ctx, 'cancel'));\n const released = await tx.query(\n `UPDATE smrt_booking_allocations SET released_at = clock_timestamp()\n WHERE tenant_id = ? AND booking_id = ? AND released_at IS NULL RETURNING booking_id`,\n ctx.tenantId,\n bookingId,\n );\n if (!released.rows.length)\n throw new BookingError(\n 'conflict',\n 'Confirmed booking has no active allocation',\n );\n } else {\n pending(row);\n }\n await tx.query(\n 'UPDATE smrt_bookings SET status = ? WHERE tenant_id = ? AND id = ?',\n operation === 'cancel' ? 'cancelled' : 'declined',\n ctx.tenantId,\n bookingId,\n );\n return {\n id: bookingId,\n ...(row.proposal_id ? { proposalId: row.proposal_id } : {}),\n };\n },\n { bookingId, reason },\n );\n }\n\n async #access(\n context: Readonly<BookingContext>,\n operation: BookingOperation,\n ): Promise<BookingAccess> {\n const access = await this.#authority.access(context, operation);\n if (!access) throw new BookingError('forbidden', 'Booking access denied');\n return {\n operator: access.operator === true,\n contactId:\n access.contactId === undefined ? undefined : id(access.contactId),\n };\n }\n\n async #discover(ctx: Readonly<BookingContext>): Promise<BookingAccess> {\n const access = await this.#access(ctx, 'read');\n if (!access.operator && !access.contactId) {\n throw new BookingError(\n 'forbidden',\n 'A named customer contact is required',\n );\n }\n return access;\n }\n\n async #verify(\n ctx: Readonly<BookingContext>,\n refs: BookingReferences,\n ): Promise<void> {\n // Use a detached copy so a host callback cannot change the version being verified and saved.\n if (\n (await this.#authority.verifyReferences(ctx, structuredClone(refs))) !==\n true\n ) {\n throw new BookingError(\n 'forbidden',\n 'Booking references or approval could not be verified',\n );\n }\n }\n\n #refs(row: BookingRow): BookingReferences {\n return {\n contactId: row.contact_id,\n projectId: row.project_id ?? undefined,\n };\n }\n\n async #booking(\n tx: DatabaseInterface,\n ctx: Readonly<BookingContext>,\n bookingId: string,\n lock: boolean,\n ): Promise<BookingRow> {\n const { rows } = await tx.query(\n `SELECT * FROM smrt_bookings WHERE tenant_id = ? AND id = ?${lock ? ' FOR UPDATE' : ''}`,\n ctx.tenantId,\n bookingId,\n );\n if (!rows[0]) throw new BookingError('not-found', 'Booking does not exist');\n return rows[0] as BookingRow;\n }\n\n #current(row: BookingRow, proposalId: string): void {\n if (row.status !== 'proposed' || row.proposal_id !== proposalId) {\n throw new BookingError(\n 'conflict',\n 'Booking does not have this current proposal',\n );\n }\n }\n\n async #proposal(\n tx: DatabaseInterface,\n ctx: Readonly<BookingContext>,\n booking: BookingRow,\n proposalId: string,\n ): Promise<BookingProposal> {\n const { rows } = await tx.query(\n `SELECT provider_id, coverage::text, terms::text, approved_plan::text FROM smrt_booking_proposals\n WHERE tenant_id = ? AND booking_id = ? AND id = ?`,\n ctx.tenantId,\n booking.id,\n proposalId,\n );\n const row = rows[0];\n if (!row) throw new BookingError('not-found', 'Proposal does not exist');\n return proposalView(proposalId, row as ProposalRow);\n }\n\n async #resource(\n tx: DatabaseInterface,\n ctx: Readonly<BookingContext>,\n providerId: string,\n lock: boolean,\n ): Promise<DeclaredAvailabilityWindow[]> {\n const { rows } = await tx.query(\n `SELECT windows::text FROM smrt_booking_resources WHERE tenant_id = ? AND id = ?${lock ? ' FOR UPDATE' : ''}`,\n ctx.tenantId,\n providerId,\n );\n if (!rows[0])\n throw new BookingError(\n 'not-found',\n 'Provider availability is not configured',\n );\n return JSON.parse(rows[0].windows);\n }\n\n async #allocations(\n tx: DatabaseInterface,\n ctx: Readonly<BookingContext>,\n providerId: string,\n window?: ResolvedBookingWindow,\n ): Promise<Allocation[]> {\n const { rows } = await tx.query(\n `SELECT a.exclusive, p.coverage::text FROM smrt_booking_allocations a\n JOIN smrt_booking_proposals p ON p.tenant_id = a.tenant_id AND p.booking_id = a.booking_id AND p.id = a.proposal_id\n WHERE a.tenant_id = ? AND a.provider_id = ? AND a.released_at IS NULL${window ? ' AND a.start_at < ? AND a.end_at > ?' : ''}`,\n ctx.tenantId,\n providerId,\n ...(window ? [window.endUtc, window.startUtc] : []),\n );\n return rows.map((row) => {\n const snapshot = JSON.parse(row.coverage) as ResolvedBookingWindow;\n return { ...snapshot, window: snapshot, exclusive: row.exclusive };\n });\n }\n\n async #write(\n context: BookingContext,\n operation: WriteOperation,\n key: string,\n canonical: object,\n permit: (\n access: BookingAccess,\n tx: DatabaseInterface,\n ctx: Readonly<BookingContext>,\n ) => void | Promise<void>,\n work: (\n tx: DatabaseInterface,\n ctx: Readonly<BookingContext>,\n ) => Promise<BookingReceipt>,\n options: {\n bookingId?: string;\n reason?: string;\n createsBooking?: boolean;\n } = {},\n ): Promise<BookingReceipt> {\n const ctx = identity(context);\n const keyHash = digest(required(key));\n const payloadHash = digest(JSON.stringify(canonical));\n if (!this.#db.transaction)\n throw new TypeError('Bookings requires a PostgreSQL transaction');\n return this.#db.transaction(async (tx) => {\n await tx.query('SET TRANSACTION ISOLATION LEVEL READ COMMITTED');\n await permit(await this.#access(ctx, operation), tx, ctx);\n const inserted = await tx.query(\n `INSERT INTO smrt_booking_commands (tenant_id, operation, key_hash, actor_id, payload_hash)\n VALUES (?, ?, ?, ?, ?) ON CONFLICT (tenant_id, operation, key_hash) DO NOTHING RETURNING key_hash`,\n ctx.tenantId,\n operation,\n keyHash,\n ctx.actorId,\n payloadHash,\n );\n if (!inserted.rows.length) {\n await permit(await this.#access(ctx, operation), tx, ctx);\n const { rows } = await tx.query(\n `SELECT actor_id, payload_hash, result::text FROM smrt_booking_commands\n WHERE tenant_id = ? AND operation = ? AND key_hash = ?`,\n ctx.tenantId,\n operation,\n keyHash,\n );\n const prior = rows[0];\n if (\n !prior ||\n prior.actor_id !== ctx.actorId ||\n prior.payload_hash !== payloadHash ||\n !prior.result\n ) {\n throw new BookingError(\n 'conflict',\n 'Idempotency key is bound to a different command',\n );\n }\n return JSON.parse(prior.result) as BookingReceipt;\n }\n const result = await work(tx, ctx);\n await tx.query(\n `UPDATE smrt_booking_commands SET booking_id = ?, result = ?, reason = ?, at = clock_timestamp()\n WHERE tenant_id = ? AND operation = ? AND key_hash = ?`,\n options.bookingId ?? (options.createsBooking ? result.id : null),\n JSON.stringify(result),\n options.reason ?? null,\n ctx.tenantId,\n operation,\n keyHash,\n );\n return result;\n });\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["value"],"sources":["../src/errors.ts","../src/migration.ts","../src/windows.ts","../src/service.ts"],"sourcesContent":["/** A service rejection suitable for mapping to a host response. */\nexport class BookingError extends Error {\n constructor(\n readonly code:\n | 'invalid'\n | 'forbidden'\n | 'not-found'\n | 'conflict'\n | 'unavailable'\n | 'duplicate-project-window',\n message: string,\n ) {\n super(message);\n this.name = 'BookingError';\n }\n}\n","import type { DatabaseInterface } from '@happyvertical/sql';\n\nconst statements = [\n `CREATE TABLE smrt_booking_offerings (\n tenant_id uuid NOT NULL,\n id uuid NOT NULL,\n kind text NOT NULL CHECK (kind IN ('developer-hour', 'supervision-day')),\n enabled boolean NOT NULL,\n PRIMARY KEY (tenant_id, id)\n )`,\n `CREATE TABLE smrt_booking_resources (\n tenant_id uuid NOT NULL,\n id uuid NOT NULL,\n windows jsonb NOT NULL,\n PRIMARY KEY (tenant_id, id)\n )`,\n `CREATE TABLE smrt_bookings (\n tenant_id uuid NOT NULL,\n id uuid NOT NULL,\n offering_id uuid NOT NULL,\n contact_id uuid NOT NULL,\n project_id uuid,\n kind text NOT NULL CHECK (kind IN ('developer-hour', 'supervision-day')),\n status text NOT NULL CHECK (status IN ('requested', 'proposed', 'confirmed', 'declined', 'cancelled')),\n proposal_id uuid,\n assented_proposal_id uuid,\n confirmed_proposal_id uuid,\n PRIMARY KEY (tenant_id, id),\n FOREIGN KEY (tenant_id, offering_id) REFERENCES smrt_booking_offerings (tenant_id, id),\n CHECK (kind <> 'supervision-day' OR project_id IS NOT NULL),\n CHECK (status <> 'confirmed' OR confirmed_proposal_id IS NOT NULL),\n CHECK (assented_proposal_id IS NULL OR assented_proposal_id IS NOT DISTINCT FROM proposal_id),\n CHECK (confirmed_proposal_id IS NULL OR confirmed_proposal_id IS NOT DISTINCT FROM assented_proposal_id)\n )`,\n `CREATE TABLE smrt_booking_proposals (\n tenant_id uuid NOT NULL,\n booking_id uuid NOT NULL,\n id uuid NOT NULL,\n provider_id uuid NOT NULL,\n coverage jsonb NOT NULL,\n terms jsonb NOT NULL,\n approved_plan jsonb,\n PRIMARY KEY (tenant_id, booking_id, id),\n FOREIGN KEY (tenant_id, booking_id) REFERENCES smrt_bookings (tenant_id, id),\n FOREIGN KEY (tenant_id, provider_id) REFERENCES smrt_booking_resources (tenant_id, id)\n )`,\n `ALTER TABLE smrt_bookings\n ADD FOREIGN KEY (tenant_id, id, proposal_id) REFERENCES smrt_booking_proposals (tenant_id, booking_id, id)`,\n `CREATE TABLE smrt_booking_allocations (\n tenant_id uuid NOT NULL,\n booking_id uuid NOT NULL,\n proposal_id uuid NOT NULL,\n provider_id uuid NOT NULL,\n project_id uuid,\n exclusive boolean NOT NULL,\n start_at timestamptz NOT NULL,\n end_at timestamptz NOT NULL CHECK (end_at > start_at),\n released_at timestamptz,\n PRIMARY KEY (tenant_id, booking_id),\n FOREIGN KEY (tenant_id, booking_id, proposal_id)\n REFERENCES smrt_booking_proposals (tenant_id, booking_id, id),\n FOREIGN KEY (tenant_id, provider_id) REFERENCES smrt_booking_resources (tenant_id, id),\n CHECK (exclusive = (project_id IS NULL))\n )`,\n `CREATE INDEX smrt_booking_active_provider ON smrt_booking_allocations (tenant_id, provider_id, start_at)\n WHERE released_at IS NULL`,\n `CREATE UNIQUE INDEX smrt_booking_active_project_window\n ON smrt_booking_allocations (tenant_id, project_id, start_at, end_at)\n WHERE released_at IS NULL AND project_id IS NOT NULL`,\n `CREATE TABLE smrt_booking_commands (\n sequence bigint GENERATED ALWAYS AS IDENTITY UNIQUE,\n tenant_id uuid NOT NULL,\n operation text NOT NULL,\n key_hash text NOT NULL,\n actor_id text NOT NULL,\n payload_hash text NOT NULL,\n booking_id uuid,\n result jsonb,\n reason text,\n at timestamptz NOT NULL DEFAULT clock_timestamp(),\n PRIMARY KEY (tenant_id, operation, key_hash),\n FOREIGN KEY (tenant_id, booking_id) REFERENCES smrt_bookings (tenant_id, id)\n )`,\n `CREATE INDEX smrt_booking_history ON smrt_booking_commands (tenant_id, booking_id, sequence)`,\n];\n\n/** Initial PostgreSQL schema. Register once in the host deployment's migration ledger. */\nexport async function migrateBookingsV1(db: DatabaseInterface): Promise<void> {\n if (!db.transaction)\n throw new TypeError('Bookings requires a PostgreSQL transaction');\n await db.transaction(async (tx) => {\n for (const statement of statements) await tx.query(statement);\n });\n}\n\nconst v2 = [\n `ALTER TABLE smrt_booking_offerings ADD COLUMN unit text`,\n `UPDATE smrt_booking_offerings SET unit = CASE kind WHEN 'supervision-day' THEN 'day' ELSE 'hour' END`,\n `ALTER TABLE smrt_booking_offerings ALTER COLUMN unit SET NOT NULL`,\n `ALTER TABLE smrt_booking_offerings ADD COLUMN price_cents bigint`,\n `ALTER TABLE smrt_booking_offerings ADD COLUMN currency text`,\n `ALTER TABLE smrt_booking_offerings\n ADD CONSTRAINT smrt_booking_offerings_unit CHECK (unit IN ('hour','day','week','month')),\n ADD CONSTRAINT smrt_booking_offerings_supervision_unit CHECK (kind <> 'supervision-day' OR unit = 'day'),\n ADD CONSTRAINT smrt_booking_offerings_price CHECK (price_cents IS NULL OR price_cents >= 0),\n ADD CONSTRAINT smrt_booking_offerings_currency CHECK (currency IS NULL OR currency ~ '^[A-Z]{3}$'),\n ADD CONSTRAINT smrt_booking_offerings_priced CHECK ((price_cents IS NULL) = (currency IS NULL))`,\n `ALTER TABLE smrt_booking_offerings ADD COLUMN title text NOT NULL DEFAULT ''`,\n `ALTER TABLE smrt_booking_offerings ADD COLUMN description text NOT NULL DEFAULT ''`,\n `ALTER TABLE smrt_booking_offerings ADD COLUMN public boolean NOT NULL DEFAULT false`,\n `ALTER TABLE smrt_booking_offerings ADD CONSTRAINT smrt_booking_offerings_title CHECK (char_length(title) <= 120)`,\n `ALTER TABLE smrt_booking_offerings ADD CONSTRAINT smrt_booking_offerings_description CHECK (char_length(description) <= 2000)`,\n `ALTER TABLE smrt_bookings ADD COLUMN unit text`,\n `UPDATE smrt_bookings b SET unit = o.unit FROM smrt_booking_offerings o\n WHERE o.tenant_id = b.tenant_id AND o.id = b.offering_id`,\n `ALTER TABLE smrt_bookings ALTER COLUMN unit SET NOT NULL`,\n `ALTER TABLE smrt_bookings ADD COLUMN quantity integer NOT NULL DEFAULT 1`,\n `ALTER TABLE smrt_bookings\n ADD CONSTRAINT smrt_bookings_unit CHECK (unit IN ('hour','day','week','month')),\n ADD CONSTRAINT smrt_bookings_quantity CHECK (quantity >= 1 AND quantity <= 1000),\n ADD CONSTRAINT smrt_bookings_supervision_quantity CHECK (kind <> 'supervision-day' OR (unit = 'day' AND quantity = 1))`,\n `ALTER TABLE smrt_booking_proposals ADD COLUMN windows jsonb`,\n `UPDATE smrt_booking_proposals SET windows = jsonb_build_array(coverage)`,\n `ALTER TABLE smrt_booking_proposals ALTER COLUMN windows SET NOT NULL`,\n `ALTER TABLE smrt_booking_proposals ADD COLUMN unit text`,\n `UPDATE smrt_booking_proposals p SET unit = b.unit FROM smrt_bookings b\n WHERE b.tenant_id = p.tenant_id AND b.id = p.booking_id`,\n `ALTER TABLE smrt_booking_proposals ALTER COLUMN unit SET NOT NULL`,\n `ALTER TABLE smrt_booking_proposals ADD COLUMN quantity integer NOT NULL DEFAULT 1`,\n `ALTER TABLE smrt_booking_proposals ADD COLUMN unit_price_cents bigint`,\n `ALTER TABLE smrt_booking_proposals ADD COLUMN currency text`,\n `ALTER TABLE smrt_booking_proposals ADD COLUMN total_cents bigint`,\n `ALTER TABLE smrt_booking_proposals\n ADD CONSTRAINT smrt_booking_proposals_unit CHECK (unit IN ('hour','day','week','month')),\n ADD CONSTRAINT smrt_booking_proposals_quantity CHECK (quantity >= 1 AND quantity <= 1000),\n ADD CONSTRAINT smrt_booking_proposals_priced\n CHECK ((unit_price_cents IS NULL) = (currency IS NULL) AND (unit_price_cents IS NULL) = (total_cents IS NULL)),\n ADD CONSTRAINT smrt_booking_proposals_total CHECK (total_cents IS NULL OR total_cents = unit_price_cents * quantity),\n ADD CONSTRAINT smrt_booking_proposals_windows CHECK (jsonb_typeof(windows) = 'array' AND jsonb_array_length(windows) >= 1)`,\n];\n\n/**\n * Additive priced-offering, quantity and multi-window schema. Register once in\n * the host deployment's migration ledger after `migrateBookingsV1`. Like V1 it\n * is not idempotent, and it never drops data.\n */\nexport async function migrateBookingsV2(db: DatabaseInterface): Promise<void> {\n if (!db.transaction)\n throw new TypeError('Bookings requires a PostgreSQL transaction');\n await db.transaction(async (tx) => {\n for (const statement of v2) await tx.query(statement);\n });\n}\n","import { BookingError } from './errors.js';\nimport type {\n AvailabilityWindow,\n BookingUnit,\n BookingWindow,\n DeclaredAvailabilityWindow,\n ResolvedBookingWindow,\n} from './types.js';\n\nconst WALL = /^(\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2})([+-]\\d{2}:\\d{2})$/;\nconst MINUTE_MS = 60_000;\n\nfunction formatter(zone: string): Intl.DateTimeFormat {\n // Intl also accepts offsets as time zone names. These are not IANA zones.\n if (typeof zone !== 'string' || !/^[A-Za-z][A-Za-z0-9_+/-]*$/.test(zone)) {\n throw new BookingError('invalid', 'An IANA time zone is required');\n }\n try {\n return new Intl.DateTimeFormat('en-GB', {\n timeZone: zone,\n calendar: 'iso8601',\n numberingSystem: 'latn',\n year: 'numeric',\n month: '2-digit',\n day: '2-digit',\n hour: '2-digit',\n minute: '2-digit',\n second: '2-digit',\n hourCycle: 'h23',\n });\n } catch {\n throw new BookingError('invalid', 'Unknown IANA time zone');\n }\n}\n\nfunction instant(value: string, format: Intl.DateTimeFormat): string {\n const match = typeof value === 'string' ? WALL.exec(value) : null;\n if (!match || match[2] === '-00:00') {\n throw new BookingError(\n 'invalid',\n 'Expected local minute with explicit UTC offset',\n );\n }\n const time = Date.parse(value);\n if (!Number.isFinite(time)) {\n throw new BookingError('invalid', 'Invalid local timestamp');\n }\n const parts = Object.fromEntries(\n format.formatToParts(time).map(({ type, value }) => [type, value]),\n );\n const local = `${parts.year.padStart(4, '0')}-${parts.month}-${parts.day}T${parts.hour}:${parts.minute}`;\n if (local !== match[1] || parts.second !== '00') {\n throw new BookingError(\n 'invalid',\n 'Local timestamp and offset do not exist in this zone',\n );\n }\n return new Date(time).toISOString();\n}\n\n/** Resolve half-open wall time; a DST fold must name the intended offset. */\nexport function resolveBookingWindow(\n window: BookingWindow,\n): ResolvedBookingWindow {\n const format = formatter(window.zone);\n const startUtc = instant(window.start, format);\n const endUtc = instant(window.end, format);\n if (startUtc >= endUtc) {\n throw new BookingError(\n 'invalid',\n 'A booking window must have positive duration',\n );\n }\n return {\n zone: format.resolvedOptions().timeZone,\n start: window.start,\n end: window.end,\n startUtc,\n endUtc,\n };\n}\n\nexport function elapsedHour(window: ResolvedBookingWindow): boolean {\n return (\n Date.parse(window.endUtc) - Date.parse(window.startUtc) === 60 * MINUTE_MS\n );\n}\n\nfunction oneLocalDay(window: ResolvedBookingWindow): boolean {\n const startDate = window.start.slice(0, 10);\n const endDate = window.end.slice(0, 10);\n if (startDate === endDate) return true;\n if (!window.end.slice(10).startsWith('T00:00')) return false;\n const tomorrow = new Date(`${startDate}T00:00:00Z`);\n tomorrow.setUTCDate(tomorrow.getUTCDate() + 1);\n return tomorrow.toISOString().slice(0, 10) === endDate;\n}\n\nexport function declareWindows(\n zone: string,\n windows: readonly AvailabilityWindow[],\n): DeclaredAvailabilityWindow[] {\n formatter(zone);\n if (!Array.isArray(windows))\n throw new BookingError('invalid', 'Expected availability windows');\n const days = new Set<string>();\n const normalized = windows\n .map(({ start, end, capacity, supervision }) => {\n if (\n !Number.isSafeInteger(capacity) ||\n capacity < 1 ||\n typeof supervision !== 'boolean'\n ) {\n throw new BookingError(\n 'invalid',\n 'Availability requires positive integer capacity and a supervision flag',\n );\n }\n const window = resolveBookingWindow({ start, end, zone });\n const day = start.slice(0, 10);\n if (supervision) {\n if (!oneLocalDay(window) || days.has(day)) {\n throw new BookingError(\n 'invalid',\n 'Declare at most one supervision window per local day',\n );\n }\n days.add(day);\n }\n return { ...window, capacity, supervision };\n })\n .sort((a, b) => a.startUtc.localeCompare(b.startUtc));\n if (\n normalized.some(\n (window, i) => i > 0 && normalized[i - 1].endUtc > window.startUtc,\n )\n ) {\n throw new BookingError('invalid', 'Availability windows must not overlap');\n }\n return normalized;\n}\n\nexport interface Reservation {\n startUtc: string;\n endUtc: string;\n exclusive: boolean;\n}\n\nexport function supervisionDeclared(\n candidate: ResolvedBookingWindow,\n windows: readonly DeclaredAvailabilityWindow[],\n): boolean {\n return windows.some(\n (window) =>\n window.supervision &&\n candidate.start === window.start &&\n candidate.end === window.end &&\n candidate.zone === window.zone,\n );\n}\n\n/**\n * Remaining units for the candidate: 0 when it does not fit, 1 for an exclusive\n * candidate that fits, and for a shared candidate the minimum of\n * (capacity - shared) across every half-open segment. Endpoints belonging to\n * ending windows are excluded.\n */\nexport function remainingCapacity(\n candidate: Reservation,\n declared: readonly DeclaredAvailabilityWindow[],\n occupied: readonly Reservation[],\n): number {\n type Delta = { capacity: number; shared: number; exclusive: number };\n const boundaries = new Map<string, Delta>();\n const add = (at: string, key: keyof Delta, delta: number) => {\n const event = boundaries.get(at) ?? {\n capacity: 0,\n shared: 0,\n exclusive: 0,\n };\n event[key] += delta;\n boundaries.set(at, event);\n };\n const sweep = (\n window: Reservation | DeclaredAvailabilityWindow,\n key: keyof Delta,\n units: number,\n ) => {\n if (\n window.startUtc >= candidate.endUtc ||\n window.endUtc <= candidate.startUtc\n )\n return;\n add(\n window.startUtc < candidate.startUtc\n ? candidate.startUtc\n : window.startUtc,\n key,\n units,\n );\n if (window.endUtc < candidate.endUtc) add(window.endUtc, key, -units);\n };\n\n // Seed the first segment even when there is no declared coverage there.\n add(candidate.startUtc, 'capacity', 0);\n for (const window of declared) sweep(window, 'capacity', window.capacity);\n for (const reservation of occupied)\n sweep(reservation, reservation.exclusive ? 'exclusive' : 'shared', 1);\n\n let capacity = 0;\n let shared = 0;\n let exclusive = 0;\n let remaining = Number.POSITIVE_INFINITY;\n for (const at of [...boundaries.keys()].sort()) {\n const delta = boundaries.get(at);\n if (!delta) continue;\n // Apply ends and starts together before checking the segment beginning here.\n capacity += delta.capacity;\n shared += delta.shared;\n exclusive += delta.exclusive;\n if (\n capacity < 1 ||\n exclusive > 0 ||\n (candidate.exclusive ? shared > 0 : shared >= capacity)\n )\n return 0;\n remaining = Math.min(\n remaining,\n candidate.exclusive ? 1 : capacity - shared,\n );\n }\n return Number.isFinite(remaining) ? remaining : 0;\n}\n\nexport function fitsCapacity(\n candidate: Reservation,\n declared: readonly DeclaredAvailabilityWindow[],\n occupied: readonly Reservation[],\n): boolean {\n return remainingCapacity(candidate, declared, occupied) >= 1;\n}\n\n/** Most segments one proposal may reserve; discovery never lists a larger bundle. */\nexport const MAX_SEGMENTS = 400;\n\n/** Resolve one or more same-zone segments into sorted, non-overlapping windows and their span. */\nexport function resolveCoverage(windows: readonly BookingWindow[]): {\n windows: ResolvedBookingWindow[];\n span: ResolvedBookingWindow;\n} {\n if (\n !Array.isArray(windows) ||\n windows.length < 1 ||\n windows.length > MAX_SEGMENTS\n ) {\n throw new BookingError(\n 'invalid',\n `Expected between 1 and ${MAX_SEGMENTS} booking windows`,\n );\n }\n const resolved = windows\n .map((window) => resolveBookingWindow(window))\n .sort((a, b) => a.startUtc.localeCompare(b.startUtc));\n for (const [i, window] of resolved.entries()) {\n if (window.zone !== resolved[0].zone) {\n throw new BookingError('invalid', 'Booking windows must share one zone');\n }\n if (i > 0 && resolved[i - 1].endUtc > window.startUtc) {\n throw new BookingError('invalid', 'Booking windows must not overlap');\n }\n }\n const first = resolved[0];\n const last = resolved[resolved.length - 1];\n return {\n windows: resolved,\n span: {\n zone: first.zone,\n start: first.start,\n end: last.end,\n startUtc: first.startUtc,\n endUtc: last.endUtc,\n },\n };\n}\n\nfunction pad(value: number, width = 2): string {\n return String(value).padStart(width, '0');\n}\n\n/** Add n local days to YYYY-MM-DD with date-only arithmetic. */\nexport function addLocalDays(date: string, days: number): string {\n const moved = new Date(`${date}T00:00:00Z`);\n moved.setUTCDate(moved.getUTCDate() + days);\n return moved.toISOString().slice(0, 10);\n}\n\n/** Add n calendar months, constraining the day to the target month's length. */\nexport function addLocalMonths(date: string, months: number): string {\n const [year, month, day] = date.split('-').map(Number);\n const index = year * 12 + (month - 1) + months;\n const targetYear = Math.floor(index / 12);\n const targetMonth = index % 12;\n const length = new Date(\n Date.UTC(targetYear, targetMonth + 1, 0),\n ).getUTCDate();\n return `${pad(targetYear, 4)}-${pad(targetMonth + 1)}-${pad(Math.min(day, length))}`;\n}\n\n/** Exclusive local end date of a quantity of the unit starting on a local date. */\nexport function localEnd(\n unit: BookingUnit,\n quantity: number,\n start: string,\n): string {\n if (unit === 'day') return addLocalDays(start, quantity);\n if (unit === 'week') return addLocalDays(start, 7 * quantity);\n return addLocalMonths(start, quantity);\n}\n\nfunction within(window: ResolvedBookingWindow, from: string, to: string) {\n return (\n window.start.slice(0, 10) >= from &&\n (window.end.slice(0, 10) < to || window.end.slice(0, 16) === `${to}T00:00`)\n );\n}\n\n/** True iff the segments measure exactly `quantity` of `unit`. */\nexport function measures(\n unit: BookingUnit,\n quantity: number,\n windows: readonly ResolvedBookingWindow[],\n): boolean {\n if (!Number.isSafeInteger(quantity) || quantity < 1 || windows.length < 1) {\n return false;\n }\n if (unit === 'hour') {\n const total = windows.reduce(\n (sum, w) => sum + (Date.parse(w.endUtc) - Date.parse(w.startUtc)),\n 0,\n );\n return total === quantity * 60 * MINUTE_MS;\n }\n if (unit !== 'day' && unit !== 'week' && unit !== 'month') return false;\n const from = windows[0].start.slice(0, 10);\n const to = localEnd(unit, quantity, from);\n if (!windows.every((window) => within(window, from, to))) return false;\n // A day offering is sold per local calendar day: the segments must touch\n // exactly `quantity` distinct days, so one day of coverage cannot bill for two.\n if (unit === 'day') {\n const days = new Set<string>();\n for (const window of windows) {\n const last =\n window.end.slice(0, 16) === `${window.end.slice(0, 10)}T00:00` &&\n window.end.slice(0, 10) > window.start.slice(0, 10)\n ? addLocalDays(window.end.slice(0, 10), -1)\n : window.end.slice(0, 10);\n for (\n let day = window.start.slice(0, 10);\n day <= last;\n day = addLocalDays(day, 1)\n ) {\n days.add(day);\n }\n }\n return days.size === quantity;\n }\n return true;\n}\n\n/** Do these segments fall in the half-open local span of a quantity of unit from a date? */\nexport function withinLocalSpan(\n window: ResolvedBookingWindow,\n unit: Exclude<BookingUnit, 'hour'>,\n quantity: number,\n from: string,\n): boolean {\n return within(window, from, localEnd(unit, quantity, from));\n}\n\n/** Render a UTC instant as a minute-precision local timestamp with its zone offset. */\nexport function formatWall(instantIso: string, zone: string): string {\n const time = Date.parse(instantIso);\n if (!Number.isFinite(time)) {\n throw new BookingError('invalid', 'Invalid instant');\n }\n const parts = Object.fromEntries(\n formatter(zone)\n .formatToParts(time)\n .map(({ type, value }) => [type, value]),\n );\n const local = Date.UTC(\n Number(parts.year),\n Number(parts.month) - 1,\n Number(parts.day),\n Number(parts.hour),\n Number(parts.minute),\n Number(parts.second),\n );\n const offset = Math.round((local - time) / MINUTE_MS);\n const sign = offset < 0 ? '-' : '+';\n const abs = Math.abs(offset);\n return `${pad(Number(parts.year), 4)}-${parts.month}-${parts.day}T${parts.hour}:${parts.minute}${sign}${pad(Math.floor(abs / 60))}:${pad(abs % 60)}`;\n}\n\n/** Build a resolved window from two UTC instants, rendered in the zone. */\nexport function windowFromInstants(\n zone: string,\n startMs: number,\n endMs: number,\n): ResolvedBookingWindow {\n return resolveBookingWindow({\n zone,\n start: formatWall(new Date(startMs).toISOString(), zone),\n end: formatWall(new Date(endMs).toISOString(), zone),\n });\n}\n","import { createHash, randomUUID } from 'node:crypto';\nimport type { DatabaseInterface } from '@happyvertical/sql';\nimport { BookingError } from './errors.js';\nimport type {\n AvailabilityInput,\n BookingAccess,\n BookingAuthority,\n BookingContext,\n BookingHistoryEvent,\n BookingKind,\n BookingOffering,\n BookingOperation,\n BookingPage,\n BookingPageOptions,\n BookingProposal,\n BookingReceipt,\n BookingReferences,\n BookingStatus,\n BookingUnit,\n BookingView,\n BookingWindow,\n ConfigureOfferingInput,\n DeclaredAvailabilityWindow,\n ListBookingsOptions,\n ListOpenSlotsOptions,\n OpenSlot,\n ProposeBookingInput,\n ProviderAvailability,\n RequestBookingInput,\n ResolvedBookingWindow,\n} from './types.js';\nimport {\n declareWindows,\n fitsCapacity,\n localEnd,\n MAX_SEGMENTS,\n measures,\n type Reservation,\n remainingCapacity,\n resolveCoverage,\n supervisionDeclared,\n windowFromInstants,\n} from './windows.js';\n\ntype WriteOperation = Exclude<BookingOperation, 'read' | 'history'>;\n\ninterface BookingRow {\n id: string;\n offering_id: string;\n contact_id: string;\n project_id: string | null;\n kind: BookingKind;\n unit: BookingUnit;\n quantity: number;\n status: BookingStatus;\n proposal_id: string | null;\n assented_proposal_id: string | null;\n confirmed_proposal_id: string | null;\n}\n\ninterface ProposalRow {\n provider_id: string;\n coverage: string;\n windows: string;\n proposal_unit: BookingUnit;\n proposal_quantity: number;\n unit_price_cents: string | number | null;\n currency: string | null;\n total_cents: string | number | null;\n terms: string;\n approved_plan: string | null;\n}\n\ninterface Allocation extends Reservation {\n window: ResolvedBookingWindow;\n providerId: string;\n}\n\nconst CURRENCY = /^[A-Z]{3}$/;\nconst UNITS: readonly BookingUnit[] = ['hour', 'day', 'week', 'month'];\nconst MAX_QUANTITY = 1000;\nconst MAX_SLOT_RANGE_MS = 400 * 86_400_000;\nconst HOUR_MS = 3_600_000;\nconst SLOT_CURSOR =\n /^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d{3}Z\\|[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n\nfunction project(window: ResolvedBookingWindow): ResolvedBookingWindow {\n const { zone, start, end, startUtc, endUtc } = window;\n return { zone, start, end, startUtc, endUtc };\n}\n\nconst UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n\nfunction id(value: string): string {\n if (typeof value !== 'string' || !UUID.test(value)) {\n throw new BookingError('invalid', 'Expected a UUID reference');\n }\n return value.toLowerCase();\n}\n\nfunction quantityOf(value: number | undefined, fallback = 1): number {\n const quantity = value === undefined ? fallback : value;\n if (\n !Number.isSafeInteger(quantity) ||\n quantity < 1 ||\n quantity > MAX_QUANTITY\n ) {\n throw new BookingError(\n 'invalid',\n `Quantity must be an integer between 1 and ${MAX_QUANTITY}`,\n );\n }\n return quantity;\n}\n\n/** Convert a bigint money column to a JavaScript-safe integer. */\nfunction money(value: string | number | bigint | null): number | null {\n if (value === null) return null;\n const amount = Number(value);\n if (!Number.isSafeInteger(amount)) {\n throw new BookingError('invalid', 'Amount is not a safe integer');\n }\n return amount;\n}\n\nfunction pageBounds({ afterId, limit = 50 }: BookingPageOptions) {\n if (!Number.isInteger(limit) || limit < 1 || limit > 100) {\n throw new BookingError('invalid', 'Page limit must be between 1 and 100');\n }\n return { after: afterId === undefined ? null : id(afterId), limit };\n}\n\nfunction page<T>(\n rows: T[],\n limit: number,\n cursor: (item: T) => string,\n): BookingPage<T> {\n const items = rows.slice(0, limit);\n return {\n items,\n nextCursor: rows.length > limit ? cursor(rows[limit - 1]) : null,\n };\n}\n\nfunction bookingView(\n row: BookingRow,\n proposal: BookingProposal | null,\n): BookingView {\n return {\n id: row.id,\n offeringId: row.offering_id,\n contactId: row.contact_id,\n projectId: row.project_id,\n kind: row.kind,\n unit: row.unit,\n quantity: row.quantity,\n status: row.status,\n proposal,\n assentedProposalId: row.assented_proposal_id,\n confirmedProposalId: row.confirmed_proposal_id,\n };\n}\n\nfunction proposalView(proposalId: string, row: ProposalRow): BookingProposal {\n return {\n id: proposalId,\n providerId: row.provider_id,\n window: JSON.parse(row.coverage),\n windows: JSON.parse(row.windows),\n unit: row.proposal_unit,\n quantity: row.proposal_quantity,\n unitPriceCents: money(row.unit_price_cents),\n currency: row.currency,\n totalCents: money(row.total_cents),\n terms: JSON.parse(row.terms),\n approvedPlan:\n row.approved_plan === null ? null : JSON.parse(row.approved_plan),\n };\n}\n\nfunction required(value: string, max = 200): string {\n if (\n typeof value !== 'string' ||\n value.trim().length === 0 ||\n value.length > max ||\n value.includes('\\0')\n ) {\n throw new BookingError(\n 'invalid',\n 'Expected a nonempty text value within the length limit',\n );\n }\n return value;\n}\n\nfunction identity(input: BookingContext): Readonly<BookingContext> {\n return Object.freeze({\n tenantId: id(input.tenantId),\n actorId: required(input.actorId),\n });\n}\n\nfunction digest(value: string): string {\n return createHash('sha256').update(value).digest('hex');\n}\n\nfunction operator(access: BookingAccess): void {\n if (!access.operator)\n throw new BookingError('forbidden', 'Operator access required');\n}\n\nfunction contact(access: BookingAccess, contactId: string): void {\n if (access.contactId !== contactId) {\n throw new BookingError(\n 'forbidden',\n 'Only the named customer contact may act for this booking',\n );\n }\n}\n\nfunction visible(access: BookingAccess, row: BookingRow): void {\n if (!access.operator && access.contactId !== row.contact_id) {\n throw new BookingError('not-found', 'Booking does not exist');\n }\n}\n\nfunction pending(row: BookingRow): void {\n if (row.status !== 'requested' && row.status !== 'proposed') {\n throw new BookingError('conflict', 'Booking is no longer pending');\n }\n}\n\nfunction declared(\n kind: BookingKind,\n window: ResolvedBookingWindow,\n availability: readonly DeclaredAvailabilityWindow[],\n): void {\n if (\n (kind === 'supervision-day' &&\n !supervisionDeclared(window, availability)) ||\n (kind === 'developer-hour' &&\n !availability.some((entry) => entry.zone === window.zone)) ||\n !fitsCapacity(\n { ...window, exclusive: kind === 'developer-hour' },\n availability,\n [],\n )\n ) {\n throw new BookingError(\n 'unavailable',\n 'The proposed window is outside declared availability',\n );\n }\n}\n\n/** Transactional PostgreSQL service. The application supplies trusted identity and authorization. */\nexport class BookingService {\n readonly #db: DatabaseInterface;\n readonly #authority: BookingAuthority;\n\n constructor(db: DatabaseInterface, authority: BookingAuthority) {\n this.#db = db;\n this.#authority = authority;\n }\n\n async configureOffering(\n context: BookingContext,\n key: string,\n input: ConfigureOfferingInput,\n ): Promise<BookingReceipt> {\n const offeringId = id(input.offeringId);\n const { kind, enabled } = input;\n if (\n (kind !== 'developer-hour' && kind !== 'supervision-day') ||\n typeof enabled !== 'boolean'\n ) {\n throw new BookingError(\n 'invalid',\n 'Invalid offering kind or enabled flag',\n );\n }\n const unit =\n input.unit ?? (kind === 'supervision-day' ? 'day' : ('hour' as const));\n if (\n !UNITS.includes(unit) ||\n (kind === 'supervision-day' && unit !== 'day')\n ) {\n throw new BookingError(\n 'invalid',\n 'Invalid offering unit; supervision is sold by the day',\n );\n }\n const priceCents = input.priceCents ?? null;\n const currency = input.currency ?? null;\n if (\n (priceCents !== null &&\n (!Number.isSafeInteger(priceCents) || priceCents < 0)) ||\n (currency !== null &&\n (typeof currency !== 'string' || !CURRENCY.test(currency))) ||\n (priceCents === null) !== (currency === null)\n ) {\n throw new BookingError(\n 'invalid',\n 'A price needs a nonnegative integer amount in minor units and an uppercase ISO 4217 currency',\n );\n }\n const title = input.title ?? '';\n const description = input.description ?? '';\n const isPublic = input.public ?? false;\n if (\n typeof title !== 'string' ||\n typeof description !== 'string' ||\n title.length > 120 ||\n description.length > 2000 ||\n title.includes('\\0') ||\n description.includes('\\0') ||\n typeof isPublic !== 'boolean'\n ) {\n throw new BookingError(\n 'invalid',\n 'Offering title (120), description (2000) and public flag are invalid',\n );\n }\n return this.#write(\n context,\n 'configure-offering',\n key,\n // Fields at their defaults are omitted so a v1 retry key still replays its receipt.\n {\n offeringId,\n kind,\n enabled,\n ...(unit !== (kind === 'supervision-day' ? 'day' : 'hour')\n ? { unit }\n : {}),\n ...(priceCents !== null ? { priceCents, currency } : {}),\n ...(title !== '' ? { title } : {}),\n ...(description !== '' ? { description } : {}),\n ...(isPublic ? { public: true } : {}),\n },\n operator,\n async (tx, ctx) => {\n const saved = await tx.query(\n `INSERT INTO smrt_booking_offerings (tenant_id, id, kind, unit, price_cents, currency, title, description, public, enabled)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n ON CONFLICT (tenant_id, id) DO UPDATE\n SET enabled = EXCLUDED.enabled, price_cents = EXCLUDED.price_cents, currency = EXCLUDED.currency,\n title = EXCLUDED.title, description = EXCLUDED.description, public = EXCLUDED.public\n WHERE smrt_booking_offerings.kind = EXCLUDED.kind AND smrt_booking_offerings.unit = EXCLUDED.unit\n RETURNING id`,\n ctx.tenantId,\n offeringId,\n kind,\n unit,\n priceCents,\n currency,\n title,\n description,\n isPublic,\n enabled,\n );\n if (!saved.rows.length)\n throw new BookingError(\n 'conflict',\n 'An offering kind or unit cannot change',\n );\n return { id: offeringId };\n },\n );\n }\n\n async declareAvailability(\n context: BookingContext,\n key: string,\n input: AvailabilityInput,\n ): Promise<BookingReceipt> {\n const providerId = id(input.providerId);\n const windows = declareWindows(input.zone, input.windows);\n return this.#write(\n context,\n 'declare-availability',\n key,\n { providerId, windows },\n operator,\n async (tx, ctx) => {\n await tx.query(\n `INSERT INTO smrt_booking_resources (tenant_id, id, windows) VALUES (?, ?, '[]')\n ON CONFLICT (tenant_id, id) DO NOTHING`,\n ctx.tenantId,\n providerId,\n );\n await this.#resource(tx, ctx, providerId, true);\n operator(await this.#access(ctx, 'declare-availability'));\n await this.#verify(ctx, { providerId });\n const allocated = await this.#allocations(tx, ctx, providerId);\n if (\n allocated.some(\n (item, i) =>\n (!item.exclusive && !supervisionDeclared(item.window, windows)) ||\n !fitsCapacity(\n item,\n windows,\n allocated.filter((_, j) => j !== i),\n ),\n )\n ) {\n throw new BookingError(\n 'unavailable',\n 'Availability change would undercut confirmed reservations',\n );\n }\n await tx.query(\n 'UPDATE smrt_booking_resources SET windows = ? WHERE tenant_id = ? AND id = ?',\n JSON.stringify(windows),\n ctx.tenantId,\n providerId,\n );\n return { id: providerId };\n },\n );\n }\n\n async request(\n context: BookingContext,\n key: string,\n input: RequestBookingInput,\n ): Promise<BookingReceipt> {\n const offeringId = id(input.offeringId);\n const contactId = id(input.contactId);\n const projectId =\n input.projectId === undefined ? null : id(input.projectId);\n const quantity = quantityOf(input.quantity);\n return this.#write(\n context,\n 'request',\n key,\n {\n offeringId,\n contactId,\n projectId,\n ...(quantity !== 1 ? { quantity } : {}),\n },\n (access) => {\n if (!access.operator) contact(access, contactId);\n },\n async (tx, ctx) => {\n const { rows } = await tx.query(\n 'SELECT kind, unit, enabled FROM smrt_booking_offerings WHERE tenant_id = ? AND id = ? FOR SHARE',\n ctx.tenantId,\n offeringId,\n );\n if (!rows[0]?.enabled)\n throw new BookingError('not-found', 'Offering is not available');\n const kind = rows[0].kind as BookingKind;\n const unit = rows[0].unit as BookingUnit;\n if (kind === 'supervision-day' && (!projectId || quantity !== 1)) {\n throw new BookingError(\n 'invalid',\n 'A supervision request requires a project and a quantity of one',\n );\n }\n await this.#verify(ctx, {\n contactId,\n projectId: projectId ?? undefined,\n });\n const bookingId = randomUUID();\n await tx.query(\n `INSERT INTO smrt_bookings (tenant_id, id, offering_id, contact_id, project_id, kind, unit, quantity, status)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, 'requested')`,\n ctx.tenantId,\n bookingId,\n offeringId,\n contactId,\n projectId,\n kind,\n unit,\n quantity,\n );\n return { id: bookingId };\n },\n { createsBooking: true },\n );\n }\n\n async propose(\n context: BookingContext,\n key: string,\n input: ProposeBookingInput,\n ): Promise<BookingReceipt> {\n const bookingId = id(input.bookingId);\n const providerId = id(input.providerId);\n if ((input.window === undefined) === (input.windows === undefined)) {\n throw new BookingError(\n 'invalid',\n 'Provide exactly one of window or windows',\n );\n }\n const { windows, span } = resolveCoverage(\n input.windows ?? [input.window as BookingWindow],\n );\n const requestedQuantity =\n input.quantity === undefined ? null : quantityOf(input.quantity);\n const terms = {\n revision: required(input.terms.revision),\n text: required(input.terms.text, 65_536),\n };\n const plan = input.approvedPlan;\n const approvedPlan =\n plan === undefined\n ? null\n : { id: id(plan.id), version: required(plan.version) };\n return this.#write(\n context,\n 'propose',\n key,\n // The v1 single-window shape (and hash) is kept for a single segment at the default quantity.\n {\n bookingId,\n providerId,\n ...(windows.length === 1 ? { window: windows[0] } : { windows }),\n ...(requestedQuantity !== null ? { quantity: requestedQuantity } : {}),\n terms,\n approvedPlan,\n },\n operator,\n async (tx, ctx) => {\n const row = await this.#booking(tx, ctx, bookingId, true);\n pending(row);\n const quantity = requestedQuantity ?? row.quantity;\n if (row.kind === 'supervision-day') {\n if (!approvedPlan || quantity !== 1 || windows.length !== 1) {\n throw new BookingError(\n 'invalid',\n 'Supervision requires a plan version, one window and a quantity of one',\n );\n }\n } else {\n if (approvedPlan) {\n throw new BookingError(\n 'invalid',\n 'Dedicated time does not take an approved plan',\n );\n }\n if (!measures(row.unit, quantity, windows)) {\n throw new BookingError(\n 'invalid',\n 'Proposed windows do not measure the requested quantity',\n );\n }\n }\n await this.#verify(ctx, {\n ...this.#refs(row),\n providerId,\n approvedPlan: approvedPlan ?? undefined,\n });\n const availability = await this.#resource(tx, ctx, providerId, false);\n for (const window of windows) declared(row.kind, window, availability);\n const offering = await tx.query(\n `SELECT price_cents, currency FROM smrt_booking_offerings\n WHERE tenant_id = ? AND id = ? FOR SHARE`,\n ctx.tenantId,\n row.offering_id,\n );\n const unitPriceCents = money(offering.rows[0]?.price_cents ?? null);\n const currency: string | null = offering.rows[0]?.currency ?? null;\n const totalCents =\n unitPriceCents === null ? null : unitPriceCents * quantity;\n if (totalCents !== null && !Number.isSafeInteger(totalCents)) {\n throw new BookingError('invalid', 'Total price is too large');\n }\n const proposalId = randomUUID();\n await tx.query(\n `INSERT INTO smrt_booking_proposals\n (tenant_id, booking_id, id, provider_id, coverage, windows, unit, quantity,\n unit_price_cents, currency, total_cents, terms, approved_plan)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n ctx.tenantId,\n bookingId,\n proposalId,\n providerId,\n JSON.stringify(span),\n JSON.stringify(windows),\n row.unit,\n quantity,\n unitPriceCents,\n currency,\n totalCents,\n JSON.stringify(terms),\n approvedPlan && JSON.stringify(approvedPlan),\n );\n await tx.query(\n `UPDATE smrt_bookings SET status = 'proposed', proposal_id = ?, assented_proposal_id = NULL\n WHERE tenant_id = ? AND id = ?`,\n proposalId,\n ctx.tenantId,\n bookingId,\n );\n return { id: bookingId, proposalId };\n },\n { bookingId },\n );\n }\n\n async assent(\n context: BookingContext,\n key: string,\n input: { bookingId: string; proposalId: string },\n ): Promise<BookingReceipt> {\n const bookingId = id(input.bookingId);\n const proposalId = id(input.proposalId);\n return this.#write(\n context,\n 'assent',\n key,\n { bookingId, proposalId },\n async (access, tx, ctx) => {\n // Check contact ownership before exposing the existence of a replay.\n const row = await this.#booking(tx, ctx, bookingId, false);\n visible(access, row);\n contact(access, row.contact_id);\n },\n async (tx, ctx) => {\n const row = await this.#booking(tx, ctx, bookingId, true);\n contact(await this.#access(ctx, 'assent'), row.contact_id);\n this.#current(row, proposalId);\n if (row.assented_proposal_id)\n throw new BookingError(\n 'conflict',\n 'Customer assent is already recorded',\n );\n await this.#verify(ctx, this.#refs(row));\n await tx.query(\n 'UPDATE smrt_bookings SET assented_proposal_id = ? WHERE tenant_id = ? AND id = ?',\n proposalId,\n ctx.tenantId,\n bookingId,\n );\n return { id: bookingId, proposalId };\n },\n { bookingId },\n );\n }\n\n async confirm(\n context: BookingContext,\n key: string,\n input: { bookingId: string; proposalId: string },\n ): Promise<BookingReceipt> {\n const bookingId = id(input.bookingId);\n const proposalId = id(input.proposalId);\n return this.#write(\n context,\n 'confirm',\n key,\n { bookingId, proposalId },\n operator,\n async (tx, ctx) => {\n const row = await this.#booking(tx, ctx, bookingId, true);\n this.#current(row, proposalId);\n if (row.assented_proposal_id !== proposalId) {\n throw new BookingError(\n 'conflict',\n 'Exact proposal does not have customer assent',\n );\n }\n const proposal = await this.#proposal(tx, ctx, row, proposalId);\n const availability = await this.#resource(\n tx,\n ctx,\n proposal.providerId,\n true,\n );\n // Refresh after waiting for contested capacity; this does not lock external authorities.\n operator(await this.#access(ctx, 'confirm'));\n await this.#verify(ctx, {\n ...this.#refs(row),\n providerId: proposal.providerId,\n approvedPlan: proposal.approvedPlan ?? undefined,\n });\n const exclusive = row.kind === 'developer-hour';\n const window = proposal.window;\n const occupied = await this.#allocations(\n tx,\n ctx,\n proposal.providerId,\n window,\n );\n for (const segment of proposal.windows) {\n declared(row.kind, segment, availability);\n if (\n !fitsCapacity({ ...segment, exclusive }, availability, occupied)\n ) {\n throw new BookingError(\n 'unavailable',\n 'The proposed window has insufficient capacity',\n );\n }\n }\n const inserted = await tx.query(\n `INSERT INTO smrt_booking_allocations\n (tenant_id, booking_id, proposal_id, provider_id, project_id, exclusive, start_at, end_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?)\n ON CONFLICT (tenant_id, project_id, start_at, end_at)\n WHERE released_at IS NULL AND project_id IS NOT NULL DO NOTHING RETURNING booking_id`,\n ctx.tenantId,\n bookingId,\n proposalId,\n proposal.providerId,\n exclusive ? null : row.project_id,\n exclusive,\n window.startUtc,\n window.endUtc,\n );\n if (!inserted.rows.length) {\n throw new BookingError(\n 'duplicate-project-window',\n 'Project already has a reservation for this window',\n );\n }\n await tx.query(\n `UPDATE smrt_bookings SET status = 'confirmed', confirmed_proposal_id = ?\n WHERE tenant_id = ? AND id = ?`,\n proposalId,\n ctx.tenantId,\n bookingId,\n );\n return { id: bookingId, proposalId };\n },\n { bookingId },\n );\n }\n\n /** Operator command: bind a lead-bound booking to a project once one exists. */\n async attachProject(\n context: BookingContext,\n key: string,\n input: { bookingId: string; projectId: string },\n ): Promise<BookingReceipt> {\n const bookingId = id(input.bookingId);\n const projectId = id(input.projectId);\n return this.#write(\n context,\n 'attach-project',\n key,\n { bookingId, projectId },\n operator,\n async (tx, ctx) => {\n const row = await this.#booking(tx, ctx, bookingId, true);\n if (row.status === 'declined' || row.status === 'cancelled') {\n throw new BookingError('conflict', 'Booking is no longer active');\n }\n if (row.project_id === projectId) return { id: bookingId };\n if (row.project_id !== null) {\n throw new BookingError(\n 'conflict',\n 'Booking is already bound to a different project',\n );\n }\n await this.#verify(ctx, { contactId: row.contact_id, projectId });\n await tx.query(\n 'UPDATE smrt_bookings SET project_id = ? WHERE tenant_id = ? AND id = ?',\n projectId,\n ctx.tenantId,\n bookingId,\n );\n return { id: bookingId };\n },\n { bookingId },\n );\n }\n\n decline(\n context: BookingContext,\n key: string,\n input: { bookingId: string; reason: string },\n ): Promise<BookingReceipt> {\n return this.#close(context, 'decline', key, input);\n }\n\n cancel(\n context: BookingContext,\n key: string,\n input: { bookingId: string; reason: string },\n ): Promise<BookingReceipt> {\n return this.#close(context, 'cancel', key, input);\n }\n\n async get(context: BookingContext, bookingId: string): Promise<BookingView> {\n const ctx = identity(context);\n const access = await this.#access(ctx, 'read');\n const row = await this.#booking(this.#db, ctx, id(bookingId), false);\n visible(access, row);\n return bookingView(\n row,\n row.proposal_id\n ? await this.#proposal(this.#db, ctx, row, row.proposal_id)\n : null,\n );\n }\n\n /** Lists the tenant's bookings, restricted to the current contact for nonoperators. */\n async listBookings(\n context: BookingContext,\n options: ListBookingsOptions = {},\n ): Promise<BookingPage<BookingView>> {\n const ctx = identity(context);\n const access = await this.#discover(ctx);\n const { after, limit } = pageBounds(options);\n const projectId =\n options.projectId === undefined ? null : id(options.projectId);\n const contactId =\n options.contactId === undefined ? null : id(options.contactId);\n if (!access.operator && contactId && contactId !== access.contactId) {\n throw new BookingError(\n 'forbidden',\n 'Only the named customer contact may be listed',\n );\n }\n const result = await this.#db.query(\n `SELECT b.id, b.offering_id, b.contact_id, b.project_id, b.kind, b.unit, b.quantity, b.status,\n b.proposal_id, b.assented_proposal_id, b.confirmed_proposal_id,\n p.provider_id, p.coverage::text, p.windows::text, p.unit AS proposal_unit,\n p.quantity AS proposal_quantity, p.unit_price_cents, p.currency, p.total_cents,\n p.terms::text, p.approved_plan::text\n FROM smrt_bookings b\n LEFT JOIN smrt_booking_proposals p\n ON p.tenant_id = b.tenant_id AND p.booking_id = b.id AND p.id = b.proposal_id\n WHERE b.tenant_id = ? AND (?::boolean OR b.contact_id = ?)\n AND (?::uuid IS NULL OR b.project_id = ?)\n AND (?::uuid IS NULL OR b.contact_id = ?)\n AND (?::uuid IS NULL OR b.id > ?)\n ORDER BY b.id LIMIT ?`,\n ctx.tenantId,\n access.operator,\n access.contactId ?? null,\n projectId,\n projectId,\n contactId,\n contactId,\n after,\n after,\n limit + 1,\n );\n const rows = result.rows as Array<BookingRow & ProposalRow>;\n const selected = page(rows, limit, (row) => row.id);\n return {\n items: selected.items.map((row) =>\n bookingView(\n row,\n row.proposal_id ? proposalView(row.proposal_id, row) : null,\n ),\n ),\n nextCursor: selected.nextCursor,\n };\n }\n\n /**\n * Lists configured offerings. Operators see all, contacts see enabled\n * offerings, and a reader with no contact mapping sees enabled public ones.\n */\n async listOfferings(\n context: BookingContext,\n options: BookingPageOptions = {},\n ): Promise<BookingPage<BookingOffering>> {\n const ctx = identity(context);\n const access = await this.#access(ctx, 'read');\n const { after, limit } = pageBounds(options);\n const { rows } = await this.#db.query(\n `SELECT id, kind, unit, price_cents, currency, title, description, public, enabled\n FROM smrt_booking_offerings\n WHERE tenant_id = ? AND (?::boolean OR (enabled AND (?::boolean OR public)))\n AND (?::uuid IS NULL OR id > ?)\n ORDER BY id LIMIT ?`,\n ctx.tenantId,\n access.operator,\n access.operator || access.contactId !== undefined,\n after,\n after,\n limit + 1,\n );\n const offerings: BookingOffering[] = rows.map((row) => ({\n id: row.id,\n kind: row.kind,\n unit: row.unit,\n priceCents: money(row.price_cents),\n currency: row.currency,\n title: row.title,\n description: row.description,\n public: row.public,\n enabled: row.enabled,\n }));\n return page(offerings, limit, (row) => row.id);\n }\n\n /**\n * Lists candidate open slots for an offering: a pure read that reserves nothing\n * and takes no retry key. Confirmation re-checks capacity under the provider lock.\n * Offerings are discoverable by operators (all), contact-mapped readers (enabled)\n * and contact-less readers (enabled and public); the host decides who may read.\n */\n async listOpenSlots(\n context: BookingContext,\n options: ListOpenSlotsOptions,\n ): Promise<BookingPage<OpenSlot>> {\n const ctx = identity(context);\n const access = await this.#access(ctx, 'read');\n const offeringId = id(options.offeringId);\n const fromMs = Date.parse(options.from);\n const toMs = Date.parse(options.to);\n if (\n !Number.isFinite(fromMs) ||\n !Number.isFinite(toMs) ||\n toMs <= fromMs ||\n toMs - fromMs > MAX_SLOT_RANGE_MS\n ) {\n throw new BookingError(\n 'invalid',\n 'Expected from before to, at most 400 days apart',\n );\n }\n const from = new Date(fromMs).toISOString();\n const to = new Date(toMs).toISOString();\n const quantity = quantityOf(options.quantity);\n const providerId =\n options.providerId === undefined ? null : id(options.providerId);\n const { limit } = pageBounds({ limit: options.limit });\n if (options.after !== undefined && !SLOT_CURSOR.test(options.after)) {\n throw new BookingError('invalid', 'Invalid slot cursor');\n }\n const after = options.after?.replace(/\\|.*$/, (provider) =>\n provider.toLowerCase(),\n );\n const found = await this.#db.query(\n 'SELECT kind, unit, enabled, public FROM smrt_booking_offerings WHERE tenant_id = ? AND id = ?',\n ctx.tenantId,\n offeringId,\n );\n const offering = found.rows[0];\n const discoverable =\n offering &&\n (access.operator ||\n (offering.enabled &&\n (access.contactId !== undefined || offering.public)));\n if (!discoverable) {\n throw new BookingError('not-found', 'Offering is not available');\n }\n const kind = offering.kind as BookingKind;\n const unit = offering.unit as BookingUnit;\n if (kind === 'supervision-day' && quantity !== 1) {\n throw new BookingError(\n 'invalid',\n 'Supervision slots have a quantity of one',\n );\n }\n const resources = await this.#db.query(\n `SELECT id, windows::text FROM smrt_booking_resources\n WHERE tenant_id = ? AND (?::uuid IS NULL OR id = ?)`,\n ctx.tenantId,\n providerId,\n providerId,\n );\n const occupied = await this.#allocations(this.#db, ctx, providerId, {\n startUtc: from,\n endUtc: to,\n });\n const slots: OpenSlot[] = [];\n for (const resource of resources.rows) {\n const declaredWindows = (\n JSON.parse(resource.windows) as DeclaredAvailabilityWindow[]\n ).sort((a, b) => a.startUtc.localeCompare(b.startUtc));\n const taken = occupied.filter((item) => item.providerId === resource.id);\n const inRange = (w: ResolvedBookingWindow) =>\n w.startUtc >= from && w.endUtc <= to;\n const push = (windows: ResolvedBookingWindow[], remaining: number) => {\n if (remaining >= 1) {\n slots.push({\n providerId: resource.id,\n offeringId,\n unit,\n quantity,\n windows,\n remainingCapacity: remaining,\n });\n }\n };\n if (kind === 'supervision-day') {\n for (const w of declaredWindows) {\n if (w.supervision && inRange(w)) {\n push(\n [project(w)],\n remainingCapacity(\n { ...w, exclusive: false },\n declaredWindows,\n taken,\n ),\n );\n }\n }\n } else if (unit === 'hour') {\n const length = quantity * HOUR_MS;\n for (const w of declaredWindows) {\n const start = Date.parse(w.startUtc);\n const end = Date.parse(w.endUtc);\n if (w.endUtc <= from || w.startUtc >= to) continue;\n const first = Math.max(0, Math.ceil((fromMs - start) / HOUR_MS));\n for (let k = first; start + k * HOUR_MS + length <= end; k++) {\n const candidate = windowFromInstants(\n w.zone,\n start + k * HOUR_MS,\n start + k * HOUR_MS + length,\n );\n if (candidate.startUtc >= to) break;\n if (!inRange(candidate)) continue;\n push(\n [candidate],\n remainingCapacity(\n { ...candidate, exclusive: true },\n declaredWindows,\n taken,\n ),\n );\n }\n }\n } else {\n const starts = [\n ...new Set(declaredWindows.map((w) => w.start.slice(0, 10))),\n ];\n for (const d0 of starts) {\n const d1 = localEnd(unit, quantity, d0);\n const bundle = declaredWindows.filter(\n (w) =>\n w.start.slice(0, 10) >= d0 &&\n w.start.slice(0, 10) < d1 &&\n (w.end.slice(0, 10) < d1 || w.end.slice(0, 16) === `${d1}T00:00`),\n );\n const segments = bundle.map(project);\n if (\n segments.length === 0 ||\n segments.length > MAX_SEGMENTS ||\n segments[0].start.slice(0, 10) !== d0 ||\n !segments.every(inRange) ||\n !measures(unit, quantity, segments) ||\n !bundle.every(\n (w) =>\n remainingCapacity(\n { ...w, exclusive: true },\n declaredWindows,\n taken,\n ) >= 1,\n )\n ) {\n continue;\n }\n push(segments, 1);\n }\n }\n }\n const key = (slot: OpenSlot) =>\n `${slot.windows[0].startUtc}|${slot.providerId}`;\n const seen = new Set<string>();\n const ordered = slots\n .sort((a, b) => (key(a) < key(b) ? -1 : key(a) > key(b) ? 1 : 0))\n .filter((slot) => !seen.has(key(slot)) && seen.add(key(slot)))\n .filter((slot) => after === undefined || key(slot) > after);\n return page(ordered.slice(0, limit + 1), limit, key);\n }\n\n /** Lists provider schedules so operators can inspect or replace existing windows. */\n async listAvailability(\n context: BookingContext,\n options: BookingPageOptions = {},\n ): Promise<BookingPage<ProviderAvailability>> {\n const ctx = identity(context);\n operator(await this.#access(ctx, 'read'));\n const { after, limit } = pageBounds(options);\n const { rows } = await this.#db.query(\n `SELECT id, windows::text FROM smrt_booking_resources\n WHERE tenant_id = ? AND (?::uuid IS NULL OR id > ?)\n ORDER BY id LIMIT ?`,\n ctx.tenantId,\n after,\n after,\n limit + 1,\n );\n const selected = page(\n rows as Array<{ id: string; windows: string }>,\n limit,\n (row) => row.id,\n );\n return {\n items: selected.items.map((row) => ({\n providerId: row.id,\n windows: JSON.parse(row.windows),\n })),\n nextCursor: selected.nextCursor,\n };\n }\n\n async getProposal(\n context: BookingContext,\n bookingId: string,\n proposalId: string,\n ): Promise<BookingProposal> {\n const ctx = identity(context);\n const access = await this.#access(ctx, 'read');\n const row = await this.#booking(this.#db, ctx, id(bookingId), false);\n visible(access, row);\n return this.#proposal(this.#db, ctx, row, id(proposalId));\n }\n\n async history(\n context: BookingContext,\n bookingId: string,\n ): Promise<BookingHistoryEvent[]> {\n const ctx = identity(context);\n operator(await this.#access(ctx, 'history'));\n await this.#booking(this.#db, ctx, id(bookingId), false);\n const { rows } = await this.#db.query(\n `SELECT operation, actor_id, result->>'proposalId' AS proposal_id, reason, at\n FROM smrt_booking_commands WHERE tenant_id = ? AND booking_id = ? ORDER BY at, sequence`,\n ctx.tenantId,\n id(bookingId),\n );\n return rows.map((row) => ({\n operation: row.operation as BookingOperation,\n actorId: row.actor_id,\n proposalId: row.proposal_id,\n reason: row.reason,\n at: new Date(row.at).toISOString(),\n }));\n }\n\n async #close(\n context: BookingContext,\n operation: 'decline' | 'cancel',\n key: string,\n input: { bookingId: string; reason: string },\n ): Promise<BookingReceipt> {\n const bookingId = id(input.bookingId);\n const reason = required(input.reason, 2_048);\n return this.#write(\n context,\n operation,\n key,\n { bookingId, reason },\n operator,\n async (tx, ctx) => {\n const row = await this.#booking(tx, ctx, bookingId, true);\n if (\n operation === 'cancel' &&\n row.confirmed_proposal_id &&\n row.status === 'confirmed'\n ) {\n const proposal = await this.#proposal(\n tx,\n ctx,\n row,\n row.confirmed_proposal_id,\n );\n await this.#resource(tx, ctx, proposal.providerId, true);\n operator(await this.#access(ctx, 'cancel'));\n const released = await tx.query(\n `UPDATE smrt_booking_allocations SET released_at = clock_timestamp()\n WHERE tenant_id = ? AND booking_id = ? AND released_at IS NULL RETURNING booking_id`,\n ctx.tenantId,\n bookingId,\n );\n if (!released.rows.length)\n throw new BookingError(\n 'conflict',\n 'Confirmed booking has no active allocation',\n );\n } else {\n pending(row);\n }\n await tx.query(\n 'UPDATE smrt_bookings SET status = ? WHERE tenant_id = ? AND id = ?',\n operation === 'cancel' ? 'cancelled' : 'declined',\n ctx.tenantId,\n bookingId,\n );\n return {\n id: bookingId,\n ...(row.proposal_id ? { proposalId: row.proposal_id } : {}),\n };\n },\n { bookingId, reason },\n );\n }\n\n async #access(\n context: Readonly<BookingContext>,\n operation: BookingOperation,\n ): Promise<BookingAccess> {\n const access = await this.#authority.access(context, operation);\n if (!access) throw new BookingError('forbidden', 'Booking access denied');\n return {\n operator: access.operator === true,\n contactId:\n access.contactId === undefined ? undefined : id(access.contactId),\n };\n }\n\n async #discover(ctx: Readonly<BookingContext>): Promise<BookingAccess> {\n const access = await this.#access(ctx, 'read');\n if (!access.operator && !access.contactId) {\n throw new BookingError(\n 'forbidden',\n 'A named customer contact is required',\n );\n }\n return access;\n }\n\n async #verify(\n ctx: Readonly<BookingContext>,\n refs: BookingReferences,\n ): Promise<void> {\n if (\n refs.projectId === undefined &&\n refs.contactId !== undefined &&\n this.#authority.verifyContact\n ) {\n // A lead-bound booking has no project: the host vouches for the contact alone.\n if ((await this.#authority.verifyContact(ctx, refs.contactId)) !== true) {\n throw new BookingError(\n 'forbidden',\n 'Booking references or approval could not be verified',\n );\n }\n const { contactId: _contact, ...others } = refs;\n const rest = Object.fromEntries(\n Object.entries(others).filter(([, value]) => value !== undefined),\n ) as BookingReferences;\n if (Object.keys(rest).length === 0) return;\n refs = rest;\n }\n // Use a detached copy so a host callback cannot change the version being verified and saved.\n if (\n (await this.#authority.verifyReferences(ctx, structuredClone(refs))) !==\n true\n ) {\n throw new BookingError(\n 'forbidden',\n 'Booking references or approval could not be verified',\n );\n }\n }\n\n #refs(row: BookingRow): BookingReferences {\n return {\n contactId: row.contact_id,\n projectId: row.project_id ?? undefined,\n };\n }\n\n async #booking(\n tx: DatabaseInterface,\n ctx: Readonly<BookingContext>,\n bookingId: string,\n lock: boolean,\n ): Promise<BookingRow> {\n const { rows } = await tx.query(\n `SELECT * FROM smrt_bookings WHERE tenant_id = ? AND id = ?${lock ? ' FOR UPDATE' : ''}`,\n ctx.tenantId,\n bookingId,\n );\n if (!rows[0]) throw new BookingError('not-found', 'Booking does not exist');\n return rows[0] as BookingRow;\n }\n\n #current(row: BookingRow, proposalId: string): void {\n if (row.status !== 'proposed' || row.proposal_id !== proposalId) {\n throw new BookingError(\n 'conflict',\n 'Booking does not have this current proposal',\n );\n }\n }\n\n async #proposal(\n tx: DatabaseInterface,\n ctx: Readonly<BookingContext>,\n booking: BookingRow,\n proposalId: string,\n ): Promise<BookingProposal> {\n const { rows } = await tx.query(\n `SELECT provider_id, coverage::text, windows::text, unit AS proposal_unit, quantity AS proposal_quantity,\n unit_price_cents, currency, total_cents, terms::text, approved_plan::text FROM smrt_booking_proposals\n WHERE tenant_id = ? AND booking_id = ? AND id = ?`,\n ctx.tenantId,\n booking.id,\n proposalId,\n );\n const row = rows[0];\n if (!row) throw new BookingError('not-found', 'Proposal does not exist');\n return proposalView(proposalId, row as ProposalRow);\n }\n\n async #resource(\n tx: DatabaseInterface,\n ctx: Readonly<BookingContext>,\n providerId: string,\n lock: boolean,\n ): Promise<DeclaredAvailabilityWindow[]> {\n const { rows } = await tx.query(\n `SELECT windows::text FROM smrt_booking_resources WHERE tenant_id = ? AND id = ?${lock ? ' FOR UPDATE' : ''}`,\n ctx.tenantId,\n providerId,\n );\n if (!rows[0])\n throw new BookingError(\n 'not-found',\n 'Provider availability is not configured',\n );\n return JSON.parse(rows[0].windows);\n }\n\n async #allocations(\n tx: DatabaseInterface,\n ctx: Readonly<BookingContext>,\n providerId: string | null,\n window?: Pick<ResolvedBookingWindow, 'startUtc' | 'endUtc'>,\n ): Promise<Allocation[]> {\n const { rows } = await tx.query(\n `SELECT a.provider_id, a.exclusive, p.windows::text FROM smrt_booking_allocations a\n JOIN smrt_booking_proposals p ON p.tenant_id = a.tenant_id AND p.booking_id = a.booking_id AND p.id = a.proposal_id\n WHERE a.tenant_id = ? AND (?::uuid IS NULL OR a.provider_id = ?) AND a.released_at IS NULL${window ? ' AND a.start_at < ? AND a.end_at > ?' : ''}`,\n ctx.tenantId,\n providerId,\n providerId,\n ...(window ? [window.endUtc, window.startUtc] : []),\n );\n // One allocation row spans its proposal; its reserved segments are what block capacity.\n return rows.flatMap((row) =>\n (JSON.parse(row.windows) as ResolvedBookingWindow[])\n .filter(\n (segment) =>\n !window ||\n (segment.startUtc < window.endUtc &&\n segment.endUtc > window.startUtc),\n )\n .map((segment) => ({\n ...segment,\n window: segment,\n providerId: row.provider_id as string,\n exclusive: row.exclusive as boolean,\n })),\n );\n }\n\n async #write(\n context: BookingContext,\n operation: WriteOperation,\n key: string,\n canonical: object,\n permit: (\n access: BookingAccess,\n tx: DatabaseInterface,\n ctx: Readonly<BookingContext>,\n ) => void | Promise<void>,\n work: (\n tx: DatabaseInterface,\n ctx: Readonly<BookingContext>,\n ) => Promise<BookingReceipt>,\n options: {\n bookingId?: string;\n reason?: string;\n createsBooking?: boolean;\n } = {},\n ): Promise<BookingReceipt> {\n const ctx = identity(context);\n const keyHash = digest(required(key));\n const payloadHash = digest(JSON.stringify(canonical));\n if (!this.#db.transaction)\n throw new TypeError('Bookings requires a PostgreSQL transaction');\n return this.#db.transaction(async (tx) => {\n await tx.query('SET TRANSACTION ISOLATION LEVEL READ COMMITTED');\n await permit(await this.#access(ctx, operation), tx, ctx);\n const inserted = await tx.query(\n `INSERT INTO smrt_booking_commands (tenant_id, operation, key_hash, actor_id, payload_hash)\n VALUES (?, ?, ?, ?, ?) ON CONFLICT (tenant_id, operation, key_hash) DO NOTHING RETURNING key_hash`,\n ctx.tenantId,\n operation,\n keyHash,\n ctx.actorId,\n payloadHash,\n );\n if (!inserted.rows.length) {\n await permit(await this.#access(ctx, operation), tx, ctx);\n const { rows } = await tx.query(\n `SELECT actor_id, payload_hash, result::text FROM smrt_booking_commands\n WHERE tenant_id = ? AND operation = ? AND key_hash = ?`,\n ctx.tenantId,\n operation,\n keyHash,\n );\n const prior = rows[0];\n if (\n !prior ||\n prior.actor_id !== ctx.actorId ||\n prior.payload_hash !== payloadHash ||\n !prior.result\n ) {\n throw new BookingError(\n 'conflict',\n 'Idempotency key is bound to a different command',\n );\n }\n return JSON.parse(prior.result) as BookingReceipt;\n }\n const result = await work(tx, ctx);\n await tx.query(\n `UPDATE smrt_booking_commands SET booking_id = ?, result = ?, reason = ?, at = clock_timestamp()\n WHERE tenant_id = ? AND operation = ? AND key_hash = ?`,\n options.bookingId ?? (options.createsBooking ? result.id : null),\n JSON.stringify(result),\n options.reason ?? null,\n ctx.tenantId,\n operation,\n keyHash,\n );\n return result;\n });\n }\n}\n"],"mappings":";;AACO,IAAM,eAAN,cAA2B,MAAM;CACtC,YACW,MAOT,SACA;EACA,MAAM,OAAO;EATJ,KAAA,OAAA;EAUT,KAAK,OAAO;CACd;CAXW;AAYb;;;ACbA,IAAM,aAAa;CACjB;;;;;;;CAOA;;;;;;CAMA;;;;;;;;;;;;;;;;;;CAkBA;;;;;;;;;;;;CAYA;;CAEA;;;;;;;;;;;;;;;;CAgBA;;CAEA;;;CAGA;;;;;;;;;;;;;;CAcA;AACF;AAGA,eAAsB,kBAAkB,IAAsC;CAC5E,IAAI,CAAC,GAAG,aACN,MAAM,IAAI,UAAU,4CAA4C;CAClE,MAAM,GAAG,YAAY,OAAO,OAAO;EACjC,KAAA,MAAW,aAAa,YAAY,MAAM,GAAG,MAAM,SAAS;CAC9D,CAAC;AACH;AAEA,IAAM,KAAK;CACT;CACA;CACA;CACA;CACA;CACA;;;;;;CAMA;CACA;CACA;CACA;CACA;CACA;CACA;;CAEA;CACA;CACA;;;;CAIA;CACA;CACA;CACA;CACA;;CAEA;CACA;CACA;CACA;CACA;CACA;;;;;;;AAOF;AAOA,eAAsB,kBAAkB,IAAsC;CAC5E,IAAI,CAAC,GAAG,aACN,MAAM,IAAI,UAAU,4CAA4C;CAClE,MAAM,GAAG,YAAY,OAAO,OAAO;EACjC,KAAA,MAAW,aAAa,IAAI,MAAM,GAAG,MAAM,SAAS;CACtD,CAAC;AACH;;;AC/IA,IAAM,OAAO;AACb,IAAM,YAAY;AAElB,SAAS,UAAU,MAAmC;CAEpD,IAAI,OAAO,SAAS,YAAY,CAAC,6BAA6B,KAAK,IAAI,GACrE,MAAM,IAAI,aAAa,WAAW,+BAA+B;CAEnE,IAAI;EACF,OAAO,IAAI,KAAK,eAAe,SAAS;GACtC,UAAU;GACV,UAAU;GACV,iBAAiB;GACjB,MAAM;GACN,OAAO;GACP,KAAK;GACL,MAAM;GACN,QAAQ;GACR,QAAQ;GACR,WAAW;EACb,CAAC;CACH,QAAQ;EACN,MAAM,IAAI,aAAa,WAAW,wBAAwB;CAC5D;AACF;AAEA,SAAS,QAAQ,OAAe,QAAqC;CACnE,MAAM,QAAQ,OAAO,UAAU,WAAW,KAAK,KAAK,KAAK,IAAI;CAC7D,IAAI,CAAC,SAAS,MAAM,OAAO,UACzB,MAAM,IAAI,aACR,WACA,gDACF;CAEF,MAAM,OAAO,KAAK,MAAM,KAAK;CAC7B,IAAI,CAAC,OAAO,SAAS,IAAI,GACvB,MAAM,IAAI,aAAa,WAAW,yBAAyB;CAE7D,MAAM,QAAQ,OAAO,YACnB,OAAO,cAAc,IAAI,CAAA,CAAE,KAAK,EAAE,MAAM,OAAAA,aAAY,CAAC,MAAMA,MAAK,CAAC,CACnE;CAEA,IAAI,GADa,MAAM,KAAK,SAAS,GAAG,GAAG,EAAC,GAAI,MAAM,MAAK,GAAI,MAAM,IAAG,GAAI,MAAM,KAAI,GAAI,MAAM,aAClF,MAAM,MAAM,MAAM,WAAW,MACzC,MAAM,IAAI,aACR,WACA,sDACF;CAEF,OAAO,IAAI,KAAK,IAAI,CAAA,CAAE,YAAY;AACpC;AAGO,SAAS,qBACd,QACuB;CACvB,MAAM,SAAS,UAAU,OAAO,IAAI;CACpC,MAAM,WAAW,QAAQ,OAAO,OAAO,MAAM;CAC7C,MAAM,SAAS,QAAQ,OAAO,KAAK,MAAM;CACzC,IAAI,YAAY,QACd,MAAM,IAAI,aACR,WACA,8CACF;CAEF,OAAO;EACL,MAAM,OAAO,gBAAgB,CAAA,CAAE;EAC/B,OAAO,OAAO;EACd,KAAK,OAAO;EACZ;EACA;CACF;AACF;AAQA,SAAS,YAAY,QAAwC;CAC3D,MAAM,YAAY,OAAO,MAAM,MAAM,GAAG,EAAE;CAC1C,MAAM,UAAU,OAAO,IAAI,MAAM,GAAG,EAAE;CACtC,IAAI,cAAc,SAAS,OAAO;CAClC,IAAI,CAAC,OAAO,IAAI,MAAM,EAAE,CAAA,CAAE,WAAW,QAAQ,GAAG,OAAO;CACvD,MAAM,2BAAW,IAAI,KAAK,GAAG,UAAS,WAAY;CAClD,SAAS,WAAW,SAAS,WAAW,IAAI,CAAC;CAC7C,OAAO,SAAS,YAAY,CAAA,CAAE,MAAM,GAAG,EAAE,MAAM;AACjD;AAEO,SAAS,eACd,MACA,SAC8B;CAC9B,UAAU,IAAI;CACd,IAAI,CAAC,MAAM,QAAQ,OAAO,GACxB,MAAM,IAAI,aAAa,WAAW,+BAA+B;CACnE,MAAM,uBAAO,IAAI,IAAY;CAC7B,MAAM,aAAa,QAChB,KAAK,EAAE,OAAO,KAAK,UAAU,kBAAkB;EAC9C,IACE,CAAC,OAAO,cAAc,QAAQ,KAC9B,WAAW,KACX,OAAO,gBAAgB,WAEvB,MAAM,IAAI,aACR,WACA,wEACF;EAEF,MAAM,SAAS,qBAAqB;GAAE;GAAO;GAAK;EAAK,CAAC;EACxD,MAAM,MAAM,MAAM,MAAM,GAAG,EAAE;EAC7B,IAAI,aAAa;GACf,IAAI,CAAC,YAAY,MAAM,KAAK,KAAK,IAAI,GAAG,GACtC,MAAM,IAAI,aACR,WACA,sDACF;GAEF,KAAK,IAAI,GAAG;EACd;EACA,OAAO;GAAE,GAAG;GAAQ;GAAU;EAAY;CAC5C,CAAC,CAAA,CACA,MAAM,GAAG,MAAM,EAAE,SAAS,cAAc,EAAE,QAAQ,CAAC;CACtD,IACE,WAAW,MACR,QAAQ,MAAM,IAAI,KAAK,WAAW,IAAI,EAAC,CAAE,SAAS,OAAO,QAC5D,GAEA,MAAM,IAAI,aAAa,WAAW,uCAAuC;CAE3E,OAAO;AACT;AAQO,SAAS,oBACd,WACA,SACS;CACT,OAAO,QAAQ,MACZ,WACC,OAAO,eACP,UAAU,UAAU,OAAO,SAC3B,UAAU,QAAQ,OAAO,OACzB,UAAU,SAAS,OAAO,IAC9B;AACF;AAQO,SAAS,kBACd,WACA,UACA,UACQ;CAER,MAAM,6BAAa,IAAI,IAAmB;CAC1C,MAAM,OAAO,IAAY,KAAkB,UAAkB;EAC3D,MAAM,QAAQ,WAAW,IAAI,EAAE,KAAK;GAClC,UAAU;GACV,QAAQ;GACR,WAAW;EACb;EACA,MAAM,QAAQ;EACd,WAAW,IAAI,IAAI,KAAK;CAC1B;CACA,MAAM,SACJ,QACA,KACA,UACG;EACH,IACE,OAAO,YAAY,UAAU,UAC7B,OAAO,UAAU,UAAU,UAE3B;EACF,IACE,OAAO,WAAW,UAAU,WACxB,UAAU,WACV,OAAO,UACX,KACA,KACF;EACA,IAAI,OAAO,SAAS,UAAU,QAAQ,IAAI,OAAO,QAAQ,KAAK,CAAC,KAAK;CACtE;CAGA,IAAI,UAAU,UAAU,YAAY,CAAC;CACrC,KAAA,MAAW,UAAU,UAAU,MAAM,QAAQ,YAAY,OAAO,QAAQ;CACxE,KAAA,MAAW,eAAe,UACxB,MAAM,aAAa,YAAY,YAAY,cAAc,UAAU,CAAC;CAEtE,IAAI,WAAW;CACf,IAAI,SAAS;CACb,IAAI,YAAY;CAChB,IAAI,YAAY,OAAO;CACvB,KAAA,MAAW,MAAM,CAAC,GAAG,WAAW,KAAK,CAAC,CAAA,CAAE,KAAK,GAAG;EAC9C,MAAM,QAAQ,WAAW,IAAI,EAAE;EAC/B,IAAI,CAAC,OAAO;EAEZ,YAAY,MAAM;EAClB,UAAU,MAAM;EAChB,aAAa,MAAM;EACnB,IACE,WAAW,KACX,YAAY,MACX,UAAU,YAAY,SAAS,IAAI,UAAU,WAE9C,OAAO;EACT,YAAY,KAAK,IACf,WACA,UAAU,YAAY,IAAI,WAAW,MACvC;CACF;CACA,OAAO,OAAO,SAAS,SAAS,IAAI,YAAY;AAClD;AAEO,SAAS,aACd,WACA,UACA,UACS;CACT,OAAO,kBAAkB,WAAW,UAAU,QAAQ,KAAK;AAC7D;AAMO,SAAS,gBAAgB,SAG9B;CACA,IACE,CAAC,MAAM,QAAQ,OAAO,KACtB,QAAQ,SAAS,KACjB,QAAQ,SAAA,KAER,MAAM,IAAI,aACR,WACA,4CACF;CAEF,MAAM,WAAW,QACd,KAAK,WAAW,qBAAqB,MAAM,CAAC,CAAA,CAC5C,MAAM,GAAG,MAAM,EAAE,SAAS,cAAc,EAAE,QAAQ,CAAC;CACtD,KAAA,MAAW,CAAC,GAAG,WAAW,SAAS,QAAQ,GAAG;EAC5C,IAAI,OAAO,SAAS,SAAS,EAAC,CAAE,MAC9B,MAAM,IAAI,aAAa,WAAW,qCAAqC;EAEzE,IAAI,IAAI,KAAK,SAAS,IAAI,EAAC,CAAE,SAAS,OAAO,UAC3C,MAAM,IAAI,aAAa,WAAW,kCAAkC;CAExE;CACA,MAAM,QAAQ,SAAS;CACvB,MAAM,OAAO,SAAS,SAAS,SAAS;CACxC,OAAO;EACL,SAAS;EACT,MAAM;GACJ,MAAM,MAAM;GACZ,OAAO,MAAM;GACb,KAAK,KAAK;GACV,UAAU,MAAM;GAChB,QAAQ,KAAK;EACf;CACF;AACF;AAEA,SAAS,IAAI,OAAe,QAAQ,GAAW;CAC7C,OAAO,OAAO,KAAK,CAAA,CAAE,SAAS,OAAO,GAAG;AAC1C;AAGO,SAAS,aAAa,MAAc,MAAsB;CAC/D,MAAM,wBAAQ,IAAI,KAAK,GAAG,KAAI,WAAY;CAC1C,MAAM,WAAW,MAAM,WAAW,IAAI,IAAI;CAC1C,OAAO,MAAM,YAAY,CAAA,CAAE,MAAM,GAAG,EAAE;AACxC;AAGO,SAAS,eAAe,MAAc,QAAwB;CACnE,MAAM,CAAC,MAAM,OAAO,OAAO,KAAK,MAAM,GAAG,CAAA,CAAE,IAAI,MAAM;CACrD,MAAM,QAAQ,OAAO,MAAM,QAAQ,KAAK;CACxC,MAAM,aAAa,KAAK,MAAM,QAAQ,EAAE;CACxC,MAAM,cAAc,QAAQ;CAC5B,MAAM,SAAS,IAAI,KACjB,KAAK,IAAI,YAAY,cAAc,GAAG,CAAC,CACzC,CAAA,CAAE,WAAW;CACb,OAAO,GAAG,IAAI,YAAY,CAAC,EAAC,GAAI,IAAI,cAAc,CAAC,EAAC,GAAI,IAAI,KAAK,IAAI,KAAK,MAAM,CAAC;AACnF;AAGO,SAAS,SACd,MACA,UACA,OACQ;CACR,IAAI,SAAS,OAAO,OAAO,aAAa,OAAO,QAAQ;CACvD,IAAI,SAAS,QAAQ,OAAO,aAAa,OAAO,IAAI,QAAQ;CAC5D,OAAO,eAAe,OAAO,QAAQ;AACvC;AAEA,SAAS,OAAO,QAA+B,MAAc,IAAY;CACvE,OACE,OAAO,MAAM,MAAM,GAAG,EAAE,KAAK,SAC5B,OAAO,IAAI,MAAM,GAAG,EAAE,IAAI,MAAM,OAAO,IAAI,MAAM,GAAG,EAAE,MAAM,GAAG,GAAE;AAEtE;AAGO,SAAS,SACd,MACA,UACA,SACS;CACT,IAAI,CAAC,OAAO,cAAc,QAAQ,KAAK,WAAW,KAAK,QAAQ,SAAS,GACtE,OAAO;CAET,IAAI,SAAS,QAKX,OAJc,QAAQ,QACnB,KAAK,MAAM,OAAO,KAAK,MAAM,EAAE,MAAM,IAAI,KAAK,MAAM,EAAE,QAAQ,IAC/D,CAEK,MAAU,WAAW,KAAK;CAEnC,IAAI,SAAS,SAAS,SAAS,UAAU,SAAS,SAAS,OAAO;CAClE,MAAM,OAAO,QAAQ,EAAC,CAAE,MAAM,MAAM,GAAG,EAAE;CACzC,MAAM,KAAK,SAAS,MAAM,UAAU,IAAI;CACxC,IAAI,CAAC,QAAQ,OAAO,WAAW,OAAO,QAAQ,MAAM,EAAE,CAAC,GAAG,OAAO;CAGjE,IAAI,SAAS,OAAO;EAClB,MAAM,uBAAO,IAAI,IAAY;EAC7B,KAAA,MAAW,UAAU,SAAS;GAC5B,MAAM,OACJ,OAAO,IAAI,MAAM,GAAG,EAAE,MAAM,GAAG,OAAO,IAAI,MAAM,GAAG,EAAE,EAAC,WACtD,OAAO,IAAI,MAAM,GAAG,EAAE,IAAI,OAAO,MAAM,MAAM,GAAG,EAAE,IAC9C,aAAa,OAAO,IAAI,MAAM,GAAG,EAAE,GAAG,EAAE,IACxC,OAAO,IAAI,MAAM,GAAG,EAAE;GAC5B,KAAA,IACM,MAAM,OAAO,MAAM,MAAM,GAAG,EAAE,GAClC,OAAO,MACP,MAAM,aAAa,KAAK,CAAC,GAEzB,KAAK,IAAI,GAAG;EAEhB;EACA,OAAO,KAAK,SAAS;CACvB;CACA,OAAO;AACT;AAaO,SAAS,WAAW,YAAoB,MAAsB;CACnE,MAAM,OAAO,KAAK,MAAM,UAAU;CAClC,IAAI,CAAC,OAAO,SAAS,IAAI,GACvB,MAAM,IAAI,aAAa,WAAW,iBAAiB;CAErD,MAAM,QAAQ,OAAO,YACnB,UAAU,IAAI,CAAA,CACX,cAAc,IAAI,CAAA,CAClB,KAAK,EAAE,MAAM,YAAY,CAAC,MAAM,KAAK,CAAC,CAC3C;CACA,MAAM,QAAQ,KAAK,IACjB,OAAO,MAAM,IAAI,GACjB,OAAO,MAAM,KAAK,IAAI,GACtB,OAAO,MAAM,GAAG,GAChB,OAAO,MAAM,IAAI,GACjB,OAAO,MAAM,MAAM,GACnB,OAAO,MAAM,MAAM,CACrB;CACA,MAAM,SAAS,KAAK,OAAO,QAAQ,QAAQ,SAAS;CACpD,MAAM,OAAO,SAAS,IAAI,MAAM;CAChC,MAAM,MAAM,KAAK,IAAI,MAAM;CAC3B,OAAO,GAAG,IAAI,OAAO,MAAM,IAAI,GAAG,CAAC,EAAC,GAAI,MAAM,MAAK,GAAI,MAAM,IAAG,GAAI,MAAM,KAAI,GAAI,MAAM,SAAS,OAAO,IAAI,KAAK,MAAM,MAAM,EAAE,CAAC,EAAC,GAAI,IAAI,MAAM,EAAE;AACnJ;AAGO,SAAS,mBACd,MACA,SACA,OACuB;CACvB,OAAO,qBAAqB;EAC1B;EACA,OAAO,WAAW,IAAI,KAAK,OAAO,CAAA,CAAE,YAAY,GAAG,IAAI;EACvD,KAAK,WAAW,IAAI,KAAK,KAAK,CAAA,CAAE,YAAY,GAAG,IAAI;CACrD,CAAC;AACH;;;ACjVA,IAAM,WAAW;AACjB,IAAM,QAAgC;CAAC;CAAQ;CAAO;CAAQ;AAAO;AACrE,IAAM,eAAe;AACrB,IAAM,oBAAoB,MAAM;AAChC,IAAM,UAAU;AAChB,IAAM,cACJ;AAEF,SAAS,QAAQ,QAAsD;CACrE,MAAM,EAAE,MAAM,OAAO,KAAK,UAAU,WAAW;CAC/C,OAAO;EAAE;EAAM;EAAO;EAAK;EAAU;CAAO;AAC9C;AAEA,IAAM,OAAO;AAEb,SAAS,GAAG,OAAuB;CACjC,IAAI,OAAO,UAAU,YAAY,CAAC,KAAK,KAAK,KAAK,GAC/C,MAAM,IAAI,aAAa,WAAW,2BAA2B;CAE/D,OAAO,MAAM,YAAY;AAC3B;AAEA,SAAS,WAAW,OAA2B,WAAW,GAAW;CACnE,MAAM,WAAW,UAAU,KAAA,IAAY,WAAW;CAClD,IACE,CAAC,OAAO,cAAc,QAAQ,KAC9B,WAAW,KACX,WAAW,cAEX,MAAM,IAAI,aACR,WACA,6CAA6C,cAC/C;CAEF,OAAO;AACT;AAGA,SAAS,MAAM,OAAuD;CACpE,IAAI,UAAU,MAAM,OAAO;CAC3B,MAAM,SAAS,OAAO,KAAK;CAC3B,IAAI,CAAC,OAAO,cAAc,MAAM,GAC9B,MAAM,IAAI,aAAa,WAAW,8BAA8B;CAElE,OAAO;AACT;AAEA,SAAS,WAAW,EAAE,SAAS,QAAQ,MAA0B;CAC/D,IAAI,CAAC,OAAO,UAAU,KAAK,KAAK,QAAQ,KAAK,QAAQ,KACnD,MAAM,IAAI,aAAa,WAAW,sCAAsC;CAE1E,OAAO;EAAE,OAAO,YAAY,KAAA,IAAY,OAAO,GAAG,OAAO;EAAG;CAAM;AACpE;AAEA,SAAS,KACP,MACA,OACA,QACgB;CAEhB,OAAO;EACL,OAFY,KAAK,MAAM,GAAG,KAE1B;EACA,YAAY,KAAK,SAAS,QAAQ,OAAO,KAAK,QAAQ,EAAE,IAAI;CAC9D;AACF;AAEA,SAAS,YACP,KACA,UACa;CACb,OAAO;EACL,IAAI,IAAI;EACR,YAAY,IAAI;EAChB,WAAW,IAAI;EACf,WAAW,IAAI;EACf,MAAM,IAAI;EACV,MAAM,IAAI;EACV,UAAU,IAAI;EACd,QAAQ,IAAI;EACZ;EACA,oBAAoB,IAAI;EACxB,qBAAqB,IAAI;CAC3B;AACF;AAEA,SAAS,aAAa,YAAoB,KAAmC;CAC3E,OAAO;EACL,IAAI;EACJ,YAAY,IAAI;EAChB,QAAQ,KAAK,MAAM,IAAI,QAAQ;EAC/B,SAAS,KAAK,MAAM,IAAI,OAAO;EAC/B,MAAM,IAAI;EACV,UAAU,IAAI;EACd,gBAAgB,MAAM,IAAI,gBAAgB;EAC1C,UAAU,IAAI;EACd,YAAY,MAAM,IAAI,WAAW;EACjC,OAAO,KAAK,MAAM,IAAI,KAAK;EAC3B,cACE,IAAI,kBAAkB,OAAO,OAAO,KAAK,MAAM,IAAI,aAAa;CACpE;AACF;AAEA,SAAS,SAAS,OAAe,MAAM,KAAa;CAClD,IACE,OAAO,UAAU,YACjB,MAAM,KAAK,CAAA,CAAE,WAAW,KACxB,MAAM,SAAS,OACf,MAAM,SAAS,IAAI,GAEnB,MAAM,IAAI,aACR,WACA,wDACF;CAEF,OAAO;AACT;AAEA,SAAS,SAAS,OAAiD;CACjE,OAAO,OAAO,OAAO;EACnB,UAAU,GAAG,MAAM,QAAQ;EAC3B,SAAS,SAAS,MAAM,OAAO;CACjC,CAAC;AACH;AAEA,SAAS,OAAO,OAAuB;CACrC,OAAO,WAAW,QAAQ,CAAA,CAAE,OAAO,KAAK,CAAA,CAAE,OAAO,KAAK;AACxD;AAEA,SAAS,SAAS,QAA6B;CAC7C,IAAI,CAAC,OAAO,UACV,MAAM,IAAI,aAAa,aAAa,0BAA0B;AAClE;AAEA,SAAS,QAAQ,QAAuB,WAAyB;CAC/D,IAAI,OAAO,cAAc,WACvB,MAAM,IAAI,aACR,aACA,0DACF;AAEJ;AAEA,SAAS,QAAQ,QAAuB,KAAuB;CAC7D,IAAI,CAAC,OAAO,YAAY,OAAO,cAAc,IAAI,YAC/C,MAAM,IAAI,aAAa,aAAa,wBAAwB;AAEhE;AAEA,SAAS,QAAQ,KAAuB;CACtC,IAAI,IAAI,WAAW,eAAe,IAAI,WAAW,YAC/C,MAAM,IAAI,aAAa,YAAY,8BAA8B;AAErE;AAEA,SAAS,SACP,MACA,QACA,cACM;CACN,IACG,SAAS,qBACR,CAAC,oBAAoB,QAAQ,YAAY,KAC1C,SAAS,oBACR,CAAC,aAAa,MAAM,UAAU,MAAM,SAAS,OAAO,IAAI,KAC1D,CAAC,aACC;EAAE,GAAG;EAAQ,WAAW,SAAS;CAAiB,GAClD,cACA,CAAC,CACH,GAEA,MAAM,IAAI,aACR,eACA,sDACF;AAEJ;AAGO,IAAM,iBAAN,MAAqB;CACjB;CACA;CAET,YAAY,IAAuB,WAA6B;EAC9D,KAAK,MAAM;EACX,KAAK,aAAa;CACpB;CAEA,MAAM,kBACJ,SACA,KACA,OACyB;EACzB,MAAM,aAAa,GAAG,MAAM,UAAU;EACtC,MAAM,EAAE,MAAM,YAAY;EAC1B,IACG,SAAS,oBAAoB,SAAS,qBACvC,OAAO,YAAY,WAEnB,MAAM,IAAI,aACR,WACA,uCACF;EAEF,MAAM,OACJ,MAAM,SAAS,SAAS,oBAAoB,QAAS;EACvD,IACE,CAAC,MAAM,SAAS,IAAI,KACnB,SAAS,qBAAqB,SAAS,OAExC,MAAM,IAAI,aACR,WACA,uDACF;EAEF,MAAM,aAAa,MAAM,cAAc;EACvC,MAAM,WAAW,MAAM,YAAY;EACnC,IACG,eAAe,SACb,CAAC,OAAO,cAAc,UAAU,KAAK,aAAa,MACpD,aAAa,SACX,OAAO,aAAa,YAAY,CAAC,SAAS,KAAK,QAAQ,MACzD,eAAe,UAAW,aAAa,OAExC,MAAM,IAAI,aACR,WACA,8FACF;EAEF,MAAM,QAAQ,MAAM,SAAS;EAC7B,MAAM,cAAc,MAAM,eAAe;EACzC,MAAM,WAAW,MAAM,UAAU;EACjC,IACE,OAAO,UAAU,YACjB,OAAO,gBAAgB,YACvB,MAAM,SAAS,OACf,YAAY,SAAS,OACrB,MAAM,SAAS,IAAI,KACnB,YAAY,SAAS,IAAI,KACzB,OAAO,aAAa,WAEpB,MAAM,IAAI,aACR,WACA,sEACF;EAEF,OAAO,KAAK,OACV,SACA,sBACA,KAEA;GACE;GACA;GACA;GACA,GAAI,UAAU,SAAS,oBAAoB,QAAQ,UAC/C,EAAE,KAAK,IACP,CAAC;GACL,GAAI,eAAe,OAAO;IAAE;IAAY;GAAS,IAAI,CAAC;GACtD,GAAI,UAAU,KAAK,EAAE,MAAM,IAAI,CAAC;GAChC,GAAI,gBAAgB,KAAK,EAAE,YAAY,IAAI,CAAC;GAC5C,GAAI,WAAW,EAAE,QAAQ,KAAK,IAAI,CAAC;EACrC,GACA,UACA,OAAO,IAAI,QAAQ;GAoBjB,IAAI,EAAC,MAnBe,GAAG,MACrB;;;;;;wBAOA,IAAI,UACJ,YACA,MACA,MACA,YACA,UACA,OACA,aACA,UACA,OACF,EAAA,CACW,KAAK,QACd,MAAM,IAAI,aACR,YACA,wCACF;GACF,OAAO,EAAE,IAAI,WAAW;EAC1B,CACF;CACF;CAEA,MAAM,oBACJ,SACA,KACA,OACyB;EACzB,MAAM,aAAa,GAAG,MAAM,UAAU;EACtC,MAAM,UAAU,eAAe,MAAM,MAAM,MAAM,OAAO;EACxD,OAAO,KAAK,OACV,SACA,wBACA,KACA;GAAE;GAAY;EAAQ,GACtB,UACA,OAAO,IAAI,QAAQ;GACjB,MAAM,GAAG,MACP;kDAEA,IAAI,UACJ,UACF;GACA,MAAM,KAAK,UAAU,IAAI,KAAK,YAAY,IAAI;GAC9C,SAAS,MAAM,KAAK,QAAQ,KAAK,sBAAsB,CAAC;GACxD,MAAM,KAAK,QAAQ,KAAK,EAAE,WAAW,CAAC;GACtC,MAAM,YAAY,MAAM,KAAK,aAAa,IAAI,KAAK,UAAU;GAC7D,IACE,UAAU,MACP,MAAM,MACJ,CAAC,KAAK,aAAa,CAAC,oBAAoB,KAAK,QAAQ,OAAO,KAC7D,CAAC,aACC,MACA,SACA,UAAU,QAAQ,GAAG,MAAM,MAAM,CAAC,CACpC,CACJ,GAEA,MAAM,IAAI,aACR,eACA,2DACF;GAEF,MAAM,GAAG,MACP,gFACA,KAAK,UAAU,OAAO,GACtB,IAAI,UACJ,UACF;GACA,OAAO,EAAE,IAAI,WAAW;EAC1B,CACF;CACF;CAEA,MAAM,QACJ,SACA,KACA,OACyB;EACzB,MAAM,aAAa,GAAG,MAAM,UAAU;EACtC,MAAM,YAAY,GAAG,MAAM,SAAS;EACpC,MAAM,YACJ,MAAM,cAAc,KAAA,IAAY,OAAO,GAAG,MAAM,SAAS;EAC3D,MAAM,WAAW,WAAW,MAAM,QAAQ;EAC1C,OAAO,KAAK,OACV,SACA,WACA,KACA;GACE;GACA;GACA;GACA,GAAI,aAAa,IAAI,EAAE,SAAS,IAAI,CAAC;EACvC,IACC,WAAW;GACV,IAAI,CAAC,OAAO,UAAU,QAAQ,QAAQ,SAAS;EACjD,GACA,OAAO,IAAI,QAAQ;GACjB,MAAM,EAAE,SAAS,MAAM,GAAG,MACxB,mGACA,IAAI,UACJ,UACF;GACA,IAAI,CAAC,KAAK,EAAC,EAAG,SACZ,MAAM,IAAI,aAAa,aAAa,2BAA2B;GACjE,MAAM,OAAO,KAAK,EAAC,CAAE;GACrB,MAAM,OAAO,KAAK,EAAC,CAAE;GACrB,IAAI,SAAS,sBAAsB,CAAC,aAAa,aAAa,IAC5D,MAAM,IAAI,aACR,WACA,gEACF;GAEF,MAAM,KAAK,QAAQ,KAAK;IACtB;IACA,WAAW,aAAa,KAAA;GAC1B,CAAC;GACD,MAAM,YAAY,WAAW;GAC7B,MAAM,GAAG,MACP;wDAEA,IAAI,UACJ,WACA,YACA,WACA,WACA,MACA,MACA,QACF;GACA,OAAO,EAAE,IAAI,UAAU;EACzB,GACA,EAAE,gBAAgB,KAAK,CACzB;CACF;CAEA,MAAM,QACJ,SACA,KACA,OACyB;EACzB,MAAM,YAAY,GAAG,MAAM,SAAS;EACpC,MAAM,aAAa,GAAG,MAAM,UAAU;EACtC,IAAK,MAAM,WAAW,KAAA,OAAgB,MAAM,YAAY,KAAA,IACtD,MAAM,IAAI,aACR,WACA,0CACF;EAEF,MAAM,EAAE,SAAS,SAAS,gBACxB,MAAM,WAAW,CAAC,MAAM,MAAuB,CACjD;EACA,MAAM,oBACJ,MAAM,aAAa,KAAA,IAAY,OAAO,WAAW,MAAM,QAAQ;EACjE,MAAM,QAAQ;GACZ,UAAU,SAAS,MAAM,MAAM,QAAQ;GACvC,MAAM,SAAS,MAAM,MAAM,MAAM,KAAM;EACzC;EACA,MAAM,OAAO,MAAM;EACnB,MAAM,eACJ,SAAS,KAAA,IACL,OACA;GAAE,IAAI,GAAG,KAAK,EAAE;GAAG,SAAS,SAAS,KAAK,OAAO;EAAE;EACzD,OAAO,KAAK,OACV,SACA,WACA,KAEA;GACE;GACA;GACA,GAAI,QAAQ,WAAW,IAAI,EAAE,QAAQ,QAAQ,GAAG,IAAI,EAAE,QAAQ;GAC9D,GAAI,sBAAsB,OAAO,EAAE,UAAU,kBAAkB,IAAI,CAAC;GACpE;GACA;EACF,GACA,UACA,OAAO,IAAI,QAAQ;GACjB,MAAM,MAAM,MAAM,KAAK,SAAS,IAAI,KAAK,WAAW,IAAI;GACxD,QAAQ,GAAG;GACX,MAAM,WAAW,qBAAqB,IAAI;GAC1C,IAAI,IAAI,SAAS;QACX,CAAC,gBAAgB,aAAa,KAAK,QAAQ,WAAW,GACxD,MAAM,IAAI,aACR,WACA,uEACF;GAAA,OAEG;IACL,IAAI,cACF,MAAM,IAAI,aACR,WACA,+CACF;IAEF,IAAI,CAAC,SAAS,IAAI,MAAM,UAAU,OAAO,GACvC,MAAM,IAAI,aACR,WACA,wDACF;GAEJ;GACA,MAAM,KAAK,QAAQ,KAAK;IACtB,GAAG,KAAK,MAAM,GAAG;IACjB;IACA,cAAc,gBAAgB,KAAA;GAChC,CAAC;GACD,MAAM,eAAe,MAAM,KAAK,UAAU,IAAI,KAAK,YAAY,KAAK;GACpE,KAAA,MAAW,UAAU,SAAS,SAAS,IAAI,MAAM,QAAQ,YAAY;GACrE,MAAM,WAAW,MAAM,GAAG,MACxB;sDAEA,IAAI,UACJ,IAAI,WACN;GACA,MAAM,iBAAiB,MAAM,SAAS,KAAK,EAAC,EAAG,eAAe,IAAI;GAClE,MAAM,WAA0B,SAAS,KAAK,EAAC,EAAG,YAAY;GAC9D,MAAM,aACJ,mBAAmB,OAAO,OAAO,iBAAiB;GACpD,IAAI,eAAe,QAAQ,CAAC,OAAO,cAAc,UAAU,GACzD,MAAM,IAAI,aAAa,WAAW,0BAA0B;GAE9D,MAAM,aAAa,WAAW;GAC9B,MAAM,GAAG,MACP;;;0DAIA,IAAI,UACJ,WACA,YACA,YACA,KAAK,UAAU,IAAI,GACnB,KAAK,UAAU,OAAO,GACtB,IAAI,MACJ,UACA,gBACA,UACA,YACA,KAAK,UAAU,KAAK,GACpB,gBAAgB,KAAK,UAAU,YAAY,CAC7C;GACA,MAAM,GAAG,MACP;0CAEA,YACA,IAAI,UACJ,SACF;GACA,OAAO;IAAE,IAAI;IAAW;GAAW;EACrC,GACA,EAAE,UAAU,CACd;CACF;CAEA,MAAM,OACJ,SACA,KACA,OACyB;EACzB,MAAM,YAAY,GAAG,MAAM,SAAS;EACpC,MAAM,aAAa,GAAG,MAAM,UAAU;EACtC,OAAO,KAAK,OACV,SACA,UACA,KACA;GAAE;GAAW;EAAW,GACxB,OAAO,QAAQ,IAAI,QAAQ;GAEzB,MAAM,MAAM,MAAM,KAAK,SAAS,IAAI,KAAK,WAAW,KAAK;GACzD,QAAQ,QAAQ,GAAG;GACnB,QAAQ,QAAQ,IAAI,UAAU;EAChC,GACA,OAAO,IAAI,QAAQ;GACjB,MAAM,MAAM,MAAM,KAAK,SAAS,IAAI,KAAK,WAAW,IAAI;GACxD,QAAQ,MAAM,KAAK,QAAQ,KAAK,QAAQ,GAAG,IAAI,UAAU;GACzD,KAAK,SAAS,KAAK,UAAU;GAC7B,IAAI,IAAI,sBACN,MAAM,IAAI,aACR,YACA,qCACF;GACF,MAAM,KAAK,QAAQ,KAAK,KAAK,MAAM,GAAG,CAAC;GACvC,MAAM,GAAG,MACP,oFACA,YACA,IAAI,UACJ,SACF;GACA,OAAO;IAAE,IAAI;IAAW;GAAW;EACrC,GACA,EAAE,UAAU,CACd;CACF;CAEA,MAAM,QACJ,SACA,KACA,OACyB;EACzB,MAAM,YAAY,GAAG,MAAM,SAAS;EACpC,MAAM,aAAa,GAAG,MAAM,UAAU;EACtC,OAAO,KAAK,OACV,SACA,WACA,KACA;GAAE;GAAW;EAAW,GACxB,UACA,OAAO,IAAI,QAAQ;GACjB,MAAM,MAAM,MAAM,KAAK,SAAS,IAAI,KAAK,WAAW,IAAI;GACxD,KAAK,SAAS,KAAK,UAAU;GAC7B,IAAI,IAAI,yBAAyB,YAC/B,MAAM,IAAI,aACR,YACA,8CACF;GAEF,MAAM,WAAW,MAAM,KAAK,UAAU,IAAI,KAAK,KAAK,UAAU;GAC9D,MAAM,eAAe,MAAM,KAAK,UAC9B,IACA,KACA,SAAS,YACT,IACF;GAEA,SAAS,MAAM,KAAK,QAAQ,KAAK,SAAS,CAAC;GAC3C,MAAM,KAAK,QAAQ,KAAK;IACtB,GAAG,KAAK,MAAM,GAAG;IACjB,YAAY,SAAS;IACrB,cAAc,SAAS,gBAAgB,KAAA;GACzC,CAAC;GACD,MAAM,YAAY,IAAI,SAAS;GAC/B,MAAM,SAAS,SAAS;GACxB,MAAM,WAAW,MAAM,KAAK,aAC1B,IACA,KACA,SAAS,YACT,MACF;GACA,KAAA,MAAW,WAAW,SAAS,SAAS;IACtC,SAAS,IAAI,MAAM,SAAS,YAAY;IACxC,IACE,CAAC,aAAa;KAAE,GAAG;KAAS;IAAU,GAAG,cAAc,QAAQ,GAE/D,MAAM,IAAI,aACR,eACA,+CACF;GAEJ;GAgBA,IAAI,EAAC,MAfkB,GAAG,MACxB;;;;kGAKA,IAAI,UACJ,WACA,YACA,SAAS,YACT,YAAY,OAAO,IAAI,YACvB,WACA,OAAO,UACP,OAAO,MACT,EAAA,CACc,KAAK,QACjB,MAAM,IAAI,aACR,4BACA,mDACF;GAEF,MAAM,GAAG,MACP;0CAEA,YACA,IAAI,UACJ,SACF;GACA,OAAO;IAAE,IAAI;IAAW;GAAW;EACrC,GACA,EAAE,UAAU,CACd;CACF;;CAGA,MAAM,cACJ,SACA,KACA,OACyB;EACzB,MAAM,YAAY,GAAG,MAAM,SAAS;EACpC,MAAM,YAAY,GAAG,MAAM,SAAS;EACpC,OAAO,KAAK,OACV,SACA,kBACA,KACA;GAAE;GAAW;EAAU,GACvB,UACA,OAAO,IAAI,QAAQ;GACjB,MAAM,MAAM,MAAM,KAAK,SAAS,IAAI,KAAK,WAAW,IAAI;GACxD,IAAI,IAAI,WAAW,cAAc,IAAI,WAAW,aAC9C,MAAM,IAAI,aAAa,YAAY,6BAA6B;GAElE,IAAI,IAAI,eAAe,WAAW,OAAO,EAAE,IAAI,UAAU;GACzD,IAAI,IAAI,eAAe,MACrB,MAAM,IAAI,aACR,YACA,iDACF;GAEF,MAAM,KAAK,QAAQ,KAAK;IAAE,WAAW,IAAI;IAAY;GAAU,CAAC;GAChE,MAAM,GAAG,MACP,0EACA,WACA,IAAI,UACJ,SACF;GACA,OAAO,EAAE,IAAI,UAAU;EACzB,GACA,EAAE,UAAU,CACd;CACF;CAEA,QACE,SACA,KACA,OACyB;EACzB,OAAO,KAAK,OAAO,SAAS,WAAW,KAAK,KAAK;CACnD;CAEA,OACE,SACA,KACA,OACyB;EACzB,OAAO,KAAK,OAAO,SAAS,UAAU,KAAK,KAAK;CAClD;CAEA,MAAM,IAAI,SAAyB,WAAyC;EAC1E,MAAM,MAAM,SAAS,OAAO;EAC5B,MAAM,SAAS,MAAM,KAAK,QAAQ,KAAK,MAAM;EAC7C,MAAM,MAAM,MAAM,KAAK,SAAS,KAAK,KAAK,KAAK,GAAG,SAAS,GAAG,KAAK;EACnE,QAAQ,QAAQ,GAAG;EACnB,OAAO,YACL,KACA,IAAI,cACA,MAAM,KAAK,UAAU,KAAK,KAAK,KAAK,KAAK,IAAI,WAAW,IACxD,IACN;CACF;;CAGA,MAAM,aACJ,SACA,UAA+B,CAAC,GACG;EACnC,MAAM,MAAM,SAAS,OAAO;EAC5B,MAAM,SAAS,MAAM,KAAK,UAAU,GAAG;EACvC,MAAM,EAAE,OAAO,UAAU,WAAW,OAAO;EAC3C,MAAM,YACJ,QAAQ,cAAc,KAAA,IAAY,OAAO,GAAG,QAAQ,SAAS;EAC/D,MAAM,YACJ,QAAQ,cAAc,KAAA,IAAY,OAAO,GAAG,QAAQ,SAAS;EAC/D,IAAI,CAAC,OAAO,YAAY,aAAa,cAAc,OAAO,WACxD,MAAM,IAAI,aACR,aACA,+CACF;EA2BF,MAAM,QAAO,MAzBQ,KAAK,IAAI,MAC5B;;;;;;;;;;;;+BAaA,IAAI,UACJ,OAAO,UACP,OAAO,aAAa,MACpB,WACA,WACA,WACA,WACA,OACA,OACA,QAAQ,CACV,EAAA,CACoB;EACpB,MAAM,WAAW,KAAK,MAAM,QAAQ,QAAQ,IAAI,EAAE;EAClD,OAAO;GACL,OAAO,SAAS,MAAM,KAAK,QACzB,YACE,KACA,IAAI,cAAc,aAAa,IAAI,aAAa,GAAG,IAAI,IACzD,CACF;GACA,YAAY,SAAS;EACvB;CACF;;;;;CAMA,MAAM,cACJ,SACA,UAA8B,CAAC,GACQ;EACvC,MAAM,MAAM,SAAS,OAAO;EAC5B,MAAM,SAAS,MAAM,KAAK,QAAQ,KAAK,MAAM;EAC7C,MAAM,EAAE,OAAO,UAAU,WAAW,OAAO;EAC3C,MAAM,EAAE,SAAS,MAAM,KAAK,IAAI,MAC9B;;;;6BAKA,IAAI,UACJ,OAAO,UACP,OAAO,YAAY,OAAO,cAAc,KAAA,GACxC,OACA,OACA,QAAQ,CACV;EAYA,OAAO,KAX8B,KAAK,KAAK,SAAS;GACtD,IAAI,IAAI;GACR,MAAM,IAAI;GACV,MAAM,IAAI;GACV,YAAY,MAAM,IAAI,WAAW;GACjC,UAAU,IAAI;GACd,OAAO,IAAI;GACX,aAAa,IAAI;GACjB,QAAQ,IAAI;GACZ,SAAS,IAAI;EACf,EACY,GAAW,QAAQ,QAAQ,IAAI,EAAE;CAC/C;;;;;;;CAQA,MAAM,cACJ,SACA,SACgC;EAChC,MAAM,MAAM,SAAS,OAAO;EAC5B,MAAM,SAAS,MAAM,KAAK,QAAQ,KAAK,MAAM;EAC7C,MAAM,aAAa,GAAG,QAAQ,UAAU;EACxC,MAAM,SAAS,KAAK,MAAM,QAAQ,IAAI;EACtC,MAAM,OAAO,KAAK,MAAM,QAAQ,EAAE;EAClC,IACE,CAAC,OAAO,SAAS,MAAM,KACvB,CAAC,OAAO,SAAS,IAAI,KACrB,QAAQ,UACR,OAAO,SAAS,mBAEhB,MAAM,IAAI,aACR,WACA,iDACF;EAEF,MAAM,OAAO,IAAI,KAAK,MAAM,CAAA,CAAE,YAAY;EAC1C,MAAM,KAAK,IAAI,KAAK,IAAI,CAAA,CAAE,YAAY;EACtC,MAAM,WAAW,WAAW,QAAQ,QAAQ;EAC5C,MAAM,aACJ,QAAQ,eAAe,KAAA,IAAY,OAAO,GAAG,QAAQ,UAAU;EACjE,MAAM,EAAE,UAAU,WAAW,EAAE,OAAO,QAAQ,MAAM,CAAC;EACrD,IAAI,QAAQ,UAAU,KAAA,KAAa,CAAC,YAAY,KAAK,QAAQ,KAAK,GAChE,MAAM,IAAI,aAAa,WAAW,qBAAqB;EAEzD,MAAM,QAAQ,QAAQ,OAAO,QAAQ,UAAU,aAC7C,SAAS,YAAY,CACvB;EAMA,MAAM,YAAW,MALG,KAAK,IAAI,MAC3B,iGACA,IAAI,UACJ,UACF,EAAA,CACuB,KAAK;EAM5B,IAAI,EAJF,aACC,OAAO,YACL,SAAS,YACP,OAAO,cAAc,KAAA,KAAa,SAAS,WAEhD,MAAM,IAAI,aAAa,aAAa,2BAA2B;EAEjE,MAAM,OAAO,SAAS;EACtB,MAAM,OAAO,SAAS;EACtB,IAAI,SAAS,qBAAqB,aAAa,GAC7C,MAAM,IAAI,aACR,WACA,0CACF;EAEF,MAAM,YAAY,MAAM,KAAK,IAAI,MAC/B;6DAEA,IAAI,UACJ,YACA,UACF;EACA,MAAM,WAAW,MAAM,KAAK,aAAa,KAAK,KAAK,KAAK,YAAY;GAClE,UAAU;GACV,QAAQ;EACV,CAAC;EACD,MAAM,QAAoB,CAAC;EAC3B,KAAA,MAAW,YAAY,UAAU,MAAM;GACrC,MAAM,kBACJ,KAAK,MAAM,SAAS,OAAO,CAAA,CAC3B,MAAM,GAAG,MAAM,EAAE,SAAS,cAAc,EAAE,QAAQ,CAAC;GACrD,MAAM,QAAQ,SAAS,QAAQ,SAAS,KAAK,eAAe,SAAS,EAAE;GACvE,MAAM,WAAW,MACf,EAAE,YAAY,QAAQ,EAAE,UAAU;GACpC,MAAM,QAAQ,SAAkC,cAAsB;IACpE,IAAI,aAAa,GACf,MAAM,KAAK;KACT,YAAY,SAAS;KACrB;KACA;KACA;KACA;KACA,mBAAmB;IACrB,CAAC;GAEL;GACA,IAAI,SAAS;SACX,MAAW,KAAK,iBACd,IAAI,EAAE,eAAe,QAAQ,CAAC,GAC5B,KACE,CAAC,QAAQ,CAAC,CAAC,GACX,kBACE;KAAE,GAAG;KAAG,WAAW;IAAM,GACzB,iBACA,KACF,CACF;GAAA,OAGN,IAAW,SAAS,QAAQ;IAC1B,MAAM,SAAS,WAAW;IAC1B,KAAA,MAAW,KAAK,iBAAiB;KAC/B,MAAM,QAAQ,KAAK,MAAM,EAAE,QAAQ;KACnC,MAAM,MAAM,KAAK,MAAM,EAAE,MAAM;KAC/B,IAAI,EAAE,UAAU,QAAQ,EAAE,YAAY,IAAI;KAC1C,MAAM,QAAQ,KAAK,IAAI,GAAG,KAAK,MAAM,SAAS,SAAS,OAAO,CAAC;KAC/D,KAAA,IAAS,IAAI,OAAO,QAAQ,IAAI,UAAU,UAAU,KAAK,KAAK;MAC5D,MAAM,YAAY,mBAChB,EAAE,MACF,QAAQ,IAAI,SACZ,QAAQ,IAAI,UAAU,MACxB;MACA,IAAI,UAAU,YAAY,IAAI;MAC9B,IAAI,CAAC,QAAQ,SAAS,GAAG;MACzB,KACE,CAAC,SAAS,GACV,kBACE;OAAE,GAAG;OAAW,WAAW;MAAK,GAChC,iBACA,KACF,CACF;KACF;IACF;GACF,OAAO;IACL,MAAM,SAAS,CACb,GAAG,IAAI,IAAI,gBAAgB,KAAK,MAAM,EAAE,MAAM,MAAM,GAAG,EAAE,CAAC,CAAC,CAC7D;IACA,KAAA,MAAW,MAAM,QAAQ;KACvB,MAAM,KAAK,SAAS,MAAM,UAAU,EAAE;KACtC,MAAM,SAAS,gBAAgB,QAC5B,MACC,EAAE,MAAM,MAAM,GAAG,EAAE,KAAK,MACxB,EAAE,MAAM,MAAM,GAAG,EAAE,IAAI,OACtB,EAAE,IAAI,MAAM,GAAG,EAAE,IAAI,MAAM,EAAE,IAAI,MAAM,GAAG,EAAE,MAAM,GAAG,GAAE,QAC5D;KACA,MAAM,WAAW,OAAO,IAAI,OAAO;KACnC,IACE,SAAS,WAAW,KACpB,SAAS,SAAA,OACT,SAAS,EAAC,CAAE,MAAM,MAAM,GAAG,EAAE,MAAM,MACnC,CAAC,SAAS,MAAM,OAAO,KACvB,CAAC,SAAS,MAAM,UAAU,QAAQ,KAClC,CAAC,OAAO,OACL,MACC,kBACE;MAAE,GAAG;MAAG,WAAW;KAAK,GACxB,iBACA,KACF,KAAK,CACT,GAEA;KAEF,KAAK,UAAU,CAAC;IAClB;GACF;EACF;EACA,MAAM,OAAO,SACX,GAAG,KAAK,QAAQ,EAAC,CAAE,SAAQ,GAAI,KAAK;EACtC,MAAM,uBAAO,IAAI,IAAY;EAK7B,OAAO,KAJS,MACb,MAAM,GAAG,MAAO,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,KAAK,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,IAAI,CAAE,CAAA,CAC/D,QAAQ,SAAS,CAAC,KAAK,IAAI,IAAI,IAAI,CAAC,KAAK,KAAK,IAAI,IAAI,IAAI,CAAC,CAAC,CAAA,CAC5D,QAAQ,SAAS,UAAU,KAAA,KAAa,IAAI,IAAI,IAAI,KAC3C,CAAA,CAAQ,MAAM,GAAG,QAAQ,CAAC,GAAG,OAAO,GAAG;CACrD;;CAGA,MAAM,iBACJ,SACA,UAA8B,CAAC,GACa;EAC5C,MAAM,MAAM,SAAS,OAAO;EAC5B,SAAS,MAAM,KAAK,QAAQ,KAAK,MAAM,CAAC;EACxC,MAAM,EAAE,OAAO,UAAU,WAAW,OAAO;EAC3C,MAAM,EAAE,SAAS,MAAM,KAAK,IAAI,MAC9B;;6BAGA,IAAI,UACJ,OACA,OACA,QAAQ,CACV;EACA,MAAM,WAAW,KACf,MACA,QACC,QAAQ,IAAI,EACf;EACA,OAAO;GACL,OAAO,SAAS,MAAM,KAAK,SAAS;IAClC,YAAY,IAAI;IAChB,SAAS,KAAK,MAAM,IAAI,OAAO;GACjC,EAAE;GACF,YAAY,SAAS;EACvB;CACF;CAEA,MAAM,YACJ,SACA,WACA,YAC0B;EAC1B,MAAM,MAAM,SAAS,OAAO;EAC5B,MAAM,SAAS,MAAM,KAAK,QAAQ,KAAK,MAAM;EAC7C,MAAM,MAAM,MAAM,KAAK,SAAS,KAAK,KAAK,KAAK,GAAG,SAAS,GAAG,KAAK;EACnE,QAAQ,QAAQ,GAAG;EACnB,OAAO,KAAK,UAAU,KAAK,KAAK,KAAK,KAAK,GAAG,UAAU,CAAC;CAC1D;CAEA,MAAM,QACJ,SACA,WACgC;EAChC,MAAM,MAAM,SAAS,OAAO;EAC5B,SAAS,MAAM,KAAK,QAAQ,KAAK,SAAS,CAAC;EAC3C,MAAM,KAAK,SAAS,KAAK,KAAK,KAAK,GAAG,SAAS,GAAG,KAAK;EACvD,MAAM,EAAE,SAAS,MAAM,KAAK,IAAI,MAC9B;iGAEA,IAAI,UACJ,GAAG,SAAS,CACd;EACA,OAAO,KAAK,KAAK,SAAS;GACxB,WAAW,IAAI;GACf,SAAS,IAAI;GACb,YAAY,IAAI;GAChB,QAAQ,IAAI;GACZ,IAAI,IAAI,KAAK,IAAI,EAAE,CAAA,CAAE,YAAY;EACnC,EAAE;CACJ;CAEA,MAAM,OACJ,SACA,WACA,KACA,OACyB;EACzB,MAAM,YAAY,GAAG,MAAM,SAAS;EACpC,MAAM,SAAS,SAAS,MAAM,QAAQ,IAAK;EAC3C,OAAO,KAAK,OACV,SACA,WACA,KACA;GAAE;GAAW;EAAO,GACpB,UACA,OAAO,IAAI,QAAQ;GACjB,MAAM,MAAM,MAAM,KAAK,SAAS,IAAI,KAAK,WAAW,IAAI;GACxD,IACE,cAAc,YACd,IAAI,yBACJ,IAAI,WAAW,aACf;IACA,MAAM,WAAW,MAAM,KAAK,UAC1B,IACA,KACA,KACA,IAAI,qBACN;IACA,MAAM,KAAK,UAAU,IAAI,KAAK,SAAS,YAAY,IAAI;IACvD,SAAS,MAAM,KAAK,QAAQ,KAAK,QAAQ,CAAC;IAO1C,IAAI,EAAC,MANkB,GAAG,MACxB;iGAEA,IAAI,UACJ,SACF,EAAA,CACc,KAAK,QACjB,MAAM,IAAI,aACR,YACA,4CACF;GACJ,OACE,QAAQ,GAAG;GAEb,MAAM,GAAG,MACP,sEACA,cAAc,WAAW,cAAc,YACvC,IAAI,UACJ,SACF;GACA,OAAO;IACL,IAAI;IACJ,GAAI,IAAI,cAAc,EAAE,YAAY,IAAI,YAAY,IAAI,CAAC;GAC3D;EACF,GACA;GAAE;GAAW;EAAO,CACtB;CACF;CAEA,MAAM,QACJ,SACA,WACwB;EACxB,MAAM,SAAS,MAAM,KAAK,WAAW,OAAO,SAAS,SAAS;EAC9D,IAAI,CAAC,QAAQ,MAAM,IAAI,aAAa,aAAa,uBAAuB;EACxE,OAAO;GACL,UAAU,OAAO,aAAa;GAC9B,WACE,OAAO,cAAc,KAAA,IAAY,KAAA,IAAY,GAAG,OAAO,SAAS;EACpE;CACF;CAEA,MAAM,UAAU,KAAuD;EACrE,MAAM,SAAS,MAAM,KAAK,QAAQ,KAAK,MAAM;EAC7C,IAAI,CAAC,OAAO,YAAY,CAAC,OAAO,WAC9B,MAAM,IAAI,aACR,aACA,sCACF;EAEF,OAAO;CACT;CAEA,MAAM,QACJ,KACA,MACe;EACf,IACE,KAAK,cAAc,KAAA,KACnB,KAAK,cAAc,KAAA,KACnB,KAAK,WAAW,eAChB;GAEA,IAAK,MAAM,KAAK,WAAW,cAAc,KAAK,KAAK,SAAS,MAAO,MACjE,MAAM,IAAI,aACR,aACA,sDACF;GAEF,MAAM,EAAE,WAAW,UAAU,GAAG,WAAW;GAC3C,MAAM,OAAO,OAAO,YAClB,OAAO,QAAQ,MAAM,CAAA,CAAE,QAAQ,GAAG,WAAW,UAAU,KAAA,CAAS,CAClE;GACA,IAAI,OAAO,KAAK,IAAI,CAAA,CAAE,WAAW,GAAG;GACpC,OAAO;EACT;EAEA,IACG,MAAM,KAAK,WAAW,iBAAiB,KAAK,gBAAgB,IAAI,CAAC,MAClE,MAEA,MAAM,IAAI,aACR,aACA,sDACF;CAEJ;CAEA,MAAM,KAAoC;EACxC,OAAO;GACL,WAAW,IAAI;GACf,WAAW,IAAI,cAAc,KAAA;EAC/B;CACF;CAEA,MAAM,SACJ,IACA,KACA,WACA,MACqB;EACrB,MAAM,EAAE,SAAS,MAAM,GAAG,MACxB,6DAA6D,OAAO,gBAAgB,MACpF,IAAI,UACJ,SACF;EACA,IAAI,CAAC,KAAK,IAAI,MAAM,IAAI,aAAa,aAAa,wBAAwB;EAC1E,OAAO,KAAK;CACd;CAEA,SAAS,KAAiB,YAA0B;EAClD,IAAI,IAAI,WAAW,cAAc,IAAI,gBAAgB,YACnD,MAAM,IAAI,aACR,YACA,6CACF;CAEJ;CAEA,MAAM,UACJ,IACA,KACA,SACA,YAC0B;EAC1B,MAAM,EAAE,SAAS,MAAM,GAAG,MACxB;;2DAGA,IAAI,UACJ,QAAQ,IACR,UACF;EACA,MAAM,MAAM,KAAK;EACjB,IAAI,CAAC,KAAK,MAAM,IAAI,aAAa,aAAa,yBAAyB;EACvE,OAAO,aAAa,YAAY,GAAkB;CACpD;CAEA,MAAM,UACJ,IACA,KACA,YACA,MACuC;EACvC,MAAM,EAAE,SAAS,MAAM,GAAG,MACxB,kFAAkF,OAAO,gBAAgB,MACzG,IAAI,UACJ,UACF;EACA,IAAI,CAAC,KAAK,IACR,MAAM,IAAI,aACR,aACA,yCACF;EACF,OAAO,KAAK,MAAM,KAAK,EAAC,CAAE,OAAO;CACnC;CAEA,MAAM,aACJ,IACA,KACA,YACA,QACuB;EACvB,MAAM,EAAE,SAAS,MAAM,GAAG,MACxB;;mGAE6F,SAAS,yCAAyC,MAC/I,IAAI,UACJ,YACA,YACA,GAAI,SAAS,CAAC,OAAO,QAAQ,OAAO,QAAQ,IAAI,CAAC,CACnD;EAEA,OAAO,KAAK,SAAS,QAClB,KAAK,MAAM,IAAI,OAAO,CAAA,CACpB,QACE,YACC,CAAC,UACA,QAAQ,WAAW,OAAO,UACzB,QAAQ,SAAS,OAAO,QAC9B,CAAA,CACC,KAAK,aAAa;GACjB,GAAG;GACH,QAAQ;GACR,YAAY,IAAI;GAChB,WAAW,IAAI;EACjB,EAAE,CACN;CACF;CAEA,MAAM,OACJ,SACA,WACA,KACA,WACA,QAKA,MAIA,UAII,CAAC,GACoB;EACzB,MAAM,MAAM,SAAS,OAAO;EAC5B,MAAM,UAAU,OAAO,SAAS,GAAG,CAAC;EACpC,MAAM,cAAc,OAAO,KAAK,UAAU,SAAS,CAAC;EACpD,IAAI,CAAC,KAAK,IAAI,aACZ,MAAM,IAAI,UAAU,4CAA4C;EAClE,OAAO,KAAK,IAAI,YAAY,OAAO,OAAO;GACxC,MAAM,GAAG,MAAM,gDAAgD;GAC/D,MAAM,OAAO,MAAM,KAAK,QAAQ,KAAK,SAAS,GAAG,IAAI,GAAG;GAUxD,IAAI,EAAC,MATkB,GAAG,MACxB;6GAEA,IAAI,UACJ,WACA,SACA,IAAI,SACJ,WACF,EAAA,CACc,KAAK,QAAQ;IACzB,MAAM,OAAO,MAAM,KAAK,QAAQ,KAAK,SAAS,GAAG,IAAI,GAAG;IACxD,MAAM,EAAE,SAAS,MAAM,GAAG,MACxB;oEAEA,IAAI,UACJ,WACA,OACF;IACA,MAAM,QAAQ,KAAK;IACnB,IACE,CAAC,SACD,MAAM,aAAa,IAAI,WACvB,MAAM,iBAAiB,eACvB,CAAC,MAAM,QAEP,MAAM,IAAI,aACR,YACA,iDACF;IAEF,OAAO,KAAK,MAAM,MAAM,MAAM;GAChC;GACA,MAAM,SAAS,MAAM,KAAK,IAAI,GAAG;GACjC,MAAM,GAAG,MACP;kEAEA,QAAQ,cAAc,QAAQ,iBAAiB,OAAO,KAAK,OAC3D,KAAK,UAAU,MAAM,GACrB,QAAQ,UAAU,MAClB,IAAI,UACJ,WACA,OACF;GACA,OAAO;EACT,CAAC;CACH;AACF"}
@@ -2,7 +2,7 @@
2
2
  "version": "1.0.0",
3
3
  "timestamp": 0,
4
4
  "packageName": "@happyvertical/smrt-bookings",
5
- "packageVersion": "0.60.7",
5
+ "packageVersion": "0.60.9",
6
6
  "objects": {},
7
7
  "moduleType": "smrt",
8
8
  "stampsConstructors": true
@@ -1,4 +1,10 @@
1
1
  import type { DatabaseInterface } from '@happyvertical/sql';
2
2
  /** Initial PostgreSQL schema. Register once in the host deployment's migration ledger. */
3
3
  export declare function migrateBookingsV1(db: DatabaseInterface): Promise<void>;
4
+ /**
5
+ * Additive priced-offering, quantity and multi-window schema. Register once in
6
+ * the host deployment's migration ledger after `migrateBookingsV1`. Like V1 it
7
+ * is not idempotent, and it never drops data.
8
+ */
9
+ export declare function migrateBookingsV2(db: DatabaseInterface): Promise<void>;
4
10
  //# sourceMappingURL=migration.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"migration.d.ts","sourceRoot":"","sources":["../src/migration.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAsF5D,0FAA0F;AAC1F,wBAAsB,iBAAiB,CAAC,EAAE,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAM5E"}
1
+ {"version":3,"file":"migration.d.ts","sourceRoot":"","sources":["../src/migration.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAsF5D,0FAA0F;AAC1F,wBAAsB,iBAAiB,CAAC,EAAE,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAM5E;AAgDD;;;;GAIG;AACH,wBAAsB,iBAAiB,CAAC,EAAE,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAM5E"}
package/dist/service.d.ts CHANGED
@@ -1,14 +1,10 @@
1
1
  import type { DatabaseInterface } from '@happyvertical/sql';
2
- import type { AvailabilityInput, BookingAuthority, BookingContext, BookingHistoryEvent, BookingKind, BookingOffering, BookingPage, BookingPageOptions, BookingProposal, BookingReceipt, BookingView, ListBookingsOptions, ProposeBookingInput, ProviderAvailability, RequestBookingInput } from './types.js';
2
+ import type { AvailabilityInput, BookingAuthority, BookingContext, BookingHistoryEvent, BookingOffering, BookingPage, BookingPageOptions, BookingProposal, BookingReceipt, BookingView, ConfigureOfferingInput, ListBookingsOptions, ListOpenSlotsOptions, OpenSlot, ProposeBookingInput, ProviderAvailability, RequestBookingInput } from './types.js';
3
3
  /** Transactional PostgreSQL service. The application supplies trusted identity and authorization. */
4
4
  export declare class BookingService {
5
5
  #private;
6
6
  constructor(db: DatabaseInterface, authority: BookingAuthority);
7
- configureOffering(context: BookingContext, key: string, input: {
8
- offeringId: string;
9
- kind: BookingKind;
10
- enabled: boolean;
11
- }): Promise<BookingReceipt>;
7
+ configureOffering(context: BookingContext, key: string, input: ConfigureOfferingInput): Promise<BookingReceipt>;
12
8
  declareAvailability(context: BookingContext, key: string, input: AvailabilityInput): Promise<BookingReceipt>;
13
9
  request(context: BookingContext, key: string, input: RequestBookingInput): Promise<BookingReceipt>;
14
10
  propose(context: BookingContext, key: string, input: ProposeBookingInput): Promise<BookingReceipt>;
@@ -20,6 +16,11 @@ export declare class BookingService {
20
16
  bookingId: string;
21
17
  proposalId: string;
22
18
  }): Promise<BookingReceipt>;
19
+ /** Operator command: bind a lead-bound booking to a project once one exists. */
20
+ attachProject(context: BookingContext, key: string, input: {
21
+ bookingId: string;
22
+ projectId: string;
23
+ }): Promise<BookingReceipt>;
23
24
  decline(context: BookingContext, key: string, input: {
24
25
  bookingId: string;
25
26
  reason: string;
@@ -31,8 +32,18 @@ export declare class BookingService {
31
32
  get(context: BookingContext, bookingId: string): Promise<BookingView>;
32
33
  /** Lists the tenant's bookings, restricted to the current contact for nonoperators. */
33
34
  listBookings(context: BookingContext, options?: ListBookingsOptions): Promise<BookingPage<BookingView>>;
34
- /** Lists configured offerings. Contacts only see enabled offerings. */
35
+ /**
36
+ * Lists configured offerings. Operators see all, contacts see enabled
37
+ * offerings, and a reader with no contact mapping sees enabled public ones.
38
+ */
35
39
  listOfferings(context: BookingContext, options?: BookingPageOptions): Promise<BookingPage<BookingOffering>>;
40
+ /**
41
+ * Lists candidate open slots for an offering: a pure read that reserves nothing
42
+ * and takes no retry key. Confirmation re-checks capacity under the provider lock.
43
+ * Offerings are discoverable by operators (all), contact-mapped readers (enabled)
44
+ * and contact-less readers (enabled and public); the host decides who may read.
45
+ */
46
+ listOpenSlots(context: BookingContext, options: ListOpenSlotsOptions): Promise<BookingPage<OpenSlot>>;
36
47
  /** Lists provider schedules so operators can inspect or replace existing windows. */
37
48
  listAvailability(context: BookingContext, options?: BookingPageOptions): Promise<BookingPage<ProviderAvailability>>;
38
49
  getProposal(context: BookingContext, bookingId: string, proposalId: string): Promise<BookingProposal>;
@@ -1 +1 @@
1
- {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"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"}
@@ -3,14 +3,14 @@
3
3
  "sensitiveFieldsExcluded": true,
4
4
  "generatedAt": "1970-01-01T00:00:00.000Z",
5
5
  "packageName": "@happyvertical/smrt-bookings",
6
- "packageVersion": "0.60.7",
6
+ "packageVersion": "0.60.9",
7
7
  "sourceManifestPath": "dist/manifest.json",
8
8
  "agentDocPath": "AGENTS.md",
9
9
  "sourceHashes": {
10
- "manifest": "bfe75b09d7dec06254512e9298270dc6feb5babd92d2515911641bf214056891",
11
- "packageJson": "a45c97d6de159c4a75dffbbdfa6714fbd8de46a9bc4d9df15d1d742b06ee880f",
12
- "agents": "01210ae016391e945e8ceac798120ebd5a8deeb807fe7c5cac6f5fc824e0ce02",
13
- "moduleDoc:README.md": "b054330ea8f35f34ebfea29bfc8295c82616d798c00b8d97273da349bc431931"
10
+ "manifest": "a5cc6149692b84da3dbe702d152783a155501d859b7409f3d9ed8674ec72c827",
11
+ "packageJson": "df3d0247cadb2f06f45c451e03472f194e753431b06fa17b0a10fea014dc2e11",
12
+ "agents": "3a1b77d2da4a9788969702cfc6615e308c02a0c37a8ac9eaf09e24e736d51d08",
13
+ "moduleDoc:README.md": "31b7f6dcb740ca0f9269db7f7e06e1233e2e551e2e921a26560ffbc2e9ca0caa"
14
14
  },
15
15
  "exports": [
16
16
  "."
@@ -46,12 +46,12 @@
46
46
  "polymorphicAssociations": 0,
47
47
  "uuidColumns": 0
48
48
  },
49
- "agentDoc": "# SMRT Bookings\n\nThis package owns service reservations and capacity. Application hosts supply\ntrusted actor and tenant context and verify their own customer, provider, project\nand plan relationships. Commerce and payment state are separate.\n\nUse service commands for writes; keep this package out of generated CRUD. Apply\nits explicit PostgreSQL migration during deployment, outside request handling.\nSee [README.md](README.md) for the transaction and verification contracts.\n",
49
+ "agentDoc": "# SMRT Bookings\n\nThis package owns service reservations and capacity. Application hosts supply\ntrusted actor and tenant context and verify their own customer, provider, project\nand plan relationships. Commerce and payment state are separate.\n\nUse service commands for writes; keep this package out of generated CRUD. Apply\nits explicit PostgreSQL migration during deployment, outside request handling.\nSee [README.md](README.md) for the transaction and verification contracts.\n\nV2 is additive: a proposal's `windows` array is the reservation and the\nallocation stores only its span.\n",
50
50
  "moduleDocs": [
51
51
  {
52
52
  "path": "README.md",
53
53
  "module": "README",
54
- "content": "# s-m-r-t Bookings\n\n`@happyvertical/smrt-bookings` reserves one elapsed developer hour or one\noperator-declared local supervision window. It does not start agents, collect\npayment, send notifications, or write Commerce records.\n\nApply `migrateBookingsV1` once through the host's deployment migration ledger\nusing a PostgreSQL database handle. Use an ordinary PostgreSQL connection or\npool for `BookingService`; the service owns its transaction and explicitly uses\nREAD COMMITTED. It does not create schema on demand. No ORM collection, CRUD\nroute, or generated CLI or MCP write is registered.\n\n1. An operator enables an offering and declares availability for a canonical\n provider UUID. Use the same provider identity for all services that reserve\n the same person. A declaration replaces that provider's configured windows.\n Windows must not overlap and must have positive integer capacity. Mark at\n most one exact supervision window per provider per local date. Changes that\n invalidate any active reservation are refused.\n2. The named customer contact, or an operator acting for them, requests an\n enabled offering. A supervision request requires a project. Disabling an\n offering stops new requests; operators may finish pending requests.\n3. An operator proposes a provider, window, and terms revision with the exact\n agreed wording. Supervision also needs an approved project plan ID and\n version. Each proposal is immutable. The contact assents to its exact ID.\n Replacing the proposal clears current assent. Requests, proposals, and\n assent reserve no capacity.\n4. An operator confirms the exact assented proposal. The service locks the\n booking and then the stable provider row, checks authority and current\n declared availability, and checks every half-open segment. An exclusive\n hour blocks all overlaps; supervision consumes one unit. Two active\n supervision reservations for the same project and exact UTC interval are\n refused even on different providers. An unsuccessful confirmation leaves\n the proposal and assent available for a retry or revision.\n5. An operator can decline a pending request or cancel a pending or confirmed\n booking with a reason. Cancelling a confirmed booking releases capacity\n in the same transaction; it preserves the confirmed terms and history.\n Booking and historical proposal reads are scoped to the named contact or\n an operator. Booking audit history is operator-only.\n\nWindows use an IANA zone and local timestamps of the form\n`2027-11-07T01:30-04:00`. For example, use that offset with America/New_York. The offset must be valid for that instant in the zone.\nDuring a DST fold the offsets identify different instants; a nonexistent wall\ntime is rejected. Stored proposals retain the local input and UTC instants.\nIntervals include their start and exclude their end. A supervision window\nstarts and ends on one local date, or ends at midnight opening the next date;\nit need not cover the entire day and may span a daylight-saving transition.\n\nUse `listBookings` to find the same booking views returned by `get`. An\noptional project UUID narrows the results within the caller's existing scope.\nOperators see all bookings in their tenant. Other callers see only bookings\nfor the contact the host maps to their actor. `listOfferings` exposes the\noffering ID, kind and enabled flag. Contacts only see enabled offerings.\nOperators can use `listAvailability` to inspect existing provider windows\nbefore replacing a declaration. All three methods use the `read` authority.\n\nLists return up to 50 items by default and accept a limit from 1 to 100.\nResults are ordered by ascending UUID. Pass the returned `nextCursor` as\n`afterId` with the same filters to fetch another page. A null cursor means\nthe page reached the end of the results.\n\nUse an authenticated tenant/actor context from the host, never from a request\nbody. The host's `BookingAuthority` supplies current operator permission and\nthe actor's named contact mapping, and must verify that external references\nexist in that tenant and relate to each other. For a supervision proposal and\nagain at confirmation, it must verify that the exact plan version is approved\nfor the project. The confirmation check occurs after the provider lock; it is\nevidence of approval at that point, not a distributed lock on a plan system.\nAuthority callbacks should be read-only and must not call a booking command.\n\nEach write takes a retry key scoped to tenant and operation. A committed key\nis bound to its actor and normalized command payload. An identical authorized\nretry returns the original receipt; a different payload or actor is rejected.\nA failed command rolls back both its changes and the key claim. Command receipts\nand audit history commit with the booking, so a failure in caller-owned\ncommunication does not undo the reservation. Clients can fetch it afterward\nand coordinate manual invoicing separately.\n\nOnly real PostgreSQL is supported for writes. To run the package unit tests,\nuse `pnpm --filter @happyvertical/smrt-bookings test`. To run the separate\nPostgreSQL suite, point `CI_POSTGRES_BASE_URL` at an isolated server with\ndatabase creation privileges and run `pnpm --filter @happyvertical/smrt-bookings\ntest:postgres`. The test wrapper provisions and removes a disposable database.\n"
54
+ "content": "# s-m-r-t Bookings\n\n`@happyvertical/smrt-bookings` reserves dedicated developer time (an hour, day,\nweek or month) or one operator-declared local supervision window. It does not\nstart agents, collect payment, send notifications, or write Commerce records.\n\nApply `migrateBookingsV1` and then `migrateBookingsV2` once each through the\nhost's deployment migration ledger using a PostgreSQL database handle. V2 is\nadditive: it adds offering units, prices and display fields, booking quantity,\nand per-proposal windows and frozen prices, backfilling existing rows without\ndropping data. Like V1 it is not idempotent. Use an ordinary PostgreSQL connection or\npool for `BookingService`; the service owns its transaction and explicitly uses\nREAD COMMITTED. It does not create schema on demand. No ORM collection, CRUD\nroute, or generated CLI or MCP write is registered.\n\n1. An operator enables an offering and declares availability for a canonical\n provider UUID. Use the same provider identity for all services that reserve\n the same person. A declaration replaces that provider's configured windows.\n Windows must not overlap and must have positive integer capacity. Mark at\n most one exact supervision window per provider per local date. Changes that\n invalidate any active reservation are refused.\n2. The named customer contact, or an operator acting for them, requests an\n enabled offering. A supervision request requires a project. Disabling an\n offering stops new requests; operators may finish pending requests.\n3. An operator proposes a provider, window, and terms revision with the exact\n agreed wording. Supervision also needs an approved project plan ID and\n version. Each proposal is immutable. The contact assents to its exact ID.\n Replacing the proposal clears current assent. Requests, proposals, and\n assent reserve no capacity.\n4. An operator confirms the exact assented proposal. The service locks the\n booking and then the stable provider row, checks authority and current\n declared availability, and checks every half-open segment. An exclusive\n hour blocks all overlaps; supervision consumes one unit. Two active\n supervision reservations for the same project and exact UTC interval are\n refused even on different providers. An unsuccessful confirmation leaves\n the proposal and assent available for a retry or revision.\n5. An operator can decline a pending request or cancel a pending or confirmed\n booking with a reason. Cancelling a confirmed booking releases capacity\n in the same transaction; it preserves the confirmed terms and history.\n Booking and historical proposal reads are scoped to the named contact or\n an operator. Booking audit history is operator-only.\n\n## Offerings, units and prices\n\nAn offering has a `kind` and a `unit` (`hour`, `day`, `week` or `month`). The\nkind is the reservation semantics and the unit is the sales granularity; both\nare frozen once the offering exists. `developer-hour` keeps its name for schema\ncompatibility but means dedicated, exclusive attention for any unit: it blocks\nevery overlapping reservation and needs no project or approved plan.\n`supervision-day` is shared supervision capacity, always a one-day unit with\nquantity one.\n\nAn offering may carry `priceCents` (integer minor units) and an ISO 4217\n`currency`, set together or both null for an unpriced offering. Prices, `title`\n(120 characters), `description` (2000 characters) and `public` may be changed\nlater. A request has a `quantity` (default 1, at most 1000). A proposal freezes\nthe offering's `unitPriceCents` and `currency` and the `totalCents`\n(`unitPriceCents * quantity`) at proposal time, so later price changes do not\nalter an offered or confirmed booking. Payment and invoicing remain host-owned.\n\nA dedicated proposal gives either `window` (the v1 single window) or `windows`,\nsorted, non-overlapping segments in one zone. The proposal `window` is the span\nfrom the first start to the last end and `windows` are the reserved segments;\nconfirmation checks and the allocation reserves every segment. The segments must\nmeasure exactly the quantity of the unit:\n\n- `hour`: the segments' elapsed UTC time sums to `quantity` hours, so a split\n hour is fine and a DST fold counts as elapsed time.\n- `day`: every segment lies in the local span of `quantity` days from the first\n segment's date and the segments touch exactly `quantity` distinct days.\n- `week` and `month`: every segment lies in the half-open local span\n `[D0, D0 + quantity weeks or months)`, with month ends constrained to the\n target month's length. A segment may end at `T00:00` of the day after the span.\n\nWindows use an IANA zone and local timestamps of the form\n`2027-11-07T01:30-04:00`. For example, use that offset with America/New_York. The offset must be valid for that instant in the zone.\nDuring a DST fold the offsets identify different instants; a nonexistent wall\ntime is rejected. Stored proposals retain the local input and UTC instants.\nIntervals include their start and exclude their end. A supervision window\nstarts and ends on one local date, or ends at midnight opening the next date;\nit need not cover the entire day and may span a daylight-saving transition.\n\nUse `listBookings` to find the same booking views returned by `get`. An\noptional project UUID narrows the results within the caller's existing scope.\nOperators see all bookings in their tenant. Other callers see only bookings\nfor the contact the host maps to their actor. `listOfferings` exposes the\noffering ID, kind, unit, price, title, description, `public` and enabled flag.\nThere are three reader classes: operators see every offering, readers mapped to a\ncontact see enabled offerings, and a reader with no contact mapping (an anonymous\nor prospect actor the host vouches for) sees only enabled public offerings.\n`public` only affects discovery; `request` still needs a contact mapping.\n`listBookings` also accepts a `contactId` filter: operators may narrow to any\ncontact and other callers may only name their own.\nOperators can use `listAvailability` to inspect existing provider windows\nbefore replacing a declaration. All three methods use the `read` authority.\n\n`listOpenSlots` returns candidate slots for one offering between `from` and `to`\n(at most 400 days apart), optionally for one provider and a `quantity`. It is a\npure read: it reserves nothing and takes no retry key, and confirmation still\nre-checks capacity under the provider lock. Each slot carries the `windows` to\npass to `propose`. Hourly offerings step by the hour inside each declared\nwindow; day, week and month offerings bundle the declared windows that measure\nthe quantity from each start date; supervision offerings list each declared\nsupervision window with its remaining shared units. Occupied segments are\nexcluded. Results are ordered by first start then provider and paged with the\nopaque `nextCursor`, passed back as `after`. The host decides who may read via\n`access(..., 'read')`; offerings follow the same three reader classes as\n`listOfferings`, and an offering the reader cannot discover is `not-found`.\n\nLists return up to 50 items by default and accept a limit from 1 to 100.\nResults are ordered by ascending UUID. Pass the returned `nextCursor` as\n`afterId` with the same filters to fetch another page. A null cursor means\nthe page reached the end of the results.\n\n## Lead-bound requests\n\nA dedicated booking may exist before the contact has a project. When the host's\nauthority implements the optional `verifyContact(context, contactId)`, a request\nwith no project calls it to confirm that the contact exists in the tenant and the\nactor may act for it, and `verifyReferences` never receives that contact. The\nsame applies to propose, assent and confirm for such a booking, where\n`verifyReferences` still checks the provider. Without `verifyContact` the contact\nis passed to `verifyReferences` as before. Supervision always requires a project.\nAn operator later binds the project with\n`attachProject(context, key, { bookingId, projectId })`: it needs an active\nbooking, verifies the contact and project together, is idempotent for the same\nproject, refuses a different one with `conflict`, and is recorded in history as\n`attach-project`. Dedicated allocations never carry a project, so attaching does\nnot touch reserved capacity.\n\nUse an authenticated tenant/actor context from the host, never from a request\nbody. The host's `BookingAuthority` supplies current operator permission and\nthe actor's named contact mapping, and must verify that external references\nexist in that tenant and relate to each other. For a supervision proposal and\nagain at confirmation, it must verify that the exact plan version is approved\nfor the project. The confirmation check occurs after the provider lock; it is\nevidence of approval at that point, not a distributed lock on a plan system.\nAuthority callbacks should be read-only and must not call a booking command.\n\nEach write takes a retry key scoped to tenant and operation. A committed key\nis bound to its actor and normalized command payload. An identical authorized\nretry returns the original receipt; a different payload or actor is rejected.\nA failed command rolls back both its changes and the key claim. Command receipts\nand audit history commit with the booking, so a failure in caller-owned\ncommunication does not undo the reservation. Clients can fetch it afterward\nand coordinate manual invoicing separately.\n\nOnly real PostgreSQL is supported for writes. To run the package unit tests,\nuse `pnpm --filter @happyvertical/smrt-bookings test`. To run the separate\nPostgreSQL suite, point `CI_POSTGRES_BASE_URL` at an isolated server with\ndatabase creation privileges and run `pnpm --filter @happyvertical/smrt-bookings\ntest:postgres`. The test wrapper provisions and removes a disposable database.\n"
55
55
  }
56
56
  ]
57
57
  }