@shipeasy/openapi 1.0.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/dist/index.d.ts +15 -0
- package/dist/index.js +4604 -0
- package/dist/resources/configs.d.ts +677 -0
- package/dist/resources/configs.js +2817 -0
- package/dist/resources/experiments.d.ts +775 -0
- package/dist/resources/experiments.js +649 -0
- package/dist/resources/gates.d.ts +670 -0
- package/dist/resources/gates.js +473 -0
- package/dist/resources/index.d.ts +2522 -0
- package/dist/resources/index.js +4528 -0
- package/dist/resources/killswitches.d.ts +432 -0
- package/dist/resources/killswitches.js +2813 -0
- package/dist/resources/universes.d.ts +182 -0
- package/dist/resources/universes.js +224 -0
- package/dist/transport.d.ts +30 -0
- package/dist/transport.js +44 -0
- package/openapi.json +6011 -0
- package/package.json +58 -0
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { UniverseCreateInput, UniverseUpdateInput } from '@shipeasy/core/schemas/universes';
|
|
3
|
+
export { UniverseCreateInput, UniverseUpdateInput } from '@shipeasy/core/schemas/universes';
|
|
4
|
+
import { PageQuery, Page } from '@shipeasy/core/pagination';
|
|
5
|
+
import { Transport } from '../transport.js';
|
|
6
|
+
|
|
7
|
+
interface Universe {
|
|
8
|
+
id: string;
|
|
9
|
+
name: string;
|
|
10
|
+
unitType: string;
|
|
11
|
+
holdoutRange: [number, number] | null;
|
|
12
|
+
allocationPct?: number;
|
|
13
|
+
holdoutPct?: number;
|
|
14
|
+
updatedAt?: string;
|
|
15
|
+
}
|
|
16
|
+
interface UniversesClient {
|
|
17
|
+
list(opts?: Partial<PageQuery>): Promise<Page<Universe>>;
|
|
18
|
+
listAll(): Promise<Universe[]>;
|
|
19
|
+
resolve(idOrName: string): Promise<Universe>;
|
|
20
|
+
create(input: UniverseCreateInput): Promise<Universe>;
|
|
21
|
+
update(id: string, input: UniverseUpdateInput): Promise<Universe>;
|
|
22
|
+
delete(id: string): Promise<{
|
|
23
|
+
ok: true;
|
|
24
|
+
}>;
|
|
25
|
+
}
|
|
26
|
+
declare function universesClient(t: Transport): UniversesClient;
|
|
27
|
+
declare const universesResource: {
|
|
28
|
+
readonly name: "universes";
|
|
29
|
+
readonly basePath: "/api/admin/universes";
|
|
30
|
+
readonly describeOne: "universe";
|
|
31
|
+
readonly describeMany: "universes";
|
|
32
|
+
readonly tag: {
|
|
33
|
+
readonly name: "Universes";
|
|
34
|
+
readonly description: string;
|
|
35
|
+
};
|
|
36
|
+
readonly schemas: {
|
|
37
|
+
readonly create: z.ZodObject<{
|
|
38
|
+
name: z.ZodString;
|
|
39
|
+
unit_type: z.ZodDefault<z.ZodString>;
|
|
40
|
+
holdout_range: z.ZodDefault<z.ZodNullable<z.ZodTuple<[z.ZodNumber, z.ZodNumber], null>>>;
|
|
41
|
+
}, z.core.$strip>;
|
|
42
|
+
readonly update: z.ZodObject<{
|
|
43
|
+
holdout_range: z.ZodOptional<z.ZodNullable<z.ZodTuple<[z.ZodNumber, z.ZodNumber], null>>>;
|
|
44
|
+
}, z.core.$strip>;
|
|
45
|
+
};
|
|
46
|
+
readonly actions: readonly [];
|
|
47
|
+
readonly endpoints: readonly [{
|
|
48
|
+
readonly operationId: "listUniverses";
|
|
49
|
+
readonly method: "GET";
|
|
50
|
+
readonly path: "";
|
|
51
|
+
readonly summary: "List universes";
|
|
52
|
+
readonly description: "Returns a single page of universes ordered by `created_at desc, id desc`. The universes table has no `updated_at`, so this list is keyed on creation time.";
|
|
53
|
+
readonly response: z.ZodObject<{
|
|
54
|
+
data: z.ZodArray<z.ZodObject<{
|
|
55
|
+
id: z.ZodString;
|
|
56
|
+
name: z.ZodString;
|
|
57
|
+
unitType: z.ZodString;
|
|
58
|
+
holdoutRange: z.ZodNullable<z.ZodTuple<[z.ZodNumber, z.ZodNumber], null>>;
|
|
59
|
+
createdAt: z.ZodString;
|
|
60
|
+
}, z.core.$strip>>;
|
|
61
|
+
next_cursor: z.ZodNullable<z.ZodString>;
|
|
62
|
+
}, z.core.$strip>;
|
|
63
|
+
readonly examples: {
|
|
64
|
+
readonly response: {
|
|
65
|
+
readonly data: readonly [{
|
|
66
|
+
readonly id: "uni_01j7w8a1b2c3d4e5f6g7h8i9j0";
|
|
67
|
+
readonly name: "primary_users";
|
|
68
|
+
readonly unitType: "user_id";
|
|
69
|
+
readonly holdoutRange: readonly [9500, 9999];
|
|
70
|
+
readonly createdAt: "2026-04-12T10:14:08.000Z";
|
|
71
|
+
}];
|
|
72
|
+
readonly next_cursor: null;
|
|
73
|
+
};
|
|
74
|
+
};
|
|
75
|
+
readonly useCase: "Snapshot every universe in the project — for example to audit which `unit_type` and `holdout_range` are in use before launching a new experiment.";
|
|
76
|
+
}, {
|
|
77
|
+
readonly operationId: "createUniverse";
|
|
78
|
+
readonly method: "POST";
|
|
79
|
+
readonly path: "";
|
|
80
|
+
readonly summary: "Create a universe";
|
|
81
|
+
readonly description: string;
|
|
82
|
+
readonly successStatus: 201;
|
|
83
|
+
readonly request: z.ZodObject<{
|
|
84
|
+
name: z.ZodString;
|
|
85
|
+
unit_type: z.ZodDefault<z.ZodString>;
|
|
86
|
+
holdout_range: z.ZodDefault<z.ZodNullable<z.ZodTuple<[z.ZodNumber, z.ZodNumber], null>>>;
|
|
87
|
+
}, z.core.$strip>;
|
|
88
|
+
readonly response: z.ZodObject<{
|
|
89
|
+
id: z.ZodString;
|
|
90
|
+
name: z.ZodString;
|
|
91
|
+
}, z.core.$strip>;
|
|
92
|
+
readonly examples: {
|
|
93
|
+
readonly requestExamples: {
|
|
94
|
+
readonly minimal: {
|
|
95
|
+
readonly summary: "Minimal — name only";
|
|
96
|
+
readonly description: "Smallest valid body. Creates a `user_id`-keyed universe with no holdout.";
|
|
97
|
+
readonly value: {
|
|
98
|
+
readonly name: "primary_users";
|
|
99
|
+
};
|
|
100
|
+
};
|
|
101
|
+
readonly holdout: {
|
|
102
|
+
readonly summary: "Holdout — reserve 5% for measurement";
|
|
103
|
+
readonly description: "Reserves buckets 9500–9999 (5% of callers) as the holdout. Pro plan or higher.";
|
|
104
|
+
readonly value: {
|
|
105
|
+
readonly name: "primary_users";
|
|
106
|
+
readonly holdout_range: readonly [9500, 9999];
|
|
107
|
+
};
|
|
108
|
+
};
|
|
109
|
+
readonly accountLevel: {
|
|
110
|
+
readonly summary: "Account-level randomisation";
|
|
111
|
+
readonly description: "Bucket whole accounts together via `unit_type: 'account_id'`.";
|
|
112
|
+
readonly value: {
|
|
113
|
+
readonly name: "saas_accounts";
|
|
114
|
+
readonly unit_type: "account_id";
|
|
115
|
+
};
|
|
116
|
+
};
|
|
117
|
+
};
|
|
118
|
+
readonly response: {
|
|
119
|
+
readonly id: "uni_01j7w8a1b2c3d4e5f6g7h8i9j0";
|
|
120
|
+
readonly name: "primary_users";
|
|
121
|
+
};
|
|
122
|
+
};
|
|
123
|
+
readonly useCase: string;
|
|
124
|
+
}, {
|
|
125
|
+
readonly operationId: "updateUniverse";
|
|
126
|
+
readonly method: "PATCH";
|
|
127
|
+
readonly path: "/{id}";
|
|
128
|
+
readonly summary: "Update a universe";
|
|
129
|
+
readonly description: string;
|
|
130
|
+
readonly pathParams: {
|
|
131
|
+
readonly id: "Stable opaque universe id (`uni_…`).";
|
|
132
|
+
};
|
|
133
|
+
readonly request: z.ZodObject<{
|
|
134
|
+
holdout_range: z.ZodOptional<z.ZodNullable<z.ZodTuple<[z.ZodNumber, z.ZodNumber], null>>>;
|
|
135
|
+
}, z.core.$strip>;
|
|
136
|
+
readonly response: z.ZodObject<{
|
|
137
|
+
id: z.ZodString;
|
|
138
|
+
}, z.core.$strip>;
|
|
139
|
+
readonly examples: {
|
|
140
|
+
readonly requestExamples: {
|
|
141
|
+
readonly setHoldout: {
|
|
142
|
+
readonly summary: "Set holdout — reserve 5%";
|
|
143
|
+
readonly description: "Reserve buckets 9500–9999 as the holdout. Pro plan or higher.";
|
|
144
|
+
readonly value: {
|
|
145
|
+
readonly holdout_range: readonly [9500, 9999];
|
|
146
|
+
};
|
|
147
|
+
};
|
|
148
|
+
readonly removeHoldout: {
|
|
149
|
+
readonly summary: "Remove holdout";
|
|
150
|
+
readonly description: "Disable the holdout entirely.";
|
|
151
|
+
readonly value: {
|
|
152
|
+
readonly holdout_range: null;
|
|
153
|
+
};
|
|
154
|
+
};
|
|
155
|
+
};
|
|
156
|
+
readonly response: {
|
|
157
|
+
readonly id: "uni_01j7w8a1b2c3d4e5f6g7h8i9j0";
|
|
158
|
+
};
|
|
159
|
+
};
|
|
160
|
+
readonly useCase: string;
|
|
161
|
+
}, {
|
|
162
|
+
readonly operationId: "deleteUniverse";
|
|
163
|
+
readonly method: "DELETE";
|
|
164
|
+
readonly path: "/{id}";
|
|
165
|
+
readonly summary: "Delete a universe";
|
|
166
|
+
readonly description: "Soft-deletes the universe. Returns `409` if any non-archived experiment still references it — archive those experiments first.";
|
|
167
|
+
readonly pathParams: {
|
|
168
|
+
readonly id: "Stable opaque universe id.";
|
|
169
|
+
};
|
|
170
|
+
readonly response: z.ZodObject<{
|
|
171
|
+
ok: z.ZodLiteral<true>;
|
|
172
|
+
}, z.core.$strip>;
|
|
173
|
+
readonly examples: {
|
|
174
|
+
readonly response: {
|
|
175
|
+
readonly ok: true;
|
|
176
|
+
};
|
|
177
|
+
};
|
|
178
|
+
readonly useCase: "Tear down a universe after every experiment that used it has been archived.";
|
|
179
|
+
}];
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
export { type Universe, type UniversesClient, universesClient, universesResource };
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
// src/resources/universes.ts
|
|
2
|
+
import { z as z2 } from "zod";
|
|
3
|
+
|
|
4
|
+
// ../core/src/schemas/universes.ts
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
var universeNameSchema = z.string().regex(/^[a-z0-9][a-z0-9_-]{0,63}$/).describe(
|
|
7
|
+
"Stable universe key referenced by experiments via `universe: '<name>'`. Lowercase letters, digits, `_` or `-`, max 64 chars. Immutable after create."
|
|
8
|
+
);
|
|
9
|
+
var holdoutRangeSchema = z.tuple([z.number().int().min(0).max(9999), z.number().int().min(0).max(9999)]).refine(([lo, hi]) => lo <= hi, "lo must be <= hi").nullable().describe(
|
|
10
|
+
"Inclusive `[lo, hi]` bucket range (0\u20139999) reserved as the **holdout** \u2014 callers hashed into this slice are excluded from every experiment in the universe. `null` disables the holdout. Pro plan or higher required."
|
|
11
|
+
);
|
|
12
|
+
var universeCreateSchema = z.object({
|
|
13
|
+
name: universeNameSchema,
|
|
14
|
+
unit_type: z.string().default("user_id").describe(
|
|
15
|
+
"Unit of randomisation. Typically `user_id`. Use `account_id` to keep whole accounts in the same group across an experiment."
|
|
16
|
+
),
|
|
17
|
+
holdout_range: holdoutRangeSchema.default(null)
|
|
18
|
+
}).describe("Body for `POST /api/admin/universes`. Only `name` is required.");
|
|
19
|
+
var universeUpdateSchema = z.object({
|
|
20
|
+
holdout_range: holdoutRangeSchema.optional()
|
|
21
|
+
}).describe(
|
|
22
|
+
"Body for `PATCH /api/admin/universes/{id}`. Only `holdout_range` is mutable \u2014 name and unit_type are immutable after create."
|
|
23
|
+
);
|
|
24
|
+
var universeResponseSchema = z.object({
|
|
25
|
+
id: z.string().describe("Stable opaque universe id."),
|
|
26
|
+
name: universeNameSchema,
|
|
27
|
+
unitType: z.string().describe("Unit of randomisation. Snake-case in request (`unit_type`), camelCase in response."),
|
|
28
|
+
holdoutRange: z.tuple([z.number(), z.number()]).nullable().describe("Reserved holdout bucket range, or `null` if none."),
|
|
29
|
+
createdAt: z.string().describe("ISO-8601 timestamp of creation.")
|
|
30
|
+
});
|
|
31
|
+
var universeCreateResponseSchema = z.object({
|
|
32
|
+
id: z.string().describe("Newly assigned universe id."),
|
|
33
|
+
name: universeNameSchema
|
|
34
|
+
});
|
|
35
|
+
var universeUpdateResponseSchema = z.object({
|
|
36
|
+
id: z.string().describe("Universe id that was updated.")
|
|
37
|
+
});
|
|
38
|
+
var universeDeleteResponseSchema = z.object({ ok: z.literal(true) });
|
|
39
|
+
|
|
40
|
+
// src/transport.ts
|
|
41
|
+
var ApiError = class extends Error {
|
|
42
|
+
constructor(message, status) {
|
|
43
|
+
super(message);
|
|
44
|
+
this.status = status;
|
|
45
|
+
this.name = "ApiError";
|
|
46
|
+
}
|
|
47
|
+
status;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
// src/resources/universes.ts
|
|
51
|
+
var universeListResponseSchema = z2.object({
|
|
52
|
+
data: z2.array(universeResponseSchema),
|
|
53
|
+
next_cursor: z2.string().nullable()
|
|
54
|
+
});
|
|
55
|
+
var BASE = "/api/admin/universes";
|
|
56
|
+
function universesClient(t) {
|
|
57
|
+
async function list(opts = {}) {
|
|
58
|
+
const query = {};
|
|
59
|
+
if (opts.limit !== void 0) query.limit = String(opts.limit);
|
|
60
|
+
if (opts.cursor) query.cursor = opts.cursor;
|
|
61
|
+
return t.request("GET", BASE, void 0, query);
|
|
62
|
+
}
|
|
63
|
+
async function listAll() {
|
|
64
|
+
const out = [];
|
|
65
|
+
let cursor;
|
|
66
|
+
do {
|
|
67
|
+
const page = await list({ limit: 500, cursor });
|
|
68
|
+
out.push(...page.data);
|
|
69
|
+
cursor = page.next_cursor ?? void 0;
|
|
70
|
+
} while (cursor);
|
|
71
|
+
return out;
|
|
72
|
+
}
|
|
73
|
+
async function resolve(idOrName) {
|
|
74
|
+
const all = await listAll();
|
|
75
|
+
const found = all.find((u) => u.id === idOrName) ?? all.find((u) => u.name === idOrName);
|
|
76
|
+
if (!found) throw new ApiError(`Universe '${idOrName}' not found`, 404);
|
|
77
|
+
return found;
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
list,
|
|
81
|
+
listAll,
|
|
82
|
+
resolve,
|
|
83
|
+
create: (input) => t.request("POST", BASE, universeCreateSchema.parse(input)),
|
|
84
|
+
update: (id, input) => t.request("PATCH", `${BASE}/${id}`, universeUpdateSchema.parse(input)),
|
|
85
|
+
delete: (id) => t.request("DELETE", `${BASE}/${id}`)
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
var universesResource = {
|
|
89
|
+
name: "universes",
|
|
90
|
+
basePath: BASE,
|
|
91
|
+
describeOne: "universe",
|
|
92
|
+
describeMany: "universes",
|
|
93
|
+
tag: {
|
|
94
|
+
name: "Universes",
|
|
95
|
+
description: [
|
|
96
|
+
"Universes: the shared bucketing space all experiments draw from.",
|
|
97
|
+
"",
|
|
98
|
+
"**Identity.** Each universe is keyed by a stable `name` (a-z, 0-9, `_`/`-`, max 64 chars). Experiments reference it via `universe: '<name>'`. The name is immutable.",
|
|
99
|
+
"",
|
|
100
|
+
"**Unit of randomisation.** `unit_type` selects the attribute hashed into a 0\u20139999 bucket \u2014 default `user_id` for per-user randomisation, `account_id` to keep a whole account in the same group.",
|
|
101
|
+
"",
|
|
102
|
+
"**Holdout.** `holdout_range` is an inclusive `[lo, hi]` bucket range (0\u20139999) reserved for measurement \u2014 callers hashed into the holdout are excluded from every experiment in the universe. Pro plan or higher.",
|
|
103
|
+
"",
|
|
104
|
+
"**Deletion.** Blocked while any non-archived experiment references the universe \u2014 archive those first."
|
|
105
|
+
].join("\n")
|
|
106
|
+
},
|
|
107
|
+
schemas: {
|
|
108
|
+
create: universeCreateSchema,
|
|
109
|
+
update: universeUpdateSchema
|
|
110
|
+
},
|
|
111
|
+
actions: [],
|
|
112
|
+
endpoints: [
|
|
113
|
+
{
|
|
114
|
+
operationId: "listUniverses",
|
|
115
|
+
method: "GET",
|
|
116
|
+
path: "",
|
|
117
|
+
summary: "List universes",
|
|
118
|
+
description: "Returns a single page of universes ordered by `created_at desc, id desc`. The universes table has no `updated_at`, so this list is keyed on creation time.",
|
|
119
|
+
response: universeListResponseSchema,
|
|
120
|
+
examples: {
|
|
121
|
+
response: {
|
|
122
|
+
data: [
|
|
123
|
+
{
|
|
124
|
+
id: "uni_01j7w8a1b2c3d4e5f6g7h8i9j0",
|
|
125
|
+
name: "primary_users",
|
|
126
|
+
unitType: "user_id",
|
|
127
|
+
holdoutRange: [9500, 9999],
|
|
128
|
+
createdAt: "2026-04-12T10:14:08.000Z"
|
|
129
|
+
}
|
|
130
|
+
],
|
|
131
|
+
next_cursor: null
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
useCase: "Snapshot every universe in the project \u2014 for example to audit which `unit_type` and `holdout_range` are in use before launching a new experiment."
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
operationId: "createUniverse",
|
|
138
|
+
method: "POST",
|
|
139
|
+
path: "",
|
|
140
|
+
summary: "Create a universe",
|
|
141
|
+
description: [
|
|
142
|
+
"Creates a new universe. Only `name` is required \u2014 `unit_type` defaults to `user_id` and `holdout_range` defaults to `null` (no holdout).",
|
|
143
|
+
"",
|
|
144
|
+
"Returns `409` if `name` already exists in the project. Returns `403` if you supply `holdout_range` on a plan below Pro."
|
|
145
|
+
].join("\n"),
|
|
146
|
+
successStatus: 201,
|
|
147
|
+
request: universeCreateSchema,
|
|
148
|
+
response: universeCreateResponseSchema,
|
|
149
|
+
examples: {
|
|
150
|
+
requestExamples: {
|
|
151
|
+
minimal: {
|
|
152
|
+
summary: "Minimal \u2014 name only",
|
|
153
|
+
description: "Smallest valid body. Creates a `user_id`-keyed universe with no holdout.",
|
|
154
|
+
value: { name: "primary_users" }
|
|
155
|
+
},
|
|
156
|
+
holdout: {
|
|
157
|
+
summary: "Holdout \u2014 reserve 5% for measurement",
|
|
158
|
+
description: "Reserves buckets 9500\u20139999 (5% of callers) as the holdout. Pro plan or higher.",
|
|
159
|
+
value: { name: "primary_users", holdout_range: [9500, 9999] }
|
|
160
|
+
},
|
|
161
|
+
accountLevel: {
|
|
162
|
+
summary: "Account-level randomisation",
|
|
163
|
+
description: "Bucket whole accounts together via `unit_type: 'account_id'`.",
|
|
164
|
+
value: { name: "saas_accounts", unit_type: "account_id" }
|
|
165
|
+
}
|
|
166
|
+
},
|
|
167
|
+
response: { id: "uni_01j7w8a1b2c3d4e5f6g7h8i9j0", name: "primary_users" }
|
|
168
|
+
},
|
|
169
|
+
useCase: [
|
|
170
|
+
'- **Default universe** \u2014 `{ "name": "primary_users" }`. Per-user randomisation, no holdout.',
|
|
171
|
+
"- **Reserved holdout** \u2014 supply `holdout_range` to carve out a measurement slice excluded from all experiments.",
|
|
172
|
+
"- **Account-level** \u2014 `unit_type: 'account_id'` so multi-seat accounts see one consistent variant."
|
|
173
|
+
].join("\n")
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
operationId: "updateUniverse",
|
|
177
|
+
method: "PATCH",
|
|
178
|
+
path: "/{id}",
|
|
179
|
+
summary: "Update a universe",
|
|
180
|
+
description: [
|
|
181
|
+
"Partial update. Only `holdout_range` is mutable \u2014 `name` and `unit_type` are immutable after create.",
|
|
182
|
+
"",
|
|
183
|
+
'Pass `"holdout_range": null` to remove an existing holdout.'
|
|
184
|
+
].join("\n"),
|
|
185
|
+
pathParams: { id: "Stable opaque universe id (`uni_\u2026`)." },
|
|
186
|
+
request: universeUpdateSchema,
|
|
187
|
+
response: universeUpdateResponseSchema,
|
|
188
|
+
examples: {
|
|
189
|
+
requestExamples: {
|
|
190
|
+
setHoldout: {
|
|
191
|
+
summary: "Set holdout \u2014 reserve 5%",
|
|
192
|
+
description: "Reserve buckets 9500\u20139999 as the holdout. Pro plan or higher.",
|
|
193
|
+
value: { holdout_range: [9500, 9999] }
|
|
194
|
+
},
|
|
195
|
+
removeHoldout: {
|
|
196
|
+
summary: "Remove holdout",
|
|
197
|
+
description: "Disable the holdout entirely.",
|
|
198
|
+
value: { holdout_range: null }
|
|
199
|
+
}
|
|
200
|
+
},
|
|
201
|
+
response: { id: "uni_01j7w8a1b2c3d4e5f6g7h8i9j0" }
|
|
202
|
+
},
|
|
203
|
+
useCase: [
|
|
204
|
+
"- **Adjust holdout** \u2014 change the reserved measurement slice without recreating experiments.",
|
|
205
|
+
'- **Remove holdout** \u2014 `{ "holdout_range": null }`.'
|
|
206
|
+
].join("\n")
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
operationId: "deleteUniverse",
|
|
210
|
+
method: "DELETE",
|
|
211
|
+
path: "/{id}",
|
|
212
|
+
summary: "Delete a universe",
|
|
213
|
+
description: "Soft-deletes the universe. Returns `409` if any non-archived experiment still references it \u2014 archive those experiments first.",
|
|
214
|
+
pathParams: { id: "Stable opaque universe id." },
|
|
215
|
+
response: universeDeleteResponseSchema,
|
|
216
|
+
examples: { response: { ok: true } },
|
|
217
|
+
useCase: "Tear down a universe after every experiment that used it has been archived."
|
|
218
|
+
}
|
|
219
|
+
]
|
|
220
|
+
};
|
|
221
|
+
export {
|
|
222
|
+
universesClient,
|
|
223
|
+
universesResource
|
|
224
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
declare class ApiError extends Error {
|
|
2
|
+
readonly status: number;
|
|
3
|
+
constructor(message: string, status: number);
|
|
4
|
+
}
|
|
5
|
+
interface Transport {
|
|
6
|
+
request<T>(method: HttpMethod, path: string, body?: unknown, query?: Record<string, string>): Promise<T>;
|
|
7
|
+
}
|
|
8
|
+
type HttpMethod = "GET" | "POST" | "PATCH" | "PUT" | "DELETE";
|
|
9
|
+
interface AuthSnapshot {
|
|
10
|
+
token: string;
|
|
11
|
+
projectId: string;
|
|
12
|
+
}
|
|
13
|
+
interface HttpTransportOptions {
|
|
14
|
+
/** Base URL for the admin API, e.g. "https://shipeasy.ai" (no trailing slash required). */
|
|
15
|
+
baseUrl: string;
|
|
16
|
+
/**
|
|
17
|
+
* Returns the auth token + project_id for the next request. Called per request
|
|
18
|
+
* so consumers can refresh credentials or apply per-call project overrides.
|
|
19
|
+
*/
|
|
20
|
+
getAuth(): AuthSnapshot;
|
|
21
|
+
/**
|
|
22
|
+
* Optional gate run before any non-GET request. Throw to abort. CLI uses this
|
|
23
|
+
* to enforce `.shipeasy` binding for mutations; MCP uses it to surface a
|
|
24
|
+
* notBound error. GET requests bypass this hook.
|
|
25
|
+
*/
|
|
26
|
+
beforeMutation?(method: HttpMethod, path: string): void | Promise<void>;
|
|
27
|
+
}
|
|
28
|
+
declare function createHttpTransport(opts: HttpTransportOptions): Transport;
|
|
29
|
+
|
|
30
|
+
export { ApiError, type AuthSnapshot, type HttpMethod, type HttpTransportOptions, type Transport, createHttpTransport };
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// src/transport.ts
|
|
2
|
+
var ApiError = class extends Error {
|
|
3
|
+
constructor(message, status) {
|
|
4
|
+
super(message);
|
|
5
|
+
this.status = status;
|
|
6
|
+
this.name = "ApiError";
|
|
7
|
+
}
|
|
8
|
+
status;
|
|
9
|
+
};
|
|
10
|
+
function createHttpTransport(opts) {
|
|
11
|
+
const baseUrl = opts.baseUrl.replace(/\/$/, "");
|
|
12
|
+
return {
|
|
13
|
+
async request(method, path, body, query) {
|
|
14
|
+
if (method !== "GET" && opts.beforeMutation) {
|
|
15
|
+
await opts.beforeMutation(method, path);
|
|
16
|
+
}
|
|
17
|
+
const auth = opts.getAuth();
|
|
18
|
+
const url = new URL(`${baseUrl}${path}`);
|
|
19
|
+
if (query) {
|
|
20
|
+
for (const [k, v] of Object.entries(query)) url.searchParams.set(k, v);
|
|
21
|
+
}
|
|
22
|
+
const res = await fetch(url.toString(), {
|
|
23
|
+
method,
|
|
24
|
+
headers: {
|
|
25
|
+
"X-SDK-Key": auth.token,
|
|
26
|
+
"X-Project-Id": auth.projectId,
|
|
27
|
+
"Content-Type": "application/json"
|
|
28
|
+
},
|
|
29
|
+
...body !== void 0 ? { body: JSON.stringify(body) } : {}
|
|
30
|
+
});
|
|
31
|
+
if (res.status === 204 || res.status === 202) return void 0;
|
|
32
|
+
const json = await res.json().catch(() => ({ error: `HTTP ${res.status}` }));
|
|
33
|
+
if (!res.ok) {
|
|
34
|
+
const msg = json.error ?? `HTTP ${res.status}`;
|
|
35
|
+
throw new ApiError(msg, res.status);
|
|
36
|
+
}
|
|
37
|
+
return json;
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
export {
|
|
42
|
+
ApiError,
|
|
43
|
+
createHttpTransport
|
|
44
|
+
};
|