@webiny/app 6.4.11 → 6.6.0-alpha.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.
Files changed (144) hide show
  1. package/config/RouterConfig.d.ts +1 -3
  2. package/core/Plugins.d.ts +2 -1
  3. package/{plugins/NetworkErrorLinkPlugin → errors/overlay}/ErrorOverlay.js +9 -15
  4. package/errors/overlay/ErrorOverlay.js.map +1 -0
  5. package/errors/overlay/GqlErrorOverlay.js.map +1 -0
  6. package/{plugins/NetworkErrorLinkPlugin → errors/overlay}/LocalAwsLambdaTimeoutMessage.js +3 -6
  7. package/errors/overlay/LocalAwsLambdaTimeoutMessage.js.map +1 -0
  8. package/errors/overlay/StyledComponents.d.ts +7 -0
  9. package/errors/overlay/StyledComponents.js +10 -0
  10. package/errors/overlay/StyledComponents.js.map +1 -0
  11. package/errors/overlay/Typography.js.map +1 -0
  12. package/errors/overlay/assets/close_24px.js.map +1 -0
  13. package/{plugins/NetworkErrorLinkPlugin → errors/overlay}/createErrorOverlay.js +2 -2
  14. package/errors/overlay/createErrorOverlay.js.map +1 -0
  15. package/exports/admin.d.ts +1 -0
  16. package/exports/admin.js +1 -0
  17. package/features/apiStreamClient/FetchApiStreamClient.d.ts +9 -0
  18. package/features/apiStreamClient/FetchApiStreamClient.js +63 -0
  19. package/features/apiStreamClient/FetchApiStreamClient.js.map +1 -0
  20. package/features/apiStreamClient/__tests__/FetchApiStreamClient.test.d.ts +1 -0
  21. package/features/apiStreamClient/__tests__/FetchApiStreamClient.test.js +170 -0
  22. package/features/apiStreamClient/__tests__/FetchApiStreamClient.test.js.map +1 -0
  23. package/features/apiStreamClient/__tests__/readServerSentEvents.test.d.ts +1 -0
  24. package/features/apiStreamClient/__tests__/readServerSentEvents.test.js +160 -0
  25. package/features/apiStreamClient/__tests__/readServerSentEvents.test.js.map +1 -0
  26. package/features/apiStreamClient/abstractions.d.ts +44 -0
  27. package/features/apiStreamClient/abstractions.js +10 -0
  28. package/features/apiStreamClient/abstractions.js.map +1 -0
  29. package/features/apiStreamClient/feature.d.ts +3 -0
  30. package/features/apiStreamClient/feature.js +17 -0
  31. package/features/apiStreamClient/feature.js.map +1 -0
  32. package/features/apiStreamClient/index.d.ts +5 -0
  33. package/features/apiStreamClient/index.js +5 -0
  34. package/features/apiStreamClient/readServerSentEvents.d.ts +10 -0
  35. package/features/apiStreamClient/readServerSentEvents.js +29 -0
  36. package/features/apiStreamClient/readServerSentEvents.js.map +1 -0
  37. package/features/apiStreamClient/toPayloadHash.d.ts +14 -0
  38. package/features/apiStreamClient/toPayloadHash.js +7 -0
  39. package/features/apiStreamClient/toPayloadHash.js.map +1 -0
  40. package/features/router/HistoryRouterGateway.d.ts +3 -0
  41. package/features/router/HistoryRouterGateway.js +12 -0
  42. package/features/router/HistoryRouterGateway.js.map +1 -1
  43. package/features/router/Route.d.ts +1 -1
  44. package/features/router/RouterPresenter.d.ts +2 -0
  45. package/features/router/RouterPresenter.js +10 -0
  46. package/features/router/RouterPresenter.js.map +1 -1
  47. package/features/router/RouterRepository.d.ts +2 -0
  48. package/features/router/RouterRepository.js +8 -2
  49. package/features/router/RouterRepository.js.map +1 -1
  50. package/features/router/abstractions.d.ts +9 -0
  51. package/features/router/abstractions.js.map +1 -1
  52. package/index.d.ts +0 -3
  53. package/index.js +0 -3
  54. package/package.json +13 -26
  55. package/plugins/image.d.ts +1 -1
  56. package/presentation/router/components/RouteParamsSync.d.ts +22 -0
  57. package/presentation/router/components/RouteParamsSync.js +53 -0
  58. package/presentation/router/components/RouteParamsSync.js.map +1 -0
  59. package/presentation/router/hooks/useRoute.d.ts +1 -0
  60. package/presentation/router/hooks/useRoute.js +2 -1
  61. package/presentation/router/hooks/useRoute.js.map +1 -1
  62. package/presentation/router/hooks/useRouter.d.ts +1 -0
  63. package/presentation/router/hooks/useRouter.js +1 -0
  64. package/presentation/router/hooks/useRouter.js.map +1 -1
  65. package/presentation/router/index.d.ts +2 -0
  66. package/presentation/router/index.js +1 -0
  67. package/types.d.ts +0 -21
  68. package/apollo-client/InMemoryCache.d.ts +0 -8
  69. package/apollo-client/InMemoryCache.js +0 -18
  70. package/apollo-client/InMemoryCache.js.map +0 -1
  71. package/apollo-client/IntrospectionFragmentMatcher.d.ts +0 -11
  72. package/apollo-client/IntrospectionFragmentMatcher.js +0 -33
  73. package/apollo-client/IntrospectionFragmentMatcher.js.map +0 -1
  74. package/hooks/useAutocomplete/index.d.ts +0 -1
  75. package/hooks/useAutocomplete/index.js +0 -1
  76. package/hooks/useAutocomplete/useAutocomplete.d.ts +0 -11
  77. package/hooks/useAutocomplete/useAutocomplete.js +0 -17
  78. package/hooks/useAutocomplete/useAutocomplete.js.map +0 -1
  79. package/hooks/useDataList/functions/getData.d.ts +0 -2
  80. package/hooks/useDataList/functions/getData.js +0 -5
  81. package/hooks/useDataList/functions/getData.js.map +0 -1
  82. package/hooks/useDataList/functions/getError.d.ts +0 -2
  83. package/hooks/useDataList/functions/getError.js +0 -5
  84. package/hooks/useDataList/functions/getError.js.map +0 -1
  85. package/hooks/useDataList/functions/getMeta.d.ts +0 -2
  86. package/hooks/useDataList/functions/getMeta.js +0 -5
  87. package/hooks/useDataList/functions/getMeta.js.map +0 -1
  88. package/hooks/useDataList/functions/index.d.ts +0 -3
  89. package/hooks/useDataList/functions/index.js +0 -3
  90. package/hooks/useDataList/functions/searchDataByKey.d.ts +0 -2
  91. package/hooks/useDataList/functions/searchDataByKey.js +0 -13
  92. package/hooks/useDataList/functions/searchDataByKey.js.map +0 -1
  93. package/hooks/useDataList/index.d.ts +0 -1
  94. package/hooks/useDataList/index.js +0 -1
  95. package/hooks/useDataList/useDataList.d.ts +0 -35
  96. package/hooks/useDataList/useDataList.js +0 -142
  97. package/hooks/useDataList/useDataList.js.map +0 -1
  98. package/hooks/useDataList/utils/index.d.ts +0 -1
  99. package/hooks/useDataList/utils/index.js +0 -1
  100. package/hooks/useDataList/utils/prepareLoadListParams.d.ts +0 -10
  101. package/hooks/useDataList/utils/prepareLoadListParams.js +0 -29
  102. package/hooks/useDataList/utils/prepareLoadListParams.js.map +0 -1
  103. package/plugins/AddQuerySelectionPlugin.d.ts +0 -22
  104. package/plugins/AddQuerySelectionPlugin.js +0 -47
  105. package/plugins/AddQuerySelectionPlugin.js.map +0 -1
  106. package/plugins/ApolloCacheObjectIdPlugin.d.ts +0 -15
  107. package/plugins/ApolloCacheObjectIdPlugin.js +0 -17
  108. package/plugins/ApolloCacheObjectIdPlugin.js.map +0 -1
  109. package/plugins/ApolloDynamicLink.d.ts +0 -7
  110. package/plugins/ApolloDynamicLink.js +0 -29
  111. package/plugins/ApolloDynamicLink.js.map +0 -1
  112. package/plugins/ApolloLinkPlugin.d.ts +0 -13
  113. package/plugins/ApolloLinkPlugin.js +0 -23
  114. package/plugins/ApolloLinkPlugin.js.map +0 -1
  115. package/plugins/ConsoleLinkPlugin.d.ts +0 -8
  116. package/plugins/ConsoleLinkPlugin.js +0 -24
  117. package/plugins/ConsoleLinkPlugin.js.map +0 -1
  118. package/plugins/NetworkErrorLinkPlugin/ErrorOverlay.js.map +0 -1
  119. package/plugins/NetworkErrorLinkPlugin/GqlErrorOverlay.js.map +0 -1
  120. package/plugins/NetworkErrorLinkPlugin/LocalAwsLambdaTimeoutMessage.js.map +0 -1
  121. package/plugins/NetworkErrorLinkPlugin/StyledComponents.d.ts +0 -8
  122. package/plugins/NetworkErrorLinkPlugin/StyledComponents.js +0 -69
  123. package/plugins/NetworkErrorLinkPlugin/StyledComponents.js.map +0 -1
  124. package/plugins/NetworkErrorLinkPlugin/Typography.js.map +0 -1
  125. package/plugins/NetworkErrorLinkPlugin/assets/close_24px.js.map +0 -1
  126. package/plugins/NetworkErrorLinkPlugin/createErrorOverlay.js.map +0 -1
  127. package/plugins/NetworkErrorLinkPlugin.d.ts +0 -10
  128. package/plugins/NetworkErrorLinkPlugin.js +0 -53
  129. package/plugins/NetworkErrorLinkPlugin.js.map +0 -1
  130. package/plugins/OmitTypenameLinkPlugin.d.ts +0 -8
  131. package/plugins/OmitTypenameLinkPlugin.js +0 -16
  132. package/plugins/OmitTypenameLinkPlugin.js.map +0 -1
  133. package/plugins/TenantHeaderLinkPlugin.d.ts +0 -10
  134. package/plugins/TenantHeaderLinkPlugin.js +0 -28
  135. package/plugins/TenantHeaderLinkPlugin.js.map +0 -1
  136. /package/{plugins/NetworkErrorLinkPlugin → errors/overlay}/ErrorOverlay.d.ts +0 -0
  137. /package/{plugins/NetworkErrorLinkPlugin → errors/overlay}/GqlErrorOverlay.d.ts +0 -0
  138. /package/{plugins/NetworkErrorLinkPlugin → errors/overlay}/GqlErrorOverlay.js +0 -0
  139. /package/{plugins/NetworkErrorLinkPlugin → errors/overlay}/LocalAwsLambdaTimeoutMessage.d.ts +0 -0
  140. /package/{plugins/NetworkErrorLinkPlugin → errors/overlay}/Typography.d.ts +0 -0
  141. /package/{plugins/NetworkErrorLinkPlugin → errors/overlay}/Typography.js +0 -0
  142. /package/{plugins/NetworkErrorLinkPlugin → errors/overlay}/assets/close_24px.js +0 -0
  143. /package/{plugins/NetworkErrorLinkPlugin → errors/overlay}/assets/close_24px.svg +0 -0
  144. /package/{plugins/NetworkErrorLinkPlugin → errors/overlay}/createErrorOverlay.d.ts +0 -0
@@ -25,7 +25,5 @@ export declare const RouterConfig: (({ priority, children }: import("@webiny/rea
25
25
  displayName: string;
26
26
  }>) => (props: unknown) => React.JSX.Element;
27
27
  };
28
- useRouterConfig: () => {
29
- routes: RouteConfig[];
30
- };
28
+ useRouterConfig: typeof useRouterConfig;
31
29
  };
package/core/Plugins.d.ts CHANGED
@@ -3,7 +3,8 @@ export declare const PluginsContext: React.Context<boolean>;
3
3
  interface PluginsProviderComponentProps {
4
4
  children: React.JSX.Element[];
5
5
  }
6
- export declare const PluginsProvider: React.MemoExoticComponent<({ children }: PluginsProviderComponentProps) => React.JSX.Element>;
6
+ declare const PluginsProviderComponent: ({ children }: PluginsProviderComponentProps) => React.JSX.Element;
7
+ export declare const PluginsProvider: React.MemoExoticComponent<typeof PluginsProviderComponent>;
7
8
  /**
8
9
  * @param children
9
10
  * @deprecated This component should not be used directly. Use the <Plugin> component to create plugins.
@@ -10,32 +10,26 @@ const ErrorOverlay = (props)=>{
10
10
  const [open, setOpen] = useState(true);
11
11
  if (!open) return null;
12
12
  return /*#__PURE__*/ react.createElement(OverlayWrapper, null, /*#__PURE__*/ react.createElement("div", {
13
- className: "inner"
13
+ className: "box-border flex flex-col w-full h-full overflow-auto bg-white p-4"
14
14
  }, /*#__PURE__*/ react.createElement("div", {
15
- className: "header"
15
+ className: "flex justify-between items-center mb-4"
16
16
  }, /*#__PURE__*/ react.createElement("div", {
17
- className: "header__title"
17
+ className: "text-[color:var(--mdc-theme-error)]"
18
18
  }, /*#__PURE__*/ react.createElement(Typography, {
19
19
  use: "headline"
20
- }, title)), false !== closeable && /*#__PURE__*/ react.createElement("div", {
21
- className: "header__action"
22
- }, /*#__PURE__*/ react.createElement("span", {
20
+ }, title)), false !== closeable && /*#__PURE__*/ react.createElement("div", null, /*#__PURE__*/ react.createElement("span", {
23
21
  onClick: ()=>setOpen(false)
24
- }, /*#__PURE__*/ react.createElement(ReactComponent, null)))), /*#__PURE__*/ react.createElement("div", {
25
- className: "body"
26
- }, /*#__PURE__*/ react.createElement("div", {
27
- className: "body__summary"
28
- }, message), description && /*#__PURE__*/ react.createElement("div", {
29
- className: "body__description"
30
- }, description)), /*#__PURE__*/ react.createElement("div", {
31
- className: "footer"
22
+ }, /*#__PURE__*/ react.createElement(ReactComponent, null)))), /*#__PURE__*/ react.createElement("div", null, /*#__PURE__*/ react.createElement("div", {
23
+ className: "mb-4"
24
+ }, message), description && /*#__PURE__*/ react.createElement("div", null, description)), /*#__PURE__*/ react.createElement("div", {
25
+ className: "text-[color:var(--mdc-theme-text-secondary-on-background)]"
32
26
  }, /*#__PURE__*/ react.createElement(Typography, {
33
27
  use: "body"
34
28
  }, t`
35
29
  This screen is only visible if {message} environment variable is set.
36
30
  Read more about it in the {link}. `({
37
31
  message: /*#__PURE__*/ react.createElement("span", {
38
- className: "highlight"
32
+ className: "bg-[rgba(251,245,180,0.5)] px-1 rounded-[6px] font-mono"
39
33
  }, "`REACT_APP_DEBUG=true`"),
40
34
  link: /*#__PURE__*/ react.createElement("a", {
41
35
  href: ENVIRONMENT_VARIABLES_ARTICLE_LINK,
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors/overlay/ErrorOverlay.js","sources":["../../../src/errors/overlay/ErrorOverlay.tsx"],"sourcesContent":["import React, { useState } from \"react\";\nimport { Typography } from \"./Typography.js\";\nimport { i18n } from \"../../i18n/index.js\";\nimport { OverlayWrapper } from \"./StyledComponents.js\";\nimport { ReactComponent as CloseIcon } from \"./assets/close_24px.svg\";\n\nconst t = i18n.ns(\"app/graphql/error-overlay\");\n\nconst ENVIRONMENT_VARIABLES_ARTICLE_LINK =\n \"https://www.webiny.com/docs/how-to-guides/environment-variables\";\n\ntype ErrorOverlayProps = Partial<{\n title: React.ReactNode;\n message: React.ReactNode;\n description: React.ReactNode;\n closeable?: boolean;\n}>;\n\nexport const ErrorOverlay = (props: ErrorOverlayProps) => {\n const { title = \"An error occurred\", message, description, closeable } = props;\n const [open, setOpen] = useState(true);\n if (!open) {\n return null;\n }\n\n return (\n <OverlayWrapper>\n <div className={\"box-border flex flex-col w-full h-full overflow-auto bg-white p-4\"}>\n <div className={\"flex justify-between items-center mb-4\"}>\n <div className={\"text-[color:var(--mdc-theme-error)]\"}>\n <Typography use={\"headline\"}>{title}</Typography>\n </div>\n {closeable !== false && (\n <div>\n <span onClick={() => setOpen(false)}>\n <CloseIcon />\n </span>\n </div>\n )}\n </div>\n <div>\n <div className={\"mb-4\"}>{message}</div>\n {description && <div>{description}</div>}\n </div>\n <div className={\"text-[color:var(--mdc-theme-text-secondary-on-background)]\"}>\n <Typography use={\"body\"}>\n {t`\n This screen is only visible if {message} environment variable is set.\n Read more about it in the {link}. `({\n message: (\n <span\n className={\n \"bg-[rgba(251,245,180,0.5)] px-1 rounded-[6px] font-mono\"\n }\n >\n `REACT_APP_DEBUG=true`\n </span>\n ),\n link: (\n <a\n href={ENVIRONMENT_VARIABLES_ARTICLE_LINK}\n target={\"_blank\"}\n rel={\"noreferrer noopener\"}\n >\n environment variables article\n </a>\n )\n })}\n <br />\n {t`Open your browser's developer console to further inspect this error.`}\n </Typography>\n </div>\n </div>\n </OverlayWrapper>\n );\n};\n"],"names":["t","i18n","ENVIRONMENT_VARIABLES_ARTICLE_LINK","ErrorOverlay","props","title","message","description","closeable","open","setOpen","useState","OverlayWrapper","Typography","CloseIcon"],"mappings":";;;;;AAMA,MAAMA,IAAIC,KAAK,EAAE,CAAC;AAElB,MAAMC,qCACF;AASG,MAAMC,eAAe,CAACC;IACzB,MAAM,EAAEC,QAAQ,mBAAmB,EAAEC,OAAO,EAAEC,WAAW,EAAEC,SAAS,EAAE,GAAGJ;IACzE,MAAM,CAACK,MAAMC,QAAQ,GAAGC,SAAS;IACjC,IAAI,CAACF,MACD,OAAO;IAGX,OAAO,WAAP,GACI,oBAACG,gBAAcA,MAAAA,WAAAA,GACX,oBAAC;QAAI,WAAW;qBACZ,oBAAC;QAAI,WAAW;qBACZ,oBAAC;QAAI,WAAW;qBACZ,oBAACC,YAAUA;QAAC,KAAK;OAAaR,SAEjCG,AAAc,UAAdA,aAAuB,WAAT,GACX,oBAAC,2BACG,oBAAC;QAAK,SAAS,IAAME,QAAQ;qBACzB,oBAACI,gBAASA,UAAAA,WAAAA,GAK1B,oBAAC,2BACG,oBAAC;QAAI,WAAW;OAASR,UACxBC,eAAe,WAAfA,GAAe,oBAAC,aAAKA,eAAAA,WAAAA,GAE1B,oBAAC;QAAI,WAAW;qBACZ,oBAACM,YAAUA;QAAC,KAAK;OACZb,CAAC,CAAC;;0DAE+B,CAAC,CAAC;QAChC,SAAS,WAAT,GACI,oBAAC;YACG,WACI;WAEP;QAIL,MAAM,WAAN,GACI,oBAAC;YACG,MAAME;YACN,QAAQ;YACR,KAAK;WACR;IAIT,kBACA,oBAAC,aACAF,CAAC,CAAC,oEAAoE,CAAC;AAMhG"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors/overlay/GqlErrorOverlay.js","sources":["../../../src/errors/overlay/GqlErrorOverlay.tsx"],"sourcesContent":["import React from \"react\";\nimport get from \"lodash/get.js\";\nimport { Typography } from \"./Typography.js\";\nimport { i18n } from \"../../i18n/index.js\";\nimport { Pre } from \"./StyledComponents.js\";\nimport { ErrorOverlay } from \"./ErrorOverlay.js\";\n\nconst t = i18n.ns(\"app/graphql/error-overlay\");\n\ninterface ErrorOverlayProps {\n query: string;\n networkError: {\n message: string;\n result?: {\n error?: {\n stack?: string;\n };\n };\n };\n}\n\nexport const GqlErrorOverlay = (props: ErrorOverlayProps) => {\n const { query, networkError } = props;\n const stackTrace = get(networkError, \"result.error.stack\");\n\n const title = networkError.message;\n\n const message = (\n <>\n <Typography use={\"subtitle\"}>{t`Error occurred while executing operation:`}</Typography>\n <Pre>\n <code>{query}</code>\n </Pre>\n </>\n );\n\n let description: React.ReactNode = null;\n if (stackTrace) {\n description = (\n <>\n <Typography use={\"subtitle\"}>{t`Complete stack trace:`}</Typography>\n <Pre>\n <code>{stackTrace}</code>\n </Pre>\n </>\n );\n }\n\n return <ErrorOverlay title={title} message={message} description={description} />;\n};\n"],"names":["t","i18n","GqlErrorOverlay","props","query","networkError","stackTrace","get","title","message","Typography","Pre","description","ErrorOverlay"],"mappings":";;;;;;AAOA,MAAMA,IAAIC,KAAK,EAAE,CAAC;AAcX,MAAMC,kBAAkB,CAACC;IAC5B,MAAM,EAAEC,KAAK,EAAEC,YAAY,EAAE,GAAGF;IAChC,MAAMG,aAAaC,IAAIF,cAAc;IAErC,MAAMG,QAAQH,aAAa,OAAO;IAElC,MAAMI,UAAU,WAAVA,GACF,wDACI,oBAACC,YAAUA;QAAC,KAAK;OAAaV,CAAC,CAAC,yCAAyC,CAAC,iBAC1E,oBAACW,KAAGA,MAAAA,WAAAA,GACA,oBAAC,cAAMP;IAKnB,IAAIQ,cAA+B;IACnC,IAAIN,YACAM,cAAc,WAAdA,GACI,wDACI,oBAACF,YAAUA;QAAC,KAAK;OAAaV,CAAC,CAAC,qBAAqB,CAAC,iBACtD,oBAACW,KAAGA,MAAAA,WAAAA,GACA,oBAAC,cAAML;IAMvB,OAAO,WAAP,GAAO,oBAACO,cAAYA;QAAC,OAAOL;QAAO,SAASC;QAAS,aAAaG;;AACtE"}
@@ -1,10 +1,7 @@
1
1
  import react from "react";
2
- import styled from "@emotion/styled";
3
- const Code = styled.code`
4
- font-family: monospace;
5
- font-size: 0.85rem;
6
- line-height: 1rem;
7
- `;
2
+ const Code = ({ children })=>/*#__PURE__*/ react.createElement("code", {
3
+ className: "font-mono text-[0.85rem] leading-[1rem]"
4
+ }, children);
8
5
  const LocalAwsLambdaTimeoutMessage = ()=>/*#__PURE__*/ react.createElement(react.Fragment, null, "Local AWS Lambda function execution timed out.", /*#__PURE__*/ react.createElement("br", null), /*#__PURE__*/ react.createElement("br", null), "Did you stop the\xa0", /*#__PURE__*/ react.createElement("a", {
9
6
  href: "https://www.webiny.com/docs/core-development-concepts/basics/watch-command",
10
7
  rel: "noreferrer noopener",
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors/overlay/LocalAwsLambdaTimeoutMessage.js","sources":["../../../src/errors/overlay/LocalAwsLambdaTimeoutMessage.tsx"],"sourcesContent":["import React from \"react\";\n\nconst Code = ({ children }: { children: React.ReactNode }) => (\n <code className={\"font-mono text-[0.85rem] leading-[1rem]\"}>{children}</code>\n);\n\nexport const LocalAwsLambdaTimeoutMessage = () => (\n <>\n Local AWS Lambda function execution timed out.\n <br />\n <br />\n Did you stop the&nbsp;\n <a\n href={\"https://www.webiny.com/docs/core-development-concepts/basics/watch-command\"}\n rel={\"noreferrer noopener\"}\n target={\"_blank\"}\n >\n <Code>webiny watch</Code>\n </a>\n &nbsp;command? If so, please restart the command or deploy your changes via the&nbsp;\n <a\n href={\"https://www.webiny.com/docs/core-development-concepts/basics/project-deployment\"}\n rel={\"noreferrer noopener\"}\n target={\"_blank\"}\n >\n <Code>webiny deploy</Code>\n </a>\n &nbsp;command. For example: <Code>yarn webiny deploy api --env dev</Code>.\n <br />\n <br />\n Learn more:&nbsp;\n <a\n href={\"https://webiny.link/local-aws-lambda-development\"}\n rel={\"noreferrer noopener\"}\n target={\"_blank\"}\n >\n https://webiny.link/local-aws-lambda-development\n </a>\n </>\n);\n"],"names":["Code","children","LocalAwsLambdaTimeoutMessage"],"mappings":";AAEA,MAAMA,OAAO,CAAC,EAAEC,QAAQ,EAAiC,iBACrD,oBAAC;QAAK,WAAW;OAA4CA;AAG1D,MAAMC,+BAA+B,kBACxC,0CAAE,gEAEE,oBAAC,2BACD,oBAAC,aAAK,sCAEN,oBAAC;QACG,MAAM;QACN,KAAK;QACL,QAAQ;qBAER,oBAACF,MAAIA,MAAC,kBACN,mGAEJ,oBAAC;QACG,MAAM;QACN,KAAK;QACL,QAAQ;qBAER,oBAACA,MAAIA,MAAC,mBACN,4CACwB,oBAACA,MAAIA,MAAC,qCAAuC,mBACzE,oBAAC,2BACD,oBAAC,aAAK,iCAEN,oBAAC;QACG,MAAM;QACN,KAAK;QACL,QAAQ;OACX"}
@@ -0,0 +1,7 @@
1
+ import React from "react";
2
+ interface WrapperProps {
3
+ children: React.ReactNode;
4
+ }
5
+ export declare const OverlayWrapper: ({ children }: WrapperProps) => React.JSX.Element;
6
+ export declare const Pre: ({ children }: WrapperProps) => React.JSX.Element;
7
+ export {};
@@ -0,0 +1,10 @@
1
+ import react from "react";
2
+ const OverlayWrapper = ({ children })=>/*#__PURE__*/ react.createElement("div", {
3
+ className: "fixed top-0 left-0 w-screen h-screen z-[105]"
4
+ }, children);
5
+ const Pre = ({ children })=>/*#__PURE__*/ react.createElement("pre", {
6
+ className: "relative block p-[0.5em] my-[0.5em] overflow-x-auto whitespace-pre-wrap rounded text-inherit bg-[rgba(251,245,180,0.3)] [&_code]:font-mono [&_code]:text-[0.85rem] [&_code]:leading-[1rem]"
7
+ }, children);
8
+ export { OverlayWrapper, Pre };
9
+
10
+ //# sourceMappingURL=StyledComponents.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors/overlay/StyledComponents.js","sources":["../../../src/errors/overlay/StyledComponents.tsx"],"sourcesContent":["import React from \"react\";\n\ninterface WrapperProps {\n children: React.ReactNode;\n}\n\n// z-index 105 is intentionally above Dialogs (whose highest z-index is 100).\nexport const OverlayWrapper = ({ children }: WrapperProps) => (\n <div className={\"fixed top-0 left-0 w-screen h-screen z-[105]\"}>{children}</div>\n);\n\nexport const Pre = ({ children }: WrapperProps) => (\n <pre\n className={\n \"relative block p-[0.5em] my-[0.5em] overflow-x-auto whitespace-pre-wrap rounded text-inherit bg-[rgba(251,245,180,0.3)] [&_code]:font-mono [&_code]:text-[0.85rem] [&_code]:leading-[1rem]\"\n }\n >\n {children}\n </pre>\n);\n"],"names":["OverlayWrapper","children","Pre"],"mappings":";AAOO,MAAMA,iBAAiB,CAAC,EAAEC,QAAQ,EAAgB,iBACrD,oBAAC;QAAI,WAAW;OAAiDA;AAG9D,MAAMC,MAAM,CAAC,EAAED,QAAQ,EAAgB,iBAC1C,oBAAC;QACG,WACI;OAGHA"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors/overlay/Typography.js","sources":["../../../src/errors/overlay/Typography.tsx"],"sourcesContent":["import React from \"react\";\n\nexport declare type TypographyT = \"headline\" | \"body\" | \"subtitle\";\n\nexport interface TypographyProps {\n use: TypographyT;\n children?: React.ReactNode;\n className?: string;\n}\n\nexport const Typography = (props: TypographyProps) => {\n const { children, use, className } = props;\n\n if (use === \"headline\") {\n return <h4 className={className}>{children}</h4>;\n }\n\n return <p className={className}>{children}</p>;\n};\n"],"names":["Typography","props","children","use","className"],"mappings":";AAUO,MAAMA,aAAa,CAACC;IACvB,MAAM,EAAEC,QAAQ,EAAEC,GAAG,EAAEC,SAAS,EAAE,GAAGH;IAErC,IAAIE,AAAQ,eAARA,KACA,OAAO,WAAP,GAAO,oBAAC;QAAG,WAAWC;OAAYF;IAGtC,OAAO,WAAP,GAAO,oBAAC;QAAE,WAAWE;OAAYF;AACrC"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors/overlay/assets/close_24px.js","sources":["../../../../src/errors/overlay/assets/close_24px.svg"],"sourcesContent":["import * as React from \"react\";\nconst SvgClose24Px = props => <svg xmlns=\"http://www.w3.org/2000/svg\" width={24} height={24} viewBox=\"0 0 24 24\" {...props}><path fill=\"none\" d=\"M0 0h24v24H0z\" /><path d=\"M18.3 5.71a.996.996 0 0 0-1.41 0L12 10.59 7.11 5.7A.996.996 0 1 0 5.7 7.11L10.59 12 5.7 16.89a.996.996 0 1 0 1.41 1.41L12 13.41l4.89 4.89a.996.996 0 1 0 1.41-1.41L13.41 12l4.89-4.89c.38-.38.38-1.02 0-1.4\" /></svg>;\nexport { SvgClose24Px as ReactComponent };\nexport default \"__RSLIB_SVGR_AUTO_PUBLIC_PATH__static/svg/close_24px.33adaadc.svg\";"],"names":["SvgClose24Px","props"],"mappings":";;AACA,MAAMA,eAAeC,CAAAA,QAAAA,WAAAA,GAAS,sCAAC;QAAI,OAAM;QAA6B,OAAO;QAAI,QAAQ;QAAI,SAAQ;QAAa,GAAGA,KAAK;qBAAE,sCAAC;QAAK,MAAK;QAAO,GAAE;sBAAkB,sCAAC;QAAK,GAAE;;AAE1K"}
@@ -9,7 +9,7 @@ const createErrorOverlay = (params)=>{
9
9
  const root = createRoot(container);
10
10
  root.render(element);
11
11
  };
12
- const NetworkErrorLinkPlugin_createErrorOverlay = createErrorOverlay;
13
- export default NetworkErrorLinkPlugin_createErrorOverlay;
12
+ const overlay_createErrorOverlay = createErrorOverlay;
13
+ export default overlay_createErrorOverlay;
14
14
 
15
15
  //# sourceMappingURL=createErrorOverlay.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors/overlay/createErrorOverlay.js","sources":["../../../src/errors/overlay/createErrorOverlay.tsx"],"sourcesContent":["import type React from \"react\";\nimport { createRoot } from \"react-dom/client\";\n\ninterface CreateErrorOverlayParams {\n element: React.ReactElement;\n closeable?: boolean;\n}\n\nconst createErrorOverlay = (params: CreateErrorOverlayParams): void => {\n const { element } = params;\n // If the element already present in DOM, return immediately.\n if (document.getElementById(\"overlay-root\")) {\n return;\n }\n // Create root element to hold React tree.\n const container: HTMLDivElement = document.createElement(\"div\");\n container.id = \"overlay-root\";\n // Insert root element into body.\n const body: HTMLBodyElement = document.getElementsByTagName(\"body\")[0];\n body.appendChild(container);\n // Mount the ErrorOverlay component into root element.\n const root = createRoot(container);\n root.render(element);\n};\n\nexport default createErrorOverlay;\n"],"names":["createErrorOverlay","params","element","document","container","body","root","createRoot"],"mappings":";AAQA,MAAMA,qBAAqB,CAACC;IACxB,MAAM,EAAEC,OAAO,EAAE,GAAGD;IAEpB,IAAIE,SAAS,cAAc,CAAC,iBACxB;IAGJ,MAAMC,YAA4BD,SAAS,aAAa,CAAC;IACzDC,UAAU,EAAE,GAAG;IAEf,MAAMC,OAAwBF,SAAS,oBAAoB,CAAC,OAAO,CAAC,EAAE;IACtEE,KAAK,WAAW,CAACD;IAEjB,MAAME,OAAOC,WAAWH;IACxBE,KAAK,MAAM,CAACJ;AAChB;AAEA,mCAAeF"}
@@ -1,4 +1,5 @@
1
1
  export { MainGraphQLClient } from "../features/mainGraphQLClient/index.js";
2
+ export { ApiStreamClient, readServerSentEvents } from "../features/apiStreamClient/index.js";
2
3
  export { useFeature } from "../shared/di/useFeature.js";
3
4
  export { NetworkErrorEventHandler } from "../errors/index.js";
4
5
  export { createProviderPlugin } from "../core/createProviderPlugin.js";
package/exports/admin.js CHANGED
@@ -1,4 +1,5 @@
1
1
  export { MainGraphQLClient } from "../features/mainGraphQLClient/index.js";
2
+ export { ApiStreamClient, readServerSentEvents } from "../features/apiStreamClient/index.js";
2
3
  export { useFeature } from "../shared/di/useFeature.js";
3
4
  export { NetworkErrorEventHandler } from "../errors/index.js";
4
5
  export { createProviderPlugin } from "../core/createProviderPlugin.js";
@@ -0,0 +1,9 @@
1
+ import { ApiStreamClient } from "./abstractions.js";
2
+ import { EnvConfig } from "../../features/envConfig/index.js";
3
+ declare class ApiStreamClientImpl implements ApiStreamClient.Interface {
4
+ private readonly apiUrl;
5
+ constructor(envConfig: EnvConfig.Interface);
6
+ execute(params: ApiStreamClient.Request): Promise<ApiStreamClient.Response>;
7
+ }
8
+ export declare const FetchApiStreamClient: import("@webiny/di").Implementation<typeof ApiStreamClientImpl>;
9
+ export {};
@@ -0,0 +1,63 @@
1
+ import { createImplementation } from "@webiny/di";
2
+ import { ApiStreamClient, ApiStreamRequestError } from "./abstractions.js";
3
+ import { EnvConfig } from "../envConfig/index.js";
4
+ import { toPayloadHash } from "./toPayloadHash.js";
5
+ function toFetchHeaders(headers = {}) {
6
+ const result = {};
7
+ for (const [key, value] of Object.entries(headers))if (void 0 !== value) result[key] = String(value);
8
+ return result;
9
+ }
10
+ function joinUrl(base, path) {
11
+ return `${base.replace(/\/+$/, "")}/${path.replace(/^\/+/, "")}`;
12
+ }
13
+ async function toRequestError(response) {
14
+ let message = `Request failed with status ${response.status}.`;
15
+ let code;
16
+ try {
17
+ const json = await response.json();
18
+ if (json?.message) message = json.message;
19
+ if (json?.code) code = json.code;
20
+ } catch {}
21
+ return new ApiStreamRequestError(message, response.status, code);
22
+ }
23
+ class ApiStreamClientImpl {
24
+ constructor(envConfig){
25
+ this.apiUrl = envConfig.get("apiUrl");
26
+ }
27
+ async execute(params) {
28
+ const hasBody = void 0 !== params.body;
29
+ const headers = {
30
+ accept: "text/event-stream"
31
+ };
32
+ if (hasBody) headers["content-type"] = "application/json";
33
+ Object.assign(headers, toFetchHeaders(params.headers));
34
+ const body = hasBody ? JSON.stringify(params.body) : void 0;
35
+ const url = joinUrl(this.apiUrl, params.path);
36
+ if (void 0 !== body) headers["x-amz-content-sha256"] = await toPayloadHash(body);
37
+ let response;
38
+ try {
39
+ response = await fetch(url, {
40
+ method: "POST",
41
+ headers,
42
+ body,
43
+ signal: params.signal
44
+ });
45
+ } catch (err) {
46
+ if (err instanceof DOMException && "AbortError" === err.name) throw err;
47
+ throw new Error(`Network error: ${err.message}`);
48
+ }
49
+ if (!response.ok) throw await toRequestError(response);
50
+ if (!response.body) throw new ApiStreamRequestError("The response carried no readable body.", response.status);
51
+ return response;
52
+ }
53
+ }
54
+ const FetchApiStreamClient = createImplementation({
55
+ abstraction: ApiStreamClient,
56
+ implementation: ApiStreamClientImpl,
57
+ dependencies: [
58
+ EnvConfig
59
+ ]
60
+ });
61
+ export { FetchApiStreamClient };
62
+
63
+ //# sourceMappingURL=FetchApiStreamClient.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"features/apiStreamClient/FetchApiStreamClient.js","sources":["../../../src/features/apiStreamClient/FetchApiStreamClient.ts"],"sourcesContent":["import { createImplementation } from \"@webiny/di\";\nimport { ApiStreamClient, ApiStreamRequestError } from \"./abstractions.js\";\nimport { EnvConfig } from \"~/features/envConfig/index.js\";\nimport { toPayloadHash } from \"./toPayloadHash.js\";\n\nfunction toFetchHeaders(headers: ApiStreamClient.Headers = {}): Record<string, string> {\n const result: Record<string, string> = {};\n for (const [key, value] of Object.entries(headers)) {\n if (value !== undefined) {\n result[key] = String(value);\n }\n }\n return result;\n}\n\nfunction joinUrl(base: string, path: string): string {\n return `${base.replace(/\\/+$/, \"\")}/${path.replace(/^\\/+/, \"\")}`;\n}\n\n/**\n * Streaming routes answer with a normal JSON error for anything they detect BEFORE opening the\n * stream (unknown file, no permission, bad input), which is why those arrive here as a non-2xx\n * rather than as an in-stream event.\n */\nasync function toRequestError(response: Response): Promise<ApiStreamRequestError> {\n let message = `Request failed with status ${response.status}.`;\n let code: string | undefined;\n\n try {\n const json = await response.json();\n if (json?.message) {\n message = json.message;\n }\n if (json?.code) {\n code = json.code;\n }\n } catch {\n // Non-JSON error body — keep the status-based message.\n }\n\n return new ApiStreamRequestError(message, response.status, code);\n}\n\nclass ApiStreamClientImpl implements ApiStreamClient.Interface {\n private readonly apiUrl: string;\n\n constructor(envConfig: EnvConfig.Interface) {\n // The API root, not `graphqlApiUrl` — streaming routes live beside /graphql, not under it.\n this.apiUrl = envConfig.get(\"apiUrl\");\n }\n\n async execute(params: ApiStreamClient.Request): Promise<ApiStreamClient.Response> {\n const hasBody = params.body !== undefined;\n\n const headers: Record<string, string> = { accept: \"text/event-stream\" };\n if (hasBody) {\n headers[\"content-type\"] = \"application/json\";\n }\n Object.assign(headers, toFetchHeaders(params.headers));\n\n const body = hasBody ? JSON.stringify(params.body) : undefined;\n const url = joinUrl(this.apiUrl, params.path);\n\n /*\n * CloudFront's Origin Access Control signs this request but does not hash the body — it trusts\n * this header for the payload hash, and the Function URL's authorizer checks it against the\n * body it received. Without it a POST carrying a body is rejected with\n * `InvalidSignatureException`, while a bodyless one succeeds. Harmless on transports that do\n * not sign.\n */\n if (body !== undefined) {\n headers[\"x-amz-content-sha256\"] = await toPayloadHash(body);\n }\n\n let response: Response;\n try {\n // Always POST. A streaming route is an action, and POST keeps the parameters in a body\n // rather than a cacheable URL — CloudFront caches GET/HEAD.\n response = await fetch(url, {\n method: \"POST\",\n headers,\n body,\n signal: params.signal\n });\n } catch (err) {\n // Preserve an abort: it isn't a network failure and callers need to tell them apart.\n if (err instanceof DOMException && err.name === \"AbortError\") {\n throw err;\n }\n throw new Error(`Network error: ${(err as Error).message}`);\n }\n\n if (!response.ok) {\n throw await toRequestError(response);\n }\n\n if (!response.body) {\n throw new ApiStreamRequestError(\n \"The response carried no readable body.\",\n response.status\n );\n }\n\n return response;\n }\n}\n\nexport const FetchApiStreamClient = createImplementation({\n abstraction: ApiStreamClient,\n implementation: ApiStreamClientImpl,\n dependencies: [EnvConfig]\n});\n"],"names":["toFetchHeaders","headers","result","key","value","Object","undefined","String","joinUrl","base","path","toRequestError","response","message","code","json","ApiStreamRequestError","ApiStreamClientImpl","envConfig","params","hasBody","body","JSON","url","toPayloadHash","fetch","err","DOMException","Error","FetchApiStreamClient","createImplementation","ApiStreamClient","EnvConfig"],"mappings":";;;;AAKA,SAASA,eAAeC,UAAmC,CAAC,CAAC;IACzD,MAAMC,SAAiC,CAAC;IACxC,KAAK,MAAM,CAACC,KAAKC,MAAM,IAAIC,OAAO,OAAO,CAACJ,SACtC,IAAIG,AAAUE,WAAVF,OACAF,MAAM,CAACC,IAAI,GAAGI,OAAOH;IAG7B,OAAOF;AACX;AAEA,SAASM,QAAQC,IAAY,EAAEC,IAAY;IACvC,OAAO,GAAGD,KAAK,OAAO,CAAC,QAAQ,IAAI,CAAC,EAAEC,KAAK,OAAO,CAAC,QAAQ,KAAK;AACpE;AAOA,eAAeC,eAAeC,QAAkB;IAC5C,IAAIC,UAAU,CAAC,2BAA2B,EAAED,SAAS,MAAM,CAAC,CAAC,CAAC;IAC9D,IAAIE;IAEJ,IAAI;QACA,MAAMC,OAAO,MAAMH,SAAS,IAAI;QAChC,IAAIG,MAAM,SACNF,UAAUE,KAAK,OAAO;QAE1B,IAAIA,MAAM,MACND,OAAOC,KAAK,IAAI;IAExB,EAAE,OAAM,CAER;IAEA,OAAO,IAAIC,sBAAsBH,SAASD,SAAS,MAAM,EAAEE;AAC/D;AAEA,MAAMG;IAGF,YAAYC,SAA8B,CAAE;QAExC,IAAI,CAAC,MAAM,GAAGA,UAAU,GAAG,CAAC;IAChC;IAEA,MAAM,QAAQC,MAA+B,EAAqC;QAC9E,MAAMC,UAAUD,AAAgBb,WAAhBa,OAAO,IAAI;QAE3B,MAAMlB,UAAkC;YAAE,QAAQ;QAAoB;QACtE,IAAImB,SACAnB,OAAO,CAAC,eAAe,GAAG;QAE9BI,OAAO,MAAM,CAACJ,SAASD,eAAemB,OAAO,OAAO;QAEpD,MAAME,OAAOD,UAAUE,KAAK,SAAS,CAACH,OAAO,IAAI,IAAIb;QACrD,MAAMiB,MAAMf,QAAQ,IAAI,CAAC,MAAM,EAAEW,OAAO,IAAI;QAS5C,IAAIE,AAASf,WAATe,MACApB,OAAO,CAAC,uBAAuB,GAAG,MAAMuB,cAAcH;QAG1D,IAAIT;QACJ,IAAI;YAGAA,WAAW,MAAMa,MAAMF,KAAK;gBACxB,QAAQ;gBACRtB;gBACAoB;gBACA,QAAQF,OAAO,MAAM;YACzB;QACJ,EAAE,OAAOO,KAAK;YAEV,IAAIA,eAAeC,gBAAgBD,AAAa,iBAAbA,IAAI,IAAI,EACvC,MAAMA;YAEV,MAAM,IAAIE,MAAM,CAAC,eAAe,EAAGF,IAAc,OAAO,EAAE;QAC9D;QAEA,IAAI,CAACd,SAAS,EAAE,EACZ,MAAM,MAAMD,eAAeC;QAG/B,IAAI,CAACA,SAAS,IAAI,EACd,MAAM,IAAII,sBACN,0CACAJ,SAAS,MAAM;QAIvB,OAAOA;IACX;AACJ;AAEO,MAAMiB,uBAAuBC,qBAAqB;IACrD,aAAaC;IACb,gBAAgBd;IAChB,cAAc;QAACe;KAAU;AAC7B"}
@@ -0,0 +1,170 @@
1
+ import { beforeEach, describe, expect, it, vi } from "vitest";
2
+ import { Container } from "@webiny/di";
3
+ import { ApiStreamClient, ApiStreamRequestError } from "../abstractions.js";
4
+ import { FetchApiStreamClient } from "../FetchApiStreamClient.js";
5
+ import { EnvConfig } from "../../envConfig/index.js";
6
+ import { __webpack_require__ } from "../../../rslib-runtime.js";
7
+ function okResponse() {
8
+ return {
9
+ ok: true,
10
+ status: 200,
11
+ body: new ReadableStream({
12
+ start (controller) {
13
+ controller.close();
14
+ }
15
+ })
16
+ };
17
+ }
18
+ describe("FetchApiStreamClient", ()=>{
19
+ let container;
20
+ let client;
21
+ beforeEach(()=>{
22
+ container = new Container();
23
+ container.registerInstance(EnvConfig, {
24
+ get: vi.fn((key)=>"apiUrl" === key ? "https://api.example.com/" : void 0)
25
+ });
26
+ container.register(FetchApiStreamClient).inSingletonScope();
27
+ client = container.resolve(ApiStreamClient);
28
+ });
29
+ it("should POST to the API root joined with the path", async ()=>{
30
+ __webpack_require__.g.fetch = vi.fn().mockResolvedValue(okResponse());
31
+ await client.execute({
32
+ path: "/stream/fm/files/abc/enrich"
33
+ });
34
+ expect(__webpack_require__.g.fetch).toHaveBeenCalledWith("https://api.example.com/stream/fm/files/abc/enrich", expect.objectContaining({
35
+ method: "POST"
36
+ }));
37
+ });
38
+ it("should send the body's payload hash so OAC signing matches", async ()=>{
39
+ __webpack_require__.g.fetch = vi.fn().mockResolvedValue(okResponse());
40
+ await client.execute({
41
+ path: "/stream/ai/chat",
42
+ body: {
43
+ prompt: "hi"
44
+ }
45
+ });
46
+ const init = __webpack_require__.g.fetch.mock.calls[0][1];
47
+ expect(init.headers["x-amz-content-sha256"]).toBe("14479f4e87d340fe0ca0d522d87a5b3a028ebb1af24fbb8d3ef4553044fc6db6");
48
+ });
49
+ it("should omit the payload hash when there is no body", async ()=>{
50
+ __webpack_require__.g.fetch = vi.fn().mockResolvedValue(okResponse());
51
+ await client.execute({
52
+ path: "/stream/fm/files/abc/enrich"
53
+ });
54
+ const init = __webpack_require__.g.fetch.mock.calls[0][1];
55
+ expect(init.headers).not.toHaveProperty("x-amz-content-sha256");
56
+ });
57
+ it("should not produce a double slash when joining", async ()=>{
58
+ __webpack_require__.g.fetch = vi.fn().mockResolvedValue(okResponse());
59
+ await client.execute({
60
+ path: "stream/thing"
61
+ });
62
+ expect(__webpack_require__.g.fetch).toHaveBeenCalledWith("https://api.example.com/stream/thing", expect.anything());
63
+ });
64
+ it("should request an event stream and stringify the body", async ()=>{
65
+ __webpack_require__.g.fetch = vi.fn().mockResolvedValue(okResponse());
66
+ await client.execute({
67
+ path: "/stream/x",
68
+ body: {
69
+ hello: "world"
70
+ }
71
+ });
72
+ const init = __webpack_require__.g.fetch.mock.calls[0][1];
73
+ expect(init.headers.accept).toBe("text/event-stream");
74
+ expect(init.headers["content-type"]).toBe("application/json");
75
+ expect(init.body).toBe(JSON.stringify({
76
+ hello: "world"
77
+ }));
78
+ });
79
+ it("should pass through caller headers and drop undefined ones", async ()=>{
80
+ __webpack_require__.g.fetch = vi.fn().mockResolvedValue(okResponse());
81
+ await client.execute({
82
+ path: "/stream/x",
83
+ headers: {
84
+ Authorization: "Bearer t",
85
+ "x-tenant": "root",
86
+ "x-skip": void 0
87
+ }
88
+ });
89
+ const { headers } = __webpack_require__.g.fetch.mock.calls[0][1];
90
+ expect(headers.Authorization).toBe("Bearer t");
91
+ expect(headers["x-tenant"]).toBe("root");
92
+ expect("x-skip" in headers).toBe(false);
93
+ });
94
+ it("should omit the body and its content-type when there is no body", async ()=>{
95
+ __webpack_require__.g.fetch = vi.fn().mockResolvedValue(okResponse());
96
+ await client.execute({
97
+ path: "/stream/x"
98
+ });
99
+ const init = __webpack_require__.g.fetch.mock.calls[0][1];
100
+ expect(init.body).toBeUndefined();
101
+ expect(init.headers["content-type"]).toBeUndefined();
102
+ });
103
+ it("should surface a JSON error body as ApiStreamRequestError", async ()=>{
104
+ __webpack_require__.g.fetch = vi.fn().mockResolvedValue({
105
+ ok: false,
106
+ status: 404,
107
+ json: async ()=>({
108
+ message: "File not found: abc",
109
+ code: "ENRICHMENT_FILE_NOT_FOUND"
110
+ })
111
+ });
112
+ const error = await client.execute({
113
+ path: "/stream/x"
114
+ }).catch((e)=>e);
115
+ expect(error).toBeInstanceOf(ApiStreamRequestError);
116
+ expect(error.message).toBe("File not found: abc");
117
+ expect(error.statusCode).toBe(404);
118
+ expect(error.code).toBe("ENRICHMENT_FILE_NOT_FOUND");
119
+ });
120
+ it("should fall back to a status message when the error body isn't JSON", async ()=>{
121
+ __webpack_require__.g.fetch = vi.fn().mockResolvedValue({
122
+ ok: false,
123
+ status: 502,
124
+ json: async ()=>{
125
+ throw new Error("not json");
126
+ }
127
+ });
128
+ const error = await client.execute({
129
+ path: "/stream/x"
130
+ }).catch((e)=>e);
131
+ expect(error).toBeInstanceOf(ApiStreamRequestError);
132
+ expect(error.message).toBe("Request failed with status 502.");
133
+ expect(error.statusCode).toBe(502);
134
+ });
135
+ it("should reject a 2xx response that carries no body", async ()=>{
136
+ __webpack_require__.g.fetch = vi.fn().mockResolvedValue({
137
+ ok: true,
138
+ status: 200,
139
+ body: null
140
+ });
141
+ await expect(client.execute({
142
+ path: "/stream/x"
143
+ })).rejects.toThrow("The response carried no readable body.");
144
+ });
145
+ it("should wrap a network failure", async ()=>{
146
+ __webpack_require__.g.fetch = vi.fn().mockRejectedValue(new Error("connection reset"));
147
+ await expect(client.execute({
148
+ path: "/stream/x"
149
+ })).rejects.toThrow("Network error: connection reset");
150
+ });
151
+ it("should rethrow an abort untouched", async ()=>{
152
+ __webpack_require__.g.fetch = vi.fn().mockRejectedValue(new DOMException("aborted", "AbortError"));
153
+ const error = await client.execute({
154
+ path: "/stream/x"
155
+ }).catch((e)=>e);
156
+ expect(error).toBeInstanceOf(DOMException);
157
+ expect(error.name).toBe("AbortError");
158
+ });
159
+ it("should forward the abort signal to fetch", async ()=>{
160
+ __webpack_require__.g.fetch = vi.fn().mockResolvedValue(okResponse());
161
+ const controller = new AbortController();
162
+ await client.execute({
163
+ path: "/stream/x",
164
+ signal: controller.signal
165
+ });
166
+ expect(__webpack_require__.g.fetch.mock.calls[0][1].signal).toBe(controller.signal);
167
+ });
168
+ });
169
+
170
+ //# sourceMappingURL=FetchApiStreamClient.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"features/apiStreamClient/__tests__/FetchApiStreamClient.test.js","sources":["../../../../src/features/apiStreamClient/__tests__/FetchApiStreamClient.test.ts"],"sourcesContent":["import { describe, it, expect, vi, beforeEach } from \"vitest\";\nimport { Container } from \"@webiny/di\";\nimport { ApiStreamClient, ApiStreamRequestError } from \"../abstractions.js\";\nimport { FetchApiStreamClient } from \"../FetchApiStreamClient.js\";\nimport { EnvConfig } from \"~/features/envConfig/index.js\";\n\nfunction okResponse() {\n return {\n ok: true,\n status: 200,\n body: new ReadableStream<Uint8Array>({\n start(controller) {\n controller.close();\n }\n })\n };\n}\n\ndescribe(\"FetchApiStreamClient\", () => {\n let container: Container;\n let client: ApiStreamClient.Interface;\n\n beforeEach(() => {\n container = new Container();\n container.registerInstance(EnvConfig, {\n get: vi.fn((key: string) => (key === \"apiUrl\" ? \"https://api.example.com/\" : undefined))\n } as any);\n container.register(FetchApiStreamClient).inSingletonScope();\n client = container.resolve(ApiStreamClient);\n });\n\n it(\"should POST to the API root joined with the path\", async () => {\n global.fetch = vi.fn().mockResolvedValue(okResponse());\n\n await client.execute({ path: \"/stream/fm/files/abc/enrich\" });\n\n expect(global.fetch).toHaveBeenCalledWith(\n \"https://api.example.com/stream/fm/files/abc/enrich\",\n expect.objectContaining({ method: \"POST\" })\n );\n });\n\n /*\n * CloudFront OAC signs the request but does not hash the body, so it takes the payload hash from\n * this header. Omit it and a POST carrying a body is rejected with InvalidSignatureException while\n * a bodyless one succeeds — a failure no existing route hit, because none sent a body.\n */\n it(\"should send the body's payload hash so OAC signing matches\", async () => {\n global.fetch = vi.fn().mockResolvedValue(okResponse());\n\n await client.execute({ path: \"/stream/ai/chat\", body: { prompt: \"hi\" } });\n\n const init = (global.fetch as any).mock.calls[0][1];\n // sha256 of {\"prompt\":\"hi\"}\n expect(init.headers[\"x-amz-content-sha256\"]).toBe(\n \"14479f4e87d340fe0ca0d522d87a5b3a028ebb1af24fbb8d3ef4553044fc6db6\"\n );\n });\n\n it(\"should omit the payload hash when there is no body\", async () => {\n global.fetch = vi.fn().mockResolvedValue(okResponse());\n\n await client.execute({ path: \"/stream/fm/files/abc/enrich\" });\n\n const init = (global.fetch as any).mock.calls[0][1];\n expect(init.headers).not.toHaveProperty(\"x-amz-content-sha256\");\n });\n\n it(\"should not produce a double slash when joining\", async () => {\n global.fetch = vi.fn().mockResolvedValue(okResponse());\n\n await client.execute({ path: \"stream/thing\" });\n\n expect(global.fetch).toHaveBeenCalledWith(\n \"https://api.example.com/stream/thing\",\n expect.anything()\n );\n });\n\n it(\"should request an event stream and stringify the body\", async () => {\n global.fetch = vi.fn().mockResolvedValue(okResponse());\n\n await client.execute({ path: \"/stream/x\", body: { hello: \"world\" } });\n\n const init = (global.fetch as any).mock.calls[0][1];\n expect(init.headers.accept).toBe(\"text/event-stream\");\n expect(init.headers[\"content-type\"]).toBe(\"application/json\");\n expect(init.body).toBe(JSON.stringify({ hello: \"world\" }));\n });\n\n it(\"should pass through caller headers and drop undefined ones\", async () => {\n global.fetch = vi.fn().mockResolvedValue(okResponse());\n\n await client.execute({\n path: \"/stream/x\",\n headers: { Authorization: \"Bearer t\", \"x-tenant\": \"root\", \"x-skip\": undefined }\n });\n\n const { headers } = (global.fetch as any).mock.calls[0][1];\n expect(headers.Authorization).toBe(\"Bearer t\");\n expect(headers[\"x-tenant\"]).toBe(\"root\");\n expect(\"x-skip\" in headers).toBe(false);\n });\n\n it(\"should omit the body and its content-type when there is no body\", async () => {\n global.fetch = vi.fn().mockResolvedValue(okResponse());\n\n await client.execute({ path: \"/stream/x\" });\n\n const init = (global.fetch as any).mock.calls[0][1];\n expect(init.body).toBeUndefined();\n expect(init.headers[\"content-type\"]).toBeUndefined();\n });\n\n it(\"should surface a JSON error body as ApiStreamRequestError\", async () => {\n global.fetch = vi.fn().mockResolvedValue({\n ok: false,\n status: 404,\n json: async () => ({\n message: \"File not found: abc\",\n code: \"ENRICHMENT_FILE_NOT_FOUND\"\n })\n });\n\n const error = await client.execute({ path: \"/stream/x\" }).catch(e => e);\n\n expect(error).toBeInstanceOf(ApiStreamRequestError);\n expect(error.message).toBe(\"File not found: abc\");\n expect(error.statusCode).toBe(404);\n expect(error.code).toBe(\"ENRICHMENT_FILE_NOT_FOUND\");\n });\n\n it(\"should fall back to a status message when the error body isn't JSON\", async () => {\n global.fetch = vi.fn().mockResolvedValue({\n ok: false,\n status: 502,\n json: async () => {\n throw new Error(\"not json\");\n }\n });\n\n const error = await client.execute({ path: \"/stream/x\" }).catch(e => e);\n\n expect(error).toBeInstanceOf(ApiStreamRequestError);\n expect(error.message).toBe(\"Request failed with status 502.\");\n expect(error.statusCode).toBe(502);\n });\n\n it(\"should reject a 2xx response that carries no body\", async () => {\n global.fetch = vi.fn().mockResolvedValue({ ok: true, status: 200, body: null });\n\n await expect(client.execute({ path: \"/stream/x\" })).rejects.toThrow(\n \"The response carried no readable body.\"\n );\n });\n\n it(\"should wrap a network failure\", async () => {\n global.fetch = vi.fn().mockRejectedValue(new Error(\"connection reset\"));\n\n await expect(client.execute({ path: \"/stream/x\" })).rejects.toThrow(\n \"Network error: connection reset\"\n );\n });\n\n it(\"should rethrow an abort untouched\", async () => {\n // Callers distinguish \"user cancelled\" from \"request failed\"; wrapping an abort in a generic\n // network error would erase that.\n global.fetch = vi.fn().mockRejectedValue(new DOMException(\"aborted\", \"AbortError\"));\n\n const error = await client.execute({ path: \"/stream/x\" }).catch(e => e);\n\n expect(error).toBeInstanceOf(DOMException);\n expect(error.name).toBe(\"AbortError\");\n });\n\n it(\"should forward the abort signal to fetch\", async () => {\n global.fetch = vi.fn().mockResolvedValue(okResponse());\n const controller = new AbortController();\n\n await client.execute({ path: \"/stream/x\", signal: controller.signal });\n\n expect((global.fetch as any).mock.calls[0][1].signal).toBe(controller.signal);\n });\n});\n"],"names":["okResponse","ReadableStream","controller","describe","container","client","beforeEach","Container","EnvConfig","vi","key","undefined","FetchApiStreamClient","ApiStreamClient","it","global","expect","init","JSON","headers","error","e","ApiStreamRequestError","Error","DOMException","AbortController"],"mappings":"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"}
@@ -0,0 +1,160 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { readServerSentEvents } from "../readServerSentEvents.js";
3
+ const encoder = new TextEncoder();
4
+ function responseFrom(chunks) {
5
+ const stream = new ReadableStream({
6
+ start (controller) {
7
+ for (const chunk of chunks)controller.enqueue("string" == typeof chunk ? encoder.encode(chunk) : chunk);
8
+ controller.close();
9
+ }
10
+ });
11
+ return {
12
+ body: stream
13
+ };
14
+ }
15
+ async function collect(response) {
16
+ const events = [];
17
+ for await (const event of readServerSentEvents(response))events.push(event);
18
+ return events;
19
+ }
20
+ describe("readServerSentEvents", ()=>{
21
+ it("should parse one event per record", async ()=>{
22
+ const events = await collect(responseFrom([
23
+ 'data: {"type":"start"}\n\n',
24
+ 'data: {"type":"done"}\n\n'
25
+ ]));
26
+ expect(events).toEqual([
27
+ {
28
+ type: "start"
29
+ },
30
+ {
31
+ type: "done"
32
+ }
33
+ ]);
34
+ });
35
+ it("should parse multiple records arriving in a single chunk", async ()=>{
36
+ const events = await collect(responseFrom([
37
+ 'data: {"n":1}\n\ndata: {"n":2}\n\ndata: {"n":3}\n\n'
38
+ ]));
39
+ expect(events).toEqual([
40
+ {
41
+ n: 1
42
+ },
43
+ {
44
+ n: 2
45
+ },
46
+ {
47
+ n: 3
48
+ }
49
+ ]);
50
+ });
51
+ it("should parse a record split across chunks", async ()=>{
52
+ const events = await collect(responseFrom([
53
+ 'data: {"ty',
54
+ 'pe":"partial"}',
55
+ "\n\n"
56
+ ]));
57
+ expect(events).toEqual([
58
+ {
59
+ type: "partial"
60
+ }
61
+ ]);
62
+ });
63
+ it("should handle CRLF line endings", async ()=>{
64
+ const events = await collect(responseFrom([
65
+ 'data: {"ok":true}\r\n\r\n'
66
+ ]));
67
+ expect(events).toEqual([
68
+ {
69
+ ok: true
70
+ }
71
+ ]);
72
+ });
73
+ it("should ignore comments and non-data fields", async ()=>{
74
+ const events = await collect(responseFrom([
75
+ ": heartbeat\n\n",
76
+ 'event: message\nid: 7\nretry: 500\ndata: {"kept":true}\n\n'
77
+ ]));
78
+ expect(events).toEqual([
79
+ {
80
+ kept: true
81
+ }
82
+ ]);
83
+ });
84
+ it("should join multi-line data fields", async ()=>{
85
+ const events = await collect(responseFrom([
86
+ 'data: {"a":1,\ndata: "b":2}\n\n'
87
+ ]));
88
+ expect(events).toEqual([
89
+ {
90
+ a: 1,
91
+ b: 2
92
+ }
93
+ ]);
94
+ });
95
+ it("should preserve multi-byte characters split across chunks", async ()=>{
96
+ const payload = encoder.encode('data: {"text":"café"}\n\n');
97
+ const split = 18;
98
+ const events = await collect(responseFrom([
99
+ payload.slice(0, split),
100
+ payload.slice(split)
101
+ ]));
102
+ expect(events).toEqual([
103
+ {
104
+ text: "café"
105
+ }
106
+ ]);
107
+ });
108
+ it("should drop a trailing record that never terminated", async ()=>{
109
+ const events = await collect(responseFrom([
110
+ 'data: {"complete":true}\n\ndata: {"trunc'
111
+ ]));
112
+ expect(events).toEqual([
113
+ {
114
+ complete: true
115
+ }
116
+ ]);
117
+ });
118
+ it("should yield events as they arrive rather than after the stream closes", async ()=>{
119
+ let released;
120
+ const gate = new Promise((resolve)=>{
121
+ released = resolve;
122
+ });
123
+ const stream = new ReadableStream({
124
+ async start (controller) {
125
+ controller.enqueue(encoder.encode('data: {"n":1}\n\n'));
126
+ await gate;
127
+ controller.enqueue(encoder.encode('data: {"n":2}\n\n'));
128
+ controller.close();
129
+ }
130
+ });
131
+ const iterator = readServerSentEvents({
132
+ body: stream
133
+ });
134
+ expect((await iterator.next()).value).toEqual({
135
+ n: 1
136
+ });
137
+ released();
138
+ expect((await iterator.next()).value).toEqual({
139
+ n: 2
140
+ });
141
+ expect((await iterator.next()).done).toBe(true);
142
+ });
143
+ it("should throw when the response has no body", async ()=>{
144
+ await expect(collect({
145
+ body: null
146
+ })).rejects.toThrow("The response carried no readable body.");
147
+ });
148
+ it("should propagate a stream error", async ()=>{
149
+ const stream = new ReadableStream({
150
+ start (controller) {
151
+ controller.error(new Error("stream broke"));
152
+ }
153
+ });
154
+ await expect(collect({
155
+ body: stream
156
+ })).rejects.toThrow("stream broke");
157
+ });
158
+ });
159
+
160
+ //# sourceMappingURL=readServerSentEvents.test.js.map