@farm.js/plugin 0.1.0-beta.7 → 0.1.0-beta.71

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.
package/dist/rsc/index.js CHANGED
@@ -26,12 +26,15 @@ import { init as initModuleLexer, parse as parseModuleImports } from "es-module-
26
26
  import { farmEnvironmentFunctionsPlugin } from "@farm.js/core/environment/vite";
27
27
  import { generateRscEntry } from "./entries/rsc.js";
28
28
  import { generateSsrEntry } from "./entries/ssr.js";
29
- import { generateClientEntry } from "./entries/client.js";
29
+ import { generateClientEntry, serverFnTransportErrorClientRuntime } from "./entries/client.js";
30
+ import { transformFarmRouteActionClients } from "./route-action-transform.js";
30
31
  import { transformFarmServerFns } from "./server-fn-transform.js";
32
+ import { transformAutomaticOptimizedBoundaries } from "./automatic-optimized-boundary.js";
31
33
  import { resolveRscBuildOutputPath } from "./build-paths.js";
32
34
  import { assertRscPackageCompatibility } from "./compatibility.js";
33
35
  import fs from "fs/promises";
34
36
  import path from "path";
37
+ import { devServableFileExists } from "../dev-static.js";
35
38
  import { pathToFileURL } from "node:url";
36
39
  import { createRequire } from "node:module";
37
40
  // Use API and middleware plugins from @farm.js/core (require so CJS build resolves when ESM .mjs is missing)
@@ -40,6 +43,7 @@ const { farmApiPlugin, farmMiddlewarePlugin } = require_("@farm.js/core");
40
43
  const { resolveServerActionsConfig } = require_("@farm.js/core/server-action-security");
41
44
  const { normalizeFarmDeploymentId } = require_("@farm.js/core/deployment");
42
45
  const { getFarmLayerAliases, getFarmSourceRoots, resolveFarmLayers } = require_("@farm.js/core/server");
46
+ const { searchParamsToObject } = require_("@farm.js/core/internal/production-runtime");
43
47
  export { buildRscNitro, waitForRscManifest, waitForRscOutputs } from "./nitro-build.js";
44
48
  import { registerRscNitroRuntimePackage } from "./nitro-build.js";
45
49
  export { default as nitro } from "./vite-plugin-nitro.js";
@@ -710,6 +714,42 @@ export default function farmRsc(options = {}) {
710
714
  return null;
711
715
  },
712
716
  },
717
+ {
718
+ name: "@farm.js/plugin/rsc:automatic-optimized-boundary",
719
+ enforce: "post",
720
+ transform(code, id) {
721
+ if (!rscEnabled || !optimizedBoundaryEnabled)
722
+ return null;
723
+ const environmentName = this.environment?.name;
724
+ if (environmentName !== "rsc")
725
+ return null;
726
+ const result = transformAutomaticOptimizedBoundaries(code, id);
727
+ if (!result)
728
+ return null;
729
+ if (debug) {
730
+ console.log(`[Farm.js] Added ${result.boundaryCount} automatic optimized-boundary candidate${result.boundaryCount === 1 ? "" : "s"} in ${id}`);
731
+ }
732
+ return { code: result.code, map: null };
733
+ },
734
+ },
735
+ {
736
+ name: "@farm.js/plugin/rsc:route-action-clients",
737
+ enforce: "pre",
738
+ transform(code, id) {
739
+ if (!rscEnabled || !actionsEnabled)
740
+ return null;
741
+ const environmentName = this.environment?.name;
742
+ if (environmentName !== "client")
743
+ return null;
744
+ const result = transformFarmRouteActionClients(code, id);
745
+ if (!result)
746
+ return null;
747
+ return {
748
+ code: result.code,
749
+ map: null,
750
+ };
751
+ },
752
+ },
713
753
  {
714
754
  name: "@farm.js/plugin/rsc:server-fn-actions",
715
755
  enforce: "pre",
@@ -829,6 +869,7 @@ import {
829
869
  createFarmDeploymentRequestHeaders,
830
870
  isFarmDeploymentMismatchResponse,
831
871
  } from '@farm.js/core/deployment';
872
+ ${serverFnTransportErrorClientRuntime}
832
873
  const farmDeploymentId = ${JSON.stringify(entryContext.deploymentId)};
833
874
  setServerCallback(async (id, args) => {
834
875
  const refs = createTemporaryReferenceSet();
@@ -855,9 +896,7 @@ setServerCallback(async (id, args) => {
855
896
  if (!res.ok) throw new Error('Server action failed: ' + res.status);
856
897
  const p = await createFromReadableStream(res.body, { temporaryReferences: refs });
857
898
  if (p?.returnValue?.ok) return p.returnValue.data;
858
- const error = new Error(p?.returnValue?.data?.message || 'Server function failed');
859
- error.name = 'ServerActionError';
860
- throw error;
899
+ throw createFarmServerFnTransportError(p?.returnValue?.data);
861
900
  });
862
901
  `
863
902
  : "";
@@ -925,12 +964,16 @@ if (document.readyState === 'loading') {
925
964
  const url = req.url || "/";
926
965
  const pathname = url.split("?")[0];
927
966
  const method = req.method || "GET";
967
+ // Dotted paths only skip RSC rendering when they map to a real
968
+ // file on disk — route segments may legitimately contain dots.
928
969
  if (pathname.startsWith("/@") ||
929
970
  pathname.startsWith("/__") ||
930
971
  pathname.startsWith("/node_modules") ||
931
972
  pathname.startsWith("/src/") ||
932
973
  pathname.startsWith("/api/") ||
933
- (pathname.includes(".") && !pathname.endsWith("/"))) {
974
+ (pathname.includes(".") &&
975
+ !pathname.endsWith("/") &&
976
+ devServableFileExists(pathname, [server.config.publicDir, server.config.root]))) {
934
977
  return next();
935
978
  }
936
979
  const startTime = Date.now();
@@ -1112,7 +1155,7 @@ if (document.readyState === 'loading') {
1112
1155
  const Layout = layoutModule?.default;
1113
1156
  // Parse URL params (basic dynamic route support)
1114
1157
  const params = {};
1115
- const searchParams = Object.fromEntries(new URLSearchParams(url.split("?")[1] || ""));
1158
+ const searchParams = searchParamsToObject(new URL(url, `http://${req.headers.host || "localhost:3000"}`).searchParams);
1116
1159
  // Render the page with middleware data
1117
1160
  const pageProps = {
1118
1161
  params,
@@ -1 +1 @@
1
- {"version":3,"file":"nitro-build.d.ts","sourceRoot":"","sources":["../../src/rsc/nitro-build.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAwBH;;;GAGG;AACH,wBAAgB,8BAA8B,CAC5C,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,GAClB,IAAI,CAKN;AAwFD;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EACd,OAAO,GAAE;IAAE,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAO,GACxD,OAAO,CAAC,IAAI,CAAC,CA0Bf;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EACd,OAAO,GAAE;IAAE,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAO,GACxD,OAAO,CAAC,IAAI,CAAC,CAgCf;AA+DD,MAAM,MAAM,oBAAoB,GAAG;IACjC,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,wBAAsB,aAAa,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,IAAI,CAAC,CAqMhF"}
1
+ {"version":3,"file":"nitro-build.d.ts","sourceRoot":"","sources":["../../src/rsc/nitro-build.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAwBH;;;GAGG;AACH,wBAAgB,8BAA8B,CAC5C,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,GAClB,IAAI,CAKN;AAwFD;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EACd,OAAO,GAAE;IAAE,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAO,GACxD,OAAO,CAAC,IAAI,CAAC,CA0Bf;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EACd,OAAO,GAAE;IAAE,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAO,GACxD,OAAO,CAAC,IAAI,CAAC,CAgCf;AA+DD,MAAM,MAAM,oBAAoB,GAAG;IACjC,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,wBAAsB,aAAa,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,IAAI,CAAC,CA8MhF"}
@@ -271,7 +271,15 @@ export async function buildRscNitro(options) {
271
271
  },
272
272
  // Externalize rsc/ssr so we don't bundle (they reference Vite-generated manifest). We copy dist into server output after build.
273
273
  rollupConfig: {
274
- external: (id) => id.includes("../dist/") || id.includes("dist/rsc") || id.includes("dist/ssr"),
274
+ external: (id) => {
275
+ // Ids reach this predicate in both separator forms on Windows, and matching
276
+ // only the POSIX one makes the same module external in one pass and not in
277
+ // the next, which Rollup rejects.
278
+ const normalized = id.split("\\").join("/");
279
+ return (normalized.includes("../dist/") ||
280
+ normalized.includes("dist/rsc") ||
281
+ normalized.includes("dist/ssr"));
282
+ },
275
283
  },
276
284
  compatibilityDate: "2024-12-01",
277
285
  minify: true,
@@ -1,6 +1,6 @@
1
1
  import { type RenderOptions, type StrataDocument } from "@farming-labs/strata";
2
2
  import { type StaticFragmentBoundary, type StaticFragmentProps } from "@farming-labs/strata/react-server";
3
- import type { ReactElement } from "react";
3
+ import { type ReactElement } from "react";
4
4
  export type { RenderOptions, StrataDocument, StrataElement, StrataNode, StrataTag, StrataText, } from "@farming-labs/strata";
5
5
  export interface OptimizedBoundaryProps extends Omit<StaticFragmentProps, "content"> {
6
6
  /**
@@ -10,6 +10,14 @@ export interface OptimizedBoundaryProps extends Omit<StaticFragmentProps, "conte
10
10
  document: StrataDocument;
11
11
  renderOptions?: RenderOptions;
12
12
  }
13
+ /**
14
+ * Internal compiler target for automatic optimized boundaries. This helper is
15
+ * deliberately fail-open: any unsupported or unsafe tree returns the original
16
+ * React element unchanged.
17
+ *
18
+ * @internal
19
+ */
20
+ export declare function _optimizeBoundary(element: ReactElement): ReactElement;
13
21
  /**
14
22
  * Render a typed host-only document with the optimized native runtime and
15
23
  * place it inside an opaque React-owned boundary.
@@ -1 +1 @@
1
- {"version":3,"file":"optimized-boundary.d.ts","sourceRoot":"","sources":["../../src/rsc/optimized-boundary.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,KAAK,aAAa,EAAE,KAAK,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACvF,OAAO,EAEL,KAAK,sBAAsB,EAC3B,KAAK,mBAAmB,EACzB,MAAM,mCAAmC,CAAC;AAC3C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,OAAO,CAAC;AAE1C,YAAY,EACV,aAAa,EACb,cAAc,EACd,aAAa,EACb,UAAU,EACV,SAAS,EACT,UAAU,GACX,MAAM,sBAAsB,CAAC;AAE9B,MAAM,WAAW,sBAAuB,SAAQ,IAAI,CAAC,mBAAmB,EAAE,SAAS,CAAC;IAClF;;;OAGG;IACH,QAAQ,EAAE,cAAc,CAAC;IACzB,aAAa,CAAC,EAAE,aAAa,CAAC;CAC/B;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,EAChC,QAAQ,EACR,aAAa,EACb,GAAG,KAAK,EACT,EAAE,sBAAsB,GAAG,YAAY,CAMvC;AAED,MAAM,MAAM,oBAAoB,GAAG,sBAAsB,CAAC"}
1
+ {"version":3,"file":"optimized-boundary.d.ts","sourceRoot":"","sources":["../../src/rsc/optimized-boundary.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,aAAa,EAClB,KAAK,cAAc,EAIpB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAEL,KAAK,sBAAsB,EAC3B,KAAK,mBAAmB,EACzB,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EAA2C,KAAK,YAAY,EAAE,MAAM,OAAO,CAAC;AAEnF,YAAY,EACV,aAAa,EACb,cAAc,EACd,aAAa,EACb,UAAU,EACV,SAAS,EACT,UAAU,GACX,MAAM,sBAAsB,CAAC;AAE9B,MAAM,WAAW,sBAAuB,SAAQ,IAAI,CAAC,mBAAmB,EAAE,SAAS,CAAC;IAClF;;;OAGG;IACH,QAAQ,EAAE,cAAc,CAAC;IACzB,aAAa,CAAC,EAAE,aAAa,CAAC;CAC/B;AAiND;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,YAAY,GAAG,YAAY,CAsBrE;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,EAChC,QAAQ,EACR,aAAa,EACb,GAAG,KAAK,EACT,EAAE,sBAAsB,GAAG,YAAY,CAMvC;AAED,MAAM,MAAM,oBAAoB,GAAG,sBAAsB,CAAC"}
@@ -1,5 +1,233 @@
1
- import { render } from "@farming-labs/strata";
1
+ import { render, } from "@farming-labs/strata";
2
2
  import { StaticFragment, } from "@farming-labs/strata/react-server";
3
+ import { createElement, Fragment, isValidElement } from "react";
4
+ const AUTOMATIC_MIN_NODES = 8;
5
+ const AUTOMATIC_MIN_BYTES = 256;
6
+ const AUTOMATIC_BOUNDARIES = new Set([
7
+ "article",
8
+ "aside",
9
+ "div",
10
+ "footer",
11
+ "header",
12
+ "main",
13
+ "nav",
14
+ "section",
15
+ "span",
16
+ ]);
17
+ const STRATA_TAGS = new Set([
18
+ "a",
19
+ "abbr",
20
+ "address",
21
+ "article",
22
+ "aside",
23
+ "b",
24
+ "blockquote",
25
+ "br",
26
+ "caption",
27
+ "cite",
28
+ "code",
29
+ "col",
30
+ "colgroup",
31
+ "dd",
32
+ "del",
33
+ "details",
34
+ "dfn",
35
+ "div",
36
+ "dl",
37
+ "dt",
38
+ "em",
39
+ "figcaption",
40
+ "figure",
41
+ "footer",
42
+ "h1",
43
+ "h2",
44
+ "h3",
45
+ "h4",
46
+ "h5",
47
+ "h6",
48
+ "header",
49
+ "hr",
50
+ "i",
51
+ "img",
52
+ "ins",
53
+ "kbd",
54
+ "li",
55
+ "main",
56
+ "mark",
57
+ "nav",
58
+ "ol",
59
+ "p",
60
+ "pre",
61
+ "q",
62
+ "s",
63
+ "samp",
64
+ "section",
65
+ "small",
66
+ "span",
67
+ "strong",
68
+ "sub",
69
+ "summary",
70
+ "sup",
71
+ "table",
72
+ "tbody",
73
+ "td",
74
+ "tfoot",
75
+ "th",
76
+ "thead",
77
+ "time",
78
+ "tr",
79
+ "u",
80
+ "ul",
81
+ "var",
82
+ "wbr",
83
+ ]);
84
+ const BOOLEAN_ATTRIBUTES = new Set(["hidden", "reversed"]);
85
+ const ATTRIBUTE_NAMES = {
86
+ className: "class",
87
+ colSpan: "colspan",
88
+ dateTime: "datetime",
89
+ rowSpan: "rowspan",
90
+ };
91
+ function AutomaticOptimizedBoundary({ document, fallback, ...props }) {
92
+ let content;
93
+ try {
94
+ content = render(document);
95
+ }
96
+ catch {
97
+ return fallback;
98
+ }
99
+ if (content.nodeCount < AUTOMATIC_MIN_NODES && content.bytes < AUTOMATIC_MIN_BYTES) {
100
+ return fallback;
101
+ }
102
+ return StaticFragment({ ...props, content });
103
+ }
104
+ function attributeValue(name, value) {
105
+ if (value === null || value === undefined)
106
+ return undefined;
107
+ if (typeof value === "string" || typeof value === "number" || typeof value === "bigint") {
108
+ return String(value);
109
+ }
110
+ if (typeof value !== "boolean")
111
+ return null;
112
+ if (name.startsWith("aria-") || name.startsWith("data-"))
113
+ return String(value);
114
+ if (BOOLEAN_ATTRIBUTES.has(name))
115
+ return value ? "" : undefined;
116
+ return null;
117
+ }
118
+ function convertAttributes(tag, props) {
119
+ const attributes = {};
120
+ for (const [reactName, rawValue] of Object.entries(props)) {
121
+ if (reactName === "children")
122
+ continue;
123
+ if (reactName === "dangerouslySetInnerHTML" ||
124
+ reactName === "ref" ||
125
+ reactName === "style" ||
126
+ reactName === "suppressHydrationWarning" ||
127
+ /^on[A-Z]/.test(reactName)) {
128
+ return null;
129
+ }
130
+ const name = ATTRIBUTE_NAMES[reactName] || reactName;
131
+ const value = attributeValue(name, rawValue);
132
+ if (value === null)
133
+ return null;
134
+ if (value !== undefined)
135
+ attributes[name] = value;
136
+ }
137
+ return Object.keys(attributes).length > 0 ? attributes : {};
138
+ }
139
+ function automaticDocumentFromElement(element) {
140
+ const rootProps = element.props;
141
+ const children = [];
142
+ if (!appendStrataNodes(rootProps.children, children))
143
+ return null;
144
+ return { type: "document", ...(children.length > 0 ? { children } : {}) };
145
+ }
146
+ function appendStrataNodes(value, output) {
147
+ if (value === null || value === undefined || typeof value === "boolean")
148
+ return true;
149
+ if (typeof value === "string" || typeof value === "number" || typeof value === "bigint") {
150
+ output.push({ type: "text", value: String(value) });
151
+ return true;
152
+ }
153
+ if (Array.isArray(value)) {
154
+ for (const child of value) {
155
+ if (!appendStrataNodes(child, output))
156
+ return false;
157
+ }
158
+ return true;
159
+ }
160
+ if (!isValidElement(value))
161
+ return false;
162
+ const element = value;
163
+ const props = element.props;
164
+ if (element.type === Fragment)
165
+ return appendStrataNodes(props.children, output);
166
+ if (element.type === OptimizedBoundary || element.type === AutomaticOptimizedBoundary) {
167
+ const boundary = props.as ?? "div";
168
+ if (typeof boundary !== "string" || !STRATA_TAGS.has(boundary))
169
+ return false;
170
+ const { as: _as, document: _document, fallback: _fallback, ...boundaryProps } = props;
171
+ const attributes = convertAttributes(boundary, boundaryProps);
172
+ if (!attributes)
173
+ return false;
174
+ const document = props.document;
175
+ if (!document || document.type !== "document")
176
+ return false;
177
+ output.push({
178
+ type: "element",
179
+ tag: boundary,
180
+ ...(Object.keys(attributes).length > 0 ? { attributes } : {}),
181
+ ...(document.children?.length ? { children: document.children } : {}),
182
+ });
183
+ return true;
184
+ }
185
+ if (typeof element.type !== "string" || !STRATA_TAGS.has(element.type)) {
186
+ return false;
187
+ }
188
+ const tag = element.type;
189
+ const attributes = convertAttributes(tag, props);
190
+ if (!attributes)
191
+ return false;
192
+ const children = [];
193
+ if (!appendStrataNodes(props.children, children))
194
+ return false;
195
+ output.push({
196
+ type: "element",
197
+ tag,
198
+ ...(Object.keys(attributes).length > 0 ? { attributes } : {}),
199
+ ...(children.length > 0 ? { children } : {}),
200
+ });
201
+ return true;
202
+ }
203
+ /**
204
+ * Internal compiler target for automatic optimized boundaries. This helper is
205
+ * deliberately fail-open: any unsupported or unsafe tree returns the original
206
+ * React element unchanged.
207
+ *
208
+ * @internal
209
+ */
210
+ export function _optimizeBoundary(element) {
211
+ if (!isValidElement(element) ||
212
+ typeof element.type !== "string" ||
213
+ !AUTOMATIC_BOUNDARIES.has(element.type)) {
214
+ return element;
215
+ }
216
+ const props = element.props;
217
+ if (props.dangerouslySetInnerHTML !== undefined || props.ref !== undefined)
218
+ return element;
219
+ const document = automaticDocumentFromElement(element);
220
+ if (!document)
221
+ return element;
222
+ const { children: _children, ...boundaryProps } = props;
223
+ return createElement(AutomaticOptimizedBoundary, {
224
+ ...boundaryProps,
225
+ ...(element.key !== null ? { key: element.key } : {}),
226
+ as: element.type,
227
+ document,
228
+ fallback: element,
229
+ });
230
+ }
3
231
  /**
4
232
  * Render a typed host-only document with the optimized native runtime and
5
233
  * place it inside an opaque React-owned boundary.
@@ -0,0 +1,11 @@
1
+ export type FarmRouteActionClientTransformResult = {
2
+ code: string;
3
+ routes: string[];
4
+ };
5
+ /**
6
+ * Replace a programmatic route imported by the browser with a small action-only
7
+ * proxy. Route loaders, components, database imports, and other server code do
8
+ * not enter the client graph.
9
+ */
10
+ export declare function transformFarmRouteActionClients(code: string, id: string): FarmRouteActionClientTransformResult | null;
11
+ //# sourceMappingURL=route-action-transform.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"route-action-transform.d.ts","sourceRoot":"","sources":["../../src/rsc/route-action-transform.ts"],"names":[],"mappings":"AAQA,MAAM,MAAM,oCAAoC,GAAG;IACjD,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB,CAAC;AAcF;;;;GAIG;AACH,wBAAgB,+BAA+B,CAC7C,IAAI,EAAE,MAAM,EACZ,EAAE,EAAE,MAAM,GACT,oCAAoC,GAAG,IAAI,CAqC7C"}