@chelone/core 0.6.0 → 0.9.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 (255) 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 +23 -17
  10. package/dist/auth/auth-options.js +82 -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 +64 -0
  15. package/dist/block-editor/block-document.js +147 -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.d.ts +4 -0
  21. package/dist/command-menu/recents.js +12 -11
  22. package/dist/command-menu/registry.d.ts +9 -5
  23. package/dist/command-menu/registry.js +36 -46
  24. package/dist/controller/calendar-dates.d.ts +12 -0
  25. package/dist/controller/calendar-dates.js +94 -113
  26. package/dist/controller/calendar-events.js +93 -88
  27. package/dist/controller/client-list-controller.d.ts +2 -2
  28. package/dist/controller/client-list-controller.js +251 -254
  29. package/dist/controller/data-table-columns.d.ts +2 -0
  30. package/dist/controller/data-table-columns.js +50 -56
  31. package/dist/controller/deep-equal.d.ts +1 -0
  32. package/dist/controller/deep-equal.js +14 -0
  33. package/dist/controller/filter-toggle.js +36 -44
  34. package/dist/controller/in-place-editor.d.ts +43 -0
  35. package/dist/controller/in-place-editor.js +43 -0
  36. package/dist/controller/infinite-list-controller.js +219 -189
  37. package/dist/controller/inspector.d.ts +55 -0
  38. package/dist/controller/inspector.js +70 -0
  39. package/dist/controller/job-monitor.d.ts +10 -4
  40. package/dist/controller/job-monitor.js +46 -47
  41. package/dist/controller/kanban-board.js +45 -24
  42. package/dist/controller/list-controller.d.ts +8 -5
  43. package/dist/controller/list-controller.js +447 -400
  44. package/dist/controller/list-params.js +96 -87
  45. package/dist/controller/pivot-grid.d.ts +4 -4
  46. package/dist/controller/pivot-grid.js +89 -95
  47. package/dist/controller/prev-next.d.ts +2 -2
  48. package/dist/controller/prev-next.js +74 -54
  49. package/dist/controller/record-controllers.d.ts +14 -6
  50. package/dist/controller/record-controllers.js +288 -257
  51. package/dist/controller/record-diff.d.ts +21 -0
  52. package/dist/controller/record-diff.js +50 -0
  53. package/dist/controller/record-timeline.d.ts +5 -5
  54. package/dist/controller/record-timeline.js +42 -30
  55. package/dist/controller/record-tree.js +57 -47
  56. package/dist/controller/record-workflow.d.ts +38 -16
  57. package/dist/controller/record-workflow.js +85 -64
  58. package/dist/controller/reference-many-controller.d.ts +2 -0
  59. package/dist/controller/reference-many-controller.js +199 -192
  60. package/dist/controller/reference-one-controller.d.ts +1 -1
  61. package/dist/controller/reference-one-controller.js +63 -56
  62. package/dist/controller/saved-queries.js +104 -91
  63. package/dist/controller/translate-fallback.js +10 -6
  64. package/dist/csv/export.d.ts +4 -3
  65. package/dist/csv/export.js +43 -40
  66. package/dist/csv/import-controller.d.ts +4 -0
  67. package/dist/csv/import-controller.js +189 -169
  68. package/dist/csv/match-headers.js +29 -22
  69. package/dist/csv/parse-with.js +60 -54
  70. package/dist/csv/run-import.js +69 -72
  71. package/dist/csv/validate-rows.d.ts +1 -1
  72. package/dist/csv/validate-rows.js +65 -52
  73. package/dist/data/cache-ops.d.ts +5 -4
  74. package/dist/data/cache-ops.js +208 -188
  75. package/dist/data/combine-data-providers.js +25 -23
  76. package/dist/data/data-provider-log.d.ts +45 -0
  77. package/dist/data/data-provider-log.js +93 -0
  78. package/dist/data/errors.js +14 -10
  79. package/dist/data/fake-provider.d.ts +6 -3
  80. package/dist/data/fake-provider.js +135 -114
  81. package/dist/data/fetch-utils.js +61 -44
  82. package/dist/data/filter-branch.d.ts +5 -0
  83. package/dist/data/filter-branch.js +10 -0
  84. package/dist/data/get-many-aggregate.js +84 -76
  85. package/dist/data/http-error.js +17 -12
  86. package/dist/data/keys.js +50 -36
  87. package/dist/data/mutation-options.d.ts +14 -9
  88. package/dist/data/mutation-options.js +93 -109
  89. package/dist/data/query-options.js +73 -68
  90. package/dist/data/remove-empty.d.ts +3 -0
  91. package/dist/data/remove-empty.js +11 -0
  92. package/dist/data/undoable.d.ts +2 -2
  93. package/dist/data/undoable.js +190 -174
  94. package/dist/data/with-lifecycle-callbacks.d.ts +45 -17
  95. package/dist/data/with-lifecycle-callbacks.js +66 -52
  96. package/dist/environment/application-update-store.js +58 -55
  97. package/dist/environment/offline-store.js +21 -21
  98. package/dist/form/array-binding.d.ts +30 -11
  99. package/dist/form/array-binding.js +184 -166
  100. package/dist/form/choice-ids.d.ts +7 -0
  101. package/dist/form/choice-ids.js +11 -0
  102. package/dist/form/choices-controller.d.ts +22 -2
  103. package/dist/form/choices-controller.js +315 -275
  104. package/dist/form/date-format.d.ts +38 -0
  105. package/dist/form/date-format.js +89 -0
  106. package/dist/form/dirty-values.d.ts +2 -2
  107. package/dist/form/dirty-values.js +22 -11
  108. package/dist/form/duration.d.ts +22 -0
  109. package/dist/form/duration.js +49 -0
  110. package/dist/form/edit-create-form.d.ts +12 -5
  111. package/dist/form/edit-create-form.js +243 -188
  112. package/dist/form/event-value.d.ts +6 -0
  113. package/dist/form/event-value.js +17 -0
  114. package/dist/form/field-error.d.ts +14 -0
  115. package/dist/form/field-error.js +28 -0
  116. package/dist/form/field-name.d.ts +20 -0
  117. package/dist/form/field-name.js +6 -0
  118. package/dist/form/filter-form-values.js +63 -49
  119. package/dist/form/form-controller.d.ts +9 -1
  120. package/dist/form/form-controller.js +165 -160
  121. package/dist/form/form-groups.d.ts +4 -3
  122. package/dist/form/form-groups.js +79 -79
  123. package/dist/form/form-handle.d.ts +175 -0
  124. package/dist/form/form-handle.js +68 -0
  125. package/dist/form/input-binding.d.ts +56 -31
  126. package/dist/form/input-binding.js +171 -118
  127. package/dist/form/input-values.d.ts +140 -0
  128. package/dist/form/input-values.js +206 -0
  129. package/dist/form/json-text.d.ts +11 -0
  130. package/dist/form/json-text.js +62 -37
  131. package/dist/form/save-pipeline.d.ts +6 -8
  132. package/dist/form/save-pipeline.js +91 -82
  133. package/dist/form/set-form-values.d.ts +6 -0
  134. package/dist/form/set-form-values.js +10 -0
  135. package/dist/form/source.js +97 -65
  136. package/dist/form/text-of.d.ts +2 -0
  137. package/dist/form/text-of.js +7 -0
  138. package/dist/form/validators.d.ts +58 -19
  139. package/dist/form/validators.js +97 -71
  140. package/dist/geo/bbox.d.ts +5 -4
  141. package/dist/geo/bbox.js +69 -48
  142. package/dist/geo/bounds.d.ts +14 -3
  143. package/dist/geo/bounds.js +98 -68
  144. package/dist/geo/constraints.js +88 -93
  145. package/dist/geo/coordinates.d.ts +10 -0
  146. package/dist/geo/coordinates.js +44 -23
  147. package/dist/geo/editor.d.ts +13 -13
  148. package/dist/geo/editor.js +176 -153
  149. package/dist/geo/geocoding.d.ts +4 -4
  150. package/dist/geo/geocoding.js +38 -41
  151. package/dist/geo/geojson.d.ts +12 -74
  152. package/dist/geo/geojson.js +52 -40
  153. package/dist/geo/geometry-math.d.ts +4 -4
  154. package/dist/geo/geometry-math.js +100 -93
  155. package/dist/geo/index.d.ts +9 -8
  156. package/dist/geo/map-layers.d.ts +42 -7
  157. package/dist/geo/map-layers.js +88 -62
  158. package/dist/geo/map-style.d.ts +12 -0
  159. package/dist/geo/map-style.js +43 -32
  160. package/dist/geo/messages.js +48 -45
  161. package/dist/geo/nominatim.js +88 -80
  162. package/dist/geo/osm-presets.d.ts +4 -3
  163. package/dist/geo/osm-presets.js +108 -86
  164. package/dist/geo/osm-tags.js +14 -12
  165. package/dist/geo/overpass.d.ts +14 -2
  166. package/dist/geo/overpass.js +83 -90
  167. package/dist/geo/shape-value.d.ts +9 -5
  168. package/dist/geo/shape-value.js +169 -146
  169. package/dist/geo/snap-to-roads.d.ts +3 -3
  170. package/dist/geo/snap-to-roads.js +55 -50
  171. package/dist/geo/throttle.js +36 -39
  172. package/dist/guards.d.ts +44 -0
  173. package/dist/guards.js +77 -0
  174. package/dist/i18n/default-messages.js +34 -29
  175. package/dist/i18n/english-messages.d.ts +172 -0
  176. package/dist/i18n/english-messages.js +359 -189
  177. package/dist/i18n/i18n.d.ts +2 -2
  178. package/dist/i18n/i18n.js +47 -60
  179. package/dist/i18n/inflection.d.ts +34 -0
  180. package/dist/i18n/inflection.js +82 -0
  181. package/dist/i18n/messages.d.ts +10 -2
  182. package/dist/i18n/messages.js +17 -4
  183. package/dist/i18n/missing-keys.d.ts +4 -0
  184. package/dist/i18n/missing-keys.js +70 -62
  185. package/dist/i18n/translatable.d.ts +16 -1
  186. package/dist/i18n/translatable.js +40 -47
  187. package/dist/index.d.ts +87 -39
  188. package/dist/index.js +54 -301
  189. package/dist/json/deep-equal.d.ts +8 -0
  190. package/dist/json/deep-equal.js +24 -0
  191. package/dist/json/strip-json-comments.js +38 -42
  192. package/dist/noop.d.ts +2 -0
  193. package/dist/noop.js +4 -0
  194. package/dist/notification/error-message.d.ts +2 -0
  195. package/dist/notification/error-message.js +10 -0
  196. package/dist/notification/notification-store.d.ts +3 -2
  197. package/dist/notification/notification-store.js +41 -45
  198. package/dist/onboarding-tour/placement.d.ts +2 -0
  199. package/dist/onboarding-tour/placement.js +65 -78
  200. package/dist/onboarding-tour/tour-controller.js +46 -54
  201. package/dist/realtime/add-events-for-mutations.js +75 -81
  202. package/dist/realtime/announce-lock.js +16 -13
  203. package/dist/realtime/broadcast-channel-transport.js +65 -59
  204. package/dist/realtime/copy-members.js +22 -14
  205. package/dist/realtime/in-memory-lock-provider.js +40 -48
  206. package/dist/realtime/live.js +95 -101
  207. package/dist/realtime/lock-options.js +16 -16
  208. package/dist/realtime/lock-session.js +91 -78
  209. package/dist/realtime/memory-transport.js +25 -25
  210. package/dist/realtime/presence.js +120 -122
  211. package/dist/realtime/realtime-data-provider.d.ts +10 -0
  212. package/dist/realtime/realtime-data-provider.js +45 -42
  213. package/dist/realtime/reconnect-backoff.js +17 -0
  214. package/dist/realtime/sse-transport.d.ts +31 -0
  215. package/dist/realtime/sse-transport.js +235 -0
  216. package/dist/realtime/status-store.js +23 -28
  217. package/dist/realtime/topic-hub.js +62 -71
  218. package/dist/realtime/topics.js +10 -12
  219. package/dist/realtime/types.js +10 -12
  220. package/dist/realtime/unread-count.js +21 -21
  221. package/dist/realtime/websocket-transport.d.ts +32 -0
  222. package/dist/realtime/websocket-transport.js +306 -0
  223. package/dist/resource/describe-resource.d.ts +2 -0
  224. package/dist/resource/describe-resource.js +247 -219
  225. package/dist/resource/get-at-path.js +29 -14
  226. package/dist/resource/identifier.d.ts +18 -0
  227. package/dist/resource/identifier.js +27 -0
  228. package/dist/resource/record.d.ts +4 -0
  229. package/dist/resource/record.js +10 -0
  230. package/dist/resource/register.d.ts +7 -0
  231. package/dist/resource/registry-store.d.ts +45 -15
  232. package/dist/resource/registry-store.js +67 -60
  233. package/dist/resource/registry.d.ts +10 -0
  234. package/dist/resource/registry.js +8 -4
  235. package/dist/resource/source-path.d.ts +23 -2
  236. package/dist/rich-text/rich-text-extras.d.ts +134 -0
  237. package/dist/rich-text/rich-text-extras.js +181 -0
  238. package/dist/rich-text/rich-text-image.d.ts +124 -0
  239. package/dist/rich-text/rich-text-image.js +163 -0
  240. package/dist/rich-text/rich-text.d.ts +6 -0
  241. package/dist/rich-text/rich-text.js +125 -88
  242. package/dist/router/location.d.ts +24 -4
  243. package/dist/router/location.js +43 -18
  244. package/dist/router/match.js +47 -39
  245. package/dist/router/paths.js +40 -34
  246. package/dist/router/record-from-location.d.ts +1 -1
  247. package/dist/router/record-from-location.js +25 -17
  248. package/dist/store/adapters.js +51 -55
  249. package/dist/store/micro-store.js +57 -59
  250. package/dist/store/shallow-equal.d.ts +12 -0
  251. package/dist/store/shallow-equal.js +20 -0
  252. package/dist/store/store.js +54 -57
  253. package/dist/ui-defaults.d.ts +15 -0
  254. package/dist/ui-defaults.js +17 -0
  255. package/package.json +13 -11
@@ -1,27 +1,31 @@
1
+ // src/auth/access-control.ts
1
2
  function matchesAction(granted, action) {
2
- const list = Array.isArray(granted) ? granted : [granted];
3
- return list.includes("*") || list.includes(action);
3
+ const list = Array.isArray(granted) ? granted : [granted];
4
+ return list.includes("*") || list.includes(action);
4
5
  }
5
6
  function matchesResource(granted, resource) {
6
- if (granted === "*") return true;
7
- if (granted.endsWith(".*")) return resource.startsWith(granted.slice(0, -1));
8
- return granted === resource;
7
+ if (granted === "*") return true;
8
+ if (granted.endsWith(".*")) return resource.startsWith(granted.slice(0, -1));
9
+ return granted === resource;
9
10
  }
11
+ /**
12
+ * Deny-by-default permission matcher (OR across permissions). `TRecord` is fixed
13
+ * by the permissions and the params together, so a grant's predicate and the
14
+ * record it reads are the same type.
15
+ */
10
16
  function canAccessWithPermissions(permissions, params) {
11
- return permissions.some((permission) => {
12
- if (!matchesAction(permission.action, params.action)) return false;
13
- if (!matchesResource(permission.resource, params.resource)) return false;
14
- if (permission.record) {
15
- if (params.record == null) return false;
16
- try {
17
- return permission.record(params.record);
18
- } catch {
19
- return false;
20
- }
21
- }
22
- return true;
23
- });
17
+ return permissions.some((permission) => {
18
+ if (!matchesAction(permission.action, params.action)) return false;
19
+ if (!matchesResource(permission.resource, params.resource)) return false;
20
+ if (permission.record) {
21
+ if (params.record == null) return false;
22
+ try {
23
+ return permission.record(params.record);
24
+ } catch {
25
+ return false;
26
+ }
27
+ }
28
+ return true;
29
+ });
24
30
  }
25
- export {
26
- canAccessWithPermissions
27
- };
31
+ export { canAccessWithPermissions };
@@ -1,19 +1,27 @@
1
+ import { type SkipToken } from "@tanstack/query-core";
1
2
  import type { AuthProvider, CanAccessParams, UserIdentity } from "./types.js";
2
- /** Plain option objects, same convention as the data factories (P2). */
3
- export declare function identityQueryOptions(authProvider: AuthProvider): {
4
- queryKey: readonly ["auth", "getIdentity"];
5
- queryFn: () => Promise<UserIdentity>;
6
- };
7
- export declare function canAccessQueryOptions(authProvider: AuthProvider, params: CanAccessParams): {
8
- queryKey: readonly ["auth", "canAccess", CanAccessParams];
3
+ /** The path a logged-out session is sent to when no `redirectTo` is given. */
4
+ export declare const DEFAULT_LOGIN_PATH = "/login";
5
+ /** The option shape of the factories that skip with no provider. */
6
+ interface SkippableQueryOptions<TKey, TData> {
7
+ queryKey: TKey;
8
+ queryFn: SkipToken | (() => Promise<TData>);
9
+ }
10
+ /** What `getPermissions` takes: the key slot of {@link permissionsQueryOptions}. */
11
+ type PermissionsParams = Parameters<NonNullable<AuthProvider["getPermissions"]>>[0];
12
+ /**
13
+ * Plain option objects, same convention as the data factories (P2). With no
14
+ * provider, `identityQueryOptions`, `authStateQueryOptions` and
15
+ * `permissionsQueryOptions` set `queryFn: skipToken`, so the query never runs.
16
+ */
17
+ export declare function identityQueryOptions(authProvider: AuthProvider | undefined): SkippableQueryOptions<readonly ["auth", "getIdentity"], UserIdentity>;
18
+ export declare function canAccessQueryOptions(authProvider: AuthProvider | undefined, params: CanAccessParams): {
19
+ queryKey: readonly ["auth", "canAccess", CanAccessParams<import("../index.js").ResourceRecord<import("../index.js").Identifier>>];
9
20
  queryFn: () => Promise<boolean>;
10
21
  };
11
- export declare function authStateQueryOptions(authProvider: AuthProvider): {
12
- queryKey: readonly ["auth", "checkAuth"];
13
- queryFn: () => Promise<{
14
- authenticated: boolean;
15
- }>;
16
- };
22
+ export declare function authStateQueryOptions(authProvider: AuthProvider | undefined): SkippableQueryOptions<readonly ["auth", "checkAuth"], {
23
+ authenticated: boolean;
24
+ }>;
17
25
  /**
18
26
  * Fetch the current user permissions via `authProvider.getPermissions`.
19
27
  * No `getPermissions` on the provider = permissions not configured =
@@ -22,10 +30,7 @@ export declare function authStateQueryOptions(authProvider: AuthProvider): {
22
30
  * a queryFn resolving `undefined`. Mirrors {@link canAccessQueryOptions}: keyed
23
31
  * by the passed `params` so distinct param objects cache independently.
24
32
  */
25
- export declare function permissionsQueryOptions(authProvider: AuthProvider, params?: unknown): {
26
- queryKey: readonly ["auth", "getPermissions", unknown];
27
- queryFn: () => Promise<unknown>;
28
- };
33
+ export declare function permissionsQueryOptions<TPermissions>(authProvider: AuthProvider<TPermissions> | undefined, params?: unknown): SkippableQueryOptions<readonly ["auth", "getPermissions", PermissionsParams], TPermissions | null>;
29
34
  /**
30
35
  * The triage result of {@link checkError}. Vanilla (react/vue-free) so both
31
36
  * adapters share the reject-payload parsing before doing logout/notify/redirect.
@@ -55,3 +60,4 @@ export interface CheckErrorOutcome {
55
60
  * redirect; only an auth rejection ever logs the user out.
56
61
  */
57
62
  export declare function checkError(authProvider: AuthProvider, error: unknown): Promise<CheckErrorOutcome>;
63
+ export {};
@@ -1,60 +1,94 @@
1
+ import { isRecord } from "../guards.js";
2
+ import { skipToken } from "@tanstack/query-core";
3
+ // src/auth/auth-options.ts
4
+ /** The path a logged-out session is sent to when no `redirectTo` is given. */
5
+ var DEFAULT_LOGIN_PATH = "/login";
6
+ /** `skipToken` (the query never runs) with no provider, else `read` bound to it. */
7
+ function queryFnFor(authProvider, read) {
8
+ return authProvider ? () => read(authProvider) : skipToken;
9
+ }
10
+ /**
11
+ * Plain option objects, same convention as the data factories (P2). With no
12
+ * provider, `identityQueryOptions`, `authStateQueryOptions` and
13
+ * `permissionsQueryOptions` set `queryFn: skipToken`, so the query never runs.
14
+ */
1
15
  function identityQueryOptions(authProvider) {
2
- return {
3
- queryKey: ["auth", "getIdentity"],
4
- queryFn: async () => {
5
- if (!authProvider.getIdentity) {
6
- throw new Error(
7
- "identityQueryOptions: the authProvider does not implement getIdentity."
8
- );
9
- }
10
- return authProvider.getIdentity();
11
- }
12
- };
16
+ return {
17
+ queryKey: ["auth", "getIdentity"],
18
+ queryFn: queryFnFor(authProvider, async (provider) => {
19
+ if (!provider.getIdentity) throw new Error("identityQueryOptions: the authProvider does not implement getIdentity.");
20
+ return provider.getIdentity();
21
+ })
22
+ };
13
23
  }
14
24
  function canAccessQueryOptions(authProvider, params) {
15
- return {
16
- queryKey: ["auth", "canAccess", params],
17
- // No canAccess on the provider = access control not configured = allow.
18
- queryFn: async () => authProvider.canAccess?.(params) ?? true
19
- };
25
+ return {
26
+ queryKey: [
27
+ "auth",
28
+ "canAccess",
29
+ params
30
+ ],
31
+ queryFn: async () => authProvider?.canAccess?.(params) ?? true
32
+ };
20
33
  }
21
34
  function authStateQueryOptions(authProvider) {
22
- return {
23
- queryKey: ["auth", "checkAuth"],
24
- queryFn: async () => {
25
- try {
26
- await authProvider.checkAuth();
27
- return { authenticated: true };
28
- } catch {
29
- return { authenticated: false };
30
- }
31
- }
32
- };
35
+ return {
36
+ queryKey: ["auth", "checkAuth"],
37
+ queryFn: queryFnFor(authProvider, async (provider) => {
38
+ try {
39
+ await provider.checkAuth();
40
+ return { authenticated: true };
41
+ } catch {
42
+ return { authenticated: false };
43
+ }
44
+ })
45
+ };
33
46
  }
47
+ /**
48
+ * Fetch the current user permissions via `authProvider.getPermissions`.
49
+ * No `getPermissions` on the provider = permissions not configured =
50
+ * resolves `null` (undefined-tolerant; never rejects for that reason). `null`
51
+ * rather than `undefined` is the sentinel because react-query/vue-query forbid
52
+ * a queryFn resolving `undefined`. Mirrors {@link canAccessQueryOptions}: keyed
53
+ * by the passed `params` so distinct param objects cache independently.
54
+ */
34
55
  function permissionsQueryOptions(authProvider, params) {
35
- return {
36
- queryKey: ["auth", "getPermissions", params],
37
- queryFn: async () => await authProvider.getPermissions?.(params) ?? null
38
- };
56
+ return {
57
+ queryKey: [
58
+ "auth",
59
+ "getPermissions",
60
+ params
61
+ ],
62
+ queryFn: queryFnFor(authProvider, async (provider) => await provider.getPermissions?.(params) ?? null)
63
+ };
39
64
  }
40
65
  function readProp(source, key) {
41
- return source != null && typeof source === "object" && key in source ? source[key] : void 0;
66
+ return isRecord(source) && key in source ? source[key] : void 0;
42
67
  }
68
+ /**
69
+ * Error-triage helper for the data error path. Fail-safe: runs
70
+ * `authProvider.checkError(error)` and NEVER throws.
71
+ *
72
+ * - checkError RESOLVES -> ordinary data error -> `{ logout: false }`.
73
+ * - checkError REJECTS -> invalid session -> `{ logout: true, ... }` carrying
74
+ * `redirectTo` / `logoutUser` / `message` hints from the reject payload (or,
75
+ * for `redirectTo`, the original error). Adapters do the logout + notify +
76
+ * redirect; only an auth rejection ever logs the user out.
77
+ */
43
78
  async function checkError(authProvider, error) {
44
- try {
45
- await authProvider.checkError(error);
46
- return { logout: false, logoutUser: false };
47
- } catch (rejection) {
48
- const logoutUser = readProp(rejection, "logoutUser") ?? true;
49
- const redirectTo = readProp(rejection, "redirectTo") ?? readProp(error, "redirectTo");
50
- const message = readProp(rejection, "message");
51
- return { logout: true, logoutUser, redirectTo, message };
52
- }
79
+ try {
80
+ await authProvider.checkError(error);
81
+ return {
82
+ logout: false,
83
+ logoutUser: false
84
+ };
85
+ } catch (rejection) {
86
+ return {
87
+ logout: true,
88
+ logoutUser: readProp(rejection, "logoutUser") ?? true,
89
+ redirectTo: readProp(rejection, "redirectTo") ?? readProp(error, "redirectTo"),
90
+ message: readProp(rejection, "message")
91
+ };
92
+ }
53
93
  }
54
- export {
55
- authStateQueryOptions,
56
- canAccessQueryOptions,
57
- checkError,
58
- identityQueryOptions,
59
- permissionsQueryOptions
60
- };
94
+ export { DEFAULT_LOGIN_PATH, authStateQueryOptions, canAccessQueryOptions, checkError, identityQueryOptions, permissionsQueryOptions };
@@ -0,0 +1,12 @@
1
+ import { type Permission } from "./access-control.js";
2
+ /** Role id to that role's grants. */
3
+ export type PermissionMatrixValue = Record<string, Permission[]>;
4
+ /** What one cell of a role's row shows. `inherited` is read-only: a wildcard grants it. */
5
+ export type PermissionCellState = "granted" | "inherited" | "denied";
6
+ export declare function getPermissionCell(grants: Permission[], resource: string, action: string): PermissionCellState;
7
+ /** Flip one cell. An inherited cell returns `value` itself, so a caller can skip `onChange`. */
8
+ export declare function togglePermission(value: PermissionMatrixValue, role: string, resource: string, action: string): PermissionMatrixValue;
9
+ /** Grant or remove every listed action on a resource at once, skipping inherited cells. */
10
+ export declare function setPermissionRow(value: PermissionMatrixValue, role: string, resource: string, actions: string[], granted: boolean): PermissionMatrixValue;
11
+ /** True when every action is granted or inherited: the All checkbox. */
12
+ export declare function isPermissionRowGranted(grants: Permission[], resource: string, actions: string[]): boolean;
@@ -0,0 +1,67 @@
1
+ import { canAccessWithPermissions } from "./access-control.js";
2
+ // src/auth/permission-matrix.ts
3
+ function actionsOf(permission) {
4
+ return Array.isArray(permission.action) ? permission.action : [permission.action];
5
+ }
6
+ /** A grant a cell can edit: this exact resource, no record predicate. */
7
+ function isEditable(permission, resource) {
8
+ return permission.resource === resource && permission.record === void 0;
9
+ }
10
+ function getPermissionCell(grants, resource, action) {
11
+ if (grants.some((grant) => isEditable(grant, resource) && actionsOf(grant).includes(action))) return "granted";
12
+ return canAccessWithPermissions(grants, {
13
+ resource,
14
+ action
15
+ }) ? "inherited" : "denied";
16
+ }
17
+ function addAction(grants, resource, action) {
18
+ const index = grants.findIndex((grant) => isEditable(grant, resource));
19
+ if (grants[index] === void 0) return [...grants, {
20
+ resource,
21
+ action: [action]
22
+ }];
23
+ return grants.map((grant, i) => i === index ? {
24
+ ...grant,
25
+ action: [...actionsOf(grant), action]
26
+ } : grant);
27
+ }
28
+ function removeAction(grants, resource, action) {
29
+ return grants.flatMap((grant) => {
30
+ if (!isEditable(grant, resource) || !actionsOf(grant).includes(action)) return [grant];
31
+ const left = actionsOf(grant).filter((name) => name !== action);
32
+ return left.length === 0 ? [] : [{
33
+ ...grant,
34
+ action: left
35
+ }];
36
+ });
37
+ }
38
+ function withGrants(value, role, grants) {
39
+ return {
40
+ ...value,
41
+ [role]: grants
42
+ };
43
+ }
44
+ /** Flip one cell. An inherited cell returns `value` itself, so a caller can skip `onChange`. */
45
+ function togglePermission(value, role, resource, action) {
46
+ const grants = value[role] ?? [];
47
+ const state = getPermissionCell(grants, resource, action);
48
+ if (state === "inherited") return value;
49
+ return withGrants(value, role, state === "denied" ? addAction(grants, resource, action) : removeAction(grants, resource, action));
50
+ }
51
+ /** Grant or remove every listed action on a resource at once, skipping inherited cells. */
52
+ function setPermissionRow(value, role, resource, actions, granted) {
53
+ const grants = value[role] ?? [];
54
+ const next = actions.reduce((current, action) => {
55
+ const state = getPermissionCell(current, resource, action);
56
+ if (state === "inherited") return current;
57
+ if (granted && state === "denied") return addAction(current, resource, action);
58
+ if (!granted && state === "granted") return removeAction(current, resource, action);
59
+ return current;
60
+ }, grants);
61
+ return next === grants ? value : withGrants(value, role, next);
62
+ }
63
+ /** True when every action is granted or inherited: the All checkbox. */
64
+ function isPermissionRowGranted(grants, resource, actions) {
65
+ return actions.every((action) => getPermissionCell(grants, resource, action) !== "denied");
66
+ }
67
+ export { getPermissionCell, isPermissionRowGranted, setPermissionRow, togglePermission };
@@ -1,21 +1,30 @@
1
1
  import type { Identifier } from "../resource/identifier.js";
2
+ import type { ResourceRecord } from "../resource/record.js";
3
+ import type { RegisteredPermissions } from "../resource/register.js";
2
4
  export interface UserIdentity {
3
5
  id: Identifier;
4
6
  fullName?: string;
5
7
  avatar?: string;
6
8
  [key: string]: unknown;
7
9
  }
8
- export interface CanAccessParams {
10
+ /**
11
+ * One access check. `TRecord` is the record shape the check carries. Left
12
+ * unannotated it is a plain {@link ResourceRecord}, which only exposes `id`: a
13
+ * provider that runs `Permission<Post>`-style grants declares its own record
14
+ * type on `canAccess`'s params, which the interface accepts, and can then hand
15
+ * its params straight to `canAccessWithPermissions`.
16
+ */
17
+ export interface CanAccessParams<TRecord extends ResourceRecord = ResourceRecord> {
9
18
  action: string;
10
19
  resource: string;
11
- record?: unknown;
20
+ record?: TRecord;
12
21
  }
13
22
  /**
14
23
  * The auth contract. All methods return promises; reject = not authenticated /
15
24
  * not permitted. `refreshAuth` is the token-refresh hook point (charter §11);
16
25
  * the P6 shell builds the no-flash SSR gate on `checkAuth`.
17
26
  */
18
- export interface AuthProvider {
27
+ export interface AuthProvider<TPermissions = RegisteredPermissions> {
19
28
  login(params: Record<string, unknown>): Promise<void | {
20
29
  redirectTo?: string;
21
30
  }>;
@@ -23,7 +32,8 @@ export interface AuthProvider {
23
32
  checkAuth(params?: unknown): Promise<void>;
24
33
  checkError(error: unknown): Promise<void>;
25
34
  getIdentity?(): Promise<UserIdentity>;
26
- getPermissions?(params?: unknown): Promise<unknown>;
35
+ /** Resolves `undefined` when there are no permissions to give, e.g. nobody is signed in. */
36
+ getPermissions?(params?: unknown): Promise<TPermissions | undefined>;
27
37
  canAccess?(params: CanAccessParams): Promise<boolean>;
28
38
  handleCallback?(): Promise<void | {
29
39
  redirectTo?: string;
@@ -0,0 +1,64 @@
1
+ import type { StandardSchemaV1 } from "@tanstack/form-core";
2
+ import { type FieldDescriptor } from "../resource/describe-resource.js";
3
+ /** The dnd-kit id of the block editor's drag handle, shared by both adapters. */
4
+ export declare const HANDLE_ID = "block-drag-handle";
5
+ /** Prefix of the dnd-kit id of each drop gap, followed by the gap's index. */
6
+ export declare const GAP_PREFIX = "block-gap-";
7
+ export type Attrs = Record<string, unknown>;
8
+ /** The shape of tiptap JSON, with no tiptap import. */
9
+ export interface BlockDocNode {
10
+ type?: string;
11
+ attrs?: Attrs;
12
+ content?: BlockDocNode[];
13
+ marks?: {
14
+ type: string;
15
+ attrs?: Attrs;
16
+ }[];
17
+ text?: string;
18
+ }
19
+ type BlockSchema<A extends Attrs> = StandardSchemaV1<unknown, A>;
20
+ /**
21
+ * One block a block editor can insert. `schema` is a Zod object (3 or 4) whose
22
+ * output is the block's attrs. Adapters extend this with their render types.
23
+ */
24
+ export interface BlockSpec<A extends Attrs = Attrs> {
25
+ name: string;
26
+ label: string;
27
+ group: string;
28
+ keywords?: string[];
29
+ description?: string;
30
+ schema: BlockSchema<A>;
31
+ content?: string;
32
+ atom?: boolean;
33
+ }
34
+ export interface BlockRegistry<B extends BlockSpec = BlockSpec> {
35
+ get(name: string): B | undefined;
36
+ list(): B[];
37
+ names(): string[];
38
+ /** Group names in the order a block first names them. */
39
+ groups(): string[];
40
+ }
41
+ export declare const unknownBlockType = "unknownBlock";
42
+ export declare function createBlockRegistry<B extends BlockSpec>(blocks: readonly B[]): BlockRegistry<B>;
43
+ /** Each key's `safeParse(undefined)` result when it succeeds, else `null`. */
44
+ export declare function getBlockDefaultAttrs<A extends Attrs>(schema: BlockSchema<A>): {
45
+ [K in keyof A]: A[K] | null;
46
+ };
47
+ /** One descriptor per attr, for an adapter to build the block's config form from. */
48
+ export declare function describeBlockAttrs(schema: BlockSchema<Attrs>): FieldDescriptor[];
49
+ export declare function matchBlocks<B extends BlockSpec>(blocks: readonly B[], query: string): B[];
50
+ /**
51
+ * Wraps each node of an unknown type in an `unknownBlock`, so the editor keeps
52
+ * it. `canLoad` also catches what a type name cannot: an unknown mark, or an
53
+ * unknown node inside a paragraph. A top-level block it rejects is wrapped
54
+ * whole, from the document as stored.
55
+ */
56
+ export declare function wrapUnknownBlocks(doc: BlockDocNode, known: ReadonlySet<string>, canLoad?: (block: BlockDocNode) => boolean): BlockDocNode;
57
+ export declare function unwrapUnknownBlocks(doc: BlockDocNode): BlockDocNode;
58
+ /** A stored value an editor can load: an object whose `type` is `"doc"`. */
59
+ export declare const isBlockDoc: (value: unknown) => value is BlockDocNode;
60
+ export declare const emptyBlockDocument: () => BlockDocNode;
61
+ export declare function isEmptyBlockDocument(value: BlockDocNode | null | undefined): boolean;
62
+ /** An embed URL for a YouTube or Vimeo link, built from the checked id alone. */
63
+ export declare function toEmbedSrc(url: string): string | null;
64
+ export {};
@@ -0,0 +1,147 @@
1
+ import { columnLabelMatches } from "../controller/data-table-columns.js";
2
+ import { describeField } from "../resource/describe-resource.js";
3
+ // src/block-editor/block-document.ts
4
+ /** The dnd-kit id of the block editor's drag handle, shared by both adapters. */
5
+ var HANDLE_ID = "block-drag-handle";
6
+ /** Prefix of the dnd-kit id of each drop gap, followed by the gap's index. */
7
+ var GAP_PREFIX = "block-gap-";
8
+ var unknownBlockType = "unknownBlock";
9
+ var builtInNodes = /* @__PURE__ */ new Set([
10
+ "doc",
11
+ "text",
12
+ "paragraph",
13
+ "heading",
14
+ "blockquote",
15
+ "bulletList",
16
+ "orderedList",
17
+ "listItem",
18
+ "codeBlock",
19
+ "horizontalRule",
20
+ "hardBreak",
21
+ unknownBlockType
22
+ ]);
23
+ function createBlockRegistry(blocks) {
24
+ const byName = /* @__PURE__ */ new Map();
25
+ const groups = /* @__PURE__ */ new Set();
26
+ for (const block of blocks) {
27
+ if (builtInNodes.has(block.name)) throw new Error(`block-editor: "${block.name}" is a built-in node`);
28
+ if (byName.has(block.name)) throw new Error(`block-editor: duplicate block name "${block.name}"`);
29
+ byName.set(block.name, block);
30
+ groups.add(block.group);
31
+ }
32
+ return {
33
+ get: (name) => byName.get(name),
34
+ list: () => [...byName.values()],
35
+ names: () => [...byName.keys()],
36
+ groups: () => [...groups]
37
+ };
38
+ }
39
+ var isObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
40
+ var isParsable = (value) => isObject(value) && typeof value.safeParse === "function";
41
+ /** Zod 3 and Zod 4 objects both expose `shape`. */
42
+ function shapeOf(schema) {
43
+ const shape = isObject(schema) ? schema.shape : void 0;
44
+ if (!isObject(shape)) throw new Error("block schema must be a z.object");
45
+ return shape;
46
+ }
47
+ /** Each key's `safeParse(undefined)` result when it succeeds, else `null`. */
48
+ function getBlockDefaultAttrs(schema) {
49
+ const shape = shapeOf(schema);
50
+ const defaults = {};
51
+ for (const key of Object.keys(shape)) {
52
+ const member = shape[key];
53
+ const result = isParsable(member) ? member.safeParse(void 0) : void 0;
54
+ defaults[key] = result?.success ? result.data : null;
55
+ }
56
+ return defaults;
57
+ }
58
+ /** One descriptor per attr, for an adapter to build the block's config form from. */
59
+ function describeBlockAttrs(schema) {
60
+ const shape = shapeOf(schema);
61
+ return Object.keys(shape).map((key) => describeField(key, shape[key], void 0));
62
+ }
63
+ function matchBlocks(blocks, query) {
64
+ return blocks.filter((block) => columnLabelMatches(block.label, query) || (block.keywords ?? []).some((keyword) => columnLabelMatches(keyword, query)) || block.description !== void 0 && columnLabelMatches(block.description, query));
65
+ }
66
+ var transparentTypes = /* @__PURE__ */ new Set(["doc", "text"]);
67
+ function mapChildren(node, visit) {
68
+ return node.content === void 0 ? node : {
69
+ ...node,
70
+ content: node.content.map(visit)
71
+ };
72
+ }
73
+ /**
74
+ * Wraps each node of an unknown type in an `unknownBlock`, so the editor keeps
75
+ * it. `canLoad` also catches what a type name cannot: an unknown mark, or an
76
+ * unknown node inside a paragraph. A top-level block it rejects is wrapped
77
+ * whole, from the document as stored.
78
+ */
79
+ function wrapUnknownBlocks(doc, known, canLoad) {
80
+ const wrapNode = (node) => {
81
+ if (node.type !== void 0 && (transparentTypes.has(node.type) || known.has(node.type))) return mapChildren(node, wrapNode);
82
+ return {
83
+ type: unknownBlockType,
84
+ attrs: { payload: node }
85
+ };
86
+ };
87
+ const wrapped = mapChildren(doc, wrapNode);
88
+ if (canLoad === void 0 || wrapped.content === void 0) return wrapped;
89
+ return {
90
+ ...wrapped,
91
+ content: wrapped.content.map((block, index) => canLoad(block) ? block : {
92
+ type: unknownBlockType,
93
+ attrs: { payload: doc.content?.[index] ?? block }
94
+ })
95
+ };
96
+ }
97
+ var isDocNode = (value) => isObject(value);
98
+ function unwrapUnknownBlocks(doc) {
99
+ const unwrapNode = (node) => {
100
+ const payload = node.attrs?.payload;
101
+ if (node.type === "unknownBlock" && isDocNode(payload)) return payload;
102
+ return mapChildren(node, unwrapNode);
103
+ };
104
+ return mapChildren(doc, unwrapNode);
105
+ }
106
+ /** A stored value an editor can load: an object whose `type` is `"doc"`. */
107
+ var isBlockDoc = (value) => isObject(value) && value.type === "doc";
108
+ var emptyBlockDocument = () => ({
109
+ type: "doc",
110
+ content: [{ type: "paragraph" }]
111
+ });
112
+ function isEmptyBlockDocument(value) {
113
+ if (value === null || value === void 0) return true;
114
+ return (value.content ?? []).every((node) => node.type === "paragraph" && (node.content === void 0 || node.content.length === 0));
115
+ }
116
+ var youTubeHosts = /* @__PURE__ */ new Set([
117
+ "youtube.com",
118
+ "www.youtube.com",
119
+ "m.youtube.com"
120
+ ]);
121
+ var youTubeId = /^[A-Za-z0-9_-]{11}$/;
122
+ var vimeoId = /^\d+$/;
123
+ /** An embed URL for a YouTube or Vimeo link, built from the checked id alone. */
124
+ function toEmbedSrc(url) {
125
+ let parsed;
126
+ try {
127
+ parsed = new URL(url);
128
+ } catch {
129
+ return null;
130
+ }
131
+ if (parsed.protocol !== "https:" && parsed.protocol !== "http:") return null;
132
+ const { hostname, pathname } = parsed;
133
+ if (youTubeHosts.has(hostname) && pathname === "/watch") {
134
+ const id = parsed.searchParams.get("v");
135
+ return id !== null && youTubeId.test(id) ? `https://www.youtube-nocookie.com/embed/${id}` : null;
136
+ }
137
+ if (hostname === "youtu.be") {
138
+ const id = pathname.slice(1);
139
+ return youTubeId.test(id) ? `https://www.youtube-nocookie.com/embed/${id}` : null;
140
+ }
141
+ if (hostname === "vimeo.com" || hostname === "www.vimeo.com") {
142
+ const id = pathname.slice(1);
143
+ return vimeoId.test(id) ? `https://player.vimeo.com/video/${id}` : null;
144
+ }
145
+ return null;
146
+ }
147
+ export { GAP_PREFIX, HANDLE_ID, createBlockRegistry, describeBlockAttrs, emptyBlockDocument, getBlockDefaultAttrs, isBlockDoc, isEmptyBlockDocument, matchBlocks, toEmbedSrc, unknownBlockType, unwrapUnknownBlocks, wrapUnknownBlocks };
@@ -0,0 +1,63 @@
1
+ import { z } from "zod";
2
+ import type { Attrs, BlockSpec } from "./block-document.js";
3
+ export declare const dataBlockNames: readonly ["referenceRecord", "recordList", "chart"];
4
+ export type DataBlockName = (typeof dataBlockNames)[number];
5
+ /** A data block spec. The keys are always `chelone.block_editor.<name>.label` and `.description`. */
6
+ export interface DataBlockSpec<TName extends DataBlockName, A extends Attrs> extends BlockSpec<A> {
7
+ name: TName;
8
+ group: "data";
9
+ labelKey: `chelone.block_editor.${TName}.label`;
10
+ descriptionKey: `chelone.block_editor.${TName}.description`;
11
+ description: string;
12
+ keywords: string[];
13
+ }
14
+ declare const referenceRecordSchema: z.ZodObject<{
15
+ resource: z.ZodDefault<z.ZodString>;
16
+ id: z.ZodDefault<z.ZodNullable<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>>;
17
+ fields: z.ZodOptional<z.ZodArray<z.ZodString>>;
18
+ layout: z.ZodDefault<z.ZodEnum<{
19
+ card: "card";
20
+ inline: "inline";
21
+ }>>;
22
+ }, z.core.$strip>;
23
+ declare const recordListSchema: z.ZodObject<{
24
+ resource: z.ZodDefault<z.ZodString>;
25
+ perPage: z.ZodDefault<z.ZodNumber>;
26
+ fields: z.ZodOptional<z.ZodArray<z.ZodString>>;
27
+ }, z.core.$strip>;
28
+ declare const chartSchema: z.ZodObject<{
29
+ resource: z.ZodDefault<z.ZodString>;
30
+ type: z.ZodDefault<z.ZodEnum<{
31
+ bar: "bar";
32
+ line: "line";
33
+ pie: "pie";
34
+ }>>;
35
+ category: z.ZodDefault<z.ZodString>;
36
+ value: z.ZodDefault<z.ZodString>;
37
+ aggregate: z.ZodDefault<z.ZodEnum<{
38
+ count: "count";
39
+ sum: "sum";
40
+ avg: "avg";
41
+ }>>;
42
+ perPage: z.ZodDefault<z.ZodNumber>;
43
+ }, z.core.$strip>;
44
+ export declare const referenceRecordBlockSpec: DataBlockSpec<"referenceRecord", z.output<typeof referenceRecordSchema>>;
45
+ export declare const recordListBlockSpec: DataBlockSpec<"recordList", z.output<typeof recordListSchema>>;
46
+ export declare const chartBlockSpec: DataBlockSpec<"chart", z.output<typeof chartSchema>>;
47
+ /** The fields a data block shows: the given ones, else every key but `id`, cut to `limit`. */
48
+ export declare function pickDataBlockFields(record: object, fields?: readonly string[], limit?: number): string[];
49
+ export declare function formatDataBlockValue(value: unknown): string;
50
+ export interface ChartAggregateOptions {
51
+ category: string;
52
+ value: string;
53
+ aggregate: "count" | "sum" | "avg";
54
+ }
55
+ export interface ChartAggregatePoint {
56
+ name: string;
57
+ value: number;
58
+ }
59
+ /** A record's own value for a key. A key the record does not own reads as absent. */
60
+ export declare const readDataBlockValue: (row: object, key: string) => unknown;
61
+ /** Group rows by `String(row[category])` in first-seen order and reduce `value` by the aggregate. */
62
+ export declare function aggregateChartData(rows: readonly object[], { category, value, aggregate }: ChartAggregateOptions): ChartAggregatePoint[];
63
+ export {};