@lumerahq/ui 0.5.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 (122) hide show
  1. package/README.md +69 -0
  2. package/dist/api-BNY_eNKa.js +262 -0
  3. package/dist/automations-DU4weMpK.js +369 -0
  4. package/dist/components/ApiDemo.d.ts +2 -0
  5. package/dist/components/ApiDemo.d.ts.map +1 -0
  6. package/dist/components/automation/AutomationRunList.d.ts +38 -0
  7. package/dist/components/automation/AutomationRunList.d.ts.map +1 -0
  8. package/dist/components/automation/AutomationRunner.d.ts +43 -0
  9. package/dist/components/automation/AutomationRunner.d.ts.map +1 -0
  10. package/dist/components/component-example.d.ts +2 -0
  11. package/dist/components/component-example.d.ts.map +1 -0
  12. package/dist/components/data-table/DataTable.d.ts +49 -0
  13. package/dist/components/data-table/DataTable.d.ts.map +1 -0
  14. package/dist/components/data-table/DataTableFilters.d.ts +17 -0
  15. package/dist/components/data-table/DataTableFilters.d.ts.map +1 -0
  16. package/dist/components/data-table/DataTablePagination.d.ts +12 -0
  17. package/dist/components/data-table/DataTablePagination.d.ts.map +1 -0
  18. package/dist/components/data-table/RecordSheet.d.ts +24 -0
  19. package/dist/components/data-table/RecordSheet.d.ts.map +1 -0
  20. package/dist/components/data-table/index.d.ts +5 -0
  21. package/dist/components/data-table/index.d.ts.map +1 -0
  22. package/dist/components/example.d.ts +7 -0
  23. package/dist/components/example.d.ts.map +1 -0
  24. package/dist/components/index.d.ts +23 -0
  25. package/dist/components/index.d.ts.map +1 -0
  26. package/dist/components/index.js +61 -0
  27. package/dist/components/ui/alert-dialog.d.ts +19 -0
  28. package/dist/components/ui/alert-dialog.d.ts.map +1 -0
  29. package/dist/components/ui/avatar.d.ts +12 -0
  30. package/dist/components/ui/avatar.d.ts.map +1 -0
  31. package/dist/components/ui/badge.d.ts +8 -0
  32. package/dist/components/ui/badge.d.ts.map +1 -0
  33. package/dist/components/ui/button.d.ts +9 -0
  34. package/dist/components/ui/button.d.ts.map +1 -0
  35. package/dist/components/ui/card.d.ts +12 -0
  36. package/dist/components/ui/card.d.ts.map +1 -0
  37. package/dist/components/ui/checkbox.d.ts +4 -0
  38. package/dist/components/ui/checkbox.d.ts.map +1 -0
  39. package/dist/components/ui/combobox.d.ts +25 -0
  40. package/dist/components/ui/combobox.d.ts.map +1 -0
  41. package/dist/components/ui/dialog.d.ts +18 -0
  42. package/dist/components/ui/dialog.d.ts.map +1 -0
  43. package/dist/components/ui/dropdown-menu.d.ts +26 -0
  44. package/dist/components/ui/dropdown-menu.d.ts.map +1 -0
  45. package/dist/components/ui/field.d.ts +25 -0
  46. package/dist/components/ui/field.d.ts.map +1 -0
  47. package/dist/components/ui/file-dropzone.d.ts +23 -0
  48. package/dist/components/ui/file-dropzone.d.ts.map +1 -0
  49. package/dist/components/ui/input-group.d.ts +19 -0
  50. package/dist/components/ui/input-group.d.ts.map +1 -0
  51. package/dist/components/ui/input.d.ts +4 -0
  52. package/dist/components/ui/input.d.ts.map +1 -0
  53. package/dist/components/ui/label.d.ts +4 -0
  54. package/dist/components/ui/label.d.ts.map +1 -0
  55. package/dist/components/ui/popover.d.ts +10 -0
  56. package/dist/components/ui/popover.d.ts.map +1 -0
  57. package/dist/components/ui/progress.d.ts +8 -0
  58. package/dist/components/ui/progress.d.ts.map +1 -0
  59. package/dist/components/ui/scroll-area.d.ts +5 -0
  60. package/dist/components/ui/scroll-area.d.ts.map +1 -0
  61. package/dist/components/ui/select.d.ts +16 -0
  62. package/dist/components/ui/select.d.ts.map +1 -0
  63. package/dist/components/ui/separator.d.ts +4 -0
  64. package/dist/components/ui/separator.d.ts.map +1 -0
  65. package/dist/components/ui/sheet.d.ts +15 -0
  66. package/dist/components/ui/sheet.d.ts.map +1 -0
  67. package/dist/components/ui/sidebar.d.ts +64 -0
  68. package/dist/components/ui/sidebar.d.ts.map +1 -0
  69. package/dist/components/ui/skeleton.d.ts +3 -0
  70. package/dist/components/ui/skeleton.d.ts.map +1 -0
  71. package/dist/components/ui/status-badge.d.ts +21 -0
  72. package/dist/components/ui/status-badge.d.ts.map +1 -0
  73. package/dist/components/ui/table.d.ts +11 -0
  74. package/dist/components/ui/table.d.ts.map +1 -0
  75. package/dist/components/ui/tabs.d.ts +11 -0
  76. package/dist/components/ui/tabs.d.ts.map +1 -0
  77. package/dist/components/ui/textarea.d.ts +4 -0
  78. package/dist/components/ui/textarea.d.ts.map +1 -0
  79. package/dist/components/ui/tooltip.d.ts +7 -0
  80. package/dist/components/ui/tooltip.d.ts.map +1 -0
  81. package/dist/formatters-Baj7FkeG.js +3217 -0
  82. package/dist/hooks/index.d.ts +6 -0
  83. package/dist/hooks/index.d.ts.map +1 -0
  84. package/dist/hooks/index.js +11 -0
  85. package/dist/hooks/use-automation-agent.d.ts +60 -0
  86. package/dist/hooks/use-automation-agent.d.ts.map +1 -0
  87. package/dist/hooks/use-automation-run.d.ts +65 -0
  88. package/dist/hooks/use-automation-run.d.ts.map +1 -0
  89. package/dist/hooks/use-file-upload.d.ts +57 -0
  90. package/dist/hooks/use-file-upload.d.ts.map +1 -0
  91. package/dist/hooks/use-lumera-api.d.ts +14 -0
  92. package/dist/hooks/use-lumera-api.d.ts.map +1 -0
  93. package/dist/hooks/use-mobile.d.ts +2 -0
  94. package/dist/hooks/use-mobile.d.ts.map +1 -0
  95. package/dist/hooks/use-sql-table.d.ts +52 -0
  96. package/dist/hooks/use-sql-table.d.ts.map +1 -0
  97. package/dist/index.d.ts +4 -0
  98. package/dist/index.d.ts.map +1 -0
  99. package/dist/index.js +134 -0
  100. package/dist/lib/api.d.ts +307 -0
  101. package/dist/lib/api.d.ts.map +1 -0
  102. package/dist/lib/automations.d.ts +274 -0
  103. package/dist/lib/automations.d.ts.map +1 -0
  104. package/dist/lib/bridge.d.ts +137 -0
  105. package/dist/lib/bridge.d.ts.map +1 -0
  106. package/dist/lib/formatters.d.ts +92 -0
  107. package/dist/lib/formatters.d.ts.map +1 -0
  108. package/dist/lib/index.d.ts +12 -0
  109. package/dist/lib/index.d.ts.map +1 -0
  110. package/dist/lib/index.js +66 -0
  111. package/dist/lib/query-client.d.ts +3 -0
  112. package/dist/lib/query-client.d.ts.map +1 -0
  113. package/dist/lib/utils.d.ts +3 -0
  114. package/dist/lib/utils.d.ts.map +1 -0
  115. package/dist/logo.png +0 -0
  116. package/dist/lumera-logo.png +0 -0
  117. package/dist/query-client-DdOWay4_.js +17 -0
  118. package/dist/tooltip-BMqvkb5u.js +17305 -0
  119. package/dist/ui.css +4911 -0
  120. package/dist/use-automation-run-CbhXCKvM.js +134 -0
  121. package/dist/use-sql-table-DuIu8eMY.js +343 -0
  122. package/package.json +79 -0
package/README.md ADDED
@@ -0,0 +1,69 @@
1
+ # @lumerahq/ui
2
+
3
+ React component library and utilities for building Lumera custom applications.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @lumerahq/ui
9
+ ```
10
+
11
+ ## Features
12
+
13
+ - **UI Components** - Buttons, cards, tables, modals, sidebar, and more (built on shadcn/ui)
14
+ - **Data Table** - Full-featured table with filtering, sorting, and pagination
15
+ - **Bridge Module** - Parent-iframe communication for embedded apps
16
+ - **API Utilities** - PocketBase client with SQL, CRUD, file, and email operations
17
+ - **Automation Runner** - Start and monitor automation runs
18
+ - **React Query Integration** - Data fetching hooks
19
+
20
+ ## Usage
21
+
22
+ ### Components
23
+
24
+ ```tsx
25
+ import { Button, Card, DataTable } from '@lumerahq/ui';
26
+ ```
27
+
28
+ ### API Utilities
29
+
30
+ ```tsx
31
+ import { pbSql, pbList, pbGet, pbCreate } from '@lumerahq/ui/lib';
32
+
33
+ // SQL queries
34
+ const result = await pbSql<{ id: string; name: string }>({
35
+ sql: 'SELECT id, name FROM users WHERE active = true'
36
+ });
37
+
38
+ // CRUD operations
39
+ const items = await pbList<User>('users', { filter: 'active = true' });
40
+ const user = await pbGet<User>('users', 'user-id');
41
+ ```
42
+
43
+ ### Automation Runner
44
+
45
+ ```tsx
46
+ import { createRun, pollRun } from '@lumerahq/ui/lib';
47
+
48
+ const run = await createRun({
49
+ automationId: 'my-automation-id',
50
+ inputs: { param: 'value' },
51
+ });
52
+
53
+ const result = await pollRun(run.id);
54
+ ```
55
+
56
+ ## Standalone Development
57
+
58
+ For developing outside of a Lumera iframe, create a `.env.local` file:
59
+
60
+ ```bash
61
+ cp .env.example .env.local
62
+ # Edit .env.local with your credentials
63
+ ```
64
+
65
+ Get your auth token by running `lumera login` and copying from `.lumera/credentials.json`.
66
+
67
+ ## License
68
+
69
+ MIT
@@ -0,0 +1,262 @@
1
+ import { p as parentApiRequest, B as BridgeError } from "./automations-DU4weMpK.js";
2
+ const API_PREFIX = "/api";
3
+ const buildUrl = (path) => {
4
+ const normalized = path.startsWith("/") ? path : `/${path}`;
5
+ return normalized.startsWith(API_PREFIX) ? normalized : `${API_PREFIX}${normalized}`;
6
+ };
7
+ const request = async (path, options) => {
8
+ const response = await parentApiRequest({
9
+ ...options,
10
+ url: buildUrl(path)
11
+ });
12
+ if (!response.ok) {
13
+ const message = extractErrorMessage(response);
14
+ throw new BridgeError(message, response.status, response);
15
+ }
16
+ return parseResponseBody(response);
17
+ };
18
+ const extractErrorMessage = (response) => {
19
+ if (typeof response.error === "string" && response.error.trim()) {
20
+ return response.error;
21
+ }
22
+ if (typeof response.body === "string" && response.body.trim()) {
23
+ return response.body;
24
+ }
25
+ return `Request failed (${response.status})`;
26
+ };
27
+ const parseResponseBody = (response) => {
28
+ if (typeof response.body === "string") {
29
+ try {
30
+ return JSON.parse(response.body);
31
+ } catch {
32
+ return response.body;
33
+ }
34
+ }
35
+ return response.body;
36
+ };
37
+ const validateSql = (req) => {
38
+ const trimmed = req.sql?.trim();
39
+ if (!trimmed) {
40
+ throw new Error("SQL string is required");
41
+ }
42
+ if (req.params && req.args) {
43
+ throw new Error("Use either named params or positional args, not both");
44
+ }
45
+ return trimmed;
46
+ };
47
+ const fileToBase64 = (file) => new Promise((resolve, reject) => {
48
+ const reader = new FileReader();
49
+ reader.onloadend = () => {
50
+ const result = reader.result;
51
+ resolve(result.split(",")[1]);
52
+ };
53
+ reader.onerror = reject;
54
+ reader.readAsDataURL(file);
55
+ });
56
+ async function pbSql(req) {
57
+ const sql = validateSql(req);
58
+ return request("/pb/sql", {
59
+ method: "POST",
60
+ headers: { "Content-Type": "application/json" },
61
+ body: { ...req, sql }
62
+ });
63
+ }
64
+ async function pbGet(collection, id, expand) {
65
+ if (!collection) throw new Error("Collection is required for pbGet");
66
+ if (!id) throw new Error("Record ID is required for pbGet");
67
+ const params = expand ? `?expand=${encodeURIComponent(expand)}` : "";
68
+ return request(`/pb/collections/${encodeURIComponent(collection)}/records/${encodeURIComponent(id)}${params}`, {
69
+ method: "GET"
70
+ });
71
+ }
72
+ async function pbList(collection, options = {}) {
73
+ if (!collection) throw new Error("Collection is required for pbList");
74
+ const { page = 1, perPage = 20, filter, sort, expand } = options;
75
+ if (page < 1) throw new Error("Page must be >= 1");
76
+ if (perPage < 1 || perPage > 500) throw new Error("perPage must be between 1 and 500");
77
+ const params = new URLSearchParams({
78
+ page: String(page),
79
+ perPage: String(perPage)
80
+ });
81
+ if (filter) params.set("filter", filter);
82
+ if (sort) params.set("sort", sort);
83
+ if (expand) params.set("expand", expand);
84
+ return request(`/pb/collections/${encodeURIComponent(collection)}/records?${params}`, {
85
+ method: "GET"
86
+ });
87
+ }
88
+ async function pbSearch(collection, query, options = {}) {
89
+ if (!collection) throw new Error("Collection is required");
90
+ const { page = 1, perPage = 20, sort, expand } = options;
91
+ const params = new URLSearchParams({
92
+ q: query,
93
+ page: String(page),
94
+ perPage: String(perPage)
95
+ });
96
+ if (sort) params.set("sort", sort);
97
+ if (expand) params.set("expand", expand);
98
+ return request(`/pb/collections/${encodeURIComponent(collection)}/search?${params}`, {
99
+ method: "GET"
100
+ });
101
+ }
102
+ async function pbCreate(collection, data) {
103
+ if (!collection) throw new Error("Collection is required");
104
+ return request(`/pb/collections/${encodeURIComponent(collection)}/records`, {
105
+ method: "POST",
106
+ headers: { "Content-Type": "application/json" },
107
+ body: data
108
+ });
109
+ }
110
+ async function pbUpdate(collection, id, data) {
111
+ if (!collection) throw new Error("Collection is required");
112
+ if (!id) throw new Error("Record ID is required");
113
+ return request(`/pb/collections/${encodeURIComponent(collection)}/records/${encodeURIComponent(id)}`, {
114
+ method: "PATCH",
115
+ headers: { "Content-Type": "application/json" },
116
+ body: data
117
+ });
118
+ }
119
+ async function pbDelete(collection, id) {
120
+ if (!collection) throw new Error("Collection is required");
121
+ if (!id) throw new Error("Record ID is required");
122
+ await request(`/pb/collections/${encodeURIComponent(collection)}/records/${encodeURIComponent(id)}`, {
123
+ method: "DELETE"
124
+ });
125
+ }
126
+ const MAX_BULK_SIZE = 1e3;
127
+ async function pbBulkUpdate(collection, records) {
128
+ if (!collection) throw new Error("Collection is required for pbBulkUpdate");
129
+ if (!records.length) return { succeeded: 0, failed: 0, errors: [] };
130
+ if (records.length > MAX_BULK_SIZE) {
131
+ throw new Error(`Bulk update limited to ${MAX_BULK_SIZE} records. Consider batching larger operations.`);
132
+ }
133
+ return request(`/pb/collections/${encodeURIComponent(collection)}/records/bulk/update`, {
134
+ method: "POST",
135
+ headers: { "Content-Type": "application/json" },
136
+ body: { records, transaction: true }
137
+ });
138
+ }
139
+ async function pbBulkDelete(collection, ids) {
140
+ if (!collection) throw new Error("Collection is required for pbBulkDelete");
141
+ if (!ids.length) return { succeeded: 0, failed: 0, errors: [] };
142
+ if (ids.length > MAX_BULK_SIZE) {
143
+ throw new Error(`Bulk delete limited to ${MAX_BULK_SIZE} records. Consider batching larger operations.`);
144
+ }
145
+ return request(`/pb/collections/${encodeURIComponent(collection)}/records/bulk/delete`, {
146
+ method: "POST",
147
+ headers: { "Content-Type": "application/json" },
148
+ body: { ids }
149
+ });
150
+ }
151
+ async function pbUpsert(collection, data) {
152
+ if (!collection) throw new Error("Collection is required");
153
+ if (!data.external_id) throw new Error("external_id is required for upsert");
154
+ return request(`/pb/collections/${encodeURIComponent(collection)}/records/upsert`, {
155
+ method: "POST",
156
+ headers: { "Content-Type": "application/json" },
157
+ body: data
158
+ });
159
+ }
160
+ async function getUploadUrl(filename, contentType, options) {
161
+ if (!filename) throw new Error("Filename is required");
162
+ if (!options.collectionId) throw new Error("Collection ID is required");
163
+ if (!options.fieldName) throw new Error("Field name is required");
164
+ return request("/pb/uploads/presign", {
165
+ method: "POST",
166
+ headers: { "Content-Type": "application/json" },
167
+ body: {
168
+ collection_id: options.collectionId,
169
+ field_name: options.fieldName,
170
+ filename,
171
+ content_type: contentType
172
+ }
173
+ });
174
+ }
175
+ const DEFAULT_UPLOAD_TIMEOUT_MS = 6e4;
176
+ async function uploadFile(file, options) {
177
+ const { object_key, upload_url } = await getUploadUrl(file.name, file.type || "application/octet-stream", options);
178
+ if (!upload_url) {
179
+ throw new Error("Missing upload URL in response");
180
+ }
181
+ const base64 = await fileToBase64(file);
182
+ const contentType = file.type || "application/octet-stream";
183
+ const response = await parentApiRequest({
184
+ method: "PUT",
185
+ url: upload_url,
186
+ headers: { "Content-Type": contentType },
187
+ body: base64,
188
+ isBase64: true,
189
+ timeout: options.timeout ?? DEFAULT_UPLOAD_TIMEOUT_MS
190
+ });
191
+ if (!response.ok) {
192
+ throw new BridgeError(`Upload failed: ${response.error || response.status}`, response.status);
193
+ }
194
+ const descriptor = {
195
+ object_key,
196
+ original_name: file.name,
197
+ size: file.size,
198
+ content_type: contentType,
199
+ uploaded_at: (/* @__PURE__ */ new Date()).toISOString()
200
+ };
201
+ return { objectKey: object_key, descriptor };
202
+ }
203
+ async function getDownloadUrl(objectKey) {
204
+ if (!objectKey) throw new Error("Object key is required");
205
+ const response = await request("/pb/uploads/download", {
206
+ method: "POST",
207
+ headers: { "Content-Type": "application/json" },
208
+ body: { object_key: objectKey }
209
+ });
210
+ if (!response?.download_url) {
211
+ throw new Error("Missing download URL in response");
212
+ }
213
+ return response.download_url;
214
+ }
215
+ async function sendEmail(options) {
216
+ if (!options.to || Array.isArray(options.to) && options.to.length === 0) {
217
+ throw new Error("At least one recipient is required");
218
+ }
219
+ if (!options.subject?.trim()) {
220
+ throw new Error("Subject is required");
221
+ }
222
+ if (!options.body_html && !options.body_text) {
223
+ throw new Error("At least one of body_html or body_text is required");
224
+ }
225
+ const payload = {
226
+ to: Array.isArray(options.to) ? options.to : [options.to],
227
+ subject: options.subject
228
+ };
229
+ if (options.body_html) payload.body_html = options.body_html;
230
+ if (options.body_text) payload.body_text = options.body_text;
231
+ if (options.cc) payload.cc = Array.isArray(options.cc) ? options.cc : [options.cc];
232
+ if (options.bcc) payload.bcc = Array.isArray(options.bcc) ? options.bcc : [options.bcc];
233
+ if (options.from_address) payload.from = options.from_address;
234
+ if (options.from_name) payload.from_name = options.from_name;
235
+ if (options.reply_to) payload.reply_to = options.reply_to;
236
+ if (options.tags) payload.tags = options.tags;
237
+ return request("/email/send", {
238
+ method: "POST",
239
+ headers: { "Content-Type": "application/json" },
240
+ body: payload
241
+ });
242
+ }
243
+ async function pbUpdateRecord(collectionId, recordId, data) {
244
+ return pbUpdate(collectionId, recordId, data);
245
+ }
246
+ export {
247
+ getUploadUrl as a,
248
+ pbBulkUpdate as b,
249
+ pbCreate as c,
250
+ pbDelete as d,
251
+ pbGet as e,
252
+ pbList as f,
253
+ getDownloadUrl as g,
254
+ pbSearch as h,
255
+ pbSql as i,
256
+ pbUpdate as j,
257
+ pbUpdateRecord as k,
258
+ pbUpsert as l,
259
+ pbBulkDelete as p,
260
+ sendEmail as s,
261
+ uploadFile as u
262
+ };
@@ -0,0 +1,369 @@
1
+ const DEFAULT_TIMEOUT_MS = 15e3;
2
+ const EXPECTED_PARENT_ORIGIN = "https://*.lumerahq.com";
3
+ class BridgeError extends Error {
4
+ status;
5
+ response;
6
+ constructor(message, status, response) {
7
+ super(message);
8
+ this.name = "BridgeError";
9
+ this.status = status;
10
+ this.response = response;
11
+ }
12
+ }
13
+ const pendingRequests = /* @__PURE__ */ new Map();
14
+ const initListeners = /* @__PURE__ */ new Set();
15
+ let listenerAttached = false;
16
+ const log = (...args) => {
17
+ };
18
+ const generateId = () => {
19
+ if (typeof crypto !== "undefined" && crypto.randomUUID) {
20
+ return crypto.randomUUID();
21
+ }
22
+ return `${Date.now()}-${Math.random().toString(16).slice(2)}`;
23
+ };
24
+ const isAllowedOrigin = (origin) => {
25
+ if (!origin || !EXPECTED_PARENT_ORIGIN) return false;
26
+ if (origin === EXPECTED_PARENT_ORIGIN) return true;
27
+ if (origin.startsWith("http://localhost:")) return true;
28
+ if (EXPECTED_PARENT_ORIGIN.includes("*")) {
29
+ try {
30
+ const originUrl = new URL(origin);
31
+ const hostnameSuffix = EXPECTED_PARENT_ORIGIN.replace(/^https?:\/\//, "").replace(/^\*\./, "");
32
+ const expectedProtocol = EXPECTED_PARENT_ORIGIN.startsWith("https") ? "https:" : "http:";
33
+ const hostMatches = originUrl.hostname === hostnameSuffix || originUrl.hostname.endsWith(`.${hostnameSuffix}`);
34
+ const protocolMatches = originUrl.protocol === expectedProtocol;
35
+ return hostMatches && protocolMatches;
36
+ } catch {
37
+ return false;
38
+ }
39
+ }
40
+ return false;
41
+ };
42
+ const handleMessage = (event) => {
43
+ if (event.source !== window.parent) {
44
+ return;
45
+ }
46
+ if (!isAllowedOrigin(event.origin)) {
47
+ log("Ignoring message from unexpected origin", {
48
+ expected: EXPECTED_PARENT_ORIGIN,
49
+ received: event.origin
50
+ });
51
+ return;
52
+ }
53
+ const { type, payload } = event.data ?? {};
54
+ if (type === "init") {
55
+ initListeners.forEach((listener) => listener(payload));
56
+ return;
57
+ }
58
+ if (type === "response") {
59
+ const response = payload;
60
+ if (response?.id && pendingRequests.has(response.id)) {
61
+ pendingRequests.get(response.id)?.(response);
62
+ pendingRequests.delete(response.id);
63
+ } else {
64
+ log("No pending request found for response id", response?.id);
65
+ }
66
+ }
67
+ };
68
+ const ensureListener = () => {
69
+ if (listenerAttached || typeof window === "undefined") return;
70
+ listenerAttached = true;
71
+ window.addEventListener("message", handleMessage);
72
+ };
73
+ const isEmbedded = () => typeof window !== "undefined" && window.self !== window.top;
74
+ const postReadyMessage = () => {
75
+ if (typeof window === "undefined") return;
76
+ ensureListener();
77
+ window.parent.postMessage({ type: "ready" }, "*");
78
+ };
79
+ const onInitMessage = (listener) => {
80
+ ensureListener();
81
+ initListeners.add(listener);
82
+ return () => initListeners.delete(listener);
83
+ };
84
+ const parentApiRequest = async (request2) => {
85
+ if (typeof window === "undefined" || !isEmbedded()) {
86
+ throw new BridgeError("App must be embedded in the Lumera host to make API requests");
87
+ }
88
+ ensureListener();
89
+ const id = generateId();
90
+ const timeout = request2.timeout ?? DEFAULT_TIMEOUT_MS;
91
+ return new Promise((resolve, reject) => {
92
+ const timeoutId = setTimeout(() => {
93
+ pendingRequests.delete(id);
94
+ reject(new BridgeError(`Request timed out after ${timeout}ms`, 408));
95
+ }, timeout);
96
+ pendingRequests.set(id, (payload) => {
97
+ clearTimeout(timeoutId);
98
+ resolve(payload);
99
+ });
100
+ window.parent.postMessage(
101
+ {
102
+ type: "request",
103
+ payload: {
104
+ id,
105
+ method: request2.method,
106
+ url: request2.url,
107
+ headers: request2.headers,
108
+ body: request2.body,
109
+ isBase64: request2.isBase64
110
+ }
111
+ },
112
+ "*"
113
+ );
114
+ });
115
+ };
116
+ const API_PREFIX = "/api";
117
+ const TERMINAL_STATUSES = ["succeeded", "failed", "cancelled"];
118
+ const ACTIVE_STATUSES = ["preparing", "queued", "running", "cancelling"];
119
+ const isTerminalStatus = (status) => TERMINAL_STATUSES.includes(status);
120
+ const isActiveStatus = (status) => ACTIVE_STATUSES.includes(status);
121
+ const automationIdCache = /* @__PURE__ */ new Map();
122
+ function clearAutomationCache(externalId) {
123
+ if (externalId) {
124
+ automationIdCache.delete(externalId);
125
+ } else {
126
+ automationIdCache.clear();
127
+ }
128
+ }
129
+ const buildUrl = (path) => {
130
+ const normalized = path.startsWith("/") ? path : `/${path}`;
131
+ return normalized.startsWith(API_PREFIX) ? normalized : `${API_PREFIX}${normalized}`;
132
+ };
133
+ const extractErrorMessage = (response) => {
134
+ if (typeof response.error === "string" && response.error.trim()) {
135
+ return response.error;
136
+ }
137
+ if (typeof response.body === "string" && response.body.trim()) {
138
+ return response.body;
139
+ }
140
+ return `Request failed (${response.status})`;
141
+ };
142
+ const parseResponseBody = (response) => {
143
+ if (typeof response.body === "string") {
144
+ try {
145
+ return JSON.parse(response.body);
146
+ } catch {
147
+ return response.body;
148
+ }
149
+ }
150
+ return response.body;
151
+ };
152
+ const request = async (path, options) => {
153
+ const response = await parentApiRequest({
154
+ ...options,
155
+ url: buildUrl(path)
156
+ });
157
+ if (!response.ok) {
158
+ const message = extractErrorMessage(response);
159
+ throw new BridgeError(message, response.status, response);
160
+ }
161
+ return parseResponseBody(response);
162
+ };
163
+ async function getAutomationByExternalId(externalId) {
164
+ if (!externalId) throw new Error("External ID is required");
165
+ const cached = automationIdCache.get(externalId);
166
+ if (cached) return cached;
167
+ const response = await request(
168
+ `/automations?external_id=${encodeURIComponent(externalId)}&limit=1`,
169
+ { method: "GET" }
170
+ );
171
+ const automation = response.automations?.[0];
172
+ if (!automation?.id) {
173
+ throw new Error(`Automation not found with external_id: ${externalId}`);
174
+ }
175
+ automationIdCache.set(externalId, automation.id);
176
+ return automation.id;
177
+ }
178
+ async function resolveAutomationId(automationId) {
179
+ if (automationId.includes(":")) {
180
+ return getAutomationByExternalId(automationId);
181
+ }
182
+ return automationId;
183
+ }
184
+ async function createRun(options) {
185
+ const { automationId, inputs, externalId, trigger, metadata } = options;
186
+ if (!automationId) throw new Error("Automation ID is required");
187
+ const resolvedId = await resolveAutomationId(automationId);
188
+ return request("/automation-runs", {
189
+ method: "POST",
190
+ headers: { "Content-Type": "application/json" },
191
+ body: {
192
+ automation_id: resolvedId,
193
+ inputs: JSON.stringify(inputs ?? {}),
194
+ external_id: externalId,
195
+ trigger,
196
+ metadata
197
+ }
198
+ });
199
+ }
200
+ async function getRun(runId) {
201
+ if (!runId) throw new Error("Run ID is required");
202
+ return request(`/automation-runs/${encodeURIComponent(runId)}`, {
203
+ method: "GET"
204
+ });
205
+ }
206
+ async function listRuns(options = {}) {
207
+ const { automationId, externalId, limit = 10, offset = 0, dir = "desc" } = options;
208
+ const params = new URLSearchParams({
209
+ limit: String(limit),
210
+ offset: String(offset),
211
+ dir
212
+ });
213
+ if (automationId) {
214
+ const resolvedId = await resolveAutomationId(automationId);
215
+ params.set("automation_id", resolvedId);
216
+ }
217
+ if (externalId) params.set("external_id", externalId);
218
+ const response = await request(`/automation-runs?${params}`, { method: "GET" });
219
+ return response.data ?? [];
220
+ }
221
+ async function cancelRun(runId) {
222
+ if (!runId) throw new Error("Run ID is required");
223
+ return request(`/automation-runs/${encodeURIComponent(runId)}/cancel`, {
224
+ method: "POST"
225
+ });
226
+ }
227
+ async function pollRun(runId, options = {}) {
228
+ const { intervalMs = 2e3, timeoutMs = 5 * 60 * 1e3, onUpdate, signal } = options;
229
+ if (!runId) throw new Error("Run ID is required");
230
+ const startedAt = Date.now();
231
+ let lastRun = null;
232
+ const poll = async () => {
233
+ if (signal?.aborted) {
234
+ throw new Error("Polling aborted");
235
+ }
236
+ if (Date.now() - startedAt > timeoutMs) {
237
+ const error = new Error("Polling timed out before run completed");
238
+ if (lastRun) error.run = lastRun;
239
+ throw error;
240
+ }
241
+ const run = await getRun(runId);
242
+ lastRun = run;
243
+ onUpdate?.(run);
244
+ if (isTerminalStatus(run.status)) {
245
+ return run;
246
+ }
247
+ await new Promise((resolve, reject) => {
248
+ const timeoutId = setTimeout(resolve, intervalMs);
249
+ if (signal) {
250
+ signal.addEventListener(
251
+ "abort",
252
+ () => {
253
+ clearTimeout(timeoutId);
254
+ reject(new Error("Polling aborted"));
255
+ },
256
+ { once: true }
257
+ );
258
+ }
259
+ });
260
+ return poll();
261
+ };
262
+ return poll();
263
+ }
264
+ async function ensureRun(options) {
265
+ const { automationId, externalId } = options;
266
+ const existing = await listRuns({
267
+ automationId,
268
+ externalId,
269
+ limit: 1
270
+ });
271
+ const active = existing.find((run) => isActiveStatus(run.status));
272
+ if (active) return active;
273
+ const succeeded = existing.find((run) => run.status === "succeeded");
274
+ if (succeeded) return succeeded;
275
+ return createRun(options);
276
+ }
277
+ async function getRunFiles(runId) {
278
+ if (!runId) throw new Error("Run ID is required");
279
+ const response = await request(
280
+ `/automation-runs/${encodeURIComponent(runId)}/files`,
281
+ { method: "GET" }
282
+ );
283
+ return response.files ?? [];
284
+ }
285
+ async function getRunFileUrl(runId, filename) {
286
+ if (!runId) throw new Error("Run ID is required");
287
+ if (!filename) throw new Error("Filename is required");
288
+ const response = await request(
289
+ `/automation-runs/${encodeURIComponent(runId)}/files/download-url?name=${encodeURIComponent(filename)}`,
290
+ { method: "GET" }
291
+ );
292
+ if (!response.download_url) {
293
+ throw new Error("Missing download URL in response");
294
+ }
295
+ return response.download_url;
296
+ }
297
+ const automationStatuses = {
298
+ TERMINAL_STATUSES,
299
+ ACTIVE_STATUSES,
300
+ isTerminal: isTerminalStatus,
301
+ isActive: isActiveStatus
302
+ };
303
+ async function createAutomationRun(options) {
304
+ return createRun({
305
+ automationId: options.agentId,
306
+ inputs: options.inputs,
307
+ externalId: options.externalId,
308
+ trigger: options.trigger,
309
+ metadata: options.metadata
310
+ });
311
+ }
312
+ const getAutomationRun = getRun;
313
+ const cancelAutomationRun = cancelRun;
314
+ async function ensureAutomationRun(options) {
315
+ return ensureRun({
316
+ automationId: options.agentId,
317
+ inputs: options.inputs,
318
+ externalId: options.externalId,
319
+ trigger: options.trigger,
320
+ metadata: options.metadata
321
+ });
322
+ }
323
+ const pollAutomationRun = pollRun;
324
+ const listAutomationRunFiles = getRunFiles;
325
+ const getAutomationRunFileDownloadUrl = getRunFileUrl;
326
+ async function listRunsByAgent(params) {
327
+ return listRuns({
328
+ automationId: params.agentId,
329
+ limit: params.limit,
330
+ offset: params.offset
331
+ });
332
+ }
333
+ async function listRunsByExternalId(params) {
334
+ return listRuns({
335
+ automationId: params.agentId,
336
+ externalId: params.externalId,
337
+ limit: params.limit
338
+ });
339
+ }
340
+ export {
341
+ BridgeError as B,
342
+ EXPECTED_PARENT_ORIGIN as E,
343
+ postReadyMessage as a,
344
+ automationStatuses as b,
345
+ cancelAutomationRun as c,
346
+ cancelRun as d,
347
+ clearAutomationCache as e,
348
+ createAutomationRun as f,
349
+ createRun as g,
350
+ ensureAutomationRun as h,
351
+ isEmbedded as i,
352
+ ensureRun as j,
353
+ getAutomationByExternalId as k,
354
+ getAutomationRun as l,
355
+ getAutomationRunFileDownloadUrl as m,
356
+ getRun as n,
357
+ onInitMessage as o,
358
+ parentApiRequest as p,
359
+ getRunFiles as q,
360
+ getRunFileUrl as r,
361
+ isActiveStatus as s,
362
+ isTerminalStatus as t,
363
+ listAutomationRunFiles as u,
364
+ listRuns as v,
365
+ listRunsByAgent as w,
366
+ listRunsByExternalId as x,
367
+ pollAutomationRun as y,
368
+ pollRun as z
369
+ };
@@ -0,0 +1,2 @@
1
+ export declare function ApiDemo(): import("react/jsx-runtime").JSX.Element;
2
+ //# sourceMappingURL=ApiDemo.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ApiDemo.d.ts","sourceRoot":"","sources":["../../src/components/ApiDemo.tsx"],"names":[],"mappings":"AAYA,wBAAgB,OAAO,4CAoGtB"}