jinrai 1.1.2 → 1.1.3

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 (55) hide show
  1. package/index.ts +2 -0
  2. package/lib/bin/bin.js +8 -7
  3. package/lib/index.d.ts +1 -0
  4. package/lib/index.js +1 -0
  5. package/lib/src/bin/agent/agent.d.ts +1 -0
  6. package/lib/src/bin/agent/agent.js +3 -0
  7. package/lib/src/bin/routes/Parser.d.ts +6 -1
  8. package/lib/src/bin/routes/Parser.js +5 -0
  9. package/lib/src/front/server-state/DataProxy.d.ts +2 -1
  10. package/lib/src/front/server-state/DataProxy.js +119 -60
  11. package/lib/src/front/server-state/SSR.d.ts +2 -0
  12. package/lib/src/front/server-state/SSR.js +16 -3
  13. package/lib/src/front/server-state/real.js +15 -1
  14. package/lib/src/front/server-state/serverStates.d.ts +2 -1
  15. package/lib/src/front/server-state/serverStates.js +19 -16
  16. package/lib/src/front/server-state/useServerState.d.ts +1 -1
  17. package/lib/src/front/server-state/useServerState.js +10 -10
  18. package/lib/src/front/url/params/useParamsIndex.js +2 -1
  19. package/lib/src/front/url/search/useSearch.js +2 -1
  20. package/lib/src/front/url/search/useSearchValue.d.ts +11 -5
  21. package/lib/src/front/url/search/useSearchValue.js +13 -8
  22. package/lib/src/front/wrapper/Custom.d.ts +3 -3
  23. package/lib/src/front/wrapper/Custom.js +14 -1
  24. package/lib/vite/plugin.js +15 -149
  25. package/package.json +5 -1
  26. package/src/bin/agent/agent.ts +1 -0
  27. package/src/bin/playwright/pageCollector.ts +0 -2
  28. package/src/bin/playwright/templates.ts +2 -1
  29. package/src/bin/routes/Parser.ts +12 -5
  30. package/src/front/server-state/DataProxy.ts +136 -61
  31. package/src/front/server-state/SSR.ts +18 -2
  32. package/src/front/server-state/real.ts +16 -1
  33. package/src/front/server-state/serverStates.ts +26 -17
  34. package/src/front/server-state/useServerState.ts +10 -10
  35. package/src/front/url/search/useSearch.ts +1 -1
  36. package/src/front/url/search/useSearchValue.ts +25 -13
  37. package/src/front/wrapper/Custom.tsx +20 -4
  38. package/tests/data-proxy/create-dataproxy.test.ts +116 -0
  39. package/tests/{custom.test.ts → parse/custom.test.ts} +2 -2
  40. package/tests/{parse.test.ts → parse/parse.test.ts} +7 -7
  41. package/vite/plugin.ts +21 -15
  42. /package/tests/{content → parse/content}/1.html +0 -0
  43. /package/tests/{content → parse/content}/1_result.json +0 -0
  44. /package/tests/{content → parse/content}/2.html +0 -0
  45. /package/tests/{content → parse/content}/2_result.json +0 -0
  46. /package/tests/{content → parse/content}/3.html +0 -0
  47. /package/tests/{content → parse/content}/3_result.json +0 -0
  48. /package/tests/{content → parse/content}/4.html +0 -0
  49. /package/tests/{content → parse/content}/4_result.json +0 -0
  50. /package/tests/{content → parse/content}/custom.html +0 -0
  51. /package/tests/{content → parse/content}/custom.json +0 -0
  52. /package/tests/{content → parse/content}/index.html +0 -0
  53. /package/tests/{content → parse/content}/index.json +0 -0
  54. /package/tests/{content → parse/content}/index_with_templates.json +0 -0
  55. /package/tests/{content → parse/content}/templates.json +0 -0
@@ -1,5 +1,18 @@
1
+ import { jsx, jsxs } from 'react/jsx-runtime';
2
+ import { Fragment } from 'react';
3
+ import { ssr } from '../server-state/SSR.js';
4
+ import { SPLIT } from '../../bin/routes/Parser.js';
5
+
1
6
  const Custom = ({ name, props, children }) => {
2
- return `<custom>${JSON.stringify({ name, props })}<custom>`;
7
+ if (!ssr.current)
8
+ return jsx(Fragment, { children: children });
9
+ const exampleProps = JSON.stringify({ name, props: props() });
10
+ ssr.exportToJV = true;
11
+ const customProps = JSON.stringify({ name, props: props() });
12
+ ssr.exportToJV = false;
13
+ return (
14
+ //@ts-ignore
15
+ jsxs("custom", { children: [customProps, SPLIT, exampleProps, SPLIT, children] }));
3
16
  };
4
17
 
5
18
  export { Custom };
@@ -1,153 +1,16 @@
1
1
  import { createServer } from 'vite';
2
2
  import { AsyncLocalStorage } from 'async_hooks';
3
3
  import { chromium } from 'playwright';
4
- import { createHash } from 'node:crypto';
5
- import { writeFile } from 'fs/promises';
6
4
 
7
5
  const pageCollector = async (page) => {
8
6
  const state = await page.evaluate(() => {
9
7
  const state = Object.fromEntries(window.$exportServerStates);
10
- console.log("BROWSER", state);
11
8
  return state;
12
9
  });
13
- console.log("CLIENT", state);
14
10
  const root = await page.locator("#root").innerHTML();
15
11
  return { root, state };
16
12
  };
17
13
 
18
- const normalizeHtmlWhitespace = (html) => {
19
- return html
20
- .replace(/\r?\n|\r/g, " ")
21
- .replace(/\s+/g, " ")
22
- .replace(/>\s+</g, "><")
23
- .trim();
24
- };
25
-
26
- class Parser {
27
- options;
28
- openVar = "{{";
29
- createVar = "}}";
30
- createArray = "</loopwrapper";
31
- createCustom = "</custom";
32
- deepUp = "<loopwrapper";
33
- deepUp2 = "<custom";
34
- templates = {};
35
- constructor(options) {
36
- this.options = options;
37
- }
38
- parse(content) {
39
- const tree = [];
40
- this.handle(this.options?.normalize ? normalizeHtmlWhitespace(content) : content, tree);
41
- return tree;
42
- }
43
- handle(content, tree) {
44
- let match;
45
- let deep = 0;
46
- let lastIndex = 0;
47
- const tagPattern = new RegExp(`(<loopwrapper(\\s+[^>]*)?>|</loopwrapper>|\{\{|\}\}|<custom(\\s+[^>]*)?>|</custom>)`, "gi");
48
- while ((match = tagPattern.exec(content)) !== null) {
49
- const currentTag = match[0];
50
- const value = content.substring(lastIndex, match.index);
51
- if (currentTag.startsWith(this.createArray)) {
52
- deep--;
53
- if (deep > 0)
54
- continue;
55
- this.createElement(tree, value);
56
- }
57
- else if (currentTag.startsWith(this.deepUp)) {
58
- deep++;
59
- if (deep > 1)
60
- continue;
61
- this.createElement(tree, value);
62
- }
63
- else if (currentTag.startsWith(this.createCustom)) {
64
- deep--;
65
- if (deep != 0)
66
- continue;
67
- this.createCustomElement(tree, value);
68
- }
69
- else if (currentTag.startsWith(this.deepUp2)) {
70
- deep++;
71
- if (deep > 1)
72
- continue;
73
- this.createElement(tree, value);
74
- }
75
- else if (currentTag == this.createVar) {
76
- if (deep != 0)
77
- continue;
78
- this.createElement(tree, value, true);
79
- }
80
- else {
81
- if (deep != 0)
82
- continue;
83
- this.createElement(tree, value);
84
- }
85
- lastIndex = match.index + currentTag.length;
86
- }
87
- if (lastIndex < content.length) {
88
- const value = content.substring(lastIndex);
89
- this.createElement(tree, value);
90
- }
91
- }
92
- createCustomElement(parent, value) {
93
- const [name, ...props] = value.trimStart().split("|");
94
- value = props.join("|");
95
- parent.push({
96
- type: "custom",
97
- name,
98
- props: value,
99
- });
100
- }
101
- createElement(parent, value, isVarible) {
102
- if (isVarible)
103
- return parent.push({
104
- type: "value",
105
- key: value,
106
- });
107
- if (value.trimStart().startsWith("ArrayDataKey=")) {
108
- const [key, ...val] = value.trimStart().substring(13).split("|");
109
- value = val.join("|");
110
- const children = [];
111
- this.handle(value, children);
112
- return parent.push({
113
- type: "array",
114
- data: children,
115
- key,
116
- });
117
- }
118
- if (value)
119
- parent.push({
120
- type: "html",
121
- content: this.options?.templates ? this.createTemplate(value) : value,
122
- });
123
- }
124
- createTemplate(html) {
125
- if (!(html in this.templates)) {
126
- this.templates[html] = createHash("md5").update(Object.keys(this.templates).length.toString()).digest("hex");
127
- }
128
- return this.templates[html];
129
- }
130
- }
131
-
132
- const getRoutesAndTemplates = (pages, normalize = true, templates = true) => {
133
- const routes = [];
134
- const parser = new Parser({ normalize, templates });
135
- for (const [id, template] of pages.entries()) {
136
- const content = parser.parse(template.root);
137
- const mask = template.mask.replaceAll("/", "\\/").replace(/{(.*?)}/, ".+?");
138
- routes.push({
139
- id,
140
- content,
141
- mask,
142
- state: template.state,
143
- });
144
- }
145
- return {
146
- routes,
147
- templates: parser.templates,
148
- };
149
- };
150
-
151
14
  const urlStorage = new AsyncLocalStorage();
152
15
  function hydration() {
153
16
  if (process.env.CHILD_JINRAI_DEV_SERVER) {
@@ -157,6 +20,7 @@ function hydration() {
157
20
  console.log("create mirror");
158
21
  let mirrorServer = undefined;
159
22
  let context = undefined;
23
+ let page = undefined;
160
24
  let debugUrl = undefined;
161
25
  createServer({
162
26
  server: {
@@ -166,12 +30,13 @@ function hydration() {
166
30
  mirrorServer = server;
167
31
  await mirrorServer.listen();
168
32
  debugUrl = mirrorServer.resolvedUrls?.local[0].slice(0, -1);
169
- chromium.launch({ headless: true, devtools: false }).then(async (browser) => {
33
+ chromium.launch({ headless: false, devtools: false, channel: "chrome" }).then(async (browser) => {
170
34
  console.log("create context");
171
35
  context = await browser.newContext({
172
- userAgent: "____fast-ssr-tool___",
36
+ // userAgent: JinraiAgent,
173
37
  locale: "ru-RU",
174
38
  });
39
+ page = await context.newPage();
175
40
  });
176
41
  });
177
42
  return {
@@ -188,23 +53,24 @@ function hydration() {
188
53
  },
189
54
  async transformIndexHtml(html) {
190
55
  const currentUrl = urlStorage.getStore();
191
- if (currentUrl && context) {
192
- const page = await context.newPage();
56
+ if (currentUrl && page) {
193
57
  await page.goto(debugUrl + currentUrl);
194
58
  await page.waitForLoadState("networkidle");
195
- const { root } = await pageCollector(page);
196
- const { routes } = getRoutesAndTemplates([{ id: 1, state: {}, mask: currentUrl, root }], true, false);
197
- console.log({ routes });
198
- writeFile("./routs.json", JSON.stringify(routes, null, 2));
199
- const result = html.replace("<!--app-html-->", root);
200
- page.close();
201
- return result;
59
+ const { state } = await pageCollector(page);
60
+ // const { routes } = getRoutesAndTemplates([{ id: 1, state: {}, mask: currentUrl, root }], true, false)
61
+ // console.log({ routes })
62
+ // writeFile("./routs.json", JSON.stringify(routes, null, 2))
63
+ // html = html.replace("<!--app-html-->", root)
64
+ html = html.replace("<!--app-head-->", JSON.stringify(state, null, 2));
65
+ return html;
202
66
  }
203
67
  return html;
204
68
  },
205
69
  closeWatcher() {
206
70
  console.log("Stop server");
207
- // mirrorServer?.close()
71
+ page?.close();
72
+ mirrorServer?.close();
73
+ context?.close();
208
74
  },
209
75
  };
210
76
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jinrai",
3
- "version": "1.1.2",
3
+ "version": "1.1.3",
4
4
  "description": "A powerful library that analyzes your modern web application and automatically generates a perfectly rendered, static snapshot of its pages. Experience unparalleled loading speed and SEO clarity without the complexity of traditional SSR setups. Simply point Jinrai at your SPA and witness divine speed.",
5
5
  "main": "lib/index.ts",
6
6
  "scripts": {
@@ -66,6 +66,10 @@
66
66
  "./config": {
67
67
  "types": "./lib/config/config.d.ts",
68
68
  "import": "./lib/config/config.js"
69
+ },
70
+ "./vite": {
71
+ "types": "./lib/vite/plugin.d.ts",
72
+ "import": "./lib/vite/plugin.js"
69
73
  }
70
74
  }
71
75
  }
@@ -0,0 +1 @@
1
+ export const JinraiAgent = "____JINRAI_AGENT____"
@@ -4,12 +4,10 @@ import { ServerStateMap } from "../../front/server-state/useServerState"
4
4
  export const pageCollector = async (page: Page): Promise<{ state: ServerStateMap; root: string }> => {
5
5
  const state = await page.evaluate(() => {
6
6
  const state = Object.fromEntries((window as any).$exportServerStates)
7
- console.log("BROWSER", state)
8
7
 
9
8
  return state
10
9
  })
11
10
 
12
- console.log("CLIENT", state)
13
11
  const root = await page.locator("#root").innerHTML()
14
12
 
15
13
  return { root, state }
@@ -3,6 +3,7 @@ import Task from "../ui/task"
3
3
  import { spinners } from "ora"
4
4
  import { pageCollector } from "./pageCollector"
5
5
  import { ServerValue } from "../../front/server-state/serverStates"
6
+ import { JinraiAgent } from "../agent/agent"
6
7
 
7
8
  export interface PageData {
8
9
  id: number
@@ -27,7 +28,7 @@ export const getRawPageData = async (
27
28
  // const test_browser = await chromium.launch({ headless: true, channel: "chrome" })
28
29
 
29
30
  const context = await browser.newContext({
30
- userAgent: "____JINRAI_CLIENT____",
31
+ userAgent: JinraiAgent,
31
32
  locale: "ru-RU",
32
33
  })
33
34
 
@@ -1,6 +1,13 @@
1
1
  import { normalizeHtmlWhitespace } from "../content/normalizeContent"
2
2
  import { createHash } from "node:crypto"
3
3
 
4
+ export const SPLIT = "@#UNIQ_SPLITTER#@"
5
+
6
+ interface CustomElement {
7
+ name: string
8
+ props: object
9
+ }
10
+
4
11
  interface ParserOptions {
5
12
  templates?: boolean
6
13
  normalize?: boolean
@@ -27,7 +34,7 @@ interface ValueElement {
27
34
  interface CustomElement {
28
35
  type: "custom"
29
36
  name: string
30
- props: string
37
+ props: object
31
38
  }
32
39
 
33
40
  export class Parser {
@@ -100,13 +107,13 @@ export class Parser {
100
107
  }
101
108
 
102
109
  createCustomElement(parent: Element[], value: string) {
103
- const [name, ...props] = value.trimStart().split("|")
104
- value = props.join("|")
110
+ const [customProps, exampleProps, children] = value.split(SPLIT)
111
+ const custom = JSON.parse(customProps) as CustomElement
105
112
 
106
113
  parent.push({
107
114
  type: "custom",
108
- name,
109
- props: value,
115
+ name: custom.name,
116
+ props: custom.props,
110
117
  })
111
118
  }
112
119
 
@@ -1,5 +1,6 @@
1
1
  import React from "react"
2
2
  import { ssr } from "./SSR"
3
+ import { JinraiValue } from "../url/search/useSearchValue"
3
4
 
4
5
  // IMPORT REACT
5
6
 
@@ -22,99 +23,173 @@ const getTarget = (data: any, path: string) => {
22
23
  case "undefined":
23
24
  case "symbol":
24
25
  // эти типы можно просто завернуть
25
- return { value: data }
26
+ return { $__ROOT__: data }
26
27
  default:
27
28
  return () => `{{${path}}}`
28
29
  }
29
30
  }
30
31
 
31
- const createDataProxy = (data: any, path: string = ""): DataProxy => {
32
+ type WithDataProxy<T> = T & DataProxy
33
+ function createDataProxy<T>(data: T, path: string = ""): WithDataProxy<T> {
32
34
  if (path.endsWith("@")) sources.set(path.slice(0, -1), data)
33
35
 
34
36
  return new Proxy(getTarget(data, path), {
35
- get: (_: any, prop: string) => {
36
- // if (typeof prop == "symbol") return data[prop]
37
+ get: (_target: any, prop: PropertyKey) => {
38
+ if (ssr.exportToJV) {
39
+ return {
40
+ $JV: {
41
+ key: path + "/" + String(prop),
42
+ type: "proxy",
43
+ def: data,
44
+ },
45
+ } as { $JV?: JinraiValue }
46
+ }
37
47
 
38
- if (!(typeof data == "object" && data !== null && prop in data))
39
- // DEV TOOLS
48
+ // ---------------------------
49
+ // 1. Обработка символов
50
+ // ---------------------------
51
+ if (typeof prop === "symbol") {
40
52
  switch (prop) {
41
- // @ts-ignore
42
53
  case Symbol.toPrimitive:
43
54
  return (hint: string) => {
44
55
  console.log("PROXYDATA", hint)
45
56
  return `{{${path}}}`
46
57
  }
47
- // @ts-ignore
58
+
48
59
  case Symbol.toStringTag:
49
60
  return "Object"
50
- // @ts-ignore
61
+
51
62
  case Symbol.iterator:
52
- return data[Symbol.iterator]
53
- case "$$typeof":
54
- case "type":
63
+ if (typeof data === "object" && data !== null && Symbol.iterator in (data as any)) {
64
+ return (data as any)[Symbol.iterator]
65
+ }
55
66
  return undefined
67
+ }
68
+ }
56
69
 
57
- case "_debugInfo":
58
- return {
59
- note: `State From Request (${path})`,
60
- kind: typeof data,
61
- timestamp: Date.now(),
62
- preview: data,
63
- }
70
+ // ---------------------------
71
+ // 2. DEV tools
72
+ // ---------------------------
73
+ if (!(typeof data === "object" && data !== null && prop in (data as any))) {
74
+ if (prop === "$$typeof" || prop === "type") {
75
+ return undefined
76
+ }
77
+
78
+ if (prop === "_debugInfo") {
79
+ return {
80
+ note: `State From Request (${path})`,
81
+ kind: typeof data,
82
+ timestamp: Date.now(),
83
+ preview: data,
84
+ }
64
85
  }
86
+ }
65
87
 
66
- // SELF
67
- if (prop.startsWith("$")) return (key: string) => `{{${path + "/" + key}${"\\" + prop}}}`
88
+ // ---------------------------
89
+ // 3. SELF: $key returns function
90
+ // ---------------------------
91
+ if (typeof prop === "string" && prop.startsWith("$")) {
92
+ return (key: string) => `{{${path + "/" + key}${"\\" + prop}}}`
93
+ }
68
94
 
69
- // TYPES
95
+ // ---------------------------
96
+ // 4. Types special cases
97
+ // ---------------------------
70
98
  switch (typeof data) {
71
99
  case "string":
72
- switch (prop) {
73
- case "length":
74
- case "entries":
75
- return undefined
76
- }
100
+ if (prop === "length" || prop === "entries") return undefined
101
+ break
102
+
77
103
  case "number":
78
- switch (prop) {
79
- case "@@iterator":
80
- return undefined
81
- }
104
+ if (prop === "@@iterator") return undefined
105
+ break
82
106
 
83
107
  default:
84
- switch (prop) {
85
- case "then":
86
- return undefined
87
- }
108
+ if (prop === "then") return undefined
88
109
  }
89
110
 
90
- // OTHER
91
- switch (prop) {
92
- case "find":
93
- return data[prop]
94
- case "map":
95
- case "forEach":
96
- return (callback: (arg0: DataProxy) => any) =>
97
- React.createElement("loopwrapper", null, [
98
- `ArrayDataKey=${path}|`,
99
- Object.entries(data)
100
- .slice(0, 1)
101
- .map(([key, itm]) => callback(createDataProxy(itm, `${path}/[ITEM=${key}]`))),
102
- ])
103
- case "getValue":
104
- return () => data
105
- case "toJSON":
106
- return () => {
107
- console.log("dataproxy toJSON", path, data)
108
- return ssr.exportParams ? `{{${path}}}` : data
109
- }
110
- default:
111
- if (data && (typeof data[prop] == "object" || Array.isArray(data[prop]))) {
112
- return createDataProxy(data[prop], path + "/" + prop)
113
- }
114
- return `{{${path + "/" + prop}}}`
111
+ // ---------------------------
112
+ // 5. Array-like handlers
113
+ // ---------------------------
114
+ if (prop === "find") {
115
+ return (data as any)[prop]
115
116
  }
117
+
118
+ if (prop === "length") {
119
+ if (Array.isArray(data)) {
120
+ return data.length
121
+ }
122
+ }
123
+
124
+ if (prop === "map" || prop === "forEach") {
125
+ return (callback: (arg: DataProxy) => any) =>
126
+ React.createElement("loopwrapper", null, [
127
+ `ArrayDataKey=${path}|`,
128
+ Object.entries(data as any)
129
+ .slice(0, 1)
130
+ .map(([key, itm]) => callback(createDataProxy(itm, `${path}/[ITEM=${key}]`))),
131
+ ])
132
+ }
133
+
134
+ // ---------------------------
135
+ // 6. getValue
136
+ // ---------------------------
137
+ if (prop === "getValue") {
138
+ return () => data
139
+ }
140
+
141
+ // ---------------------------
142
+ // 7. toJSON
143
+ // ---------------------------
144
+ if (prop === "toJSON") {
145
+ return () => {
146
+ console.log("dataproxy toJSON", path, data)
147
+
148
+ return ssr.exportParams
149
+ ? `{{${path}}}`
150
+ : ({
151
+ $JV: {
152
+ key: path,
153
+ type: "proxy",
154
+ def: data,
155
+ separator: "",
156
+ },
157
+ } as { $JV?: JinraiValue })
158
+ }
159
+ }
160
+
161
+ // ---------------------------
162
+ // 8. Nested object
163
+ // ---------------------------
164
+ const value = (data as any)[prop]
165
+ if (!ssr.exportParams) {
166
+ return value
167
+ }
168
+
169
+ if (value && (typeof value === "object" || Array.isArray(value))) {
170
+ return createDataProxy(value, path + "/" + String(prop))
171
+ }
172
+
173
+ // if (ssr.exportParams) {
174
+
175
+ // if (typeof value == "string") {
176
+ // const key = `{{${path + "/" + String(prop)}}}`
177
+ // const jv = getJinraiValue(key, "proxyValue", "", value)
178
+
179
+ // return value.bindSource(jv)
180
+ // }
181
+
182
+ // return key
183
+ // } else {
184
+ // return value
185
+ // }
186
+
187
+ // ---------------------------
188
+ // 9. Final primitive fallback
189
+ // ---------------------------
190
+ return `{{${path + "/" + String(prop)}}}`
116
191
  },
117
- })
192
+ }) as any
118
193
  }
119
194
 
120
195
  export default createDataProxy
@@ -1,4 +1,20 @@
1
+ import { JinraiAgent } from "../../bin/agent/agent"
2
+
1
3
  export const ssr = {
2
- current: true, //navigator.userAgent == "____JINRAI_CLIENT____",
3
- exportParams: false,
4
+ current: navigator.userAgent == JinraiAgent,
5
+ exportParams: true,
6
+ exportToJV: false,
7
+ }
8
+
9
+ if (window != undefined) {
10
+ // @ts-ignore
11
+ window.__ssr = ssr
12
+ }
13
+
14
+ export const stringifyInput = (input: any) => {
15
+ ssr.exportParams = false
16
+ const result = JSON.stringify(input)
17
+ ssr.exportParams = true
18
+
19
+ return result
4
20
  }
@@ -1,5 +1,6 @@
1
1
  import { ssr } from "./SSR"
2
2
  import { sources } from "./DataProxy"
3
+ import { getJinraiValue, JinraiValue } from "../url/search/useSearchValue"
3
4
 
4
5
  export function real<T>(value: T): T {
5
6
  if (!ssr.current) return value
@@ -8,7 +9,13 @@ export function real<T>(value: T): T {
8
9
  case "number":
9
10
  return value
10
11
  case "string":
11
- return value.startsWith("{{") && value.endsWith("}}") ? getArrayByPath(value.slice(2, -2)) : value
12
+ if (value.startsWith("{{") && value.endsWith("}}")) {
13
+ const result = getArrayByPath(value.slice(2, -2))
14
+
15
+ return wrapSource<T>(result, getJinraiValue(value, "proxy", "", result))
16
+ }
17
+
18
+ return value
12
19
 
13
20
  case "object":
14
21
  // @ts-ignore
@@ -24,6 +31,14 @@ export function real<T>(value: T): T {
24
31
  }
25
32
  }
26
33
 
34
+ const wrapSource = <T>(value: T, source: JinraiValue): T => {
35
+ if (typeof value == "string") {
36
+ return value.bindSource(source) as T
37
+ }
38
+
39
+ return value
40
+ }
41
+
27
42
  const getArrayByPath = (path: string) => {
28
43
  const [sourceIndex, requestPath] = path.split("@", 2)
29
44
  const keys = requestPath.split("/")