@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.
- package/dist/assistant/assistant-session.d.ts +6 -0
- package/dist/assistant/assistant-session.js +204 -181
- package/dist/assistant/echo-transport.js +26 -29
- package/dist/assistant/http-transport.d.ts +10 -0
- package/dist/assistant/http-transport.js +98 -0
- package/dist/assistant/types.d.ts +1 -7
- package/dist/auth/access-control.d.ts +13 -5
- package/dist/auth/access-control.js +25 -21
- package/dist/auth/auth-options.d.ts +21 -17
- package/dist/auth/auth-options.js +80 -48
- package/dist/auth/permission-matrix.d.ts +12 -0
- package/dist/auth/permission-matrix.js +67 -0
- package/dist/auth/types.d.ts +14 -4
- package/dist/block-editor/block-document.d.ts +60 -0
- package/dist/block-editor/block-document.js +143 -0
- package/dist/block-editor/data-blocks.d.ts +63 -0
- package/dist/block-editor/data-blocks.js +117 -0
- package/dist/chart/chart-data.js +46 -44
- package/dist/command-menu/hotkey.js +58 -47
- package/dist/command-menu/recents.js +8 -11
- package/dist/command-menu/registry.d.ts +9 -5
- package/dist/command-menu/registry.js +36 -46
- package/dist/controller/calendar-dates.js +82 -113
- package/dist/controller/calendar-events.js +93 -88
- package/dist/controller/client-list-controller.d.ts +2 -2
- package/dist/controller/client-list-controller.js +251 -254
- package/dist/controller/data-table-columns.d.ts +2 -0
- package/dist/controller/data-table-columns.js +50 -56
- package/dist/controller/deep-equal.d.ts +1 -0
- package/dist/controller/deep-equal.js +14 -0
- package/dist/controller/filter-toggle.js +36 -44
- package/dist/controller/in-place-editor.d.ts +42 -0
- package/dist/controller/in-place-editor.js +42 -0
- package/dist/controller/infinite-list-controller.js +219 -189
- package/dist/controller/inspector.d.ts +49 -0
- package/dist/controller/inspector.js +64 -0
- package/dist/controller/job-monitor.d.ts +3 -4
- package/dist/controller/job-monitor.js +37 -47
- package/dist/controller/kanban-board.d.ts +2 -3
- package/dist/controller/kanban-board.js +43 -24
- package/dist/controller/list-controller.d.ts +5 -5
- package/dist/controller/list-controller.js +444 -400
- package/dist/controller/list-params.js +96 -87
- package/dist/controller/pivot-grid.js +89 -95
- package/dist/controller/prev-next.d.ts +2 -2
- package/dist/controller/prev-next.js +74 -54
- package/dist/controller/record-controllers.d.ts +14 -6
- package/dist/controller/record-controllers.js +288 -257
- package/dist/controller/record-diff.d.ts +21 -0
- package/dist/controller/record-diff.js +50 -0
- package/dist/controller/record-timeline.js +42 -30
- package/dist/controller/record-tree.js +57 -47
- package/dist/controller/record-workflow.d.ts +38 -16
- package/dist/controller/record-workflow.js +85 -64
- package/dist/controller/reference-many-controller.js +198 -192
- package/dist/controller/reference-one-controller.d.ts +1 -1
- package/dist/controller/reference-one-controller.js +63 -56
- package/dist/controller/saved-queries.js +104 -91
- package/dist/controller/translate-fallback.js +10 -6
- package/dist/csv/export.d.ts +2 -1
- package/dist/csv/export.js +43 -40
- package/dist/csv/import-controller.js +185 -169
- package/dist/csv/match-headers.js +29 -22
- package/dist/csv/parse-with.js +60 -54
- package/dist/csv/run-import.js +69 -72
- package/dist/csv/validate-rows.d.ts +1 -1
- package/dist/csv/validate-rows.js +65 -52
- package/dist/data/cache-ops.d.ts +5 -4
- package/dist/data/cache-ops.js +208 -188
- package/dist/data/combine-data-providers.js +25 -23
- package/dist/data/data-provider-log.d.ts +45 -0
- package/dist/data/data-provider-log.js +93 -0
- package/dist/data/errors.js +14 -10
- package/dist/data/fake-provider.d.ts +6 -3
- package/dist/data/fake-provider.js +129 -114
- package/dist/data/fetch-utils.js +61 -44
- package/dist/data/filter-branch.d.ts +5 -0
- package/dist/data/filter-branch.js +10 -0
- package/dist/data/get-many-aggregate.js +84 -76
- package/dist/data/http-error.js +17 -12
- package/dist/data/keys.js +50 -36
- package/dist/data/mutation-options.d.ts +14 -9
- package/dist/data/mutation-options.js +93 -109
- package/dist/data/query-options.js +73 -68
- package/dist/data/remove-empty.d.ts +3 -0
- package/dist/data/remove-empty.js +11 -0
- package/dist/data/undoable.d.ts +2 -2
- package/dist/data/undoable.js +190 -174
- package/dist/data/with-lifecycle-callbacks.d.ts +45 -17
- package/dist/data/with-lifecycle-callbacks.js +66 -52
- package/dist/environment/application-update-store.js +58 -55
- package/dist/environment/offline-store.js +21 -21
- package/dist/form/array-binding.d.ts +30 -11
- package/dist/form/array-binding.js +184 -166
- package/dist/form/choice-ids.d.ts +7 -0
- package/dist/form/choice-ids.js +11 -0
- package/dist/form/choices-controller.d.ts +3 -2
- package/dist/form/choices-controller.js +302 -275
- package/dist/form/date-format.d.ts +38 -0
- package/dist/form/date-format.js +89 -0
- package/dist/form/dirty-values.d.ts +2 -2
- package/dist/form/dirty-values.js +22 -11
- package/dist/form/duration.d.ts +22 -0
- package/dist/form/duration.js +49 -0
- package/dist/form/edit-create-form.d.ts +12 -5
- package/dist/form/edit-create-form.js +243 -188
- package/dist/form/event-value.d.ts +6 -0
- package/dist/form/event-value.js +17 -0
- package/dist/form/field-error.d.ts +14 -0
- package/dist/form/field-error.js +28 -0
- package/dist/form/field-name.d.ts +20 -0
- package/dist/form/field-name.js +6 -0
- package/dist/form/filter-form-values.js +63 -49
- package/dist/form/form-controller.d.ts +9 -1
- package/dist/form/form-controller.js +165 -160
- package/dist/form/form-groups.d.ts +4 -3
- package/dist/form/form-groups.js +79 -79
- package/dist/form/form-handle.d.ts +175 -0
- package/dist/form/form-handle.js +68 -0
- package/dist/form/input-binding.d.ts +56 -31
- package/dist/form/input-binding.js +169 -118
- package/dist/form/input-values.d.ts +130 -0
- package/dist/form/input-values.js +177 -0
- package/dist/form/json-text.js +51 -37
- package/dist/form/save-pipeline.d.ts +6 -8
- package/dist/form/save-pipeline.js +91 -82
- package/dist/form/set-form-values.d.ts +6 -0
- package/dist/form/set-form-values.js +10 -0
- package/dist/form/source.js +97 -65
- package/dist/form/text-of.d.ts +2 -0
- package/dist/form/text-of.js +7 -0
- package/dist/form/validators.d.ts +58 -19
- package/dist/form/validators.js +97 -71
- package/dist/geo/bbox.d.ts +5 -4
- package/dist/geo/bbox.js +69 -48
- package/dist/geo/bounds.d.ts +14 -3
- package/dist/geo/bounds.js +98 -68
- package/dist/geo/constraints.js +88 -93
- package/dist/geo/coordinates.js +31 -23
- package/dist/geo/editor.d.ts +13 -13
- package/dist/geo/editor.js +176 -153
- package/dist/geo/geocoding.d.ts +4 -4
- package/dist/geo/geocoding.js +38 -41
- package/dist/geo/geojson.d.ts +12 -74
- package/dist/geo/geojson.js +52 -40
- package/dist/geo/geometry-math.d.ts +4 -4
- package/dist/geo/geometry-math.js +100 -93
- package/dist/geo/index.d.ts +7 -6
- package/dist/geo/map-layers.d.ts +42 -7
- package/dist/geo/map-layers.js +88 -62
- package/dist/geo/map-style.js +31 -32
- package/dist/geo/messages.js +48 -45
- package/dist/geo/nominatim.js +88 -80
- package/dist/geo/osm-presets.d.ts +4 -3
- package/dist/geo/osm-presets.js +108 -86
- package/dist/geo/osm-tags.js +14 -12
- package/dist/geo/overpass.d.ts +14 -2
- package/dist/geo/overpass.js +83 -90
- package/dist/geo/shape-value.d.ts +9 -5
- package/dist/geo/shape-value.js +169 -146
- package/dist/geo/snap-to-roads.d.ts +3 -3
- package/dist/geo/snap-to-roads.js +55 -50
- package/dist/geo/throttle.js +36 -39
- package/dist/guards.d.ts +37 -0
- package/dist/guards.js +62 -0
- package/dist/i18n/default-messages.js +34 -29
- package/dist/i18n/english-messages.js +187 -189
- package/dist/i18n/i18n.d.ts +2 -2
- package/dist/i18n/i18n.js +47 -60
- package/dist/i18n/inflection.d.ts +34 -0
- package/dist/i18n/inflection.js +82 -0
- package/dist/i18n/messages.d.ts +10 -2
- package/dist/i18n/messages.js +17 -4
- package/dist/i18n/missing-keys.js +66 -62
- package/dist/i18n/translatable.d.ts +16 -1
- package/dist/i18n/translatable.js +40 -47
- package/dist/index.d.ts +71 -24
- package/dist/index.js +40 -288
- package/dist/json/deep-equal.d.ts +8 -0
- package/dist/json/deep-equal.js +24 -0
- package/dist/json/strip-json-comments.js +38 -42
- package/dist/noop.d.ts +2 -0
- package/dist/noop.js +4 -0
- package/dist/notification/error-message.d.ts +2 -0
- package/dist/notification/error-message.js +10 -0
- package/dist/notification/notification-store.d.ts +3 -2
- package/dist/notification/notification-store.js +41 -45
- package/dist/onboarding-tour/placement.js +63 -78
- package/dist/onboarding-tour/tour-controller.js +46 -54
- package/dist/realtime/add-events-for-mutations.js +75 -81
- package/dist/realtime/announce-lock.js +16 -13
- package/dist/realtime/broadcast-channel-transport.js +65 -59
- package/dist/realtime/copy-members.js +22 -14
- package/dist/realtime/in-memory-lock-provider.js +40 -48
- package/dist/realtime/live.js +95 -101
- package/dist/realtime/lock-options.js +16 -16
- package/dist/realtime/lock-session.js +91 -78
- package/dist/realtime/memory-transport.js +25 -25
- package/dist/realtime/presence.js +120 -122
- package/dist/realtime/realtime-data-provider.d.ts +10 -0
- package/dist/realtime/realtime-data-provider.js +45 -42
- package/dist/realtime/reconnect-backoff.js +17 -0
- package/dist/realtime/sse-transport.d.ts +31 -0
- package/dist/realtime/sse-transport.js +235 -0
- package/dist/realtime/status-store.js +23 -28
- package/dist/realtime/topic-hub.js +62 -71
- package/dist/realtime/topics.js +10 -12
- package/dist/realtime/types.js +10 -12
- package/dist/realtime/unread-count.js +21 -21
- package/dist/realtime/websocket-transport.d.ts +32 -0
- package/dist/realtime/websocket-transport.js +306 -0
- package/dist/resource/describe-resource.d.ts +2 -0
- package/dist/resource/describe-resource.js +232 -219
- package/dist/resource/get-at-path.js +29 -14
- package/dist/resource/identifier.d.ts +18 -0
- package/dist/resource/identifier.js +27 -0
- package/dist/resource/record.d.ts +4 -0
- package/dist/resource/record.js +10 -0
- package/dist/resource/register.d.ts +7 -0
- package/dist/resource/registry-store.d.ts +45 -15
- package/dist/resource/registry-store.js +67 -60
- package/dist/resource/registry.d.ts +10 -0
- package/dist/resource/registry.js +8 -4
- package/dist/resource/source-path.d.ts +16 -1
- package/dist/rich-text/rich-text-extras.d.ts +132 -0
- package/dist/rich-text/rich-text-extras.js +179 -0
- package/dist/rich-text/rich-text-image.d.ts +124 -0
- package/dist/rich-text/rich-text-image.js +163 -0
- package/dist/rich-text/rich-text.d.ts +6 -0
- package/dist/rich-text/rich-text.js +125 -88
- package/dist/router/location.d.ts +22 -4
- package/dist/router/location.js +41 -18
- package/dist/router/match.js +47 -39
- package/dist/router/paths.js +40 -34
- package/dist/router/record-from-location.d.ts +1 -1
- package/dist/router/record-from-location.js +25 -17
- package/dist/store/adapters.js +51 -55
- package/dist/store/micro-store.js +57 -59
- package/dist/store/shallow-equal.d.ts +12 -0
- package/dist/store/shallow-equal.js +20 -0
- package/dist/store/store.js +54 -57
- package/package.json +13 -11
|
@@ -1,27 +1,31 @@
|
|
|
1
|
+
// src/auth/access-control.ts
|
|
1
2
|
function matchesAction(granted, action) {
|
|
2
|
-
|
|
3
|
-
|
|
3
|
+
const list = Array.isArray(granted) ? granted : [granted];
|
|
4
|
+
return list.includes("*") || list.includes(action);
|
|
4
5
|
}
|
|
5
6
|
function matchesResource(granted, resource) {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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,25 @@
|
|
|
1
|
+
import { type SkipToken } from "@tanstack/query-core";
|
|
1
2
|
import type { AuthProvider, CanAccessParams, UserIdentity } from "./types.js";
|
|
2
|
-
/**
|
|
3
|
-
|
|
4
|
-
queryKey:
|
|
5
|
-
queryFn: () => Promise<
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
|
|
3
|
+
/** The option shape of the factories that skip with no provider. */
|
|
4
|
+
interface SkippableQueryOptions<TKey, TData> {
|
|
5
|
+
queryKey: TKey;
|
|
6
|
+
queryFn: SkipToken | (() => Promise<TData>);
|
|
7
|
+
}
|
|
8
|
+
/** What `getPermissions` takes: the key slot of {@link permissionsQueryOptions}. */
|
|
9
|
+
type PermissionsParams = Parameters<NonNullable<AuthProvider["getPermissions"]>>[0];
|
|
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
|
+
*/
|
|
15
|
+
export declare function identityQueryOptions(authProvider: AuthProvider | undefined): SkippableQueryOptions<readonly ["auth", "getIdentity"], UserIdentity>;
|
|
16
|
+
export declare function canAccessQueryOptions(authProvider: AuthProvider | undefined, params: CanAccessParams): {
|
|
17
|
+
queryKey: readonly ["auth", "canAccess", CanAccessParams<import("../index.js").ResourceRecord<import("../index.js").Identifier>>];
|
|
9
18
|
queryFn: () => Promise<boolean>;
|
|
10
19
|
};
|
|
11
|
-
export declare function authStateQueryOptions(authProvider: AuthProvider): {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
authenticated: boolean;
|
|
15
|
-
}>;
|
|
16
|
-
};
|
|
20
|
+
export declare function authStateQueryOptions(authProvider: AuthProvider | undefined): SkippableQueryOptions<readonly ["auth", "checkAuth"], {
|
|
21
|
+
authenticated: boolean;
|
|
22
|
+
}>;
|
|
17
23
|
/**
|
|
18
24
|
* Fetch the current user permissions via `authProvider.getPermissions`.
|
|
19
25
|
* No `getPermissions` on the provider = permissions not configured =
|
|
@@ -22,10 +28,7 @@ export declare function authStateQueryOptions(authProvider: AuthProvider): {
|
|
|
22
28
|
* a queryFn resolving `undefined`. Mirrors {@link canAccessQueryOptions}: keyed
|
|
23
29
|
* by the passed `params` so distinct param objects cache independently.
|
|
24
30
|
*/
|
|
25
|
-
export declare function permissionsQueryOptions(authProvider: AuthProvider, params?: unknown):
|
|
26
|
-
queryKey: readonly ["auth", "getPermissions", unknown];
|
|
27
|
-
queryFn: () => Promise<unknown>;
|
|
28
|
-
};
|
|
31
|
+
export declare function permissionsQueryOptions<TPermissions>(authProvider: AuthProvider<TPermissions> | undefined, params?: unknown): SkippableQueryOptions<readonly ["auth", "getPermissions", PermissionsParams], TPermissions | null>;
|
|
29
32
|
/**
|
|
30
33
|
* The triage result of {@link checkError}. Vanilla (react/vue-free) so both
|
|
31
34
|
* adapters share the reject-payload parsing before doing logout/notify/redirect.
|
|
@@ -55,3 +58,4 @@ export interface CheckErrorOutcome {
|
|
|
55
58
|
* redirect; only an auth rejection ever logs the user out.
|
|
56
59
|
*/
|
|
57
60
|
export declare function checkError(authProvider: AuthProvider, error: unknown): Promise<CheckErrorOutcome>;
|
|
61
|
+
export {};
|
|
@@ -1,60 +1,92 @@
|
|
|
1
|
+
import { isRecord } from "../guards.js";
|
|
2
|
+
import { skipToken } from "@tanstack/query-core";
|
|
3
|
+
// src/auth/auth-options.ts
|
|
4
|
+
/** `skipToken` (the query never runs) with no provider, else `read` bound to it. */
|
|
5
|
+
function queryFnFor(authProvider, read) {
|
|
6
|
+
return authProvider ? () => read(authProvider) : skipToken;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Plain option objects, same convention as the data factories (P2). With no
|
|
10
|
+
* provider, `identityQueryOptions`, `authStateQueryOptions` and
|
|
11
|
+
* `permissionsQueryOptions` set `queryFn: skipToken`, so the query never runs.
|
|
12
|
+
*/
|
|
1
13
|
function identityQueryOptions(authProvider) {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
}
|
|
10
|
-
return authProvider.getIdentity();
|
|
11
|
-
}
|
|
12
|
-
};
|
|
14
|
+
return {
|
|
15
|
+
queryKey: ["auth", "getIdentity"],
|
|
16
|
+
queryFn: queryFnFor(authProvider, async (provider) => {
|
|
17
|
+
if (!provider.getIdentity) throw new Error("identityQueryOptions: the authProvider does not implement getIdentity.");
|
|
18
|
+
return provider.getIdentity();
|
|
19
|
+
})
|
|
20
|
+
};
|
|
13
21
|
}
|
|
14
22
|
function canAccessQueryOptions(authProvider, params) {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
23
|
+
return {
|
|
24
|
+
queryKey: [
|
|
25
|
+
"auth",
|
|
26
|
+
"canAccess",
|
|
27
|
+
params
|
|
28
|
+
],
|
|
29
|
+
queryFn: async () => authProvider?.canAccess?.(params) ?? true
|
|
30
|
+
};
|
|
20
31
|
}
|
|
21
32
|
function authStateQueryOptions(authProvider) {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
+
return {
|
|
34
|
+
queryKey: ["auth", "checkAuth"],
|
|
35
|
+
queryFn: queryFnFor(authProvider, async (provider) => {
|
|
36
|
+
try {
|
|
37
|
+
await provider.checkAuth();
|
|
38
|
+
return { authenticated: true };
|
|
39
|
+
} catch {
|
|
40
|
+
return { authenticated: false };
|
|
41
|
+
}
|
|
42
|
+
})
|
|
43
|
+
};
|
|
33
44
|
}
|
|
45
|
+
/**
|
|
46
|
+
* Fetch the current user permissions via `authProvider.getPermissions`.
|
|
47
|
+
* No `getPermissions` on the provider = permissions not configured =
|
|
48
|
+
* resolves `null` (undefined-tolerant; never rejects for that reason). `null`
|
|
49
|
+
* rather than `undefined` is the sentinel because react-query/vue-query forbid
|
|
50
|
+
* a queryFn resolving `undefined`. Mirrors {@link canAccessQueryOptions}: keyed
|
|
51
|
+
* by the passed `params` so distinct param objects cache independently.
|
|
52
|
+
*/
|
|
34
53
|
function permissionsQueryOptions(authProvider, params) {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
54
|
+
return {
|
|
55
|
+
queryKey: [
|
|
56
|
+
"auth",
|
|
57
|
+
"getPermissions",
|
|
58
|
+
params
|
|
59
|
+
],
|
|
60
|
+
queryFn: queryFnFor(authProvider, async (provider) => await provider.getPermissions?.(params) ?? null)
|
|
61
|
+
};
|
|
39
62
|
}
|
|
40
63
|
function readProp(source, key) {
|
|
41
|
-
|
|
64
|
+
return isRecord(source) && key in source ? source[key] : void 0;
|
|
42
65
|
}
|
|
66
|
+
/**
|
|
67
|
+
* Error-triage helper for the data error path. Fail-safe: runs
|
|
68
|
+
* `authProvider.checkError(error)` and NEVER throws.
|
|
69
|
+
*
|
|
70
|
+
* - checkError RESOLVES -> ordinary data error -> `{ logout: false }`.
|
|
71
|
+
* - checkError REJECTS -> invalid session -> `{ logout: true, ... }` carrying
|
|
72
|
+
* `redirectTo` / `logoutUser` / `message` hints from the reject payload (or,
|
|
73
|
+
* for `redirectTo`, the original error). Adapters do the logout + notify +
|
|
74
|
+
* redirect; only an auth rejection ever logs the user out.
|
|
75
|
+
*/
|
|
43
76
|
async function checkError(authProvider, error) {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
77
|
+
try {
|
|
78
|
+
await authProvider.checkError(error);
|
|
79
|
+
return {
|
|
80
|
+
logout: false,
|
|
81
|
+
logoutUser: false
|
|
82
|
+
};
|
|
83
|
+
} catch (rejection) {
|
|
84
|
+
return {
|
|
85
|
+
logout: true,
|
|
86
|
+
logoutUser: readProp(rejection, "logoutUser") ?? true,
|
|
87
|
+
redirectTo: readProp(rejection, "redirectTo") ?? readProp(error, "redirectTo"),
|
|
88
|
+
message: readProp(rejection, "message")
|
|
89
|
+
};
|
|
90
|
+
}
|
|
53
91
|
}
|
|
54
|
-
export {
|
|
55
|
-
authStateQueryOptions,
|
|
56
|
-
canAccessQueryOptions,
|
|
57
|
-
checkError,
|
|
58
|
-
identityQueryOptions,
|
|
59
|
-
permissionsQueryOptions
|
|
60
|
-
};
|
|
92
|
+
export { 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 };
|
package/dist/auth/types.d.ts
CHANGED
|
@@ -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
|
-
|
|
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?:
|
|
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
|
-
|
|
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,60 @@
|
|
|
1
|
+
import type { StandardSchemaV1 } from "@tanstack/form-core";
|
|
2
|
+
import { type FieldDescriptor } from "../resource/describe-resource.js";
|
|
3
|
+
export type Attrs = Record<string, unknown>;
|
|
4
|
+
/** The shape of tiptap JSON, with no tiptap import. */
|
|
5
|
+
export interface BlockDocNode {
|
|
6
|
+
type?: string;
|
|
7
|
+
attrs?: Attrs;
|
|
8
|
+
content?: BlockDocNode[];
|
|
9
|
+
marks?: {
|
|
10
|
+
type: string;
|
|
11
|
+
attrs?: Attrs;
|
|
12
|
+
}[];
|
|
13
|
+
text?: string;
|
|
14
|
+
}
|
|
15
|
+
type BlockSchema<A extends Attrs> = StandardSchemaV1<unknown, A>;
|
|
16
|
+
/**
|
|
17
|
+
* One block a block editor can insert. `schema` is a Zod object (3 or 4) whose
|
|
18
|
+
* output is the block's attrs. Adapters extend this with their render types.
|
|
19
|
+
*/
|
|
20
|
+
export interface BlockSpec<A extends Attrs = Attrs> {
|
|
21
|
+
name: string;
|
|
22
|
+
label: string;
|
|
23
|
+
group: string;
|
|
24
|
+
keywords?: string[];
|
|
25
|
+
description?: string;
|
|
26
|
+
schema: BlockSchema<A>;
|
|
27
|
+
content?: string;
|
|
28
|
+
atom?: boolean;
|
|
29
|
+
}
|
|
30
|
+
export interface BlockRegistry<B extends BlockSpec = BlockSpec> {
|
|
31
|
+
get(name: string): B | undefined;
|
|
32
|
+
list(): B[];
|
|
33
|
+
names(): string[];
|
|
34
|
+
/** Group names in the order a block first names them. */
|
|
35
|
+
groups(): string[];
|
|
36
|
+
}
|
|
37
|
+
export declare const unknownBlockType = "unknownBlock";
|
|
38
|
+
export declare function createBlockRegistry<B extends BlockSpec>(blocks: readonly B[]): BlockRegistry<B>;
|
|
39
|
+
/** Each key's `safeParse(undefined)` result when it succeeds, else `null`. */
|
|
40
|
+
export declare function getBlockDefaultAttrs<A extends Attrs>(schema: BlockSchema<A>): {
|
|
41
|
+
[K in keyof A]: A[K] | null;
|
|
42
|
+
};
|
|
43
|
+
/** One descriptor per attr, for an adapter to build the block's config form from. */
|
|
44
|
+
export declare function describeBlockAttrs(schema: BlockSchema<Attrs>): FieldDescriptor[];
|
|
45
|
+
export declare function matchBlocks<B extends BlockSpec>(blocks: readonly B[], query: string): B[];
|
|
46
|
+
/**
|
|
47
|
+
* Wraps each node of an unknown type in an `unknownBlock`, so the editor keeps
|
|
48
|
+
* it. `canLoad` also catches what a type name cannot: an unknown mark, or an
|
|
49
|
+
* unknown node inside a paragraph. A top-level block it rejects is wrapped
|
|
50
|
+
* whole, from the document as stored.
|
|
51
|
+
*/
|
|
52
|
+
export declare function wrapUnknownBlocks(doc: BlockDocNode, known: ReadonlySet<string>, canLoad?: (block: BlockDocNode) => boolean): BlockDocNode;
|
|
53
|
+
export declare function unwrapUnknownBlocks(doc: BlockDocNode): BlockDocNode;
|
|
54
|
+
/** A stored value an editor can load: an object whose `type` is `"doc"`. */
|
|
55
|
+
export declare const isBlockDoc: (value: unknown) => value is BlockDocNode;
|
|
56
|
+
export declare const emptyBlockDocument: () => BlockDocNode;
|
|
57
|
+
export declare function isEmptyBlockDocument(value: BlockDocNode | null | undefined): boolean;
|
|
58
|
+
/** An embed URL for a YouTube or Vimeo link, built from the checked id alone. */
|
|
59
|
+
export declare function toEmbedSrc(url: string): string | null;
|
|
60
|
+
export {};
|
|
@@ -0,0 +1,143 @@
|
|
|
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
|
+
var unknownBlockType = "unknownBlock";
|
|
5
|
+
var builtInNodes = /* @__PURE__ */ new Set([
|
|
6
|
+
"doc",
|
|
7
|
+
"text",
|
|
8
|
+
"paragraph",
|
|
9
|
+
"heading",
|
|
10
|
+
"blockquote",
|
|
11
|
+
"bulletList",
|
|
12
|
+
"orderedList",
|
|
13
|
+
"listItem",
|
|
14
|
+
"codeBlock",
|
|
15
|
+
"horizontalRule",
|
|
16
|
+
"hardBreak",
|
|
17
|
+
unknownBlockType
|
|
18
|
+
]);
|
|
19
|
+
function createBlockRegistry(blocks) {
|
|
20
|
+
const byName = /* @__PURE__ */ new Map();
|
|
21
|
+
const groups = /* @__PURE__ */ new Set();
|
|
22
|
+
for (const block of blocks) {
|
|
23
|
+
if (builtInNodes.has(block.name)) throw new Error(`block-editor: "${block.name}" is a built-in node`);
|
|
24
|
+
if (byName.has(block.name)) throw new Error(`block-editor: duplicate block name "${block.name}"`);
|
|
25
|
+
byName.set(block.name, block);
|
|
26
|
+
groups.add(block.group);
|
|
27
|
+
}
|
|
28
|
+
return {
|
|
29
|
+
get: (name) => byName.get(name),
|
|
30
|
+
list: () => [...byName.values()],
|
|
31
|
+
names: () => [...byName.keys()],
|
|
32
|
+
groups: () => [...groups]
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
var isObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
36
|
+
var isParsable = (value) => isObject(value) && typeof value.safeParse === "function";
|
|
37
|
+
/** Zod 3 and Zod 4 objects both expose `shape`. */
|
|
38
|
+
function shapeOf(schema) {
|
|
39
|
+
const shape = isObject(schema) ? schema.shape : void 0;
|
|
40
|
+
if (!isObject(shape)) throw new Error("block schema must be a z.object");
|
|
41
|
+
return shape;
|
|
42
|
+
}
|
|
43
|
+
/** Each key's `safeParse(undefined)` result when it succeeds, else `null`. */
|
|
44
|
+
function getBlockDefaultAttrs(schema) {
|
|
45
|
+
const shape = shapeOf(schema);
|
|
46
|
+
const defaults = {};
|
|
47
|
+
for (const key of Object.keys(shape)) {
|
|
48
|
+
const member = shape[key];
|
|
49
|
+
const result = isParsable(member) ? member.safeParse(void 0) : void 0;
|
|
50
|
+
defaults[key] = result?.success ? result.data : null;
|
|
51
|
+
}
|
|
52
|
+
return defaults;
|
|
53
|
+
}
|
|
54
|
+
/** One descriptor per attr, for an adapter to build the block's config form from. */
|
|
55
|
+
function describeBlockAttrs(schema) {
|
|
56
|
+
const shape = shapeOf(schema);
|
|
57
|
+
return Object.keys(shape).map((key) => describeField(key, shape[key], void 0));
|
|
58
|
+
}
|
|
59
|
+
function matchBlocks(blocks, query) {
|
|
60
|
+
return blocks.filter((block) => columnLabelMatches(block.label, query) || (block.keywords ?? []).some((keyword) => columnLabelMatches(keyword, query)) || block.description !== void 0 && columnLabelMatches(block.description, query));
|
|
61
|
+
}
|
|
62
|
+
var transparentTypes = /* @__PURE__ */ new Set(["doc", "text"]);
|
|
63
|
+
function mapChildren(node, visit) {
|
|
64
|
+
return node.content === void 0 ? node : {
|
|
65
|
+
...node,
|
|
66
|
+
content: node.content.map(visit)
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Wraps each node of an unknown type in an `unknownBlock`, so the editor keeps
|
|
71
|
+
* it. `canLoad` also catches what a type name cannot: an unknown mark, or an
|
|
72
|
+
* unknown node inside a paragraph. A top-level block it rejects is wrapped
|
|
73
|
+
* whole, from the document as stored.
|
|
74
|
+
*/
|
|
75
|
+
function wrapUnknownBlocks(doc, known, canLoad) {
|
|
76
|
+
const wrapNode = (node) => {
|
|
77
|
+
if (node.type !== void 0 && (transparentTypes.has(node.type) || known.has(node.type))) return mapChildren(node, wrapNode);
|
|
78
|
+
return {
|
|
79
|
+
type: unknownBlockType,
|
|
80
|
+
attrs: { payload: node }
|
|
81
|
+
};
|
|
82
|
+
};
|
|
83
|
+
const wrapped = mapChildren(doc, wrapNode);
|
|
84
|
+
if (canLoad === void 0 || wrapped.content === void 0) return wrapped;
|
|
85
|
+
return {
|
|
86
|
+
...wrapped,
|
|
87
|
+
content: wrapped.content.map((block, index) => canLoad(block) ? block : {
|
|
88
|
+
type: unknownBlockType,
|
|
89
|
+
attrs: { payload: doc.content?.[index] ?? block }
|
|
90
|
+
})
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
var isDocNode = (value) => isObject(value);
|
|
94
|
+
function unwrapUnknownBlocks(doc) {
|
|
95
|
+
const unwrapNode = (node) => {
|
|
96
|
+
const payload = node.attrs?.payload;
|
|
97
|
+
if (node.type === "unknownBlock" && isDocNode(payload)) return payload;
|
|
98
|
+
return mapChildren(node, unwrapNode);
|
|
99
|
+
};
|
|
100
|
+
return mapChildren(doc, unwrapNode);
|
|
101
|
+
}
|
|
102
|
+
/** A stored value an editor can load: an object whose `type` is `"doc"`. */
|
|
103
|
+
var isBlockDoc = (value) => isObject(value) && value.type === "doc";
|
|
104
|
+
var emptyBlockDocument = () => ({
|
|
105
|
+
type: "doc",
|
|
106
|
+
content: [{ type: "paragraph" }]
|
|
107
|
+
});
|
|
108
|
+
function isEmptyBlockDocument(value) {
|
|
109
|
+
if (value === null || value === void 0) return true;
|
|
110
|
+
return (value.content ?? []).every((node) => node.type === "paragraph" && (node.content === void 0 || node.content.length === 0));
|
|
111
|
+
}
|
|
112
|
+
var youTubeHosts = /* @__PURE__ */ new Set([
|
|
113
|
+
"youtube.com",
|
|
114
|
+
"www.youtube.com",
|
|
115
|
+
"m.youtube.com"
|
|
116
|
+
]);
|
|
117
|
+
var youTubeId = /^[A-Za-z0-9_-]{11}$/;
|
|
118
|
+
var vimeoId = /^\d+$/;
|
|
119
|
+
/** An embed URL for a YouTube or Vimeo link, built from the checked id alone. */
|
|
120
|
+
function toEmbedSrc(url) {
|
|
121
|
+
let parsed;
|
|
122
|
+
try {
|
|
123
|
+
parsed = new URL(url);
|
|
124
|
+
} catch {
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
if (parsed.protocol !== "https:" && parsed.protocol !== "http:") return null;
|
|
128
|
+
const { hostname, pathname } = parsed;
|
|
129
|
+
if (youTubeHosts.has(hostname) && pathname === "/watch") {
|
|
130
|
+
const id = parsed.searchParams.get("v");
|
|
131
|
+
return id !== null && youTubeId.test(id) ? `https://www.youtube-nocookie.com/embed/${id}` : null;
|
|
132
|
+
}
|
|
133
|
+
if (hostname === "youtu.be") {
|
|
134
|
+
const id = pathname.slice(1);
|
|
135
|
+
return youTubeId.test(id) ? `https://www.youtube-nocookie.com/embed/${id}` : null;
|
|
136
|
+
}
|
|
137
|
+
if (hostname === "vimeo.com" || hostname === "www.vimeo.com") {
|
|
138
|
+
const id = pathname.slice(1);
|
|
139
|
+
return vimeoId.test(id) ? `https://player.vimeo.com/video/${id}` : null;
|
|
140
|
+
}
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
export { 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 {};
|