@checkstack/status-page-backend 0.1.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/CHANGELOG.md +62 -0
- package/drizzle/0000_late_alex_wilder.sql +14 -0
- package/drizzle/meta/0000_snapshot.json +113 -0
- package/drizzle/meta/_journal.json +13 -0
- package/drizzle.config.ts +7 -0
- package/package.json +34 -0
- package/src/content-widgets.ts +94 -0
- package/src/hooks.ts +17 -0
- package/src/index.ts +98 -0
- package/src/router.ts +106 -0
- package/src/schema.ts +48 -0
- package/src/service.test.ts +276 -0
- package/src/service.ts +337 -0
- package/src/user-client.ts +39 -0
- package/src/widget-registry.ts +120 -0
- package/tsconfig.json +23 -0
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { createExtensionPoint, type RpcClient } from "@checkstack/backend-api";
|
|
3
|
+
import type { PluginMetadata } from "@checkstack/common";
|
|
4
|
+
import type { WidgetBindingKind } from "@checkstack/status-page-common";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Extensible registry of status-page WIDGET TYPES. Mirrors the automation action
|
|
8
|
+
* registry: a widget type carries a config schema (what the builder edits), a
|
|
9
|
+
* public DTO schema (the field allow-list the public surface receives), a
|
|
10
|
+
* declaration of which resources it binds (for edit-time authz + publish audit),
|
|
11
|
+
* and a `resolvePublic` that turns a config into the public DTO.
|
|
12
|
+
*
|
|
13
|
+
* SECURITY: `resolvePublic` runs with the TRUSTED service `rpcClient` (so it can
|
|
14
|
+
* read the bound resources regardless of the anonymous caller's grants), but it
|
|
15
|
+
* MUST emit only its `dtoSchema` shape — never the source resource's internal
|
|
16
|
+
* config, ids, or `createdBy`. The service validates the returned value against
|
|
17
|
+
* `dtoSchema` before it leaves the backend, so a resolver bug fails closed.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
export interface WidgetResolveContext {
|
|
21
|
+
/** Trusted service RPC client for reading bound resources' public-safe data. */
|
|
22
|
+
rpcClient: RpcClient;
|
|
23
|
+
/**
|
|
24
|
+
* Per-page-resolve memoization. A resolver wraps an expensive read (e.g. the
|
|
25
|
+
* full catalog, which has no fetch-by-ids endpoint) in `cache(key, loader)` so
|
|
26
|
+
* many widgets on one page share a single fetch instead of O(widgets) scans
|
|
27
|
+
* per public hit. Keys are resolver-chosen and scoped to a single resolve.
|
|
28
|
+
*/
|
|
29
|
+
cache<T>(key: string, loader: () => Promise<T>): Promise<T>;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** A resource a widget binds to, for edit-time access checks + publish audit. */
|
|
33
|
+
export interface BoundResource {
|
|
34
|
+
/** Qualified resource type, e.g. "catalog.system". */
|
|
35
|
+
resourceType: string;
|
|
36
|
+
resourceId: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface WidgetTypeDefinition {
|
|
40
|
+
/** Local id (qualified with the registering plugin id, e.g. "banner"). */
|
|
41
|
+
id: string;
|
|
42
|
+
displayName: string;
|
|
43
|
+
description: string;
|
|
44
|
+
category: string;
|
|
45
|
+
binding: WidgetBindingKind;
|
|
46
|
+
/**
|
|
47
|
+
* Validates + defaults the stored config. The service parses with this BEFORE
|
|
48
|
+
* `resolvePublic`/`boundResources`, but each implementation also re-parses
|
|
49
|
+
* (`config: unknown`) so it is independently type-safe and cast-free.
|
|
50
|
+
*/
|
|
51
|
+
configSchema: z.ZodType;
|
|
52
|
+
/** The public output shape (the allow-list). */
|
|
53
|
+
dtoSchema: z.ZodType;
|
|
54
|
+
/** Resources this config references (drives edit-time authz + audit). */
|
|
55
|
+
boundResources(config: unknown): BoundResource[];
|
|
56
|
+
/** Resolve the public DTO from a config using trusted reads. */
|
|
57
|
+
resolvePublic(args: {
|
|
58
|
+
config: unknown;
|
|
59
|
+
ctx: WidgetResolveContext;
|
|
60
|
+
}): Promise<unknown>;
|
|
61
|
+
/**
|
|
62
|
+
* Publish-time gate: throw if the EDITOR (the `userClient`, scoped to the
|
|
63
|
+
* caller) cannot read every resource this config binds — "you cannot publish
|
|
64
|
+
* what you cannot see". REQUIRED for any widget whose `boundResources` is
|
|
65
|
+
* non-empty: the platform fails the publish CLOSED for a binding widget that
|
|
66
|
+
* does not provide this (it owns the resource type, so it owns the check).
|
|
67
|
+
* Content widgets (no bindings) omit it.
|
|
68
|
+
*/
|
|
69
|
+
assertBindingsReadable?(args: {
|
|
70
|
+
userClient: RpcClient;
|
|
71
|
+
config: unknown;
|
|
72
|
+
}): Promise<void>;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export interface RegisteredWidgetType extends WidgetTypeDefinition {
|
|
76
|
+
qualifiedId: string;
|
|
77
|
+
ownerPluginId: string;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export interface WidgetTypeRegistry {
|
|
81
|
+
register(
|
|
82
|
+
definition: WidgetTypeDefinition,
|
|
83
|
+
pluginMetadata: PluginMetadata,
|
|
84
|
+
): void;
|
|
85
|
+
get(qualifiedId: string): RegisteredWidgetType | undefined;
|
|
86
|
+
list(): RegisteredWidgetType[];
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function createWidgetTypeRegistry(): WidgetTypeRegistry {
|
|
90
|
+
const types = new Map<string, RegisteredWidgetType>();
|
|
91
|
+
return {
|
|
92
|
+
register(definition, pluginMetadata) {
|
|
93
|
+
const qualifiedId = `${pluginMetadata.pluginId}.${definition.id}`;
|
|
94
|
+
types.set(qualifiedId, {
|
|
95
|
+
...definition,
|
|
96
|
+
qualifiedId,
|
|
97
|
+
ownerPluginId: pluginMetadata.pluginId,
|
|
98
|
+
});
|
|
99
|
+
},
|
|
100
|
+
get(qualifiedId) {
|
|
101
|
+
return types.get(qualifiedId);
|
|
102
|
+
},
|
|
103
|
+
list() {
|
|
104
|
+
return [...types.values()];
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Extension point so any plugin can contribute a widget type. */
|
|
110
|
+
export interface StatusWidgetTypeExtensionPoint {
|
|
111
|
+
registerWidgetType(
|
|
112
|
+
definition: WidgetTypeDefinition,
|
|
113
|
+
pluginMetadata: PluginMetadata,
|
|
114
|
+
): void;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export const statusWidgetTypeExtensionPoint =
|
|
118
|
+
createExtensionPoint<StatusWidgetTypeExtensionPoint>(
|
|
119
|
+
"statuspage.widgetTypeExtensionPoint",
|
|
120
|
+
);
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "@checkstack/tsconfig/backend.json",
|
|
3
|
+
"include": [
|
|
4
|
+
"src"
|
|
5
|
+
],
|
|
6
|
+
"references": [
|
|
7
|
+
{
|
|
8
|
+
"path": "../backend-api"
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"path": "../common"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"path": "../drizzle-helper"
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"path": "../status-page-common"
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"path": "../test-utils-backend"
|
|
21
|
+
}
|
|
22
|
+
]
|
|
23
|
+
}
|