@burdenoff/microfe-devportal 2026.1102.1 → 2026.1107.1

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.
@@ -10,10 +10,13 @@ export interface PackageUploadState {
10
10
  progress: number;
11
11
  error: string | null;
12
12
  }
13
+ type GqlFetcher = (query: string, variables: Record<string, unknown>) => Promise<Record<string, unknown>>;
14
+ export declare function makeFetcher(workspacesGatewayUrl: string, authToken: string | undefined, workspaceId: string | undefined, workspaceToken: string | undefined): GqlFetcher;
13
15
  export declare function usePackageUpload(workspacesGatewayUrl: string | undefined, authToken: string | undefined, workspaceId: string | undefined, getWorkspaceToken: (() => Promise<string | null>) | undefined, folderPath?: string): {
14
16
  uploading: boolean;
15
17
  progress: number;
16
18
  error: string | null;
17
19
  upload: (file: File) => Promise<PackageUploadResult>;
18
20
  };
21
+ export {};
19
22
  //# sourceMappingURL=usePackageUpload.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"usePackageUpload.d.ts","sourceRoot":"","sources":["../../../src/devportal/hooks/usePackageUpload.ts"],"names":[],"mappings":"AAuBA,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAC;IACf;;qDAEiD;IACjD,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,kBAAkB;IACjC,SAAS,EAAE,OAAO,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAoKD,wBAAgB,gBAAgB,CAC9B,oBAAoB,EAAE,MAAM,GAAG,SAAS,EACxC,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,WAAW,EAAE,MAAM,GAAG,SAAS,EAC/B,iBAAiB,EAAE,CAAC,MAAM,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC,GAAG,SAAS,EAC7D,UAAU,SAAwB;eA5KvB,OAAO;cACR,MAAM;WACT,MAAM,GAAG,IAAI;mBAmLL,IAAI,KAAG,OAAO,CAAC,mBAAmB,CAAC;EAoDnD"}
1
+ {"version":3,"file":"usePackageUpload.d.ts","sourceRoot":"","sources":["../../../src/devportal/hooks/usePackageUpload.ts"],"names":[],"mappings":"AAwBA,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAC;IACf;;qDAEiD;IACjD,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,kBAAkB;IACjC,SAAS,EAAE,OAAO,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAED,KAAK,UAAU,GAAG,CAChB,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAC/B,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;AAWtC,wBAAgB,WAAW,CACzB,oBAAoB,EAAE,MAAM,EAC5B,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,WAAW,EAAE,MAAM,GAAG,SAAS,EAC/B,cAAc,EAAE,MAAM,GAAG,SAAS,GACjC,UAAU,CAoCZ;AAsHD,wBAAgB,gBAAgB,CAC9B,oBAAoB,EAAE,MAAM,GAAG,SAAS,EACxC,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,WAAW,EAAE,MAAM,GAAG,SAAS,EAC/B,iBAAiB,EAAE,CAAC,MAAM,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC,GAAG,SAAS,EAC7D,UAAU,SAAwB;eAvLvB,OAAO;cACR,MAAM;WACT,MAAM,GAAG,IAAI;mBA8LL,IAAI,KAAG,OAAO,CAAC,mBAAmB,CAAC;EAoDnD"}
@@ -1,30 +1,39 @@
1
- import { useCallback as e, useState as t } from "react";
1
+ import { graphQLErrorFrom as e, httpErrorFrom as t } from "../utils/uploadErrors.js";
2
+ import { useCallback as n, useState as r } from "react";
2
3
  //#region src/devportal/hooks/usePackageUpload.ts
3
- function n(e, t, n, r) {
4
- return async (i, a) => {
5
- let o = { "Content-Type": "application/json" };
6
- t && (o.Authorization = `Bearer ${t}`), r && (o["X-Workspace-Authorization"] = `Bearer ${r}`), n && (o["x-workspace-id"] = n);
7
- let s = e.endsWith("/graphql") ? e : `${e}/workspaces/graphql`, c = await fetch(s, {
4
+ async function i(e) {
5
+ try {
6
+ return await e.json();
7
+ } catch {
8
+ return;
9
+ }
10
+ }
11
+ function a(n, r, a, o) {
12
+ return async (s, c) => {
13
+ let l = { "Content-Type": "application/json" };
14
+ r && (l.Authorization = `Bearer ${r}`), o && (l["X-Workspace-Authorization"] = `Bearer ${o}`), a && (l["x-workspace-id"] = a);
15
+ let u = n.endsWith("/graphql") ? n : `${n}/workspaces/graphql`, d = await fetch(u, {
8
16
  method: "POST",
9
- headers: o,
17
+ headers: l,
10
18
  credentials: "include",
11
19
  body: JSON.stringify({
12
- query: i,
13
- variables: a
20
+ query: s,
21
+ variables: c
14
22
  })
15
- });
16
- if (!c.ok) throw Error(`GraphQL request failed: ${c.status} ${c.statusText}`);
17
- let l = await c.json();
18
- if (l.errors?.length) throw Error(l.errors.map((e) => e.message).join("; "));
19
- return l.data ?? {};
23
+ }), f = await i(d);
24
+ if (!d.ok) throw t(d.status, d.statusText, f);
25
+ let p = e(f);
26
+ if (p) throw p;
27
+ let m = f?.data;
28
+ return typeof m == "object" && m ? m : {};
20
29
  };
21
30
  }
22
- async function r(e, t) {
31
+ async function o(e, t) {
23
32
  let n = (await e("mutation DevPortal_EnsureFolder($path: String!) {\n ensureFolderByPath(path: $path) { id }\n }", { path: t })).ensureFolderByPath?.id;
24
33
  if (!n) throw Error("ensureFolderByPath returned no id");
25
34
  return n;
26
35
  }
27
- async function i(e, t, n) {
36
+ async function s(e, t, n) {
28
37
  let r = t.name.includes(".") ? t.name.substring(t.name.lastIndexOf(".") + 1) : "zip", i = (await e("mutation DevPortal_InitiateUpload($input: InitiateUploadInput!) {\n initiateUpload(input: $input) {\n uploadUrl\n session { id }\n }\n }", { input: {
29
38
  fileName: t.name,
30
39
  fileSize: String(t.size),
@@ -39,7 +48,7 @@ async function i(e, t, n) {
39
48
  uploadUrl: o
40
49
  };
41
50
  }
42
- async function a(e, t, n) {
51
+ async function c(e, t, n) {
43
52
  await new Promise((r, i) => {
44
53
  let a = new XMLHttpRequest();
45
54
  a.upload.addEventListener("progress", (e) => {
@@ -49,7 +58,7 @@ async function a(e, t, n) {
49
58
  }), a.addEventListener("error", () => i(/* @__PURE__ */ Error("S3 PUT network error"))), a.addEventListener("abort", () => i(/* @__PURE__ */ Error("S3 PUT aborted"))), a.open("PUT", t), a.setRequestHeader("Content-Type", e.type || "application/zip"), a.send(e);
50
59
  });
51
60
  }
52
- async function o(e, t, n, r) {
61
+ async function l(e, t, n, r) {
53
62
  let i = (await e("mutation DevPortal_CompleteUpload($input: CompleteUploadInput!) {\n completeUpload(input: $input) {\n file { id downloadUrl publicUrl }\n }\n }", { input: {
54
63
  sessionId: t,
55
64
  folderId: n,
@@ -61,44 +70,44 @@ async function o(e, t, n, r) {
61
70
  downloadUrl: a
62
71
  };
63
72
  }
64
- function s(s, c, l, u, d = "/DevPortal/Packages") {
65
- let [f, p] = t({
73
+ function u(e, t, i, u, d = "/DevPortal/Packages") {
74
+ let [f, p] = r({
66
75
  uploading: !1,
67
76
  progress: 0,
68
77
  error: null
69
78
  });
70
79
  return {
71
- upload: e(async (e) => {
72
- if (!s) throw Error("workspacesGatewayUrl is not configured. Pass it as a prop to DevportalRoot.");
73
- let t = n(s, c, l, (u ? await u() : void 0) ?? void 0);
80
+ upload: n(async (n) => {
81
+ if (!e) throw Error("workspacesGatewayUrl is not configured. Pass it as a prop to DevportalRoot.");
82
+ let r = a(e, t, i, (u ? await u() : void 0) ?? void 0);
74
83
  p({
75
84
  uploading: !0,
76
85
  progress: 0,
77
86
  error: null
78
87
  });
79
88
  try {
80
- let n = await r(t, d);
89
+ let e = await o(r, d);
81
90
  p((e) => ({
82
91
  ...e,
83
92
  progress: 5
84
93
  }));
85
- let { sessionId: s, uploadUrl: c } = await i(t, e, n);
94
+ let { sessionId: t, uploadUrl: i } = await s(r, n, e);
86
95
  p((e) => ({
87
96
  ...e,
88
97
  progress: 10
89
- })), await a(e, c, (e) => p((t) => ({
98
+ })), await c(n, i, (e) => p((t) => ({
90
99
  ...t,
91
100
  progress: e
92
101
  }))), p((e) => ({
93
102
  ...e,
94
103
  progress: 90
95
104
  }));
96
- let l = await o(t, s, n, e.size);
105
+ let a = await l(r, t, e, n.size);
97
106
  return p({
98
107
  uploading: !1,
99
108
  progress: 100,
100
109
  error: null
101
- }), l;
110
+ }), a;
102
111
  } catch (e) {
103
112
  throw p({
104
113
  uploading: !1,
@@ -107,15 +116,15 @@ function s(s, c, l, u, d = "/DevPortal/Packages") {
107
116
  }), e;
108
117
  }
109
118
  }, [
110
- s,
111
- c,
112
- l,
119
+ e,
120
+ t,
121
+ i,
113
122
  u
114
123
  ]),
115
124
  ...f
116
125
  };
117
126
  }
118
127
  //#endregion
119
- export { s as usePackageUpload };
128
+ export { u as usePackageUpload };
120
129
 
121
130
  //# sourceMappingURL=usePackageUpload.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"usePackageUpload.js","names":[],"sources":["../../../src/devportal/hooks/usePackageUpload.ts"],"sourcesContent":["/**\n * usePackageUpload — uploads a file to wspace-files-svc and returns a URL to\n * persist (ApplicationVersion.packageUrl, a physical item's image, an app\n * icon — this hook is generic despite its name; folderPath picks the\n * destination). Every upload here is visibility: PUBLIC (see\n * initiateUpload below), so the returned URL prefers the permanent CDN\n * publicUrl over the presigned downloadUrl (~1 hour TTL, wrong for anything\n * persisted and displayed later — see wspace-files-svc's FieldResolvers.ts\n * doc comment on `downloadUrl`). Falls back to downloadUrl when publicUrl\n * is null (the public-assets CDN infra isn't provisioned in every\n * environment yet), so this keeps working exactly as before until it is.\n *\n * Flow (mirrors uploadBlobToFiles in @burdenoff/fe-libs/storage):\n * 1. ensureFolderByPath(folderPath) → folderId\n * 2. initiateUpload → { sessionId, presignedUrl }\n * 3. PUT blob to presignedUrl (progress tracked via XHR)\n * 4. completeUpload → { file.publicUrl ?? file.downloadUrl }\n *\n * Uses raw fetch for GraphQL mutations so we can target the workspace gateway\n * without a second ApolloProvider.\n */\nimport { useCallback, useState } from \"react\";\n\nexport interface PackageUploadResult {\n fileId: string;\n /** The permanent publicUrl when available, else the presigned downloadUrl\n * (field name kept as `downloadUrl` so existing callers don't need to\n * change — see the module doc comment above). */\n downloadUrl: string;\n}\n\nexport interface PackageUploadState {\n uploading: boolean;\n progress: number;\n error: string | null;\n}\n\ntype GqlFetcher = (\n query: string,\n variables: Record<string, unknown>,\n) => Promise<Record<string, unknown>>;\n\nfunction makeFetcher(\n workspacesGatewayUrl: string,\n authToken: string | undefined,\n workspaceId: string | undefined,\n workspaceToken: string | undefined,\n): GqlFetcher {\n return async (query, variables) => {\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n };\n if (authToken) headers[\"Authorization\"] = `Bearer ${authToken}`;\n if (workspaceToken)\n headers[\"X-Workspace-Authorization\"] = `Bearer ${workspaceToken}`;\n if (workspaceId) headers[\"x-workspace-id\"] = workspaceId;\n\n const endpoint = workspacesGatewayUrl.endsWith(\"/graphql\")\n ? workspacesGatewayUrl\n : `${workspacesGatewayUrl}/workspaces/graphql`;\n\n const res = await fetch(endpoint, {\n method: \"POST\",\n headers,\n credentials: \"include\",\n body: JSON.stringify({ query, variables }),\n });\n if (!res.ok) {\n throw new Error(\n `GraphQL request failed: ${res.status} ${res.statusText}`,\n );\n }\n const json = (await res.json()) as {\n data?: Record<string, unknown>;\n errors?: Array<{ message: string }>;\n };\n if (json.errors?.length) {\n throw new Error(json.errors.map((e) => e.message).join(\"; \"));\n }\n return json.data ?? {};\n };\n}\n\nasync function ensureFolder(gql: GqlFetcher, path: string): Promise<string> {\n const data = await gql(\n `mutation DevPortal_EnsureFolder($path: String!) {\n ensureFolderByPath(path: $path) { id }\n }`,\n { path },\n );\n const id = (data.ensureFolderByPath as { id?: string } | undefined)?.id;\n if (!id) throw new Error(\"ensureFolderByPath returned no id\");\n return id;\n}\n\nasync function initiateUpload(\n gql: GqlFetcher,\n file: File,\n folderId: string,\n): Promise<{ sessionId: string; uploadUrl: string }> {\n const ext = file.name.includes(\".\")\n ? file.name.substring(file.name.lastIndexOf(\".\") + 1)\n : \"zip\";\n const data = await gql(\n `mutation DevPortal_InitiateUpload($input: InitiateUploadInput!) {\n initiateUpload(input: $input) {\n uploadUrl\n session { id }\n }\n }`,\n {\n input: {\n fileName: file.name,\n fileSize: String(file.size),\n mimeType: file.type || \"application/zip\",\n extension: ext,\n folderId,\n visibility: \"PUBLIC\",\n },\n },\n );\n const result = data.initiateUpload as\n | { uploadUrl?: string; session?: { id?: string } }\n | undefined;\n const sessionId = result?.session?.id;\n const uploadUrl = result?.uploadUrl;\n if (!sessionId || !uploadUrl) {\n throw new Error(\"initiateUpload did not return sessionId or uploadUrl\");\n }\n return { sessionId, uploadUrl };\n}\n\nasync function putToPresignedUrl(\n file: File,\n uploadUrl: string,\n onProgress: (pct: number) => void,\n): Promise<void> {\n await new Promise<void>((resolve, reject) => {\n const xhr = new XMLHttpRequest();\n xhr.upload.addEventListener(\"progress\", (evt) => {\n if (evt.lengthComputable) {\n onProgress(10 + Math.round((evt.loaded / evt.total) * 80));\n }\n });\n xhr.addEventListener(\"load\", () => {\n if (xhr.status >= 200 && xhr.status < 300) {\n resolve();\n } else {\n reject(new Error(`S3 PUT failed: ${xhr.status}`));\n }\n });\n xhr.addEventListener(\"error\", () =>\n reject(new Error(\"S3 PUT network error\")),\n );\n xhr.addEventListener(\"abort\", () => reject(new Error(\"S3 PUT aborted\")));\n xhr.open(\"PUT\", uploadUrl);\n xhr.setRequestHeader(\"Content-Type\", file.type || \"application/zip\");\n xhr.send(file);\n });\n}\n\nasync function completeUpload(\n gql: GqlFetcher,\n sessionId: string,\n folderId: string,\n fileSize: number,\n): Promise<PackageUploadResult> {\n const data = await gql(\n `mutation DevPortal_CompleteUpload($input: CompleteUploadInput!) {\n completeUpload(input: $input) {\n file { id downloadUrl publicUrl }\n }\n }`,\n {\n input: {\n sessionId,\n folderId,\n fileSize: String(fileSize),\n },\n },\n );\n const file = (\n data.completeUpload as\n | {\n file?: {\n id?: string;\n downloadUrl?: string;\n publicUrl?: string | null;\n };\n }\n | undefined\n )?.file;\n const url = file?.publicUrl || file?.downloadUrl;\n if (!file?.id || !url) {\n throw new Error(\"completeUpload did not return file id or a usable URL\");\n }\n return { fileId: file.id, downloadUrl: url };\n}\n\nexport function usePackageUpload(\n workspacesGatewayUrl: string | undefined,\n authToken: string | undefined,\n workspaceId: string | undefined,\n getWorkspaceToken: (() => Promise<string | null>) | undefined,\n folderPath = \"/DevPortal/Packages\",\n) {\n const [state, setState] = useState<PackageUploadState>({\n uploading: false,\n progress: 0,\n error: null,\n });\n\n const upload = useCallback(\n async (file: File): Promise<PackageUploadResult> => {\n if (!workspacesGatewayUrl) {\n throw new Error(\n \"workspacesGatewayUrl is not configured. Pass it as a prop to DevportalRoot.\",\n );\n }\n\n const freshToken = getWorkspaceToken\n ? await getWorkspaceToken()\n : undefined;\n const gql = makeFetcher(\n workspacesGatewayUrl,\n authToken,\n workspaceId,\n freshToken ?? undefined,\n );\n\n setState({ uploading: true, progress: 0, error: null });\n try {\n const folderId = await ensureFolder(gql, folderPath);\n setState((s) => ({ ...s, progress: 5 }));\n\n const { sessionId, uploadUrl } = await initiateUpload(\n gql,\n file,\n folderId,\n );\n setState((s) => ({ ...s, progress: 10 }));\n\n await putToPresignedUrl(file, uploadUrl, (pct) =>\n setState((s) => ({ ...s, progress: pct })),\n );\n setState((s) => ({ ...s, progress: 90 }));\n\n const result = await completeUpload(\n gql,\n sessionId,\n folderId,\n file.size,\n );\n setState({ uploading: false, progress: 100, error: null });\n return result;\n } catch (err) {\n const message = err instanceof Error ? err.message : \"Upload failed\";\n setState({ uploading: false, progress: 0, error: message });\n throw err;\n }\n },\n [workspacesGatewayUrl, authToken, workspaceId, getWorkspaceToken],\n );\n\n return { upload, ...state };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"usePackageUpload.js","names":[],"sources":["../../../src/devportal/hooks/usePackageUpload.ts"],"sourcesContent":["/**\n * usePackageUpload — uploads a file to wspace-files-svc and returns a URL to\n * persist (ApplicationVersion.packageUrl, a physical item's image, an app\n * icon — this hook is generic despite its name; folderPath picks the\n * destination). Every upload here is visibility: PUBLIC (see\n * initiateUpload below), so the returned URL prefers the permanent CDN\n * publicUrl over the presigned downloadUrl (~1 hour TTL, wrong for anything\n * persisted and displayed later — see wspace-files-svc's FieldResolvers.ts\n * doc comment on `downloadUrl`). Falls back to downloadUrl when publicUrl\n * is null (the public-assets CDN infra isn't provisioned in every\n * environment yet), so this keeps working exactly as before until it is.\n *\n * Flow (mirrors uploadBlobToFiles in @burdenoff/fe-libs/storage):\n * 1. ensureFolderByPath(folderPath) → folderId\n * 2. initiateUpload → { sessionId, presignedUrl }\n * 3. PUT blob to presignedUrl (progress tracked via XHR)\n * 4. completeUpload → { file.publicUrl ?? file.downloadUrl }\n *\n * Uses raw fetch for GraphQL mutations so we can target the workspace gateway\n * without a second ApolloProvider.\n */\nimport { useCallback, useState } from \"react\";\nimport { graphQLErrorFrom, httpErrorFrom } from \"../utils/uploadErrors\";\n\nexport interface PackageUploadResult {\n fileId: string;\n /** The permanent publicUrl when available, else the presigned downloadUrl\n * (field name kept as `downloadUrl` so existing callers don't need to\n * change — see the module doc comment above). */\n downloadUrl: string;\n}\n\nexport interface PackageUploadState {\n uploading: boolean;\n progress: number;\n error: string | null;\n}\n\ntype GqlFetcher = (\n query: string,\n variables: Record<string, unknown>,\n) => Promise<Record<string, unknown>>;\n\n/** The parsed JSON body, or undefined when it is missing or not JSON. */\nasync function readJsonBody(res: Response): Promise<unknown> {\n try {\n return await res.json();\n } catch {\n return undefined;\n }\n}\n\nexport function makeFetcher(\n workspacesGatewayUrl: string,\n authToken: string | undefined,\n workspaceId: string | undefined,\n workspaceToken: string | undefined,\n): GqlFetcher {\n return async (query, variables) => {\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n };\n if (authToken) headers[\"Authorization\"] = `Bearer ${authToken}`;\n if (workspaceToken)\n headers[\"X-Workspace-Authorization\"] = `Bearer ${workspaceToken}`;\n if (workspaceId) headers[\"x-workspace-id\"] = workspaceId;\n\n const endpoint = workspacesGatewayUrl.endsWith(\"/graphql\")\n ? workspacesGatewayUrl\n : `${workspacesGatewayUrl}/workspaces/graphql`;\n\n const res = await fetch(endpoint, {\n method: \"POST\",\n headers,\n credentials: \"include\",\n body: JSON.stringify({ query, variables }),\n });\n // Read the body even on a non-2xx status: the gateway explains a quota\n // refusal (HTTP 429) in the GraphQL errors, and the status alone reads as\n // rate limiting (BOFF-10676).\n const body = await readJsonBody(res);\n if (!res.ok) {\n throw httpErrorFrom(res.status, res.statusText, body);\n }\n const inBand = graphQLErrorFrom(body);\n if (inBand) throw inBand;\n // Cast: `body` is untrusted JSON; only `data` is read, and its shape is checked below.\n const data = (body as { data?: unknown } | undefined)?.data;\n return typeof data === \"object\" && data !== null\n ? // A GraphQL `data` member is always an object keyed by root field.\n (data as Record<string, unknown>)\n : {};\n };\n}\n\nasync function ensureFolder(gql: GqlFetcher, path: string): Promise<string> {\n const data = await gql(\n `mutation DevPortal_EnsureFolder($path: String!) {\n ensureFolderByPath(path: $path) { id }\n }`,\n { path },\n );\n const id = (data.ensureFolderByPath as { id?: string } | undefined)?.id;\n if (!id) throw new Error(\"ensureFolderByPath returned no id\");\n return id;\n}\n\nasync function initiateUpload(\n gql: GqlFetcher,\n file: File,\n folderId: string,\n): Promise<{ sessionId: string; uploadUrl: string }> {\n const ext = file.name.includes(\".\")\n ? file.name.substring(file.name.lastIndexOf(\".\") + 1)\n : \"zip\";\n const data = await gql(\n `mutation DevPortal_InitiateUpload($input: InitiateUploadInput!) {\n initiateUpload(input: $input) {\n uploadUrl\n session { id }\n }\n }`,\n {\n input: {\n fileName: file.name,\n fileSize: String(file.size),\n mimeType: file.type || \"application/zip\",\n extension: ext,\n folderId,\n visibility: \"PUBLIC\",\n },\n },\n );\n const result = data.initiateUpload as\n | { uploadUrl?: string; session?: { id?: string } }\n | undefined;\n const sessionId = result?.session?.id;\n const uploadUrl = result?.uploadUrl;\n if (!sessionId || !uploadUrl) {\n throw new Error(\"initiateUpload did not return sessionId or uploadUrl\");\n }\n return { sessionId, uploadUrl };\n}\n\nasync function putToPresignedUrl(\n file: File,\n uploadUrl: string,\n onProgress: (pct: number) => void,\n): Promise<void> {\n await new Promise<void>((resolve, reject) => {\n const xhr = new XMLHttpRequest();\n xhr.upload.addEventListener(\"progress\", (evt) => {\n if (evt.lengthComputable) {\n onProgress(10 + Math.round((evt.loaded / evt.total) * 80));\n }\n });\n xhr.addEventListener(\"load\", () => {\n if (xhr.status >= 200 && xhr.status < 300) {\n resolve();\n } else {\n reject(new Error(`S3 PUT failed: ${xhr.status}`));\n }\n });\n xhr.addEventListener(\"error\", () =>\n reject(new Error(\"S3 PUT network error\")),\n );\n xhr.addEventListener(\"abort\", () => reject(new Error(\"S3 PUT aborted\")));\n xhr.open(\"PUT\", uploadUrl);\n xhr.setRequestHeader(\"Content-Type\", file.type || \"application/zip\");\n xhr.send(file);\n });\n}\n\nasync function completeUpload(\n gql: GqlFetcher,\n sessionId: string,\n folderId: string,\n fileSize: number,\n): Promise<PackageUploadResult> {\n const data = await gql(\n `mutation DevPortal_CompleteUpload($input: CompleteUploadInput!) {\n completeUpload(input: $input) {\n file { id downloadUrl publicUrl }\n }\n }`,\n {\n input: {\n sessionId,\n folderId,\n fileSize: String(fileSize),\n },\n },\n );\n const file = (\n data.completeUpload as\n | {\n file?: {\n id?: string;\n downloadUrl?: string;\n publicUrl?: string | null;\n };\n }\n | undefined\n )?.file;\n const url = file?.publicUrl || file?.downloadUrl;\n if (!file?.id || !url) {\n throw new Error(\"completeUpload did not return file id or a usable URL\");\n }\n return { fileId: file.id, downloadUrl: url };\n}\n\nexport function usePackageUpload(\n workspacesGatewayUrl: string | undefined,\n authToken: string | undefined,\n workspaceId: string | undefined,\n getWorkspaceToken: (() => Promise<string | null>) | undefined,\n folderPath = \"/DevPortal/Packages\",\n) {\n const [state, setState] = useState<PackageUploadState>({\n uploading: false,\n progress: 0,\n error: null,\n });\n\n const upload = useCallback(\n async (file: File): Promise<PackageUploadResult> => {\n if (!workspacesGatewayUrl) {\n throw new Error(\n \"workspacesGatewayUrl is not configured. Pass it as a prop to DevportalRoot.\",\n );\n }\n\n const freshToken = getWorkspaceToken\n ? await getWorkspaceToken()\n : undefined;\n const gql = makeFetcher(\n workspacesGatewayUrl,\n authToken,\n workspaceId,\n freshToken ?? undefined,\n );\n\n setState({ uploading: true, progress: 0, error: null });\n try {\n const folderId = await ensureFolder(gql, folderPath);\n setState((s) => ({ ...s, progress: 5 }));\n\n const { sessionId, uploadUrl } = await initiateUpload(\n gql,\n file,\n folderId,\n );\n setState((s) => ({ ...s, progress: 10 }));\n\n await putToPresignedUrl(file, uploadUrl, (pct) =>\n setState((s) => ({ ...s, progress: pct })),\n );\n setState((s) => ({ ...s, progress: 90 }));\n\n const result = await completeUpload(\n gql,\n sessionId,\n folderId,\n file.size,\n );\n setState({ uploading: false, progress: 100, error: null });\n return result;\n } catch (err) {\n const message = err instanceof Error ? err.message : \"Upload failed\";\n setState({ uploading: false, progress: 0, error: message });\n throw err;\n }\n },\n [workspacesGatewayUrl, authToken, workspaceId, getWorkspaceToken],\n );\n\n return { upload, ...state };\n}\n"],"mappings":"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"}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Turns a failed files-upload GraphQL call into a message a developer can act on.
3
+ *
4
+ * The workspace gateway refuses a quota-gated mutation (`completeUpload`,
5
+ * `uploadFile` and `createFolder` carry `@usage(checkQuota: true)`) with HTTP
6
+ * 429 and a GraphQL body whose first error carries
7
+ * `extensions.code` = `QUOTA_NOT_PROVISIONED` (the current workspace's plan does
8
+ * not include the quota at all) or `QUOTA_EXHAUSTED` (the allowance is used
9
+ * up). Reading only the status turned both into "GraphQL request failed: 429",
10
+ * which reads as rate limiting and gives no way forward (BOFF-10676).
11
+ *
12
+ * These helpers are pure so the upload hook stays a thin transport and the
13
+ * mapping can be unit tested without a network.
14
+ */
15
+ /** One GraphQL error, as far as these helpers read it. */
16
+ export interface GraphQLErrorLike {
17
+ message?: unknown;
18
+ extensions?: unknown;
19
+ }
20
+ /** Gateway quota block codes (wspace-public-gateway `sendQuotaExhausted`). */
21
+ export declare const QUOTA_NOT_PROVISIONED = "QUOTA_NOT_PROVISIONED";
22
+ export declare const QUOTA_EXHAUSTED = "QUOTA_EXHAUSTED";
23
+ export declare const QUOTA_NOT_PROVISIONED_MESSAGE = "File uploads are not available in this workspace because its plan does not include file storage. Switch to a workspace your plan covers, or upgrade this workspace's plan in Billing, then try again.";
24
+ export declare const QUOTA_EXHAUSTED_MESSAGE = "This workspace has used up its file storage. Delete files you no longer need or upgrade the workspace's plan in Billing, then try again.";
25
+ export declare const RATE_LIMITED_MESSAGE = "Too many requests right now. Wait a moment, then try the upload again.";
26
+ /** Error raised by the upload transport; `code` is the gateway code when there is one. */
27
+ export declare class UploadRequestError extends Error {
28
+ readonly code: string | null;
29
+ readonly status: number | null;
30
+ constructor(message: string, code: string | null, status: number | null);
31
+ }
32
+ /** Human-readable byte size (binary units, one decimal above KB). */
33
+ export declare function formatBytes(bytes: number): string;
34
+ /**
35
+ * The quota refusal in a GraphQL body, if there is one. Used for both the
36
+ * gateway's 429 response and an in-band error on a 200 response.
37
+ */
38
+ export declare function quotaErrorFrom(body: unknown): UploadRequestError | null;
39
+ /**
40
+ * The error for a non-2xx gateway response. `body` is the parsed JSON body, or
41
+ * undefined when it was missing or not JSON.
42
+ */
43
+ export declare function httpErrorFrom(status: number, statusText: string, body: unknown): UploadRequestError;
44
+ /** The error for in-band GraphQL errors on a 2xx response. */
45
+ export declare function graphQLErrorFrom(body: unknown): UploadRequestError | null;
46
+ //# sourceMappingURL=uploadErrors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"uploadErrors.d.ts","sourceRoot":"","sources":["../../../src/devportal/utils/uploadErrors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,0DAA0D;AAC1D,MAAM,WAAW,gBAAgB;IAC/B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,8EAA8E;AAC9E,eAAO,MAAM,qBAAqB,0BAA0B,CAAC;AAC7D,eAAO,MAAM,eAAe,oBAAoB,CAAC;AAEjD,eAAO,MAAM,6BAA6B,0MAC+J,CAAC;AAE1M,eAAO,MAAM,uBAAuB,6IACwG,CAAC;AAE7I,eAAO,MAAM,oBAAoB,2EACyC,CAAC;AAE3E,0FAA0F;AAC1F,qBAAa,kBAAmB,SAAQ,KAAK;IAC3C,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;gBAEnB,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI;CAMxE;AA4BD,qEAAqE;AACrE,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAYjD;AASD;;;GAGG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,OAAO,GAAG,kBAAkB,GAAG,IAAI,CAWvE;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,OAAO,GACZ,kBAAkB,CAkBpB;AAED,8DAA8D;AAC9D,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,OAAO,GAAG,kBAAkB,GAAG,IAAI,CAezE"}
@@ -0,0 +1,62 @@
1
+ var e = "File uploads are not available in this workspace because its plan does not include file storage. Switch to a workspace your plan covers, or upgrade this workspace's plan in Billing, then try again.", t = "This workspace has used up its file storage. Delete files you no longer need or upgrade the workspace's plan in Billing, then try again.", n = "Too many requests right now. Wait a moment, then try the upload again.", r = class extends Error {
2
+ code;
3
+ status;
4
+ constructor(e, t, n) {
5
+ super(e), this.name = "UploadRequestError", this.code = t, this.status = n;
6
+ }
7
+ };
8
+ function i(e) {
9
+ return typeof e == "object" && e && !Array.isArray(e) ? e : null;
10
+ }
11
+ function a(e) {
12
+ let t = i(e)?.errors;
13
+ return Array.isArray(t) ? t.filter((e) => i(e) !== null) : [];
14
+ }
15
+ function o(e) {
16
+ let t = i(e.extensions)?.code;
17
+ return typeof t == "string" ? t : null;
18
+ }
19
+ function s(e) {
20
+ let t = i(e.extensions)?.limitLeft, n = typeof t == "string" ? Number(t) : t;
21
+ return typeof n == "number" && Number.isFinite(n) && n >= 0 ? n : null;
22
+ }
23
+ function c(e) {
24
+ if (!Number.isFinite(e) || e < 0) return "0 B";
25
+ let t = [
26
+ "B",
27
+ "KB",
28
+ "MB",
29
+ "GB",
30
+ "TB"
31
+ ], n = e, r = 0;
32
+ for (; n >= 1024 && r < t.length - 1;) n /= 1024, r += 1;
33
+ return r === 0 ? `${Math.round(n)} ${t[r]}` : `${n.toFixed(1)} ${t[r]}`;
34
+ }
35
+ function l(e) {
36
+ let n = s(e);
37
+ return n === null ? t : `${t} Space left: ${c(n)}.`;
38
+ }
39
+ function u(t) {
40
+ for (let n of a(t)) {
41
+ let t = o(n);
42
+ if (t === "QUOTA_NOT_PROVISIONED") return new r(e, t, null);
43
+ if (t === "QUOTA_EXHAUSTED") return new r(l(n), t, null);
44
+ }
45
+ return null;
46
+ }
47
+ function d(e, t, i) {
48
+ let s = u(i);
49
+ if (s) return new r(s.message, s.code, e);
50
+ let c = a(i)[0];
51
+ return c && typeof c.message == "string" && c.message.trim() ? new r(c.message, o(c), e) : e === 429 ? new r(n, null, e) : new r(`GraphQL request failed: ${e}${t ? ` ${t}` : ""}`, null, e);
52
+ }
53
+ function f(e) {
54
+ let t = u(e);
55
+ if (t) return t;
56
+ let n = a(e), [i] = n;
57
+ return i ? new r(n.map((e) => typeof e.message == "string" ? e.message : "").filter(Boolean).join("; ") || "GraphQL request failed", o(i), null) : null;
58
+ }
59
+ //#endregion
60
+ export { f as graphQLErrorFrom, d as httpErrorFrom };
61
+
62
+ //# sourceMappingURL=uploadErrors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"uploadErrors.js","names":[],"sources":["../../../src/devportal/utils/uploadErrors.ts"],"sourcesContent":["/**\n * Turns a failed files-upload GraphQL call into a message a developer can act on.\n *\n * The workspace gateway refuses a quota-gated mutation (`completeUpload`,\n * `uploadFile` and `createFolder` carry `@usage(checkQuota: true)`) with HTTP\n * 429 and a GraphQL body whose first error carries\n * `extensions.code` = `QUOTA_NOT_PROVISIONED` (the current workspace's plan does\n * not include the quota at all) or `QUOTA_EXHAUSTED` (the allowance is used\n * up). Reading only the status turned both into \"GraphQL request failed: 429\",\n * which reads as rate limiting and gives no way forward (BOFF-10676).\n *\n * These helpers are pure so the upload hook stays a thin transport and the\n * mapping can be unit tested without a network.\n */\n\n/** One GraphQL error, as far as these helpers read it. */\nexport interface GraphQLErrorLike {\n message?: unknown;\n extensions?: unknown;\n}\n\n/** Gateway quota block codes (wspace-public-gateway `sendQuotaExhausted`). */\nexport const QUOTA_NOT_PROVISIONED = \"QUOTA_NOT_PROVISIONED\";\nexport const QUOTA_EXHAUSTED = \"QUOTA_EXHAUSTED\";\n\nexport const QUOTA_NOT_PROVISIONED_MESSAGE =\n \"File uploads are not available in this workspace because its plan does not include file storage. Switch to a workspace your plan covers, or upgrade this workspace's plan in Billing, then try again.\";\n\nexport const QUOTA_EXHAUSTED_MESSAGE =\n \"This workspace has used up its file storage. Delete files you no longer need or upgrade the workspace's plan in Billing, then try again.\";\n\nexport const RATE_LIMITED_MESSAGE =\n \"Too many requests right now. Wait a moment, then try the upload again.\";\n\n/** Error raised by the upload transport; `code` is the gateway code when there is one. */\nexport class UploadRequestError extends Error {\n readonly code: string | null;\n readonly status: number | null;\n\n constructor(message: string, code: string | null, status: number | null) {\n super(message);\n this.name = \"UploadRequestError\";\n this.code = code;\n this.status = status;\n }\n}\n\nfunction readRecord(value: unknown): Record<string, unknown> | null {\n return typeof value === \"object\" && value !== null && !Array.isArray(value)\n ? // Narrowed to a non-null, non-array object above; fields are read defensively below.\n (value as Record<string, unknown>)\n : null;\n}\n\nfunction errorsOf(body: unknown): GraphQLErrorLike[] {\n const errors = readRecord(body)?.errors;\n if (!Array.isArray(errors)) return [];\n return errors.filter((e): e is GraphQLErrorLike => readRecord(e) !== null);\n}\n\nfunction codeOf(error: GraphQLErrorLike): string | null {\n const code = readRecord(error.extensions)?.code;\n return typeof code === \"string\" ? code : null;\n}\n\nfunction limitLeftOf(error: GraphQLErrorLike): number | null {\n const raw = readRecord(error.extensions)?.limitLeft;\n const value = typeof raw === \"string\" ? Number(raw) : raw;\n return typeof value === \"number\" && Number.isFinite(value) && value >= 0\n ? value\n : null;\n}\n\n/** Human-readable byte size (binary units, one decimal above KB). */\nexport function formatBytes(bytes: number): string {\n if (!Number.isFinite(bytes) || bytes < 0) return \"0 B\";\n const units = [\"B\", \"KB\", \"MB\", \"GB\", \"TB\"];\n let value = bytes;\n let unit = 0;\n while (value >= 1024 && unit < units.length - 1) {\n value /= 1024;\n unit += 1;\n }\n return unit === 0\n ? `${Math.round(value)} ${units[unit]}`\n : `${value.toFixed(1)} ${units[unit]}`;\n}\n\nfunction exhaustedMessage(error: GraphQLErrorLike): string {\n const left = limitLeftOf(error);\n return left === null\n ? QUOTA_EXHAUSTED_MESSAGE\n : `${QUOTA_EXHAUSTED_MESSAGE} Space left: ${formatBytes(left)}.`;\n}\n\n/**\n * The quota refusal in a GraphQL body, if there is one. Used for both the\n * gateway's 429 response and an in-band error on a 200 response.\n */\nexport function quotaErrorFrom(body: unknown): UploadRequestError | null {\n for (const error of errorsOf(body)) {\n const code = codeOf(error);\n if (code === QUOTA_NOT_PROVISIONED) {\n return new UploadRequestError(QUOTA_NOT_PROVISIONED_MESSAGE, code, null);\n }\n if (code === QUOTA_EXHAUSTED) {\n return new UploadRequestError(exhaustedMessage(error), code, null);\n }\n }\n return null;\n}\n\n/**\n * The error for a non-2xx gateway response. `body` is the parsed JSON body, or\n * undefined when it was missing or not JSON.\n */\nexport function httpErrorFrom(\n status: number,\n statusText: string,\n body: unknown,\n): UploadRequestError {\n const quota = quotaErrorFrom(body);\n if (quota) {\n return new UploadRequestError(quota.message, quota.code, status);\n }\n const first = errorsOf(body)[0];\n if (first && typeof first.message === \"string\" && first.message.trim()) {\n return new UploadRequestError(first.message, codeOf(first), status);\n }\n if (status === 429) {\n return new UploadRequestError(RATE_LIMITED_MESSAGE, null, status);\n }\n const suffix = statusText ? ` ${statusText}` : \"\";\n return new UploadRequestError(\n `GraphQL request failed: ${status}${suffix}`,\n null,\n status,\n );\n}\n\n/** The error for in-band GraphQL errors on a 2xx response. */\nexport function graphQLErrorFrom(body: unknown): UploadRequestError | null {\n const quota = quotaErrorFrom(body);\n if (quota) return quota;\n const errors = errorsOf(body);\n const [first] = errors;\n if (!first) return null;\n const message = errors\n .map((e) => (typeof e.message === \"string\" ? e.message : \"\"))\n .filter(Boolean)\n .join(\"; \");\n return new UploadRequestError(\n message || \"GraphQL request failed\",\n codeOf(first),\n null,\n );\n}\n"],"mappings":"AAyBA,IAAa,IACX,yMAEW,IACX,4IAEW,IACX,0EAGW,IAAb,cAAwC,MAAM;CAC5C;CACA;CAEA,YAAY,GAAiB,GAAqB,GAAuB;AAIvE,EAHA,MAAM,EAAQ,EACd,KAAK,OAAO,sBACZ,KAAK,OAAO,GACZ,KAAK,SAAS;;;AAIlB,SAAS,EAAW,GAAgD;AAClE,QAAO,OAAO,KAAU,YAAY,KAAkB,CAAC,MAAM,QAAQ,EAAM,GAEtE,IACD;;AAGN,SAAS,EAAS,GAAmC;CACnD,IAAM,IAAS,EAAW,EAAK,EAAE;AAEjC,QADK,MAAM,QAAQ,EAAO,GACnB,EAAO,QAAQ,MAA6B,EAAW,EAAE,KAAK,KAAK,GADvC,EAAE;;AAIvC,SAAS,EAAO,GAAwC;CACtD,IAAM,IAAO,EAAW,EAAM,WAAW,EAAE;AAC3C,QAAO,OAAO,KAAS,WAAW,IAAO;;AAG3C,SAAS,EAAY,GAAwC;CAC3D,IAAM,IAAM,EAAW,EAAM,WAAW,EAAE,WACpC,IAAQ,OAAO,KAAQ,WAAW,OAAO,EAAI,GAAG;AACtD,QAAO,OAAO,KAAU,YAAY,OAAO,SAAS,EAAM,IAAI,KAAS,IACnE,IACA;;AAIN,SAAgB,EAAY,GAAuB;AACjD,KAAI,CAAC,OAAO,SAAS,EAAM,IAAI,IAAQ,EAAG,QAAO;CACjD,IAAM,IAAQ;EAAC;EAAK;EAAM;EAAM;EAAM;EAAK,EACvC,IAAQ,GACR,IAAO;AACX,QAAO,KAAS,QAAQ,IAAO,EAAM,SAAS,GAE5C,CADA,KAAS,MACT,KAAQ;AAEV,QAAO,MAAS,IACZ,GAAG,KAAK,MAAM,EAAM,CAAC,GAAG,EAAM,OAC9B,GAAG,EAAM,QAAQ,EAAE,CAAC,GAAG,EAAM;;AAGnC,SAAS,EAAiB,GAAiC;CACzD,IAAM,IAAO,EAAY,EAAM;AAC/B,QAAO,MAAS,OACZ,IACA,GAAG,EAAwB,eAAe,EAAY,EAAK,CAAC;;AAOlE,SAAgB,EAAe,GAA0C;AACvE,MAAK,IAAM,KAAS,EAAS,EAAK,EAAE;EAClC,IAAM,IAAO,EAAO,EAAM;AAC1B,MAAI,MAAA,wBACF,QAAO,IAAI,EAAmB,GAA+B,GAAM,KAAK;AAE1E,MAAI,MAAA,kBACF,QAAO,IAAI,EAAmB,EAAiB,EAAM,EAAE,GAAM,KAAK;;AAGtE,QAAO;;AAOT,SAAgB,EACd,GACA,GACA,GACoB;CACpB,IAAM,IAAQ,EAAe,EAAK;AAClC,KAAI,EACF,QAAO,IAAI,EAAmB,EAAM,SAAS,EAAM,MAAM,EAAO;CAElE,IAAM,IAAQ,EAAS,EAAK,CAAC;AAQ7B,QAPI,KAAS,OAAO,EAAM,WAAY,YAAY,EAAM,QAAQ,MAAM,GAC7D,IAAI,EAAmB,EAAM,SAAS,EAAO,EAAM,EAAE,EAAO,GAEjE,MAAW,MACN,IAAI,EAAmB,GAAsB,MAAM,EAAO,GAG5D,IAAI,EACT,2BAA2B,IAFd,IAAa,IAAI,MAAe,MAG7C,MACA,EACD;;AAIH,SAAgB,EAAiB,GAA0C;CACzE,IAAM,IAAQ,EAAe,EAAK;AAClC,KAAI,EAAO,QAAO;CAClB,IAAM,IAAS,EAAS,EAAK,EACvB,CAAC,KAAS;AAMhB,QALK,IAKE,IAAI,EAJK,EACb,KAAK,MAAO,OAAO,EAAE,WAAY,WAAW,EAAE,UAAU,GAAI,CAC5D,OAAO,QAAQ,CACf,KAAK,KAAK,IAEA,0BACX,EAAO,EAAM,EACb,KACD,GATkB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@burdenoff/microfe-devportal",
3
- "version": "2026.1102.1",
3
+ "version": "2026.1107.1",
4
4
  "description": "Burdenoff Developer Portal Microfrontend - Complete developer operations interface",
5
5
  "type": "module",
6
6
  "files": [