uidex 0.2.1 → 0.3.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 (61) hide show
  1. package/README.md +263 -263
  2. package/dist/cli/cli.cjs +3243 -0
  3. package/dist/cli/cli.cjs.map +1 -0
  4. package/dist/cloud/index.cjs +149 -0
  5. package/dist/cloud/index.cjs.map +1 -0
  6. package/dist/cloud/index.d.cts +108 -0
  7. package/dist/cloud/index.d.ts +108 -0
  8. package/dist/cloud/index.js +120 -0
  9. package/dist/cloud/index.js.map +1 -0
  10. package/dist/headless/index.cjs +3580 -0
  11. package/dist/headless/index.cjs.map +1 -0
  12. package/dist/headless/index.d.cts +214 -0
  13. package/dist/headless/index.d.ts +214 -0
  14. package/dist/headless/index.js +3562 -0
  15. package/dist/headless/index.js.map +1 -0
  16. package/dist/index.cjs +7977 -3301
  17. package/dist/index.cjs.map +1 -1
  18. package/dist/index.d.cts +898 -108
  19. package/dist/index.d.ts +898 -108
  20. package/dist/index.js +7934 -3270
  21. package/dist/index.js.map +1 -1
  22. package/dist/playwright/index.cjs +164 -24
  23. package/dist/playwright/index.cjs.map +1 -1
  24. package/dist/playwright/index.d.cts +32 -55
  25. package/dist/playwright/index.d.ts +32 -55
  26. package/dist/playwright/index.js +148 -21
  27. package/dist/playwright/index.js.map +1 -1
  28. package/dist/playwright/reporter.cjs +62 -28
  29. package/dist/playwright/reporter.cjs.map +1 -1
  30. package/dist/playwright/reporter.d.cts +24 -12
  31. package/dist/playwright/reporter.d.ts +24 -12
  32. package/dist/playwright/reporter.js +62 -28
  33. package/dist/playwright/reporter.js.map +1 -1
  34. package/dist/react/index.cjs +7970 -3267
  35. package/dist/react/index.cjs.map +1 -1
  36. package/dist/react/index.d.cts +670 -108
  37. package/dist/react/index.d.ts +670 -108
  38. package/dist/react/index.js +8016 -3274
  39. package/dist/react/index.js.map +1 -1
  40. package/dist/scan/index.cjs +3281 -0
  41. package/dist/scan/index.cjs.map +1 -0
  42. package/dist/scan/index.d.cts +373 -0
  43. package/dist/scan/index.d.ts +373 -0
  44. package/dist/scan/index.js +3224 -0
  45. package/dist/scan/index.js.map +1 -0
  46. package/package.json +74 -56
  47. package/templates/claude/audit.md +37 -0
  48. package/templates/claude/rules.md +212 -0
  49. package/claude/audit-command.md +0 -16
  50. package/claude/rules.md +0 -88
  51. package/dist/core/index.cjs +0 -3490
  52. package/dist/core/index.cjs.map +0 -1
  53. package/dist/core/index.d.cts +0 -441
  54. package/dist/core/index.d.ts +0 -441
  55. package/dist/core/index.global.js +0 -3469
  56. package/dist/core/index.global.js.map +0 -1
  57. package/dist/core/index.js +0 -3444
  58. package/dist/core/index.js.map +0 -1
  59. package/dist/core/style.css +0 -971
  60. package/dist/scripts/cli.cjs +0 -1168
  61. package/uidex.schema.json +0 -93
@@ -0,0 +1,149 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/cloud/index.ts
21
+ var cloud_exports = {};
22
+ __export(cloud_exports, {
23
+ CloudError: () => CloudError,
24
+ DEFAULT_CLOUD_ENDPOINT: () => DEFAULT_CLOUD_ENDPOINT,
25
+ cloud: () => cloud
26
+ });
27
+ module.exports = __toCommonJS(cloud_exports);
28
+
29
+ // src/cloud/types.ts
30
+ var DEFAULT_CLOUD_ENDPOINT = "https://app.uidex.dev";
31
+ var CloudError = class extends Error {
32
+ status;
33
+ retryAfter;
34
+ details;
35
+ constructor(message, options) {
36
+ super(message);
37
+ this.name = "CloudError";
38
+ this.status = options.status;
39
+ this.retryAfter = options.retryAfter;
40
+ this.details = options.details;
41
+ }
42
+ };
43
+
44
+ // src/cloud/client.ts
45
+ function resolveFetch(override) {
46
+ if (override) return override;
47
+ if (typeof globalThis !== "undefined" && typeof globalThis.fetch === "function") {
48
+ return globalThis.fetch.bind(globalThis);
49
+ }
50
+ throw new Error(
51
+ "uidex/cloud: global fetch is not available; pass a `fetch` override"
52
+ );
53
+ }
54
+ function trimEndpoint(endpoint) {
55
+ return endpoint.endsWith("/") ? endpoint.slice(0, -1) : endpoint;
56
+ }
57
+ function parseRetryAfter(header) {
58
+ if (!header) return void 0;
59
+ const seconds = Number(header);
60
+ if (Number.isFinite(seconds) && seconds >= 0) return seconds;
61
+ const date = Date.parse(header);
62
+ if (Number.isFinite(date)) {
63
+ const delta = Math.ceil((date - Date.now()) / 1e3);
64
+ return delta > 0 ? delta : 0;
65
+ }
66
+ return void 0;
67
+ }
68
+ async function readBody(response) {
69
+ const text = await response.text();
70
+ if (!text) return void 0;
71
+ try {
72
+ return JSON.parse(text);
73
+ } catch {
74
+ return text;
75
+ }
76
+ }
77
+ function errorMessage(body, fallback) {
78
+ if (body && typeof body === "object" && "error" in body && typeof body.error === "string") {
79
+ return body.error;
80
+ }
81
+ return fallback;
82
+ }
83
+ function cloud(options) {
84
+ const projectKey = options.projectKey;
85
+ if (!projectKey) {
86
+ throw new Error("uidex/cloud: `projectKey` is required");
87
+ }
88
+ const endpoint = trimEndpoint(options.endpoint ?? DEFAULT_CLOUD_ENDPOINT);
89
+ const fetchImpl = resolveFetch(options.fetch);
90
+ const authHeader = `Bearer ${projectKey}`;
91
+ async function submit(payload) {
92
+ const response = await fetchImpl(`${endpoint}/api/ingest`, {
93
+ method: "POST",
94
+ headers: {
95
+ "Content-Type": "application/json",
96
+ Authorization: authHeader
97
+ },
98
+ body: JSON.stringify(payload)
99
+ });
100
+ const body = await readBody(response);
101
+ if (!response.ok) {
102
+ const retryAfter = response.status === 429 ? parseRetryAfter(response.headers.get("Retry-After")) : void 0;
103
+ throw new CloudError(
104
+ errorMessage(body, `Feedback submission failed (${response.status})`),
105
+ { status: response.status, retryAfter, details: body }
106
+ );
107
+ }
108
+ if (!body || typeof body !== "object") {
109
+ throw new CloudError("Feedback submission returned an empty response", {
110
+ status: response.status
111
+ });
112
+ }
113
+ return body;
114
+ }
115
+ async function getConfig() {
116
+ const response = await fetchImpl(`${endpoint}/api/ingest/config`, {
117
+ method: "GET",
118
+ headers: {
119
+ Accept: "application/json",
120
+ Authorization: authHeader
121
+ }
122
+ });
123
+ const body = await readBody(response);
124
+ if (!response.ok) {
125
+ const retryAfter = response.status === 429 ? parseRetryAfter(response.headers.get("Retry-After")) : void 0;
126
+ throw new CloudError(
127
+ errorMessage(body, `Ingest config request failed (${response.status})`),
128
+ { status: response.status, retryAfter, details: body }
129
+ );
130
+ }
131
+ if (!body || typeof body !== "object") {
132
+ throw new CloudError("Ingest config returned an empty response", {
133
+ status: response.status
134
+ });
135
+ }
136
+ return body;
137
+ }
138
+ return {
139
+ feedback: { submit },
140
+ integrations: { getConfig }
141
+ };
142
+ }
143
+ // Annotate the CommonJS export names for ESM import in node:
144
+ 0 && (module.exports = {
145
+ CloudError,
146
+ DEFAULT_CLOUD_ENDPOINT,
147
+ cloud
148
+ });
149
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/cloud/index.ts","../../src/cloud/types.ts","../../src/cloud/client.ts"],"sourcesContent":["export { cloud } from \"./client\"\nexport {\n CloudError,\n DEFAULT_CLOUD_ENDPOINT,\n type CloudAdapter,\n type CloudOptions,\n type FeedbackExternalLink,\n type FeedbackPayload,\n type FeedbackResult,\n type FeedbackSuggestedTarget,\n type IngestConfig,\n type IngestConfigEpic,\n type SourceRef,\n} from \"./types\"\n","export type {\n CloudAdapter,\n FeedbackExternalLink,\n FeedbackPayload,\n FeedbackResult,\n FeedbackSuggestedTarget,\n IngestConfig,\n IngestConfigEpic,\n SourceRef,\n} from \"../ingest/feedback-contract\"\n\nexport const DEFAULT_CLOUD_ENDPOINT = \"https://app.uidex.dev\"\n\nexport interface CloudOptions {\n projectKey: string\n endpoint?: string\n fetch?: typeof fetch\n}\n\nexport class CloudError extends Error {\n readonly status: number\n readonly retryAfter?: number\n readonly details?: unknown\n\n constructor(\n message: string,\n options: { status: number; retryAfter?: number; details?: unknown }\n ) {\n super(message)\n this.name = \"CloudError\"\n this.status = options.status\n this.retryAfter = options.retryAfter\n this.details = options.details\n }\n}\n","import {\n CloudError,\n DEFAULT_CLOUD_ENDPOINT,\n type CloudAdapter,\n type CloudOptions,\n type FeedbackPayload,\n type FeedbackResult,\n type IngestConfig,\n} from \"./types\"\n\nfunction resolveFetch(override?: typeof fetch): typeof fetch {\n if (override) return override\n if (\n typeof globalThis !== \"undefined\" &&\n typeof globalThis.fetch === \"function\"\n ) {\n return globalThis.fetch.bind(globalThis)\n }\n throw new Error(\n \"uidex/cloud: global fetch is not available; pass a `fetch` override\"\n )\n}\n\nfunction trimEndpoint(endpoint: string): string {\n return endpoint.endsWith(\"/\") ? endpoint.slice(0, -1) : endpoint\n}\n\nfunction parseRetryAfter(header: string | null): number | undefined {\n if (!header) return undefined\n const seconds = Number(header)\n if (Number.isFinite(seconds) && seconds >= 0) return seconds\n const date = Date.parse(header)\n if (Number.isFinite(date)) {\n const delta = Math.ceil((date - Date.now()) / 1000)\n return delta > 0 ? delta : 0\n }\n return undefined\n}\n\nasync function readBody(response: Response): Promise<unknown> {\n const text = await response.text()\n if (!text) return undefined\n try {\n return JSON.parse(text)\n } catch {\n return text\n }\n}\n\nfunction errorMessage(body: unknown, fallback: string): string {\n if (\n body &&\n typeof body === \"object\" &&\n \"error\" in body &&\n typeof (body as { error: unknown }).error === \"string\"\n ) {\n return (body as { error: string }).error\n }\n return fallback\n}\n\nexport function cloud(options: CloudOptions): CloudAdapter {\n const projectKey = options.projectKey\n if (!projectKey) {\n throw new Error(\"uidex/cloud: `projectKey` is required\")\n }\n const endpoint = trimEndpoint(options.endpoint ?? DEFAULT_CLOUD_ENDPOINT)\n const fetchImpl = resolveFetch(options.fetch)\n const authHeader = `Bearer ${projectKey}`\n\n async function submit(payload: FeedbackPayload): Promise<FeedbackResult> {\n const response = await fetchImpl(`${endpoint}/api/ingest`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: authHeader,\n },\n body: JSON.stringify(payload),\n })\n\n const body = await readBody(response)\n\n if (!response.ok) {\n const retryAfter =\n response.status === 429\n ? parseRetryAfter(response.headers.get(\"Retry-After\"))\n : undefined\n throw new CloudError(\n errorMessage(body, `Feedback submission failed (${response.status})`),\n { status: response.status, retryAfter, details: body }\n )\n }\n\n if (!body || typeof body !== \"object\") {\n throw new CloudError(\"Feedback submission returned an empty response\", {\n status: response.status,\n })\n }\n return body as FeedbackResult\n }\n\n async function getConfig(): Promise<IngestConfig> {\n const response = await fetchImpl(`${endpoint}/api/ingest/config`, {\n method: \"GET\",\n headers: {\n Accept: \"application/json\",\n Authorization: authHeader,\n },\n })\n\n const body = await readBody(response)\n\n if (!response.ok) {\n const retryAfter =\n response.status === 429\n ? parseRetryAfter(response.headers.get(\"Retry-After\"))\n : undefined\n throw new CloudError(\n errorMessage(body, `Ingest config request failed (${response.status})`),\n { status: response.status, retryAfter, details: body }\n )\n }\n\n if (!body || typeof body !== \"object\") {\n throw new CloudError(\"Ingest config returned an empty response\", {\n status: response.status,\n })\n }\n return body as IngestConfig\n }\n\n return {\n feedback: { submit },\n integrations: { getConfig },\n }\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,108 @@
1
+ interface ConsoleLogEntry {
2
+ level: "log" | "warn" | "error" | "info";
3
+ message: string;
4
+ timestamp: string;
5
+ }
6
+ interface NetworkErrorEntry {
7
+ url: string;
8
+ method: string;
9
+ status: number | null;
10
+ statusText: string | null;
11
+ timestamp: string;
12
+ }
13
+ interface Viewport {
14
+ width: number;
15
+ height: number;
16
+ }
17
+ interface SourceRef {
18
+ filePath: string;
19
+ line: number;
20
+ }
21
+ interface FeedbackSuggestedTarget {
22
+ integrationId: string;
23
+ targetConfig: Record<string, unknown>;
24
+ }
25
+ interface FeedbackPayload {
26
+ type: "bug" | "feature" | "improvement" | "question";
27
+ severity: "low" | "medium" | "high" | "critical";
28
+ title?: string;
29
+ description: string;
30
+ componentId: string;
31
+ element?: string | null;
32
+ sources?: SourceRef[];
33
+ url: string;
34
+ path: string;
35
+ route?: string | null;
36
+ pageTitle?: string;
37
+ sessionId?: string;
38
+ reporterEmail?: string;
39
+ reporterName?: string;
40
+ timestamp: string;
41
+ viewport: Viewport;
42
+ screen: Viewport;
43
+ userAgent: string;
44
+ locale?: string;
45
+ environment?: string;
46
+ appVersion?: string;
47
+ consoleLogs?: ConsoleLogEntry[];
48
+ networkErrors?: NetworkErrorEntry[];
49
+ metadata?: Record<string, string>;
50
+ screenshot?: string;
51
+ suggestedTarget?: FeedbackSuggestedTarget;
52
+ }
53
+ interface FeedbackExternalLink {
54
+ ok: boolean;
55
+ url?: string;
56
+ key?: string;
57
+ error?: string;
58
+ }
59
+ interface FeedbackResult {
60
+ id: string;
61
+ sequenceNumber: number;
62
+ externalLink?: FeedbackExternalLink;
63
+ }
64
+ interface IngestConfigEpic {
65
+ key: string;
66
+ summary: string;
67
+ status: string;
68
+ }
69
+ interface IngestConfig {
70
+ hasJira: boolean;
71
+ integrationId?: string;
72
+ epics?: IngestConfigEpic[];
73
+ }
74
+ /**
75
+ * Generic so third-party adapters (e.g. `uidex-cloud`) can plug in their own
76
+ * payload/result/integration shapes. SDK-bundled `cloud()` returns the
77
+ * defaulted form. `ViewContext.cloud` uses the fully-open variant; consumers
78
+ * that need the SDK shape narrow with `as CloudAdapter`.
79
+ */
80
+ interface CloudAdapter<TPayload = FeedbackPayload, TResult = FeedbackResult, TIntegrations = {
81
+ getConfig(): Promise<IngestConfig>;
82
+ }> {
83
+ readonly feedback: {
84
+ submit(payload: TPayload): Promise<TResult>;
85
+ };
86
+ readonly integrations: TIntegrations;
87
+ }
88
+
89
+ declare const DEFAULT_CLOUD_ENDPOINT = "https://app.uidex.dev";
90
+ interface CloudOptions {
91
+ projectKey: string;
92
+ endpoint?: string;
93
+ fetch?: typeof fetch;
94
+ }
95
+ declare class CloudError extends Error {
96
+ readonly status: number;
97
+ readonly retryAfter?: number;
98
+ readonly details?: unknown;
99
+ constructor(message: string, options: {
100
+ status: number;
101
+ retryAfter?: number;
102
+ details?: unknown;
103
+ });
104
+ }
105
+
106
+ declare function cloud(options: CloudOptions): CloudAdapter;
107
+
108
+ export { type CloudAdapter, CloudError, type CloudOptions, DEFAULT_CLOUD_ENDPOINT, type FeedbackExternalLink, type FeedbackPayload, type FeedbackResult, type FeedbackSuggestedTarget, type IngestConfig, type IngestConfigEpic, type SourceRef, cloud };
@@ -0,0 +1,108 @@
1
+ interface ConsoleLogEntry {
2
+ level: "log" | "warn" | "error" | "info";
3
+ message: string;
4
+ timestamp: string;
5
+ }
6
+ interface NetworkErrorEntry {
7
+ url: string;
8
+ method: string;
9
+ status: number | null;
10
+ statusText: string | null;
11
+ timestamp: string;
12
+ }
13
+ interface Viewport {
14
+ width: number;
15
+ height: number;
16
+ }
17
+ interface SourceRef {
18
+ filePath: string;
19
+ line: number;
20
+ }
21
+ interface FeedbackSuggestedTarget {
22
+ integrationId: string;
23
+ targetConfig: Record<string, unknown>;
24
+ }
25
+ interface FeedbackPayload {
26
+ type: "bug" | "feature" | "improvement" | "question";
27
+ severity: "low" | "medium" | "high" | "critical";
28
+ title?: string;
29
+ description: string;
30
+ componentId: string;
31
+ element?: string | null;
32
+ sources?: SourceRef[];
33
+ url: string;
34
+ path: string;
35
+ route?: string | null;
36
+ pageTitle?: string;
37
+ sessionId?: string;
38
+ reporterEmail?: string;
39
+ reporterName?: string;
40
+ timestamp: string;
41
+ viewport: Viewport;
42
+ screen: Viewport;
43
+ userAgent: string;
44
+ locale?: string;
45
+ environment?: string;
46
+ appVersion?: string;
47
+ consoleLogs?: ConsoleLogEntry[];
48
+ networkErrors?: NetworkErrorEntry[];
49
+ metadata?: Record<string, string>;
50
+ screenshot?: string;
51
+ suggestedTarget?: FeedbackSuggestedTarget;
52
+ }
53
+ interface FeedbackExternalLink {
54
+ ok: boolean;
55
+ url?: string;
56
+ key?: string;
57
+ error?: string;
58
+ }
59
+ interface FeedbackResult {
60
+ id: string;
61
+ sequenceNumber: number;
62
+ externalLink?: FeedbackExternalLink;
63
+ }
64
+ interface IngestConfigEpic {
65
+ key: string;
66
+ summary: string;
67
+ status: string;
68
+ }
69
+ interface IngestConfig {
70
+ hasJira: boolean;
71
+ integrationId?: string;
72
+ epics?: IngestConfigEpic[];
73
+ }
74
+ /**
75
+ * Generic so third-party adapters (e.g. `uidex-cloud`) can plug in their own
76
+ * payload/result/integration shapes. SDK-bundled `cloud()` returns the
77
+ * defaulted form. `ViewContext.cloud` uses the fully-open variant; consumers
78
+ * that need the SDK shape narrow with `as CloudAdapter`.
79
+ */
80
+ interface CloudAdapter<TPayload = FeedbackPayload, TResult = FeedbackResult, TIntegrations = {
81
+ getConfig(): Promise<IngestConfig>;
82
+ }> {
83
+ readonly feedback: {
84
+ submit(payload: TPayload): Promise<TResult>;
85
+ };
86
+ readonly integrations: TIntegrations;
87
+ }
88
+
89
+ declare const DEFAULT_CLOUD_ENDPOINT = "https://app.uidex.dev";
90
+ interface CloudOptions {
91
+ projectKey: string;
92
+ endpoint?: string;
93
+ fetch?: typeof fetch;
94
+ }
95
+ declare class CloudError extends Error {
96
+ readonly status: number;
97
+ readonly retryAfter?: number;
98
+ readonly details?: unknown;
99
+ constructor(message: string, options: {
100
+ status: number;
101
+ retryAfter?: number;
102
+ details?: unknown;
103
+ });
104
+ }
105
+
106
+ declare function cloud(options: CloudOptions): CloudAdapter;
107
+
108
+ export { type CloudAdapter, CloudError, type CloudOptions, DEFAULT_CLOUD_ENDPOINT, type FeedbackExternalLink, type FeedbackPayload, type FeedbackResult, type FeedbackSuggestedTarget, type IngestConfig, type IngestConfigEpic, type SourceRef, cloud };
@@ -0,0 +1,120 @@
1
+ // src/cloud/types.ts
2
+ var DEFAULT_CLOUD_ENDPOINT = "https://app.uidex.dev";
3
+ var CloudError = class extends Error {
4
+ status;
5
+ retryAfter;
6
+ details;
7
+ constructor(message, options) {
8
+ super(message);
9
+ this.name = "CloudError";
10
+ this.status = options.status;
11
+ this.retryAfter = options.retryAfter;
12
+ this.details = options.details;
13
+ }
14
+ };
15
+
16
+ // src/cloud/client.ts
17
+ function resolveFetch(override) {
18
+ if (override) return override;
19
+ if (typeof globalThis !== "undefined" && typeof globalThis.fetch === "function") {
20
+ return globalThis.fetch.bind(globalThis);
21
+ }
22
+ throw new Error(
23
+ "uidex/cloud: global fetch is not available; pass a `fetch` override"
24
+ );
25
+ }
26
+ function trimEndpoint(endpoint) {
27
+ return endpoint.endsWith("/") ? endpoint.slice(0, -1) : endpoint;
28
+ }
29
+ function parseRetryAfter(header) {
30
+ if (!header) return void 0;
31
+ const seconds = Number(header);
32
+ if (Number.isFinite(seconds) && seconds >= 0) return seconds;
33
+ const date = Date.parse(header);
34
+ if (Number.isFinite(date)) {
35
+ const delta = Math.ceil((date - Date.now()) / 1e3);
36
+ return delta > 0 ? delta : 0;
37
+ }
38
+ return void 0;
39
+ }
40
+ async function readBody(response) {
41
+ const text = await response.text();
42
+ if (!text) return void 0;
43
+ try {
44
+ return JSON.parse(text);
45
+ } catch {
46
+ return text;
47
+ }
48
+ }
49
+ function errorMessage(body, fallback) {
50
+ if (body && typeof body === "object" && "error" in body && typeof body.error === "string") {
51
+ return body.error;
52
+ }
53
+ return fallback;
54
+ }
55
+ function cloud(options) {
56
+ const projectKey = options.projectKey;
57
+ if (!projectKey) {
58
+ throw new Error("uidex/cloud: `projectKey` is required");
59
+ }
60
+ const endpoint = trimEndpoint(options.endpoint ?? DEFAULT_CLOUD_ENDPOINT);
61
+ const fetchImpl = resolveFetch(options.fetch);
62
+ const authHeader = `Bearer ${projectKey}`;
63
+ async function submit(payload) {
64
+ const response = await fetchImpl(`${endpoint}/api/ingest`, {
65
+ method: "POST",
66
+ headers: {
67
+ "Content-Type": "application/json",
68
+ Authorization: authHeader
69
+ },
70
+ body: JSON.stringify(payload)
71
+ });
72
+ const body = await readBody(response);
73
+ if (!response.ok) {
74
+ const retryAfter = response.status === 429 ? parseRetryAfter(response.headers.get("Retry-After")) : void 0;
75
+ throw new CloudError(
76
+ errorMessage(body, `Feedback submission failed (${response.status})`),
77
+ { status: response.status, retryAfter, details: body }
78
+ );
79
+ }
80
+ if (!body || typeof body !== "object") {
81
+ throw new CloudError("Feedback submission returned an empty response", {
82
+ status: response.status
83
+ });
84
+ }
85
+ return body;
86
+ }
87
+ async function getConfig() {
88
+ const response = await fetchImpl(`${endpoint}/api/ingest/config`, {
89
+ method: "GET",
90
+ headers: {
91
+ Accept: "application/json",
92
+ Authorization: authHeader
93
+ }
94
+ });
95
+ const body = await readBody(response);
96
+ if (!response.ok) {
97
+ const retryAfter = response.status === 429 ? parseRetryAfter(response.headers.get("Retry-After")) : void 0;
98
+ throw new CloudError(
99
+ errorMessage(body, `Ingest config request failed (${response.status})`),
100
+ { status: response.status, retryAfter, details: body }
101
+ );
102
+ }
103
+ if (!body || typeof body !== "object") {
104
+ throw new CloudError("Ingest config returned an empty response", {
105
+ status: response.status
106
+ });
107
+ }
108
+ return body;
109
+ }
110
+ return {
111
+ feedback: { submit },
112
+ integrations: { getConfig }
113
+ };
114
+ }
115
+ export {
116
+ CloudError,
117
+ DEFAULT_CLOUD_ENDPOINT,
118
+ cloud
119
+ };
120
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/cloud/types.ts","../../src/cloud/client.ts"],"sourcesContent":["export type {\n CloudAdapter,\n FeedbackExternalLink,\n FeedbackPayload,\n FeedbackResult,\n FeedbackSuggestedTarget,\n IngestConfig,\n IngestConfigEpic,\n SourceRef,\n} from \"../ingest/feedback-contract\"\n\nexport const DEFAULT_CLOUD_ENDPOINT = \"https://app.uidex.dev\"\n\nexport interface CloudOptions {\n projectKey: string\n endpoint?: string\n fetch?: typeof fetch\n}\n\nexport class CloudError extends Error {\n readonly status: number\n readonly retryAfter?: number\n readonly details?: unknown\n\n constructor(\n message: string,\n options: { status: number; retryAfter?: number; details?: unknown }\n ) {\n super(message)\n this.name = \"CloudError\"\n this.status = options.status\n this.retryAfter = options.retryAfter\n this.details = options.details\n }\n}\n","import {\n CloudError,\n DEFAULT_CLOUD_ENDPOINT,\n type CloudAdapter,\n type CloudOptions,\n type FeedbackPayload,\n type FeedbackResult,\n type IngestConfig,\n} from \"./types\"\n\nfunction resolveFetch(override?: typeof fetch): typeof fetch {\n if (override) return override\n if (\n typeof globalThis !== \"undefined\" &&\n typeof globalThis.fetch === \"function\"\n ) {\n return globalThis.fetch.bind(globalThis)\n }\n throw new Error(\n \"uidex/cloud: global fetch is not available; pass a `fetch` override\"\n )\n}\n\nfunction trimEndpoint(endpoint: string): string {\n return endpoint.endsWith(\"/\") ? endpoint.slice(0, -1) : endpoint\n}\n\nfunction parseRetryAfter(header: string | null): number | undefined {\n if (!header) return undefined\n const seconds = Number(header)\n if (Number.isFinite(seconds) && seconds >= 0) return seconds\n const date = Date.parse(header)\n if (Number.isFinite(date)) {\n const delta = Math.ceil((date - Date.now()) / 1000)\n return delta > 0 ? delta : 0\n }\n return undefined\n}\n\nasync function readBody(response: Response): Promise<unknown> {\n const text = await response.text()\n if (!text) return undefined\n try {\n return JSON.parse(text)\n } catch {\n return text\n }\n}\n\nfunction errorMessage(body: unknown, fallback: string): string {\n if (\n body &&\n typeof body === \"object\" &&\n \"error\" in body &&\n typeof (body as { error: unknown }).error === \"string\"\n ) {\n return (body as { error: string }).error\n }\n return fallback\n}\n\nexport function cloud(options: CloudOptions): CloudAdapter {\n const projectKey = options.projectKey\n if (!projectKey) {\n throw new Error(\"uidex/cloud: `projectKey` is required\")\n }\n const endpoint = trimEndpoint(options.endpoint ?? DEFAULT_CLOUD_ENDPOINT)\n const fetchImpl = resolveFetch(options.fetch)\n const authHeader = `Bearer ${projectKey}`\n\n async function submit(payload: FeedbackPayload): Promise<FeedbackResult> {\n const response = await fetchImpl(`${endpoint}/api/ingest`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: authHeader,\n },\n body: JSON.stringify(payload),\n })\n\n const body = await readBody(response)\n\n if (!response.ok) {\n const retryAfter =\n response.status === 429\n ? parseRetryAfter(response.headers.get(\"Retry-After\"))\n : undefined\n throw new CloudError(\n errorMessage(body, `Feedback submission failed (${response.status})`),\n { status: response.status, retryAfter, details: body }\n )\n }\n\n if (!body || typeof body !== \"object\") {\n throw new CloudError(\"Feedback submission returned an empty response\", {\n status: response.status,\n })\n }\n return body as FeedbackResult\n }\n\n async function getConfig(): Promise<IngestConfig> {\n const response = await fetchImpl(`${endpoint}/api/ingest/config`, {\n method: \"GET\",\n headers: {\n Accept: \"application/json\",\n Authorization: authHeader,\n },\n })\n\n const body = await readBody(response)\n\n if (!response.ok) {\n const retryAfter =\n response.status === 429\n ? parseRetryAfter(response.headers.get(\"Retry-After\"))\n : undefined\n throw new CloudError(\n errorMessage(body, `Ingest config request failed (${response.status})`),\n { status: response.status, retryAfter, details: body }\n )\n }\n\n if (!body || typeof body !== \"object\") {\n throw new CloudError(\"Ingest config returned an empty response\", {\n status: response.status,\n })\n }\n return body as IngestConfig\n }\n\n return {\n feedback: { submit },\n integrations: { getConfig },\n }\n}\n"],"mappings":"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","names":[]}