@checkstack/backend 0.0.2
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 +225 -0
- package/drizzle/0000_loose_yellow_claw.sql +28 -0
- package/drizzle/meta/0000_snapshot.json +187 -0
- package/drizzle/meta/_journal.json +13 -0
- package/drizzle.config.ts +10 -0
- package/package.json +42 -0
- package/src/db.ts +20 -0
- package/src/health-check-plugin-integration.test.ts +93 -0
- package/src/index.ts +419 -0
- package/src/integration/event-bus.integration.test.ts +313 -0
- package/src/logger.ts +65 -0
- package/src/openapi-router.ts +177 -0
- package/src/plugin-lifecycle.test.ts +276 -0
- package/src/plugin-manager/api-router.ts +163 -0
- package/src/plugin-manager/core-services.ts +312 -0
- package/src/plugin-manager/dependency-sorter.ts +103 -0
- package/src/plugin-manager/deregistration-guard.ts +41 -0
- package/src/plugin-manager/extension-points.ts +85 -0
- package/src/plugin-manager/index.ts +13 -0
- package/src/plugin-manager/plugin-admin-router.ts +89 -0
- package/src/plugin-manager/plugin-loader.ts +464 -0
- package/src/plugin-manager/types.ts +14 -0
- package/src/plugin-manager.test.ts +464 -0
- package/src/plugin-manager.ts +431 -0
- package/src/rpc-rest-compat.test.ts +80 -0
- package/src/schema.ts +46 -0
- package/src/services/config-service.test.ts +66 -0
- package/src/services/config-service.ts +322 -0
- package/src/services/event-bus.test.ts +469 -0
- package/src/services/event-bus.ts +317 -0
- package/src/services/health-check-registry.test.ts +101 -0
- package/src/services/health-check-registry.ts +27 -0
- package/src/services/jwt.ts +45 -0
- package/src/services/keystore.test.ts +198 -0
- package/src/services/keystore.ts +136 -0
- package/src/services/plugin-installer.test.ts +90 -0
- package/src/services/plugin-installer.ts +70 -0
- package/src/services/queue-manager.ts +382 -0
- package/src/services/queue-plugin-registry.ts +17 -0
- package/src/services/queue-proxy.ts +182 -0
- package/src/services/service-registry.ts +35 -0
- package/src/test-preload.ts +114 -0
- package/src/utils/plugin-discovery.test.ts +383 -0
- package/src/utils/plugin-discovery.ts +157 -0
- package/src/utils/strip-public-schema.ts +40 -0
- package/tsconfig.json +6 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
# @checkstack/backend
|
|
2
|
+
|
|
3
|
+
## 0.0.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- d20d274: Initial release of all @checkstack packages. Rebranded from Checkmate to Checkstack with new npm organization @checkstack and domain checkstack.dev.
|
|
8
|
+
- Updated dependencies [d20d274]
|
|
9
|
+
- @checkstack/api-docs-common@0.0.2
|
|
10
|
+
- @checkstack/auth-common@0.0.2
|
|
11
|
+
- @checkstack/backend-api@0.0.2
|
|
12
|
+
- @checkstack/common@0.0.2
|
|
13
|
+
- @checkstack/drizzle-helper@0.0.2
|
|
14
|
+
- @checkstack/queue-api@0.0.2
|
|
15
|
+
- @checkstack/signal-backend@0.0.2
|
|
16
|
+
- @checkstack/signal-common@0.0.2
|
|
17
|
+
|
|
18
|
+
## 0.1.4
|
|
19
|
+
|
|
20
|
+
### Patch Changes
|
|
21
|
+
|
|
22
|
+
- Updated dependencies [b4eb432]
|
|
23
|
+
- Updated dependencies [a65e002]
|
|
24
|
+
- @checkstack/backend-api@1.1.0
|
|
25
|
+
- @checkstack/common@0.2.0
|
|
26
|
+
- @checkstack/auth-common@0.2.1
|
|
27
|
+
- @checkstack/queue-api@1.0.1
|
|
28
|
+
- @checkstack/signal-backend@0.1.1
|
|
29
|
+
- @checkstack/api-docs-common@0.0.3
|
|
30
|
+
- @checkstack/signal-common@0.1.1
|
|
31
|
+
|
|
32
|
+
## 0.1.3
|
|
33
|
+
|
|
34
|
+
### Patch Changes
|
|
35
|
+
|
|
36
|
+
- Updated dependencies [e26c08e]
|
|
37
|
+
- @checkstack/auth-common@0.2.0
|
|
38
|
+
|
|
39
|
+
## 0.1.2
|
|
40
|
+
|
|
41
|
+
### Patch Changes
|
|
42
|
+
|
|
43
|
+
- 0f8cc7d: Add runtime configuration API for Docker deployments
|
|
44
|
+
|
|
45
|
+
- Backend: Add `/api/config` endpoint serving `BASE_URL` at runtime
|
|
46
|
+
- Backend: Update CORS to use `BASE_URL` and auto-allow Vite dev server
|
|
47
|
+
- Backend: `INTERNAL_URL` now defaults to `localhost:3000` (no BASE_URL fallback)
|
|
48
|
+
- Frontend API: Add `RuntimeConfigProvider` context for runtime config
|
|
49
|
+
- Frontend: Use `RuntimeConfigProvider` from `frontend-api`
|
|
50
|
+
- Auth Frontend: Add `useAuthClient()` hook using runtime config
|
|
51
|
+
|
|
52
|
+
## 0.1.1
|
|
53
|
+
|
|
54
|
+
### Patch Changes
|
|
55
|
+
|
|
56
|
+
- f0bdec2: Fixed CI test failures by implementing proper module mocking infrastructure:
|
|
57
|
+
- Added test-preload.ts with comprehensive mocks for db, logger, and core-services
|
|
58
|
+
- Added skipDiscovery option to loadPlugins() for test isolation
|
|
59
|
+
- Configured bunfig.toml preload for workspace-wide test setup
|
|
60
|
+
|
|
61
|
+
## 0.1.0
|
|
62
|
+
|
|
63
|
+
### Minor Changes
|
|
64
|
+
|
|
65
|
+
- ffc28f6: ### Anonymous Role and Public Access
|
|
66
|
+
|
|
67
|
+
Introduces a configurable "anonymous" role for managing permissions available to unauthenticated users.
|
|
68
|
+
|
|
69
|
+
**Core Changes:**
|
|
70
|
+
|
|
71
|
+
- Added `userType: "public"` - endpoints accessible by both authenticated users (with their permissions) and anonymous users (with anonymous role permissions)
|
|
72
|
+
- Renamed `userType: "both"` to `"authenticated"` for clarity
|
|
73
|
+
- Renamed `isDefault` to `isAuthenticatedDefault` on Permission interface
|
|
74
|
+
- Added `isPublicDefault` flag for permissions that should be granted to the anonymous role by default
|
|
75
|
+
|
|
76
|
+
**Backend Infrastructure:**
|
|
77
|
+
|
|
78
|
+
- New `anonymous` system role created during auth-backend initialization
|
|
79
|
+
- New `disabled_public_default_permission` table tracks admin-disabled public defaults
|
|
80
|
+
- `autoAuthMiddleware` now checks anonymous role permissions for unauthenticated public endpoint access
|
|
81
|
+
- `AuthService.getAnonymousPermissions()` with 1-minute caching for performance
|
|
82
|
+
- Anonymous role filtered from `getRoles` endpoint (not assignable to users)
|
|
83
|
+
- Validation prevents assigning anonymous role to users
|
|
84
|
+
|
|
85
|
+
**Catalog Integration:**
|
|
86
|
+
|
|
87
|
+
- `catalog.read` permission now has both `isAuthenticatedDefault` and `isPublicDefault`
|
|
88
|
+
- Read endpoints (`getSystems`, `getGroups`, `getEntities`) now use `userType: "public"`
|
|
89
|
+
|
|
90
|
+
**UI:**
|
|
91
|
+
|
|
92
|
+
- New `PermissionGate` component for conditionally rendering content based on permissions
|
|
93
|
+
|
|
94
|
+
- 71275dd: fix: Anonymous and non-admin user authorization
|
|
95
|
+
|
|
96
|
+
- Fixed permission metadata preservation in `plugin-manager.ts` - changed from outdated `isDefault` field to `isAuthenticatedDefault` and `isPublicDefault`
|
|
97
|
+
- Added `pluginId` to `RpcContext` to enable proper permission ID matching
|
|
98
|
+
- Updated `autoAuthMiddleware` to prefix contract permission IDs with the pluginId from context, ensuring that contract permissions (e.g., `catalog.read`) correctly match database permissions (e.g., `catalog-backend.catalog.read`)
|
|
99
|
+
- Route now uses `/api/:pluginId/*` pattern with Hono path parameters for clean pluginId extraction
|
|
100
|
+
|
|
101
|
+
- b55fae6: Added realtime Signal Service for backend-to-frontend push notifications via WebSockets.
|
|
102
|
+
|
|
103
|
+
## New Packages
|
|
104
|
+
|
|
105
|
+
- **@checkstack/signal-common**: Shared types including `Signal`, `SignalService`, `createSignal()`, and WebSocket protocol messages
|
|
106
|
+
- **@checkstack/signal-backend**: `SignalServiceImpl` with EventBus integration and Bun WebSocket handler using native pub/sub
|
|
107
|
+
- **@checkstack/signal-frontend**: React `SignalProvider` and `useSignal()` hook for consuming typed signals
|
|
108
|
+
|
|
109
|
+
## Changes
|
|
110
|
+
|
|
111
|
+
- **@checkstack/backend-api**: Added `coreServices.signalService` reference for plugins to emit signals
|
|
112
|
+
- **@checkstack/backend**: Integrated WebSocket server at `/api/signals/ws` with session-based authentication
|
|
113
|
+
|
|
114
|
+
## Usage
|
|
115
|
+
|
|
116
|
+
Backend plugins can emit signals:
|
|
117
|
+
|
|
118
|
+
```typescript
|
|
119
|
+
import { coreServices } from "@checkstack/backend-api";
|
|
120
|
+
import { NOTIFICATION_RECEIVED } from "@checkstack/notification-common";
|
|
121
|
+
|
|
122
|
+
const signalService = context.signalService;
|
|
123
|
+
await signalService.sendToUser(NOTIFICATION_RECEIVED, userId, { ... });
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Frontend components subscribe to signals:
|
|
127
|
+
|
|
128
|
+
```tsx
|
|
129
|
+
import { useSignal } from "@checkstack/signal-frontend";
|
|
130
|
+
import { NOTIFICATION_RECEIVED } from "@checkstack/notification-common";
|
|
131
|
+
|
|
132
|
+
useSignal(NOTIFICATION_RECEIVED, (payload) => {
|
|
133
|
+
// Handle realtime notification
|
|
134
|
+
});
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Patch Changes
|
|
138
|
+
|
|
139
|
+
- ae19ff6: Add configurable state thresholds for health check evaluation
|
|
140
|
+
|
|
141
|
+
**@checkstack/backend-api:**
|
|
142
|
+
|
|
143
|
+
- Added `VersionedData<T>` generic interface as base for all versioned data structures
|
|
144
|
+
- `VersionedConfig<T>` now extends `VersionedData<T>` and adds `pluginId`
|
|
145
|
+
- Added `migrateVersionedData()` utility function for running migrations on any `VersionedData` subtype
|
|
146
|
+
|
|
147
|
+
**@checkstack/backend:**
|
|
148
|
+
|
|
149
|
+
- Refactored `ConfigMigrationRunner` to use the new `migrateVersionedData` utility
|
|
150
|
+
|
|
151
|
+
**@checkstack/healthcheck-common:**
|
|
152
|
+
|
|
153
|
+
- Added state threshold schemas with two evaluation modes (consecutive, window)
|
|
154
|
+
- Added `stateThresholds` field to `AssociateHealthCheckSchema`
|
|
155
|
+
- Added `getSystemHealthStatus` RPC endpoint contract
|
|
156
|
+
|
|
157
|
+
**@checkstack/healthcheck-backend:**
|
|
158
|
+
|
|
159
|
+
- Added `stateThresholds` column to `system_health_checks` table
|
|
160
|
+
- Added `state-evaluator.ts` with health status evaluation logic
|
|
161
|
+
- Added `state-thresholds-migrations.ts` with migration infrastructure
|
|
162
|
+
- Added `getSystemHealthStatus` RPC handler
|
|
163
|
+
|
|
164
|
+
**@checkstack/healthcheck-frontend:**
|
|
165
|
+
|
|
166
|
+
- Updated `SystemHealthBadge` to use new backend endpoint
|
|
167
|
+
|
|
168
|
+
- 81f3f85: ## Breaking: Unified Versioned<T> Architecture
|
|
169
|
+
|
|
170
|
+
Refactored the versioning system to use a unified `Versioned<T>` class instead of separate `VersionedSchema`, `VersionedData`, and `VersionedConfig` types.
|
|
171
|
+
|
|
172
|
+
### Breaking Changes
|
|
173
|
+
|
|
174
|
+
- **`VersionedSchema<T>`** is replaced by `Versioned<T>` class
|
|
175
|
+
- **`VersionedData<T>`** is replaced by `VersionedRecord<T>` interface
|
|
176
|
+
- **`VersionedConfig<T>`** is replaced by `VersionedPluginRecord<T>` interface
|
|
177
|
+
- **`ConfigMigration<F, T>`** is replaced by `Migration<F, T>` interface
|
|
178
|
+
- **`MigrationChain<T>`** is removed (use `Migration<unknown, unknown>[]`)
|
|
179
|
+
- **`migrateVersionedData()`** is removed (use `versioned.parse()`)
|
|
180
|
+
- **`ConfigMigrationRunner`** is removed (migrations are internal to Versioned)
|
|
181
|
+
|
|
182
|
+
### Migration Guide
|
|
183
|
+
|
|
184
|
+
Before:
|
|
185
|
+
|
|
186
|
+
```typescript
|
|
187
|
+
const strategy: HealthCheckStrategy = {
|
|
188
|
+
config: {
|
|
189
|
+
version: 1,
|
|
190
|
+
schema: mySchema,
|
|
191
|
+
migrations: [],
|
|
192
|
+
},
|
|
193
|
+
};
|
|
194
|
+
const data = await migrateVersionedData(stored, 1, migrations);
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
After:
|
|
198
|
+
|
|
199
|
+
```typescript
|
|
200
|
+
const strategy: HealthCheckStrategy = {
|
|
201
|
+
config: new Versioned({
|
|
202
|
+
version: 1,
|
|
203
|
+
schema: mySchema,
|
|
204
|
+
migrations: [],
|
|
205
|
+
}),
|
|
206
|
+
};
|
|
207
|
+
const data = await strategy.config.parse(stored);
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
- Updated dependencies [ffc28f6]
|
|
211
|
+
- Updated dependencies [e4d83fc]
|
|
212
|
+
- Updated dependencies [71275dd]
|
|
213
|
+
- Updated dependencies [ae19ff6]
|
|
214
|
+
- Updated dependencies [32f2535]
|
|
215
|
+
- Updated dependencies [b55fae6]
|
|
216
|
+
- Updated dependencies [b354ab3]
|
|
217
|
+
- Updated dependencies [8e889b4]
|
|
218
|
+
- Updated dependencies [81f3f85]
|
|
219
|
+
- @checkstack/common@0.1.0
|
|
220
|
+
- @checkstack/backend-api@1.0.0
|
|
221
|
+
- @checkstack/auth-common@0.1.0
|
|
222
|
+
- @checkstack/queue-api@1.0.0
|
|
223
|
+
- @checkstack/signal-common@0.1.0
|
|
224
|
+
- @checkstack/signal-backend@0.1.0
|
|
225
|
+
- @checkstack/api-docs-common@0.0.2
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
CREATE TABLE "jwt_keys" (
|
|
2
|
+
"id" text PRIMARY KEY NOT NULL,
|
|
3
|
+
"public_key" text NOT NULL,
|
|
4
|
+
"private_key" text NOT NULL,
|
|
5
|
+
"algorithm" text NOT NULL,
|
|
6
|
+
"created_at" text NOT NULL,
|
|
7
|
+
"expires_at" text,
|
|
8
|
+
"revoked_at" text
|
|
9
|
+
);
|
|
10
|
+
--> statement-breakpoint
|
|
11
|
+
CREATE TABLE "plugin_configs" (
|
|
12
|
+
"plugin_id" text NOT NULL,
|
|
13
|
+
"config_id" text NOT NULL,
|
|
14
|
+
"data" jsonb NOT NULL,
|
|
15
|
+
"updated_at" timestamp DEFAULT now() NOT NULL,
|
|
16
|
+
CONSTRAINT "plugin_configs_plugin_id_config_id_pk" PRIMARY KEY("plugin_id","config_id")
|
|
17
|
+
);
|
|
18
|
+
--> statement-breakpoint
|
|
19
|
+
CREATE TABLE "plugins" (
|
|
20
|
+
"id" serial PRIMARY KEY NOT NULL,
|
|
21
|
+
"name" text NOT NULL,
|
|
22
|
+
"path" text NOT NULL,
|
|
23
|
+
"is_uninstallable" boolean DEFAULT false NOT NULL,
|
|
24
|
+
"config" json DEFAULT '{}'::json,
|
|
25
|
+
"enabled" boolean DEFAULT true NOT NULL,
|
|
26
|
+
"type" text DEFAULT 'backend' NOT NULL,
|
|
27
|
+
CONSTRAINT "plugins_name_unique" UNIQUE("name")
|
|
28
|
+
);
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "0f04d1ab-b47d-4057-b267-4b904deebd99",
|
|
3
|
+
"prevId": "00000000-0000-0000-0000-000000000000",
|
|
4
|
+
"version": "7",
|
|
5
|
+
"dialect": "postgresql",
|
|
6
|
+
"tables": {
|
|
7
|
+
"public.jwt_keys": {
|
|
8
|
+
"name": "jwt_keys",
|
|
9
|
+
"schema": "",
|
|
10
|
+
"columns": {
|
|
11
|
+
"id": {
|
|
12
|
+
"name": "id",
|
|
13
|
+
"type": "text",
|
|
14
|
+
"primaryKey": true,
|
|
15
|
+
"notNull": true
|
|
16
|
+
},
|
|
17
|
+
"public_key": {
|
|
18
|
+
"name": "public_key",
|
|
19
|
+
"type": "text",
|
|
20
|
+
"primaryKey": false,
|
|
21
|
+
"notNull": true
|
|
22
|
+
},
|
|
23
|
+
"private_key": {
|
|
24
|
+
"name": "private_key",
|
|
25
|
+
"type": "text",
|
|
26
|
+
"primaryKey": false,
|
|
27
|
+
"notNull": true
|
|
28
|
+
},
|
|
29
|
+
"algorithm": {
|
|
30
|
+
"name": "algorithm",
|
|
31
|
+
"type": "text",
|
|
32
|
+
"primaryKey": false,
|
|
33
|
+
"notNull": true
|
|
34
|
+
},
|
|
35
|
+
"created_at": {
|
|
36
|
+
"name": "created_at",
|
|
37
|
+
"type": "text",
|
|
38
|
+
"primaryKey": false,
|
|
39
|
+
"notNull": true
|
|
40
|
+
},
|
|
41
|
+
"expires_at": {
|
|
42
|
+
"name": "expires_at",
|
|
43
|
+
"type": "text",
|
|
44
|
+
"primaryKey": false,
|
|
45
|
+
"notNull": false
|
|
46
|
+
},
|
|
47
|
+
"revoked_at": {
|
|
48
|
+
"name": "revoked_at",
|
|
49
|
+
"type": "text",
|
|
50
|
+
"primaryKey": false,
|
|
51
|
+
"notNull": false
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
"indexes": {},
|
|
55
|
+
"foreignKeys": {},
|
|
56
|
+
"compositePrimaryKeys": {},
|
|
57
|
+
"uniqueConstraints": {},
|
|
58
|
+
"policies": {},
|
|
59
|
+
"checkConstraints": {},
|
|
60
|
+
"isRLSEnabled": false
|
|
61
|
+
},
|
|
62
|
+
"public.plugin_configs": {
|
|
63
|
+
"name": "plugin_configs",
|
|
64
|
+
"schema": "",
|
|
65
|
+
"columns": {
|
|
66
|
+
"plugin_id": {
|
|
67
|
+
"name": "plugin_id",
|
|
68
|
+
"type": "text",
|
|
69
|
+
"primaryKey": false,
|
|
70
|
+
"notNull": true
|
|
71
|
+
},
|
|
72
|
+
"config_id": {
|
|
73
|
+
"name": "config_id",
|
|
74
|
+
"type": "text",
|
|
75
|
+
"primaryKey": false,
|
|
76
|
+
"notNull": true
|
|
77
|
+
},
|
|
78
|
+
"data": {
|
|
79
|
+
"name": "data",
|
|
80
|
+
"type": "jsonb",
|
|
81
|
+
"primaryKey": false,
|
|
82
|
+
"notNull": true
|
|
83
|
+
},
|
|
84
|
+
"updated_at": {
|
|
85
|
+
"name": "updated_at",
|
|
86
|
+
"type": "timestamp",
|
|
87
|
+
"primaryKey": false,
|
|
88
|
+
"notNull": true,
|
|
89
|
+
"default": "now()"
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
"indexes": {},
|
|
93
|
+
"foreignKeys": {},
|
|
94
|
+
"compositePrimaryKeys": {
|
|
95
|
+
"plugin_configs_plugin_id_config_id_pk": {
|
|
96
|
+
"name": "plugin_configs_plugin_id_config_id_pk",
|
|
97
|
+
"columns": [
|
|
98
|
+
"plugin_id",
|
|
99
|
+
"config_id"
|
|
100
|
+
]
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
"uniqueConstraints": {},
|
|
104
|
+
"policies": {},
|
|
105
|
+
"checkConstraints": {},
|
|
106
|
+
"isRLSEnabled": false
|
|
107
|
+
},
|
|
108
|
+
"public.plugins": {
|
|
109
|
+
"name": "plugins",
|
|
110
|
+
"schema": "",
|
|
111
|
+
"columns": {
|
|
112
|
+
"id": {
|
|
113
|
+
"name": "id",
|
|
114
|
+
"type": "serial",
|
|
115
|
+
"primaryKey": true,
|
|
116
|
+
"notNull": true
|
|
117
|
+
},
|
|
118
|
+
"name": {
|
|
119
|
+
"name": "name",
|
|
120
|
+
"type": "text",
|
|
121
|
+
"primaryKey": false,
|
|
122
|
+
"notNull": true
|
|
123
|
+
},
|
|
124
|
+
"path": {
|
|
125
|
+
"name": "path",
|
|
126
|
+
"type": "text",
|
|
127
|
+
"primaryKey": false,
|
|
128
|
+
"notNull": true
|
|
129
|
+
},
|
|
130
|
+
"is_uninstallable": {
|
|
131
|
+
"name": "is_uninstallable",
|
|
132
|
+
"type": "boolean",
|
|
133
|
+
"primaryKey": false,
|
|
134
|
+
"notNull": true,
|
|
135
|
+
"default": false
|
|
136
|
+
},
|
|
137
|
+
"config": {
|
|
138
|
+
"name": "config",
|
|
139
|
+
"type": "json",
|
|
140
|
+
"primaryKey": false,
|
|
141
|
+
"notNull": false,
|
|
142
|
+
"default": "'{}'::json"
|
|
143
|
+
},
|
|
144
|
+
"enabled": {
|
|
145
|
+
"name": "enabled",
|
|
146
|
+
"type": "boolean",
|
|
147
|
+
"primaryKey": false,
|
|
148
|
+
"notNull": true,
|
|
149
|
+
"default": true
|
|
150
|
+
},
|
|
151
|
+
"type": {
|
|
152
|
+
"name": "type",
|
|
153
|
+
"type": "text",
|
|
154
|
+
"primaryKey": false,
|
|
155
|
+
"notNull": true,
|
|
156
|
+
"default": "'backend'"
|
|
157
|
+
}
|
|
158
|
+
},
|
|
159
|
+
"indexes": {},
|
|
160
|
+
"foreignKeys": {},
|
|
161
|
+
"compositePrimaryKeys": {},
|
|
162
|
+
"uniqueConstraints": {
|
|
163
|
+
"plugins_name_unique": {
|
|
164
|
+
"name": "plugins_name_unique",
|
|
165
|
+
"nullsNotDistinct": false,
|
|
166
|
+
"columns": [
|
|
167
|
+
"name"
|
|
168
|
+
]
|
|
169
|
+
}
|
|
170
|
+
},
|
|
171
|
+
"policies": {},
|
|
172
|
+
"checkConstraints": {},
|
|
173
|
+
"isRLSEnabled": false
|
|
174
|
+
}
|
|
175
|
+
},
|
|
176
|
+
"enums": {},
|
|
177
|
+
"schemas": {},
|
|
178
|
+
"sequences": {},
|
|
179
|
+
"roles": {},
|
|
180
|
+
"policies": {},
|
|
181
|
+
"views": {},
|
|
182
|
+
"_meta": {
|
|
183
|
+
"columns": {},
|
|
184
|
+
"schemas": {},
|
|
185
|
+
"tables": {}
|
|
186
|
+
}
|
|
187
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@checkstack/backend",
|
|
3
|
+
"version": "0.0.2",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"dev": "bun --env-file=../../.env --watch src/index.ts",
|
|
7
|
+
"typecheck": "tsc --noEmit",
|
|
8
|
+
"generate": "bun --env-file=../../.env run drizzle-kit generate",
|
|
9
|
+
"lint": "bun run lint:code",
|
|
10
|
+
"lint:code": "eslint . --max-warnings 0"
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"@checkstack/api-docs-common": "workspace:*",
|
|
14
|
+
"@checkstack/auth-common": "workspace:*",
|
|
15
|
+
"@checkstack/backend-api": "workspace:*",
|
|
16
|
+
"@checkstack/common": "workspace:*",
|
|
17
|
+
"@checkstack/drizzle-helper": "workspace:*",
|
|
18
|
+
"@checkstack/queue-api": "workspace:*",
|
|
19
|
+
"@checkstack/signal-backend": "workspace:*",
|
|
20
|
+
"@checkstack/signal-common": "workspace:*",
|
|
21
|
+
"@hono/zod-validator": "^0.7.6",
|
|
22
|
+
"@orpc/client": "^1.13.2",
|
|
23
|
+
"@orpc/openapi": "^1.13.2",
|
|
24
|
+
"@orpc/server": "^1.13.2",
|
|
25
|
+
"@orpc/zod": "^1.13.2",
|
|
26
|
+
"better-auth": "^1.4.7",
|
|
27
|
+
"drizzle-orm": "^0.45.1",
|
|
28
|
+
"hono": "^4.0.0",
|
|
29
|
+
"jose": "^6.1.3",
|
|
30
|
+
"pg": "^8.11.0",
|
|
31
|
+
"winston": "^3.19.0",
|
|
32
|
+
"zod": "^4.2.1"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"drizzle-kit": "^0.31.8",
|
|
36
|
+
"@types/pg": "^8.11.0",
|
|
37
|
+
"@types/bun": "latest",
|
|
38
|
+
"@checkstack/tsconfig": "workspace:*",
|
|
39
|
+
"@checkstack/scripts": "workspace:*",
|
|
40
|
+
"@checkstack/test-utils-backend": "workspace:*"
|
|
41
|
+
}
|
|
42
|
+
}
|
package/src/db.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { drizzle } from "drizzle-orm/node-postgres";
|
|
2
|
+
import { Pool } from "pg";
|
|
3
|
+
import * as schema from "./schema";
|
|
4
|
+
|
|
5
|
+
// Basic connection string sometimes fails with Bun + pg + docker SASL
|
|
6
|
+
// parsing manually or relying on pg to pick up ENV variables if we don't pass anything
|
|
7
|
+
// But we passed connectionString.
|
|
8
|
+
|
|
9
|
+
// Explicitly parse config or fallback to individual env vars to avoid SASL string errors
|
|
10
|
+
const connectionString = process.env.DATABASE_URL;
|
|
11
|
+
|
|
12
|
+
if (!connectionString) {
|
|
13
|
+
throw new Error("DATABASE_URL is not defined");
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const adminPool = new Pool({
|
|
17
|
+
connectionString,
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
export const db = drizzle(adminPool, { schema });
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { describe, it, expect, mock, beforeEach } from "bun:test";
|
|
2
|
+
import {
|
|
3
|
+
coreServices,
|
|
4
|
+
createBackendPlugin,
|
|
5
|
+
HealthCheckStrategy,
|
|
6
|
+
Versioned,
|
|
7
|
+
} from "@checkstack/backend-api";
|
|
8
|
+
import {
|
|
9
|
+
createMockQueueManager,
|
|
10
|
+
createMockLogger,
|
|
11
|
+
} from "@checkstack/test-utils-backend";
|
|
12
|
+
import { z } from "zod";
|
|
13
|
+
|
|
14
|
+
// Note: ./db and ./logger are mocked via test-preload.ts (bunfig.toml preload)
|
|
15
|
+
// This ensures mocks are in place BEFORE any module imports them
|
|
16
|
+
|
|
17
|
+
import { PluginManager } from "./plugin-manager";
|
|
18
|
+
|
|
19
|
+
describe("HealthCheck Plugin Integration", () => {
|
|
20
|
+
let pluginManager: PluginManager;
|
|
21
|
+
|
|
22
|
+
beforeEach(() => {
|
|
23
|
+
pluginManager = new PluginManager();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("should allow a plugin to register a health check strategy", async () => {
|
|
27
|
+
const mockRouter = {
|
|
28
|
+
route: mock(),
|
|
29
|
+
all: mock(),
|
|
30
|
+
newResponse: mock(),
|
|
31
|
+
} as never;
|
|
32
|
+
|
|
33
|
+
// Define a mock execute function for the strategy
|
|
34
|
+
const mockExecute = mock(async () => ({ status: "healthy" as const }));
|
|
35
|
+
|
|
36
|
+
// 1. Define a mock strategy
|
|
37
|
+
const mockStrategy: HealthCheckStrategy = {
|
|
38
|
+
id: "test-strategy",
|
|
39
|
+
displayName: "Test Strategy",
|
|
40
|
+
description: "A test strategy for integration testing",
|
|
41
|
+
config: new Versioned({
|
|
42
|
+
version: 1,
|
|
43
|
+
schema: z.object({}),
|
|
44
|
+
}),
|
|
45
|
+
aggregatedResult: new Versioned({
|
|
46
|
+
version: 1,
|
|
47
|
+
schema: z.record(z.string(), z.unknown()),
|
|
48
|
+
}),
|
|
49
|
+
execute: mockExecute,
|
|
50
|
+
aggregateResult: mock(() => ({})),
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
// 2. Define a mock plugin that registers this strategy
|
|
54
|
+
const testPlugin = createBackendPlugin({
|
|
55
|
+
metadata: { pluginId: "test-plugin" },
|
|
56
|
+
register(env) {
|
|
57
|
+
env.registerInit({
|
|
58
|
+
deps: {
|
|
59
|
+
healthCheckRegistry: coreServices.healthCheckRegistry,
|
|
60
|
+
},
|
|
61
|
+
init: async ({ healthCheckRegistry }) => {
|
|
62
|
+
healthCheckRegistry.register(mockStrategy);
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// Register mock services since core-services is mocked as no-op
|
|
69
|
+
pluginManager.registerService(
|
|
70
|
+
coreServices.queueManager,
|
|
71
|
+
createMockQueueManager()
|
|
72
|
+
);
|
|
73
|
+
pluginManager.registerService(coreServices.logger, createMockLogger());
|
|
74
|
+
pluginManager.registerService(coreServices.database, {} as never); // Mock database
|
|
75
|
+
|
|
76
|
+
// 4. Load plugins using the PluginManager with manual injection
|
|
77
|
+
await pluginManager.loadPlugins(mockRouter, [testPlugin], {
|
|
78
|
+
skipDiscovery: true,
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// 5. Verify the strategy is registered in the registry managed by PluginManager
|
|
82
|
+
const registry = await pluginManager.getService(
|
|
83
|
+
coreServices.healthCheckRegistry
|
|
84
|
+
);
|
|
85
|
+
expect(registry).toBeDefined();
|
|
86
|
+
|
|
87
|
+
const retrieved = registry?.getStrategy(mockStrategy.id);
|
|
88
|
+
expect(retrieved).toBe(mockStrategy);
|
|
89
|
+
expect(retrieved?.displayName).toBe("Test Strategy");
|
|
90
|
+
expect(retrieved?.id).toBe("test-strategy");
|
|
91
|
+
expect(retrieved?.execute).toBe(mockExecute);
|
|
92
|
+
});
|
|
93
|
+
});
|