@checkstack/dependency-common 0.2.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 +33 -0
- package/package.json +30 -0
- package/src/access.ts +36 -0
- package/src/index.ts +61 -0
- package/src/plugin-metadata.ts +9 -0
- package/src/routes.ts +29 -0
- package/src/rpc-contract.ts +128 -0
- package/src/schemas.ts +148 -0
- package/tests/schemas.test.ts +96 -0
- package/tsconfig.json +6 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# @checkstack/dependency-common
|
|
2
|
+
|
|
3
|
+
## 0.2.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 3f36a64: Add System Dependencies plugin
|
|
8
|
+
|
|
9
|
+
Introduces the system dependencies feature with three new core plugins and
|
|
10
|
+
extends the catalog with a new SystemEditorSlot extension point.
|
|
11
|
+
|
|
12
|
+
**New plugins:**
|
|
13
|
+
|
|
14
|
+
- **dependency-common**: Shared Zod schemas, RPC contract with resource-level access control, signal definitions, and routes
|
|
15
|
+
- **dependency-backend**: Drizzle schema, DependencyService with cycle detection, WarningEvaluationService with transitive impact matrix, RPC router with signal broadcasting, and per-user canvas node position persistence
|
|
16
|
+
- **dependency-frontend**: DependencyBadge (dashboard), DependencyAlert (system details), DependencyEditor (system editor dialog), and interactive DependencyMapPage (React Flow canvas)
|
|
17
|
+
|
|
18
|
+
**Catalog extensions:**
|
|
19
|
+
|
|
20
|
+
- **catalog-common**: New `SystemEditorSlot` for plugin-injected sections in the system editor dialog
|
|
21
|
+
- **catalog-frontend**: `SystemEditor` renders the slot after TeamAccessEditor for existing systems
|
|
22
|
+
|
|
23
|
+
**Key capabilities:**
|
|
24
|
+
|
|
25
|
+
- Directional dependency edges between systems (source depends on target)
|
|
26
|
+
- Three impact types: informational, degraded, critical
|
|
27
|
+
- Transitive multi-hop warning propagation with toggle switch
|
|
28
|
+
- Cycle detection at creation time with graphical chain visualization
|
|
29
|
+
- Health check-level dependency rules
|
|
30
|
+
- Interactive dependency map with drag-to-connect, edge click editor, and auto-saving node positions
|
|
31
|
+
- Inline editing of dependencies in both the system editor and the map canvas
|
|
32
|
+
- Team-based resource-level access control on all mutation endpoints
|
|
33
|
+
- Realtime signal-driven UI updates
|
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@checkstack/dependency-common",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"exports": {
|
|
6
|
+
".": {
|
|
7
|
+
"import": "./src/index.ts"
|
|
8
|
+
}
|
|
9
|
+
},
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"@checkstack/common": "0.6.4",
|
|
12
|
+
"@checkstack/frontend-api": "0.3.8",
|
|
13
|
+
"@checkstack/signal-common": "0.1.8",
|
|
14
|
+
"@orpc/contract": "^1.13.14",
|
|
15
|
+
"zod": "^4.2.1"
|
|
16
|
+
},
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"typescript": "^5.7.2",
|
|
19
|
+
"@checkstack/tsconfig": "0.0.4",
|
|
20
|
+
"@checkstack/scripts": "0.1.2"
|
|
21
|
+
},
|
|
22
|
+
"scripts": {
|
|
23
|
+
"typecheck": "tsc --noEmit",
|
|
24
|
+
"lint": "bun run lint:code",
|
|
25
|
+
"lint:code": "eslint . --max-warnings 0"
|
|
26
|
+
},
|
|
27
|
+
"checkstack": {
|
|
28
|
+
"type": "common"
|
|
29
|
+
}
|
|
30
|
+
}
|
package/src/access.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { accessPair } from "@checkstack/common";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Access rules for the Dependency plugin.
|
|
5
|
+
*/
|
|
6
|
+
export const dependencyAccess = {
|
|
7
|
+
/**
|
|
8
|
+
* Dependency access with both read and manage levels.
|
|
9
|
+
* Read is public by default so all users can see dependency warnings.
|
|
10
|
+
*/
|
|
11
|
+
dependency: accessPair(
|
|
12
|
+
"dependency",
|
|
13
|
+
{
|
|
14
|
+
read: {
|
|
15
|
+
description: "View system dependencies and dependency warnings",
|
|
16
|
+
isDefault: true,
|
|
17
|
+
isPublic: true,
|
|
18
|
+
},
|
|
19
|
+
manage: {
|
|
20
|
+
description:
|
|
21
|
+
"Manage system dependencies - create, edit, and delete dependency relationships",
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
idParam: "systemId",
|
|
26
|
+
},
|
|
27
|
+
),
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* All access rules for registration with the plugin system.
|
|
32
|
+
*/
|
|
33
|
+
export const dependencyAccessRules = [
|
|
34
|
+
dependencyAccess.dependency.read,
|
|
35
|
+
dependencyAccess.dependency.manage,
|
|
36
|
+
];
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
export { dependencyAccess, dependencyAccessRules } from "./access";
|
|
2
|
+
export {
|
|
3
|
+
dependencyContract,
|
|
4
|
+
DependencyApi,
|
|
5
|
+
type DependencyContract,
|
|
6
|
+
} from "./rpc-contract";
|
|
7
|
+
export {
|
|
8
|
+
ImpactTypeSchema,
|
|
9
|
+
DerivedStateSchema,
|
|
10
|
+
HealthCheckRuleSchema,
|
|
11
|
+
DependencySchema,
|
|
12
|
+
AffectedUpstreamSchema,
|
|
13
|
+
DependencyWarningSchema,
|
|
14
|
+
CreateHealthCheckRuleInputSchema,
|
|
15
|
+
CreateDependencyInputSchema,
|
|
16
|
+
UpdateDependencyInputSchema,
|
|
17
|
+
NodePositionSchema,
|
|
18
|
+
type ImpactType,
|
|
19
|
+
type DerivedState,
|
|
20
|
+
type HealthCheckRule,
|
|
21
|
+
type Dependency,
|
|
22
|
+
type AffectedUpstream,
|
|
23
|
+
type DependencyWarning,
|
|
24
|
+
type CreateDependencyInput,
|
|
25
|
+
type UpdateDependencyInput,
|
|
26
|
+
type NodePosition,
|
|
27
|
+
} from "./schemas";
|
|
28
|
+
export * from "./plugin-metadata";
|
|
29
|
+
export { dependencyRoutes } from "./routes";
|
|
30
|
+
|
|
31
|
+
// =============================================================================
|
|
32
|
+
// REALTIME SIGNALS
|
|
33
|
+
// =============================================================================
|
|
34
|
+
|
|
35
|
+
import { createSignal } from "@checkstack/signal-common";
|
|
36
|
+
import { z } from "zod";
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Broadcast when dependency definitions change (created, updated, deleted).
|
|
40
|
+
* Frontend components can refetch the dependency graph.
|
|
41
|
+
*/
|
|
42
|
+
export const DEPENDENCY_CHANGED = createSignal(
|
|
43
|
+
"dependency.changed",
|
|
44
|
+
z.object({
|
|
45
|
+
dependencyId: z.string(),
|
|
46
|
+
sourceSystemId: z.string(),
|
|
47
|
+
targetSystemId: z.string(),
|
|
48
|
+
action: z.enum(["created", "updated", "deleted"]),
|
|
49
|
+
}),
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Broadcast when computed dependency warnings change for one or more systems.
|
|
54
|
+
* Badge components listen to this to refresh without polling.
|
|
55
|
+
*/
|
|
56
|
+
export const DEPENDENCY_WARNINGS_CHANGED = createSignal(
|
|
57
|
+
"dependency.warnings.changed",
|
|
58
|
+
z.object({
|
|
59
|
+
affectedSystemIds: z.array(z.string()),
|
|
60
|
+
}),
|
|
61
|
+
);
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { definePluginMetadata } from "@checkstack/common";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Plugin metadata for the dependency plugin.
|
|
5
|
+
* Exported from the common package so both backend and frontend can reference it.
|
|
6
|
+
*/
|
|
7
|
+
export const pluginMetadata = definePluginMetadata({
|
|
8
|
+
pluginId: "dependency",
|
|
9
|
+
});
|
package/src/routes.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { createRoutes } from "@checkstack/common";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Route definitions for the dependency plugin.
|
|
5
|
+
* The Dependency Map page is registered under the Catalog namespace.
|
|
6
|
+
*
|
|
7
|
+
* @example Frontend plugin usage
|
|
8
|
+
* ```tsx
|
|
9
|
+
* import { dependencyRoutes } from "@checkstack/dependency-common";
|
|
10
|
+
*
|
|
11
|
+
* createFrontendPlugin({
|
|
12
|
+
* routes: [
|
|
13
|
+
* { route: dependencyRoutes.routes.map, element: <DependencyMapPage /> },
|
|
14
|
+
* ],
|
|
15
|
+
* });
|
|
16
|
+
* ```
|
|
17
|
+
*
|
|
18
|
+
* @example Link generation
|
|
19
|
+
* ```tsx
|
|
20
|
+
* import { dependencyRoutes } from "@checkstack/dependency-common";
|
|
21
|
+
* import { resolveRoute } from "@checkstack/common";
|
|
22
|
+
*
|
|
23
|
+
* const mapPath = resolveRoute(dependencyRoutes.routes.map);
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export const dependencyRoutes = createRoutes("dependency", {
|
|
27
|
+
map: "/map",
|
|
28
|
+
systemDependencies: "/system/:systemId",
|
|
29
|
+
});
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { createClientDefinition, proc } from "@checkstack/common";
|
|
3
|
+
import { dependencyAccess } from "./access";
|
|
4
|
+
import { pluginMetadata } from "./plugin-metadata";
|
|
5
|
+
import {
|
|
6
|
+
DependencySchema,
|
|
7
|
+
DependencyWarningSchema,
|
|
8
|
+
CreateDependencyInputSchema,
|
|
9
|
+
UpdateDependencyInputSchema,
|
|
10
|
+
NodePositionSchema,
|
|
11
|
+
} from "./schemas";
|
|
12
|
+
|
|
13
|
+
export const dependencyContract = {
|
|
14
|
+
// ==========================================================================
|
|
15
|
+
// READ ENDPOINTS (public - accessible by anyone with read access)
|
|
16
|
+
// ==========================================================================
|
|
17
|
+
|
|
18
|
+
/** Get all dependencies for a system (both directions) */
|
|
19
|
+
getDependencies: proc({
|
|
20
|
+
operationType: "query",
|
|
21
|
+
userType: "public",
|
|
22
|
+
access: [dependencyAccess.dependency.read],
|
|
23
|
+
instanceAccess: { idParam: "systemId" },
|
|
24
|
+
})
|
|
25
|
+
.input(
|
|
26
|
+
z.object({
|
|
27
|
+
systemId: z.string(),
|
|
28
|
+
direction: z
|
|
29
|
+
.enum(["upstream", "downstream", "both"])
|
|
30
|
+
.optional()
|
|
31
|
+
.default("both"),
|
|
32
|
+
}),
|
|
33
|
+
)
|
|
34
|
+
.output(z.object({ dependencies: z.array(DependencySchema) })),
|
|
35
|
+
|
|
36
|
+
/** Get the full dependency graph (all dependencies, for the canvas) */
|
|
37
|
+
getAllDependencies: proc({
|
|
38
|
+
operationType: "query",
|
|
39
|
+
userType: "public",
|
|
40
|
+
access: [dependencyAccess.dependency.read],
|
|
41
|
+
}).output(z.object({ dependencies: z.array(DependencySchema) })),
|
|
42
|
+
|
|
43
|
+
/** Bulk-fetch derived warnings for multiple systems (for dashboard badges) */
|
|
44
|
+
getWarnings: proc({
|
|
45
|
+
operationType: "query",
|
|
46
|
+
userType: "public",
|
|
47
|
+
access: [dependencyAccess.dependency.read],
|
|
48
|
+
})
|
|
49
|
+
.input(z.object({ systemIds: z.array(z.string()) }))
|
|
50
|
+
.output(
|
|
51
|
+
z.object({
|
|
52
|
+
warnings: z.record(z.string(), DependencyWarningSchema),
|
|
53
|
+
}),
|
|
54
|
+
),
|
|
55
|
+
|
|
56
|
+
/** Get dependency warnings for a single system */
|
|
57
|
+
getWarningsForSystem: proc({
|
|
58
|
+
operationType: "query",
|
|
59
|
+
userType: "public",
|
|
60
|
+
access: [dependencyAccess.dependency.read],
|
|
61
|
+
})
|
|
62
|
+
.input(z.object({ systemId: z.string() }))
|
|
63
|
+
.output(DependencyWarningSchema.nullable()),
|
|
64
|
+
|
|
65
|
+
// ==========================================================================
|
|
66
|
+
// MANAGEMENT ENDPOINTS (authenticated with manage access)
|
|
67
|
+
// ==========================================================================
|
|
68
|
+
|
|
69
|
+
/** Create a new dependency with cycle detection */
|
|
70
|
+
createDependency: proc({
|
|
71
|
+
operationType: "mutation",
|
|
72
|
+
userType: "authenticated",
|
|
73
|
+
access: [dependencyAccess.dependency.manage],
|
|
74
|
+
instanceAccess: { idParam: "sourceSystemId" },
|
|
75
|
+
})
|
|
76
|
+
.input(CreateDependencyInputSchema)
|
|
77
|
+
.output(DependencySchema),
|
|
78
|
+
|
|
79
|
+
/** Update an existing dependency */
|
|
80
|
+
updateDependency: proc({
|
|
81
|
+
operationType: "mutation",
|
|
82
|
+
userType: "authenticated",
|
|
83
|
+
access: [dependencyAccess.dependency.manage],
|
|
84
|
+
instanceAccess: { idParam: "systemId" },
|
|
85
|
+
})
|
|
86
|
+
.input(UpdateDependencyInputSchema)
|
|
87
|
+
.output(DependencySchema),
|
|
88
|
+
|
|
89
|
+
/** Delete a dependency */
|
|
90
|
+
deleteDependency: proc({
|
|
91
|
+
operationType: "mutation",
|
|
92
|
+
userType: "authenticated",
|
|
93
|
+
access: [dependencyAccess.dependency.manage],
|
|
94
|
+
instanceAccess: { idParam: "systemId" },
|
|
95
|
+
})
|
|
96
|
+
.input(z.object({ id: z.string(), systemId: z.string() }))
|
|
97
|
+
.output(z.object({ success: z.boolean() })),
|
|
98
|
+
|
|
99
|
+
// ==========================================================================
|
|
100
|
+
// NODE POSITIONS (authenticated - per-user canvas layout persistence)
|
|
101
|
+
// ==========================================================================
|
|
102
|
+
|
|
103
|
+
/** Get saved node positions for the dependency map canvas */
|
|
104
|
+
getNodePositions: proc({
|
|
105
|
+
operationType: "query",
|
|
106
|
+
userType: "user",
|
|
107
|
+
access: [dependencyAccess.dependency.read],
|
|
108
|
+
}).output(z.object({ positions: z.array(NodePositionSchema) })),
|
|
109
|
+
|
|
110
|
+
/** Save node positions for the dependency map canvas */
|
|
111
|
+
saveNodePositions: proc({
|
|
112
|
+
operationType: "mutation",
|
|
113
|
+
userType: "user",
|
|
114
|
+
access: [dependencyAccess.dependency.read],
|
|
115
|
+
})
|
|
116
|
+
.input(z.object({ positions: z.array(NodePositionSchema) }))
|
|
117
|
+
.output(z.object({ success: z.boolean() })),
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
// Export contract type
|
|
121
|
+
export type DependencyContract = typeof dependencyContract;
|
|
122
|
+
|
|
123
|
+
// Export client definition for type-safe forPlugin usage
|
|
124
|
+
// Use: const client = rpcApi.forPlugin(DependencyApi);
|
|
125
|
+
export const DependencyApi = createClientDefinition(
|
|
126
|
+
dependencyContract,
|
|
127
|
+
pluginMetadata,
|
|
128
|
+
);
|
package/src/schemas.ts
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
// =============================================================================
|
|
4
|
+
// ENUMS
|
|
5
|
+
// =============================================================================
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Impact type determines how an upstream system's status affects the downstream.
|
|
9
|
+
* - informational: Show a link/badge, no status impact
|
|
10
|
+
* - degraded: Downstream shows as degraded when upstream is affected
|
|
11
|
+
* - critical: Downstream shows as degraded when upstream is degraded, down when upstream is down
|
|
12
|
+
*/
|
|
13
|
+
export const ImpactTypeSchema = z.enum([
|
|
14
|
+
"informational",
|
|
15
|
+
"degraded",
|
|
16
|
+
"critical",
|
|
17
|
+
]);
|
|
18
|
+
export type ImpactType = z.infer<typeof ImpactTypeSchema>;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Derived warning state computed from dependency evaluation.
|
|
22
|
+
*/
|
|
23
|
+
export const DerivedStateSchema = z.enum(["info", "degraded", "down"]);
|
|
24
|
+
export type DerivedState = z.infer<typeof DerivedStateSchema>;
|
|
25
|
+
|
|
26
|
+
// =============================================================================
|
|
27
|
+
// HEALTH CHECK RULE
|
|
28
|
+
// =============================================================================
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Optional advanced rule linking a dependency to a specific health check.
|
|
32
|
+
* When rules exist on a dependency, only specified checks trigger the impact.
|
|
33
|
+
*/
|
|
34
|
+
export const HealthCheckRuleSchema = z.object({
|
|
35
|
+
id: z.string(),
|
|
36
|
+
dependencyId: z.string(),
|
|
37
|
+
healthCheckId: z.string(),
|
|
38
|
+
overrideImpactType: ImpactTypeSchema,
|
|
39
|
+
});
|
|
40
|
+
export type HealthCheckRule = z.infer<typeof HealthCheckRuleSchema>;
|
|
41
|
+
|
|
42
|
+
// =============================================================================
|
|
43
|
+
// DEPENDENCY ENTITY
|
|
44
|
+
// =============================================================================
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Core dependency entity representing a directional edge between two systems.
|
|
48
|
+
* sourceSystemId (downstream) depends on targetSystemId (upstream).
|
|
49
|
+
*/
|
|
50
|
+
export const DependencySchema = z.object({
|
|
51
|
+
id: z.string(),
|
|
52
|
+
sourceSystemId: z.string(),
|
|
53
|
+
targetSystemId: z.string(),
|
|
54
|
+
impactType: ImpactTypeSchema,
|
|
55
|
+
transitive: z.boolean(),
|
|
56
|
+
label: z.string().nullable(),
|
|
57
|
+
healthCheckRules: z.array(HealthCheckRuleSchema).optional(),
|
|
58
|
+
createdAt: z.date(),
|
|
59
|
+
updatedAt: z.date(),
|
|
60
|
+
});
|
|
61
|
+
export type Dependency = z.infer<typeof DependencySchema>;
|
|
62
|
+
|
|
63
|
+
// =============================================================================
|
|
64
|
+
// DEPENDENCY WARNING (Computed, not persisted)
|
|
65
|
+
// =============================================================================
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Information about a single affected upstream system contributing to a warning.
|
|
69
|
+
*/
|
|
70
|
+
export const AffectedUpstreamSchema = z.object({
|
|
71
|
+
systemId: z.string(),
|
|
72
|
+
systemName: z.string(),
|
|
73
|
+
ownStatus: z.string(),
|
|
74
|
+
impactType: ImpactTypeSchema,
|
|
75
|
+
dependencyLabel: z.string().nullable(),
|
|
76
|
+
});
|
|
77
|
+
export type AffectedUpstream = z.infer<typeof AffectedUpstreamSchema>;
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Computed dependency warning for a system.
|
|
81
|
+
* Represents the worst derived state across all upstream dependencies.
|
|
82
|
+
*/
|
|
83
|
+
export const DependencyWarningSchema = z.object({
|
|
84
|
+
systemId: z.string(),
|
|
85
|
+
derivedState: DerivedStateSchema,
|
|
86
|
+
affectedUpstreams: z.array(AffectedUpstreamSchema),
|
|
87
|
+
});
|
|
88
|
+
export type DependencyWarning = z.infer<typeof DependencyWarningSchema>;
|
|
89
|
+
|
|
90
|
+
// =============================================================================
|
|
91
|
+
// INPUT SCHEMAS
|
|
92
|
+
// =============================================================================
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Input for creating a health check rule within a dependency.
|
|
96
|
+
*/
|
|
97
|
+
export const CreateHealthCheckRuleInputSchema = z.object({
|
|
98
|
+
healthCheckId: z.string(),
|
|
99
|
+
overrideImpactType: ImpactTypeSchema,
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Input for creating a new dependency.
|
|
104
|
+
*/
|
|
105
|
+
export const CreateDependencyInputSchema = z
|
|
106
|
+
.object({
|
|
107
|
+
sourceSystemId: z.string().min(1, "Source system is required"),
|
|
108
|
+
targetSystemId: z.string().min(1, "Target system is required"),
|
|
109
|
+
impactType: ImpactTypeSchema,
|
|
110
|
+
transitive: z.boolean().optional().default(false),
|
|
111
|
+
label: z.string().optional(),
|
|
112
|
+
healthCheckRules: z
|
|
113
|
+
.array(CreateHealthCheckRuleInputSchema)
|
|
114
|
+
.optional()
|
|
115
|
+
.default([]),
|
|
116
|
+
})
|
|
117
|
+
.refine(
|
|
118
|
+
({ sourceSystemId, targetSystemId }) => sourceSystemId !== targetSystemId,
|
|
119
|
+
{ message: "A system cannot depend on itself" },
|
|
120
|
+
);
|
|
121
|
+
export type CreateDependencyInput = z.infer<typeof CreateDependencyInputSchema>;
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Input for updating an existing dependency.
|
|
125
|
+
*/
|
|
126
|
+
export const UpdateDependencyInputSchema = z.object({
|
|
127
|
+
id: z.string(),
|
|
128
|
+
systemId: z.string(),
|
|
129
|
+
impactType: ImpactTypeSchema.optional(),
|
|
130
|
+
transitive: z.boolean().optional(),
|
|
131
|
+
label: z.string().nullable().optional(),
|
|
132
|
+
healthCheckRules: z.array(CreateHealthCheckRuleInputSchema).optional(),
|
|
133
|
+
});
|
|
134
|
+
export type UpdateDependencyInput = z.infer<typeof UpdateDependencyInputSchema>;
|
|
135
|
+
|
|
136
|
+
// =============================================================================
|
|
137
|
+
// NODE POSITION (persisted server-side for the dependency map canvas)
|
|
138
|
+
// =============================================================================
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Persisted node position for the dependency map canvas.
|
|
142
|
+
*/
|
|
143
|
+
export const NodePositionSchema = z.object({
|
|
144
|
+
systemId: z.string(),
|
|
145
|
+
x: z.number(),
|
|
146
|
+
y: z.number(),
|
|
147
|
+
});
|
|
148
|
+
export type NodePosition = z.infer<typeof NodePositionSchema>;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { describe, test, expect } from "bun:test";
|
|
2
|
+
import {
|
|
3
|
+
CreateDependencyInputSchema,
|
|
4
|
+
ImpactTypeSchema,
|
|
5
|
+
DerivedStateSchema,
|
|
6
|
+
} from "@checkstack/dependency-common";
|
|
7
|
+
|
|
8
|
+
describe("dependency-common schemas", () => {
|
|
9
|
+
describe("ImpactTypeSchema", () => {
|
|
10
|
+
test("accepts valid impact types", () => {
|
|
11
|
+
expect(ImpactTypeSchema.parse("informational")).toBe("informational");
|
|
12
|
+
expect(ImpactTypeSchema.parse("degraded")).toBe("degraded");
|
|
13
|
+
expect(ImpactTypeSchema.parse("critical")).toBe("critical");
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
test("rejects invalid impact types", () => {
|
|
17
|
+
expect(() => ImpactTypeSchema.parse("invalid")).toThrow();
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
describe("DerivedStateSchema", () => {
|
|
22
|
+
test("accepts valid derived states", () => {
|
|
23
|
+
expect(DerivedStateSchema.parse("info")).toBe("info");
|
|
24
|
+
expect(DerivedStateSchema.parse("degraded")).toBe("degraded");
|
|
25
|
+
expect(DerivedStateSchema.parse("down")).toBe("down");
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test("rejects invalid derived states", () => {
|
|
29
|
+
expect(() => DerivedStateSchema.parse("invalid")).toThrow();
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
describe("CreateDependencyInputSchema", () => {
|
|
34
|
+
test("accepts valid input", () => {
|
|
35
|
+
const input = CreateDependencyInputSchema.parse({
|
|
36
|
+
sourceSystemId: "sys-a",
|
|
37
|
+
targetSystemId: "sys-b",
|
|
38
|
+
impactType: "degraded",
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
expect(input.sourceSystemId).toBe("sys-a");
|
|
42
|
+
expect(input.targetSystemId).toBe("sys-b");
|
|
43
|
+
expect(input.impactType).toBe("degraded");
|
|
44
|
+
expect(input.transitive).toBe(false); // default
|
|
45
|
+
expect(input.healthCheckRules).toEqual([]); // default
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test("rejects self-referencing dependency", () => {
|
|
49
|
+
expect(() =>
|
|
50
|
+
CreateDependencyInputSchema.parse({
|
|
51
|
+
sourceSystemId: "sys-a",
|
|
52
|
+
targetSystemId: "sys-a",
|
|
53
|
+
impactType: "degraded",
|
|
54
|
+
}),
|
|
55
|
+
).toThrow("A system cannot depend on itself");
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test("rejects empty source system", () => {
|
|
59
|
+
expect(() =>
|
|
60
|
+
CreateDependencyInputSchema.parse({
|
|
61
|
+
sourceSystemId: "",
|
|
62
|
+
targetSystemId: "sys-b",
|
|
63
|
+
impactType: "degraded",
|
|
64
|
+
}),
|
|
65
|
+
).toThrow();
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test("rejects empty target system", () => {
|
|
69
|
+
expect(() =>
|
|
70
|
+
CreateDependencyInputSchema.parse({
|
|
71
|
+
sourceSystemId: "sys-a",
|
|
72
|
+
targetSystemId: "",
|
|
73
|
+
impactType: "degraded",
|
|
74
|
+
}),
|
|
75
|
+
).toThrow();
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test("accepts health check rules", () => {
|
|
79
|
+
const input = CreateDependencyInputSchema.parse({
|
|
80
|
+
sourceSystemId: "sys-a",
|
|
81
|
+
targetSystemId: "sys-b",
|
|
82
|
+
impactType: "degraded",
|
|
83
|
+
healthCheckRules: [
|
|
84
|
+
{
|
|
85
|
+
healthCheckId: "hc-1",
|
|
86
|
+
overrideImpactType: "critical",
|
|
87
|
+
},
|
|
88
|
+
],
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
expect(input.healthCheckRules).toHaveLength(1);
|
|
92
|
+
expect(input.healthCheckRules[0].healthCheckId).toBe("hc-1");
|
|
93
|
+
expect(input.healthCheckRules[0].overrideImpactType).toBe("critical");
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
});
|