@chelone/core 0.6.0 → 0.7.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.
Files changed (242) hide show
  1. package/dist/assistant/assistant-session.d.ts +6 -0
  2. package/dist/assistant/assistant-session.js +204 -181
  3. package/dist/assistant/echo-transport.js +26 -29
  4. package/dist/assistant/http-transport.d.ts +10 -0
  5. package/dist/assistant/http-transport.js +98 -0
  6. package/dist/assistant/types.d.ts +1 -7
  7. package/dist/auth/access-control.d.ts +13 -5
  8. package/dist/auth/access-control.js +25 -21
  9. package/dist/auth/auth-options.d.ts +21 -17
  10. package/dist/auth/auth-options.js +80 -48
  11. package/dist/auth/permission-matrix.d.ts +12 -0
  12. package/dist/auth/permission-matrix.js +67 -0
  13. package/dist/auth/types.d.ts +14 -4
  14. package/dist/block-editor/block-document.d.ts +60 -0
  15. package/dist/block-editor/block-document.js +143 -0
  16. package/dist/block-editor/data-blocks.d.ts +63 -0
  17. package/dist/block-editor/data-blocks.js +117 -0
  18. package/dist/chart/chart-data.js +46 -44
  19. package/dist/command-menu/hotkey.js +58 -47
  20. package/dist/command-menu/recents.js +8 -11
  21. package/dist/command-menu/registry.d.ts +9 -5
  22. package/dist/command-menu/registry.js +36 -46
  23. package/dist/controller/calendar-dates.js +82 -113
  24. package/dist/controller/calendar-events.js +93 -88
  25. package/dist/controller/client-list-controller.d.ts +2 -2
  26. package/dist/controller/client-list-controller.js +251 -254
  27. package/dist/controller/data-table-columns.d.ts +2 -0
  28. package/dist/controller/data-table-columns.js +50 -56
  29. package/dist/controller/deep-equal.d.ts +1 -0
  30. package/dist/controller/deep-equal.js +14 -0
  31. package/dist/controller/filter-toggle.js +36 -44
  32. package/dist/controller/in-place-editor.d.ts +42 -0
  33. package/dist/controller/in-place-editor.js +42 -0
  34. package/dist/controller/infinite-list-controller.js +219 -189
  35. package/dist/controller/inspector.d.ts +49 -0
  36. package/dist/controller/inspector.js +64 -0
  37. package/dist/controller/job-monitor.d.ts +3 -4
  38. package/dist/controller/job-monitor.js +37 -47
  39. package/dist/controller/kanban-board.d.ts +2 -3
  40. package/dist/controller/kanban-board.js +43 -24
  41. package/dist/controller/list-controller.d.ts +5 -5
  42. package/dist/controller/list-controller.js +444 -400
  43. package/dist/controller/list-params.js +96 -87
  44. package/dist/controller/pivot-grid.js +89 -95
  45. package/dist/controller/prev-next.d.ts +2 -2
  46. package/dist/controller/prev-next.js +74 -54
  47. package/dist/controller/record-controllers.d.ts +14 -6
  48. package/dist/controller/record-controllers.js +288 -257
  49. package/dist/controller/record-diff.d.ts +21 -0
  50. package/dist/controller/record-diff.js +50 -0
  51. package/dist/controller/record-timeline.js +42 -30
  52. package/dist/controller/record-tree.js +57 -47
  53. package/dist/controller/record-workflow.d.ts +38 -16
  54. package/dist/controller/record-workflow.js +85 -64
  55. package/dist/controller/reference-many-controller.js +198 -192
  56. package/dist/controller/reference-one-controller.d.ts +1 -1
  57. package/dist/controller/reference-one-controller.js +63 -56
  58. package/dist/controller/saved-queries.js +104 -91
  59. package/dist/controller/translate-fallback.js +10 -6
  60. package/dist/csv/export.d.ts +2 -1
  61. package/dist/csv/export.js +43 -40
  62. package/dist/csv/import-controller.js +185 -169
  63. package/dist/csv/match-headers.js +29 -22
  64. package/dist/csv/parse-with.js +60 -54
  65. package/dist/csv/run-import.js +69 -72
  66. package/dist/csv/validate-rows.d.ts +1 -1
  67. package/dist/csv/validate-rows.js +65 -52
  68. package/dist/data/cache-ops.d.ts +5 -4
  69. package/dist/data/cache-ops.js +208 -188
  70. package/dist/data/combine-data-providers.js +25 -23
  71. package/dist/data/data-provider-log.d.ts +45 -0
  72. package/dist/data/data-provider-log.js +93 -0
  73. package/dist/data/errors.js +14 -10
  74. package/dist/data/fake-provider.d.ts +6 -3
  75. package/dist/data/fake-provider.js +129 -114
  76. package/dist/data/fetch-utils.js +61 -44
  77. package/dist/data/filter-branch.d.ts +5 -0
  78. package/dist/data/filter-branch.js +10 -0
  79. package/dist/data/get-many-aggregate.js +84 -76
  80. package/dist/data/http-error.js +17 -12
  81. package/dist/data/keys.js +50 -36
  82. package/dist/data/mutation-options.d.ts +14 -9
  83. package/dist/data/mutation-options.js +93 -109
  84. package/dist/data/query-options.js +73 -68
  85. package/dist/data/remove-empty.d.ts +3 -0
  86. package/dist/data/remove-empty.js +11 -0
  87. package/dist/data/undoable.d.ts +2 -2
  88. package/dist/data/undoable.js +190 -174
  89. package/dist/data/with-lifecycle-callbacks.d.ts +45 -17
  90. package/dist/data/with-lifecycle-callbacks.js +66 -52
  91. package/dist/environment/application-update-store.js +58 -55
  92. package/dist/environment/offline-store.js +21 -21
  93. package/dist/form/array-binding.d.ts +30 -11
  94. package/dist/form/array-binding.js +184 -166
  95. package/dist/form/choice-ids.d.ts +7 -0
  96. package/dist/form/choice-ids.js +11 -0
  97. package/dist/form/choices-controller.d.ts +3 -2
  98. package/dist/form/choices-controller.js +302 -275
  99. package/dist/form/date-format.d.ts +38 -0
  100. package/dist/form/date-format.js +89 -0
  101. package/dist/form/dirty-values.d.ts +2 -2
  102. package/dist/form/dirty-values.js +22 -11
  103. package/dist/form/duration.d.ts +22 -0
  104. package/dist/form/duration.js +49 -0
  105. package/dist/form/edit-create-form.d.ts +12 -5
  106. package/dist/form/edit-create-form.js +243 -188
  107. package/dist/form/event-value.d.ts +6 -0
  108. package/dist/form/event-value.js +17 -0
  109. package/dist/form/field-error.d.ts +14 -0
  110. package/dist/form/field-error.js +28 -0
  111. package/dist/form/field-name.d.ts +20 -0
  112. package/dist/form/field-name.js +6 -0
  113. package/dist/form/filter-form-values.js +63 -49
  114. package/dist/form/form-controller.d.ts +9 -1
  115. package/dist/form/form-controller.js +165 -160
  116. package/dist/form/form-groups.d.ts +4 -3
  117. package/dist/form/form-groups.js +79 -79
  118. package/dist/form/form-handle.d.ts +175 -0
  119. package/dist/form/form-handle.js +68 -0
  120. package/dist/form/input-binding.d.ts +56 -31
  121. package/dist/form/input-binding.js +169 -118
  122. package/dist/form/input-values.d.ts +130 -0
  123. package/dist/form/input-values.js +177 -0
  124. package/dist/form/json-text.js +51 -37
  125. package/dist/form/save-pipeline.d.ts +6 -8
  126. package/dist/form/save-pipeline.js +91 -82
  127. package/dist/form/set-form-values.d.ts +6 -0
  128. package/dist/form/set-form-values.js +10 -0
  129. package/dist/form/source.js +97 -65
  130. package/dist/form/text-of.d.ts +2 -0
  131. package/dist/form/text-of.js +7 -0
  132. package/dist/form/validators.d.ts +58 -19
  133. package/dist/form/validators.js +97 -71
  134. package/dist/geo/bbox.d.ts +5 -4
  135. package/dist/geo/bbox.js +69 -48
  136. package/dist/geo/bounds.d.ts +14 -3
  137. package/dist/geo/bounds.js +98 -68
  138. package/dist/geo/constraints.js +88 -93
  139. package/dist/geo/coordinates.js +31 -23
  140. package/dist/geo/editor.d.ts +13 -13
  141. package/dist/geo/editor.js +176 -153
  142. package/dist/geo/geocoding.d.ts +4 -4
  143. package/dist/geo/geocoding.js +38 -41
  144. package/dist/geo/geojson.d.ts +12 -74
  145. package/dist/geo/geojson.js +52 -40
  146. package/dist/geo/geometry-math.d.ts +4 -4
  147. package/dist/geo/geometry-math.js +100 -93
  148. package/dist/geo/index.d.ts +7 -6
  149. package/dist/geo/map-layers.d.ts +42 -7
  150. package/dist/geo/map-layers.js +88 -62
  151. package/dist/geo/map-style.js +31 -32
  152. package/dist/geo/messages.js +48 -45
  153. package/dist/geo/nominatim.js +88 -80
  154. package/dist/geo/osm-presets.d.ts +4 -3
  155. package/dist/geo/osm-presets.js +108 -86
  156. package/dist/geo/osm-tags.js +14 -12
  157. package/dist/geo/overpass.d.ts +14 -2
  158. package/dist/geo/overpass.js +83 -90
  159. package/dist/geo/shape-value.d.ts +9 -5
  160. package/dist/geo/shape-value.js +169 -146
  161. package/dist/geo/snap-to-roads.d.ts +3 -3
  162. package/dist/geo/snap-to-roads.js +55 -50
  163. package/dist/geo/throttle.js +36 -39
  164. package/dist/guards.d.ts +37 -0
  165. package/dist/guards.js +62 -0
  166. package/dist/i18n/default-messages.js +34 -29
  167. package/dist/i18n/english-messages.js +187 -189
  168. package/dist/i18n/i18n.d.ts +2 -2
  169. package/dist/i18n/i18n.js +47 -60
  170. package/dist/i18n/inflection.d.ts +34 -0
  171. package/dist/i18n/inflection.js +82 -0
  172. package/dist/i18n/messages.d.ts +10 -2
  173. package/dist/i18n/messages.js +17 -4
  174. package/dist/i18n/missing-keys.js +66 -62
  175. package/dist/i18n/translatable.d.ts +16 -1
  176. package/dist/i18n/translatable.js +40 -47
  177. package/dist/index.d.ts +71 -24
  178. package/dist/index.js +40 -288
  179. package/dist/json/deep-equal.d.ts +8 -0
  180. package/dist/json/deep-equal.js +24 -0
  181. package/dist/json/strip-json-comments.js +38 -42
  182. package/dist/noop.d.ts +2 -0
  183. package/dist/noop.js +4 -0
  184. package/dist/notification/error-message.d.ts +2 -0
  185. package/dist/notification/error-message.js +10 -0
  186. package/dist/notification/notification-store.d.ts +3 -2
  187. package/dist/notification/notification-store.js +41 -45
  188. package/dist/onboarding-tour/placement.js +63 -78
  189. package/dist/onboarding-tour/tour-controller.js +46 -54
  190. package/dist/realtime/add-events-for-mutations.js +75 -81
  191. package/dist/realtime/announce-lock.js +16 -13
  192. package/dist/realtime/broadcast-channel-transport.js +65 -59
  193. package/dist/realtime/copy-members.js +22 -14
  194. package/dist/realtime/in-memory-lock-provider.js +40 -48
  195. package/dist/realtime/live.js +95 -101
  196. package/dist/realtime/lock-options.js +16 -16
  197. package/dist/realtime/lock-session.js +91 -78
  198. package/dist/realtime/memory-transport.js +25 -25
  199. package/dist/realtime/presence.js +120 -122
  200. package/dist/realtime/realtime-data-provider.d.ts +10 -0
  201. package/dist/realtime/realtime-data-provider.js +45 -42
  202. package/dist/realtime/reconnect-backoff.js +17 -0
  203. package/dist/realtime/sse-transport.d.ts +31 -0
  204. package/dist/realtime/sse-transport.js +235 -0
  205. package/dist/realtime/status-store.js +23 -28
  206. package/dist/realtime/topic-hub.js +62 -71
  207. package/dist/realtime/topics.js +10 -12
  208. package/dist/realtime/types.js +10 -12
  209. package/dist/realtime/unread-count.js +21 -21
  210. package/dist/realtime/websocket-transport.d.ts +32 -0
  211. package/dist/realtime/websocket-transport.js +306 -0
  212. package/dist/resource/describe-resource.d.ts +2 -0
  213. package/dist/resource/describe-resource.js +232 -219
  214. package/dist/resource/get-at-path.js +29 -14
  215. package/dist/resource/identifier.d.ts +18 -0
  216. package/dist/resource/identifier.js +27 -0
  217. package/dist/resource/record.d.ts +4 -0
  218. package/dist/resource/record.js +10 -0
  219. package/dist/resource/register.d.ts +7 -0
  220. package/dist/resource/registry-store.d.ts +45 -15
  221. package/dist/resource/registry-store.js +67 -60
  222. package/dist/resource/registry.d.ts +10 -0
  223. package/dist/resource/registry.js +8 -4
  224. package/dist/resource/source-path.d.ts +16 -1
  225. package/dist/rich-text/rich-text-extras.d.ts +132 -0
  226. package/dist/rich-text/rich-text-extras.js +179 -0
  227. package/dist/rich-text/rich-text-image.d.ts +124 -0
  228. package/dist/rich-text/rich-text-image.js +163 -0
  229. package/dist/rich-text/rich-text.d.ts +6 -0
  230. package/dist/rich-text/rich-text.js +125 -88
  231. package/dist/router/location.d.ts +22 -4
  232. package/dist/router/location.js +41 -18
  233. package/dist/router/match.js +47 -39
  234. package/dist/router/paths.js +40 -34
  235. package/dist/router/record-from-location.d.ts +1 -1
  236. package/dist/router/record-from-location.js +25 -17
  237. package/dist/store/adapters.js +51 -55
  238. package/dist/store/micro-store.js +57 -59
  239. package/dist/store/shallow-equal.d.ts +12 -0
  240. package/dist/store/shallow-equal.js +20 -0
  241. package/dist/store/store.js +54 -57
  242. package/package.json +13 -11
@@ -1,45 +1,53 @@
1
+ // src/router/match.ts
2
+ /** Compile a pattern into a case-insensitive regex + ordered param names. */
1
3
  function compilePattern(pattern) {
2
- const paramNames = [];
3
- let regexpSource = `^${pattern.replace(/[\\.*+^${}|()[\]]/g, "\\$&").replace(/\\\*/g, "*").replace(/^\/*/, "/").replace(/\/+$/, "").replace(/\/:(\w+)/g, (_m, name) => {
4
- paramNames.push(name);
5
- return "/([^\\/]+)";
6
- })}`;
7
- if (pattern.endsWith("*")) {
8
- paramNames.push("*");
9
- regexpSource += pattern === "*" || pattern === "/*" ? "(.*)$" : "(?:\\/(.+)|\\/*)$";
10
- } else {
11
- regexpSource += "\\/*$";
12
- }
13
- return { matcher: new RegExp(regexpSource, "i"), paramNames };
4
+ const paramNames = [];
5
+ let regexpSource = `^${pattern.replace(/[\\.*+^${}|()[\]]/g, "\\$&").replace(/\\\*/g, "*").replace(/^\/*/, "/").replace(/\/+$/, "").replace(/\/:(\w+)/g, (_m, name) => {
6
+ paramNames.push(name);
7
+ return "/([^\\/]+)";
8
+ })}`;
9
+ if (pattern.endsWith("*")) {
10
+ paramNames.push("*");
11
+ regexpSource += pattern === "*" || pattern === "/*" ? "(.*)$" : "(?:\\/(.+)|\\/*)$";
12
+ } else regexpSource += "\\/*$";
13
+ return {
14
+ matcher: new RegExp(regexpSource, "i"),
15
+ paramNames
16
+ };
14
17
  }
18
+ /** Safe URI-decode: fall back to the raw value on a malformed sequence. */
15
19
  function safeDecode(value) {
16
- try {
17
- return decodeURIComponent(value);
18
- } catch {
19
- return value;
20
- }
20
+ try {
21
+ return decodeURIComponent(value);
22
+ } catch {
23
+ return value;
24
+ }
21
25
  }
26
+ /**
27
+ * Match `pathname` against `pattern`. Returns the captured params, the matched
28
+ * `pathname`, and `pathnameBase` (the match minus any splat remainder), or
29
+ * `null` when the pattern does not match.
30
+ */
22
31
  function matchPath(pattern, pathname) {
23
- const { matcher, paramNames } = compilePattern(pattern);
24
- const match = pathname.match(matcher);
25
- if (!match) return null;
26
- const matchedPathname = match[0];
27
- const hasSplat = paramNames[paramNames.length - 1] === "*";
28
- let pathnameBase = matchedPathname;
29
- const params = {};
30
- paramNames.forEach((name, index) => {
31
- if (name === "*") {
32
- const splat = match[index + 1] ?? "";
33
- pathnameBase = matchedPathname.slice(0, matchedPathname.length - splat.length).replace(/(.)\/+$/, "$1");
34
- params["*"] = splat;
35
- } else {
36
- params[name] = safeDecode(match[index + 1] ?? "");
37
- }
38
- });
39
- if (hasSplat && pathnameBase === "") pathnameBase = "/";
40
- const normalizedPathname = matchedPathname.length > 1 ? matchedPathname.replace(/\/+$/, "") : matchedPathname;
41
- return { params, pathname: normalizedPathname || "/", pathnameBase };
32
+ const { matcher, paramNames } = compilePattern(pattern);
33
+ const match = pathname.match(matcher);
34
+ if (!match) return null;
35
+ const matchedPathname = match[0];
36
+ const hasSplat = paramNames[paramNames.length - 1] === "*";
37
+ let pathnameBase = matchedPathname;
38
+ const params = {};
39
+ paramNames.forEach((name, index) => {
40
+ if (name === "*") {
41
+ const splat = match[index + 1] ?? "";
42
+ pathnameBase = matchedPathname.slice(0, matchedPathname.length - splat.length).replace(/(.)\/+$/, "$1");
43
+ params["*"] = splat;
44
+ } else params[name] = safeDecode(match[index + 1] ?? "");
45
+ });
46
+ if (hasSplat && pathnameBase === "") pathnameBase = "/";
47
+ return {
48
+ params,
49
+ pathname: (matchedPathname.length > 1 ? matchedPathname.replace(/\/+$/, "") : matchedPathname) || "/",
50
+ pathnameBase
51
+ };
42
52
  }
43
- export {
44
- matchPath
45
- };
53
+ export { matchPath };
@@ -1,40 +1,46 @@
1
+ // src/router/paths.ts
2
+ /**
3
+ * Collapse runs of `/` into a single `/`, WITHOUT touching a `scheme://`
4
+ * authority separator. Matches ra-core's `removeDoubleSlashes` for internal
5
+ * paths while staying safe on the rare absolute URL.
6
+ */
1
7
  function removeDoubleSlashes(path) {
2
- const schemeMatch = path.match(/^[a-zA-Z][a-zA-Z\d+\-.]*:\/\//);
3
- if (schemeMatch) {
4
- const prefix = schemeMatch[0];
5
- return prefix + path.slice(prefix.length).replace(/\/\/+/g, "/");
6
- }
7
- return path.replace(/\/\/+/g, "/");
8
+ const schemeMatch = path.match(/^[a-zA-Z][a-zA-Z\d+\-.]*:\/\//);
9
+ if (schemeMatch) {
10
+ const prefix = schemeMatch[0];
11
+ return prefix + path.slice(prefix.length).replace(/\/\/+/g, "/");
12
+ }
13
+ return path.replace(/\/\/+/g, "/");
8
14
  }
15
+ /**
16
+ * Prefix `path` with `basename`, collapsing the seam and any trailing basename
17
+ * slash. An empty basename is a no-op. The result is always slash-normalized.
18
+ */
9
19
  function joinBasename(path, basename = "") {
10
- const joined = basename ? `${basename}/${path}` : path;
11
- const collapsed = removeDoubleSlashes(joined);
12
- return collapsed.length > 1 ? collapsed.replace(/\/$/, "") : collapsed;
20
+ const collapsed = removeDoubleSlashes(basename ? `${basename}/${path}` : path);
21
+ return collapsed.length > 1 ? collapsed.replace(/\/$/, "") : collapsed;
13
22
  }
23
+ /**
24
+ * Build a route path for a link intent (parity §0a-11):
25
+ * `list`→`/res`, `create`→`/res/create`, `edit`→`/res/:id`, `show`→`/res/:id/show`.
26
+ * `id==null` on `edit`/`show` falls back to the list path. The id is
27
+ * `encodeURIComponent`d, and double slashes are removed from these four known
28
+ * intents. A custom string `type` is returned COMPLETELY verbatim — NO basename
29
+ * prefix, NO slash collapse — matching ra-core's `useCreatePath` `default`
30
+ * branch (the author owns the full, final path).
31
+ */
14
32
  function createPath(params, basename = "") {
15
- const { type, resource, id } = params;
16
- switch (type) {
17
- case "list":
18
- return joinBasename(`/${resource}`, basename);
19
- case "create":
20
- return joinBasename(`/${resource}/create`, basename);
21
- case "edit": {
22
- if (id == null) return joinBasename(`/${resource}`, basename);
23
- return joinBasename(`/${resource}/${encodeURIComponent(id)}`, basename);
24
- }
25
- case "show": {
26
- if (id == null) return joinBasename(`/${resource}`, basename);
27
- return joinBasename(
28
- `/${resource}/${encodeURIComponent(id)}/show`,
29
- basename
30
- );
31
- }
32
- default:
33
- return type;
34
- }
33
+ const { type, resource, id } = params;
34
+ switch (type) {
35
+ case "list": return joinBasename(`/${resource}`, basename);
36
+ case "create": return joinBasename(`/${resource}/create`, basename);
37
+ case "edit":
38
+ if (id == null) return joinBasename(`/${resource}`, basename);
39
+ return joinBasename(`/${resource}/${encodeURIComponent(id)}`, basename);
40
+ case "show":
41
+ if (id == null) return joinBasename(`/${resource}`, basename);
42
+ return joinBasename(`/${resource}/${encodeURIComponent(id)}/show`, basename);
43
+ default: return type;
44
+ }
35
45
  }
36
- export {
37
- createPath,
38
- joinBasename,
39
- removeDoubleSlashes
40
- };
46
+ export { createPath, joinBasename, removeDoubleSlashes };
@@ -13,6 +13,6 @@ export interface RecordFromLocationOptions {
13
13
  /**
14
14
  * The record at `state[stateSource]` if it is an object, else the JSON in the
15
15
  * `searchSource` param. Returns `null` for no param, invalid JSON, a repeated
16
- * param or a value that is not a plain object. Never throws.
16
+ * param or a value that is not an object. Never throws.
17
17
  */
18
18
  export declare function getRecordFromLocation(location: Pick<CheloneLocation, "state" | "searchParams">, options?: RecordFromLocationOptions): Record<string, unknown> | null;
@@ -1,19 +1,27 @@
1
- function isPlainObject(value) {
2
- return typeof value === "object" && value !== null && !Array.isArray(value);
3
- }
1
+ import { isRecord } from "../guards.js";
2
+ // src/router/record-from-location.ts
3
+ /**
4
+ * Read a seed record out of a location: the framework-free twin of ra-core's
5
+ * `useRecordFromLocation`. A router adapter passes its `CheloneLocation`;
6
+ * the clone link writes `?source=<json>`.
7
+ */
8
+ /**
9
+ * The record at `state[stateSource]` if it is an object, else the JSON in the
10
+ * `searchSource` param. Returns `null` for no param, invalid JSON, a repeated
11
+ * param or a value that is not an object. Never throws.
12
+ */
4
13
  function getRecordFromLocation(location, options = {}) {
5
- const { searchSource = "source", stateSource = "record" } = options;
6
- const fromState = isPlainObject(location.state) ? location.state[stateSource] : void 0;
7
- if (isPlainObject(fromState)) return fromState;
8
- const values = location.searchParams.getAll(searchSource);
9
- if (values.length !== 1) return null;
10
- try {
11
- const parsed = JSON.parse(values[0]);
12
- return isPlainObject(parsed) ? parsed : null;
13
- } catch {
14
- return null;
15
- }
14
+ const { searchSource = "source", stateSource = "record" } = options;
15
+ const fromState = isRecord(location.state) ? location.state[stateSource] : void 0;
16
+ if (isRecord(fromState)) return fromState;
17
+ const values = location.searchParams.getAll(searchSource);
18
+ const [value] = values;
19
+ if (values.length !== 1 || value === void 0) return null;
20
+ try {
21
+ const parsed = JSON.parse(value);
22
+ return isRecord(parsed) ? parsed : null;
23
+ } catch {
24
+ return null;
25
+ }
16
26
  }
17
- export {
18
- getRecordFromLocation
19
- };
27
+ export { getRecordFromLocation };
@@ -1,61 +1,57 @@
1
+ // src/store/adapters.ts
1
2
  function memoryStoreAdapter(initial = {}) {
2
- const map = new Map(Object.entries(initial));
3
- return {
4
- getItem: (key) => map.get(key),
5
- setItem: (key, value) => void map.set(key, value),
6
- removeItem: (key) => void map.delete(key)
7
- };
3
+ const map = new Map(Object.entries(initial));
4
+ return {
5
+ getItem: (key) => map.get(key),
6
+ setItem: (key, value) => void map.set(key, value),
7
+ removeItem: (key) => void map.delete(key)
8
+ };
8
9
  }
10
+ /** window/localStorage touched ONLY inside method bodies — module import is SSR-safe. */
9
11
  function storage() {
10
- if (typeof window === "undefined") return void 0;
11
- try {
12
- return window.localStorage;
13
- } catch {
14
- return void 0;
15
- }
12
+ if (typeof window === "undefined") return void 0;
13
+ try {
14
+ return window.localStorage;
15
+ } catch {
16
+ return;
17
+ }
16
18
  }
17
19
  function localStorageAdapter(options = {}) {
18
- const prefix = options.prefix ?? "chelone.";
19
- return {
20
- getItem(key) {
21
- const raw = storage()?.getItem(prefix + key);
22
- if (raw == null) return void 0;
23
- try {
24
- return JSON.parse(raw);
25
- } catch {
26
- return void 0;
27
- }
28
- },
29
- setItem(key, value) {
30
- try {
31
- storage()?.setItem(prefix + key, JSON.stringify(value));
32
- } catch {
33
- }
34
- },
35
- removeItem(key) {
36
- storage()?.removeItem(prefix + key);
37
- },
38
- subscribeExternal(onChange) {
39
- if (typeof window === "undefined") return () => {
40
- };
41
- const listener = (event) => {
42
- if (!event.key?.startsWith(prefix)) return;
43
- const key = event.key.slice(prefix.length);
44
- if (event.newValue == null) {
45
- onChange(key, void 0);
46
- return;
47
- }
48
- try {
49
- onChange(key, JSON.parse(event.newValue));
50
- } catch {
51
- }
52
- };
53
- window.addEventListener("storage", listener);
54
- return () => window.removeEventListener("storage", listener);
55
- }
56
- };
20
+ const prefix = options.prefix ?? "chelone.";
21
+ return {
22
+ getItem(key) {
23
+ const raw = storage()?.getItem(prefix + key);
24
+ if (raw == null) return void 0;
25
+ try {
26
+ return JSON.parse(raw);
27
+ } catch {
28
+ return;
29
+ }
30
+ },
31
+ setItem(key, value) {
32
+ try {
33
+ storage()?.setItem(prefix + key, JSON.stringify(value));
34
+ } catch {}
35
+ },
36
+ removeItem(key) {
37
+ storage()?.removeItem(prefix + key);
38
+ },
39
+ subscribeExternal(onChange) {
40
+ if (typeof window === "undefined") return () => {};
41
+ const listener = (event) => {
42
+ if (!event.key?.startsWith(prefix)) return;
43
+ const key = event.key.slice(prefix.length);
44
+ if (event.newValue == null) {
45
+ onChange(key, void 0);
46
+ return;
47
+ }
48
+ try {
49
+ onChange(key, JSON.parse(event.newValue));
50
+ } catch {}
51
+ };
52
+ window.addEventListener("storage", listener);
53
+ return () => window.removeEventListener("storage", listener);
54
+ }
55
+ };
57
56
  }
58
- export {
59
- localStorageAdapter,
60
- memoryStoreAdapter
61
- };
57
+ export { localStorageAdapter, memoryStoreAdapter };
@@ -1,61 +1,59 @@
1
+ // src/store/micro-store.ts
1
2
  function createMicroStore(initial, options) {
2
- let state = initial;
3
- let destroyed = false;
4
- let attachTeardown;
5
- const subscriptions = /* @__PURE__ */ new Set();
6
- const cleanups = /* @__PURE__ */ new Set();
7
- const detach = () => {
8
- const teardown = attachTeardown;
9
- attachTeardown = void 0;
10
- teardown?.();
11
- };
12
- return {
13
- getSnapshot: () => state,
14
- setState(update) {
15
- if (destroyed) return;
16
- const patch = typeof update === "function" ? update(state) : update;
17
- let changed = false;
18
- for (const [key, value] of Object.entries(patch)) {
19
- if (!Object.is(state[key], value)) {
20
- changed = true;
21
- break;
22
- }
23
- }
24
- if (!changed) return;
25
- state = { ...state, ...patch };
26
- for (const subscription of subscriptions) subscription.callback();
27
- },
28
- subscribe(callback) {
29
- if (destroyed) return () => {
30
- };
31
- const subscription = { callback };
32
- const isFirst = subscriptions.size === 0;
33
- subscriptions.add(subscription);
34
- if (isFirst && options?.onSubscribed) {
35
- attachTeardown = options.onSubscribed() ?? void 0;
36
- }
37
- return () => {
38
- if (!subscriptions.delete(subscription)) return;
39
- if (subscriptions.size === 0 && !destroyed) detach();
40
- };
41
- },
42
- destroy() {
43
- if (destroyed) return;
44
- destroyed = true;
45
- detach();
46
- for (const cleanup of cleanups) cleanup();
47
- cleanups.clear();
48
- subscriptions.clear();
49
- },
50
- onDestroy(cleanup) {
51
- if (destroyed) {
52
- cleanup();
53
- return;
54
- }
55
- cleanups.add(cleanup);
56
- }
57
- };
3
+ let state = initial;
4
+ let destroyed = false;
5
+ let attachTeardown;
6
+ const subscriptions = /* @__PURE__ */ new Set();
7
+ const cleanups = /* @__PURE__ */ new Set();
8
+ /** Runs the attach teardown at most once per attach cycle. */
9
+ const detach = () => {
10
+ const teardown = attachTeardown;
11
+ attachTeardown = void 0;
12
+ teardown?.();
13
+ };
14
+ return {
15
+ getSnapshot: () => state,
16
+ setState(update) {
17
+ if (destroyed) return;
18
+ const patch = typeof update === "function" ? update(state) : update;
19
+ let changed = false;
20
+ for (const key in patch) if (!Object.is(state[key], patch[key])) {
21
+ changed = true;
22
+ break;
23
+ }
24
+ if (!changed) return;
25
+ state = {
26
+ ...state,
27
+ ...patch
28
+ };
29
+ for (const subscription of subscriptions) subscription.callback();
30
+ },
31
+ subscribe(callback) {
32
+ if (destroyed) return () => {};
33
+ const subscription = { callback };
34
+ const isFirst = subscriptions.size === 0;
35
+ subscriptions.add(subscription);
36
+ if (isFirst && options?.onSubscribed) attachTeardown = options.onSubscribed() ?? void 0;
37
+ return () => {
38
+ if (!subscriptions.delete(subscription)) return;
39
+ if (subscriptions.size === 0 && !destroyed) detach();
40
+ };
41
+ },
42
+ destroy() {
43
+ if (destroyed) return;
44
+ destroyed = true;
45
+ detach();
46
+ for (const cleanup of cleanups) cleanup();
47
+ cleanups.clear();
48
+ subscriptions.clear();
49
+ },
50
+ onDestroy(cleanup) {
51
+ if (destroyed) {
52
+ cleanup();
53
+ return;
54
+ }
55
+ cleanups.add(cleanup);
56
+ }
57
+ };
58
58
  }
59
- export {
60
- createMicroStore
61
- };
59
+ export { createMicroStore };
@@ -0,0 +1,12 @@
1
+ /**
2
+ * `shallowEqual(a, b)`: shallow equality with `Object.is` semantics at the top
3
+ * level. Parity with ra-core's `shallowEqual`.
4
+ *
5
+ * - `Object.is`-equal inputs are equal, so `NaN` equals `NaN` and `0` differs
6
+ * from `-0`;
7
+ * - two objects or arrays are equal iff they have the same own enumerable keys
8
+ * and each value pair is `Object.is`-equal; nested values compare by
9
+ * reference;
10
+ * - a non-object, or `null`, that is not `Object.is`-equal is unequal.
11
+ */
12
+ export declare function shallowEqual(a: unknown, b: unknown): boolean;
@@ -0,0 +1,20 @@
1
+ // src/store/shallow-equal.ts
2
+ /**
3
+ * `shallowEqual(a, b)`: shallow equality with `Object.is` semantics at the top
4
+ * level. Parity with ra-core's `shallowEqual`.
5
+ *
6
+ * - `Object.is`-equal inputs are equal, so `NaN` equals `NaN` and `0` differs
7
+ * from `-0`;
8
+ * - two objects or arrays are equal iff they have the same own enumerable keys
9
+ * and each value pair is `Object.is`-equal; nested values compare by
10
+ * reference;
11
+ * - a non-object, or `null`, that is not `Object.is`-equal is unequal.
12
+ */
13
+ function shallowEqual(a, b) {
14
+ if (Object.is(a, b)) return true;
15
+ if (typeof a !== "object" || a === null || typeof b !== "object" || b === null) return false;
16
+ const keysA = Object.keys(a);
17
+ if (keysA.length !== Object.keys(b).length) return false;
18
+ return keysA.every((key) => Object.hasOwn(b, key) && Object.is(Reflect.get(a, key), Reflect.get(b, key)));
19
+ }
20
+ export { shallowEqual };
@@ -1,59 +1,56 @@
1
+ // src/store/store.ts
1
2
  function createStore(adapter) {
2
- const cache = /* @__PURE__ */ new Map();
3
- const hydrated = /* @__PURE__ */ new Set();
4
- const subscribers = /* @__PURE__ */ new Map();
5
- const notify = (key) => {
6
- const set = subscribers.get(key);
7
- if (set) for (const cb of set) cb();
8
- };
9
- const apply = (key, value) => {
10
- hydrated.add(key);
11
- if (value === void 0) cache.delete(key);
12
- else cache.set(key, value);
13
- notify(key);
14
- };
15
- adapter?.subscribeExternal?.((key, value) => apply(key, value));
16
- return {
17
- getItem(key) {
18
- if (!hydrated.has(key) && adapter) {
19
- hydrated.add(key);
20
- const persisted = adapter.getItem(key);
21
- if (persisted !== void 0) cache.set(key, persisted);
22
- }
23
- return cache.get(key);
24
- },
25
- setItem(key, value) {
26
- hydrated.add(key);
27
- if (Object.is(cache.get(key), value)) return;
28
- cache.set(key, value);
29
- adapter?.setItem(key, value);
30
- notify(key);
31
- },
32
- removeItem(key) {
33
- hydrated.add(key);
34
- cache.delete(key);
35
- adapter?.removeItem(key);
36
- notify(key);
37
- },
38
- subscribe(key, callback) {
39
- let set = subscribers.get(key);
40
- if (!set) {
41
- set = /* @__PURE__ */ new Set();
42
- subscribers.set(key, set);
43
- }
44
- set.add(callback);
45
- return () => {
46
- set.delete(callback);
47
- if (set.size === 0 && subscribers.get(key) === set) {
48
- subscribers.delete(key);
49
- }
50
- };
51
- },
52
- keys() {
53
- return [...cache.keys()];
54
- }
55
- };
3
+ const cache = /* @__PURE__ */ new Map();
4
+ const hydrated = /* @__PURE__ */ new Set();
5
+ const subscribers = /* @__PURE__ */ new Map();
6
+ const notify = (key) => {
7
+ const set = subscribers.get(key);
8
+ if (set) for (const cb of set) cb();
9
+ };
10
+ const apply = (key, value) => {
11
+ hydrated.add(key);
12
+ if (value === void 0) cache.delete(key);
13
+ else cache.set(key, value);
14
+ notify(key);
15
+ };
16
+ adapter?.subscribeExternal?.((key, value) => apply(key, value));
17
+ return {
18
+ getItem(key) {
19
+ if (!hydrated.has(key) && adapter) {
20
+ hydrated.add(key);
21
+ const persisted = adapter.getItem(key);
22
+ if (persisted !== void 0) cache.set(key, persisted);
23
+ }
24
+ return cache.get(key);
25
+ },
26
+ setItem(key, value) {
27
+ hydrated.add(key);
28
+ if (Object.is(cache.get(key), value)) return;
29
+ cache.set(key, value);
30
+ adapter?.setItem(key, value);
31
+ notify(key);
32
+ },
33
+ removeItem(key) {
34
+ hydrated.add(key);
35
+ cache.delete(key);
36
+ adapter?.removeItem(key);
37
+ notify(key);
38
+ },
39
+ subscribe(key, callback) {
40
+ let set = subscribers.get(key);
41
+ if (!set) {
42
+ set = /* @__PURE__ */ new Set();
43
+ subscribers.set(key, set);
44
+ }
45
+ set.add(callback);
46
+ return () => {
47
+ set.delete(callback);
48
+ if (set.size === 0 && subscribers.get(key) === set) subscribers.delete(key);
49
+ };
50
+ },
51
+ keys() {
52
+ return [...cache.keys()];
53
+ }
54
+ };
56
55
  }
57
- export {
58
- createStore
59
- };
56
+ export { createStore };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chelone/core",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -30,21 +30,23 @@
30
30
  "zod": "^3.25.0 || ^4.0.0"
31
31
  },
32
32
  "devDependencies": {
33
- "@arethetypeswrong/cli": "^0.18.0",
34
- "@tanstack/form-core": "^1.33.0",
35
- "@tanstack/query-core": "^5.0.0",
36
- "@types/node": "^22.20.0",
33
+ "@arethetypeswrong/cli": "^0.18.5",
34
+ "@chelone/test-utils": "0.0.0",
35
+ "@tanstack/form-core": "^1.33.5",
36
+ "@tanstack/query-core": "^5.106.1",
37
+ "@types/node": "^26.6.6",
37
38
  "@types/papaparse": "^5.5.2",
38
- "jsdom": "^26.1.0",
39
+ "jsdom": "^31.0.0",
39
40
  "papaparse": "^5.7.0",
40
- "publint": "^0.3.0",
41
+ "publint": "^0.3.25",
41
42
  "terra-draw": "^1.39.0",
42
- "vite": "^7.0.0",
43
- "vitest": "^3.0.0",
44
- "zod": "^3.25.0"
43
+ "vite": "^8.3.6",
44
+ "vitest": "^5.1.0",
45
+ "zod": "^4.6.7"
45
46
  },
46
47
  "dependencies": {
47
- "dot.paths": "^0.2.1"
48
+ "@types/geojson": "^7946.0.16",
49
+ "dot.paths": "^0.2.2"
48
50
  },
49
51
  "scripts": {
50
52
  "build": "vite build && tsc -p tsconfig.build.json && node ../../scripts/dts-extensions.mjs dist",