@astrogate/sdk 0.1.0 → 0.3.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.
@@ -0,0 +1,35 @@
1
+ import type { ManagementOptions } from "./management.js";
2
+ import type { components } from "./wire.gen.js";
3
+ type Schemas = components["schemas"];
4
+ export type AdmissionPolicy = Schemas["AdmissionPolicy"];
5
+ export type AdmissionState = Schemas["AdmissionState"];
6
+ export type AdmissionUsagePage = Schemas["AdmissionUsagePage"];
7
+ export interface UsageQuery {
8
+ from: string;
9
+ until: string;
10
+ limit: number;
11
+ project?: string;
12
+ environment?: string;
13
+ workload?: "application" | "database" | "identity" | "storage" | "jobs" | "builds" | "realtime";
14
+ subject?: string;
15
+ after?: string;
16
+ }
17
+ export declare function admissionState(value: unknown): value is AdmissionState;
18
+ export declare function admissionUsage(value: unknown): value is AdmissionUsagePage;
19
+ export declare function admissionClient(options: ManagementOptions, endpoint: URL, base: string): {
20
+ get: (signal?: AbortSignal) => Promise<{
21
+ policy: components["schemas"]["AdmissionPolicy"];
22
+ occupancy: components["schemas"]["AdmissionOccupancy"][];
23
+ storageCredits: components["schemas"]["TenantStorageCredit"][];
24
+ }>;
25
+ assign: (input: Schemas["AssignAdmissionPolicy"], signal?: AbortSignal) => Promise<{
26
+ revision: number;
27
+ profile: components["schemas"]["AdmissionProfile"];
28
+ assignedAt: string;
29
+ }>;
30
+ usage: (query: UsageQuery, signal?: AbortSignal) => Promise<{
31
+ points: components["schemas"]["AdmissionUsagePoint"][];
32
+ next?: string;
33
+ }>;
34
+ };
35
+ export {};
@@ -0,0 +1,112 @@
1
+ import { AstrogateError, object, requestJSON } from "./http.js";
2
+ function count(value) {
3
+ return Number.isSafeInteger(value) && Number(value) >= 0;
4
+ }
5
+ function date(value) {
6
+ return typeof value === "string" && Number.isFinite(Date.parse(value));
7
+ }
8
+ function positive(value) {
9
+ return count(value) && value > 0;
10
+ }
11
+ function rule(value) {
12
+ return (object(value) &&
13
+ typeof value.requestsPerSecond === "number" &&
14
+ Number.isFinite(value.requestsPerSecond) &&
15
+ value.requestsPerSecond > 0 &&
16
+ ["burst", "concurrent", "executionMs"].every((key) => positive(value[key])) &&
17
+ ["pending", "pendingBytes", "waitMs"].every((key) => count(value[key])));
18
+ }
19
+ function validRetention(value) {
20
+ return (object(value) &&
21
+ positive(value.rawLogsSeconds) &&
22
+ positive(value.usageSeconds) &&
23
+ count(value.subjectSeconds) &&
24
+ value.subjectSeconds <= value.usageSeconds);
25
+ }
26
+ function policy(value) {
27
+ if (!object(value) ||
28
+ !positive(value.revision) ||
29
+ !date(value.assignedAt) ||
30
+ !object(value.profile))
31
+ return false;
32
+ const p = value.profile;
33
+ return (typeof p.id === "string" &&
34
+ p.id.length > 0 &&
35
+ typeof p.evidence === "string" &&
36
+ p.evidence.length > 0 &&
37
+ positive(p.durableBytes) &&
38
+ validRetention(p.retention) &&
39
+ object(p.rules) &&
40
+ object(p.rules.all) &&
41
+ Object.entries(p.rules).every(([kind, value]) => [
42
+ "all",
43
+ "application",
44
+ "database",
45
+ "identity",
46
+ "storage",
47
+ "jobs",
48
+ "builds",
49
+ "realtime",
50
+ ].includes(kind) &&
51
+ object(value) &&
52
+ rule(value.tenant) &&
53
+ rule(value.application)));
54
+ }
55
+ export function admissionState(value) {
56
+ return (object(value) &&
57
+ policy(value.policy) &&
58
+ Array.isArray(value.occupancy) &&
59
+ value.occupancy.every((o) => object(o) &&
60
+ typeof o.projectId === "string" &&
61
+ typeof o.workload === "string" &&
62
+ ["running", "unacknowledged", "pending", "pendingBytes"].every((key) => count(o[key])) &&
63
+ (o.oldest === undefined || date(o.oldest))) &&
64
+ Array.isArray(value.storageCredits) &&
65
+ value.storageCredits.every((c) => object(c) &&
66
+ ["projectId", "environmentId", "block", "owner"].every((key) => typeof c[key] === "string") &&
67
+ count(c.bytes) &&
68
+ count(c.epoch)));
69
+ }
70
+ export function admissionUsage(value) {
71
+ return (object(value) &&
72
+ (value.next === undefined || date(value.next)) &&
73
+ Array.isArray(value.points) &&
74
+ value.points.every((p) => object(p) &&
75
+ date(p.hour) &&
76
+ typeof p.workload === "string" &&
77
+ typeof p.outcome === "string" &&
78
+ ["requests", "bytes", "elapsedMs"].every((key) => count(p[key]))));
79
+ }
80
+ // This namespace uses the same authority and transport as the management client.
81
+ // A profile reference is the only writable entitlement, never arbitrary limits.
82
+ export function admissionClient(options, endpoint, base) {
83
+ const headers = { authorization: `Bearer ${options.token}` };
84
+ return {
85
+ get: (signal) => requestJSON(new URL(base + "/admission", endpoint), options, { headers }, admissionState, signal),
86
+ assign: (input, signal) => {
87
+ if (!input.profileId || !count(input.expectedRevision))
88
+ throw new AstrogateError("invalid_policy_assignment", 0, "", false);
89
+ // A lost response to CAS needs a read/reconciliation, not a blind replay.
90
+ return requestJSON(new URL(base + "/admission", endpoint), { ...options, maxAttempts: 1 }, {
91
+ method: "POST",
92
+ headers: { ...headers, "content-type": "application/json" },
93
+ body: JSON.stringify(input),
94
+ }, policy, signal);
95
+ },
96
+ usage: (query, signal) => {
97
+ if (!date(query.from) ||
98
+ !date(query.until) ||
99
+ Date.parse(query.from) >= Date.parse(query.until) ||
100
+ !count(query.limit) ||
101
+ query.limit === 0 ||
102
+ (query.environment && !query.project) ||
103
+ (query.after && !date(query.after)))
104
+ throw new AstrogateError("invalid_usage_query", 0, "", false);
105
+ const url = new URL(base + "/usage", endpoint);
106
+ for (const [key, value] of Object.entries(query))
107
+ if (value !== undefined)
108
+ url.searchParams.set(key, String(value));
109
+ return requestJSON(url, options, { headers }, admissionUsage, signal);
110
+ },
111
+ };
112
+ }
package/dist/http.d.ts CHANGED
@@ -1,9 +1,12 @@
1
+ import type { components } from "./wire.gen.js";
2
+ export type FailureDetails = Pick<components["schemas"]["Error"], "message" | "scope" | "dimension" | "reason">;
1
3
  export declare class AstrogateError extends Error {
2
4
  readonly code: string;
3
5
  readonly status: number;
4
6
  readonly requestId: string;
5
7
  readonly retryable: boolean;
6
- constructor(code: string, status: number, requestId: string, retryable: boolean);
8
+ readonly details?: Partial<FailureDetails> | undefined;
9
+ constructor(code: string, status: number, requestId: string, retryable: boolean, details?: Partial<FailureDetails> | undefined);
7
10
  }
8
11
  export interface TransportBudget {
9
12
  deadlineMs: number;
package/dist/http.js CHANGED
@@ -1,42 +1,85 @@
1
- // Shared owned HTTP behavior. Native database/auth clients retain their own
2
- // return values and transport semantics.
3
1
  export class AstrogateError extends Error {
4
2
  code;
5
3
  status;
6
4
  requestId;
7
5
  retryable;
8
- constructor(code, status, requestId, retryable) {
9
- super(code);
6
+ details;
7
+ constructor(code, status, requestId, retryable, details) {
8
+ super(details?.message ?? code);
10
9
  this.code = code;
11
10
  this.status = status;
12
11
  this.requestId = requestId;
13
12
  this.retryable = retryable;
13
+ this.details = details;
14
14
  this.name = "AstrogateError";
15
15
  }
16
16
  }
17
- export function object(value) { return typeof value === "object" && value !== null && !Array.isArray(value); }
18
- export function identifier(value) { if (!/^[A-Za-z0-9_-]+$/.test(value))
19
- throw new AstrogateError("invalid_resource_reference", 0, "", false); return value; }
20
- export function validateBudget(options) { if (!Number.isSafeInteger(options.deadlineMs) || options.deadlineMs <= 0 || !Number.isSafeInteger(options.maxAttempts) || options.maxAttempts < 1)
21
- throw new AstrogateError("explicit_transport_budget_required", 0, "", false); }
22
- function delay(ms, signal) { return new Promise((resolve, reject) => { if (signal.aborted) {
23
- reject(signal.reason);
24
- return;
25
- } const abort = () => { clearTimeout(timer); reject(signal.reason); }; const timer = setTimeout(() => { signal.removeEventListener("abort", abort); resolve(); }, ms); signal.addEventListener("abort", abort, { once: true }); }); }
17
+ export function object(value) {
18
+ return typeof value === "object" && value !== null && !Array.isArray(value);
19
+ }
20
+ export function identifier(value) {
21
+ if (!/^[A-Za-z0-9_-]+$/.test(value))
22
+ throw new AstrogateError("invalid_resource_reference", 0, "", false);
23
+ return value;
24
+ }
25
+ export function validateBudget(options) {
26
+ if (!Number.isSafeInteger(options.deadlineMs) ||
27
+ options.deadlineMs <= 0 ||
28
+ options.deadlineMs > timerMaximumMs ||
29
+ !Number.isSafeInteger(options.maxAttempts) ||
30
+ options.maxAttempts < 1)
31
+ throw new AstrogateError("explicit_transport_budget_required", 0, "", false);
32
+ }
33
+ // Native browser/Node timers use signed 32-bit millisecond delays. Durable
34
+ // operations outlive transport calls and are polled instead of held for days.
35
+ const timerMaximumMs = 2 ** 31 - 1;
36
+ function optionalText(value, key) {
37
+ const field = value[key];
38
+ if (field !== undefined && typeof field !== "string")
39
+ throw new AstrogateError("invalid_error_response", 0, "", false);
40
+ return field;
41
+ }
42
+ function delay(ms, signal) {
43
+ return new Promise((resolve, reject) => {
44
+ if (signal.aborted) {
45
+ reject(signal.reason);
46
+ return;
47
+ }
48
+ const abort = () => {
49
+ clearTimeout(timer);
50
+ reject(signal.reason);
51
+ };
52
+ const timer = setTimeout(() => {
53
+ signal.removeEventListener("abort", abort);
54
+ resolve();
55
+ }, ms);
56
+ signal.addEventListener("abort", abort, { once: true });
57
+ });
58
+ }
26
59
  export async function requestResponse(url, options, input, signal) {
27
60
  validateBudget(options);
28
- const combined = signal ? AbortSignal.any([AbortSignal.timeout(options.deadlineMs), signal]) : AbortSignal.timeout(options.deadlineMs);
61
+ const combined = signal
62
+ ? AbortSignal.any([AbortSignal.timeout(options.deadlineMs), signal])
63
+ : AbortSignal.timeout(options.deadlineMs);
29
64
  let last;
30
65
  for (let attempt = 0; attempt < options.maxAttempts; attempt++) {
31
66
  let retryAfter = 0;
32
67
  try {
33
- const response = await fetch(url, { ...input, signal: combined, redirect: "error", cache: "no-store" });
68
+ const response = await fetch(url, {
69
+ ...input,
70
+ signal: combined,
71
+ redirect: "error",
72
+ cache: "no-store",
73
+ });
34
74
  if (response.ok)
35
75
  return response;
36
76
  const after = response.headers.get("retry-after");
37
77
  if (after) {
38
78
  const seconds = Number(after);
39
- retryAfter = Number.isFinite(seconds) ? Math.max(0, seconds * 1000) : Math.max(0, Date.parse(after) - Date.now());
79
+ const parsed = Number.isFinite(seconds)
80
+ ? seconds * 1000
81
+ : Date.parse(after) - Date.now();
82
+ retryAfter = Number.isFinite(parsed) ? Math.max(0, parsed) : 0;
40
83
  }
41
84
  let value;
42
85
  try {
@@ -45,9 +88,17 @@ export async function requestResponse(url, options, input, signal) {
45
88
  catch {
46
89
  throw new AstrogateError("invalid_error_response", response.status, "", false);
47
90
  }
48
- if (!object(value) || typeof value.code !== "string" || typeof value.requestId !== "string" || typeof value.retryable !== "boolean")
91
+ if (!object(value) ||
92
+ typeof value.code !== "string" ||
93
+ typeof value.requestId !== "string" ||
94
+ typeof value.retryable !== "boolean")
49
95
  throw new AstrogateError("invalid_error_response", response.status, "", false);
50
- throw new AstrogateError(value.code, response.status, value.requestId, value.retryable);
96
+ throw new AstrogateError(value.code, response.status, value.requestId, value.retryable, {
97
+ message: optionalText(value, "message"),
98
+ scope: optionalText(value, "scope"),
99
+ dimension: optionalText(value, "dimension"),
100
+ reason: optionalText(value, "reason"),
101
+ });
51
102
  }
52
103
  catch (error) {
53
104
  if (combined.aborted)
@@ -57,7 +108,7 @@ export async function requestResponse(url, options, input, signal) {
57
108
  throw error;
58
109
  if (attempt + 1 === options.maxAttempts)
59
110
  break;
60
- await delay(Math.max(retryAfter, 100 * 2 ** attempt * (0.5 + Math.random())), combined);
111
+ await delay(Math.min(options.deadlineMs, Math.max(retryAfter, 100 * 2 ** attempt * (0.5 + Math.random()))), combined);
61
112
  }
62
113
  }
63
114
  if (last instanceof AstrogateError)
@@ -83,7 +134,14 @@ export function managementAuthority(options) {
83
134
  if (typeof window !== "undefined")
84
135
  throw new AstrogateError("server_only_entrypoint", 0, "", false);
85
136
  const endpoint = new URL(options.endpoint);
86
- if ((endpoint.protocol !== "https:" && !(endpoint.protocol === "http:" && ["127.0.0.1", "[::1]", "localhost"].includes(endpoint.hostname))) || endpoint.username || endpoint.password || endpoint.search || endpoint.hash || endpoint.pathname !== "/")
137
+ if ((endpoint.protocol !== "https:" &&
138
+ !(endpoint.protocol === "http:" &&
139
+ ["127.0.0.1", "[::1]", "localhost"].includes(endpoint.hostname))) ||
140
+ endpoint.username ||
141
+ endpoint.password ||
142
+ endpoint.search ||
143
+ endpoint.hash ||
144
+ endpoint.pathname !== "/")
87
145
  throw new AstrogateError("unsafe_management_authority", 0, "", false);
88
146
  validateBudget(options);
89
147
  if (!options.token)
package/dist/inventory.js CHANGED
@@ -6,6 +6,8 @@ export function listQuery(options) {
6
6
  throw new AstrogateError("invalid_page_limit", 0, "", false);
7
7
  query.set("limit", String(options.limit));
8
8
  }
9
+ if (options.search !== undefined)
10
+ query.set("search", options.search);
9
11
  if (options.cursor !== undefined)
10
12
  query.set("cursor", options.cursor);
11
13
  return query.size ? `?${query}` : "";
@@ -1,4 +1,5 @@
1
1
  import { type PageOptions } from "./inventory.js";
2
+ export type { AdmissionPolicy, AdmissionState, AdmissionUsagePage, UsageQuery } from "./admission.js";
2
3
  export { AstrogateError } from "./http.js";
3
4
  import type { components } from "./wire.gen.js";
4
5
  type Schemas = components["schemas"];
@@ -47,6 +48,25 @@ export interface ManagementOptions {
47
48
  maxAttempts: number;
48
49
  }
49
50
  export declare function createManagement(options: ManagementOptions): {
51
+ admission: {
52
+ get: (signal?: AbortSignal) => Promise<{
53
+ policy: components["schemas"]["AdmissionPolicy"];
54
+ occupancy: components["schemas"]["AdmissionOccupancy"][];
55
+ storageCredits: components["schemas"]["TenantStorageCredit"][];
56
+ }>;
57
+ assign: (input: {
58
+ profileId: string;
59
+ expectedRevision: number;
60
+ }, signal?: AbortSignal) => Promise<{
61
+ revision: number;
62
+ profile: components["schemas"]["AdmissionProfile"];
63
+ assignedAt: string;
64
+ }>;
65
+ usage: (query: import("./admission.js").UsageQuery, signal?: AbortSignal) => Promise<{
66
+ points: components["schemas"]["AdmissionUsagePoint"][];
67
+ next?: string;
68
+ }>;
69
+ };
50
70
  projects: {
51
71
  create: (input: CreateProject, controls: RequestOptions) => Promise<{
52
72
  operationId: string;
@@ -272,6 +292,11 @@ export declare function createManagement(options: ManagementOptions): {
272
292
  }>;
273
293
  };
274
294
  environments: {
295
+ create: (project: string, input: Schemas["CreateEnvironment"], controls: RequestOptions) => Promise<{
296
+ operationId: string;
297
+ resourceId: string;
298
+ statusUrl: string;
299
+ }>;
275
300
  list: (project: string, page?: PageOptions, signal?: AbortSignal) => Promise<import("./inventory.js").InventoryPage<{
276
301
  id: string;
277
302
  projectId: string;
@@ -3,6 +3,7 @@ import { project, environment, placementState, placementLocations, operation, op
3
3
  import { listQuery, pageOf } from "./inventory.js";
4
4
  import { managementAuthority } from "./http.js";
5
5
  import { serviceBindings } from "./services.js";
6
+ import { admissionClient } from "./admission.js";
6
7
  export { AstrogateError } from "./http.js";
7
8
  function object(value) {
8
9
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -66,6 +67,7 @@ export function createManagement(options) {
66
67
  return requestJSON(new URL(base + path, endpoint), options, { method: method ?? (body === undefined ? "GET" : "POST"), headers, body }, validate, controls?.signal ?? signal);
67
68
  }
68
69
  return {
70
+ admission: admissionClient(options, endpoint, base),
69
71
  projects: { create: (input, controls) => request(`/projects`, accepted, input, controls), list: (page = {}, signal) => request(`/projects${listQuery(page)}`, pageOf(project), undefined, undefined, signal), get: (resource, signal) => request(`/projects/${id(resource)}`, project, undefined, undefined, signal) },
70
72
  repositories: {
71
73
  create: (project, input, controls) => request(`/projects/${id(project)}/repository`, accepted, input, controls),
@@ -121,6 +123,7 @@ export function createManagement(options) {
121
123
  create: (project, environment, input, controls) => request(`/projects/${id(project)}/environments/${id(environment)}/deployments`, accepted, input, controls),
122
124
  },
123
125
  environments: {
126
+ create: (project, input, controls) => request(`/projects/${id(project)}/environments`, accepted, input, controls),
124
127
  list: (project, page = {}, signal) => request(`/projects/${id(project)}/environments${listQuery(page)}`, pageOf(environment), undefined, undefined, signal),
125
128
  get: (project, resource, signal) => request(`/projects/${id(project)}/environments/${id(resource)}`, environment, undefined, undefined, signal),
126
129
  getPlacement: (project, environment, signal) => request(`/projects/${id(project)}/environments/${id(environment)}/placement`, placementState, undefined, undefined, signal),
@@ -0,0 +1,8 @@
1
+ import type { components } from "./wire.gen.js";
2
+ export type ResourceSummary = components["schemas"]["ResourceSummary"];
3
+ export type ResourceRelationships = components["schemas"]["ResourceRelationships"];
4
+ export type ResourceNode = components["schemas"]["ResourceNode"];
5
+ export type ResourceRelationship = components["schemas"]["ResourceRelationship"];
6
+ export type ResourceSummaryPage = components["schemas"]["ResourceSummaryPage"];
7
+ export declare function resourceSummary(value: unknown): value is ResourceSummary;
8
+ export declare function resourceRelationships(value: unknown): value is ResourceRelationships;
@@ -0,0 +1,73 @@
1
+ import { object } from "./http.js";
2
+ function strings(value, fields) {
3
+ return object(value) && fields.every((key) => typeof value[key] === "string");
4
+ }
5
+ export function resourceSummary(value) {
6
+ return strings(value, [
7
+ "product",
8
+ "tenant",
9
+ "project",
10
+ "projectName",
11
+ "environment",
12
+ "environmentName",
13
+ "environmentKind",
14
+ "cell",
15
+ "location",
16
+ "phase",
17
+ ]);
18
+ }
19
+ function dateOrNull(value) {
20
+ return (value === null ||
21
+ (typeof value === "string" && Number.isFinite(Date.parse(value))));
22
+ }
23
+ export function resourceRelationships(value) {
24
+ if (!object(value) ||
25
+ !resourceSummary(value.resource) ||
26
+ !Number.isSafeInteger(value.epoch) ||
27
+ !dateOrNull(value.observedAt) ||
28
+ !dateOrNull(value.validUntil) ||
29
+ typeof value.fresh !== "boolean")
30
+ return false;
31
+ if (!Array.isArray(value.nodes) ||
32
+ !value.nodes.every((node) => strings(node, [
33
+ "id",
34
+ "kind",
35
+ "label",
36
+ "resourceId",
37
+ "providerId",
38
+ "cell",
39
+ "location",
40
+ "state",
41
+ "allocation",
42
+ "evidence",
43
+ "operationId",
44
+ ])))
45
+ return false;
46
+ const ids = new Set(value.nodes.map((node) => node.id));
47
+ if (ids.size !== value.nodes.length ||
48
+ !Array.isArray(value.relationships) ||
49
+ !value.relationships.every((edge) => strings(edge, ["id", "source", "target", "kind", "evidence"]) &&
50
+ object(edge) &&
51
+ ids.has(edge.source) &&
52
+ ids.has(edge.target)))
53
+ return false;
54
+ if (!object(value.recovery) ||
55
+ !strings(value.recovery, [
56
+ "protection",
57
+ "targetCell",
58
+ "targetLocation",
59
+ "automaticFailover",
60
+ ]) ||
61
+ value.recovery.automaticFailover !== "not-established" ||
62
+ !dateOrNull(value.recovery.checkpointAt))
63
+ return false;
64
+ const test = value.recovery.latestTest;
65
+ if (test !== null &&
66
+ (!strings(test, ["operationId", "state", "recordedAt"]) ||
67
+ !object(test) ||
68
+ typeof test.recordedAt !== "string" ||
69
+ !Number.isFinite(Date.parse(test.recordedAt))))
70
+ return false;
71
+ return (Array.isArray(value.notes) &&
72
+ value.notes.every((note) => typeof note === "string"));
73
+ }
@@ -1,4 +1,7 @@
1
1
  import { type TransportBudget } from "./http.js";
2
+ import { type UsageQuery } from "./admission.js";
3
+ export type { AdmissionState, AdmissionPolicy, AdmissionUsagePage, UsageQuery } from "./admission.js";
4
+ export type { ResourceSummary, ResourceRelationships, ResourceNode, ResourceRelationship, ResourceSummaryPage } from "./operator-resources.js";
2
5
  import { type PageOptions } from "./inventory.js";
3
6
  import type { components } from "./wire.gen.js";
4
7
  export { AstrogateError } from "./http.js";
@@ -9,6 +12,54 @@ export type OperatorOptions = TransportBudget & {
9
12
  export type { PageOptions } from "./inventory.js";
10
13
  type EnvironmentTopology = components["schemas"]["EnvironmentTopology"];
11
14
  export declare function createOperator(options: OperatorOptions): {
15
+ admission: {
16
+ get: (product: string, tenant: string, signal?: AbortSignal) => Promise<{
17
+ policy: components["schemas"]["AdmissionPolicy"];
18
+ occupancy: components["schemas"]["AdmissionOccupancy"][];
19
+ storageCredits: components["schemas"]["TenantStorageCredit"][];
20
+ }>;
21
+ usage: (product: string, tenant: string, query: UsageQuery, signal?: AbortSignal) => Promise<{
22
+ points: components["schemas"]["AdmissionUsagePoint"][];
23
+ next?: string;
24
+ }>;
25
+ };
26
+ resources: {
27
+ list: (product: string, tenant: string, page?: PageOptions, signal?: AbortSignal) => Promise<import("./inventory.js").InventoryPage<{
28
+ product: string;
29
+ tenant: string;
30
+ project: string;
31
+ projectName: string;
32
+ environment: string;
33
+ environmentName: string;
34
+ environmentKind: string;
35
+ cell: string;
36
+ location: string;
37
+ phase: string;
38
+ }>>;
39
+ relationships: (product: string, tenant: string, project: string, environment: string, signal?: AbortSignal) => Promise<{
40
+ resource: components["schemas"]["ResourceSummary"];
41
+ epoch: number;
42
+ observedAt: string | null;
43
+ validUntil: string | null;
44
+ fresh: boolean;
45
+ nodes: components["schemas"]["ResourceNode"][];
46
+ relationships: components["schemas"]["ResourceRelationship"][];
47
+ recovery: components["schemas"]["ResourceRecovery"];
48
+ notes: string[];
49
+ }>;
50
+ forHost: (providerId: string, page?: PageOptions, signal?: AbortSignal) => Promise<import("./inventory.js").InventoryPage<{
51
+ product: string;
52
+ tenant: string;
53
+ project: string;
54
+ projectName: string;
55
+ environment: string;
56
+ environmentName: string;
57
+ environmentKind: string;
58
+ cell: string;
59
+ location: string;
60
+ phase: string;
61
+ }>>;
62
+ };
12
63
  products: {
13
64
  list: (page?: PageOptions, signal?: AbortSignal) => Promise<import("./inventory.js").InventoryPage<{
14
65
  id: string;
package/dist/operator.js CHANGED
@@ -2,6 +2,8 @@
2
2
  // authenticate here. Issuance and mutation remain installation-owned.
3
3
  import { identifier, managementAuthority, object, requestJSON, } from "./http.js";
4
4
  import { project, environment, placementState, operation, operationLog, operatorOperationDetail, buildingBlock, hostnameBinding } from "./management-read.js";
5
+ import { admissionState, admissionUsage } from "./admission.js";
6
+ import { resourceSummary, resourceRelationships } from "./operator-resources.js";
5
7
  import { listQuery, pageOf } from "./inventory.js";
6
8
  export { AstrogateError } from "./http.js";
7
9
  function catalogEntry(value) {
@@ -14,13 +16,32 @@ function environmentTopology(value) {
14
16
  }
15
17
  export function createOperator(options) {
16
18
  const endpoint = managementAuthority(options);
17
- function read(path, validate, signal) {
18
- return requestJSON(new URL(`/v1/operator/products${path}`, endpoint), options, { method: "GET", headers: { authorization: `Bearer ${options.token}` } }, validate, signal);
19
+ function read(path, validate, signal, root = "/v1/operator/products") {
20
+ return requestJSON(new URL(`${root}${path}`, endpoint), options, { method: "GET", headers: { authorization: `Bearer ${options.token}` } }, validate, signal);
19
21
  }
20
22
  const scope = (product, tenant) => `/${identifier(product)}/tenants/${identifier(tenant)}`;
21
23
  const projectPath = (product, tenant, id) => `${scope(product, tenant)}/projects/${identifier(id)}`;
22
24
  const environmentPath = (product, tenant, project, id) => `${projectPath(product, tenant, project)}/environments/${identifier(id)}`;
23
25
  return {
26
+ admission: {
27
+ get: (product, tenant, signal) => read(`${scope(product, tenant)}/admission`, admissionState, signal),
28
+ usage: (product, tenant, query, signal) => {
29
+ const parameters = new URLSearchParams();
30
+ for (const [key, value] of Object.entries(query))
31
+ if (value !== undefined)
32
+ parameters.set(key, String(value));
33
+ return read(`${scope(product, tenant)}/usage?${parameters}`, admissionUsage, signal);
34
+ },
35
+ },
36
+ resources: {
37
+ list: (product, tenant, page = {}, signal) => read(`${scope(product, tenant)}/resources${listQuery(page)}`, pageOf(resourceSummary), signal),
38
+ relationships: (product, tenant, project, environment, signal) => read(`${environmentPath(product, tenant, project, environment)}/relationships`, resourceRelationships, signal),
39
+ forHost: (providerId, page = {}, signal) => {
40
+ const query = new URLSearchParams(listQuery(page));
41
+ query.set("providerId", providerId);
42
+ return read(`?${query}`, pageOf(resourceSummary), signal, "/v1/operator/host-environments");
43
+ },
44
+ },
24
45
  products: {
25
46
  list: (page = {}, signal) => read(listQuery(page), pageOf(catalogEntry), signal),
26
47
  },