@altertable/data-app 0.59.1 → 0.62.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/AGENTS.md +5 -3
  2. package/CONTRIBUTING.md +16 -4
  3. package/README.md +20 -14
  4. package/dist/chunks/{contract-jksbmt5q.js → contract-3bnrf5pf.js} +27 -5
  5. package/dist/chunks/contract-3bnrf5pf.js.map +10 -0
  6. package/dist/chunks/{contract-tkkc28tg.js → contract-4vrw9zk9.js} +43 -13
  7. package/dist/chunks/contract-4vrw9zk9.js.map +12 -0
  8. package/dist/chunks/{contract-14vxdcrs.js → contract-farfe948.js} +17 -21
  9. package/dist/chunks/contract-farfe948.js.map +10 -0
  10. package/dist/chunks/contract-nt819swq.js.map +1 -1
  11. package/dist/chunks/{contract-tqrf3ykr.js → contract-rrm7s5zp.js} +61 -32
  12. package/dist/chunks/contract-rrm7s5zp.js.map +12 -0
  13. package/dist/chunks/{contract-mb5nfzwg.js → contract-xjv197ck.js} +178 -31
  14. package/dist/chunks/contract-xjv197ck.js.map +13 -0
  15. package/dist/client/index.js +6 -4
  16. package/dist/client/index.js.map +1 -1
  17. package/dist/core/appearance.js +1 -1
  18. package/dist/core/contract.js +5 -3
  19. package/dist/core/contract.js.map +1 -1
  20. package/dist/embed/index.js +42 -8
  21. package/dist/embed/index.js.map +5 -4
  22. package/dist/local.js +191 -79
  23. package/dist/local.js.map +8 -7
  24. package/dist/react/embed/index.js +84 -99
  25. package/dist/react/embed/index.js.map +4 -5
  26. package/dist/react/index.css +156 -6
  27. package/dist/react/index.js +313 -152
  28. package/dist/react/index.js.map +17 -13
  29. package/dist/server.js +172 -76
  30. package/dist/server.js.map +7 -6
  31. package/dist/types/client/iframe.d.ts +14 -0
  32. package/dist/types/client/index.d.ts +19 -13
  33. package/dist/types/core/appearance.d.ts +7 -6
  34. package/dist/types/core/contract.d.ts +3 -3
  35. package/dist/types/core/messages.d.ts +9 -3
  36. package/dist/types/core/operation.d.ts +27 -0
  37. package/dist/types/core/presentation.d.ts +7 -0
  38. package/dist/types/embed/bridge.d.ts +12 -0
  39. package/dist/types/embed/host.d.ts +10 -3
  40. package/dist/types/embed/index.d.ts +7 -4
  41. package/dist/types/embed/{shell.d.ts → source.d.ts} +5 -4
  42. package/dist/types/embed/sql.d.ts +4 -0
  43. package/dist/types/react/embed/bridge.d.ts +19 -10
  44. package/dist/types/react/embed/index.d.ts +0 -2
  45. package/dist/types/react/ui/DataAppSkeleton.d.ts +8 -0
  46. package/dist/types/react/ui/SearchInput.d.ts +1 -1
  47. package/dist/types/react/ui/index.d.ts +2 -0
  48. package/dist/types/react/ui/useAppAppearance.d.ts +3 -0
  49. package/dist/types/react/ui/useDataAppPresentation.d.ts +3 -0
  50. package/dist/types/react/view-controls.d.ts +1 -1
  51. package/dist/{bootstrap.js → worker.js} +161 -29
  52. package/docs/app-authoring.md +24 -4
  53. package/docs/appearance.md +21 -8
  54. package/docs/client.md +40 -3
  55. package/docs/contract.md +9 -7
  56. package/docs/embed.md +114 -19
  57. package/docs/react-embed.md +84 -24
  58. package/docs/react.md +51 -0
  59. package/docs/releasing.md +5 -0
  60. package/docs/server-bun.md +5 -0
  61. package/docs/starter-agent-instructions.md +4 -2
  62. package/docs/worker.md +57 -0
  63. package/package.json +9 -5
  64. package/dist/chunks/contract-14vxdcrs.js.map +0 -10
  65. package/dist/chunks/contract-jksbmt5q.js.map +0 -10
  66. package/dist/chunks/contract-mb5nfzwg.js.map +0 -12
  67. package/dist/chunks/contract-tkkc28tg.js.map +0 -12
  68. package/dist/chunks/contract-tqrf3ykr.js.map +0 -11
  69. package/dist/types/embed/standalone.d.ts +0 -1
  70. package/dist/types/react/embed/shell.d.ts +0 -10
  71. package/docs/bootstrap.md +0 -76
@@ -2,7 +2,7 @@
2
2
  "version": 3,
3
3
  "sources": ["../src/core/contract.ts", "../src/core/dimension.ts"],
4
4
  "sourcesContent": [
5
- "/**\n * Shared operation contracts and input/output validation for browser and server.\n * @module @altertable/data-app/contract\n * @see https://github.com/altertable-ai/data-app/blob/main/docs/contract.md\n */\nimport { parseDate, today } from '@internationalized/date';\nimport { formatDateRange } from '@/src/core/format';\nimport { invariant } from '@/src/core/invariant';\n\nexport type QueryResult = {\n columns: { name: string; type?: string }[];\n rows: unknown[][];\n queryId?: string;\n};\n\nexport function parseEmptyInput(value: unknown): Record<string, never> {\n invariant(\n typeof value === 'object' &&\n value !== null &&\n !Array.isArray(value) &&\n Object.keys(value).length === 0,\n 'This operation takes no inputs.'\n );\n\n return {};\n}\n\nexport function parseTrue(value: unknown): true {\n invariant(value === true, 'Expected a successful check.');\n\n return true;\n}\n\nexport type DateRangeInput = { start: string; end: string };\nexport type DateRangeRequest = {\n range: DateRangeInput;\n comparison: DateRangeInput | null;\n};\n\n/** The immediately preceding, equally long set of calendar days. */\nexport function previousDateRange({\n start,\n end,\n}: DateRangeInput): DateRangeInput {\n const first = parseDate(start);\n const last = parseDate(end);\n const days =\n (last.toDate('UTC').getTime() - first.toDate('UTC').getTime()) /\n 86_400_000 +\n 1;\n invariant(days >= 1, 'Date range must start before it ends.');\n const previousEnd = first.subtract({ days: 1 });\n\n return {\n start: previousEnd.subtract({ days: days - 1 }).toString(),\n end: previousEnd.toString(),\n };\n}\n\nexport type ReportingPeriod =\n | { kind: 'rolling'; amount: number; unit: 'hour' | 'day'; end: string }\n | { kind: 'calendar'; start: string; end: string; timeZone: string };\n\nexport type DateRangeContractOptions = {\n minDate?: string;\n maxDate?: string | (() => string);\n maxRangeDays: number;\n timeZone: string;\n completeDays?: boolean;\n};\n\n/** One source policy for the server parser, URL variable, and displayed reporting period. */\nexport function defineDateRangeContract(options: DateRangeContractOptions) {\n invariant(\n Number.isInteger(options.maxRangeDays) && options.maxRangeDays > 0,\n 'Date range needs a positive maxRangeDays.'\n );\n if (options.minDate) parseDate(options.minDate);\n if (typeof options.maxDate === 'string') parseDate(options.maxDate);\n today(options.timeZone);\n\n function bounds() {\n const latest = today(options.timeZone).subtract({\n days: options.completeDays === false ? 0 : 1,\n });\n const sourceMax =\n typeof options.maxDate === 'function'\n ? options.maxDate()\n : options.maxDate;\n const maxDate =\n sourceMax && parseDate(sourceMax).compare(latest) < 0\n ? parseDate(sourceMax).toString()\n : latest.toString();\n\n return {\n minDate: options.minDate,\n maxDate,\n maxRangeDays: options.maxRangeDays,\n timeZone: options.timeZone,\n };\n }\n\n function period({ start, end }: DateRangeInput): ReportingPeriod {\n return {\n kind: 'calendar',\n start,\n end,\n timeZone: options.timeZone,\n };\n }\n\n function describeInput(input: DateRangeInput) {\n return `${formatDateRange(input)} ${options.timeZone}`;\n }\n\n function comparison(input: DateRangeInput): DateRangeInput | null {\n const previous = previousDateRange(input);\n\n return options.minDate && previous.start < options.minDate\n ? null\n : previous;\n }\n\n function request(value: unknown, compare = false): DateRangeRequest {\n const range = parseDateRangeInput(value, bounds());\n const previous = compare ? comparison(range) : null;\n invariant(\n !compare || previous,\n 'Comparison is outside available source coverage.'\n );\n\n return { range, comparison: previous };\n }\n\n function parseRequest(value: unknown): DateRangeRequest {\n invariant(value && typeof value === 'object', 'Choose a date range.');\n const input = value as Record<string, unknown>;\n const parsed = request(input.range, input.comparison != null);\n if (input.comparison != null) {\n const supplied = parseDateRangeInput(input.comparison, bounds());\n invariant(\n supplied.start === parsed.comparison?.start &&\n supplied.end === parsed.comparison?.end,\n 'Comparison must be the preceding equal-length range.'\n );\n }\n\n return parsed;\n }\n\n return {\n request,\n parseRequest,\n bounds,\n parse(value: unknown) {\n return parseDateRangeInput(value, bounds());\n },\n period,\n comparison,\n describeInput,\n view: { period, describeInput },\n };\n}\n\nexport type DateRangeContract = ReturnType<typeof defineDateRangeContract>;\n\n/** Validate calendar dates and a bounded inclusive range on the server. */\nexport function parseDateRangeInput(\n value: unknown,\n {\n minDate,\n maxDate,\n maxRangeDays,\n }: { minDate?: string; maxDate?: string; maxRangeDays: number }\n): DateRangeInput {\n invariant(\n value && typeof value === 'object' && !Array.isArray(value),\n 'Choose a date range.'\n );\n const { start, end } = value as Record<string, unknown>;\n\n function day(input: unknown): number {\n invariant(\n typeof input === 'string' && /^\\d{4}-\\d{2}-\\d{2}$/.test(input),\n 'Dates must be YYYY-MM-DD.'\n );\n const timestamp = Date.parse(`${input}T00:00:00Z`);\n invariant(\n Number.isFinite(timestamp) &&\n new Date(timestamp).toISOString().slice(0, 10) === input,\n 'Choose valid calendar dates.'\n );\n\n return timestamp;\n }\n const first = day(start);\n const last = day(end);\n invariant(\n Number.isInteger(maxRangeDays) && maxRangeDays > 0,\n 'Maximum range days must be positive.'\n );\n invariant(\n first <= last &&\n (last - first) / 86_400_000 < maxRangeDays &&\n (minDate === undefined || first >= day(minDate)) &&\n (maxDate === undefined || last <= day(maxDate)),\n `Choose up to ${maxRangeDays} days within the available range.`\n );\n\n return { start: start as string, end: end as string };\n}\n\n/** Accepts numeric strings from DuckDB; rejects negative, fractional, and unsafe integers. */\nexport function parseCount(value: unknown): number {\n const number =\n typeof value === 'number' || typeof value === 'string'\n ? Number(value)\n : NaN;\n invariant(\n Number.isSafeInteger(number) &&\n number >= 0 &&\n (typeof value !== 'string' || !!value.trim()),\n 'Invalid count in query result.'\n );\n\n return number;\n}\n\nexport function parseLabel(value: unknown, maxLength = 100): string {\n invariant(\n typeof value === 'string' && !!value && value.length <= maxLength,\n 'Invalid label in query result.'\n );\n\n return value;\n}\n\n/** Rejects missing or duplicate columns and rows whose width differs from the column list. */\nexport function rowsAsRecords(\n result: QueryResult,\n requiredColumns: readonly string[]\n): Record<string, unknown>[] {\n const names = result.columns.map(column => column.name);\n invariant(\n new Set(names).size === names.length &&\n requiredColumns.every(name => names.includes(name)),\n 'Query result columns do not match the expected shape.'\n );\n\n return result.rows.map(row => {\n invariant(\n row.length === names.length,\n 'Query result row has the wrong width.'\n );\n\n return Object.fromEntries(names.map((name, index) => [name, row[index]]));\n });\n}\n\n/** One named statement an operation ran. Returned only when SQL disclosure is allowed. */\nexport type DisclosedQuery = {\n name: string;\n statement: string;\n queryId?: string;\n};\n\nexport function defineQueryNames<const Names extends Record<string, string>>(\n names: Names\n): Names {\n const values = Object.values(names);\n invariant(\n values.every(name => !!name.trim()) &&\n new Set(values).size === values.length,\n 'Query names must be nonempty and unique.'\n );\n\n return names;\n}\n\nexport class DataSourceError extends Error {\n queryName?: string;\n\n constructor(\n public readonly reason:\n | 'unauthorized'\n | 'forbidden'\n | 'rate_limited'\n | 'query_rejected'\n | 'unavailable',\n public readonly status?: number\n ) {\n super(`Data source ${reason}${status ? ` (${status})` : ''}.`);\n this.name = 'DataSourceError';\n }\n}\n\n/** Server-only query interface supplied by a local or hosted adapter. */\nexport type Lakehouse = {\n queryAll(\n statement: string,\n options: { limit: number; signal: AbortSignal; name?: string }\n ): Promise<QueryResult>;\n};\n\nexport type OperationContext = { lakehouse: Lakehouse; signal: AbortSignal };\n\n/**\n * Both parsers run on the server before results cross the JSON boundary. Schema libraries can be\n * used inside either parser.\n */\nexport type DataOperation<Input, Output> = {\n input: (value: unknown) => Input;\n output: (value: unknown) => Output;\n run: (context: OperationContext, input: Input) => Promise<Output>;\n checks: readonly Input[];\n queryNames?: Readonly<Record<string, string>>;\n policy: {\n maxQueryRows: number;\n maxDurationMs: number;\n maxResponseBytes?: number;\n exposeSql?: boolean;\n };\n};\n\nexport type OperationQuery<Names extends Readonly<Record<string, string>>> = (\n name: Names[keyof Names],\n statement: string,\n options?: { limit?: number }\n) => Promise<QueryResult>;\n\nexport function defineOperation<\n Input,\n Output,\n const Names extends Readonly<Record<string, string>> = Record<string, never>,\n>(\n operation: Omit<DataOperation<Input, Output>, 'run' | 'queryNames'> & {\n queryNames?: Names;\n run: (\n context: OperationContext & { query: OperationQuery<NoInfer<Names>> },\n input: Input\n ) => Promise<Output>;\n }\n): DataOperation<Input, Output> & { queryNames?: Names } {\n invariant(\n Array.isArray(operation.checks) &&\n operation.checks.length > 0 &&\n Number.isInteger(operation.policy.maxQueryRows) &&\n operation.policy.maxQueryRows > 0 &&\n Number.isInteger(operation.policy.maxDurationMs) &&\n operation.policy.maxDurationMs > 0 &&\n (operation.policy.maxResponseBytes === undefined ||\n (Number.isInteger(operation.policy.maxResponseBytes) &&\n operation.policy.maxResponseBytes > 0)),\n 'Each data operation needs check inputs and positive row and duration limits.'\n );\n if (operation.queryNames) defineQueryNames(operation.queryNames);\n for (const input of operation.checks) operation.input(input);\n\n return {\n ...operation,\n run(context, input) {\n function query(\n name: Names[keyof Names],\n statement: string,\n options?: { limit?: number }\n ): Promise<QueryResult> {\n invariant(\n operation.queryNames &&\n Object.values(operation.queryNames).includes(name),\n `Unknown query name: ${name}.`\n );\n\n return context.lakehouse.queryAll(statement, {\n name,\n limit: options?.limit ?? operation.policy.maxQueryRows,\n signal: context.signal,\n });\n }\n\n return operation.run({ ...context, query }, input);\n },\n };\n}\n\nexport const connectionQueryNames = defineQueryNames({\n connection: 'connection-check',\n});\n\n/** Success requires a bounded SQL query; it does not establish access to a particular dataset. */\nexport function connectionCheck(): DataOperation<Record<string, never>, true> {\n return defineOperation({\n input: parseEmptyInput,\n output: parseTrue,\n checks: [{}],\n queryNames: connectionQueryNames,\n policy: { maxQueryRows: 1, maxDurationMs: 15_000, exposeSql: true },\n async run({ query }): Promise<true> {\n await query(\n connectionQueryNames.connection,\n 'SELECT 1 AS connection_check'\n );\n\n return true;\n },\n });\n}\n\nexport type DataOperations = Record<\n string,\n {\n input: (value: unknown) => unknown;\n output: (value: unknown) => unknown;\n run: (context: OperationContext, input: never) => Promise<unknown>;\n checks: readonly unknown[];\n queryNames?: Readonly<Record<string, string>>;\n policy: DataOperation<never, unknown>['policy'];\n }\n>;\nexport {\n dimensionFilter,\n parseDimensionSelection,\n parseFacetOptions,\n dimensionPredicate,\n} from '@/src/core/dimension';\nexport type {\n DimensionSelection,\n DimensionMember,\n DimensionOption,\n DimensionVariable,\n DimensionFilterOptions,\n} from '@/src/core/dimension';\n\nexport * from '@/src/core/messages';\nexport type { TransportResponse } from '@/src/core/bridge';\n",
5
+ "/**\n * Shared operation contracts and input/output validation for browser and server.\n * @module @altertable/data-app/contract\n * @see https://github.com/altertable-ai/data-app/blob/main/docs/contract.md\n */\nimport { parseDate, today } from '@internationalized/date';\nimport { formatDateRange } from '@/src/core/format';\nimport { invariant } from '@/src/core/invariant';\n\nexport type QueryResult = {\n columns: { name: string; type?: string }[];\n rows: unknown[][];\n queryId?: string;\n};\n\nexport function parseEmptyInput(value: unknown): Record<string, never> {\n invariant(\n typeof value === 'object' &&\n value !== null &&\n !Array.isArray(value) &&\n Object.keys(value).length === 0,\n 'This operation takes no inputs.'\n );\n\n return {};\n}\n\nexport function parseTrue(value: unknown): true {\n invariant(value === true, 'Expected a successful check.');\n\n return true;\n}\n\nexport type DateRangeInput = { start: string; end: string };\nexport type DateRangeRequest = {\n range: DateRangeInput;\n comparison: DateRangeInput | null;\n};\n\n/** The immediately preceding, equally long set of calendar days. */\nexport function previousDateRange({\n start,\n end,\n}: DateRangeInput): DateRangeInput {\n const first = parseDate(start);\n const last = parseDate(end);\n const days =\n (last.toDate('UTC').getTime() - first.toDate('UTC').getTime()) /\n 86_400_000 +\n 1;\n invariant(days >= 1, 'Date range must start before it ends.');\n const previousEnd = first.subtract({ days: 1 });\n\n return {\n start: previousEnd.subtract({ days: days - 1 }).toString(),\n end: previousEnd.toString(),\n };\n}\n\nexport type ReportingPeriod =\n | { kind: 'rolling'; amount: number; unit: 'hour' | 'day'; end: string }\n | { kind: 'calendar'; start: string; end: string; timeZone: string };\n\nexport type DateRangeContractOptions = {\n minDate?: string;\n maxDate?: string | (() => string);\n maxRangeDays: number;\n timeZone: string;\n completeDays?: boolean;\n};\n\n/** One source policy for the server parser, URL variable, and displayed reporting period. */\nexport function defineDateRangeContract(options: DateRangeContractOptions) {\n invariant(\n Number.isInteger(options.maxRangeDays) && options.maxRangeDays > 0,\n 'Date range needs a positive maxRangeDays.'\n );\n if (options.minDate) parseDate(options.minDate);\n if (typeof options.maxDate === 'string') parseDate(options.maxDate);\n today(options.timeZone);\n\n function bounds() {\n const latest = today(options.timeZone).subtract({\n days: options.completeDays === false ? 0 : 1,\n });\n const sourceMax =\n typeof options.maxDate === 'function'\n ? options.maxDate()\n : options.maxDate;\n const maxDate =\n sourceMax && parseDate(sourceMax).compare(latest) < 0\n ? parseDate(sourceMax).toString()\n : latest.toString();\n\n return {\n minDate: options.minDate,\n maxDate,\n maxRangeDays: options.maxRangeDays,\n timeZone: options.timeZone,\n };\n }\n\n function period({ start, end }: DateRangeInput): ReportingPeriod {\n return {\n kind: 'calendar',\n start,\n end,\n timeZone: options.timeZone,\n };\n }\n\n function describeInput(input: DateRangeInput) {\n return `${formatDateRange(input)} ${options.timeZone}`;\n }\n\n function comparison(input: DateRangeInput): DateRangeInput | null {\n const previous = previousDateRange(input);\n\n return options.minDate && previous.start < options.minDate\n ? null\n : previous;\n }\n\n function request(value: unknown, compare = false): DateRangeRequest {\n const range = parseDateRangeInput(value, bounds());\n const previous = compare ? comparison(range) : null;\n invariant(\n !compare || previous,\n 'Comparison is outside available source coverage.'\n );\n\n return { range, comparison: previous };\n }\n\n function parseRequest(value: unknown): DateRangeRequest {\n invariant(value && typeof value === 'object', 'Choose a date range.');\n const input = value as Record<string, unknown>;\n const parsed = request(input.range, input.comparison != null);\n if (input.comparison != null) {\n const supplied = parseDateRangeInput(input.comparison, bounds());\n invariant(\n supplied.start === parsed.comparison?.start &&\n supplied.end === parsed.comparison?.end,\n 'Comparison must be the preceding equal-length range.'\n );\n }\n\n return parsed;\n }\n\n return {\n request,\n parseRequest,\n bounds,\n parse(value: unknown) {\n return parseDateRangeInput(value, bounds());\n },\n period,\n comparison,\n describeInput,\n view: { period, describeInput },\n };\n}\n\nexport type DateRangeContract = ReturnType<typeof defineDateRangeContract>;\n\n/** Validate calendar dates and a bounded inclusive range on the server. */\nexport function parseDateRangeInput(\n value: unknown,\n {\n minDate,\n maxDate,\n maxRangeDays,\n }: { minDate?: string; maxDate?: string; maxRangeDays: number }\n): DateRangeInput {\n invariant(\n value && typeof value === 'object' && !Array.isArray(value),\n 'Choose a date range.'\n );\n const { start, end } = value as Record<string, unknown>;\n\n function day(input: unknown): number {\n invariant(\n typeof input === 'string' && /^\\d{4}-\\d{2}-\\d{2}$/.test(input),\n 'Dates must be YYYY-MM-DD.'\n );\n const timestamp = Date.parse(`${input}T00:00:00Z`);\n invariant(\n Number.isFinite(timestamp) &&\n new Date(timestamp).toISOString().slice(0, 10) === input,\n 'Choose valid calendar dates.'\n );\n\n return timestamp;\n }\n const first = day(start);\n const last = day(end);\n invariant(\n Number.isInteger(maxRangeDays) && maxRangeDays > 0,\n 'Maximum range days must be positive.'\n );\n invariant(\n first <= last &&\n (last - first) / 86_400_000 < maxRangeDays &&\n (minDate === undefined || first >= day(minDate)) &&\n (maxDate === undefined || last <= day(maxDate)),\n `Choose up to ${maxRangeDays} days within the available range.`\n );\n\n return { start: start as string, end: end as string };\n}\n\n/** Accepts numeric strings from DuckDB; rejects negative, fractional, and unsafe integers. */\nexport function parseCount(value: unknown): number {\n const number =\n typeof value === 'number' || typeof value === 'string'\n ? Number(value)\n : NaN;\n invariant(\n Number.isSafeInteger(number) &&\n number >= 0 &&\n (typeof value !== 'string' || !!value.trim()),\n 'Invalid count in query result.'\n );\n\n return number;\n}\n\nexport function parseLabel(value: unknown, maxLength = 100): string {\n invariant(\n typeof value === 'string' && !!value && value.length <= maxLength,\n 'Invalid label in query result.'\n );\n\n return value;\n}\n\n/** Rejects missing or duplicate columns and rows whose width differs from the column list. */\nexport function rowsAsRecords(\n result: QueryResult,\n requiredColumns: readonly string[]\n): Record<string, unknown>[] {\n const names = result.columns.map(column => column.name);\n invariant(\n new Set(names).size === names.length &&\n requiredColumns.every(name => names.includes(name)),\n 'Query result columns do not match the expected shape.'\n );\n\n return result.rows.map(row => {\n invariant(\n row.length === names.length,\n 'Query result row has the wrong width.'\n );\n\n return Object.fromEntries(names.map((name, index) => [name, row[index]]));\n });\n}\n\n/** One named statement an operation ran. Returned only when SQL disclosure is allowed. */\nexport type DisclosedQuery = {\n name: string;\n statement: string;\n queryId?: string;\n};\n\nexport function defineQueryNames<const Names extends Record<string, string>>(\n names: Names\n): Names {\n const values = Object.values(names);\n invariant(\n values.every(name => !!name.trim()) &&\n new Set(values).size === values.length,\n 'Query names must be nonempty and unique.'\n );\n\n return names;\n}\n\nexport class DataSourceError extends Error {\n queryName?: string;\n\n constructor(\n public readonly reason:\n | 'unauthorized'\n | 'forbidden'\n | 'rate_limited'\n | 'query_rejected'\n | 'unavailable',\n public readonly status?: number\n ) {\n super(`Data source ${reason}${status ? ` (${status})` : ''}.`);\n this.name = 'DataSourceError';\n }\n}\n\n/** Query interface supplied by a server adapter or an authorized iframe bridge. */\nexport type Lakehouse = {\n queryAll(\n statement: string,\n options: { limit: number; signal: AbortSignal; name?: string }\n ): Promise<QueryResult>;\n};\n\nexport type OperationContext = { lakehouse: Lakehouse; signal: AbortSignal };\n\n/**\n * Parsers run in the operation executor's runtime: server for HTTP apps, browser for bundle apps.\n * Browser validation does not replace backend authorization or query limits.\n */\nexport type DataOperation<Input, Output> = {\n input: (value: unknown) => Input;\n output: (value: unknown) => Output;\n run: (context: OperationContext, input: Input) => Promise<Output>;\n checks: readonly Input[];\n queryNames?: Readonly<Record<string, string>>;\n policy: {\n maxQueryRows: number;\n maxDurationMs: number;\n maxResponseBytes?: number;\n exposeSql?: boolean;\n };\n};\n\nexport type OperationQuery<Names extends Readonly<Record<string, string>>> = (\n name: Names[keyof Names],\n statement: string,\n options?: { limit?: number }\n) => Promise<QueryResult>;\n\nexport function defineOperation<\n Input,\n Output,\n const Names extends Readonly<Record<string, string>> = Record<string, never>,\n>(\n operation: Omit<DataOperation<Input, Output>, 'run' | 'queryNames'> & {\n queryNames?: Names;\n run: (\n context: OperationContext & { query: OperationQuery<NoInfer<Names>> },\n input: Input\n ) => Promise<Output>;\n }\n): DataOperation<Input, Output> & { queryNames?: Names } {\n invariant(\n Array.isArray(operation.checks) &&\n operation.checks.length > 0 &&\n Number.isInteger(operation.policy.maxQueryRows) &&\n operation.policy.maxQueryRows > 0 &&\n Number.isInteger(operation.policy.maxDurationMs) &&\n operation.policy.maxDurationMs > 0 &&\n (operation.policy.maxResponseBytes === undefined ||\n (Number.isInteger(operation.policy.maxResponseBytes) &&\n operation.policy.maxResponseBytes > 0)),\n 'Each data operation needs check inputs and positive row and duration limits.'\n );\n if (operation.queryNames) defineQueryNames(operation.queryNames);\n for (const input of operation.checks) operation.input(input);\n\n return {\n ...operation,\n run(context, input) {\n function query(\n name: Names[keyof Names],\n statement: string,\n options?: { limit?: number }\n ): Promise<QueryResult> {\n invariant(\n operation.queryNames &&\n Object.values(operation.queryNames).includes(name),\n `Unknown query name: ${name}.`\n );\n\n return context.lakehouse.queryAll(statement, {\n name,\n limit: options?.limit ?? operation.policy.maxQueryRows,\n signal: context.signal,\n });\n }\n\n return operation.run({ ...context, query }, input);\n },\n };\n}\n\nexport const connectionQueryNames = defineQueryNames({\n connection: 'connection-check',\n});\n\n/** Success requires a bounded SQL query; it does not establish access to a particular dataset. */\nexport function connectionCheck(): DataOperation<Record<string, never>, true> {\n return defineOperation({\n input: parseEmptyInput,\n output: parseTrue,\n checks: [{}],\n queryNames: connectionQueryNames,\n policy: { maxQueryRows: 1, maxDurationMs: 15_000, exposeSql: true },\n async run({ query }): Promise<true> {\n await query(\n connectionQueryNames.connection,\n 'SELECT 1 AS connection_check'\n );\n\n return true;\n },\n });\n}\n\nexport type DataOperations = Record<\n string,\n {\n input: (value: unknown) => unknown;\n output: (value: unknown) => unknown;\n run: (context: OperationContext, input: never) => Promise<unknown>;\n checks: readonly unknown[];\n queryNames?: Readonly<Record<string, string>>;\n policy: DataOperation<never, unknown>['policy'];\n }\n>;\nexport {\n dimensionFilter,\n parseDimensionSelection,\n parseFacetOptions,\n dimensionPredicate,\n} from '@/src/core/dimension';\nexport type {\n DimensionSelection,\n DimensionMember,\n DimensionOption,\n DimensionVariable,\n DimensionFilterOptions,\n} from '@/src/core/dimension';\n\nexport * from '@/src/core/messages';\nexport type { TransportResponse } from '@/src/core/bridge';\n",
6
6
  "import type { AppVariable, HistoryMode } from '@/src/core/variables';\nimport { invariant } from '@/src/core/invariant';\n\nexport type DimensionValue = string | number;\nexport type DimensionMember<T extends DimensionValue = DimensionValue> =\n | { kind: 'value'; value: T }\n | { kind: 'missing' };\nexport type DimensionSelection<T extends DimensionValue = DimensionValue> =\n | { kind: 'all' }\n | { kind: 'include'; members: readonly DimensionMember<T>[] };\nexport type DimensionOption<T extends DimensionValue = DimensionValue> = {\n value: T;\n label: string;\n count?: number;\n};\nexport type DimensionVariable<T extends DimensionValue = DimensionValue> = Omit<\n AppVariable<DimensionSelection<T>, 'dimension'>,\n 'options'\n> & {\n label: string;\n selection: 'single' | 'multiple';\n valueType: 'string' | 'number';\n allowMissing: boolean;\n maxSelected: number;\n options: readonly DimensionOption<T>[];\n facet?: {\n operation: string;\n input: (values: Record<string, unknown>) => unknown;\n };\n describe: (selection: DimensionSelection<T>) => string;\n};\n\nconst allSelection: DimensionSelection<never> = { kind: 'all' };\n\nexport function dimensionMemberKey(member: DimensionMember) {\n return member.kind === 'missing'\n ? 'missing'\n : `value:${typeof member.value}:${member.value}`;\n}\n\nexport type DimensionFilterOptions<T extends DimensionValue> = {\n key: string;\n label: string;\n valueType: T extends number ? 'number' : 'string';\n selection: 'single' | 'multiple';\n allowMissing?: boolean;\n maxSelected?: number;\n history?: HistoryMode;\n} & (\n | { options: readonly DimensionOption<T>[]; facet?: never }\n | {\n /** A bounded operation supplies options and counts; input chooses other filters affecting counts. */\n facet: {\n operation: string;\n input: (values: Record<string, unknown>) => unknown;\n };\n options?: never;\n }\n);\n\n/** A bounded categorical filter. URL tags distinguish missing from literal \"null\". */\nexport function dimensionFilter<const T extends DimensionValue>(\n config: DimensionFilterOptions<T>\n): DimensionVariable<T> {\n const { key, label, selection, valueType } = config;\n const options = config.options ?? [];\n invariant(\n !!config.options !== !!config.facet,\n 'Choose fixed options or one facet operation.'\n );\n const optionKeys = options.map(option =>\n dimensionMemberKey({ kind: 'value', value: option.value })\n );\n invariant(\n !!key &&\n options.length <= 200 &&\n new Set(optionKeys).size === options.length,\n 'Dimension options need a key, at most 200 distinct values.'\n );\n invariant(\n options.every(\n option =>\n typeof option.value === valueType &&\n !!option.label.trim() &&\n (option.count === undefined ||\n (Number.isSafeInteger(option.count) && option.count >= 0))\n ),\n 'Dimension options need typed values, labels, and nonnegative counts.'\n );\n const allowed = new Set(optionKeys);\n const maxSelected = config.maxSelected ?? (selection === 'single' ? 1 : 20);\n invariant(\n Number.isInteger(maxSelected) &&\n maxSelected >= 1 &&\n maxSelected <= 50 &&\n (selection !== 'single' || maxSelected === 1),\n 'Dimension selection limit is invalid.'\n );\n const allowMissing = config.allowMissing ?? false;\n const defaultValue = allSelection;\n\n function validMember(member: DimensionMember<T>): boolean {\n if (member.kind === 'missing') return allowMissing;\n if (member.kind !== 'value' || typeof member.value !== valueType)\n return false;\n if (!config.facet) return allowed.has(dimensionMemberKey(member));\n\n return typeof member.value === 'number'\n ? Number.isFinite(member.value)\n : member.value.length <= 100;\n }\n\n function valid(value: DimensionSelection<T>): boolean {\n if (!value || typeof value !== 'object') return false;\n if (value.kind === 'all') return true;\n if (\n value.kind !== 'include' ||\n !Array.isArray(value.members) ||\n !value.members.length ||\n value.members.length > maxSelected\n )\n return false;\n const ids = value.members.map(dimensionMemberKey);\n\n return new Set(ids).size === ids.length && value.members.every(validMember);\n }\n\n function encode(value: DimensionSelection<T>) {\n return value.kind === 'all'\n ? null\n : JSON.stringify(\n value.members.map(member =>\n member.kind === 'missing' ? ['m'] : ['v', member.value]\n )\n );\n }\n\n function parse(raw: string | null): DimensionSelection<T> {\n if (raw === null) return defaultValue;\n if (raw.length > 4096) return defaultValue;\n try {\n const tokens = JSON.parse(raw);\n if (!Array.isArray(tokens)) return defaultValue;\n const value = {\n kind: 'include' as const,\n members: tokens.map(token =>\n Array.isArray(token) && token.length === 1 && token[0] === 'm'\n ? { kind: 'missing' as const }\n : Array.isArray(token) && token.length === 2 && token[0] === 'v'\n ? { kind: 'value' as const, value: token[1] as T }\n : { kind: 'invalid' as const }\n ),\n };\n\n return valid(value as DimensionSelection<T>)\n ? (value as DimensionSelection<T>)\n : defaultValue;\n } catch {\n return defaultValue;\n }\n }\n\n return {\n kind: 'dimension',\n label,\n valueType,\n selection,\n options,\n facet: config.facet,\n allowMissing,\n maxSelected,\n urlKeys: [key],\n defaultValue,\n history: config.history ?? 'push',\n valid,\n same(left, right) {\n return encode(left) === encode(right);\n },\n read(params) {\n return parse(params.get(key));\n },\n write(value) {\n return { [key]: encode(value) };\n },\n describe(value) {\n return value.kind === 'all'\n ? `All ${label.toLocaleLowerCase()}`\n : value.members\n .map(member =>\n member.kind === 'missing'\n ? 'Missing'\n : (options.find(option => option.value === member.value)\n ?.label ?? String(member.value))\n )\n .join(', ');\n },\n };\n}\n\n/** Server-side parser; call from the operation input parser before building SQL. */\nexport function parseDimensionSelection<T extends DimensionValue>(\n value: unknown,\n filter: DimensionVariable<T>\n): DimensionSelection<T> {\n invariant(\n filter.valid(value as DimensionSelection<T>),\n `Invalid ${filter.label} selection.`\n );\n\n return value as DimensionSelection<T>;\n}\n\n/** Validate bounded facet results before showing them in a picker. */\nexport function parseFacetOptions<T extends DimensionValue>(\n value: unknown,\n filter: DimensionVariable<T>\n): DimensionOption<T>[] {\n invariant(\n Array.isArray(value) && value.length <= 200,\n 'Facet options exceed their bound.'\n );\n const options: DimensionOption<T>[] = [];\n const seen = new Set<string>();\n for (const item of value) {\n invariant(item && typeof item === 'object', 'Invalid facet option.');\n const option = item as DimensionOption<T>;\n invariant(\n typeof option.value === filter.valueType &&\n filter.valid({\n kind: 'include',\n members: [{ kind: 'value', value: option.value }],\n }) &&\n typeof option.label === 'string' &&\n !!option.label.trim() &&\n option.label.length <= 100 &&\n (option.count === undefined ||\n (Number.isSafeInteger(option.count) && option.count >= 0)),\n 'Invalid facet option.'\n );\n const id = dimensionMemberKey({ kind: 'value', value: option.value });\n invariant(!seen.has(id), 'Duplicate facet option.');\n seen.add(id);\n options.push(option);\n }\n\n return options;\n}\n\n/** Central SQL literal encoding for transports without bound parameters. Column is an allowlisted identifier. */\nexport function dimensionPredicate<\n T extends DimensionValue,\n const Columns extends readonly string[],\n>(\n column: Columns[number],\n selection: DimensionSelection<T>,\n allowedColumns: Columns\n): string {\n invariant(\n allowedColumns.includes(column) &&\n /^[A-Za-z_][A-Za-z_0-9]*(?:\\.[A-Za-z_][A-Za-z_0-9]*)*$/.test(column),\n 'Invalid dimension column.'\n );\n if (selection.kind === 'all') return '';\n invariant(\n selection.members.length > 0,\n 'Empty dimension inclusion is invalid.'\n );\n const values = selection.members.filter(\n (member): member is Extract<DimensionMember<T>, { kind: 'value' }> =>\n member.kind === 'value'\n );\n const missing = selection.members.some(member => member.kind === 'missing');\n const literals = values.map(({ value }) => {\n if (typeof value === 'number') {\n invariant(Number.isFinite(value), 'Invalid numeric dimension value.');\n\n return String(value);\n }\n const text = value as string;\n for (let index = 0; index < text.length; index++)\n invariant(\n text.charCodeAt(index) >= 32 && text.charCodeAt(index) !== 127,\n 'Invalid control character in dimension value.'\n );\n\n return `'${text.replaceAll(\"'\", \"''\")}'`;\n });\n const clauses = [\n ...(literals.length ? [`${column} IN (${literals.join(', ')})`] : []),\n ...(missing ? [`${column} IS NULL`] : []),\n ];\n\n return `(${clauses.join(' OR ')})`;\n}\n"
7
7
  ],
8
8
  "mappings": "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",
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  MessageRoutingError2
3
- } from "./contract-jksbmt5q.js";
3
+ } from "./contract-3bnrf5pf.js";
4
4
  import {
5
5
  BRIDGE,
6
6
  PARENT_PARAM,
@@ -11,10 +11,11 @@ import {
11
11
  } from "./contract-ryyf6dme.js";
12
12
 
13
13
  // src/embed/host.ts
14
- function attachDataAppBridge2({
14
+ function attachDataAppConnection({
15
15
  iframe,
16
16
  connection,
17
17
  javascript,
18
+ presentation,
18
19
  onStatusChange,
19
20
  onDiagnostic,
20
21
  onMessage,
@@ -27,9 +28,27 @@ function attachDataAppBridge2({
27
28
  throw new Error("Invalid sandbox token.");
28
29
  let token = connection.type === "opaque" ? connection.token : undefined;
29
30
  const targetOrigin = connection.type === "opaque" ? "*" : frameOrigin;
31
+ let currentPresentation;
32
+ let disposed = false;
30
33
  let documentId;
31
34
  let sessionId;
32
35
  const pending = new Map;
36
+ function hostState() {
37
+ return {
38
+ search: host.location.search,
39
+ hash: host.location.hash,
40
+ ...currentPresentation ? { presentation: currentPresentation } : {}
41
+ };
42
+ }
43
+ function setPresentation(value) {
44
+ if (disposed)
45
+ return;
46
+ if (value?.surface === currentPresentation?.surface && value?.theme === currentPresentation?.theme)
47
+ return;
48
+ currentPresentation = value && { ...value };
49
+ if (sessionId)
50
+ publishState();
51
+ }
33
52
  function cancelAll() {
34
53
  for (const controller of pending.values())
35
54
  controller.abort();
@@ -47,7 +66,7 @@ function attachDataAppBridge2({
47
66
  }, targetOrigin);
48
67
  }
49
68
  function error(id, code, message, requestId) {
50
- send({ type: "error", id, code, message, requestId });
69
+ send({ type: "bridge:error", id, code, message, requestId });
51
70
  }
52
71
  async function request(message) {
53
72
  const id = message.id;
@@ -79,7 +98,7 @@ function attachDataAppBridge2({
79
98
  try {
80
99
  const response = await onMessage({ route: message.route, payload: message.payload }, { signal: controller.signal });
81
100
  if (pending.get(id) === controller)
82
- send({ type: "result", id, response });
101
+ send({ type: "bridge:result", id, response });
83
102
  } catch (failure) {
84
103
  if (pending.get(id) === controller) {
85
104
  if (failure instanceof MessageRoutingError2)
@@ -100,7 +119,7 @@ function attachDataAppBridge2({
100
119
  if (connection.type === "opaque" && message.token !== token)
101
120
  return;
102
121
  onDiagnostic?.({ direction: "receive", type: message.type });
103
- if (message.type === "ready") {
122
+ if (message.type === "bridge:ready") {
104
123
  if (message.documentId !== documentId) {
105
124
  cancelAll();
106
125
  documentId = message.documentId;
@@ -108,35 +127,35 @@ function attachDataAppBridge2({
108
127
  }
109
128
  onStatusChange?.("connected");
110
129
  send({
111
- type: "initialize",
112
- state: { search: host.location.search, hash: host.location.hash }
130
+ type: "bridge:initialize",
131
+ state: hostState()
113
132
  });
114
133
  if (javascript !== undefined)
115
- send({ type: "script.load", javascript });
134
+ send({ type: "script:load", javascript });
116
135
  return;
117
136
  }
118
137
  if (!sessionId || message.sessionId !== sessionId || message.documentId !== documentId)
119
138
  return;
120
- if (message.type === "runtime.ready")
139
+ if (message.type === "runtime:ready")
121
140
  onStatusChange?.("ready");
122
- else if (message.type === "runtime.error")
141
+ else if (message.type === "runtime:error")
123
142
  onStatusChange?.("failed");
124
- else if (message.type === "request")
143
+ else if (message.type === "bridge:request")
125
144
  request(message);
126
- else if (message.type === "cancel" && validId(message.id)) {
145
+ else if (message.type === "bridge:cancel" && validId(message.id)) {
127
146
  pending.get(message.id)?.abort();
128
147
  pending.delete(message.id);
129
- } else if (message.type === "disconnect") {
148
+ } else if (message.type === "bridge:disconnect") {
130
149
  cancelAll();
131
150
  documentId = undefined;
132
151
  sessionId = undefined;
133
152
  onStatusChange?.("disconnected");
134
153
  }
135
154
  }
136
- function navigate() {
155
+ function publishState() {
137
156
  send({
138
- type: "state",
139
- state: { search: host.location.search, hash: host.location.hash }
157
+ type: "state:update",
158
+ state: hostState()
140
159
  });
141
160
  }
142
161
  function load() {
@@ -146,28 +165,31 @@ function attachDataAppBridge2({
146
165
  documentId = undefined;
147
166
  sessionId = undefined;
148
167
  onStatusChange?.("connecting");
149
- send({ type: "connect", documentId: "host" });
168
+ send({ type: "bridge:connect", documentId: "host" });
150
169
  }
151
170
  function dispose() {
171
+ disposed = true;
152
172
  cancelAll();
153
173
  iframe.removeEventListener("load", load);
154
174
  host.removeEventListener("message", receive);
155
- host.removeEventListener("popstate", navigate);
175
+ host.removeEventListener("popstate", publishState);
156
176
  host.removeEventListener("pagehide", cancelAll);
157
177
  }
178
+ setPresentation(presentation);
158
179
  iframe.addEventListener("load", load);
159
180
  host.addEventListener("message", receive);
160
- host.addEventListener("popstate", navigate);
181
+ host.addEventListener("popstate", publishState);
161
182
  host.addEventListener("pagehide", cancelAll);
162
183
  onStatusChange?.("connecting");
163
- send({ type: "connect", documentId: "host" });
164
- return dispose;
184
+ send({ type: "bridge:connect", documentId: "host" });
185
+ return { dispose, setPresentation };
165
186
  }
166
187
 
167
- // src/embed/shell.ts
168
- function attachDataAppShell2({
188
+ // src/embed/source.ts
189
+ function attachDataAppSource({
169
190
  iframe,
170
191
  source,
192
+ presentation,
171
193
  onMessage,
172
194
  onStatusChange,
173
195
  onDiagnostic,
@@ -181,7 +203,7 @@ function attachDataAppShell2({
181
203
  throw new Error("Data apps require an HTTP(S) URL.");
182
204
  const opaque = source.type === "bundle";
183
205
  if (!opaque && url.origin === host.location.origin)
184
- throw new Error("URL data apps require a separate origin from the shell.");
206
+ throw new Error("URL data apps require a separate origin from the host.");
185
207
  iframe.setAttribute("sandbox", opaque ? "allow-scripts" : "allow-scripts allow-same-origin allow-downloads allow-popups allow-popups-to-escape-sandbox");
186
208
  iframe.referrerPolicy = "no-referrer";
187
209
  if (!opaque)
@@ -197,19 +219,20 @@ function attachDataAppShell2({
197
219
  if (value === "connecting" || value === "connected")
198
220
  timer = setTimeout(() => {
199
221
  failed = true;
200
- disposeBridge();
222
+ bridge.dispose();
201
223
  onStatusChange?.("failed");
202
224
  }, startupTimeoutMs);
203
225
  if (value === "failed") {
204
226
  failed = true;
205
- disposeBridge();
227
+ bridge.dispose();
206
228
  }
207
229
  onStatusChange?.(value);
208
230
  }
209
- const disposeBridge = attachDataAppBridge2({
231
+ const bridge = attachDataAppConnection({
210
232
  iframe,
211
233
  connection: opaque ? { type: "opaque", token: crypto.randomUUID() } : { type: "origin", origin: url.origin },
212
234
  javascript: opaque ? source.javascript : undefined,
235
+ presentation,
213
236
  onMessage,
214
237
  onStatusChange: status,
215
238
  onDiagnostic,
@@ -220,13 +243,19 @@ function attachDataAppShell2({
220
243
  }
221
244
  iframe.addEventListener("error", error);
222
245
  iframe.src = url.href;
223
- return () => {
246
+ function dispose() {
224
247
  clearTimeout(timer);
225
248
  iframe.removeEventListener("error", error);
226
- disposeBridge();
227
- };
249
+ bridge.dispose();
250
+ }
251
+ return { dispose, setPresentation: bridge.setPresentation };
252
+ }
253
+
254
+ // src/embed/bridge.ts
255
+ function attachDataAppBridge2(options) {
256
+ return options.source ? attachDataAppSource(options) : attachDataAppConnection(options);
228
257
  }
229
258
 
230
- export { attachDataAppBridge2, attachDataAppShell2 };
259
+ export { attachDataAppBridge2 };
231
260
 
232
- //# debugId=AFE2FEFDAF8B989664756E2164756E21
261
+ //# debugId=3C364E4A3943864564756E2164756E21
@@ -0,0 +1,12 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/embed/host.ts", "../src/embed/source.ts", "../src/embed/bridge.ts"],
4
+ "sourcesContent": [
5
+ "import type { DataAppPresentation } from '@/src/core/presentation';\nimport {\n BRIDGE,\n MAX_PENDING,\n REQUEST_TIMEOUT_MS,\n isBridgeMessage,\n validId,\n type BridgeMessage,\n} from '@/src/core/bridge';\n\nimport {\n MessageRoutingError,\n type MessageDispatcher,\n} from '@/src/core/messages';\n\nexport type DataAppConnection =\n | { type: 'origin'; origin: string }\n | { type: 'opaque'; token: string };\nexport type DataAppStatus =\n | 'connecting'\n | 'connected'\n | 'ready'\n | 'failed'\n | 'disconnected';\nexport type DataAppDiagnostic = { direction: 'send' | 'receive'; type: string };\n\nexport type DataAppHost = {\n dispose: () => void;\n setPresentation: (presentation?: DataAppPresentation) => void;\n};\n\nexport type DataAppConnectionOptions = {\n iframe: HTMLIFrameElement;\n connection: DataAppConnection;\n javascript?: string;\n presentation?: DataAppPresentation;\n onStatusChange?: (status: DataAppStatus) => void;\n onDiagnostic?: (event: DataAppDiagnostic) => void;\n onMessage: MessageDispatcher;\n window?: Window;\n};\n\n/** The bridge owns delivery and cancellation; the routed handler owns validation, authorization and execution. */\nexport function attachDataAppConnection({\n iframe,\n connection,\n javascript,\n presentation,\n onStatusChange,\n onDiagnostic,\n onMessage,\n window: host = window,\n}: DataAppConnectionOptions): DataAppHost {\n const frameOrigin = connection.type === 'origin' ? connection.origin : 'null';\n if (\n connection.type === 'origin' &&\n new URL(frameOrigin).origin !== frameOrigin\n )\n throw new Error('Expected an exact frame origin.');\n if (connection.type === 'opaque' && !validId(connection.token))\n throw new Error('Invalid sandbox token.');\n let token = connection.type === 'opaque' ? connection.token : undefined;\n const targetOrigin = connection.type === 'opaque' ? '*' : frameOrigin;\n let currentPresentation: DataAppPresentation | undefined;\n let disposed = false;\n let documentId: string | undefined;\n let sessionId: string | undefined;\n const pending = new Map<string, AbortController>();\n\n function hostState() {\n return {\n search: host.location.search,\n hash: host.location.hash,\n ...(currentPresentation ? { presentation: currentPresentation } : {}),\n };\n }\n\n function setPresentation(value: DataAppPresentation | undefined) {\n if (disposed) return;\n if (\n value?.surface === currentPresentation?.surface &&\n value?.theme === currentPresentation?.theme\n )\n return;\n currentPresentation = value && { ...value };\n if (sessionId) publishState();\n }\n\n function cancelAll() {\n for (const controller of pending.values()) controller.abort();\n pending.clear();\n }\n\n function send(message: Partial<BridgeMessage>) {\n onDiagnostic?.({ direction: 'send', type: message.type ?? 'unknown' });\n iframe.contentWindow?.postMessage(\n {\n channel: BRIDGE,\n version: 1,\n documentId,\n sessionId,\n ...(connection.type === 'opaque' ? { token } : {}),\n ...message,\n },\n targetOrigin\n );\n }\n\n function error(\n id: string,\n code: string,\n message: string,\n requestId?: string\n ) {\n send({ type: 'bridge:error', id, code, message, requestId });\n }\n\n async function request(message: BridgeMessage) {\n const id = message.id;\n if (!validId(id) || pending.has(id)) return;\n if (pending.size >= MAX_PENDING)\n return error(id, 'bridge_busy', 'Too many pending data requests.');\n if (\n typeof message.route !== 'string' ||\n !message.route ||\n message.route.length > 256\n )\n return error(id, 'invalid_message', 'Invalid message route.');\n try {\n const body = JSON.stringify(message.payload);\n if (\n message.payload !== undefined &&\n (typeof body !== 'string' || body.length > 16_384)\n )\n throw new Error('Invalid payload');\n } catch {\n return error(\n id,\n 'invalid_payload',\n 'The message payload is invalid or too large.'\n );\n }\n const controller = new AbortController();\n pending.set(id, controller);\n const timer = setTimeout(() => {\n if (pending.get(id) !== controller) return;\n pending.delete(id);\n controller.abort();\n error(id, 'timeout', 'The data request timed out.');\n }, REQUEST_TIMEOUT_MS);\n controller.signal.addEventListener('abort', () => clearTimeout(timer), {\n once: true,\n });\n try {\n const response = await onMessage(\n { route: message.route, payload: message.payload },\n { signal: controller.signal }\n );\n if (pending.get(id) === controller)\n send({ type: 'bridge:result', id, response });\n } catch (failure) {\n if (pending.get(id) === controller) {\n if (failure instanceof MessageRoutingError)\n error(id, failure.code, failure.message, failure.requestId);\n else\n error(\n id,\n 'request_failed',\n 'The message request failed. Retry the request.'\n );\n }\n } finally {\n clearTimeout(timer);\n if (pending.get(id) === controller) pending.delete(id);\n }\n }\n\n function receive(event: MessageEvent) {\n if (\n event.origin !== frameOrigin ||\n event.source !== iframe.contentWindow ||\n !isBridgeMessage(event.data)\n )\n return;\n const message = event.data;\n if (connection.type === 'opaque' && message.token !== token) return;\n onDiagnostic?.({ direction: 'receive', type: message.type });\n if (message.type === 'bridge:ready') {\n if (message.documentId !== documentId) {\n cancelAll();\n documentId = message.documentId;\n sessionId = crypto.randomUUID();\n }\n onStatusChange?.('connected');\n send({\n type: 'bridge:initialize',\n state: hostState(),\n });\n if (javascript !== undefined) send({ type: 'script:load', javascript });\n\n return;\n }\n if (\n !sessionId ||\n message.sessionId !== sessionId ||\n message.documentId !== documentId\n )\n return;\n if (message.type === 'runtime:ready') onStatusChange?.('ready');\n else if (message.type === 'runtime:error') onStatusChange?.('failed');\n else if (message.type === 'bridge:request') void request(message);\n else if (message.type === 'bridge:cancel' && validId(message.id)) {\n pending.get(message.id)?.abort();\n pending.delete(message.id);\n } else if (message.type === 'bridge:disconnect') {\n cancelAll();\n documentId = undefined;\n sessionId = undefined;\n onStatusChange?.('disconnected');\n }\n }\n\n function publishState() {\n send({\n type: 'state:update',\n state: hostState(),\n });\n }\n\n function load() {\n if (connection.type === 'opaque') token = crypto.randomUUID();\n cancelAll();\n documentId = undefined;\n sessionId = undefined;\n onStatusChange?.('connecting');\n send({ type: 'bridge:connect', documentId: 'host' });\n }\n\n function dispose() {\n disposed = true;\n cancelAll();\n iframe.removeEventListener('load', load);\n host.removeEventListener('message', receive);\n host.removeEventListener('popstate', publishState);\n host.removeEventListener('pagehide', cancelAll);\n }\n\n setPresentation(presentation);\n iframe.addEventListener('load', load);\n host.addEventListener('message', receive);\n host.addEventListener('popstate', publishState);\n host.addEventListener('pagehide', cancelAll);\n // Reconnect an already-loaded iframe when its host bridge mounts again.\n onStatusChange?.('connecting');\n send({ type: 'bridge:connect', documentId: 'host' });\n\n return { dispose, setPresentation };\n}\n",
6
+ "import type { DataAppPresentation } from '@/src/core/presentation';\nimport { PARENT_PARAM } from '@/src/core/bridge';\nimport type { MessageDispatcher } from '@/src/core/messages';\nimport {\n attachDataAppConnection,\n type DataAppStatus,\n type DataAppHost,\n type DataAppDiagnostic,\n} from '@/src/embed/host';\n\nexport type DataAppSource =\n | { type: 'url'; url: string }\n | {\n type: 'bundle';\n bootstrapUrl: string;\n javascript: string;\n };\nexport type DataAppSourceOptions = {\n iframe: HTMLIFrameElement;\n source: DataAppSource;\n presentation?: DataAppPresentation;\n onMessage: MessageDispatcher;\n onStatusChange?: (status: DataAppStatus) => void;\n onDiagnostic?: (event: DataAppDiagnostic) => void;\n startupTimeoutMs?: number;\n};\n\n/** Owns loading and sandbox policy. Dispose before replacing the source or retrying. */\nexport function attachDataAppSource({\n iframe,\n source,\n presentation,\n onMessage,\n onStatusChange,\n onDiagnostic,\n startupTimeoutMs = 30_000,\n}: DataAppSourceOptions): DataAppHost {\n const host = iframe.ownerDocument.defaultView;\n if (!host) throw new Error('The iframe requires a host window.');\n const url = new URL(\n source.type === 'url' ? source.url : source.bootstrapUrl,\n host.location.href\n );\n if (!/^https?:$/.test(url.protocol))\n throw new Error('Data apps require an HTTP(S) URL.');\n const opaque = source.type === 'bundle';\n if (!opaque && url.origin === host.location.origin)\n throw new Error('URL data apps require a separate origin from the host.');\n iframe.setAttribute(\n 'sandbox',\n opaque\n ? 'allow-scripts'\n : 'allow-scripts allow-same-origin allow-downloads allow-popups allow-popups-to-escape-sandbox'\n );\n iframe.referrerPolicy = 'no-referrer';\n if (!opaque) url.searchParams.set(PARENT_PARAM, host.location.origin);\n let timer: ReturnType<typeof setTimeout> | undefined;\n let failed = false;\n\n function status(value: DataAppStatus) {\n if (value === 'connecting') failed = false;\n if (failed && value !== 'connecting') return;\n clearTimeout(timer);\n if (value === 'connecting' || value === 'connected')\n timer = setTimeout(() => {\n failed = true;\n bridge.dispose();\n onStatusChange?.('failed');\n }, startupTimeoutMs);\n if (value === 'failed') {\n failed = true;\n bridge.dispose();\n }\n onStatusChange?.(value);\n }\n\n const bridge = attachDataAppConnection({\n iframe,\n connection: opaque\n ? { type: 'opaque', token: crypto.randomUUID() }\n : { type: 'origin', origin: url.origin },\n javascript: opaque ? source.javascript : undefined,\n presentation,\n onMessage,\n onStatusChange: status,\n onDiagnostic,\n window: host,\n });\n\n function error() {\n return status('failed');\n }\n\n iframe.addEventListener('error', error);\n iframe.src = url.href;\n\n function dispose() {\n clearTimeout(timer);\n iframe.removeEventListener('error', error);\n bridge.dispose();\n }\n\n return { dispose, setPresentation: bridge.setPresentation };\n}\n",
7
+ "import {\n attachDataAppConnection,\n type DataAppConnectionOptions,\n} from '@/src/embed/host';\nimport {\n attachDataAppSource,\n type DataAppSourceOptions,\n} from '@/src/embed/source';\n\nexport type DataAppBridgeOptions =\n | (DataAppSourceOptions & {\n connection?: never;\n window?: never;\n javascript?: never;\n })\n | (DataAppConnectionOptions & { source?: never; startupTimeoutMs?: never });\n\n/** Source mode configures and loads the iframe; connection mode attaches to a host-owned document. */\nexport function attachDataAppBridge(options: DataAppBridgeOptions) {\n return options.source\n ? attachDataAppSource(options)\n : attachDataAppConnection(options);\n}\n"
8
+ ],
9
+ "mappings": "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",
10
+ "debugId": "3C364E4A3943864564756E2164756E21",
11
+ "names": []
12
+ }