@hasna/instructions 0.6.1 → 0.7.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/README.md +123 -6
- package/dist/chunks/{apply-ynfn1av7.js → apply-7svah2qm.js} +5 -3
- package/dist/chunks/{apply-sfzja1f3.js → apply-jp13sa1e.js} +6 -3
- package/dist/chunks/database-wstmr56z.js +23 -0
- package/dist/chunks/{index-85n5wnd8.js → index-1cnyjnd7.js} +5 -7
- package/dist/chunks/{index-pkscjnj5.js → index-3djthcm6.js} +0 -1
- package/dist/chunks/{index-v7dkb384.js → index-4pfq42t5.js} +129 -77
- package/dist/chunks/index-5d7zdftc.js +186 -0
- package/dist/chunks/{index-1hpwskwd.js → index-7m4j8a33.js} +205 -329
- package/dist/chunks/index-8rb7ng2y.js +148 -0
- package/dist/chunks/index-arrrx0ed.js +344 -0
- package/dist/chunks/{index-9p9gn7gh.js → index-bzyxvwcd.js} +5 -7
- package/dist/chunks/index-fpeyzn3n.js +186 -0
- package/dist/chunks/{index-axhwg61p.js → index-fwbs6tma.js} +15 -4
- package/dist/chunks/index-n3fjspr0.js +119 -0
- package/dist/chunks/{index-e9epbsvs.js → index-z3ma4aqk.js} +15 -4
- package/dist/chunks/legacy-store-migration-z2m5yzyk.js +322 -0
- package/dist/chunks/{local-a9nen65z.js → local-5wvv5xv3.js} +2 -2
- package/dist/chunks/{local-0d8qrc92.js → local-zxa5dpf4.js} +13 -188
- package/dist/chunks/s3-backup-n2z4fwyh.js +240 -0
- package/dist/chunks/s3-config-hhght91k.js +14 -0
- package/dist/chunks/s3-object-store-90tgbh6k.js +14 -0
- package/dist/chunks/{sync-m46fc257.js → sync-2075deve.js} +5 -4
- package/dist/chunks/{sync-vy1yjvs6.js → sync-r8t7zsp3.js} +4 -4
- package/dist/chunks/{template-02wjvv05.js → template-fsfxpe4p.js} +1 -1
- package/dist/chunks/{template-ke972qqe.js → template-wgnzdn7h.js} +1 -1
- package/dist/cli/index.js +1011 -247
- package/dist/data/config-store.d.ts +1 -0
- package/dist/data/config-store.d.ts.map +1 -1
- package/dist/index.js +840 -215
- package/dist/lib/apply.d.ts +7 -0
- package/dist/lib/apply.d.ts.map +1 -1
- package/dist/lib/compact-output.d.ts +1 -0
- package/dist/lib/compact-output.d.ts.map +1 -1
- package/dist/lib/export.d.ts +22 -3
- package/dist/lib/export.d.ts.map +1 -1
- package/dist/lib/import.d.ts +18 -0
- package/dist/lib/import.d.ts.map +1 -1
- package/dist/lib/legacy-store-migration.d.ts +39 -0
- package/dist/lib/legacy-store-migration.d.ts.map +1 -0
- package/dist/lib/managed-skill-runtimes.d.ts +1 -0
- package/dist/lib/managed-skill-runtimes.d.ts.map +1 -1
- package/dist/lib/sync-dir.d.ts.map +1 -1
- package/dist/mcp/index.js +31 -20
- package/dist/sdk/index.d.ts +2 -0
- package/dist/sdk/index.d.ts.map +1 -1
- package/dist/sdk/index.js +205 -10
- package/dist/sdk/resolve.d.ts +1 -1
- package/dist/sdk/resolve.d.ts.map +1 -1
- package/dist/sdk/v1-client.d.ts +57 -0
- package/dist/sdk/v1-client.d.ts.map +1 -0
- package/dist/sdk/v1.generated.d.ts +212 -21
- package/dist/sdk/v1.generated.d.ts.map +1 -1
- package/dist/server/cloud.d.ts.map +1 -1
- package/dist/server/index.d.ts +13 -5
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +1180 -432
- package/dist/server/openapi.d.ts +1264 -92
- package/dist/server/openapi.d.ts.map +1 -1
- package/dist/server/v1.d.ts +25 -1
- package/dist/server/v1.d.ts.map +1 -1
- package/dist/storage/cloud-store.d.ts +57 -1
- package/dist/storage/cloud-store.d.ts.map +1 -1
- package/dist/storage/index.d.ts +7 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/index.js +725 -0
- package/dist/storage/s3-backup.d.ts +76 -0
- package/dist/storage/s3-backup.d.ts.map +1 -0
- package/dist/storage/s3-config.d.ts +46 -0
- package/dist/storage/s3-config.d.ts.map +1 -0
- package/dist/storage/s3-object-store.d.ts +70 -0
- package/dist/storage/s3-object-store.d.ts.map +1 -0
- package/dist/types/index.d.ts +65 -3
- package/dist/types/index.d.ts.map +1 -1
- package/hasna.contract.json +110 -0
- package/migrations/0001_instructions.sql +63 -0
- package/migrations/0002_api_keys.sql +20 -0
- package/migrations/0003_idempotency_receipts.sql +24 -0
- package/package.json +22 -11
- package/assets/skills/inbox/SKILL.md +0 -86
package/dist/server/index.js
CHANGED
|
@@ -70,9 +70,9 @@ function parseCursor(value) {
|
|
|
70
70
|
const parsed = typeof value === "number" ? value : Number.parseInt(String(value ?? ""), 10);
|
|
71
71
|
if (!Number.isFinite(parsed) || parsed < 0)
|
|
72
72
|
return 0;
|
|
73
|
-
return Math.floor(parsed);
|
|
73
|
+
return Math.min(Math.floor(parsed), MAX_LIST_CURSOR);
|
|
74
74
|
}
|
|
75
|
-
var DEFAULT_LIST_LIMIT = 20, MAX_LIST_LIMIT = 100;
|
|
75
|
+
var DEFAULT_LIST_LIMIT = 20, MAX_LIST_LIMIT = 100, MAX_LIST_CURSOR = 1e5;
|
|
76
76
|
|
|
77
77
|
// src/lib/bounded-read.ts
|
|
78
78
|
function normalizeBoundedReadOptions(options = {}) {
|
|
@@ -105,7 +105,7 @@ function boundedReadPage(items, total, options = {}) {
|
|
|
105
105
|
}
|
|
106
106
|
var init_bounded_read = () => {};
|
|
107
107
|
|
|
108
|
-
//
|
|
108
|
+
// ../../node_modules/.bun/@hasna+contracts@1.0.2+e8014c875821e0be/node_modules/@hasna/contracts/dist/client/transport.js
|
|
109
109
|
import { createRequire } from "module";
|
|
110
110
|
var MAX_CREDENTIAL_FILE_BYTES, INSPECT_CUSTOM, CREDENTIAL_SEAL, AMBIENT_ENVIRONMENT, SECRETS_PACKAGE_SPECIFIER, requireSecretsSdk, IDEMPOTENT_METHODS, AUTHORITY_OVERRIDE_HEADERS;
|
|
111
111
|
var init_transport = __esm(() => {
|
|
@@ -125,7 +125,7 @@ var init_transport = __esm(() => {
|
|
|
125
125
|
]);
|
|
126
126
|
});
|
|
127
127
|
|
|
128
|
-
//
|
|
128
|
+
// ../../node_modules/.bun/@hasna+contracts@1.0.2+e8014c875821e0be/node_modules/@hasna/contracts/dist/client/storage.js
|
|
129
129
|
import { createRequire as createRequire2 } from "module";
|
|
130
130
|
var MAX_CREDENTIAL_FILE_BYTES2, INSPECT_CUSTOM2, CREDENTIAL_SEAL2, AMBIENT_ENVIRONMENT2, SECRETS_PACKAGE_SPECIFIER2, requireSecretsSdk2, IDEMPOTENT_METHODS2, AUTHORITY_OVERRIDE_HEADERS2;
|
|
131
131
|
var init_storage = __esm(() => {
|
|
@@ -5156,7 +5156,7 @@ var init_machine = __esm(() => {
|
|
|
5156
5156
|
init_template();
|
|
5157
5157
|
});
|
|
5158
5158
|
|
|
5159
|
-
// ../../node_modules/.bun/hono@4.13.
|
|
5159
|
+
// ../../node_modules/.bun/hono@4.13.7/node_modules/hono/dist/compose.js
|
|
5160
5160
|
var compose = (middleware, onError, onNotFound) => {
|
|
5161
5161
|
return (context, next) => {
|
|
5162
5162
|
let index = -1;
|
|
@@ -5200,10 +5200,10 @@ var compose = (middleware, onError, onNotFound) => {
|
|
|
5200
5200
|
};
|
|
5201
5201
|
};
|
|
5202
5202
|
|
|
5203
|
-
// ../../node_modules/.bun/hono@4.13.
|
|
5203
|
+
// ../../node_modules/.bun/hono@4.13.7/node_modules/hono/dist/request/constants.js
|
|
5204
5204
|
var GET_MATCH_RESULT = /* @__PURE__ */ Symbol();
|
|
5205
5205
|
|
|
5206
|
-
// ../../node_modules/.bun/hono@4.13.
|
|
5206
|
+
// ../../node_modules/.bun/hono@4.13.7/node_modules/hono/dist/utils/buffer.js
|
|
5207
5207
|
var bufferToFormData = (arrayBuffer, contentType) => {
|
|
5208
5208
|
const response = new Response(arrayBuffer, {
|
|
5209
5209
|
headers: {
|
|
@@ -5213,7 +5213,9 @@ var bufferToFormData = (arrayBuffer, contentType) => {
|
|
|
5213
5213
|
return response.formData();
|
|
5214
5214
|
};
|
|
5215
5215
|
|
|
5216
|
-
// ../../node_modules/.bun/hono@4.13.
|
|
5216
|
+
// ../../node_modules/.bun/hono@4.13.7/node_modules/hono/dist/utils/body.js
|
|
5217
|
+
var MAX_NESTING_DEPTH = 32;
|
|
5218
|
+
var MAX_NESTED_OBJECTS = 1e4;
|
|
5217
5219
|
var isRawRequest = (request) => ("headers" in request);
|
|
5218
5220
|
var parseBody = async (request, options = /* @__PURE__ */ Object.create(null)) => {
|
|
5219
5221
|
const { all = false, dot = false } = options;
|
|
@@ -5243,6 +5245,7 @@ async function parseFormData(request, options) {
|
|
|
5243
5245
|
}
|
|
5244
5246
|
function convertFormDataToBodyData(formData, options) {
|
|
5245
5247
|
const form = /* @__PURE__ */ Object.create(null);
|
|
5248
|
+
const nestingState = { count: 0 };
|
|
5246
5249
|
formData.forEach((value, key) => {
|
|
5247
5250
|
const shouldParseAllValues = options.all || key.endsWith("[]");
|
|
5248
5251
|
if (!shouldParseAllValues) {
|
|
@@ -5255,7 +5258,7 @@ function convertFormDataToBodyData(formData, options) {
|
|
|
5255
5258
|
Object.entries(form).forEach(([key, value]) => {
|
|
5256
5259
|
const shouldParseDotValues = key.includes(".");
|
|
5257
5260
|
if (shouldParseDotValues) {
|
|
5258
|
-
handleParsingNestedValues(form, key, value);
|
|
5261
|
+
handleParsingNestedValues(form, key, value, nestingState);
|
|
5259
5262
|
delete form[key];
|
|
5260
5263
|
}
|
|
5261
5264
|
});
|
|
@@ -5277,25 +5280,34 @@ var handleParsingAllValues = (form, key, value) => {
|
|
|
5277
5280
|
}
|
|
5278
5281
|
}
|
|
5279
5282
|
};
|
|
5280
|
-
var handleParsingNestedValues = (form, key, value) => {
|
|
5283
|
+
var handleParsingNestedValues = (form, key, value, state) => {
|
|
5281
5284
|
if (/(?:^|\.)__proto__\./.test(key)) {
|
|
5282
5285
|
return;
|
|
5283
5286
|
}
|
|
5284
5287
|
let nestedForm = form;
|
|
5285
|
-
const keys = key.split(".");
|
|
5288
|
+
const keys = key.split(".", MAX_NESTING_DEPTH + 2);
|
|
5289
|
+
if (keys.length > MAX_NESTING_DEPTH + 1) {
|
|
5290
|
+
throwNestingLimitExceeded();
|
|
5291
|
+
}
|
|
5286
5292
|
keys.forEach((key2, index) => {
|
|
5287
5293
|
if (index === keys.length - 1) {
|
|
5288
5294
|
nestedForm[key2] = value;
|
|
5289
5295
|
} else {
|
|
5290
5296
|
if (!nestedForm[key2] || typeof nestedForm[key2] !== "object" || Array.isArray(nestedForm[key2]) || nestedForm[key2] instanceof File) {
|
|
5297
|
+
if (state.count++ >= MAX_NESTED_OBJECTS) {
|
|
5298
|
+
throwNestingLimitExceeded();
|
|
5299
|
+
}
|
|
5291
5300
|
nestedForm[key2] = /* @__PURE__ */ Object.create(null);
|
|
5292
5301
|
}
|
|
5293
5302
|
nestedForm = nestedForm[key2];
|
|
5294
5303
|
}
|
|
5295
5304
|
});
|
|
5296
5305
|
};
|
|
5306
|
+
var throwNestingLimitExceeded = () => {
|
|
5307
|
+
throw new Error("Nesting limit exceeded");
|
|
5308
|
+
};
|
|
5297
5309
|
|
|
5298
|
-
// ../../node_modules/.bun/hono@4.13.
|
|
5310
|
+
// ../../node_modules/.bun/hono@4.13.7/node_modules/hono/dist/utils/url.js
|
|
5299
5311
|
var splitPath = (path) => {
|
|
5300
5312
|
const paths = path.split("/");
|
|
5301
5313
|
if (paths[0] === "") {
|
|
@@ -5425,6 +5437,10 @@ var _decodeURI = (value) => {
|
|
|
5425
5437
|
return tryDecodeURIComponent(value);
|
|
5426
5438
|
};
|
|
5427
5439
|
var _getQueryParam = (url, key, multiple) => {
|
|
5440
|
+
const hashIndex = url.indexOf("#", 8);
|
|
5441
|
+
if (hashIndex !== -1) {
|
|
5442
|
+
url = url.slice(0, hashIndex);
|
|
5443
|
+
}
|
|
5428
5444
|
let encoded;
|
|
5429
5445
|
if (!multiple && key && key.indexOf("%") === -1 && key.indexOf("+") === -1) {
|
|
5430
5446
|
let keyIndex2 = url.indexOf("?", 8);
|
|
@@ -5493,7 +5509,7 @@ var getQueryParams = (url, key) => {
|
|
|
5493
5509
|
};
|
|
5494
5510
|
var decodeURIComponent_ = decodeURIComponent;
|
|
5495
5511
|
|
|
5496
|
-
// ../../node_modules/.bun/hono@4.13.
|
|
5512
|
+
// ../../node_modules/.bun/hono@4.13.7/node_modules/hono/dist/request.js
|
|
5497
5513
|
var HonoRequest = class {
|
|
5498
5514
|
raw;
|
|
5499
5515
|
#validatedData;
|
|
@@ -5510,13 +5526,13 @@ var HonoRequest = class {
|
|
|
5510
5526
|
return key ? this.#getDecodedParam(key) : this.#getAllDecodedParams();
|
|
5511
5527
|
}
|
|
5512
5528
|
#getDecodedParam(key) {
|
|
5513
|
-
const paramKey = this.#matchResult[0][this.routeIndex][1][key];
|
|
5529
|
+
const paramKey = this.#matchResult[0][this.routeIndex]?.[1][key];
|
|
5514
5530
|
const param = this.#getParamValue(paramKey);
|
|
5515
5531
|
return param && tryDecodeURIComponent(param);
|
|
5516
5532
|
}
|
|
5517
5533
|
#getAllDecodedParams() {
|
|
5518
5534
|
const decoded = {};
|
|
5519
|
-
const keys = Object.keys(this.#matchResult[0][this.routeIndex][1]);
|
|
5535
|
+
const keys = Object.keys(this.#matchResult[0][this.routeIndex]?.[1] ?? {});
|
|
5520
5536
|
for (const key of keys) {
|
|
5521
5537
|
const value = this.#getParamValue(this.#matchResult[0][this.routeIndex][1][key]);
|
|
5522
5538
|
if (value !== undefined) {
|
|
@@ -5604,7 +5620,7 @@ var HonoRequest = class {
|
|
|
5604
5620
|
}
|
|
5605
5621
|
};
|
|
5606
5622
|
|
|
5607
|
-
// ../../node_modules/.bun/hono@4.13.
|
|
5623
|
+
// ../../node_modules/.bun/hono@4.13.7/node_modules/hono/dist/utils/html.js
|
|
5608
5624
|
var HtmlEscapedCallbackPhase = {
|
|
5609
5625
|
Stringify: 1,
|
|
5610
5626
|
BeforeStream: 2,
|
|
@@ -5642,7 +5658,7 @@ var resolveCallback = async (str, phase, preserveCallbacks, context, buffer) =>
|
|
|
5642
5658
|
}
|
|
5643
5659
|
};
|
|
5644
5660
|
|
|
5645
|
-
// ../../node_modules/.bun/hono@4.13.
|
|
5661
|
+
// ../../node_modules/.bun/hono@4.13.7/node_modules/hono/dist/context.js
|
|
5646
5662
|
var TEXT_PLAIN = "text/plain; charset=UTF-8";
|
|
5647
5663
|
var setDefaultContentType = (contentType, headers) => {
|
|
5648
5664
|
return {
|
|
@@ -5824,7 +5840,7 @@ var Context = class {
|
|
|
5824
5840
|
};
|
|
5825
5841
|
};
|
|
5826
5842
|
|
|
5827
|
-
// ../../node_modules/.bun/hono@4.13.
|
|
5843
|
+
// ../../node_modules/.bun/hono@4.13.7/node_modules/hono/dist/router.js
|
|
5828
5844
|
var METHOD_NAME_ALL = "ALL";
|
|
5829
5845
|
var METHOD_NAME_ALL_LOWERCASE = "all";
|
|
5830
5846
|
var METHODS = ["get", "post", "put", "delete", "options", "patch", "query"];
|
|
@@ -5832,10 +5848,10 @@ var MESSAGE_MATCHER_IS_ALREADY_BUILT = "Can not add a route since the matcher is
|
|
|
5832
5848
|
var UnsupportedPathError = class extends Error {
|
|
5833
5849
|
};
|
|
5834
5850
|
|
|
5835
|
-
// ../../node_modules/.bun/hono@4.13.
|
|
5851
|
+
// ../../node_modules/.bun/hono@4.13.7/node_modules/hono/dist/utils/constants.js
|
|
5836
5852
|
var COMPOSED_HANDLER = "__COMPOSED_HANDLER";
|
|
5837
5853
|
|
|
5838
|
-
// ../../node_modules/.bun/hono@4.13.
|
|
5854
|
+
// ../../node_modules/.bun/hono@4.13.7/node_modules/hono/dist/hono-base.js
|
|
5839
5855
|
var notFoundHandler = (c) => {
|
|
5840
5856
|
return c.text("404 Not Found", 404);
|
|
5841
5857
|
};
|
|
@@ -5867,13 +5883,14 @@ var Hono = class _Hono {
|
|
|
5867
5883
|
const allMethods = [...METHODS, METHOD_NAME_ALL_LOWERCASE];
|
|
5868
5884
|
allMethods.forEach((method) => {
|
|
5869
5885
|
this[method] = (args1, ...args) => {
|
|
5886
|
+
const methodName = method.toUpperCase();
|
|
5870
5887
|
if (typeof args1 === "string") {
|
|
5871
5888
|
this.#path = args1;
|
|
5872
5889
|
} else {
|
|
5873
|
-
this.#addRoute(
|
|
5890
|
+
this.#addRoute(methodName, this.#path, args1);
|
|
5874
5891
|
}
|
|
5875
5892
|
args.forEach((handler) => {
|
|
5876
|
-
this.#addRoute(
|
|
5893
|
+
this.#addRoute(methodName, this.#path, handler);
|
|
5877
5894
|
});
|
|
5878
5895
|
return this;
|
|
5879
5896
|
};
|
|
@@ -5882,9 +5899,10 @@ var Hono = class _Hono {
|
|
|
5882
5899
|
for (const p of [path].flat()) {
|
|
5883
5900
|
this.#path = p;
|
|
5884
5901
|
for (const m of [method].flat()) {
|
|
5885
|
-
|
|
5886
|
-
|
|
5887
|
-
|
|
5902
|
+
const methodName = m.toUpperCase();
|
|
5903
|
+
for (const handler of handlers) {
|
|
5904
|
+
this.#addRoute(methodName, this.#path, handler);
|
|
5905
|
+
}
|
|
5888
5906
|
}
|
|
5889
5907
|
}
|
|
5890
5908
|
return this;
|
|
@@ -5989,7 +6007,6 @@ var Hono = class _Hono {
|
|
|
5989
6007
|
return this;
|
|
5990
6008
|
}
|
|
5991
6009
|
#addRoute(method, path, handler, baseRoutePath) {
|
|
5992
|
-
method = method.toUpperCase();
|
|
5993
6010
|
path = mergePath(this._basePath, path);
|
|
5994
6011
|
const r = {
|
|
5995
6012
|
basePath: baseRoutePath !== undefined ? mergePath(this._basePath, baseRoutePath) : this._basePath,
|
|
@@ -6060,7 +6077,10 @@ var Hono = class _Hono {
|
|
|
6060
6077
|
};
|
|
6061
6078
|
};
|
|
6062
6079
|
|
|
6063
|
-
// ../../node_modules/.bun/hono@4.13.
|
|
6080
|
+
// ../../node_modules/.bun/hono@4.13.7/node_modules/hono/dist/router/utils.js
|
|
6081
|
+
var createNullObject = () => /* @__PURE__ */ Object.create(null);
|
|
6082
|
+
|
|
6083
|
+
// ../../node_modules/.bun/hono@4.13.7/node_modules/hono/dist/router/reg-exp-router/matcher.js
|
|
6064
6084
|
var emptyParam = [];
|
|
6065
6085
|
function match(method, path) {
|
|
6066
6086
|
const matchers = this.buildAllMatchers();
|
|
@@ -6081,7 +6101,7 @@ function match(method, path) {
|
|
|
6081
6101
|
return match2(method, path);
|
|
6082
6102
|
}
|
|
6083
6103
|
|
|
6084
|
-
// ../../node_modules/.bun/hono@4.13.
|
|
6104
|
+
// ../../node_modules/.bun/hono@4.13.7/node_modules/hono/dist/router/reg-exp-router/node.js
|
|
6085
6105
|
var LABEL_REG_EXP_STR = "[^/]+";
|
|
6086
6106
|
var ONLY_WILDCARD_REG_EXP_STR = ".*";
|
|
6087
6107
|
var TAIL_WILDCARD_REG_EXP_STR = "(?:|/.*)";
|
|
@@ -6109,7 +6129,7 @@ function compareKey(a, b) {
|
|
|
6109
6129
|
var Node = class _Node {
|
|
6110
6130
|
#index;
|
|
6111
6131
|
#varIndex;
|
|
6112
|
-
#children =
|
|
6132
|
+
#children = createNullObject();
|
|
6113
6133
|
insert(tokens, index, paramMap, context, isStatic) {
|
|
6114
6134
|
let node = this;
|
|
6115
6135
|
for (let i = 0, len = tokens.length;i < len; i++) {
|
|
@@ -6184,12 +6204,12 @@ var Node = class _Node {
|
|
|
6184
6204
|
}
|
|
6185
6205
|
};
|
|
6186
6206
|
|
|
6187
|
-
// ../../node_modules/.bun/hono@4.13.
|
|
6207
|
+
// ../../node_modules/.bun/hono@4.13.7/node_modules/hono/dist/router/reg-exp-router/trie.js
|
|
6188
6208
|
var Trie = class {
|
|
6189
6209
|
#context = { varIndex: 0 };
|
|
6190
6210
|
#root = new Node;
|
|
6191
6211
|
#index = 0;
|
|
6192
|
-
paths =
|
|
6212
|
+
paths = createNullObject();
|
|
6193
6213
|
insert(path, isStatic) {
|
|
6194
6214
|
if (isStatic) {
|
|
6195
6215
|
this.#root.insert(path.split(""), 0, [], this.#context, true);
|
|
@@ -6247,18 +6267,12 @@ var Trie = class {
|
|
|
6247
6267
|
}
|
|
6248
6268
|
};
|
|
6249
6269
|
|
|
6250
|
-
// ../../node_modules/.bun/hono@4.13.
|
|
6251
|
-
var wildcardRegExpCache =
|
|
6270
|
+
// ../../node_modules/.bun/hono@4.13.7/node_modules/hono/dist/router/reg-exp-router/router.js
|
|
6271
|
+
var wildcardRegExpCache = createNullObject();
|
|
6252
6272
|
function buildWildcardRegExp(path) {
|
|
6253
|
-
return wildcardRegExpCache[path] ??= new RegExp(
|
|
6254
|
-
}
|
|
6255
|
-
function clearWildcardRegExpCache() {
|
|
6256
|
-
wildcardRegExpCache = /* @__PURE__ */ Object.create(null);
|
|
6273
|
+
return wildcardRegExpCache[path] ??= new RegExp(`^${path.replace(/\/:[^/{}]+(?:\{\[\^\/]\+})?(?=[/{]|$)|\/?\*$|([.\\+*[^\]$()?{}|])/g, (match2, metaChar) => metaChar ? `\\${metaChar}` : match2 === "/*" ? TAIL_WILDCARD_REG_EXP_STR : match2 === "*" ? ONLY_WILDCARD_REG_EXP_STR : `/:${LABEL_REG_EXP_STR}`)}$`);
|
|
6257
6274
|
}
|
|
6258
6275
|
function findMiddleware(middleware, path) {
|
|
6259
|
-
if (!middleware) {
|
|
6260
|
-
return;
|
|
6261
|
-
}
|
|
6262
6276
|
for (const k of Object.keys(middleware).sort((a, b) => b.length - a.length)) {
|
|
6263
6277
|
if (buildWildcardRegExp(k).test(path)) {
|
|
6264
6278
|
return [...middleware[k]];
|
|
@@ -6272,8 +6286,8 @@ var RegExpRouter = class {
|
|
|
6272
6286
|
#routes;
|
|
6273
6287
|
#tries;
|
|
6274
6288
|
constructor() {
|
|
6275
|
-
this.#middleware = { [METHOD_NAME_ALL]:
|
|
6276
|
-
this.#routes = { [METHOD_NAME_ALL]:
|
|
6289
|
+
this.#middleware = { [METHOD_NAME_ALL]: createNullObject() };
|
|
6290
|
+
this.#routes = { [METHOD_NAME_ALL]: createNullObject() };
|
|
6277
6291
|
this.#tries = { [METHOD_NAME_ALL]: new Trie };
|
|
6278
6292
|
}
|
|
6279
6293
|
#insertPath(method, path) {
|
|
@@ -6286,119 +6300,90 @@ var RegExpRouter = class {
|
|
|
6286
6300
|
add(method, path, handler) {
|
|
6287
6301
|
const middleware = this.#middleware;
|
|
6288
6302
|
const routes = this.#routes;
|
|
6289
|
-
if (!middleware
|
|
6303
|
+
if (!middleware) {
|
|
6290
6304
|
throw new Error(MESSAGE_MATCHER_IS_ALREADY_BUILT);
|
|
6291
6305
|
}
|
|
6292
6306
|
if (!middleware[method]) {
|
|
6293
6307
|
this.#tries[method] = new Trie;
|
|
6294
|
-
[middleware, routes]
|
|
6295
|
-
handlerMap[method] =
|
|
6296
|
-
|
|
6308
|
+
for (const handlerMap of [middleware, routes]) {
|
|
6309
|
+
handlerMap[method] = createNullObject();
|
|
6310
|
+
for (const p in handlerMap[METHOD_NAME_ALL]) {
|
|
6297
6311
|
handlerMap[method][p] = [...handlerMap[METHOD_NAME_ALL][p]];
|
|
6298
6312
|
this.#insertPath(method, p);
|
|
6299
|
-
}
|
|
6300
|
-
}
|
|
6313
|
+
}
|
|
6314
|
+
}
|
|
6301
6315
|
}
|
|
6302
6316
|
if (path === "/*") {
|
|
6303
6317
|
path = "*";
|
|
6304
6318
|
}
|
|
6305
|
-
const
|
|
6319
|
+
const methods = method === METHOD_NAME_ALL ? Object.keys(middleware) : [method];
|
|
6306
6320
|
if (/\*$/.test(path)) {
|
|
6307
6321
|
const re = buildWildcardRegExp(path);
|
|
6308
|
-
|
|
6309
|
-
if (
|
|
6322
|
+
for (const m of methods) {
|
|
6323
|
+
if (!middleware[m][path]) {
|
|
6310
6324
|
this.#insertPath(m, path);
|
|
6311
6325
|
middleware[m][path] = findMiddleware(middleware[m], path) || findMiddleware(middleware[METHOD_NAME_ALL], path) || [];
|
|
6312
6326
|
}
|
|
6313
|
-
}
|
|
6314
|
-
|
|
6315
|
-
|
|
6316
|
-
|
|
6317
|
-
re.test(p) &&
|
|
6318
|
-
}
|
|
6319
|
-
}
|
|
6320
|
-
});
|
|
6321
|
-
Object.keys(routes).forEach((m) => {
|
|
6322
|
-
if (method === METHOD_NAME_ALL || method === m) {
|
|
6323
|
-
Object.keys(routes[m]).forEach((p) => re.test(p) && routes[m][p].push([handler, paramCount]));
|
|
6327
|
+
}
|
|
6328
|
+
for (const handlerMap of [middleware, routes]) {
|
|
6329
|
+
for (const m of methods) {
|
|
6330
|
+
for (const p in handlerMap[m]) {
|
|
6331
|
+
re.test(p) && handlerMap[m][p].push([handler, path]);
|
|
6332
|
+
}
|
|
6324
6333
|
}
|
|
6325
|
-
}
|
|
6334
|
+
}
|
|
6326
6335
|
return;
|
|
6327
6336
|
}
|
|
6328
6337
|
const paths = checkOptionalParameter(path) || [path];
|
|
6329
|
-
for (
|
|
6330
|
-
const
|
|
6331
|
-
|
|
6332
|
-
|
|
6333
|
-
|
|
6334
|
-
this.#insertPath(m, path2);
|
|
6335
|
-
routes[m][path2] = [
|
|
6336
|
-
...findMiddleware(middleware[m], path2) || findMiddleware(middleware[METHOD_NAME_ALL], path2) || []
|
|
6337
|
-
];
|
|
6338
|
-
}
|
|
6339
|
-
routes[m][path2].push([handler, paramCount - len + i + 1]);
|
|
6338
|
+
for (const path2 of paths) {
|
|
6339
|
+
for (const m of methods) {
|
|
6340
|
+
if (!routes[m][path2]) {
|
|
6341
|
+
this.#insertPath(m, path2);
|
|
6342
|
+
routes[m][path2] = findMiddleware(middleware[m], path2) || findMiddleware(middleware[METHOD_NAME_ALL], path2) || [];
|
|
6340
6343
|
}
|
|
6341
|
-
|
|
6344
|
+
routes[m][path2].push([handler, path2]);
|
|
6345
|
+
}
|
|
6342
6346
|
}
|
|
6343
6347
|
}
|
|
6344
6348
|
match = match;
|
|
6345
6349
|
buildAllMatchers() {
|
|
6346
|
-
const matchers =
|
|
6347
|
-
Object.keys(this.#routes)
|
|
6348
|
-
matchers[method]
|
|
6349
|
-
}
|
|
6350
|
+
const matchers = createNullObject();
|
|
6351
|
+
for (const method of Object.keys(this.#routes)) {
|
|
6352
|
+
matchers[method] = this.#buildMatcher(method);
|
|
6353
|
+
}
|
|
6350
6354
|
this.#middleware = this.#routes = this.#tries = undefined;
|
|
6351
|
-
|
|
6355
|
+
wildcardRegExpCache = createNullObject();
|
|
6352
6356
|
return matchers;
|
|
6353
6357
|
}
|
|
6354
6358
|
#buildMatcher(method) {
|
|
6355
6359
|
const middleware = this.#middleware[method];
|
|
6356
6360
|
const routes = this.#routes[method];
|
|
6357
6361
|
const trie = this.#tries[method];
|
|
6358
|
-
const staticMap =
|
|
6362
|
+
const staticMap = createNullObject();
|
|
6359
6363
|
const handlerData = [];
|
|
6360
|
-
[
|
|
6364
|
+
const [regexp, indexReplacementMap, paramReplacementMap] = trie.buildRegExp();
|
|
6365
|
+
for (const r of [middleware, routes]) {
|
|
6361
6366
|
for (const path in r) {
|
|
6362
6367
|
const handlers = r[path];
|
|
6363
6368
|
const pathData = trie.paths[path];
|
|
6364
6369
|
if (!pathData) {
|
|
6365
|
-
staticMap[path] = [handlers.map(([h]) => [h,
|
|
6366
|
-
continue;
|
|
6367
|
-
}
|
|
6368
|
-
const paramAssoc = pathData[1];
|
|
6369
|
-
handlerData[pathData[0]] = handlers.map(([h, paramCount]) => {
|
|
6370
|
-
const paramIndexMap = /* @__PURE__ */ Object.create(null);
|
|
6371
|
-
paramCount -= 1;
|
|
6372
|
-
for (;paramCount >= 0; paramCount--) {
|
|
6373
|
-
const [key, value] = paramAssoc[paramCount];
|
|
6374
|
-
paramIndexMap[key] = value;
|
|
6375
|
-
}
|
|
6376
|
-
return [h, paramIndexMap];
|
|
6377
|
-
});
|
|
6378
|
-
}
|
|
6379
|
-
});
|
|
6380
|
-
const [regexp, indexReplacementMap, paramReplacementMap] = trie.buildRegExp();
|
|
6381
|
-
for (let i = 0, len = handlerData.length;i < len; i++) {
|
|
6382
|
-
for (let j = 0, len2 = handlerData[i].length;j < len2; j++) {
|
|
6383
|
-
const map = handlerData[i][j]?.[1];
|
|
6384
|
-
if (!map) {
|
|
6370
|
+
staticMap[path] = [handlers.map(([h]) => [h, createNullObject()]), emptyParam];
|
|
6385
6371
|
continue;
|
|
6386
6372
|
}
|
|
6387
|
-
|
|
6388
|
-
|
|
6389
|
-
|
|
6390
|
-
|
|
6373
|
+
handlerData[pathData[0]] = handlers.map(([h, handlerPath]) => [
|
|
6374
|
+
h,
|
|
6375
|
+
trie.paths[handlerPath][1].reduceRight((map, [key], i) => {
|
|
6376
|
+
map[key] = paramReplacementMap[pathData[1][i][1]];
|
|
6377
|
+
return map;
|
|
6378
|
+
}, createNullObject())
|
|
6379
|
+
]);
|
|
6391
6380
|
}
|
|
6392
6381
|
}
|
|
6393
|
-
|
|
6394
|
-
for (const i in indexReplacementMap) {
|
|
6395
|
-
handlerMap[i] = handlerData[indexReplacementMap[i]];
|
|
6396
|
-
}
|
|
6397
|
-
return [regexp, handlerMap, staticMap];
|
|
6382
|
+
return [regexp, indexReplacementMap.map((i) => handlerData[i]), staticMap];
|
|
6398
6383
|
}
|
|
6399
6384
|
};
|
|
6400
6385
|
|
|
6401
|
-
// ../../node_modules/.bun/hono@4.13.
|
|
6386
|
+
// ../../node_modules/.bun/hono@4.13.7/node_modules/hono/dist/router/reg-exp-router/prepared-router.js
|
|
6402
6387
|
var PreparedRegExpRouter = class {
|
|
6403
6388
|
name = "PreparedRegExpRouter";
|
|
6404
6389
|
#matchers;
|
|
@@ -6470,7 +6455,7 @@ var PreparedRegExpRouter = class {
|
|
|
6470
6455
|
match = match;
|
|
6471
6456
|
};
|
|
6472
6457
|
|
|
6473
|
-
// ../../node_modules/.bun/hono@4.13.
|
|
6458
|
+
// ../../node_modules/.bun/hono@4.13.7/node_modules/hono/dist/router/smart-router/router.js
|
|
6474
6459
|
var SmartRouter = class {
|
|
6475
6460
|
name = "SmartRouter";
|
|
6476
6461
|
#routers = [];
|
|
@@ -6525,12 +6510,12 @@ var SmartRouter = class {
|
|
|
6525
6510
|
}
|
|
6526
6511
|
};
|
|
6527
6512
|
|
|
6528
|
-
// ../../node_modules/.bun/hono@4.13.
|
|
6529
|
-
var emptyParams =
|
|
6513
|
+
// ../../node_modules/.bun/hono@4.13.7/node_modules/hono/dist/router/trie-router/node.js
|
|
6514
|
+
var emptyParams = createNullObject();
|
|
6530
6515
|
var order = 0;
|
|
6531
6516
|
var Node2 = class _Node2 {
|
|
6532
6517
|
#methods = [];
|
|
6533
|
-
#children =
|
|
6518
|
+
#children = createNullObject();
|
|
6534
6519
|
#patterns = [];
|
|
6535
6520
|
#pattern;
|
|
6536
6521
|
#params = emptyParams;
|
|
@@ -6567,7 +6552,7 @@ var Node2 = class _Node2 {
|
|
|
6567
6552
|
const m = node.#methods[i];
|
|
6568
6553
|
const handlerSet = m[method] || m[METHOD_NAME_ALL];
|
|
6569
6554
|
if (handlerSet) {
|
|
6570
|
-
handlerSet.params =
|
|
6555
|
+
handlerSet.params = createNullObject();
|
|
6571
6556
|
handlerSets.push(handlerSet);
|
|
6572
6557
|
for (let i2 = 0, len2 = handlerSet.possibleKeys.length;i2 < len2; i2++) {
|
|
6573
6558
|
const key = handlerSet.possibleKeys[i2];
|
|
@@ -6673,7 +6658,7 @@ var Node2 = class _Node2 {
|
|
|
6673
6658
|
}
|
|
6674
6659
|
};
|
|
6675
6660
|
|
|
6676
|
-
// ../../node_modules/.bun/hono@4.13.
|
|
6661
|
+
// ../../node_modules/.bun/hono@4.13.7/node_modules/hono/dist/router/trie-router/router.js
|
|
6677
6662
|
var TrieRouter = class {
|
|
6678
6663
|
name = "TrieRouter";
|
|
6679
6664
|
#node = new Node2;
|
|
@@ -6687,7 +6672,7 @@ var TrieRouter = class {
|
|
|
6687
6672
|
}
|
|
6688
6673
|
};
|
|
6689
6674
|
|
|
6690
|
-
// ../../node_modules/.bun/hono@4.13.
|
|
6675
|
+
// ../../node_modules/.bun/hono@4.13.7/node_modules/hono/dist/hono.js
|
|
6691
6676
|
var Hono2 = class extends Hono {
|
|
6692
6677
|
constructor(options = {}) {
|
|
6693
6678
|
super(options);
|
|
@@ -6697,7 +6682,7 @@ var Hono2 = class extends Hono {
|
|
|
6697
6682
|
}
|
|
6698
6683
|
};
|
|
6699
6684
|
|
|
6700
|
-
// ../../node_modules/.bun/hono@4.13.
|
|
6685
|
+
// ../../node_modules/.bun/hono@4.13.7/node_modules/hono/dist/middleware/cors/index.js
|
|
6701
6686
|
var cors = (options) => {
|
|
6702
6687
|
const opts = {
|
|
6703
6688
|
origin: "*",
|
|
@@ -6814,7 +6799,7 @@ function getPackageVersion() {
|
|
|
6814
6799
|
// src/server/v1.ts
|
|
6815
6800
|
init_types();
|
|
6816
6801
|
|
|
6817
|
-
//
|
|
6802
|
+
// ../../node_modules/.bun/@hasna+contracts@1.0.2+e8014c875821e0be/node_modules/@hasna/contracts/dist/auth/index.js
|
|
6818
6803
|
import { createHash, createHmac, randomBytes, timingSafeEqual } from "crypto";
|
|
6819
6804
|
var MAX_TENANT_ID_LENGTH = 64;
|
|
6820
6805
|
var TENANT_ID_PATTERN = new RegExp(`^[A-Za-z0-9][A-Za-z0-9._-]{0,${MAX_TENANT_ID_LENGTH - 1}}$`);
|
|
@@ -7769,119 +7754,178 @@ function instructionsSchemaSql() {
|
|
|
7769
7754
|
];
|
|
7770
7755
|
}
|
|
7771
7756
|
|
|
7772
|
-
// src/
|
|
7773
|
-
|
|
7774
|
-
|
|
7775
|
-
|
|
7776
|
-
|
|
7777
|
-
|
|
7778
|
-
|
|
7779
|
-
|
|
7780
|
-
|
|
7781
|
-
|
|
7782
|
-
}
|
|
7783
|
-
|
|
7784
|
-
var cachedStore = null;
|
|
7785
|
-
var cachedVerifier = null;
|
|
7786
|
-
var schemaEnsured = null;
|
|
7787
|
-
function getClient() {
|
|
7788
|
-
if (cachedClient)
|
|
7789
|
-
return cachedClient;
|
|
7790
|
-
const url = resolveCloudDatabaseUrl();
|
|
7791
|
-
if (!url) {
|
|
7792
|
-
throw new Error("Cloud /v1 requires a remote database URL (HASNA_INSTRUCTIONS_DATABASE_URL / INSTRUCTIONS_DATABASE_URL / DATABASE_URL).");
|
|
7757
|
+
// src/storage/cloud-store.ts
|
|
7758
|
+
init_types();
|
|
7759
|
+
init_bounded_read();
|
|
7760
|
+
init_instruction_graph();
|
|
7761
|
+
init_asset_plan();
|
|
7762
|
+
init_machine();
|
|
7763
|
+
import { createHash as createHash2, randomUUID } from "crypto";
|
|
7764
|
+
|
|
7765
|
+
class CollectionChangedWhilePagingError extends Error {
|
|
7766
|
+
constructor(label, detail) {
|
|
7767
|
+
super(`${label} changed while paging: ${detail}`);
|
|
7768
|
+
this.name = "CollectionChangedWhilePagingError";
|
|
7793
7769
|
}
|
|
7794
|
-
const pool2 = createPgPool({
|
|
7795
|
-
connectionString: url,
|
|
7796
|
-
max: 6,
|
|
7797
|
-
idleTimeoutMillis: 30000,
|
|
7798
|
-
connectionTimeoutMillis: 15000,
|
|
7799
|
-
applicationName: "instructions-serve"
|
|
7800
|
-
});
|
|
7801
|
-
cachedClient = createQueryClient(pool2);
|
|
7802
|
-
return cachedClient;
|
|
7803
7770
|
}
|
|
7804
|
-
function
|
|
7805
|
-
return
|
|
7771
|
+
function isBoundedPageConsistencyError(error) {
|
|
7772
|
+
return error instanceof Error && (error.message.startsWith("bounded read returned ") || error.message.startsWith("bounded read did not advance "));
|
|
7806
7773
|
}
|
|
7807
|
-
function
|
|
7808
|
-
|
|
7809
|
-
|
|
7810
|
-
|
|
7811
|
-
|
|
7812
|
-
|
|
7813
|
-
|
|
7814
|
-
|
|
7815
|
-
|
|
7816
|
-
|
|
7817
|
-
|
|
7774
|
+
async function aggregateBoundedCollectionRead(label, readPage, identity, requireAscendingIdentity = false, retryBoundedPageConsistencyErrors = false) {
|
|
7775
|
+
let lastError = null;
|
|
7776
|
+
for (let attempt = 0;attempt < 2; attempt += 1) {
|
|
7777
|
+
try {
|
|
7778
|
+
const items = [];
|
|
7779
|
+
const seen = new Set;
|
|
7780
|
+
let expectedTotal = null;
|
|
7781
|
+
let previousIdentity = null;
|
|
7782
|
+
let cursor = 0;
|
|
7783
|
+
while (true) {
|
|
7784
|
+
let page;
|
|
7785
|
+
try {
|
|
7786
|
+
page = await readPage(cursor);
|
|
7787
|
+
} catch (error) {
|
|
7788
|
+
if (!retryBoundedPageConsistencyErrors || !isBoundedPageConsistencyError(error))
|
|
7789
|
+
throw error;
|
|
7790
|
+
throw new CollectionChangedWhilePagingError(label, error.message);
|
|
7791
|
+
}
|
|
7792
|
+
if (page.cursor !== cursor) {
|
|
7793
|
+
throw new CollectionChangedWhilePagingError(label, `server returned cursor ${page.cursor} for requested cursor ${cursor}`);
|
|
7794
|
+
}
|
|
7795
|
+
if (expectedTotal === null)
|
|
7796
|
+
expectedTotal = page.total;
|
|
7797
|
+
else if (page.total !== expectedTotal) {
|
|
7798
|
+
throw new CollectionChangedWhilePagingError(label, `total changed from ${expectedTotal} to ${page.total}`);
|
|
7799
|
+
}
|
|
7800
|
+
for (const item of page.items) {
|
|
7801
|
+
const key = identity(item);
|
|
7802
|
+
if (!key)
|
|
7803
|
+
throw new CollectionChangedWhilePagingError(label, "an item had no stable identity");
|
|
7804
|
+
if (seen.has(key))
|
|
7805
|
+
throw new CollectionChangedWhilePagingError(label, `duplicate identity ${key}`);
|
|
7806
|
+
if (requireAscendingIdentity && previousIdentity !== null && key <= previousIdentity) {
|
|
7807
|
+
throw new CollectionChangedWhilePagingError(label, `identity order was not strictly increasing at ${key}`);
|
|
7808
|
+
}
|
|
7809
|
+
seen.add(key);
|
|
7810
|
+
previousIdentity = key;
|
|
7811
|
+
items.push(item);
|
|
7812
|
+
}
|
|
7813
|
+
if (page.complete) {
|
|
7814
|
+
if (items.length !== expectedTotal) {
|
|
7815
|
+
throw new CollectionChangedWhilePagingError(label, `received ${items.length} unique rows for total ${expectedTotal}`);
|
|
7816
|
+
}
|
|
7817
|
+
return { items, total: expectedTotal };
|
|
7818
|
+
}
|
|
7819
|
+
if (page.next_cursor === null || page.next_cursor <= cursor) {
|
|
7820
|
+
throw new CollectionChangedWhilePagingError(label, `cursor did not advance from ${cursor}`);
|
|
7821
|
+
}
|
|
7822
|
+
cursor = page.next_cursor;
|
|
7823
|
+
}
|
|
7824
|
+
} catch (error) {
|
|
7825
|
+
if (!(error instanceof CollectionChangedWhilePagingError))
|
|
7826
|
+
throw error;
|
|
7827
|
+
lastError = error;
|
|
7818
7828
|
}
|
|
7819
|
-
}
|
|
7820
|
-
|
|
7821
|
-
function getApiKeyStore() {
|
|
7822
|
-
if (cachedStore)
|
|
7823
|
-
return cachedStore;
|
|
7824
|
-
cachedStore = new ApiKeyStore(authClient());
|
|
7825
|
-
return cachedStore;
|
|
7829
|
+
}
|
|
7830
|
+
throw lastError ?? new CollectionChangedWhilePagingError(label, "read did not stabilize");
|
|
7826
7831
|
}
|
|
7827
|
-
|
|
7828
|
-
|
|
7829
|
-
|
|
7830
|
-
|
|
7831
|
-
|
|
7832
|
-
|
|
7833
|
-
|
|
7834
|
-
}
|
|
7835
|
-
await getApiKeyStore().ensureSchema();
|
|
7836
|
-
})().catch((e) => {
|
|
7837
|
-
schemaEnsured = null;
|
|
7838
|
-
throw e;
|
|
7839
|
-
});
|
|
7840
|
-
return schemaEnsured;
|
|
7832
|
+
class StoreValidationError extends Error {
|
|
7833
|
+
code;
|
|
7834
|
+
constructor(message, code) {
|
|
7835
|
+
super(message);
|
|
7836
|
+
this.code = code;
|
|
7837
|
+
this.name = "StoreValidationError";
|
|
7838
|
+
}
|
|
7841
7839
|
}
|
|
7842
|
-
|
|
7843
|
-
|
|
7844
|
-
|
|
7845
|
-
|
|
7846
|
-
|
|
7847
|
-
|
|
7848
|
-
|
|
7849
|
-
|
|
7850
|
-
|
|
7840
|
+
|
|
7841
|
+
class IdempotencyConflictError extends Error {
|
|
7842
|
+
code = "IDEMPOTENCY_KEY_REUSED";
|
|
7843
|
+
constructor() {
|
|
7844
|
+
super("Idempotency-Key was already used for a different request body");
|
|
7845
|
+
this.name = "IdempotencyConflictError";
|
|
7846
|
+
}
|
|
7847
|
+
}
|
|
7848
|
+
var IDEMPOTENCY_SCHEMA_SQL = `CREATE TABLE IF NOT EXISTS instruction_idempotency_receipts (
|
|
7849
|
+
principal TEXT NOT NULL,
|
|
7850
|
+
operation TEXT NOT NULL,
|
|
7851
|
+
idempotency_key TEXT NOT NULL,
|
|
7852
|
+
request_sha256 TEXT NOT NULL,
|
|
7853
|
+
response_status INTEGER,
|
|
7854
|
+
response_body JSONB,
|
|
7855
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
7856
|
+
completed_at TIMESTAMPTZ,
|
|
7857
|
+
PRIMARY KEY (principal, operation, idempotency_key),
|
|
7858
|
+
CHECK (length(principal) BETWEEN 1 AND 512),
|
|
7859
|
+
CHECK (length(operation) BETWEEN 1 AND 255),
|
|
7860
|
+
CHECK (length(idempotency_key) BETWEEN 1 AND 255),
|
|
7861
|
+
CHECK (length(request_sha256) = 64),
|
|
7862
|
+
CHECK (
|
|
7863
|
+
(response_status IS NULL AND response_body IS NULL AND completed_at IS NULL)
|
|
7864
|
+
OR
|
|
7865
|
+
(response_status BETWEEN 200 AND 599 AND response_body IS NOT NULL AND completed_at IS NOT NULL)
|
|
7866
|
+
)
|
|
7867
|
+
)`;
|
|
7868
|
+
function canonicalJson(value) {
|
|
7869
|
+
if (value === null || typeof value === "string" || typeof value === "boolean")
|
|
7870
|
+
return JSON.stringify(value);
|
|
7871
|
+
if (typeof value === "number")
|
|
7872
|
+
return JSON.stringify(value);
|
|
7873
|
+
if (Array.isArray(value))
|
|
7874
|
+
return `[${value.map(canonicalJson).join(",")}]`;
|
|
7875
|
+
if (typeof value === "object") {
|
|
7876
|
+
const record = value;
|
|
7877
|
+
return `{${Object.keys(record).sort().filter((key) => record[key] !== undefined).map((key) => `${JSON.stringify(key)}:${canonicalJson(record[key])}`).join(",")}}`;
|
|
7851
7878
|
}
|
|
7852
|
-
|
|
7853
|
-
const mw = honoApiKey({
|
|
7854
|
-
app: INSTRUCTIONS_APP_SLUG,
|
|
7855
|
-
signingSecret,
|
|
7856
|
-
keyStatus: store.keyStatus,
|
|
7857
|
-
requiredScopes
|
|
7858
|
-
});
|
|
7859
|
-
honoMiddlewareCache.set(key, mw);
|
|
7860
|
-
return mw;
|
|
7879
|
+
return JSON.stringify(null);
|
|
7861
7880
|
}
|
|
7862
|
-
|
|
7863
|
-
|
|
7864
|
-
const row = await client.get("SELECT 1 AS ok");
|
|
7865
|
-
return row?.ok === 1;
|
|
7881
|
+
function idempotencyBodyDigest(body) {
|
|
7882
|
+
return createHash2("sha256").update(canonicalJson(body)).digest("hex");
|
|
7866
7883
|
}
|
|
7867
|
-
|
|
7868
|
-
|
|
7869
|
-
|
|
7870
|
-
|
|
7871
|
-
|
|
7872
|
-
|
|
7873
|
-
|
|
7874
|
-
|
|
7875
|
-
|
|
7884
|
+
function hasTransaction(client) {
|
|
7885
|
+
return typeof client.transaction === "function";
|
|
7886
|
+
}
|
|
7887
|
+
var idempotencySchemaReady = new WeakSet;
|
|
7888
|
+
async function ensureIdempotencySchema(client) {
|
|
7889
|
+
if (idempotencySchemaReady.has(client))
|
|
7890
|
+
return;
|
|
7891
|
+
await client.execute(IDEMPOTENCY_SCHEMA_SQL);
|
|
7892
|
+
await client.execute("CREATE INDEX IF NOT EXISTS instruction_idempotency_receipts_created_at_idx ON instruction_idempotency_receipts (created_at)");
|
|
7893
|
+
idempotencySchemaReady.add(client);
|
|
7894
|
+
}
|
|
7895
|
+
async function executeIdempotentRequest(client, input, perform) {
|
|
7896
|
+
if (!hasTransaction(client)) {
|
|
7897
|
+
throw new Error("durable idempotency requires a transactional PostgreSQL client");
|
|
7898
|
+
}
|
|
7899
|
+
await ensureIdempotencySchema(client);
|
|
7900
|
+
const digest = idempotencyBodyDigest(input.body);
|
|
7901
|
+
return client.transaction(async (tx) => {
|
|
7902
|
+
const identity = [input.principal, input.operation, input.key];
|
|
7903
|
+
const inserted = await tx.query(`INSERT INTO instruction_idempotency_receipts
|
|
7904
|
+
(principal, operation, idempotency_key, request_sha256)
|
|
7905
|
+
VALUES ($1,$2,$3,$4)
|
|
7906
|
+
ON CONFLICT (principal, operation, idempotency_key) DO NOTHING
|
|
7907
|
+
RETURNING true AS inserted`, [...identity, digest]);
|
|
7908
|
+
const receipt = await tx.get(`SELECT request_sha256, response_status, response_body
|
|
7909
|
+
FROM instruction_idempotency_receipts
|
|
7910
|
+
WHERE principal = $1 AND operation = $2 AND idempotency_key = $3
|
|
7911
|
+
FOR UPDATE`, identity);
|
|
7912
|
+
if (!receipt)
|
|
7913
|
+
throw new Error("idempotency receipt disappeared during transaction");
|
|
7914
|
+
if (receipt.request_sha256 !== digest)
|
|
7915
|
+
throw new IdempotencyConflictError;
|
|
7916
|
+
if (inserted.rowCount === 0) {
|
|
7917
|
+
if (receipt.response_status === null || receipt.response_body === null) {
|
|
7918
|
+
throw new Error("idempotency receipt is incomplete after serialization");
|
|
7919
|
+
}
|
|
7920
|
+
return { status: Number(receipt.response_status), body: receipt.response_body, replayed: true };
|
|
7921
|
+
}
|
|
7922
|
+
const response = await perform(tx);
|
|
7923
|
+
await tx.execute(`UPDATE instruction_idempotency_receipts
|
|
7924
|
+
SET response_status = $5, response_body = $6::jsonb, completed_at = now()
|
|
7925
|
+
WHERE principal = $1 AND operation = $2 AND idempotency_key = $3 AND request_sha256 = $4`, [...identity, digest, response.status, JSON.stringify(response.body)]);
|
|
7926
|
+
return { ...response, replayed: false };
|
|
7927
|
+
});
|
|
7876
7928
|
}
|
|
7877
|
-
|
|
7878
|
-
// src/storage/cloud-store.ts
|
|
7879
|
-
init_types();
|
|
7880
|
-
init_bounded_read();
|
|
7881
|
-
init_instruction_graph();
|
|
7882
|
-
init_asset_plan();
|
|
7883
|
-
init_machine();
|
|
7884
|
-
import { randomUUID } from "crypto";
|
|
7885
7929
|
function slugify(name) {
|
|
7886
7930
|
return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
7887
7931
|
}
|
|
@@ -7949,7 +7993,7 @@ async function uniqueSlug(client, name, excludeId) {
|
|
|
7949
7993
|
}
|
|
7950
7994
|
throw new Error(`could not allocate a unique slug for '${name}'`);
|
|
7951
7995
|
}
|
|
7952
|
-
|
|
7996
|
+
function configFilterSql(filter) {
|
|
7953
7997
|
const conditions = [];
|
|
7954
7998
|
const params = [];
|
|
7955
7999
|
const add = (sql, value) => {
|
|
@@ -7966,12 +8010,37 @@ async function listConfigs(client, filter = {}) {
|
|
|
7966
8010
|
add("is_template = $?", filter.is_template);
|
|
7967
8011
|
if (filter.search) {
|
|
7968
8012
|
params.push(`%${filter.search}%`);
|
|
7969
|
-
const
|
|
7970
|
-
conditions.push(`(name ILIKE ${
|
|
8013
|
+
const parameter = `$${params.length}`;
|
|
8014
|
+
conditions.push(`(name ILIKE ${parameter} OR description ILIKE ${parameter} OR content ILIKE ${parameter})`);
|
|
7971
8015
|
}
|
|
7972
|
-
|
|
7973
|
-
|
|
7974
|
-
|
|
8016
|
+
return {
|
|
8017
|
+
where: conditions.length ? `WHERE ${conditions.join(" AND ")}` : "",
|
|
8018
|
+
params
|
|
8019
|
+
};
|
|
8020
|
+
}
|
|
8021
|
+
async function listConfigsPage(client, filter = {}, options = {}) {
|
|
8022
|
+
const normalized = normalizeBoundedReadOptions(options);
|
|
8023
|
+
const { where, params } = configFilterSql(filter);
|
|
8024
|
+
const count = await client.get(`SELECT COUNT(*) AS total FROM configs ${where}`, params);
|
|
8025
|
+
const rows = await client.many(`SELECT * FROM configs ${where} ORDER BY id LIMIT $${params.length + 1} OFFSET $${params.length + 2}`, [...params, normalized.limit, normalized.cursor]);
|
|
8026
|
+
return boundedReadPage(rows.map(rowToConfig), Number(count?.total ?? 0), normalized);
|
|
8027
|
+
}
|
|
8028
|
+
async function listConfigIdentitiesPage(client, filterOrOptions = {}, maybeOptions) {
|
|
8029
|
+
const filterKeys = ["category", "agent", "kind", "is_template", "search", "tags"];
|
|
8030
|
+
const isFilterOnly = maybeOptions === undefined && filterKeys.some((key) => (key in filterOrOptions));
|
|
8031
|
+
const filter = maybeOptions === undefined && !isFilterOnly ? {} : filterOrOptions;
|
|
8032
|
+
const options = maybeOptions === undefined && !isFilterOnly ? filterOrOptions : maybeOptions ?? {};
|
|
8033
|
+
const normalized = normalizeBoundedReadOptions(options);
|
|
8034
|
+
const { where, params } = configFilterSql(filter);
|
|
8035
|
+
const count = await client.get(`SELECT COUNT(*) AS total FROM configs ${where}`, params);
|
|
8036
|
+
const rows = await client.many(`SELECT id, name, slug, kind, category, agent, format, is_template, version, created_at, updated_at, synced_at
|
|
8037
|
+
FROM configs ${where} ORDER BY id LIMIT $${params.length + 1} OFFSET $${params.length + 2}`, [...params, normalized.limit, normalized.cursor]);
|
|
8038
|
+
return boundedReadPage(rows.map((row) => ({
|
|
8039
|
+
...row,
|
|
8040
|
+
created_at: toIso2(row.created_at),
|
|
8041
|
+
updated_at: toIso2(row.updated_at),
|
|
8042
|
+
synced_at: row.synced_at == null ? null : toIso2(row.synced_at)
|
|
8043
|
+
})), Number(count?.total ?? 0), normalized);
|
|
7975
8044
|
}
|
|
7976
8045
|
async function getConfig(client, idOrSlug) {
|
|
7977
8046
|
const row = await client.get("SELECT * FROM configs WHERE id = $1 OR slug = $1", [idOrSlug]);
|
|
@@ -7980,10 +8049,10 @@ async function getConfig(client, idOrSlug) {
|
|
|
7980
8049
|
return rowToConfig(row);
|
|
7981
8050
|
}
|
|
7982
8051
|
async function createConfig(client, input) {
|
|
7983
|
-
if (
|
|
7984
|
-
throw new
|
|
7985
|
-
if (!input.category)
|
|
7986
|
-
throw new
|
|
8052
|
+
if (typeof input.name !== "string" || !input.name.trim())
|
|
8053
|
+
throw new StoreValidationError("name is required", "NAME_REQUIRED");
|
|
8054
|
+
if (typeof input.category !== "string" || !input.category.trim())
|
|
8055
|
+
throw new StoreValidationError("category is required", "CATEGORY_REQUIRED");
|
|
7987
8056
|
const id = randomUUID();
|
|
7988
8057
|
const slug = await uniqueSlug(client, input.name);
|
|
7989
8058
|
const snapshotId = randomUUID();
|
|
@@ -8092,10 +8161,20 @@ async function createSnapshotContent(client, idOrSlug, content, version) {
|
|
|
8092
8161
|
throw new Error("snapshot insert failed");
|
|
8093
8162
|
return { id: row.id, config_id: row.config_id, content: row.content, version: Number(row.version), created_at: toIso2(row.created_at) };
|
|
8094
8163
|
}
|
|
8095
|
-
async function
|
|
8164
|
+
async function listSnapshotsPage(client, idOrSlug, options = {}) {
|
|
8096
8165
|
const config = await getConfig(client, idOrSlug);
|
|
8097
|
-
const
|
|
8098
|
-
|
|
8166
|
+
const normalized = normalizeBoundedReadOptions(options);
|
|
8167
|
+
const count = await client.get("SELECT COUNT(*) AS total FROM config_snapshots WHERE config_id = $1", [config.id]);
|
|
8168
|
+
const rows = await client.many(`SELECT id, config_id, content, version, created_at
|
|
8169
|
+
FROM config_snapshots WHERE config_id = $1
|
|
8170
|
+
ORDER BY version DESC, id LIMIT $2 OFFSET $3`, [config.id, normalized.limit, normalized.cursor]);
|
|
8171
|
+
return boundedReadPage(rows.map((row) => ({
|
|
8172
|
+
id: row.id,
|
|
8173
|
+
config_id: row.config_id,
|
|
8174
|
+
content: row.content,
|
|
8175
|
+
version: Number(row.version),
|
|
8176
|
+
created_at: toIso2(row.created_at)
|
|
8177
|
+
})), Number(count?.total ?? 0), normalized);
|
|
8099
8178
|
}
|
|
8100
8179
|
async function getSnapshotById(client, snapshotId) {
|
|
8101
8180
|
const row = await client.get("SELECT id, config_id, content, version, created_at FROM config_snapshots WHERE id = $1", [snapshotId]);
|
|
@@ -8125,40 +8204,31 @@ function rowToProfile(row) {
|
|
|
8125
8204
|
updated_at: toIso2(row.updated_at)
|
|
8126
8205
|
};
|
|
8127
8206
|
}
|
|
8128
|
-
async function listProfiles(client) {
|
|
8129
|
-
const profiles = [];
|
|
8130
|
-
let cursor = 0;
|
|
8131
|
-
while (true) {
|
|
8132
|
-
const page = await listProfilesPage(client, { limit: 100, cursor });
|
|
8133
|
-
profiles.push(...page.items);
|
|
8134
|
-
if (page.complete)
|
|
8135
|
-
return profiles;
|
|
8136
|
-
cursor = page.next_cursor;
|
|
8137
|
-
}
|
|
8138
|
-
}
|
|
8139
8207
|
async function listProfilesPage(client, options = {}) {
|
|
8140
8208
|
const normalized = normalizeBoundedReadOptions(options);
|
|
8141
8209
|
const count = await client.get("SELECT COUNT(*) AS total FROM profiles");
|
|
8142
|
-
const rows = await client.many("SELECT * FROM profiles ORDER BY
|
|
8210
|
+
const rows = await client.many("SELECT * FROM profiles ORDER BY id LIMIT $1 OFFSET $2", [normalized.limit, normalized.cursor]);
|
|
8143
8211
|
return boundedReadPage(rows.map(rowToProfile), Number(count?.total ?? 0), normalized);
|
|
8144
8212
|
}
|
|
8213
|
+
async function listProfileIdentitiesPage(client, options = {}) {
|
|
8214
|
+
const normalized = normalizeBoundedReadOptions(options);
|
|
8215
|
+
const count = await client.get("SELECT COUNT(*) AS total FROM profiles");
|
|
8216
|
+
const rows = await client.many(`SELECT id, name, slug, created_at, updated_at
|
|
8217
|
+
FROM profiles ORDER BY id LIMIT $1 OFFSET $2`, [normalized.limit, normalized.cursor]);
|
|
8218
|
+
return boundedReadPage(rows.map((row) => ({
|
|
8219
|
+
id: row.id,
|
|
8220
|
+
name: row.name,
|
|
8221
|
+
slug: row.slug,
|
|
8222
|
+
created_at: toIso2(row.created_at),
|
|
8223
|
+
updated_at: toIso2(row.updated_at)
|
|
8224
|
+
})), Number(count?.total ?? 0), normalized);
|
|
8225
|
+
}
|
|
8145
8226
|
async function getProfile(client, idOrSlug) {
|
|
8146
8227
|
const row = await client.get("SELECT * FROM profiles WHERE id = $1 OR slug = $1", [idOrSlug]);
|
|
8147
8228
|
if (!row)
|
|
8148
8229
|
throw new ProfileNotFoundError(idOrSlug);
|
|
8149
8230
|
return rowToProfile(row);
|
|
8150
8231
|
}
|
|
8151
|
-
async function getProfileConfigs(client, idOrSlug) {
|
|
8152
|
-
const configs = [];
|
|
8153
|
-
let cursor = 0;
|
|
8154
|
-
while (true) {
|
|
8155
|
-
const page = await getProfileConfigsPage(client, idOrSlug, { limit: 100, cursor });
|
|
8156
|
-
configs.push(...page.items);
|
|
8157
|
-
if (page.complete)
|
|
8158
|
-
return configs;
|
|
8159
|
-
cursor = page.next_cursor;
|
|
8160
|
-
}
|
|
8161
|
-
}
|
|
8162
8232
|
async function getProfileConfigsPage(client, idOrSlug, options = {}) {
|
|
8163
8233
|
const profile = await getProfile(client, idOrSlug);
|
|
8164
8234
|
const normalized = normalizeBoundedReadOptions(options);
|
|
@@ -8166,13 +8236,13 @@ async function getProfileConfigsPage(client, idOrSlug, options = {}) {
|
|
|
8166
8236
|
const rows = await client.many(`SELECT c.* FROM configs c
|
|
8167
8237
|
JOIN profile_configs pc ON pc.config_id = c.id
|
|
8168
8238
|
WHERE pc.profile_id = $1
|
|
8169
|
-
ORDER BY pc.sort_order
|
|
8239
|
+
ORDER BY pc.sort_order, c.id
|
|
8170
8240
|
LIMIT $2 OFFSET $3`, [profile.id, normalized.limit, normalized.cursor]);
|
|
8171
8241
|
return boundedReadPage(rows.map(rowToConfig), Number(count?.total ?? 0), normalized);
|
|
8172
8242
|
}
|
|
8173
8243
|
async function createProfile(client, input) {
|
|
8174
|
-
if (
|
|
8175
|
-
throw new
|
|
8244
|
+
if (typeof input.name !== "string" || !input.name.trim())
|
|
8245
|
+
throw new StoreValidationError("name is required", "NAME_REQUIRED");
|
|
8176
8246
|
const id = randomUUID();
|
|
8177
8247
|
const slug = await uniqueProfileSlug(client, input.name);
|
|
8178
8248
|
await client.execute(`INSERT INTO profiles (id, name, slug, description, selectors, variables, created_at, updated_at)
|
|
@@ -8224,18 +8294,32 @@ async function addConfigToProfile(client, profileIdOrSlug, configId) {
|
|
|
8224
8294
|
await client.execute(`INSERT INTO profile_configs (profile_id, config_id, sort_order)
|
|
8225
8295
|
VALUES ($1,$2,$3) ON CONFLICT (profile_id, config_id) DO NOTHING`, [profile.id, configId, order2]);
|
|
8226
8296
|
}
|
|
8227
|
-
|
|
8228
|
-
|
|
8229
|
-
const rows = await client.many(`SELECT profile_id, config_id, sort_order, binding
|
|
8230
|
-
FROM profile_configs
|
|
8231
|
-
WHERE profile_id = $1
|
|
8232
|
-
ORDER BY sort_order, config_id`, [profile.id]);
|
|
8233
|
-
return rows.map((row) => ({
|
|
8297
|
+
function rowToProfileConfigBinding(row) {
|
|
8298
|
+
return {
|
|
8234
8299
|
profile_id: row.profile_id,
|
|
8235
8300
|
config_id: row.config_id,
|
|
8236
8301
|
sort_order: Number(row.sort_order),
|
|
8237
8302
|
binding: row.binding == null ? legacyProfileConfigBinding() : normalizeProfileConfigBinding(row.binding)
|
|
8238
|
-
}
|
|
8303
|
+
};
|
|
8304
|
+
}
|
|
8305
|
+
async function getProfileConfigBindingsPage(client, profileIdOrSlug, options = {}) {
|
|
8306
|
+
const profile = await getProfile(client, profileIdOrSlug);
|
|
8307
|
+
const normalized = normalizeBoundedReadOptions(options);
|
|
8308
|
+
const count = await client.get("SELECT COUNT(*) AS total FROM profile_configs WHERE profile_id = $1", [profile.id]);
|
|
8309
|
+
const rows = await client.many(`SELECT profile_id, config_id, sort_order, binding
|
|
8310
|
+
FROM profile_configs
|
|
8311
|
+
WHERE profile_id = $1
|
|
8312
|
+
ORDER BY sort_order, config_id
|
|
8313
|
+
LIMIT $2 OFFSET $3`, [profile.id, normalized.limit, normalized.cursor]);
|
|
8314
|
+
return boundedReadPage(rows.map(rowToProfileConfigBinding), Number(count?.total ?? 0), normalized);
|
|
8315
|
+
}
|
|
8316
|
+
async function getProfileConfigBinding(client, profileId, configId) {
|
|
8317
|
+
const row = await client.get(`SELECT profile_id, config_id, sort_order, binding
|
|
8318
|
+
FROM profile_configs
|
|
8319
|
+
WHERE profile_id = $1 AND config_id = $2`, [profileId, configId]);
|
|
8320
|
+
if (!row)
|
|
8321
|
+
throw new Error(`Config ${configId} is not a member of profile ${profileId}.`);
|
|
8322
|
+
return rowToProfileConfigBinding(row);
|
|
8239
8323
|
}
|
|
8240
8324
|
async function setProfileConfigBinding(client, profileIdOrSlug, configId, binding) {
|
|
8241
8325
|
const profile = await getProfile(client, profileIdOrSlug);
|
|
@@ -8243,12 +8327,39 @@ async function setProfileConfigBinding(client, profileIdOrSlug, configId, bindin
|
|
|
8243
8327
|
const result = await client.query("UPDATE profile_configs SET binding = $1 WHERE profile_id = $2 AND config_id = $3", [JSON.stringify(normalized), profile.id, configId]);
|
|
8244
8328
|
if ((result.rowCount ?? 0) !== 1)
|
|
8245
8329
|
throw new Error(`Config ${configId} is not a member of profile ${profile.slug}.`);
|
|
8246
|
-
return (
|
|
8330
|
+
return getProfileConfigBinding(client, profile.id, configId);
|
|
8247
8331
|
}
|
|
8248
8332
|
async function removeConfigFromProfile(client, profileIdOrSlug, configId) {
|
|
8249
8333
|
const profile = await getProfile(client, profileIdOrSlug);
|
|
8250
8334
|
await client.execute("DELETE FROM profile_configs WHERE profile_id = $1 AND config_id = $2", [profile.id, configId]);
|
|
8251
8335
|
}
|
|
8336
|
+
function rowToProfileAssetBinding(row) {
|
|
8337
|
+
return {
|
|
8338
|
+
profile_id: row.profile_id,
|
|
8339
|
+
source_config_id: row.source_config_id,
|
|
8340
|
+
sort_order: Number(row.sort_order),
|
|
8341
|
+
binding: normalizeProfileAssetBinding(row.binding)
|
|
8342
|
+
};
|
|
8343
|
+
}
|
|
8344
|
+
async function getProfileAssetBindingsPage(client, profileIdOrSlug, options = {}) {
|
|
8345
|
+
const profile = await getProfile(client, profileIdOrSlug);
|
|
8346
|
+
const normalized = normalizeBoundedReadOptions(options);
|
|
8347
|
+
const count = await client.get("SELECT COUNT(*) AS total FROM profile_assets WHERE profile_id = $1", [profile.id]);
|
|
8348
|
+
const rows = await client.many(`SELECT profile_id, source_config_id, sort_order, binding
|
|
8349
|
+
FROM profile_assets
|
|
8350
|
+
WHERE profile_id = $1
|
|
8351
|
+
ORDER BY sort_order, asset_key
|
|
8352
|
+
LIMIT $2 OFFSET $3`, [profile.id, normalized.limit, normalized.cursor]);
|
|
8353
|
+
return boundedReadPage(rows.map(rowToProfileAssetBinding), Number(count?.total ?? 0), normalized);
|
|
8354
|
+
}
|
|
8355
|
+
async function getProfileAssetBinding(client, profileId, assetKey) {
|
|
8356
|
+
const row = await client.get(`SELECT profile_id, source_config_id, sort_order, binding
|
|
8357
|
+
FROM profile_assets
|
|
8358
|
+
WHERE profile_id = $1 AND asset_key = $2`, [profileId, assetKey]);
|
|
8359
|
+
if (!row)
|
|
8360
|
+
throw new Error(`Asset ${assetKey} is not a member of profile ${profileId}.`);
|
|
8361
|
+
return rowToProfileAssetBinding(row);
|
|
8362
|
+
}
|
|
8252
8363
|
async function addAssetToProfile(client, profileIdOrSlug, sourceConfigId, binding) {
|
|
8253
8364
|
const profile = await getProfile(client, profileIdOrSlug);
|
|
8254
8365
|
await getConfig(client, sourceConfigId);
|
|
@@ -8256,7 +8367,7 @@ async function addAssetToProfile(client, profileIdOrSlug, sourceConfigId, bindin
|
|
|
8256
8367
|
const maxRow = await client.get("SELECT MAX(sort_order) AS max_order FROM profile_assets WHERE profile_id = $1", [profile.id]);
|
|
8257
8368
|
const order2 = (maxRow?.max_order ?? -1) + 1;
|
|
8258
8369
|
await client.execute("INSERT INTO profile_assets (profile_id, source_config_id, asset_key, sort_order, binding) VALUES ($1,$2,$3,$4,$5::jsonb)", [profile.id, sourceConfigId, normalized.assetKey, order2, JSON.stringify(normalized)]);
|
|
8259
|
-
return (
|
|
8370
|
+
return getProfileAssetBinding(client, profile.id, normalized.assetKey);
|
|
8260
8371
|
}
|
|
8261
8372
|
async function setProfileAssetBinding(client, profileIdOrSlug, assetKey, binding) {
|
|
8262
8373
|
const profile = await getProfile(client, profileIdOrSlug);
|
|
@@ -8266,17 +8377,7 @@ async function setProfileAssetBinding(client, profileIdOrSlug, assetKey, binding
|
|
|
8266
8377
|
const result = await client.query("UPDATE profile_assets SET binding = $1::jsonb WHERE profile_id = $2 AND asset_key = $3", [JSON.stringify(normalized), profile.id, assetKey]);
|
|
8267
8378
|
if ((result.rowCount ?? 0) !== 1)
|
|
8268
8379
|
throw new Error(`Asset ${assetKey} is not a member of profile ${profile.slug}.`);
|
|
8269
|
-
return (
|
|
8270
|
-
}
|
|
8271
|
-
async function getProfileAssetBindings(client, profileIdOrSlug) {
|
|
8272
|
-
const profile = await getProfile(client, profileIdOrSlug);
|
|
8273
|
-
const rows = await client.many("SELECT profile_id, source_config_id, sort_order, binding FROM profile_assets WHERE profile_id = $1 ORDER BY sort_order, asset_key", [profile.id]);
|
|
8274
|
-
return rows.map((row) => ({
|
|
8275
|
-
profile_id: row.profile_id,
|
|
8276
|
-
source_config_id: row.source_config_id,
|
|
8277
|
-
sort_order: Number(row.sort_order),
|
|
8278
|
-
binding: normalizeProfileAssetBinding(row.binding)
|
|
8279
|
-
}));
|
|
8380
|
+
return getProfileAssetBinding(client, profile.id, assetKey);
|
|
8280
8381
|
}
|
|
8281
8382
|
async function removeAssetFromProfile(client, profileIdOrSlug, assetKey) {
|
|
8282
8383
|
const profile = await getProfile(client, profileIdOrSlug);
|
|
@@ -8291,39 +8392,33 @@ async function resolveProfileForMachineRead(client, machine, options = {}) {
|
|
|
8291
8392
|
const os = (machine.os ?? "").trim().toLowerCase();
|
|
8292
8393
|
const osFamily = normalizeOsFamily(machine.os);
|
|
8293
8394
|
const arch = (machine.arch ?? "").trim().toLowerCase();
|
|
8294
|
-
|
|
8295
|
-
|
|
8296
|
-
|
|
8395
|
+
const readStableProfiles = (queryClient) => aggregateBoundedCollectionRead("profile resolution", (cursor) => listProfilesPage(queryClient, { limit, cursor }), (profile) => profile.id, true, true);
|
|
8396
|
+
const stableRead = hasTransaction(client) ? await client.transaction(async (transaction) => {
|
|
8397
|
+
await transaction.execute("SET TRANSACTION ISOLATION LEVEL REPEATABLE READ READ ONLY");
|
|
8398
|
+
return readStableProfiles(transaction);
|
|
8399
|
+
}) : await readStableProfiles(client);
|
|
8297
8400
|
let selected = null;
|
|
8298
|
-
|
|
8299
|
-
|
|
8300
|
-
|
|
8301
|
-
|
|
8302
|
-
|
|
8303
|
-
|
|
8304
|
-
|
|
8305
|
-
|
|
8306
|
-
|
|
8307
|
-
|
|
8308
|
-
|
|
8309
|
-
|
|
8310
|
-
|
|
8311
|
-
|
|
8312
|
-
|
|
8313
|
-
continue;
|
|
8314
|
-
const score = (p.selectors.hostnames?.length ? 100 : 0) + (p.selectors.os?.length ? 10 : 0) + (p.selectors.arch?.length ? 10 : 0);
|
|
8315
|
-
if (!selected || score > selected.score || score === selected.score && p.name.localeCompare(selected.profile.name) < 0) {
|
|
8316
|
-
selected = { profile: p, score };
|
|
8317
|
-
}
|
|
8401
|
+
for (const p of stableRead.items) {
|
|
8402
|
+
if (!profileHasSelectors(p.selectors))
|
|
8403
|
+
continue;
|
|
8404
|
+
const s = p.selectors;
|
|
8405
|
+
const osOk = !s.os?.length || s.os.some((c) => {
|
|
8406
|
+
const value = c.trim().toLowerCase();
|
|
8407
|
+
return value === os || normalizeOsFamily(c) === osFamily;
|
|
8408
|
+
});
|
|
8409
|
+
const archOk = !s.arch?.length || s.arch.some((c) => c.trim().toLowerCase() === arch);
|
|
8410
|
+
const hostOk = !s.hostnames?.length || s.hostnames.some((c) => c.trim().toLowerCase() === host);
|
|
8411
|
+
if (!osOk || !archOk || !hostOk)
|
|
8412
|
+
continue;
|
|
8413
|
+
const score = (p.selectors.hostnames?.length ? 100 : 0) + (p.selectors.os?.length ? 10 : 0) + (p.selectors.arch?.length ? 10 : 0);
|
|
8414
|
+
if (!selected || score > selected.score || score === selected.score && p.name.localeCompare(selected.profile.name) < 0) {
|
|
8415
|
+
selected = { profile: p, score };
|
|
8318
8416
|
}
|
|
8319
|
-
if (page.complete)
|
|
8320
|
-
break;
|
|
8321
|
-
cursor = page.next_cursor;
|
|
8322
8417
|
}
|
|
8323
8418
|
return {
|
|
8324
8419
|
profile: selected?.profile ?? null,
|
|
8325
|
-
scanned,
|
|
8326
|
-
total,
|
|
8420
|
+
scanned: stableRead.items.length,
|
|
8421
|
+
total: stableRead.total,
|
|
8327
8422
|
batch_limit: limit,
|
|
8328
8423
|
source_bounded: true,
|
|
8329
8424
|
complete: true,
|
|
@@ -8340,9 +8435,19 @@ function rowToMachine(row) {
|
|
|
8340
8435
|
created_at: toIso2(row.created_at)
|
|
8341
8436
|
};
|
|
8342
8437
|
}
|
|
8343
|
-
async function
|
|
8344
|
-
const
|
|
8345
|
-
|
|
8438
|
+
async function listMachinesPage(client, options = {}) {
|
|
8439
|
+
const normalized = normalizeBoundedReadOptions(options);
|
|
8440
|
+
const count = await client.get("SELECT COUNT(*) AS total FROM machines");
|
|
8441
|
+
const rows = await client.many(`SELECT * FROM machines
|
|
8442
|
+
ORDER BY id LIMIT $1 OFFSET $2`, [normalized.limit, normalized.cursor]);
|
|
8443
|
+
return boundedReadPage(rows.map(rowToMachine), Number(count?.total ?? 0), normalized);
|
|
8444
|
+
}
|
|
8445
|
+
async function listMachineIdentitiesPage(client, options = {}) {
|
|
8446
|
+
const normalized = normalizeBoundedReadOptions(options);
|
|
8447
|
+
const count = await client.get("SELECT COUNT(*) AS total FROM machines");
|
|
8448
|
+
const rows = await client.many(`SELECT id, hostname, os, arch, last_applied_at, created_at
|
|
8449
|
+
FROM machines ORDER BY id LIMIT $1 OFFSET $2`, [normalized.limit, normalized.cursor]);
|
|
8450
|
+
return boundedReadPage(rows.map(rowToMachine), Number(count?.total ?? 0), normalized);
|
|
8346
8451
|
}
|
|
8347
8452
|
async function registerMachine(client, hostname, os, arch) {
|
|
8348
8453
|
const existing = await client.get("SELECT * FROM machines WHERE hostname = $1", [hostname]);
|
|
@@ -8365,38 +8470,229 @@ async function insertFeedback(client, input) {
|
|
|
8365
8470
|
await client.execute("INSERT INTO feedback (id, message, email, category, version, created_at) VALUES ($1,$2,$3,$4,$5,now())", [randomUUID(), input.message, input.email ?? null, input.category ?? "general", input.version ?? null]);
|
|
8366
8471
|
}
|
|
8367
8472
|
|
|
8368
|
-
// src/server/
|
|
8369
|
-
var
|
|
8370
|
-
function
|
|
8371
|
-
return
|
|
8473
|
+
// src/server/cloud.ts
|
|
8474
|
+
var INSTRUCTIONS_APP_SLUG = "instructions";
|
|
8475
|
+
function resolveCloudDatabaseUrl(env = process.env) {
|
|
8476
|
+
return env.HASNA_INSTRUCTIONS_DATABASE_URL || env.INSTRUCTIONS_DATABASE_URL || env.DATABASE_URL || undefined;
|
|
8372
8477
|
}
|
|
8373
|
-
function
|
|
8374
|
-
return
|
|
8478
|
+
function resolveSigningSecret(env = process.env) {
|
|
8479
|
+
return env.HASNA_INSTRUCTIONS_API_SIGNING_KEY?.trim() || env.HASNA_API_SIGNING_KEY?.trim() || env.API_KEY_SIGNING_SECRET?.trim() || undefined;
|
|
8375
8480
|
}
|
|
8376
|
-
function
|
|
8481
|
+
function isPostgresBackendEnabled(env = process.env) {
|
|
8482
|
+
return Boolean(resolveCloudDatabaseUrl(env));
|
|
8483
|
+
}
|
|
8484
|
+
var cachedClient = null;
|
|
8485
|
+
var cachedStore = null;
|
|
8486
|
+
var cachedVerifier = null;
|
|
8487
|
+
var schemaEnsured = null;
|
|
8488
|
+
function getClient() {
|
|
8489
|
+
if (cachedClient)
|
|
8490
|
+
return cachedClient;
|
|
8491
|
+
const url = resolveCloudDatabaseUrl();
|
|
8492
|
+
if (!url) {
|
|
8493
|
+
throw new Error("Cloud /v1 requires a remote database URL (HASNA_INSTRUCTIONS_DATABASE_URL / INSTRUCTIONS_DATABASE_URL / DATABASE_URL).");
|
|
8494
|
+
}
|
|
8495
|
+
const pool2 = createPgPool({
|
|
8496
|
+
connectionString: url,
|
|
8497
|
+
max: 6,
|
|
8498
|
+
idleTimeoutMillis: 30000,
|
|
8499
|
+
connectionTimeoutMillis: 15000,
|
|
8500
|
+
applicationName: "instructions-serve"
|
|
8501
|
+
});
|
|
8502
|
+
cachedClient = createQueryClient(pool2);
|
|
8503
|
+
return cachedClient;
|
|
8504
|
+
}
|
|
8505
|
+
function getCloudClient() {
|
|
8506
|
+
return getClient();
|
|
8507
|
+
}
|
|
8508
|
+
function authClient() {
|
|
8509
|
+
const client = getClient();
|
|
8377
8510
|
return {
|
|
8378
|
-
|
|
8379
|
-
|
|
8380
|
-
|
|
8381
|
-
|
|
8382
|
-
|
|
8383
|
-
|
|
8384
|
-
|
|
8385
|
-
|
|
8386
|
-
|
|
8511
|
+
async many(sql, params = []) {
|
|
8512
|
+
return client.many(sql, params);
|
|
8513
|
+
},
|
|
8514
|
+
async get(sql, params = []) {
|
|
8515
|
+
return client.get(sql, params);
|
|
8516
|
+
},
|
|
8517
|
+
async execute(sql, params = []) {
|
|
8518
|
+
await client.execute(sql, params);
|
|
8519
|
+
}
|
|
8520
|
+
};
|
|
8521
|
+
}
|
|
8522
|
+
function getApiKeyStore() {
|
|
8523
|
+
if (cachedStore)
|
|
8524
|
+
return cachedStore;
|
|
8525
|
+
cachedStore = new ApiKeyStore(authClient());
|
|
8526
|
+
return cachedStore;
|
|
8527
|
+
}
|
|
8528
|
+
async function ensureCloudSchema() {
|
|
8529
|
+
if (schemaEnsured)
|
|
8530
|
+
return schemaEnsured;
|
|
8531
|
+
schemaEnsured = (async () => {
|
|
8532
|
+
const client = getClient();
|
|
8533
|
+
for (const sql of instructionsSchemaSql()) {
|
|
8534
|
+
await client.execute(sql);
|
|
8535
|
+
}
|
|
8536
|
+
await ensureIdempotencySchema(client);
|
|
8537
|
+
await getApiKeyStore().ensureSchema();
|
|
8538
|
+
})().catch((e) => {
|
|
8539
|
+
schemaEnsured = null;
|
|
8540
|
+
throw e;
|
|
8541
|
+
});
|
|
8542
|
+
return schemaEnsured;
|
|
8543
|
+
}
|
|
8544
|
+
var honoMiddlewareCache = new Map;
|
|
8545
|
+
function getHonoAuthMiddleware(requiredScopes) {
|
|
8546
|
+
const key = requiredScopes.join(",");
|
|
8547
|
+
const cached2 = honoMiddlewareCache.get(key);
|
|
8548
|
+
if (cached2)
|
|
8549
|
+
return cached2;
|
|
8550
|
+
const signingSecret = resolveSigningSecret();
|
|
8551
|
+
if (!signingSecret) {
|
|
8552
|
+
throw new Error("Cloud /v1 auth requires a signing secret (HASNA_INSTRUCTIONS_API_SIGNING_KEY / HASNA_API_SIGNING_KEY / API_KEY_SIGNING_SECRET).");
|
|
8553
|
+
}
|
|
8554
|
+
const store = getApiKeyStore();
|
|
8555
|
+
const mw = honoApiKey({
|
|
8556
|
+
app: INSTRUCTIONS_APP_SLUG,
|
|
8557
|
+
signingSecret,
|
|
8558
|
+
keyStatus: store.keyStatus,
|
|
8559
|
+
requiredScopes
|
|
8560
|
+
});
|
|
8561
|
+
honoMiddlewareCache.set(key, mw);
|
|
8562
|
+
return mw;
|
|
8563
|
+
}
|
|
8564
|
+
async function pingCloud() {
|
|
8565
|
+
const client = getClient();
|
|
8566
|
+
const row = await client.get("SELECT 1 AS ok");
|
|
8567
|
+
return row?.ok === 1;
|
|
8568
|
+
}
|
|
8569
|
+
async function closeCloud() {
|
|
8570
|
+
if (cachedClient)
|
|
8571
|
+
await cachedClient.close();
|
|
8572
|
+
cachedClient = null;
|
|
8573
|
+
cachedStore = null;
|
|
8574
|
+
cachedVerifier = null;
|
|
8575
|
+
honoMiddlewareCache.clear();
|
|
8576
|
+
schemaEnsured = null;
|
|
8577
|
+
honoMiddlewareCache.clear();
|
|
8578
|
+
}
|
|
8579
|
+
|
|
8580
|
+
// src/server/v1.ts
|
|
8581
|
+
var JSON_HEADERS = { "Content-Type": "application/json" };
|
|
8582
|
+
var MAX_REQUEST_BODY_BYTES = 1024 * 1024;
|
|
8583
|
+
var MAX_SEARCH_QUERY_CHARS = 512;
|
|
8584
|
+
var MAX_IDEMPOTENCY_KEY_CHARS = 255;
|
|
8585
|
+
|
|
8586
|
+
class HttpInputError extends Error {
|
|
8587
|
+
status;
|
|
8588
|
+
code;
|
|
8589
|
+
constructor(status, code, message) {
|
|
8590
|
+
super(message);
|
|
8591
|
+
this.status = status;
|
|
8592
|
+
this.code = code;
|
|
8593
|
+
this.name = "HttpInputError";
|
|
8594
|
+
}
|
|
8595
|
+
}
|
|
8596
|
+
function json(body, status = 200) {
|
|
8597
|
+
return new Response(JSON.stringify(body), { status, headers: JSON_HEADERS });
|
|
8598
|
+
}
|
|
8599
|
+
function errorResponse(status, message, extra) {
|
|
8600
|
+
return json({ error: message, ...extra ?? {} }, status);
|
|
8601
|
+
}
|
|
8602
|
+
function pagePayload(alias, page) {
|
|
8603
|
+
return {
|
|
8604
|
+
...page,
|
|
8605
|
+
[alias]: page.items,
|
|
8606
|
+
count: page.items.length
|
|
8387
8607
|
};
|
|
8388
8608
|
}
|
|
8609
|
+
function authenticatedPrincipalAuthority(principal) {
|
|
8610
|
+
if (!principal)
|
|
8611
|
+
throw new Error("authenticated principal is unavailable");
|
|
8612
|
+
return [
|
|
8613
|
+
principal.app,
|
|
8614
|
+
`tenant:${principal.tid ?? "-"}`,
|
|
8615
|
+
`agent:${principal.agent ?? "-"}`,
|
|
8616
|
+
`kid:${principal.kid}`
|
|
8617
|
+
].join("|");
|
|
8618
|
+
}
|
|
8619
|
+
function readIdempotencyKey(req) {
|
|
8620
|
+
const raw2 = req.headers.get("idempotency-key");
|
|
8621
|
+
if (raw2 === null)
|
|
8622
|
+
return null;
|
|
8623
|
+
const key = raw2.trim();
|
|
8624
|
+
if (key.length < 1 || key.length > MAX_IDEMPOTENCY_KEY_CHARS || /[^\x21-\x7e]/.test(key)) {
|
|
8625
|
+
throw new HttpInputError(400, "INVALID_IDEMPOTENCY_KEY", `Idempotency-Key must contain 1-${MAX_IDEMPOTENCY_KEY_CHARS} visible ASCII characters`);
|
|
8626
|
+
}
|
|
8627
|
+
return key;
|
|
8628
|
+
}
|
|
8629
|
+
async function mutationResponse(req, client, context, operation, requestBody, status, perform) {
|
|
8630
|
+
const key = readIdempotencyKey(req);
|
|
8631
|
+
if (!key)
|
|
8632
|
+
return json(await perform(client), status);
|
|
8633
|
+
const result = await executeIdempotentRequest(client, {
|
|
8634
|
+
principal: authenticatedPrincipalAuthority(context.principal),
|
|
8635
|
+
operation,
|
|
8636
|
+
key,
|
|
8637
|
+
body: requestBody
|
|
8638
|
+
}, async (transaction) => ({ status, body: await perform(transaction) }));
|
|
8639
|
+
return json(result.body, result.status);
|
|
8640
|
+
}
|
|
8389
8641
|
async function readJson(req) {
|
|
8642
|
+
const declaredLength = req.headers.get("content-length");
|
|
8643
|
+
if (declaredLength !== null) {
|
|
8644
|
+
const parsed = Number(declaredLength);
|
|
8645
|
+
if (!Number.isSafeInteger(parsed) || parsed < 0)
|
|
8646
|
+
return null;
|
|
8647
|
+
if (parsed > MAX_REQUEST_BODY_BYTES) {
|
|
8648
|
+
throw new HttpInputError(413, "REQUEST_BODY_TOO_LARGE", "request body exceeds the 1 MiB limit");
|
|
8649
|
+
}
|
|
8650
|
+
}
|
|
8651
|
+
if (!req.body)
|
|
8652
|
+
return {};
|
|
8653
|
+
const reader = req.body.getReader();
|
|
8654
|
+
const chunks = [];
|
|
8655
|
+
let total = 0;
|
|
8390
8656
|
try {
|
|
8391
|
-
|
|
8392
|
-
|
|
8393
|
-
|
|
8394
|
-
|
|
8657
|
+
while (true) {
|
|
8658
|
+
const { done, value } = await reader.read();
|
|
8659
|
+
if (done)
|
|
8660
|
+
break;
|
|
8661
|
+
if (!value)
|
|
8662
|
+
continue;
|
|
8663
|
+
total += value.byteLength;
|
|
8664
|
+
if (total > MAX_REQUEST_BODY_BYTES) {
|
|
8665
|
+
await reader.cancel();
|
|
8666
|
+
throw new HttpInputError(413, "REQUEST_BODY_TOO_LARGE", "request body exceeds the 1 MiB limit");
|
|
8667
|
+
}
|
|
8668
|
+
chunks.push(value);
|
|
8669
|
+
}
|
|
8670
|
+
} finally {
|
|
8671
|
+
reader.releaseLock();
|
|
8672
|
+
}
|
|
8673
|
+
if (total === 0)
|
|
8674
|
+
return {};
|
|
8675
|
+
const bytes = new Uint8Array(total);
|
|
8676
|
+
let offset = 0;
|
|
8677
|
+
for (const chunk of chunks) {
|
|
8678
|
+
bytes.set(chunk, offset);
|
|
8679
|
+
offset += chunk.byteLength;
|
|
8680
|
+
}
|
|
8681
|
+
try {
|
|
8682
|
+
return JSON.parse(new TextDecoder().decode(bytes));
|
|
8395
8683
|
} catch {
|
|
8396
8684
|
return null;
|
|
8397
8685
|
}
|
|
8398
8686
|
}
|
|
8399
|
-
|
|
8687
|
+
function boundedSearchQuery(value) {
|
|
8688
|
+
if (value === null || value === "")
|
|
8689
|
+
return;
|
|
8690
|
+
if (value.length > MAX_SEARCH_QUERY_CHARS) {
|
|
8691
|
+
throw new HttpInputError(400, "SEARCH_QUERY_TOO_LONG", `search query exceeds ${MAX_SEARCH_QUERY_CHARS} characters`);
|
|
8692
|
+
}
|
|
8693
|
+
return value;
|
|
8694
|
+
}
|
|
8695
|
+
async function handleV1Request(req, url, context = {}) {
|
|
8400
8696
|
const path = url.pathname;
|
|
8401
8697
|
if (path !== "/v1" && !path.startsWith("/v1/"))
|
|
8402
8698
|
return null;
|
|
@@ -8404,7 +8700,8 @@ async function handleV1Request(req, url) {
|
|
|
8404
8700
|
try {
|
|
8405
8701
|
await ensureCloudSchema();
|
|
8406
8702
|
} catch (e) {
|
|
8407
|
-
|
|
8703
|
+
console.error("instructions /v1: database unavailable");
|
|
8704
|
+
return errorResponse(503, "Instructions database is unavailable", { code: "DATABASE_UNAVAILABLE" });
|
|
8408
8705
|
}
|
|
8409
8706
|
const client = getCloudClient();
|
|
8410
8707
|
const segments = path.split("/").filter(Boolean);
|
|
@@ -8415,25 +8712,31 @@ async function handleV1Request(req, url) {
|
|
|
8415
8712
|
if (resource === "configs") {
|
|
8416
8713
|
if (!id) {
|
|
8417
8714
|
if (method === "GET") {
|
|
8715
|
+
const search = boundedSearchQuery(url.searchParams.get("search"));
|
|
8418
8716
|
const filter = {
|
|
8419
8717
|
...url.searchParams.get("category") ? { category: url.searchParams.get("category") } : {},
|
|
8420
8718
|
...url.searchParams.get("agent") ? { agent: url.searchParams.get("agent") } : {},
|
|
8421
8719
|
...url.searchParams.get("kind") ? { kind: url.searchParams.get("kind") } : {},
|
|
8422
|
-
...
|
|
8720
|
+
...search ? { search } : {}
|
|
8721
|
+
};
|
|
8722
|
+
const options = {
|
|
8723
|
+
limit: url.searchParams.get("limit") ?? undefined,
|
|
8724
|
+
cursor: url.searchParams.get("cursor") ?? undefined
|
|
8423
8725
|
};
|
|
8424
|
-
|
|
8425
|
-
|
|
8726
|
+
if (url.searchParams.get("view") === "identity") {
|
|
8727
|
+
const page2 = await listConfigIdentitiesPage(client, filter, options);
|
|
8728
|
+
return json(pagePayload("configs", page2));
|
|
8729
|
+
}
|
|
8730
|
+
const page = await listConfigsPage(client, filter, options);
|
|
8731
|
+
return json(pagePayload("configs", page));
|
|
8426
8732
|
}
|
|
8427
8733
|
if (method === "POST") {
|
|
8428
8734
|
const body = await readJson(req);
|
|
8429
8735
|
if (!body)
|
|
8430
8736
|
return errorResponse(400, "invalid JSON body");
|
|
8431
|
-
|
|
8432
|
-
|
|
8433
|
-
|
|
8434
|
-
} catch (e) {
|
|
8435
|
-
return errorResponse(400, e.message);
|
|
8436
|
-
}
|
|
8737
|
+
return await mutationResponse(req, client, context, "POST /v1/configs", body, 201, async (transaction) => ({
|
|
8738
|
+
config: await createConfig(transaction, body)
|
|
8739
|
+
}));
|
|
8437
8740
|
}
|
|
8438
8741
|
return errorResponse(405, `method ${method} not allowed on /v1/configs`);
|
|
8439
8742
|
}
|
|
@@ -8455,13 +8758,17 @@ async function handleV1Request(req, url) {
|
|
|
8455
8758
|
return json({ snapshot });
|
|
8456
8759
|
}
|
|
8457
8760
|
if (method === "GET") {
|
|
8458
|
-
const
|
|
8459
|
-
|
|
8761
|
+
const page = await listSnapshotsPage(client, id, {
|
|
8762
|
+
limit: url.searchParams.get("limit") ?? undefined,
|
|
8763
|
+
cursor: url.searchParams.get("cursor") ?? undefined
|
|
8764
|
+
});
|
|
8765
|
+
return json(pagePayload("snapshots", page));
|
|
8460
8766
|
}
|
|
8461
8767
|
if (method === "POST") {
|
|
8462
8768
|
const body = await readJson(req);
|
|
8463
|
-
|
|
8464
|
-
|
|
8769
|
+
return await mutationResponse(req, client, context, "POST /v1/configs/:id/snapshots", { config_id: id, ...body ?? {} }, 201, async (transaction) => ({
|
|
8770
|
+
snapshot: body && typeof body.content === "string" && typeof body.version === "number" ? await createSnapshotContent(transaction, id, body.content, body.version) : await createSnapshot(transaction, id)
|
|
8771
|
+
}));
|
|
8465
8772
|
}
|
|
8466
8773
|
return errorResponse(405, `method ${method} not allowed on /v1/configs/:id/snapshots`);
|
|
8467
8774
|
}
|
|
@@ -8487,26 +8794,24 @@ async function handleV1Request(req, url) {
|
|
|
8487
8794
|
if (resource === "profiles") {
|
|
8488
8795
|
if (!id) {
|
|
8489
8796
|
if (method === "GET") {
|
|
8490
|
-
|
|
8491
|
-
const profiles = await listProfiles(client);
|
|
8492
|
-
return json({ ...completeLegacyPage(profiles), profiles, count: profiles.length });
|
|
8493
|
-
}
|
|
8494
|
-
const page = await listProfilesPage(client, {
|
|
8797
|
+
const options = {
|
|
8495
8798
|
limit: url.searchParams.get("limit") ?? undefined,
|
|
8496
8799
|
cursor: url.searchParams.get("cursor") ?? undefined
|
|
8497
|
-
}
|
|
8498
|
-
|
|
8800
|
+
};
|
|
8801
|
+
if (url.searchParams.get("view") === "identity") {
|
|
8802
|
+
const page2 = await listProfileIdentitiesPage(client, options);
|
|
8803
|
+
return json(pagePayload("profiles", page2));
|
|
8804
|
+
}
|
|
8805
|
+
const page = await listProfilesPage(client, options);
|
|
8806
|
+
return json(pagePayload("profiles", page));
|
|
8499
8807
|
}
|
|
8500
8808
|
if (method === "POST") {
|
|
8501
8809
|
const body = await readJson(req);
|
|
8502
8810
|
if (!body)
|
|
8503
8811
|
return errorResponse(400, "invalid JSON body");
|
|
8504
|
-
|
|
8505
|
-
|
|
8506
|
-
|
|
8507
|
-
} catch (e) {
|
|
8508
|
-
return errorResponse(400, e.message);
|
|
8509
|
-
}
|
|
8812
|
+
return await mutationResponse(req, client, context, "POST /v1/profiles", body, 201, async (transaction) => ({
|
|
8813
|
+
profile: await createProfile(transaction, body)
|
|
8814
|
+
}));
|
|
8510
8815
|
}
|
|
8511
8816
|
return errorResponse(405, `method ${method} not allowed on /v1/profiles`);
|
|
8512
8817
|
}
|
|
@@ -8523,8 +8828,11 @@ async function handleV1Request(req, url) {
|
|
|
8523
8828
|
if (action === "bindings") {
|
|
8524
8829
|
if (method !== "GET")
|
|
8525
8830
|
return errorResponse(405, `method ${method} not allowed on /v1/profiles/:id/bindings`);
|
|
8526
|
-
const
|
|
8527
|
-
|
|
8831
|
+
const page = await getProfileConfigBindingsPage(client, id, {
|
|
8832
|
+
limit: url.searchParams.get("limit") ?? undefined,
|
|
8833
|
+
cursor: url.searchParams.get("cursor") ?? undefined
|
|
8834
|
+
});
|
|
8835
|
+
return json(pagePayload("bindings", page));
|
|
8528
8836
|
}
|
|
8529
8837
|
if (action === "configs") {
|
|
8530
8838
|
const configId = segments[4] ? decodeURIComponent(segments[4]) : undefined;
|
|
@@ -8532,8 +8840,10 @@ async function handleV1Request(req, url) {
|
|
|
8532
8840
|
const body = await readJson(req);
|
|
8533
8841
|
if (!body?.config_id)
|
|
8534
8842
|
return errorResponse(400, "config_id is required");
|
|
8535
|
-
await
|
|
8536
|
-
|
|
8843
|
+
return await mutationResponse(req, client, context, "POST /v1/profiles/:id/configs", { profile_id: id, config_id: body.config_id }, 200, async (transaction) => {
|
|
8844
|
+
await addConfigToProfile(transaction, id, body.config_id);
|
|
8845
|
+
return { added: true };
|
|
8846
|
+
});
|
|
8537
8847
|
}
|
|
8538
8848
|
if (method === "DELETE" && configId) {
|
|
8539
8849
|
await removeConfigFromProfile(client, id, configId);
|
|
@@ -8543,30 +8853,36 @@ async function handleV1Request(req, url) {
|
|
|
8543
8853
|
const body = await readJson(req);
|
|
8544
8854
|
if (!body?.binding)
|
|
8545
8855
|
return errorResponse(400, "binding is required");
|
|
8546
|
-
|
|
8547
|
-
|
|
8856
|
+
return await mutationResponse(req, client, context, "PUT /v1/profiles/:id/configs/:configId", { profile_id: id, config_id: configId, binding: body.binding }, 200, async (transaction) => ({
|
|
8857
|
+
binding: await setProfileConfigBinding(transaction, id, configId, body.binding)
|
|
8858
|
+
}));
|
|
8548
8859
|
}
|
|
8549
8860
|
return errorResponse(405, `method ${method} not allowed on /v1/profiles/:id/configs`);
|
|
8550
8861
|
}
|
|
8551
8862
|
if (action === "assets") {
|
|
8552
8863
|
const assetKey = segments[4] ? decodeURIComponent(segments[4]) : undefined;
|
|
8553
8864
|
if (method === "GET" && !assetKey) {
|
|
8554
|
-
const
|
|
8555
|
-
|
|
8865
|
+
const page = await getProfileAssetBindingsPage(client, id, {
|
|
8866
|
+
limit: url.searchParams.get("limit") ?? undefined,
|
|
8867
|
+
cursor: url.searchParams.get("cursor") ?? undefined
|
|
8868
|
+
});
|
|
8869
|
+
return json(pagePayload("assets", page));
|
|
8556
8870
|
}
|
|
8557
8871
|
if (method === "POST" && !assetKey) {
|
|
8558
8872
|
const body = await readJson(req);
|
|
8559
8873
|
if (!body?.source_config_id || !body.binding)
|
|
8560
8874
|
return errorResponse(400, "source_config_id and binding are required");
|
|
8561
|
-
|
|
8562
|
-
|
|
8875
|
+
return await mutationResponse(req, client, context, "POST /v1/profiles/:id/assets", { profile_id: id, source_config_id: body.source_config_id, binding: body.binding }, 201, async (transaction) => ({
|
|
8876
|
+
asset: await addAssetToProfile(transaction, id, body.source_config_id, body.binding)
|
|
8877
|
+
}));
|
|
8563
8878
|
}
|
|
8564
8879
|
if (method === "PUT" && assetKey) {
|
|
8565
8880
|
const body = await readJson(req);
|
|
8566
8881
|
if (!body?.binding)
|
|
8567
8882
|
return errorResponse(400, "binding is required");
|
|
8568
|
-
|
|
8569
|
-
|
|
8883
|
+
return await mutationResponse(req, client, context, "PUT /v1/profiles/:id/assets/:assetKey", { profile_id: id, asset_key: assetKey, binding: body.binding }, 200, async (transaction) => ({
|
|
8884
|
+
asset: await setProfileAssetBinding(transaction, id, assetKey, body.binding)
|
|
8885
|
+
}));
|
|
8570
8886
|
}
|
|
8571
8887
|
if (method === "DELETE" && assetKey) {
|
|
8572
8888
|
await removeAssetFromProfile(client, id, assetKey);
|
|
@@ -8578,13 +8894,6 @@ async function handleV1Request(req, url) {
|
|
|
8578
8894
|
return errorResponse(404, `unknown profile action: ${action}`);
|
|
8579
8895
|
if (method === "GET") {
|
|
8580
8896
|
const profile = await getProfile(client, id);
|
|
8581
|
-
if (!url.searchParams.has("limit") && !url.searchParams.has("cursor")) {
|
|
8582
|
-
const legacyConfigs = await getProfileConfigs(client, id);
|
|
8583
|
-
return json({
|
|
8584
|
-
profile: { ...profile, configs: legacyConfigs },
|
|
8585
|
-
configs: completeLegacyPage(legacyConfigs)
|
|
8586
|
-
});
|
|
8587
|
-
}
|
|
8588
8897
|
const configs = await getProfileConfigsPage(client, id, {
|
|
8589
8898
|
limit: url.searchParams.get("limit") ?? undefined,
|
|
8590
8899
|
cursor: url.searchParams.get("cursor") ?? undefined
|
|
@@ -8629,15 +8938,20 @@ async function handleV1Request(req, url) {
|
|
|
8629
8938
|
}
|
|
8630
8939
|
if (!id) {
|
|
8631
8940
|
if (method === "GET") {
|
|
8632
|
-
const
|
|
8633
|
-
|
|
8941
|
+
const options = {
|
|
8942
|
+
limit: url.searchParams.get("limit") ?? undefined,
|
|
8943
|
+
cursor: url.searchParams.get("cursor") ?? undefined
|
|
8944
|
+
};
|
|
8945
|
+
const page = url.searchParams.get("view") === "identity" ? await listMachineIdentitiesPage(client, options) : await listMachinesPage(client, options);
|
|
8946
|
+
return json(pagePayload("machines", page));
|
|
8634
8947
|
}
|
|
8635
8948
|
if (method === "POST") {
|
|
8636
8949
|
const body = await readJson(req);
|
|
8637
8950
|
if (!body?.hostname)
|
|
8638
8951
|
return errorResponse(400, "hostname is required");
|
|
8639
|
-
|
|
8640
|
-
|
|
8952
|
+
return await mutationResponse(req, client, context, "POST /v1/machines", body, 201, async (transaction) => ({
|
|
8953
|
+
machine: await registerMachine(transaction, body.hostname, body.os ?? null, body.arch ?? null)
|
|
8954
|
+
}));
|
|
8641
8955
|
}
|
|
8642
8956
|
return errorResponse(405, `method ${method} not allowed on /v1/machines`);
|
|
8643
8957
|
}
|
|
@@ -8659,10 +8973,17 @@ async function handleV1Request(req, url) {
|
|
|
8659
8973
|
}
|
|
8660
8974
|
return errorResponse(404, `unknown /v1 resource: ${resource ?? "(root)"}`);
|
|
8661
8975
|
} catch (e) {
|
|
8976
|
+
if (e instanceof HttpInputError)
|
|
8977
|
+
return errorResponse(e.status, e.message, { code: e.code });
|
|
8978
|
+
if (e instanceof StoreValidationError)
|
|
8979
|
+
return errorResponse(400, e.message, { code: e.code });
|
|
8980
|
+
if (e instanceof IdempotencyConflictError)
|
|
8981
|
+
return errorResponse(409, e.message, { code: e.code });
|
|
8662
8982
|
if (e instanceof ConfigNotFoundError || e instanceof ProfileNotFoundError) {
|
|
8663
8983
|
return errorResponse(404, e.message);
|
|
8664
8984
|
}
|
|
8665
|
-
|
|
8985
|
+
console.error("instructions /v1: request failed");
|
|
8986
|
+
return errorResponse(500, "Instructions request failed", { code: "INTERNAL_ERROR" });
|
|
8666
8987
|
}
|
|
8667
8988
|
}
|
|
8668
8989
|
|
|
@@ -8702,6 +9023,88 @@ var profileSchema = {
|
|
|
8702
9023
|
updated_at: { type: "string" }
|
|
8703
9024
|
}
|
|
8704
9025
|
};
|
|
9026
|
+
var configIdentitySchema = {
|
|
9027
|
+
type: "object",
|
|
9028
|
+
required: ["id", "name", "slug", "kind", "category", "agent", "format", "is_template", "version", "created_at", "updated_at", "synced_at"],
|
|
9029
|
+
properties: {
|
|
9030
|
+
id: { type: "string" },
|
|
9031
|
+
name: { type: "string" },
|
|
9032
|
+
slug: { type: "string" },
|
|
9033
|
+
kind: { type: "string" },
|
|
9034
|
+
category: { type: "string" },
|
|
9035
|
+
agent: { type: "string" },
|
|
9036
|
+
format: { type: "string" },
|
|
9037
|
+
is_template: { type: "boolean" },
|
|
9038
|
+
version: { type: "number" },
|
|
9039
|
+
created_at: { type: "string" },
|
|
9040
|
+
updated_at: { type: "string" },
|
|
9041
|
+
synced_at: { type: "string", nullable: true }
|
|
9042
|
+
}
|
|
9043
|
+
};
|
|
9044
|
+
var profileIdentitySchema = {
|
|
9045
|
+
type: "object",
|
|
9046
|
+
required: ["id", "name", "slug", "created_at", "updated_at"],
|
|
9047
|
+
properties: {
|
|
9048
|
+
id: { type: "string" },
|
|
9049
|
+
name: { type: "string" },
|
|
9050
|
+
slug: { type: "string" },
|
|
9051
|
+
created_at: { type: "string" },
|
|
9052
|
+
updated_at: { type: "string" }
|
|
9053
|
+
}
|
|
9054
|
+
};
|
|
9055
|
+
var machineSchema = {
|
|
9056
|
+
type: "object",
|
|
9057
|
+
required: ["id", "hostname", "os", "arch", "last_applied_at", "created_at"],
|
|
9058
|
+
properties: {
|
|
9059
|
+
id: { type: "string" },
|
|
9060
|
+
hostname: { type: "string" },
|
|
9061
|
+
os: { type: "string", nullable: true },
|
|
9062
|
+
arch: { type: "string", nullable: true },
|
|
9063
|
+
last_applied_at: { type: "string", nullable: true },
|
|
9064
|
+
created_at: { type: "string" }
|
|
9065
|
+
}
|
|
9066
|
+
};
|
|
9067
|
+
var snapshotSchema = {
|
|
9068
|
+
type: "object",
|
|
9069
|
+
required: ["id", "config_id", "content", "version", "created_at"],
|
|
9070
|
+
properties: {
|
|
9071
|
+
id: { type: "string" },
|
|
9072
|
+
config_id: { type: "string" },
|
|
9073
|
+
content: { type: "string" },
|
|
9074
|
+
version: { type: "number" },
|
|
9075
|
+
created_at: { type: "string" }
|
|
9076
|
+
}
|
|
9077
|
+
};
|
|
9078
|
+
var limitParameter = { name: "limit", in: "query", schema: { type: "integer", minimum: 1, maximum: 100 } };
|
|
9079
|
+
var cursorParameter = { name: "cursor", in: "query", schema: { type: "integer", minimum: 0, maximum: 1e5 } };
|
|
9080
|
+
var identityViewParameter = {
|
|
9081
|
+
name: "view",
|
|
9082
|
+
in: "query",
|
|
9083
|
+
description: "Use identity for a metadata-only projection that omits instruction content, paths, outputs, and private profile fields.",
|
|
9084
|
+
schema: { type: "string", enum: ["identity"] }
|
|
9085
|
+
};
|
|
9086
|
+
var idempotencyKeyParameter = {
|
|
9087
|
+
name: "Idempotency-Key",
|
|
9088
|
+
in: "header",
|
|
9089
|
+
required: false,
|
|
9090
|
+
description: "Optional visible-ASCII request key (1-255 characters). Replays the first committed response for the same authenticated principal, operation, and canonical JSON body; reuse with a different body returns 409.",
|
|
9091
|
+
schema: { type: "string", minLength: 1, maxLength: 255, pattern: "^[!-~]+$" }
|
|
9092
|
+
};
|
|
9093
|
+
var idempotencyConflictResponse = {
|
|
9094
|
+
description: "The Idempotency-Key was already used for a different request body.",
|
|
9095
|
+
content: {
|
|
9096
|
+
"application/json": {
|
|
9097
|
+
schema: {
|
|
9098
|
+
type: "object",
|
|
9099
|
+
required: ["error", "code"],
|
|
9100
|
+
properties: {
|
|
9101
|
+
error: { type: "string" },
|
|
9102
|
+
code: { type: "string", const: "IDEMPOTENCY_KEY_REUSED" }
|
|
9103
|
+
}
|
|
9104
|
+
}
|
|
9105
|
+
}
|
|
9106
|
+
}
|
|
9107
|
+
};
|
|
8705
9108
|
function buildV1OpenApiDocument(version = getPackageVersion()) {
|
|
8706
9109
|
return {
|
|
8707
9110
|
openapi: "3.1.0",
|
|
@@ -8717,7 +9120,11 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
|
|
|
8717
9120
|
},
|
|
8718
9121
|
schemas: {
|
|
8719
9122
|
Config: configSchema,
|
|
9123
|
+
ConfigIdentity: configIdentitySchema,
|
|
8720
9124
|
Profile: profileSchema,
|
|
9125
|
+
ProfileIdentity: profileIdentitySchema,
|
|
9126
|
+
Machine: machineSchema,
|
|
9127
|
+
ConfigSnapshot: snapshotSchema,
|
|
8721
9128
|
CreateConfigInput: {
|
|
8722
9129
|
type: "object",
|
|
8723
9130
|
required: ["name", "category", "content"],
|
|
@@ -8738,12 +9145,17 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
|
|
|
8738
9145
|
type: "object",
|
|
8739
9146
|
properties: {
|
|
8740
9147
|
name: { type: "string" },
|
|
9148
|
+
kind: { type: "string" },
|
|
8741
9149
|
category: { type: "string" },
|
|
8742
9150
|
agent: { type: "string" },
|
|
9151
|
+
target_path: { type: "string", nullable: true },
|
|
9152
|
+
outputs: { type: "array", items: { type: "object" } },
|
|
9153
|
+
format: { type: "string" },
|
|
8743
9154
|
content: { type: "string" },
|
|
8744
|
-
description: { type: "string" },
|
|
9155
|
+
description: { type: "string", nullable: true },
|
|
8745
9156
|
tags: { type: "array", items: { type: "string" } },
|
|
8746
|
-
is_template: { type: "boolean" }
|
|
9157
|
+
is_template: { type: "boolean" },
|
|
9158
|
+
synced_at: { type: "string", nullable: true }
|
|
8747
9159
|
}
|
|
8748
9160
|
},
|
|
8749
9161
|
CreateProfileInput: {
|
|
@@ -8756,6 +9168,38 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
|
|
|
8756
9168
|
variables: { type: "object" }
|
|
8757
9169
|
}
|
|
8758
9170
|
},
|
|
9171
|
+
UpdateProfileInput: {
|
|
9172
|
+
type: "object",
|
|
9173
|
+
properties: {
|
|
9174
|
+
name: { type: "string" },
|
|
9175
|
+
description: { type: "string", nullable: true },
|
|
9176
|
+
selectors: { type: "object" },
|
|
9177
|
+
variables: { type: "object" }
|
|
9178
|
+
}
|
|
9179
|
+
},
|
|
9180
|
+
PruneSnapshotsInput: {
|
|
9181
|
+
type: "object",
|
|
9182
|
+
properties: {
|
|
9183
|
+
keep: { type: "integer", minimum: 0, default: 10 }
|
|
9184
|
+
}
|
|
9185
|
+
},
|
|
9186
|
+
MachineAppliedInput: {
|
|
9187
|
+
type: "object",
|
|
9188
|
+
required: ["hostname"],
|
|
9189
|
+
properties: {
|
|
9190
|
+
hostname: { type: "string" }
|
|
9191
|
+
}
|
|
9192
|
+
},
|
|
9193
|
+
FeedbackInput: {
|
|
9194
|
+
type: "object",
|
|
9195
|
+
required: ["message"],
|
|
9196
|
+
properties: {
|
|
9197
|
+
message: { type: "string" },
|
|
9198
|
+
email: { type: "string" },
|
|
9199
|
+
category: { type: "string" },
|
|
9200
|
+
version: { type: "string" }
|
|
9201
|
+
}
|
|
9202
|
+
},
|
|
8759
9203
|
AddProfileConfigInput: {
|
|
8760
9204
|
type: "object",
|
|
8761
9205
|
required: ["config_id"],
|
|
@@ -8890,7 +9334,111 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
|
|
|
8890
9334
|
type: "object",
|
|
8891
9335
|
required: ["items", "total", "limit", "cursor", "next_cursor", "has_more", "complete", "truncated", "source_bounded"],
|
|
8892
9336
|
properties: {
|
|
9337
|
+
configs: { type: "array", items: { $ref: "#/components/schemas/Config" } },
|
|
8893
9338
|
items: { type: "array", items: { $ref: "#/components/schemas/Config" } },
|
|
9339
|
+
count: { type: "number" },
|
|
9340
|
+
total: { type: "number" },
|
|
9341
|
+
limit: { type: "number" },
|
|
9342
|
+
cursor: { type: "number" },
|
|
9343
|
+
next_cursor: { type: "number", nullable: true },
|
|
9344
|
+
has_more: { type: "boolean" },
|
|
9345
|
+
complete: { type: "boolean" },
|
|
9346
|
+
truncated: { type: "boolean", const: false },
|
|
9347
|
+
source_bounded: { type: "boolean" }
|
|
9348
|
+
}
|
|
9349
|
+
},
|
|
9350
|
+
BoundedConfigIdentityPage: {
|
|
9351
|
+
type: "object",
|
|
9352
|
+
required: ["items", "total", "limit", "cursor", "next_cursor", "has_more", "complete", "truncated", "source_bounded"],
|
|
9353
|
+
properties: {
|
|
9354
|
+
configs: { type: "array", items: { $ref: "#/components/schemas/ConfigIdentity" } },
|
|
9355
|
+
items: { type: "array", items: { $ref: "#/components/schemas/ConfigIdentity" } },
|
|
9356
|
+
count: { type: "number" },
|
|
9357
|
+
total: { type: "number" },
|
|
9358
|
+
limit: { type: "number" },
|
|
9359
|
+
cursor: { type: "number" },
|
|
9360
|
+
next_cursor: { type: "number", nullable: true },
|
|
9361
|
+
has_more: { type: "boolean" },
|
|
9362
|
+
complete: { type: "boolean" },
|
|
9363
|
+
truncated: { type: "boolean", const: false },
|
|
9364
|
+
source_bounded: { type: "boolean" }
|
|
9365
|
+
}
|
|
9366
|
+
},
|
|
9367
|
+
BoundedProfileIdentityPage: {
|
|
9368
|
+
type: "object",
|
|
9369
|
+
required: ["items", "total", "limit", "cursor", "next_cursor", "has_more", "complete", "truncated", "source_bounded"],
|
|
9370
|
+
properties: {
|
|
9371
|
+
profiles: { type: "array", items: { $ref: "#/components/schemas/ProfileIdentity" } },
|
|
9372
|
+
items: { type: "array", items: { $ref: "#/components/schemas/ProfileIdentity" } },
|
|
9373
|
+
count: { type: "number" },
|
|
9374
|
+
total: { type: "number" },
|
|
9375
|
+
limit: { type: "number" },
|
|
9376
|
+
cursor: { type: "number" },
|
|
9377
|
+
next_cursor: { type: "number", nullable: true },
|
|
9378
|
+
has_more: { type: "boolean" },
|
|
9379
|
+
complete: { type: "boolean" },
|
|
9380
|
+
truncated: { type: "boolean", const: false },
|
|
9381
|
+
source_bounded: { type: "boolean" }
|
|
9382
|
+
}
|
|
9383
|
+
},
|
|
9384
|
+
BoundedSnapshotPage: {
|
|
9385
|
+
type: "object",
|
|
9386
|
+
required: ["items", "total", "limit", "cursor", "next_cursor", "has_more", "complete", "truncated", "source_bounded"],
|
|
9387
|
+
properties: {
|
|
9388
|
+
snapshots: { type: "array", items: { $ref: "#/components/schemas/ConfigSnapshot" } },
|
|
9389
|
+
items: { type: "array", items: { $ref: "#/components/schemas/ConfigSnapshot" } },
|
|
9390
|
+
count: { type: "number" },
|
|
9391
|
+
total: { type: "number" },
|
|
9392
|
+
limit: { type: "number" },
|
|
9393
|
+
cursor: { type: "number" },
|
|
9394
|
+
next_cursor: { type: "number", nullable: true },
|
|
9395
|
+
has_more: { type: "boolean" },
|
|
9396
|
+
complete: { type: "boolean" },
|
|
9397
|
+
truncated: { type: "boolean", const: false },
|
|
9398
|
+
source_bounded: { type: "boolean" }
|
|
9399
|
+
}
|
|
9400
|
+
},
|
|
9401
|
+
BoundedMachinePage: {
|
|
9402
|
+
type: "object",
|
|
9403
|
+
required: ["items", "total", "limit", "cursor", "next_cursor", "has_more", "complete", "truncated", "source_bounded"],
|
|
9404
|
+
properties: {
|
|
9405
|
+
machines: { type: "array", items: { $ref: "#/components/schemas/Machine" } },
|
|
9406
|
+
items: { type: "array", items: { $ref: "#/components/schemas/Machine" } },
|
|
9407
|
+
count: { type: "number" },
|
|
9408
|
+
total: { type: "number" },
|
|
9409
|
+
limit: { type: "number" },
|
|
9410
|
+
cursor: { type: "number" },
|
|
9411
|
+
next_cursor: { type: "number", nullable: true },
|
|
9412
|
+
has_more: { type: "boolean" },
|
|
9413
|
+
complete: { type: "boolean" },
|
|
9414
|
+
truncated: { type: "boolean", const: false },
|
|
9415
|
+
source_bounded: { type: "boolean" }
|
|
9416
|
+
}
|
|
9417
|
+
},
|
|
9418
|
+
BoundedProfileConfigBindingPage: {
|
|
9419
|
+
type: "object",
|
|
9420
|
+
required: ["items", "total", "limit", "cursor", "next_cursor", "has_more", "complete", "truncated", "source_bounded"],
|
|
9421
|
+
properties: {
|
|
9422
|
+
bindings: { type: "array", items: { $ref: "#/components/schemas/ProfileConfigBinding" } },
|
|
9423
|
+
items: { type: "array", items: { $ref: "#/components/schemas/ProfileConfigBinding" } },
|
|
9424
|
+
count: { type: "number" },
|
|
9425
|
+
total: { type: "number" },
|
|
9426
|
+
limit: { type: "number" },
|
|
9427
|
+
cursor: { type: "number" },
|
|
9428
|
+
next_cursor: { type: "number", nullable: true },
|
|
9429
|
+
has_more: { type: "boolean" },
|
|
9430
|
+
complete: { type: "boolean" },
|
|
9431
|
+
truncated: { type: "boolean", const: false },
|
|
9432
|
+
source_bounded: { type: "boolean" }
|
|
9433
|
+
}
|
|
9434
|
+
},
|
|
9435
|
+
BoundedProfileAssetBindingPage: {
|
|
9436
|
+
type: "object",
|
|
9437
|
+
required: ["items", "total", "limit", "cursor", "next_cursor", "has_more", "complete", "truncated", "source_bounded"],
|
|
9438
|
+
properties: {
|
|
9439
|
+
assets: { type: "array", items: { $ref: "#/components/schemas/ProfileAssetBinding" } },
|
|
9440
|
+
items: { type: "array", items: { $ref: "#/components/schemas/ProfileAssetBinding" } },
|
|
9441
|
+
count: { type: "number" },
|
|
8894
9442
|
total: { type: "number" },
|
|
8895
9443
|
limit: { type: "number" },
|
|
8896
9444
|
cursor: { type: "number" },
|
|
@@ -8934,18 +9482,20 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
|
|
|
8934
9482
|
{ name: "category", in: "query", schema: { type: "string" } },
|
|
8935
9483
|
{ name: "agent", in: "query", schema: { type: "string" } },
|
|
8936
9484
|
{ name: "kind", in: "query", schema: { type: "string" } },
|
|
8937
|
-
{ name: "search", in: "query", schema: { type: "string" } }
|
|
9485
|
+
{ name: "search", in: "query", schema: { type: "string", maxLength: 512 } },
|
|
9486
|
+
limitParameter,
|
|
9487
|
+
cursorParameter,
|
|
9488
|
+
identityViewParameter
|
|
8938
9489
|
],
|
|
8939
9490
|
responses: {
|
|
8940
9491
|
"200": {
|
|
8941
9492
|
content: {
|
|
8942
9493
|
"application/json": {
|
|
8943
9494
|
schema: {
|
|
8944
|
-
|
|
8945
|
-
|
|
8946
|
-
|
|
8947
|
-
|
|
8948
|
-
}
|
|
9495
|
+
oneOf: [
|
|
9496
|
+
{ $ref: "#/components/schemas/BoundedConfigPage" },
|
|
9497
|
+
{ $ref: "#/components/schemas/BoundedConfigIdentityPage" }
|
|
9498
|
+
]
|
|
8949
9499
|
}
|
|
8950
9500
|
}
|
|
8951
9501
|
}
|
|
@@ -8955,6 +9505,7 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
|
|
|
8955
9505
|
post: {
|
|
8956
9506
|
operationId: "createConfig",
|
|
8957
9507
|
summary: "Create a config",
|
|
9508
|
+
parameters: [idempotencyKeyParameter],
|
|
8958
9509
|
requestBody: {
|
|
8959
9510
|
required: true,
|
|
8960
9511
|
content: { "application/json": { schema: { $ref: "#/components/schemas/CreateConfigInput" } } }
|
|
@@ -8966,7 +9517,8 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
|
|
|
8966
9517
|
schema: { type: "object", properties: { config: { $ref: "#/components/schemas/Config" } } }
|
|
8967
9518
|
}
|
|
8968
9519
|
}
|
|
8969
|
-
}
|
|
9520
|
+
},
|
|
9521
|
+
"409": idempotencyConflictResponse
|
|
8970
9522
|
}
|
|
8971
9523
|
}
|
|
8972
9524
|
},
|
|
@@ -9003,6 +9555,24 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
|
|
|
9003
9555
|
}
|
|
9004
9556
|
}
|
|
9005
9557
|
},
|
|
9558
|
+
put: {
|
|
9559
|
+
operationId: "putConfig",
|
|
9560
|
+
summary: "Update a config via PUT",
|
|
9561
|
+
parameters: [{ name: "id", in: "path", required: true, schema: { type: "string" } }],
|
|
9562
|
+
requestBody: {
|
|
9563
|
+
required: true,
|
|
9564
|
+
content: { "application/json": { schema: { $ref: "#/components/schemas/UpdateConfigInput" } } }
|
|
9565
|
+
},
|
|
9566
|
+
responses: {
|
|
9567
|
+
"200": {
|
|
9568
|
+
content: {
|
|
9569
|
+
"application/json": {
|
|
9570
|
+
schema: { type: "object", properties: { config: { $ref: "#/components/schemas/Config" } } }
|
|
9571
|
+
}
|
|
9572
|
+
}
|
|
9573
|
+
}
|
|
9574
|
+
}
|
|
9575
|
+
},
|
|
9006
9576
|
delete: {
|
|
9007
9577
|
operationId: "deleteConfig",
|
|
9008
9578
|
summary: "Delete a config",
|
|
@@ -9022,29 +9592,72 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
|
|
|
9022
9592
|
get: {
|
|
9023
9593
|
operationId: "listSnapshots",
|
|
9024
9594
|
summary: "List a config's version snapshots",
|
|
9025
|
-
parameters: [
|
|
9026
|
-
|
|
9595
|
+
parameters: [
|
|
9596
|
+
{ name: "id", in: "path", required: true, schema: { type: "string" } },
|
|
9597
|
+
limitParameter,
|
|
9598
|
+
cursorParameter
|
|
9599
|
+
],
|
|
9600
|
+
responses: { "200": { content: { "application/json": { schema: { $ref: "#/components/schemas/BoundedSnapshotPage" } } } } }
|
|
9027
9601
|
},
|
|
9028
9602
|
post: {
|
|
9029
9603
|
operationId: "createSnapshot",
|
|
9030
9604
|
summary: "Snapshot a config's current content",
|
|
9605
|
+
parameters: [
|
|
9606
|
+
{ name: "id", in: "path", required: true, schema: { type: "string" } },
|
|
9607
|
+
idempotencyKeyParameter
|
|
9608
|
+
],
|
|
9609
|
+
requestBody: {
|
|
9610
|
+
required: false,
|
|
9611
|
+
content: { "application/json": { schema: { type: "object", properties: { content: { type: "string" }, version: { type: "integer" } } } } }
|
|
9612
|
+
},
|
|
9613
|
+
responses: {
|
|
9614
|
+
"201": { content: { "application/json": { schema: { type: "object", properties: { snapshot: { $ref: "#/components/schemas/ConfigSnapshot" } } } } } },
|
|
9615
|
+
"409": idempotencyConflictResponse
|
|
9616
|
+
}
|
|
9617
|
+
}
|
|
9618
|
+
},
|
|
9619
|
+
"/v1/configs/{id}/snapshots/prune": {
|
|
9620
|
+
post: {
|
|
9621
|
+
operationId: "pruneSnapshots",
|
|
9622
|
+
summary: "Prune older snapshots for a config",
|
|
9031
9623
|
parameters: [{ name: "id", in: "path", required: true, schema: { type: "string" } }],
|
|
9032
|
-
|
|
9624
|
+
requestBody: {
|
|
9625
|
+
required: false,
|
|
9626
|
+
content: { "application/json": { schema: { $ref: "#/components/schemas/PruneSnapshotsInput" } } }
|
|
9627
|
+
},
|
|
9628
|
+
responses: {
|
|
9629
|
+
"200": { content: { "application/json": { schema: { type: "object", required: ["pruned"], properties: { pruned: { type: "integer" } } } } } }
|
|
9630
|
+
}
|
|
9631
|
+
}
|
|
9632
|
+
},
|
|
9633
|
+
"/v1/configs/{id}/snapshots/{version}": {
|
|
9634
|
+
get: {
|
|
9635
|
+
operationId: "getSnapshotByVersion",
|
|
9636
|
+
summary: "Get one config snapshot by version",
|
|
9637
|
+
parameters: [
|
|
9638
|
+
{ name: "id", in: "path", required: true, schema: { type: "string" } },
|
|
9639
|
+
{ name: "version", in: "path", required: true, schema: { type: "integer" } }
|
|
9640
|
+
],
|
|
9641
|
+
responses: {
|
|
9642
|
+
"200": { content: { "application/json": { schema: { type: "object", properties: { snapshot: { $ref: "#/components/schemas/ConfigSnapshot" } } } } } }
|
|
9643
|
+
}
|
|
9033
9644
|
}
|
|
9034
9645
|
},
|
|
9035
9646
|
"/v1/profiles": {
|
|
9036
9647
|
get: {
|
|
9037
9648
|
operationId: "listProfiles",
|
|
9038
9649
|
summary: "List profiles with producer-side bounds",
|
|
9039
|
-
parameters: [
|
|
9040
|
-
{ name: "limit", in: "query", schema: { type: "integer", minimum: 1, maximum: 100 } },
|
|
9041
|
-
{ name: "cursor", in: "query", schema: { type: "integer", minimum: 0 } }
|
|
9042
|
-
],
|
|
9650
|
+
parameters: [limitParameter, cursorParameter, identityViewParameter],
|
|
9043
9651
|
responses: {
|
|
9044
9652
|
"200": {
|
|
9045
9653
|
content: {
|
|
9046
9654
|
"application/json": {
|
|
9047
|
-
schema: {
|
|
9655
|
+
schema: {
|
|
9656
|
+
oneOf: [
|
|
9657
|
+
{ $ref: "#/components/schemas/BoundedProfilePage" },
|
|
9658
|
+
{ $ref: "#/components/schemas/BoundedProfileIdentityPage" }
|
|
9659
|
+
]
|
|
9660
|
+
}
|
|
9048
9661
|
}
|
|
9049
9662
|
}
|
|
9050
9663
|
}
|
|
@@ -9053,11 +9666,15 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
|
|
|
9053
9666
|
post: {
|
|
9054
9667
|
operationId: "createProfile",
|
|
9055
9668
|
summary: "Create a profile",
|
|
9669
|
+
parameters: [idempotencyKeyParameter],
|
|
9056
9670
|
requestBody: {
|
|
9057
9671
|
required: true,
|
|
9058
9672
|
content: { "application/json": { schema: { $ref: "#/components/schemas/CreateProfileInput" } } }
|
|
9059
9673
|
},
|
|
9060
|
-
responses: {
|
|
9674
|
+
responses: {
|
|
9675
|
+
"201": { content: { "application/json": { schema: { type: "object", properties: { profile: { $ref: "#/components/schemas/Profile" } } } } } },
|
|
9676
|
+
"409": idempotencyConflictResponse
|
|
9677
|
+
}
|
|
9061
9678
|
}
|
|
9062
9679
|
},
|
|
9063
9680
|
"/v1/profiles/resolve": {
|
|
@@ -9092,6 +9709,31 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
|
|
|
9092
9709
|
],
|
|
9093
9710
|
responses: { "200": { content: { "application/json": { schema: { $ref: "#/components/schemas/ProfileShowResponse" } } } } }
|
|
9094
9711
|
},
|
|
9712
|
+
patch: {
|
|
9713
|
+
operationId: "updateProfile",
|
|
9714
|
+
summary: "Update a profile",
|
|
9715
|
+
parameters: [{ name: "id", in: "path", required: true, schema: { type: "string" } }],
|
|
9716
|
+
requestBody: {
|
|
9717
|
+
required: true,
|
|
9718
|
+
content: { "application/json": { schema: { $ref: "#/components/schemas/UpdateProfileInput" } } }
|
|
9719
|
+
},
|
|
9720
|
+
responses: {
|
|
9721
|
+
"200": { content: { "application/json": { schema: { type: "object", properties: { profile: { $ref: "#/components/schemas/Profile" } } } } } }
|
|
9722
|
+
}
|
|
9723
|
+
},
|
|
9724
|
+
put: {
|
|
9725
|
+
operationId: "putProfile",
|
|
9726
|
+
summary: "Update a profile via PUT",
|
|
9727
|
+
description: "Compatibility update route. Like PATCH, omitted properties remain unchanged.",
|
|
9728
|
+
parameters: [{ name: "id", in: "path", required: true, schema: { type: "string" } }],
|
|
9729
|
+
requestBody: {
|
|
9730
|
+
required: true,
|
|
9731
|
+
content: { "application/json": { schema: { $ref: "#/components/schemas/UpdateProfileInput" } } }
|
|
9732
|
+
},
|
|
9733
|
+
responses: {
|
|
9734
|
+
"200": { content: { "application/json": { schema: { type: "object", properties: { profile: { $ref: "#/components/schemas/Profile" } } } } } }
|
|
9735
|
+
}
|
|
9736
|
+
},
|
|
9095
9737
|
delete: {
|
|
9096
9738
|
operationId: "deleteProfile",
|
|
9097
9739
|
summary: "Delete a profile",
|
|
@@ -9106,7 +9748,8 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
|
|
|
9106
9748
|
description: "Requires an API key with the `instructions:write` scope.",
|
|
9107
9749
|
security: [{ apiKey: [] }],
|
|
9108
9750
|
parameters: [
|
|
9109
|
-
{ name: "id", in: "path", required: true, schema: { type: "string" } }
|
|
9751
|
+
{ name: "id", in: "path", required: true, schema: { type: "string" } },
|
|
9752
|
+
idempotencyKeyParameter
|
|
9110
9753
|
],
|
|
9111
9754
|
requestBody: {
|
|
9112
9755
|
required: true,
|
|
@@ -9123,7 +9766,8 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
|
|
|
9123
9766
|
schema: { $ref: "#/components/schemas/ProfileConfigAddedResponse" }
|
|
9124
9767
|
}
|
|
9125
9768
|
}
|
|
9126
|
-
}
|
|
9769
|
+
},
|
|
9770
|
+
"409": idempotencyConflictResponse
|
|
9127
9771
|
}
|
|
9128
9772
|
}
|
|
9129
9773
|
},
|
|
@@ -9134,13 +9778,17 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
|
|
|
9134
9778
|
security: [{ apiKey: [] }],
|
|
9135
9779
|
parameters: [
|
|
9136
9780
|
{ name: "id", in: "path", required: true, schema: { type: "string" } },
|
|
9137
|
-
{ name: "configId", in: "path", required: true, schema: { type: "string" } }
|
|
9781
|
+
{ name: "configId", in: "path", required: true, schema: { type: "string" } },
|
|
9782
|
+
idempotencyKeyParameter
|
|
9138
9783
|
],
|
|
9139
9784
|
requestBody: {
|
|
9140
9785
|
required: true,
|
|
9141
9786
|
content: { "application/json": { schema: { type: "object", required: ["binding"], properties: { binding: { $ref: "#/components/schemas/ProfileConfigBindingSpec" } } } } }
|
|
9142
9787
|
},
|
|
9143
|
-
responses: {
|
|
9788
|
+
responses: {
|
|
9789
|
+
"200": { content: { "application/json": { schema: { type: "object", properties: { binding: { $ref: "#/components/schemas/ProfileConfigBinding" } } } } } },
|
|
9790
|
+
"409": idempotencyConflictResponse
|
|
9791
|
+
}
|
|
9144
9792
|
},
|
|
9145
9793
|
delete: {
|
|
9146
9794
|
operationId: "removeConfigFromProfile",
|
|
@@ -9166,24 +9814,38 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
|
|
|
9166
9814
|
get: {
|
|
9167
9815
|
operationId: "getProfileConfigBindings",
|
|
9168
9816
|
summary: "List schema-versioned config bindings for a profile",
|
|
9169
|
-
parameters: [
|
|
9170
|
-
|
|
9817
|
+
parameters: [
|
|
9818
|
+
{ name: "id", in: "path", required: true, schema: { type: "string" } },
|
|
9819
|
+
limitParameter,
|
|
9820
|
+
cursorParameter
|
|
9821
|
+
],
|
|
9822
|
+
responses: { "200": { content: { "application/json": { schema: { $ref: "#/components/schemas/BoundedProfileConfigBindingPage" } } } } }
|
|
9171
9823
|
}
|
|
9172
9824
|
},
|
|
9173
9825
|
"/v1/profiles/{id}/assets": {
|
|
9174
9826
|
get: {
|
|
9175
9827
|
operationId: "getProfileAssetBindings",
|
|
9176
9828
|
summary: "List typed asset bindings for a profile",
|
|
9177
|
-
parameters: [
|
|
9178
|
-
|
|
9829
|
+
parameters: [
|
|
9830
|
+
{ name: "id", in: "path", required: true, schema: { type: "string" } },
|
|
9831
|
+
limitParameter,
|
|
9832
|
+
cursorParameter
|
|
9833
|
+
],
|
|
9834
|
+
responses: { "200": { content: { "application/json": { schema: { $ref: "#/components/schemas/BoundedProfileAssetBindingPage" } } } } }
|
|
9179
9835
|
},
|
|
9180
9836
|
post: {
|
|
9181
9837
|
operationId: "addAssetToProfile",
|
|
9182
9838
|
summary: "Add a content-addressed asset binding to a profile",
|
|
9183
9839
|
security: [{ apiKey: [] }],
|
|
9184
|
-
parameters: [
|
|
9840
|
+
parameters: [
|
|
9841
|
+
{ name: "id", in: "path", required: true, schema: { type: "string" } },
|
|
9842
|
+
idempotencyKeyParameter
|
|
9843
|
+
],
|
|
9185
9844
|
requestBody: { required: true, content: { "application/json": { schema: { $ref: "#/components/schemas/AddProfileAssetInput" } } } },
|
|
9186
|
-
responses: {
|
|
9845
|
+
responses: {
|
|
9846
|
+
"201": { content: { "application/json": { schema: { type: "object", properties: { asset: { $ref: "#/components/schemas/ProfileAssetBinding" } } } } } },
|
|
9847
|
+
"409": idempotencyConflictResponse
|
|
9848
|
+
}
|
|
9187
9849
|
}
|
|
9188
9850
|
},
|
|
9189
9851
|
"/v1/profiles/{id}/assets/{assetKey}": {
|
|
@@ -9193,10 +9855,14 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
|
|
|
9193
9855
|
security: [{ apiKey: [] }],
|
|
9194
9856
|
parameters: [
|
|
9195
9857
|
{ name: "id", in: "path", required: true, schema: { type: "string" } },
|
|
9196
|
-
{ name: "assetKey", in: "path", required: true, schema: { type: "string" } }
|
|
9858
|
+
{ name: "assetKey", in: "path", required: true, schema: { type: "string" } },
|
|
9859
|
+
idempotencyKeyParameter
|
|
9197
9860
|
],
|
|
9198
9861
|
requestBody: { required: true, content: { "application/json": { schema: { type: "object", required: ["binding"], properties: { binding: { $ref: "#/components/schemas/ProfileAssetBindingSpec" } } } } } },
|
|
9199
|
-
responses: {
|
|
9862
|
+
responses: {
|
|
9863
|
+
"200": { content: { "application/json": { schema: { type: "object", properties: { asset: { $ref: "#/components/schemas/ProfileAssetBinding" } } } } } },
|
|
9864
|
+
"409": idempotencyConflictResponse
|
|
9865
|
+
}
|
|
9200
9866
|
},
|
|
9201
9867
|
delete: {
|
|
9202
9868
|
operationId: "removeAssetFromProfile",
|
|
@@ -9209,6 +9875,77 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
|
|
|
9209
9875
|
responses: { "200": { content: { "application/json": { schema: { type: "object", properties: { removed: { type: "boolean", const: true } } } } } } }
|
|
9210
9876
|
}
|
|
9211
9877
|
},
|
|
9878
|
+
"/v1/snapshots/{id}": {
|
|
9879
|
+
get: {
|
|
9880
|
+
operationId: "getSnapshot",
|
|
9881
|
+
summary: "Get a snapshot by id",
|
|
9882
|
+
parameters: [{ name: "id", in: "path", required: true, schema: { type: "string" } }],
|
|
9883
|
+
responses: {
|
|
9884
|
+
"200": { content: { "application/json": { schema: { type: "object", properties: { snapshot: { $ref: "#/components/schemas/ConfigSnapshot" } } } } } }
|
|
9885
|
+
}
|
|
9886
|
+
}
|
|
9887
|
+
},
|
|
9888
|
+
"/v1/machines/applied": {
|
|
9889
|
+
post: {
|
|
9890
|
+
operationId: "markMachineApplied",
|
|
9891
|
+
summary: "Mark a machine as having applied its resolved instructions",
|
|
9892
|
+
requestBody: {
|
|
9893
|
+
required: true,
|
|
9894
|
+
content: { "application/json": { schema: { $ref: "#/components/schemas/MachineAppliedInput" } } }
|
|
9895
|
+
},
|
|
9896
|
+
responses: {
|
|
9897
|
+
"200": { content: { "application/json": { schema: { type: "object", required: ["updated"], properties: { updated: { type: "boolean", const: true } } } } } }
|
|
9898
|
+
}
|
|
9899
|
+
}
|
|
9900
|
+
},
|
|
9901
|
+
"/v1/machines": {
|
|
9902
|
+
get: {
|
|
9903
|
+
operationId: "listMachines",
|
|
9904
|
+
summary: "List registered machines with producer-side bounds",
|
|
9905
|
+
parameters: [limitParameter, cursorParameter, identityViewParameter],
|
|
9906
|
+
responses: {
|
|
9907
|
+
"200": { content: { "application/json": { schema: { $ref: "#/components/schemas/BoundedMachinePage" } } } }
|
|
9908
|
+
}
|
|
9909
|
+
},
|
|
9910
|
+
post: {
|
|
9911
|
+
operationId: "registerMachine",
|
|
9912
|
+
summary: "Register or refresh a machine",
|
|
9913
|
+
parameters: [idempotencyKeyParameter],
|
|
9914
|
+
requestBody: {
|
|
9915
|
+
required: true,
|
|
9916
|
+
content: {
|
|
9917
|
+
"application/json": {
|
|
9918
|
+
schema: {
|
|
9919
|
+
type: "object",
|
|
9920
|
+
required: ["hostname"],
|
|
9921
|
+
properties: {
|
|
9922
|
+
hostname: { type: "string" },
|
|
9923
|
+
os: { type: "string", nullable: true },
|
|
9924
|
+
arch: { type: "string", nullable: true }
|
|
9925
|
+
}
|
|
9926
|
+
}
|
|
9927
|
+
}
|
|
9928
|
+
}
|
|
9929
|
+
},
|
|
9930
|
+
responses: {
|
|
9931
|
+
"201": { content: { "application/json": { schema: { type: "object", properties: { machine: { $ref: "#/components/schemas/Machine" } } } } } },
|
|
9932
|
+
"409": idempotencyConflictResponse
|
|
9933
|
+
}
|
|
9934
|
+
}
|
|
9935
|
+
},
|
|
9936
|
+
"/v1/feedback": {
|
|
9937
|
+
post: {
|
|
9938
|
+
operationId: "createFeedback",
|
|
9939
|
+
summary: "Submit Instructions feedback",
|
|
9940
|
+
requestBody: {
|
|
9941
|
+
required: true,
|
|
9942
|
+
content: { "application/json": { schema: { $ref: "#/components/schemas/FeedbackInput" } } }
|
|
9943
|
+
},
|
|
9944
|
+
responses: {
|
|
9945
|
+
"201": { content: { "application/json": { schema: { type: "object", required: ["ok"], properties: { ok: { type: "boolean", const: true } } } } } }
|
|
9946
|
+
}
|
|
9947
|
+
}
|
|
9948
|
+
},
|
|
9212
9949
|
"/v1/stats": {
|
|
9213
9950
|
get: {
|
|
9214
9951
|
operationId: "getStats",
|
|
@@ -9241,7 +9978,7 @@ if (process.argv.includes("--version") || process.argv.includes("-V")) {
|
|
|
9241
9978
|
if (process.argv.includes("--help") || process.argv.includes("-h")) {
|
|
9242
9979
|
console.log(`Usage: instructions-serve [options]
|
|
9243
9980
|
|
|
9244
|
-
HTTP API server for @hasna/instructions (
|
|
9981
|
+
HTTP API server for @hasna/instructions (PostgreSQL is required for /v1).
|
|
9245
9982
|
|
|
9246
9983
|
Options:
|
|
9247
9984
|
-V, --version output the version number
|
|
@@ -9252,26 +9989,28 @@ Environment:
|
|
|
9252
9989
|
PORT / INSTRUCTIONS_PORT HTTP port (default: 3457)
|
|
9253
9990
|
HOST / INSTRUCTIONS_HOST bind host (default: localhost)
|
|
9254
9991
|
HASNA_INSTRUCTIONS_DATABASE_URL / INSTRUCTIONS_DATABASE_URL / DATABASE_URL
|
|
9255
|
-
|
|
9992
|
+
PostgreSQL backend URL (required for /v1)`);
|
|
9256
9993
|
process.exit(0);
|
|
9257
9994
|
}
|
|
9258
9995
|
var PORT = Number(process.env["PORT"] ?? process.env["INSTRUCTIONS_PORT"] ?? 3457);
|
|
9259
9996
|
var app = new Hono2;
|
|
9260
9997
|
app.use("*", cors());
|
|
9261
9998
|
function serviceBackend() {
|
|
9262
|
-
return isPostgresBackendEnabled() ? "postgresql" : "
|
|
9999
|
+
return isPostgresBackendEnabled() ? "postgresql" : "unconfigured";
|
|
9263
10000
|
}
|
|
9264
10001
|
app.get("/health", (c) => c.json({ status: "ok", version: getPackageVersion(), backend: serviceBackend(), name: "instructions" }));
|
|
9265
10002
|
app.get("/version", (c) => c.json({ status: "ok", version: getPackageVersion(), backend: serviceBackend(), name: "instructions" }));
|
|
9266
10003
|
app.get("/ready", async (c) => {
|
|
9267
10004
|
const version = getPackageVersion();
|
|
9268
10005
|
const backend2 = serviceBackend();
|
|
9269
|
-
if (backend2
|
|
9270
|
-
|
|
9271
|
-
|
|
9272
|
-
|
|
9273
|
-
|
|
9274
|
-
|
|
10006
|
+
if (backend2 !== "postgresql") {
|
|
10007
|
+
return c.json({ status: "unavailable", version, backend: backend2, code: "SERVER_BACKEND_UNCONFIGURED" }, 503);
|
|
10008
|
+
}
|
|
10009
|
+
try {
|
|
10010
|
+
await pingCloud();
|
|
10011
|
+
} catch {
|
|
10012
|
+
console.error("instructions ready: database unavailable");
|
|
10013
|
+
return c.json({ status: "unavailable", version, backend: backend2, code: "DATABASE_UNAVAILABLE" }, 503);
|
|
9275
10014
|
}
|
|
9276
10015
|
return c.json({ status: "ready", version, backend: backend2 });
|
|
9277
10016
|
});
|
|
@@ -9282,18 +10021,27 @@ app.use("/v1/*", async (c, next) => {
|
|
|
9282
10021
|
let mw;
|
|
9283
10022
|
try {
|
|
9284
10023
|
mw = getHonoAuthMiddleware([isWrite ? "instructions:write" : "instructions:read"]);
|
|
9285
|
-
} catch
|
|
9286
|
-
return c.json({ error:
|
|
10024
|
+
} catch {
|
|
10025
|
+
return c.json({ error: "Instructions API is unavailable", code: "API_AUTH_UNCONFIGURED" }, 503);
|
|
9287
10026
|
}
|
|
9288
10027
|
return mw(c, next);
|
|
9289
10028
|
});
|
|
9290
10029
|
app.all("/v1/*", async (c) => {
|
|
9291
|
-
const res = await handleV1Request(c.req.raw, new URL(c.req.url)
|
|
10030
|
+
const res = await handleV1Request(c.req.raw, new URL(c.req.url), {
|
|
10031
|
+
principal: c.get("apiKey")
|
|
10032
|
+
});
|
|
9292
10033
|
return res ?? c.json({ error: "Not found" }, 404);
|
|
9293
10034
|
});
|
|
9294
10035
|
var HOST = process.env["HOST"] ?? process.env["INSTRUCTIONS_HOST"] ?? "localhost";
|
|
9295
|
-
|
|
9296
|
-
|
|
10036
|
+
var serverOptions = { port: PORT, hostname: HOST, fetch: app.fetch };
|
|
10037
|
+
if (import.meta.main) {
|
|
10038
|
+
console.log(`instructions-serve listening on http://${HOST}:${PORT} (backend: ${serviceBackend()})`);
|
|
10039
|
+
Bun.serve(serverOptions);
|
|
10040
|
+
}
|
|
10041
|
+
var server_default = app;
|
|
9297
10042
|
export {
|
|
9298
|
-
|
|
10043
|
+
serviceBackend,
|
|
10044
|
+
serverOptions,
|
|
10045
|
+
server_default as default,
|
|
10046
|
+
app
|
|
9299
10047
|
};
|