@matthesketh/utopia-router 0.3.1 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -31,33 +31,49 @@ __export(index_exports, {
31
31
  destroy: () => destroy,
32
32
  filePathToRoute: () => filePathToRoute,
33
33
  forward: () => forward,
34
+ getQueryParam: () => getQueryParam,
35
+ getRouteParam: () => getRouteParam,
34
36
  isNavigating: () => isNavigating,
35
37
  matchRoute: () => matchRoute,
36
- navigate: () => navigate
38
+ navigate: () => navigate,
39
+ queryParams: () => queryParams,
40
+ setQueryParam: () => setQueryParam,
41
+ setQueryParams: () => setQueryParams
37
42
  });
38
43
  module.exports = __toCommonJS(index_exports);
39
44
 
40
45
  // src/matcher.ts
46
+ var BACKSLASH_RE = /\\/g;
47
+ var PAGE_FILE_RE = /\/?\+page\.\w+$/;
48
+ var LAYOUT_OR_ERROR_FILE_RE = /\/?\+(layout|error)\.\w+$/;
49
+ var GROUP_SEGMENT_RE = /^\(.+\)$/;
50
+ var REST_PARAM_RE = /^\[\.\.\..+\]$/;
51
+ var DYNAMIC_PARAM_RE = /^\[.+\]$/;
52
+ var ROOT_ROUTE_RE = /^\/$/;
53
+ var REGEX_SPECIAL_CHARS_RE = /[.*+?^${}()|[\]\\]/g;
54
+ var PAGE_FILE_TEST_RE = /\+page\.\w+$/;
55
+ var LAYOUT_FILE_TEST_RE = /\+layout\.\w+$/;
56
+ var ERROR_FILE_TEST_RE = /\+error\.\w+$/;
41
57
  function filePathToRoute(filePath) {
42
- let normalized = filePath.replace(/\\/g, "/");
58
+ let normalized = filePath.replace(BACKSLASH_RE, "/");
43
59
  const routesIdx = normalized.indexOf("routes/");
44
60
  if (routesIdx !== -1) {
45
61
  normalized = normalized.slice(routesIdx + "routes/".length);
46
62
  }
47
- normalized = normalized.replace(/\/?\+page\.\w+$/, "");
48
- normalized = normalized.replace(/\/?\+(layout|error)\.\w+$/, "");
63
+ normalized = normalized.replace(PAGE_FILE_RE, "");
64
+ normalized = normalized.replace(LAYOUT_OR_ERROR_FILE_RE, "");
49
65
  const segments = normalized.split("/").filter(Boolean);
50
66
  const routeSegments = [];
51
67
  for (const segment of segments) {
52
- if (/^\(.+\)$/.test(segment)) {
68
+ if (GROUP_SEGMENT_RE.test(segment)) {
53
69
  continue;
54
70
  }
55
- if (/^\[\.\.\..+\]$/.test(segment)) {
71
+ if (REST_PARAM_RE.test(segment)) {
56
72
  const paramName = segment.slice(4, -1);
57
73
  routeSegments.push(`*${paramName}`);
58
74
  continue;
59
75
  }
60
- if (/^\[.+\]$/.test(segment)) {
76
+ if (DYNAMIC_PARAM_RE.test(segment)) {
61
77
  const paramName = segment.slice(1, -1);
62
78
  routeSegments.push(`:${paramName}`);
63
79
  continue;
@@ -71,7 +87,7 @@ function compilePattern(pattern) {
71
87
  const params = [];
72
88
  if (pattern === "/") {
73
89
  return {
74
- regex: /^\/$/,
90
+ regex: ROOT_ROUTE_RE,
75
91
  params: []
76
92
  };
77
93
  }
@@ -93,12 +109,13 @@ function compilePattern(pattern) {
93
109
  }
94
110
  regexStr = "^" + regexStr + "/?$";
95
111
  return {
112
+ // Dynamic regex — built from sanitized input (segments are escaped via escapeRegex).
96
113
  regex: new RegExp(regexStr),
97
114
  params
98
115
  };
99
116
  }
100
117
  function escapeRegex(str) {
101
- return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
118
+ return str.replace(REGEX_SPECIAL_CHARS_RE, "\\$&");
102
119
  }
103
120
  function matchRoute(url, routes2) {
104
121
  let pathname = url.pathname;
@@ -122,12 +139,12 @@ function buildRouteTable(manifest) {
122
139
  const layouts = /* @__PURE__ */ new Map();
123
140
  const errors = /* @__PURE__ */ new Map();
124
141
  for (const [filePath, importFn] of Object.entries(manifest)) {
125
- const normalized = filePath.replace(/\\/g, "/");
126
- if (/\+page\.\w+$/.test(normalized)) {
142
+ const normalized = filePath.replace(BACKSLASH_RE, "/");
143
+ if (PAGE_FILE_TEST_RE.test(normalized)) {
127
144
  pages.set(normalized, importFn);
128
- } else if (/\+layout\.\w+$/.test(normalized)) {
145
+ } else if (LAYOUT_FILE_TEST_RE.test(normalized)) {
129
146
  layouts.set(normalized, importFn);
130
- } else if (/\+error\.\w+$/.test(normalized)) {
147
+ } else if (ERROR_FILE_TEST_RE.test(normalized)) {
131
148
  errors.set(normalized, importFn);
132
149
  }
133
150
  }
@@ -174,12 +191,12 @@ function routeSpecificity(path) {
174
191
  return score;
175
192
  }
176
193
  function findNearestSpecialFile(pageFilePath, specialFiles) {
177
- const normalized = pageFilePath.replace(/\\/g, "/");
194
+ const normalized = pageFilePath.replace(BACKSLASH_RE, "/");
178
195
  const lastSlash = normalized.lastIndexOf("/");
179
196
  let dir = lastSlash !== -1 ? normalized.slice(0, lastSlash) : "";
180
197
  while (dir) {
181
198
  for (const [specialPath, importFn] of specialFiles) {
182
- const specialNorm = specialPath.replace(/\\/g, "/");
199
+ const specialNorm = specialPath.replace(BACKSLASH_RE, "/");
183
200
  const specialLastSlash = specialNorm.lastIndexOf("/");
184
201
  const specialDir = specialLastSlash !== -1 ? specialNorm.slice(0, specialLastSlash) : "";
185
202
  if (specialDir === dir) {
@@ -197,6 +214,7 @@ function findNearestSpecialFile(pageFilePath, specialFiles) {
197
214
 
198
215
  // src/router.ts
199
216
  var import_utopia_core = require("@matthesketh/utopia-core");
217
+ var VALID_DOM_ID_RE = /^[A-Za-z0-9_-]+$/;
200
218
  var currentRoute = (0, import_utopia_core.signal)(null);
201
219
  var isNavigating = (0, import_utopia_core.signal)(false);
202
220
  var routes = [];
@@ -319,10 +337,13 @@ async function navigate(url, options = {}) {
319
337
  currentRoute.set(match);
320
338
  requestAnimationFrame(() => {
321
339
  if (fullUrl.hash) {
322
- const el = document.getElementById(fullUrl.hash.slice(1));
323
- if (el) {
324
- el.scrollIntoView();
325
- return;
340
+ const hashId = fullUrl.hash.slice(1);
341
+ if (hashId && VALID_DOM_ID_RE.test(hashId)) {
342
+ const el = document.getElementById(hashId);
343
+ if (el) {
344
+ el.scrollIntoView();
345
+ return;
346
+ }
326
347
  }
327
348
  }
328
349
  window.scrollTo(0, 0);
@@ -567,8 +588,61 @@ function clearContainer(container) {
567
588
  container.removeChild(container.firstChild);
568
589
  }
569
590
  }
591
+ var AMPERSAND_RE = /&/g;
592
+ var LESS_THAN_RE = /</g;
593
+ var GREATER_THAN_RE = />/g;
594
+ var DOUBLE_QUOTE_RE = /"/g;
595
+ var SINGLE_QUOTE_RE = /'/g;
570
596
  function escapeHtml(str) {
571
- return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#x27;");
597
+ return str.replace(AMPERSAND_RE, "&amp;").replace(LESS_THAN_RE, "&lt;").replace(GREATER_THAN_RE, "&gt;").replace(DOUBLE_QUOTE_RE, "&quot;").replace(SINGLE_QUOTE_RE, "&#x27;");
598
+ }
599
+
600
+ // src/query.ts
601
+ var import_utopia_core3 = require("@matthesketh/utopia-core");
602
+ var queryParams = (0, import_utopia_core3.computed)(() => {
603
+ const match = currentRoute();
604
+ if (!match) return {};
605
+ const result = {};
606
+ match.url.searchParams.forEach((value, key) => {
607
+ result[key] = value;
608
+ });
609
+ return result;
610
+ });
611
+ function getQueryParam(name) {
612
+ return (0, import_utopia_core3.computed)(() => {
613
+ const match = currentRoute();
614
+ if (!match) return null;
615
+ return match.url.searchParams.get(name);
616
+ });
617
+ }
618
+ function setQueryParam(name, value) {
619
+ if (typeof window === "undefined") return;
620
+ const url = new URL(window.location.href);
621
+ if (value === null) {
622
+ url.searchParams.delete(name);
623
+ } else {
624
+ url.searchParams.set(name, value);
625
+ }
626
+ navigate(url.pathname + url.search + url.hash, { replace: true });
627
+ }
628
+ function setQueryParams(params) {
629
+ if (typeof window === "undefined") return;
630
+ const url = new URL(window.location.href);
631
+ for (const [key, value] of Object.entries(params)) {
632
+ if (value === null) {
633
+ url.searchParams.delete(key);
634
+ } else {
635
+ url.searchParams.set(key, value);
636
+ }
637
+ }
638
+ navigate(url.pathname + url.search + url.hash, { replace: true });
639
+ }
640
+ function getRouteParam(name) {
641
+ return (0, import_utopia_core3.computed)(() => {
642
+ const match = currentRoute();
643
+ if (!match) return null;
644
+ return match.params[name] ?? null;
645
+ });
572
646
  }
573
647
  // Annotate the CommonJS export names for ESM import in node:
574
648
  0 && (module.exports = {
@@ -583,7 +657,12 @@ function escapeHtml(str) {
583
657
  destroy,
584
658
  filePathToRoute,
585
659
  forward,
660
+ getQueryParam,
661
+ getRouteParam,
586
662
  isNavigating,
587
663
  matchRoute,
588
- navigate
664
+ navigate,
665
+ queryParams,
666
+ setQueryParam,
667
+ setQueryParams
589
668
  });
package/dist/index.d.cts CHANGED
@@ -214,4 +214,29 @@ declare function createLink(props: {
214
214
  activeClass?: string;
215
215
  }): HTMLAnchorElement;
216
216
 
217
- export { type BeforeNavigateHook, type Route, type RouteMatch, type RouterState, back, beforeNavigate, buildRouteTable, compilePattern, createLink, createRouter, createRouterView, currentRoute, destroy, filePathToRoute, forward, isNavigating, matchRoute, navigate };
217
+ /**
218
+ * Reactive computed signal returning all current query parameters as a plain object.
219
+ */
220
+ declare const queryParams: _matthesketh_utopia_core.ReadonlySignal<Record<string, string>>;
221
+ /**
222
+ * Returns a computed signal for a specific query parameter.
223
+ * Returns null if the parameter is not present.
224
+ */
225
+ declare function getQueryParam(name: string): _matthesketh_utopia_core.ReadonlySignal<string | null>;
226
+ /**
227
+ * Update a single query parameter and navigate (replace mode).
228
+ * Pass null to remove the parameter.
229
+ */
230
+ declare function setQueryParam(name: string, value: string | null): void;
231
+ /**
232
+ * Update multiple query parameters in a single navigation.
233
+ * Pass null for a value to remove that parameter.
234
+ */
235
+ declare function setQueryParams(params: Record<string, string | null>): void;
236
+ /**
237
+ * Returns a computed signal for a specific route path parameter.
238
+ * Returns null if the parameter is not present.
239
+ */
240
+ declare function getRouteParam(name: string): _matthesketh_utopia_core.ReadonlySignal<string | null>;
241
+
242
+ export { type BeforeNavigateHook, type Route, type RouteMatch, type RouterState, back, beforeNavigate, buildRouteTable, compilePattern, createLink, createRouter, createRouterView, currentRoute, destroy, filePathToRoute, forward, getQueryParam, getRouteParam, isNavigating, matchRoute, navigate, queryParams, setQueryParam, setQueryParams };
package/dist/index.d.ts CHANGED
@@ -214,4 +214,29 @@ declare function createLink(props: {
214
214
  activeClass?: string;
215
215
  }): HTMLAnchorElement;
216
216
 
217
- export { type BeforeNavigateHook, type Route, type RouteMatch, type RouterState, back, beforeNavigate, buildRouteTable, compilePattern, createLink, createRouter, createRouterView, currentRoute, destroy, filePathToRoute, forward, isNavigating, matchRoute, navigate };
217
+ /**
218
+ * Reactive computed signal returning all current query parameters as a plain object.
219
+ */
220
+ declare const queryParams: _matthesketh_utopia_core.ReadonlySignal<Record<string, string>>;
221
+ /**
222
+ * Returns a computed signal for a specific query parameter.
223
+ * Returns null if the parameter is not present.
224
+ */
225
+ declare function getQueryParam(name: string): _matthesketh_utopia_core.ReadonlySignal<string | null>;
226
+ /**
227
+ * Update a single query parameter and navigate (replace mode).
228
+ * Pass null to remove the parameter.
229
+ */
230
+ declare function setQueryParam(name: string, value: string | null): void;
231
+ /**
232
+ * Update multiple query parameters in a single navigation.
233
+ * Pass null for a value to remove that parameter.
234
+ */
235
+ declare function setQueryParams(params: Record<string, string | null>): void;
236
+ /**
237
+ * Returns a computed signal for a specific route path parameter.
238
+ * Returns null if the parameter is not present.
239
+ */
240
+ declare function getRouteParam(name: string): _matthesketh_utopia_core.ReadonlySignal<string | null>;
241
+
242
+ export { type BeforeNavigateHook, type Route, type RouteMatch, type RouterState, back, beforeNavigate, buildRouteTable, compilePattern, createLink, createRouter, createRouterView, currentRoute, destroy, filePathToRoute, forward, getQueryParam, getRouteParam, isNavigating, matchRoute, navigate, queryParams, setQueryParam, setQueryParams };
package/dist/index.js CHANGED
@@ -1,24 +1,35 @@
1
1
  // src/matcher.ts
2
+ var BACKSLASH_RE = /\\/g;
3
+ var PAGE_FILE_RE = /\/?\+page\.\w+$/;
4
+ var LAYOUT_OR_ERROR_FILE_RE = /\/?\+(layout|error)\.\w+$/;
5
+ var GROUP_SEGMENT_RE = /^\(.+\)$/;
6
+ var REST_PARAM_RE = /^\[\.\.\..+\]$/;
7
+ var DYNAMIC_PARAM_RE = /^\[.+\]$/;
8
+ var ROOT_ROUTE_RE = /^\/$/;
9
+ var REGEX_SPECIAL_CHARS_RE = /[.*+?^${}()|[\]\\]/g;
10
+ var PAGE_FILE_TEST_RE = /\+page\.\w+$/;
11
+ var LAYOUT_FILE_TEST_RE = /\+layout\.\w+$/;
12
+ var ERROR_FILE_TEST_RE = /\+error\.\w+$/;
2
13
  function filePathToRoute(filePath) {
3
- let normalized = filePath.replace(/\\/g, "/");
14
+ let normalized = filePath.replace(BACKSLASH_RE, "/");
4
15
  const routesIdx = normalized.indexOf("routes/");
5
16
  if (routesIdx !== -1) {
6
17
  normalized = normalized.slice(routesIdx + "routes/".length);
7
18
  }
8
- normalized = normalized.replace(/\/?\+page\.\w+$/, "");
9
- normalized = normalized.replace(/\/?\+(layout|error)\.\w+$/, "");
19
+ normalized = normalized.replace(PAGE_FILE_RE, "");
20
+ normalized = normalized.replace(LAYOUT_OR_ERROR_FILE_RE, "");
10
21
  const segments = normalized.split("/").filter(Boolean);
11
22
  const routeSegments = [];
12
23
  for (const segment of segments) {
13
- if (/^\(.+\)$/.test(segment)) {
24
+ if (GROUP_SEGMENT_RE.test(segment)) {
14
25
  continue;
15
26
  }
16
- if (/^\[\.\.\..+\]$/.test(segment)) {
27
+ if (REST_PARAM_RE.test(segment)) {
17
28
  const paramName = segment.slice(4, -1);
18
29
  routeSegments.push(`*${paramName}`);
19
30
  continue;
20
31
  }
21
- if (/^\[.+\]$/.test(segment)) {
32
+ if (DYNAMIC_PARAM_RE.test(segment)) {
22
33
  const paramName = segment.slice(1, -1);
23
34
  routeSegments.push(`:${paramName}`);
24
35
  continue;
@@ -32,7 +43,7 @@ function compilePattern(pattern) {
32
43
  const params = [];
33
44
  if (pattern === "/") {
34
45
  return {
35
- regex: /^\/$/,
46
+ regex: ROOT_ROUTE_RE,
36
47
  params: []
37
48
  };
38
49
  }
@@ -54,12 +65,13 @@ function compilePattern(pattern) {
54
65
  }
55
66
  regexStr = "^" + regexStr + "/?$";
56
67
  return {
68
+ // Dynamic regex — built from sanitized input (segments are escaped via escapeRegex).
57
69
  regex: new RegExp(regexStr),
58
70
  params
59
71
  };
60
72
  }
61
73
  function escapeRegex(str) {
62
- return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
74
+ return str.replace(REGEX_SPECIAL_CHARS_RE, "\\$&");
63
75
  }
64
76
  function matchRoute(url, routes2) {
65
77
  let pathname = url.pathname;
@@ -83,12 +95,12 @@ function buildRouteTable(manifest) {
83
95
  const layouts = /* @__PURE__ */ new Map();
84
96
  const errors = /* @__PURE__ */ new Map();
85
97
  for (const [filePath, importFn] of Object.entries(manifest)) {
86
- const normalized = filePath.replace(/\\/g, "/");
87
- if (/\+page\.\w+$/.test(normalized)) {
98
+ const normalized = filePath.replace(BACKSLASH_RE, "/");
99
+ if (PAGE_FILE_TEST_RE.test(normalized)) {
88
100
  pages.set(normalized, importFn);
89
- } else if (/\+layout\.\w+$/.test(normalized)) {
101
+ } else if (LAYOUT_FILE_TEST_RE.test(normalized)) {
90
102
  layouts.set(normalized, importFn);
91
- } else if (/\+error\.\w+$/.test(normalized)) {
103
+ } else if (ERROR_FILE_TEST_RE.test(normalized)) {
92
104
  errors.set(normalized, importFn);
93
105
  }
94
106
  }
@@ -135,12 +147,12 @@ function routeSpecificity(path) {
135
147
  return score;
136
148
  }
137
149
  function findNearestSpecialFile(pageFilePath, specialFiles) {
138
- const normalized = pageFilePath.replace(/\\/g, "/");
150
+ const normalized = pageFilePath.replace(BACKSLASH_RE, "/");
139
151
  const lastSlash = normalized.lastIndexOf("/");
140
152
  let dir = lastSlash !== -1 ? normalized.slice(0, lastSlash) : "";
141
153
  while (dir) {
142
154
  for (const [specialPath, importFn] of specialFiles) {
143
- const specialNorm = specialPath.replace(/\\/g, "/");
155
+ const specialNorm = specialPath.replace(BACKSLASH_RE, "/");
144
156
  const specialLastSlash = specialNorm.lastIndexOf("/");
145
157
  const specialDir = specialLastSlash !== -1 ? specialNorm.slice(0, specialLastSlash) : "";
146
158
  if (specialDir === dir) {
@@ -158,6 +170,7 @@ function findNearestSpecialFile(pageFilePath, specialFiles) {
158
170
 
159
171
  // src/router.ts
160
172
  import { signal } from "@matthesketh/utopia-core";
173
+ var VALID_DOM_ID_RE = /^[A-Za-z0-9_-]+$/;
161
174
  var currentRoute = signal(null);
162
175
  var isNavigating = signal(false);
163
176
  var routes = [];
@@ -280,10 +293,13 @@ async function navigate(url, options = {}) {
280
293
  currentRoute.set(match);
281
294
  requestAnimationFrame(() => {
282
295
  if (fullUrl.hash) {
283
- const el = document.getElementById(fullUrl.hash.slice(1));
284
- if (el) {
285
- el.scrollIntoView();
286
- return;
296
+ const hashId = fullUrl.hash.slice(1);
297
+ if (hashId && VALID_DOM_ID_RE.test(hashId)) {
298
+ const el = document.getElementById(hashId);
299
+ if (el) {
300
+ el.scrollIntoView();
301
+ return;
302
+ }
287
303
  }
288
304
  }
289
305
  window.scrollTo(0, 0);
@@ -528,8 +544,61 @@ function clearContainer(container) {
528
544
  container.removeChild(container.firstChild);
529
545
  }
530
546
  }
547
+ var AMPERSAND_RE = /&/g;
548
+ var LESS_THAN_RE = /</g;
549
+ var GREATER_THAN_RE = />/g;
550
+ var DOUBLE_QUOTE_RE = /"/g;
551
+ var SINGLE_QUOTE_RE = /'/g;
531
552
  function escapeHtml(str) {
532
- return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#x27;");
553
+ return str.replace(AMPERSAND_RE, "&amp;").replace(LESS_THAN_RE, "&lt;").replace(GREATER_THAN_RE, "&gt;").replace(DOUBLE_QUOTE_RE, "&quot;").replace(SINGLE_QUOTE_RE, "&#x27;");
554
+ }
555
+
556
+ // src/query.ts
557
+ import { computed } from "@matthesketh/utopia-core";
558
+ var queryParams = computed(() => {
559
+ const match = currentRoute();
560
+ if (!match) return {};
561
+ const result = {};
562
+ match.url.searchParams.forEach((value, key) => {
563
+ result[key] = value;
564
+ });
565
+ return result;
566
+ });
567
+ function getQueryParam(name) {
568
+ return computed(() => {
569
+ const match = currentRoute();
570
+ if (!match) return null;
571
+ return match.url.searchParams.get(name);
572
+ });
573
+ }
574
+ function setQueryParam(name, value) {
575
+ if (typeof window === "undefined") return;
576
+ const url = new URL(window.location.href);
577
+ if (value === null) {
578
+ url.searchParams.delete(name);
579
+ } else {
580
+ url.searchParams.set(name, value);
581
+ }
582
+ navigate(url.pathname + url.search + url.hash, { replace: true });
583
+ }
584
+ function setQueryParams(params) {
585
+ if (typeof window === "undefined") return;
586
+ const url = new URL(window.location.href);
587
+ for (const [key, value] of Object.entries(params)) {
588
+ if (value === null) {
589
+ url.searchParams.delete(key);
590
+ } else {
591
+ url.searchParams.set(key, value);
592
+ }
593
+ }
594
+ navigate(url.pathname + url.search + url.hash, { replace: true });
595
+ }
596
+ function getRouteParam(name) {
597
+ return computed(() => {
598
+ const match = currentRoute();
599
+ if (!match) return null;
600
+ return match.params[name] ?? null;
601
+ });
533
602
  }
534
603
  export {
535
604
  back,
@@ -543,7 +612,12 @@ export {
543
612
  destroy,
544
613
  filePathToRoute,
545
614
  forward,
615
+ getQueryParam,
616
+ getRouteParam,
546
617
  isNavigating,
547
618
  matchRoute,
548
- navigate
619
+ navigate,
620
+ queryParams,
621
+ setQueryParam,
622
+ setQueryParams
549
623
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@matthesketh/utopia-router",
3
- "version": "0.3.1",
3
+ "version": "0.5.0",
4
4
  "description": "File-based routing for UtopiaJS",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -40,8 +40,8 @@
40
40
  "dist"
41
41
  ],
42
42
  "dependencies": {
43
- "@matthesketh/utopia-core": "0.3.1",
44
- "@matthesketh/utopia-runtime": "0.3.1"
43
+ "@matthesketh/utopia-core": "0.5.0",
44
+ "@matthesketh/utopia-runtime": "0.5.0"
45
45
  },
46
46
  "scripts": {
47
47
  "build": "tsup src/index.ts --format esm,cjs --dts",