@astrale-os/adapter-cloudflare 0.1.9 → 0.1.10
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/package.json +1 -1
- package/template/.agents/skills/astrale-cli/SKILL.md +1 -1
- package/template/.agents/skills/astrale-domain/SKILL.md +5 -5
- package/template/.env.example +5 -7
- package/template/README.md +2 -2
- package/template/client/README.md +79 -62
- package/template/client/__tests__/app.test.tsx +188 -99
- package/template/client/__tests__/harness.ts +67 -12
- package/template/client/__tests__/kernel.test.ts +65 -50
- package/template/client/__tests__/seam.test.tsx +111 -0
- package/template/client/index.html +1 -1
- package/template/client/package.json +1 -0
- package/template/client/src/app.tsx +40 -83
- package/template/client/src/main.tsx +2 -2
- package/template/client/src/monitor/components/MonitorCard.tsx +50 -0
- package/template/client/src/monitor/components/index.ts +1 -0
- package/template/client/src/monitor/hooks/index.ts +3 -0
- package/template/client/src/monitor/hooks/useCheck.mutation.ts +16 -0
- package/template/client/src/monitor/hooks/useMonitor.query.ts +64 -0
- package/template/client/src/monitor/index.ts +6 -0
- package/template/client/src/monitor/monitor.api.ts +11 -0
- package/template/client/src/monitor/monitor.mappers.ts +38 -0
- package/template/client/src/monitor/monitor.types.ts +23 -0
- package/template/client/src/monitor/ui/MonitorDetails.UI.tsx +38 -0
- package/template/client/src/monitor/ui/StatusBadge.UI.tsx +14 -0
- package/template/client/src/monitor/ui/index.ts +8 -0
- package/template/client/src/shell/client.ts +67 -0
- package/template/client/src/shell/index.ts +20 -0
- package/template/client/src/shell/invoke.ts +35 -0
- package/template/client/src/shell/transformers.ts +72 -0
- package/template/client/src/shell/use-async.ts +56 -0
- package/template/client/src/shell/use-capability.ts +61 -0
- package/template/client/src/shell/use-node.ts +61 -0
- package/template/client/src/shell/use-shell.ts +91 -0
- package/template/client/src/shell/view-router.tsx +98 -0
- package/template/client/src/styles.css +177 -4
- package/template/client/src/ui/format.ts +24 -0
- package/template/client/src/ui/index.ts +9 -0
- package/template/client/src/ui/surface.tsx +56 -0
- package/template/client/src/ui/value.tsx +32 -0
- package/template/client/src/views/monitor.tsx +30 -0
- package/template/client/tsconfig.json +2 -1
- package/template/client/vite.config.ts +12 -13
- package/template/client/vitest.config.ts +12 -5
- package/template/core/monitor/health.ts +19 -0
- package/template/core/monitor/index.ts +9 -0
- package/template/core/monitor/keys.ts +29 -0
- package/template/core/monitor/node.ts +51 -0
- package/template/deps.ts +8 -8
- package/template/domain.ts +1 -1
- package/template/env.ts +2 -9
- package/template/integrations/prober/http.ts +43 -0
- package/template/integrations/prober/mock.ts +22 -0
- package/template/integrations/prober/port.ts +28 -0
- package/template/integrations/prober/registry.ts +66 -0
- package/template/package.json +1 -1
- package/template/pnpm-lock.yaml +2766 -0
- package/template/runtime/index.ts +36 -19
- package/template/runtime/monitor/check.ts +29 -0
- package/template/runtime/monitor/dependsOn.ts +16 -0
- package/template/runtime/monitor/index.ts +12 -0
- package/template/runtime/monitor/seed.ts +74 -0
- package/template/runtime/monitor/shared.ts +17 -0
- package/template/runtime/monitor/watch.ts +37 -0
- package/template/schema/index.ts +11 -4
- package/template/schema/monitor.ts +80 -0
- package/template/views/index.ts +9 -2
- package/template/views/monitor.ts +22 -0
- package/template/client/src/lib/kernel.ts +0 -135
- package/template/client/src/lib/shell.ts +0 -197
- package/template/client/src/lib/use-node.ts +0 -66
- package/template/client/src/lib/use-shell.ts +0 -85
- package/template/core/keys.ts +0 -28
- package/template/core/note.ts +0 -148
- package/template/integrations/summary/heuristic.ts +0 -25
- package/template/integrations/summary/http.ts +0 -69
- package/template/integrations/summary/port.ts +0 -21
- package/template/integrations/summary/registry.ts +0 -52
- package/template/schema/note.ts +0 -67
- package/template/views/note.ts +0 -21
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The monitor bounded context's PURE core, in one place: schema accessors
|
|
3
|
+
* (`keys`), the health status rule (`health`), and node identity/layout/seed
|
|
4
|
+
* data (`node`). No I/O, no clock/RNG — all deterministic + testable.
|
|
5
|
+
* `runtime/monitor/` imports the operations' logic from here.
|
|
6
|
+
*/
|
|
7
|
+
export * from './keys'
|
|
8
|
+
export * from './health'
|
|
9
|
+
export * from './node'
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compiled-schema accessors for the monitor context — the ONE place class paths,
|
|
3
|
+
* method paths, and qualified prop keys come from. Pure (schema-derived); never
|
|
4
|
+
* hand-write key strings. `D` (the compiled domain) comes from `schema/index`,
|
|
5
|
+
* re-exported here so the rest of the context reads it from one place.
|
|
6
|
+
*/
|
|
7
|
+
import { K } from '@astrale-os/kernel-core'
|
|
8
|
+
|
|
9
|
+
import { D } from '../../schema'
|
|
10
|
+
|
|
11
|
+
export { D, K }
|
|
12
|
+
|
|
13
|
+
/** Kernel ops/classes the logic addresses. */
|
|
14
|
+
export const NODE_CREATE = K.Node.createNode.path.method.raw
|
|
15
|
+
export const FOLDER_CLASS = K.Folder.path.class.raw
|
|
16
|
+
export const NAME_KEY = K.Named.name.key
|
|
17
|
+
|
|
18
|
+
/** Domain class paths. */
|
|
19
|
+
export const MONITOR_CLASS = D.Monitor.path.class.raw
|
|
20
|
+
export const DEPENDS_ON_EDGE = D.depends_on.path.class.raw
|
|
21
|
+
|
|
22
|
+
/** Qualified storage keys for Monitor node props. */
|
|
23
|
+
export const MONITOR_KEYS = {
|
|
24
|
+
url: D.Monitor.url.key,
|
|
25
|
+
status: D.Monitor.status.key,
|
|
26
|
+
statusCode: D.Monitor.statusCode.key,
|
|
27
|
+
latencyMs: D.Monitor.latencyMs.key,
|
|
28
|
+
lastCheckedAt: D.Monitor.lastCheckedAt.key,
|
|
29
|
+
} as const
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Monitor node identity — PURE domain constants + slug logic (no I/O): where
|
|
3
|
+
* monitors live in the graph, how a new node's slug is formed, and the seed set.
|
|
4
|
+
* `runtime/monitor/` consumes these. The IMPURE bits (the slug's entropy suffix,
|
|
5
|
+
* the actual node writes) stay in `runtime/`, so this file is deterministic and
|
|
6
|
+
* trivially testable.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/** Where Monitor nodes live in the graph — a domain layout choice (`seed` creates the folder). */
|
|
10
|
+
export const MONITORS_PARENT = '/monitors'
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Deterministic URL-safe stem for a monitor's node slug (host + path, lowercased,
|
|
14
|
+
* non-alnum → `-`). Pure.
|
|
15
|
+
*/
|
|
16
|
+
export function slugForUrl(url: string): string {
|
|
17
|
+
return (
|
|
18
|
+
url
|
|
19
|
+
.replace(/^https?:\/\//i, '')
|
|
20
|
+
.toLowerCase()
|
|
21
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
22
|
+
.replace(/^-|-$/g, '')
|
|
23
|
+
.slice(0, 40) || 'monitor'
|
|
24
|
+
)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Slug for a NEW monitor node: the url stem + a caller-supplied `suffix`. Pure —
|
|
29
|
+
* `runtime/monitor/watch` injects the `suffix` (clock/RNG entropy for collision
|
|
30
|
+
* safety) so core stays deterministic. `seed` doesn't use this (it pins fixed
|
|
31
|
+
* slugs for idempotency); `watch` does.
|
|
32
|
+
*/
|
|
33
|
+
export function monitorSlug(url: string, suffix: string): string {
|
|
34
|
+
return `${slugForUrl(url)}-${suffix}`
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface StarterMonitor {
|
|
38
|
+
slug: string
|
|
39
|
+
name: string
|
|
40
|
+
url: string
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Monitors laid down by `seed` (postInstall). httpbin returns a deterministic
|
|
45
|
+
* status code, so the second starter is a stable demo target; the first points at
|
|
46
|
+
* a real site. Fixed slugs keep `seed` idempotent across reinstalls.
|
|
47
|
+
*/
|
|
48
|
+
export const STARTERS: readonly StarterMonitor[] = [
|
|
49
|
+
{ slug: 'astrale', name: 'Astrale', url: 'https://astrale.ai' },
|
|
50
|
+
{ slug: 'httpbin-200', name: 'httpbin (200)', url: 'https://httpbin.org/status/200' },
|
|
51
|
+
]
|
package/template/deps.ts
CHANGED
|
@@ -5,21 +5,21 @@
|
|
|
5
5
|
* it's where ports/clients are wired once instead of being re-derived in every
|
|
6
6
|
* handler.
|
|
7
7
|
*
|
|
8
|
-
* The
|
|
9
|
-
*
|
|
10
|
-
*
|
|
8
|
+
* The prober port is resolved PER-REQUEST and PER-NODE (each `prober(target)`
|
|
9
|
+
* call), not here — so this stays a cheap synchronous wiring step and the backend
|
|
10
|
+
* can be chosen from the monitor being probed, not just the env.
|
|
11
11
|
*
|
|
12
12
|
* Omit the `deps` field in `domain.ts` for the trivial case (handlers then get
|
|
13
|
-
* raw `Env`). Transform here the moment a handler should depend on a PORT
|
|
14
|
-
*
|
|
13
|
+
* raw `Env`). Transform here the moment a handler should depend on a PORT instead
|
|
14
|
+
* of raw config — that's the whole point of this seam.
|
|
15
15
|
*/
|
|
16
|
-
import {
|
|
16
|
+
import { buildProberRegistry, type ProberRegistry } from './integrations/prober/registry'
|
|
17
17
|
import type { Env } from './env'
|
|
18
18
|
|
|
19
19
|
/** Typed dependency container handed to every method as `ctx.deps`. */
|
|
20
|
-
export interface Deps extends
|
|
20
|
+
export interface Deps extends ProberRegistry {}
|
|
21
21
|
|
|
22
22
|
/** Map the worker env to the handler deps (the seam `defineDomain({ deps })` mounts). */
|
|
23
23
|
export function deps(env: Env): Deps {
|
|
24
|
-
return
|
|
24
|
+
return buildProberRegistry(env)
|
|
25
25
|
}
|
package/template/domain.ts
CHANGED
package/template/env.ts
CHANGED
|
@@ -19,15 +19,8 @@ export interface Env {
|
|
|
19
19
|
/** Dev-only: forward /ui/* to a running Vite dev server. */
|
|
20
20
|
VIEW_DEV_URL?: string
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
NOTE_SUMMARIZER?: string
|
|
25
|
-
/** `http` only — bearer token for the OpenAI-compatible upstream (a secret). */
|
|
26
|
-
SUMMARIZER_API_KEY?: string
|
|
27
|
-
/** `http` only — OpenAI-compatible base URL (e.g. https://api.openai.com/v1). */
|
|
28
|
-
SUMMARIZER_BASE_URL?: string
|
|
29
|
-
/** `http` only — model id (default gpt-4o-mini). */
|
|
30
|
-
SUMMARIZER_MODEL?: string
|
|
22
|
+
/** `http` only — per-probe timeout in ms (default 10000). */
|
|
23
|
+
PROBE_TIMEOUT_MS?: string
|
|
31
24
|
|
|
32
25
|
[key: string]: unknown
|
|
33
26
|
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP prober — the REAL external adapter: a single keyless `fetch` to the
|
|
3
|
+
* target, timing the round-trip. This is the shape your domain's external calls
|
|
4
|
+
* take — a timeout via `AbortSignal`, failures mapped to a domain result rather
|
|
5
|
+
* than thrown. NO API key: a fresh scaffold makes a true external call out of
|
|
6
|
+
* the box (it's the registry default).
|
|
7
|
+
*/
|
|
8
|
+
import type { Prober, ProbeResult } from './port'
|
|
9
|
+
|
|
10
|
+
export interface HttpProberConfig {
|
|
11
|
+
/** Probe verb — `GET` (default) or the lighter `HEAD`. */
|
|
12
|
+
method?: 'GET' | 'HEAD'
|
|
13
|
+
/** Per-probe timeout in ms (default 10000). */
|
|
14
|
+
timeoutMs?: number
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const DEFAULT_TIMEOUT_MS = 10_000
|
|
18
|
+
|
|
19
|
+
/** Build the keyless HTTP prober. */
|
|
20
|
+
export function createHttpProber(config: HttpProberConfig = {}): Prober {
|
|
21
|
+
const method = config.method ?? 'GET'
|
|
22
|
+
const timeoutMs = config.timeoutMs ?? DEFAULT_TIMEOUT_MS
|
|
23
|
+
return {
|
|
24
|
+
async probe(url): Promise<ProbeResult> {
|
|
25
|
+
const start = Date.now()
|
|
26
|
+
try {
|
|
27
|
+
const res = await fetch(url, {
|
|
28
|
+
method,
|
|
29
|
+
redirect: 'follow',
|
|
30
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
31
|
+
})
|
|
32
|
+
return {
|
|
33
|
+
statusCode: res.status,
|
|
34
|
+
latencyMs: Date.now() - start,
|
|
35
|
+
ok: res.status >= 200 && res.status < 400,
|
|
36
|
+
}
|
|
37
|
+
} catch {
|
|
38
|
+
// Unreachable / timeout / DNS failure — a `down` result, not an error.
|
|
39
|
+
return { statusCode: 0, latencyMs: Date.now() - start, ok: false }
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mock prober — offline + deterministic, no network. Used by tests (construct it
|
|
3
|
+
* directly with the status you want) and by `PROBER=mock` for an offline dev
|
|
4
|
+
* loop. Returns a fixed result regardless of URL.
|
|
5
|
+
*/
|
|
6
|
+
import type { Prober, ProbeResult } from './port'
|
|
7
|
+
|
|
8
|
+
/** Build an offline prober that always returns `opts` (default: a healthy 200). */
|
|
9
|
+
export function createMockProber(opts: { statusCode?: number; latencyMs?: number } = {}): Prober {
|
|
10
|
+
const statusCode = opts.statusCode ?? 200
|
|
11
|
+
const latencyMs = opts.latencyMs ?? 1
|
|
12
|
+
const result: ProbeResult = {
|
|
13
|
+
statusCode,
|
|
14
|
+
latencyMs,
|
|
15
|
+
ok: statusCode >= 200 && statusCode < 400,
|
|
16
|
+
}
|
|
17
|
+
return {
|
|
18
|
+
probe() {
|
|
19
|
+
return Promise.resolve(result)
|
|
20
|
+
},
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prober — the PORT between the domain's logic and whatever performs the actual
|
|
3
|
+
* uptime probe. This is the external-API seam every real domain has, reduced to
|
|
4
|
+
* the narrowest interface the logic needs.
|
|
5
|
+
*
|
|
6
|
+
* `runtime/` logic depends on THIS interface only — never on `fetch`, an SDK, or
|
|
7
|
+
* `env`. Adapters in this folder implement it: `http.ts` (a real, keyless HTTP
|
|
8
|
+
* request) and `mock.ts` (offline + deterministic, for tests). The registry
|
|
9
|
+
* picks one from `env`; the handler logic only ever sees the resolved port. Swap
|
|
10
|
+
* or add a backend = one adapter + one arm in `registry.ts`.
|
|
11
|
+
*/
|
|
12
|
+
export interface ProbeResult {
|
|
13
|
+
/** Observed HTTP status code, or `0` if the host was unreachable. */
|
|
14
|
+
statusCode: number
|
|
15
|
+
/** Round-trip time in ms. */
|
|
16
|
+
latencyMs: number
|
|
17
|
+
/** Convenience flag — `statusCode` in `[200, 400)`. */
|
|
18
|
+
ok: boolean
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface Prober {
|
|
22
|
+
/**
|
|
23
|
+
* Probe a URL once. MUST resolve, never reject: an unreachable host or a
|
|
24
|
+
* timeout is a `down` RESULT (`statusCode: 0`), not an error — a probe failing
|
|
25
|
+
* is the normal case a monitor exists to observe.
|
|
26
|
+
*/
|
|
27
|
+
probe(url: string): Promise<ProbeResult>
|
|
28
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prober registry — the ONE place the worker env (and the probed node) become the
|
|
3
|
+
* live `Prober` port. Adding a backend = one more arm in `selectProber`; handler
|
|
4
|
+
* logic only ever sees the resolved port.
|
|
5
|
+
*
|
|
6
|
+
* Resolution is PER-REQUEST and PER-NODE: each `prober(target)` call selects +
|
|
7
|
+
* builds the port from env AND the monitor being probed — nothing is built at
|
|
8
|
+
* `deps()` / isolate setup. A per-node choice can't be memoized at the isolate
|
|
9
|
+
* level, which is exactly why `deps` exposes `prober()` as a FUNCTION, not a
|
|
10
|
+
* value. The default is the keyless `http` prober (so the scaffold makes a real
|
|
11
|
+
* probe out of the box); `PROBER=mock` forces the offline prober.
|
|
12
|
+
*/
|
|
13
|
+
import type { Env } from '../../env'
|
|
14
|
+
import type { Prober } from './port'
|
|
15
|
+
|
|
16
|
+
import { createHttpProber } from './http'
|
|
17
|
+
import { createMockProber } from './mock'
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* The node a probe is for — what the registry may branch on. Mirrors how the
|
|
21
|
+
* `services` domain picks a provider from a node's class; here we also look at
|
|
22
|
+
* the target url. The handler passes this from `self.node()`; omit it for
|
|
23
|
+
* node-less callers (e.g. `seed`, which just wants the env default).
|
|
24
|
+
*/
|
|
25
|
+
export interface ProbeTarget {
|
|
26
|
+
/** The monitor node's class path (`node.class.raw`). */
|
|
27
|
+
class: string
|
|
28
|
+
/** The url this monitor probes (`node.props[MONITOR_KEYS.url]`). */
|
|
29
|
+
url: string
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface ProberRegistry {
|
|
33
|
+
/** Resolve the prober for a probe — built per call from env + the target node. */
|
|
34
|
+
prober(target?: ProbeTarget): Prober
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Build the registry from the worker env. The port is resolved per request. */
|
|
38
|
+
export function buildProberRegistry(env: Env): ProberRegistry {
|
|
39
|
+
return {
|
|
40
|
+
prober(target) {
|
|
41
|
+
return selectProber(env, target)
|
|
42
|
+
},
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const DEFAULT_TIMEOUT_MS = 10_000
|
|
47
|
+
|
|
48
|
+
/** Bind the abstract prober port to the concrete adapter for this env + target. */
|
|
49
|
+
function selectProber(env: Env, target?: ProbeTarget): Prober {
|
|
50
|
+
if (target && isLocalTarget(target.url)) {
|
|
51
|
+
return createMockProber()
|
|
52
|
+
}
|
|
53
|
+
// Default: the real, keyless HTTP prober.
|
|
54
|
+
return createHttpProber({ timeoutMs: parseIntOr(env.PROBE_TIMEOUT_MS, DEFAULT_TIMEOUT_MS) })
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** A loopback / unspecified host the public edge can't reach. */
|
|
58
|
+
function isLocalTarget(url: string): boolean {
|
|
59
|
+
return /^https?:\/\/(localhost|127\.0\.0\.1|0\.0\.0\.0|\[::1\])(:|\/|$)/i.test(url)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function parseIntOr(value: string | undefined, fallback: number): number {
|
|
63
|
+
if (!value) return fallback
|
|
64
|
+
const n = Number.parseInt(value, 10)
|
|
65
|
+
return Number.isFinite(n) && n > 0 ? n : fallback
|
|
66
|
+
}
|
package/template/package.json
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"typecheck": "tsgo --noEmit"
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"@astrale-os/adapter-cloudflare": ">=0.1.
|
|
17
|
+
"@astrale-os/adapter-cloudflare": ">=0.1.10 <1.0.0",
|
|
18
18
|
"@astrale-os/kernel-core": ">=0.4.3 <1.0.0",
|
|
19
19
|
"@astrale-os/kernel-dsl": ">=0.1.2 <1.0.0",
|
|
20
20
|
"@astrale-os/sdk": ">=0.1.7 <1.0.0",
|