@hasna/instructions 0.6.1 → 0.7.1
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 +141 -10
- package/dist/chunks/{apply-ynfn1av7.js → apply-c3sd4agz.js} +5 -3
- package/dist/chunks/{apply-sfzja1f3.js → apply-pnn7a3y7.js} +6 -3
- package/dist/chunks/database-wstmr56z.js +23 -0
- package/dist/chunks/{index-pkscjnj5.js → index-3djthcm6.js} +0 -1
- package/dist/chunks/index-5d7zdftc.js +186 -0
- package/dist/chunks/{index-1hpwskwd.js → index-6jncnnw7.js} +318 -329
- package/dist/chunks/index-8rb7ng2y.js +148 -0
- package/dist/chunks/{index-v7dkb384.js → index-aj66f6xr.js} +243 -100
- package/dist/chunks/index-fpeyzn3n.js +186 -0
- package/dist/chunks/{index-9p9gn7gh.js → index-jg8ptcwh.js} +5 -7
- package/dist/chunks/index-mvckkf15.js +363 -0
- package/dist/chunks/index-n3fjspr0.js +119 -0
- package/dist/chunks/{index-85n5wnd8.js → index-pdpg8a44.js} +5 -7
- package/dist/chunks/{index-e9epbsvs.js → index-r7nfef2g.js} +15 -4
- package/dist/chunks/{index-axhwg61p.js → index-ypcef3a2.js} +15 -4
- package/dist/chunks/legacy-store-migration-z2m5yzyk.js +322 -0
- package/dist/chunks/{local-a9nen65z.js → local-0a9gja58.js} +142 -2
- package/dist/chunks/{local-0d8qrc92.js → local-969ye538.js} +153 -188
- package/dist/chunks/s3-backup-hxe3zpfz.js +240 -0
- package/dist/chunks/s3-config-hhght91k.js +14 -0
- package/dist/chunks/s3-object-store-n0s82swt.js +14 -0
- package/dist/chunks/{sync-vy1yjvs6.js → sync-q5hvwq6e.js} +4 -4
- package/dist/chunks/{sync-m46fc257.js → sync-xtkbm5br.js} +5 -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 +1236 -256
- package/dist/data/config-store.d.ts +10 -1
- package/dist/data/config-store.d.ts.map +1 -1
- package/dist/db/configs.d.ts +4 -1
- package/dist/db/configs.d.ts.map +1 -1
- package/dist/db/local.d.ts +1 -1
- package/dist/db/local.d.ts.map +1 -1
- package/dist/index.js +1086 -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 +3 -18
- 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 +41 -30
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/sdk/index.d.ts +2 -0
- package/dist/sdk/index.d.ts.map +1 -1
- package/dist/sdk/index.js +229 -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 +58 -0
- package/dist/sdk/v1-client.d.ts.map +1 -0
- package/dist/sdk/v1.generated.d.ts +242 -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 +1261 -427
- package/dist/server/openapi.d.ts +1379 -93
- 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 +45 -2
- 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 +744 -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 +98 -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 +24 -13
- 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,66 @@ 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
|
+
if (filter.tags?.length) {
|
|
8017
|
+
for (const tag of filter.tags)
|
|
8018
|
+
add("tags @> $?::jsonb", JSON.stringify([tag]));
|
|
8019
|
+
}
|
|
8020
|
+
return {
|
|
8021
|
+
where: conditions.length ? `WHERE ${conditions.join(" AND ")}` : "",
|
|
8022
|
+
params
|
|
8023
|
+
};
|
|
8024
|
+
}
|
|
8025
|
+
async function listConfigsPage(client, filter = {}, options = {}) {
|
|
8026
|
+
const normalized = normalizeBoundedReadOptions(options);
|
|
8027
|
+
const { where, params } = configFilterSql(filter);
|
|
8028
|
+
const count = await client.get(`SELECT COUNT(*) AS total FROM configs ${where}`, params);
|
|
8029
|
+
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]);
|
|
8030
|
+
return boundedReadPage(rows.map(rowToConfig), Number(count?.total ?? 0), normalized);
|
|
8031
|
+
}
|
|
8032
|
+
async function listConfigSummariesPage(client, filter = {}, options = {}) {
|
|
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, target_path, format,
|
|
8037
|
+
jsonb_array_length(outputs) AS output_count, description, tags,
|
|
8038
|
+
is_template, version, updated_at
|
|
8039
|
+
FROM configs ${where} ORDER BY id LIMIT $${params.length + 1} OFFSET $${params.length + 2}`, [...params, normalized.limit, normalized.cursor]);
|
|
8040
|
+
return boundedReadPage(rows.map((row) => ({
|
|
8041
|
+
id: row.id,
|
|
8042
|
+
slug: row.slug,
|
|
8043
|
+
name: row.name,
|
|
8044
|
+
category: row.category,
|
|
8045
|
+
agent: row.agent,
|
|
8046
|
+
kind: row.kind,
|
|
8047
|
+
format: row.format,
|
|
8048
|
+
target_path: row.target_path,
|
|
8049
|
+
output_count: Number(row.output_count ?? 0),
|
|
8050
|
+
version: row.version,
|
|
8051
|
+
is_template: Boolean(row.is_template),
|
|
8052
|
+
updated_at: toIso2(row.updated_at),
|
|
8053
|
+
description: row.description,
|
|
8054
|
+
tags: Array.isArray(row.tags) ? row.tags.map(String) : []
|
|
8055
|
+
})), Number(count?.total ?? 0), normalized);
|
|
8056
|
+
}
|
|
8057
|
+
async function listConfigIdentitiesPage(client, filterOrOptions = {}, maybeOptions) {
|
|
8058
|
+
const filterKeys = ["category", "agent", "kind", "is_template", "search", "tags"];
|
|
8059
|
+
const isFilterOnly = maybeOptions === undefined && filterKeys.some((key) => (key in filterOrOptions));
|
|
8060
|
+
const filter = maybeOptions === undefined && !isFilterOnly ? {} : filterOrOptions;
|
|
8061
|
+
const options = maybeOptions === undefined && !isFilterOnly ? filterOrOptions : maybeOptions ?? {};
|
|
8062
|
+
const normalized = normalizeBoundedReadOptions(options);
|
|
8063
|
+
const { where, params } = configFilterSql(filter);
|
|
8064
|
+
const count = await client.get(`SELECT COUNT(*) AS total FROM configs ${where}`, params);
|
|
8065
|
+
const rows = await client.many(`SELECT id, name, slug, kind, category, agent, format, is_template, version, created_at, updated_at, synced_at
|
|
8066
|
+
FROM configs ${where} ORDER BY id LIMIT $${params.length + 1} OFFSET $${params.length + 2}`, [...params, normalized.limit, normalized.cursor]);
|
|
8067
|
+
return boundedReadPage(rows.map((row) => ({
|
|
8068
|
+
...row,
|
|
8069
|
+
created_at: toIso2(row.created_at),
|
|
8070
|
+
updated_at: toIso2(row.updated_at),
|
|
8071
|
+
synced_at: row.synced_at == null ? null : toIso2(row.synced_at)
|
|
8072
|
+
})), Number(count?.total ?? 0), normalized);
|
|
7975
8073
|
}
|
|
7976
8074
|
async function getConfig(client, idOrSlug) {
|
|
7977
8075
|
const row = await client.get("SELECT * FROM configs WHERE id = $1 OR slug = $1", [idOrSlug]);
|
|
@@ -7980,10 +8078,10 @@ async function getConfig(client, idOrSlug) {
|
|
|
7980
8078
|
return rowToConfig(row);
|
|
7981
8079
|
}
|
|
7982
8080
|
async function createConfig(client, input) {
|
|
7983
|
-
if (
|
|
7984
|
-
throw new
|
|
7985
|
-
if (!input.category)
|
|
7986
|
-
throw new
|
|
8081
|
+
if (typeof input.name !== "string" || !input.name.trim())
|
|
8082
|
+
throw new StoreValidationError("name is required", "NAME_REQUIRED");
|
|
8083
|
+
if (typeof input.category !== "string" || !input.category.trim())
|
|
8084
|
+
throw new StoreValidationError("category is required", "CATEGORY_REQUIRED");
|
|
7987
8085
|
const id = randomUUID();
|
|
7988
8086
|
const slug = await uniqueSlug(client, input.name);
|
|
7989
8087
|
const snapshotId = randomUUID();
|
|
@@ -8092,10 +8190,20 @@ async function createSnapshotContent(client, idOrSlug, content, version) {
|
|
|
8092
8190
|
throw new Error("snapshot insert failed");
|
|
8093
8191
|
return { id: row.id, config_id: row.config_id, content: row.content, version: Number(row.version), created_at: toIso2(row.created_at) };
|
|
8094
8192
|
}
|
|
8095
|
-
async function
|
|
8193
|
+
async function listSnapshotsPage(client, idOrSlug, options = {}) {
|
|
8096
8194
|
const config = await getConfig(client, idOrSlug);
|
|
8097
|
-
const
|
|
8098
|
-
|
|
8195
|
+
const normalized = normalizeBoundedReadOptions(options);
|
|
8196
|
+
const count = await client.get("SELECT COUNT(*) AS total FROM config_snapshots WHERE config_id = $1", [config.id]);
|
|
8197
|
+
const rows = await client.many(`SELECT id, config_id, content, version, created_at
|
|
8198
|
+
FROM config_snapshots WHERE config_id = $1
|
|
8199
|
+
ORDER BY version DESC, id LIMIT $2 OFFSET $3`, [config.id, normalized.limit, normalized.cursor]);
|
|
8200
|
+
return boundedReadPage(rows.map((row) => ({
|
|
8201
|
+
id: row.id,
|
|
8202
|
+
config_id: row.config_id,
|
|
8203
|
+
content: row.content,
|
|
8204
|
+
version: Number(row.version),
|
|
8205
|
+
created_at: toIso2(row.created_at)
|
|
8206
|
+
})), Number(count?.total ?? 0), normalized);
|
|
8099
8207
|
}
|
|
8100
8208
|
async function getSnapshotById(client, snapshotId) {
|
|
8101
8209
|
const row = await client.get("SELECT id, config_id, content, version, created_at FROM config_snapshots WHERE id = $1", [snapshotId]);
|
|
@@ -8125,40 +8233,31 @@ function rowToProfile(row) {
|
|
|
8125
8233
|
updated_at: toIso2(row.updated_at)
|
|
8126
8234
|
};
|
|
8127
8235
|
}
|
|
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
8236
|
async function listProfilesPage(client, options = {}) {
|
|
8140
8237
|
const normalized = normalizeBoundedReadOptions(options);
|
|
8141
8238
|
const count = await client.get("SELECT COUNT(*) AS total FROM profiles");
|
|
8142
|
-
const rows = await client.many("SELECT * FROM profiles ORDER BY
|
|
8239
|
+
const rows = await client.many("SELECT * FROM profiles ORDER BY id LIMIT $1 OFFSET $2", [normalized.limit, normalized.cursor]);
|
|
8143
8240
|
return boundedReadPage(rows.map(rowToProfile), Number(count?.total ?? 0), normalized);
|
|
8144
8241
|
}
|
|
8242
|
+
async function listProfileIdentitiesPage(client, options = {}) {
|
|
8243
|
+
const normalized = normalizeBoundedReadOptions(options);
|
|
8244
|
+
const count = await client.get("SELECT COUNT(*) AS total FROM profiles");
|
|
8245
|
+
const rows = await client.many(`SELECT id, name, slug, created_at, updated_at
|
|
8246
|
+
FROM profiles ORDER BY id LIMIT $1 OFFSET $2`, [normalized.limit, normalized.cursor]);
|
|
8247
|
+
return boundedReadPage(rows.map((row) => ({
|
|
8248
|
+
id: row.id,
|
|
8249
|
+
name: row.name,
|
|
8250
|
+
slug: row.slug,
|
|
8251
|
+
created_at: toIso2(row.created_at),
|
|
8252
|
+
updated_at: toIso2(row.updated_at)
|
|
8253
|
+
})), Number(count?.total ?? 0), normalized);
|
|
8254
|
+
}
|
|
8145
8255
|
async function getProfile(client, idOrSlug) {
|
|
8146
8256
|
const row = await client.get("SELECT * FROM profiles WHERE id = $1 OR slug = $1", [idOrSlug]);
|
|
8147
8257
|
if (!row)
|
|
8148
8258
|
throw new ProfileNotFoundError(idOrSlug);
|
|
8149
8259
|
return rowToProfile(row);
|
|
8150
8260
|
}
|
|
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
8261
|
async function getProfileConfigsPage(client, idOrSlug, options = {}) {
|
|
8163
8262
|
const profile = await getProfile(client, idOrSlug);
|
|
8164
8263
|
const normalized = normalizeBoundedReadOptions(options);
|
|
@@ -8166,13 +8265,13 @@ async function getProfileConfigsPage(client, idOrSlug, options = {}) {
|
|
|
8166
8265
|
const rows = await client.many(`SELECT c.* FROM configs c
|
|
8167
8266
|
JOIN profile_configs pc ON pc.config_id = c.id
|
|
8168
8267
|
WHERE pc.profile_id = $1
|
|
8169
|
-
ORDER BY pc.sort_order
|
|
8268
|
+
ORDER BY pc.sort_order, c.id
|
|
8170
8269
|
LIMIT $2 OFFSET $3`, [profile.id, normalized.limit, normalized.cursor]);
|
|
8171
8270
|
return boundedReadPage(rows.map(rowToConfig), Number(count?.total ?? 0), normalized);
|
|
8172
8271
|
}
|
|
8173
8272
|
async function createProfile(client, input) {
|
|
8174
|
-
if (
|
|
8175
|
-
throw new
|
|
8273
|
+
if (typeof input.name !== "string" || !input.name.trim())
|
|
8274
|
+
throw new StoreValidationError("name is required", "NAME_REQUIRED");
|
|
8176
8275
|
const id = randomUUID();
|
|
8177
8276
|
const slug = await uniqueProfileSlug(client, input.name);
|
|
8178
8277
|
await client.execute(`INSERT INTO profiles (id, name, slug, description, selectors, variables, created_at, updated_at)
|
|
@@ -8224,18 +8323,32 @@ async function addConfigToProfile(client, profileIdOrSlug, configId) {
|
|
|
8224
8323
|
await client.execute(`INSERT INTO profile_configs (profile_id, config_id, sort_order)
|
|
8225
8324
|
VALUES ($1,$2,$3) ON CONFLICT (profile_id, config_id) DO NOTHING`, [profile.id, configId, order2]);
|
|
8226
8325
|
}
|
|
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) => ({
|
|
8326
|
+
function rowToProfileConfigBinding(row) {
|
|
8327
|
+
return {
|
|
8234
8328
|
profile_id: row.profile_id,
|
|
8235
8329
|
config_id: row.config_id,
|
|
8236
8330
|
sort_order: Number(row.sort_order),
|
|
8237
8331
|
binding: row.binding == null ? legacyProfileConfigBinding() : normalizeProfileConfigBinding(row.binding)
|
|
8238
|
-
}
|
|
8332
|
+
};
|
|
8333
|
+
}
|
|
8334
|
+
async function getProfileConfigBindingsPage(client, profileIdOrSlug, options = {}) {
|
|
8335
|
+
const profile = await getProfile(client, profileIdOrSlug);
|
|
8336
|
+
const normalized = normalizeBoundedReadOptions(options);
|
|
8337
|
+
const count = await client.get("SELECT COUNT(*) AS total FROM profile_configs WHERE profile_id = $1", [profile.id]);
|
|
8338
|
+
const rows = await client.many(`SELECT profile_id, config_id, sort_order, binding
|
|
8339
|
+
FROM profile_configs
|
|
8340
|
+
WHERE profile_id = $1
|
|
8341
|
+
ORDER BY sort_order, config_id
|
|
8342
|
+
LIMIT $2 OFFSET $3`, [profile.id, normalized.limit, normalized.cursor]);
|
|
8343
|
+
return boundedReadPage(rows.map(rowToProfileConfigBinding), Number(count?.total ?? 0), normalized);
|
|
8344
|
+
}
|
|
8345
|
+
async function getProfileConfigBinding(client, profileId, configId) {
|
|
8346
|
+
const row = await client.get(`SELECT profile_id, config_id, sort_order, binding
|
|
8347
|
+
FROM profile_configs
|
|
8348
|
+
WHERE profile_id = $1 AND config_id = $2`, [profileId, configId]);
|
|
8349
|
+
if (!row)
|
|
8350
|
+
throw new Error(`Config ${configId} is not a member of profile ${profileId}.`);
|
|
8351
|
+
return rowToProfileConfigBinding(row);
|
|
8239
8352
|
}
|
|
8240
8353
|
async function setProfileConfigBinding(client, profileIdOrSlug, configId, binding) {
|
|
8241
8354
|
const profile = await getProfile(client, profileIdOrSlug);
|
|
@@ -8243,12 +8356,39 @@ async function setProfileConfigBinding(client, profileIdOrSlug, configId, bindin
|
|
|
8243
8356
|
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
8357
|
if ((result.rowCount ?? 0) !== 1)
|
|
8245
8358
|
throw new Error(`Config ${configId} is not a member of profile ${profile.slug}.`);
|
|
8246
|
-
return (
|
|
8359
|
+
return getProfileConfigBinding(client, profile.id, configId);
|
|
8247
8360
|
}
|
|
8248
8361
|
async function removeConfigFromProfile(client, profileIdOrSlug, configId) {
|
|
8249
8362
|
const profile = await getProfile(client, profileIdOrSlug);
|
|
8250
8363
|
await client.execute("DELETE FROM profile_configs WHERE profile_id = $1 AND config_id = $2", [profile.id, configId]);
|
|
8251
8364
|
}
|
|
8365
|
+
function rowToProfileAssetBinding(row) {
|
|
8366
|
+
return {
|
|
8367
|
+
profile_id: row.profile_id,
|
|
8368
|
+
source_config_id: row.source_config_id,
|
|
8369
|
+
sort_order: Number(row.sort_order),
|
|
8370
|
+
binding: normalizeProfileAssetBinding(row.binding)
|
|
8371
|
+
};
|
|
8372
|
+
}
|
|
8373
|
+
async function getProfileAssetBindingsPage(client, profileIdOrSlug, options = {}) {
|
|
8374
|
+
const profile = await getProfile(client, profileIdOrSlug);
|
|
8375
|
+
const normalized = normalizeBoundedReadOptions(options);
|
|
8376
|
+
const count = await client.get("SELECT COUNT(*) AS total FROM profile_assets WHERE profile_id = $1", [profile.id]);
|
|
8377
|
+
const rows = await client.many(`SELECT profile_id, source_config_id, sort_order, binding
|
|
8378
|
+
FROM profile_assets
|
|
8379
|
+
WHERE profile_id = $1
|
|
8380
|
+
ORDER BY sort_order, asset_key
|
|
8381
|
+
LIMIT $2 OFFSET $3`, [profile.id, normalized.limit, normalized.cursor]);
|
|
8382
|
+
return boundedReadPage(rows.map(rowToProfileAssetBinding), Number(count?.total ?? 0), normalized);
|
|
8383
|
+
}
|
|
8384
|
+
async function getProfileAssetBinding(client, profileId, assetKey) {
|
|
8385
|
+
const row = await client.get(`SELECT profile_id, source_config_id, sort_order, binding
|
|
8386
|
+
FROM profile_assets
|
|
8387
|
+
WHERE profile_id = $1 AND asset_key = $2`, [profileId, assetKey]);
|
|
8388
|
+
if (!row)
|
|
8389
|
+
throw new Error(`Asset ${assetKey} is not a member of profile ${profileId}.`);
|
|
8390
|
+
return rowToProfileAssetBinding(row);
|
|
8391
|
+
}
|
|
8252
8392
|
async function addAssetToProfile(client, profileIdOrSlug, sourceConfigId, binding) {
|
|
8253
8393
|
const profile = await getProfile(client, profileIdOrSlug);
|
|
8254
8394
|
await getConfig(client, sourceConfigId);
|
|
@@ -8256,7 +8396,7 @@ async function addAssetToProfile(client, profileIdOrSlug, sourceConfigId, bindin
|
|
|
8256
8396
|
const maxRow = await client.get("SELECT MAX(sort_order) AS max_order FROM profile_assets WHERE profile_id = $1", [profile.id]);
|
|
8257
8397
|
const order2 = (maxRow?.max_order ?? -1) + 1;
|
|
8258
8398
|
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 (
|
|
8399
|
+
return getProfileAssetBinding(client, profile.id, normalized.assetKey);
|
|
8260
8400
|
}
|
|
8261
8401
|
async function setProfileAssetBinding(client, profileIdOrSlug, assetKey, binding) {
|
|
8262
8402
|
const profile = await getProfile(client, profileIdOrSlug);
|
|
@@ -8266,17 +8406,7 @@ async function setProfileAssetBinding(client, profileIdOrSlug, assetKey, binding
|
|
|
8266
8406
|
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
8407
|
if ((result.rowCount ?? 0) !== 1)
|
|
8268
8408
|
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
|
-
}));
|
|
8409
|
+
return getProfileAssetBinding(client, profile.id, assetKey);
|
|
8280
8410
|
}
|
|
8281
8411
|
async function removeAssetFromProfile(client, profileIdOrSlug, assetKey) {
|
|
8282
8412
|
const profile = await getProfile(client, profileIdOrSlug);
|
|
@@ -8291,39 +8421,33 @@ async function resolveProfileForMachineRead(client, machine, options = {}) {
|
|
|
8291
8421
|
const os = (machine.os ?? "").trim().toLowerCase();
|
|
8292
8422
|
const osFamily = normalizeOsFamily(machine.os);
|
|
8293
8423
|
const arch = (machine.arch ?? "").trim().toLowerCase();
|
|
8294
|
-
|
|
8295
|
-
|
|
8296
|
-
|
|
8424
|
+
const readStableProfiles = (queryClient) => aggregateBoundedCollectionRead("profile resolution", (cursor) => listProfilesPage(queryClient, { limit, cursor }), (profile) => profile.id, true, true);
|
|
8425
|
+
const stableRead = hasTransaction(client) ? await client.transaction(async (transaction) => {
|
|
8426
|
+
await transaction.execute("SET TRANSACTION ISOLATION LEVEL REPEATABLE READ READ ONLY");
|
|
8427
|
+
return readStableProfiles(transaction);
|
|
8428
|
+
}) : await readStableProfiles(client);
|
|
8297
8429
|
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
|
-
}
|
|
8430
|
+
for (const p of stableRead.items) {
|
|
8431
|
+
if (!profileHasSelectors(p.selectors))
|
|
8432
|
+
continue;
|
|
8433
|
+
const s = p.selectors;
|
|
8434
|
+
const osOk = !s.os?.length || s.os.some((c) => {
|
|
8435
|
+
const value = c.trim().toLowerCase();
|
|
8436
|
+
return value === os || normalizeOsFamily(c) === osFamily;
|
|
8437
|
+
});
|
|
8438
|
+
const archOk = !s.arch?.length || s.arch.some((c) => c.trim().toLowerCase() === arch);
|
|
8439
|
+
const hostOk = !s.hostnames?.length || s.hostnames.some((c) => c.trim().toLowerCase() === host);
|
|
8440
|
+
if (!osOk || !archOk || !hostOk)
|
|
8441
|
+
continue;
|
|
8442
|
+
const score = (p.selectors.hostnames?.length ? 100 : 0) + (p.selectors.os?.length ? 10 : 0) + (p.selectors.arch?.length ? 10 : 0);
|
|
8443
|
+
if (!selected || score > selected.score || score === selected.score && p.name.localeCompare(selected.profile.name) < 0) {
|
|
8444
|
+
selected = { profile: p, score };
|
|
8318
8445
|
}
|
|
8319
|
-
if (page.complete)
|
|
8320
|
-
break;
|
|
8321
|
-
cursor = page.next_cursor;
|
|
8322
8446
|
}
|
|
8323
8447
|
return {
|
|
8324
8448
|
profile: selected?.profile ?? null,
|
|
8325
|
-
scanned,
|
|
8326
|
-
total,
|
|
8449
|
+
scanned: stableRead.items.length,
|
|
8450
|
+
total: stableRead.total,
|
|
8327
8451
|
batch_limit: limit,
|
|
8328
8452
|
source_bounded: true,
|
|
8329
8453
|
complete: true,
|
|
@@ -8340,11 +8464,21 @@ function rowToMachine(row) {
|
|
|
8340
8464
|
created_at: toIso2(row.created_at)
|
|
8341
8465
|
};
|
|
8342
8466
|
}
|
|
8343
|
-
async function
|
|
8344
|
-
const
|
|
8345
|
-
|
|
8467
|
+
async function listMachinesPage(client, options = {}) {
|
|
8468
|
+
const normalized = normalizeBoundedReadOptions(options);
|
|
8469
|
+
const count = await client.get("SELECT COUNT(*) AS total FROM machines");
|
|
8470
|
+
const rows = await client.many(`SELECT * FROM machines
|
|
8471
|
+
ORDER BY id LIMIT $1 OFFSET $2`, [normalized.limit, normalized.cursor]);
|
|
8472
|
+
return boundedReadPage(rows.map(rowToMachine), Number(count?.total ?? 0), normalized);
|
|
8346
8473
|
}
|
|
8347
|
-
async function
|
|
8474
|
+
async function listMachineIdentitiesPage(client, options = {}) {
|
|
8475
|
+
const normalized = normalizeBoundedReadOptions(options);
|
|
8476
|
+
const count = await client.get("SELECT COUNT(*) AS total FROM machines");
|
|
8477
|
+
const rows = await client.many(`SELECT id, hostname, os, arch, last_applied_at, created_at
|
|
8478
|
+
FROM machines ORDER BY id LIMIT $1 OFFSET $2`, [normalized.limit, normalized.cursor]);
|
|
8479
|
+
return boundedReadPage(rows.map(rowToMachine), Number(count?.total ?? 0), normalized);
|
|
8480
|
+
}
|
|
8481
|
+
async function registerMachine(client, hostname, os, arch) {
|
|
8348
8482
|
const existing = await client.get("SELECT * FROM machines WHERE hostname = $1", [hostname]);
|
|
8349
8483
|
if (existing) {
|
|
8350
8484
|
if (existing.os !== os || existing.arch !== arch) {
|
|
@@ -8365,38 +8499,229 @@ async function insertFeedback(client, input) {
|
|
|
8365
8499
|
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
8500
|
}
|
|
8367
8501
|
|
|
8502
|
+
// src/server/cloud.ts
|
|
8503
|
+
var INSTRUCTIONS_APP_SLUG = "instructions";
|
|
8504
|
+
function resolveCloudDatabaseUrl(env = process.env) {
|
|
8505
|
+
return env.HASNA_INSTRUCTIONS_DATABASE_URL || env.INSTRUCTIONS_DATABASE_URL || env.DATABASE_URL || undefined;
|
|
8506
|
+
}
|
|
8507
|
+
function resolveSigningSecret(env = process.env) {
|
|
8508
|
+
return env.HASNA_INSTRUCTIONS_API_SIGNING_KEY?.trim() || env.HASNA_API_SIGNING_KEY?.trim() || env.API_KEY_SIGNING_SECRET?.trim() || undefined;
|
|
8509
|
+
}
|
|
8510
|
+
function isPostgresBackendEnabled(env = process.env) {
|
|
8511
|
+
return Boolean(resolveCloudDatabaseUrl(env));
|
|
8512
|
+
}
|
|
8513
|
+
var cachedClient = null;
|
|
8514
|
+
var cachedStore = null;
|
|
8515
|
+
var cachedVerifier = null;
|
|
8516
|
+
var schemaEnsured = null;
|
|
8517
|
+
function getClient() {
|
|
8518
|
+
if (cachedClient)
|
|
8519
|
+
return cachedClient;
|
|
8520
|
+
const url = resolveCloudDatabaseUrl();
|
|
8521
|
+
if (!url) {
|
|
8522
|
+
throw new Error("Cloud /v1 requires a remote database URL (HASNA_INSTRUCTIONS_DATABASE_URL / INSTRUCTIONS_DATABASE_URL / DATABASE_URL).");
|
|
8523
|
+
}
|
|
8524
|
+
const pool2 = createPgPool({
|
|
8525
|
+
connectionString: url,
|
|
8526
|
+
max: 6,
|
|
8527
|
+
idleTimeoutMillis: 30000,
|
|
8528
|
+
connectionTimeoutMillis: 15000,
|
|
8529
|
+
applicationName: "instructions-serve"
|
|
8530
|
+
});
|
|
8531
|
+
cachedClient = createQueryClient(pool2);
|
|
8532
|
+
return cachedClient;
|
|
8533
|
+
}
|
|
8534
|
+
function getCloudClient() {
|
|
8535
|
+
return getClient();
|
|
8536
|
+
}
|
|
8537
|
+
function authClient() {
|
|
8538
|
+
const client = getClient();
|
|
8539
|
+
return {
|
|
8540
|
+
async many(sql, params = []) {
|
|
8541
|
+
return client.many(sql, params);
|
|
8542
|
+
},
|
|
8543
|
+
async get(sql, params = []) {
|
|
8544
|
+
return client.get(sql, params);
|
|
8545
|
+
},
|
|
8546
|
+
async execute(sql, params = []) {
|
|
8547
|
+
await client.execute(sql, params);
|
|
8548
|
+
}
|
|
8549
|
+
};
|
|
8550
|
+
}
|
|
8551
|
+
function getApiKeyStore() {
|
|
8552
|
+
if (cachedStore)
|
|
8553
|
+
return cachedStore;
|
|
8554
|
+
cachedStore = new ApiKeyStore(authClient());
|
|
8555
|
+
return cachedStore;
|
|
8556
|
+
}
|
|
8557
|
+
async function ensureCloudSchema() {
|
|
8558
|
+
if (schemaEnsured)
|
|
8559
|
+
return schemaEnsured;
|
|
8560
|
+
schemaEnsured = (async () => {
|
|
8561
|
+
const client = getClient();
|
|
8562
|
+
for (const sql of instructionsSchemaSql()) {
|
|
8563
|
+
await client.execute(sql);
|
|
8564
|
+
}
|
|
8565
|
+
await ensureIdempotencySchema(client);
|
|
8566
|
+
await getApiKeyStore().ensureSchema();
|
|
8567
|
+
})().catch((e) => {
|
|
8568
|
+
schemaEnsured = null;
|
|
8569
|
+
throw e;
|
|
8570
|
+
});
|
|
8571
|
+
return schemaEnsured;
|
|
8572
|
+
}
|
|
8573
|
+
var honoMiddlewareCache = new Map;
|
|
8574
|
+
function getHonoAuthMiddleware(requiredScopes) {
|
|
8575
|
+
const key = requiredScopes.join(",");
|
|
8576
|
+
const cached2 = honoMiddlewareCache.get(key);
|
|
8577
|
+
if (cached2)
|
|
8578
|
+
return cached2;
|
|
8579
|
+
const signingSecret = resolveSigningSecret();
|
|
8580
|
+
if (!signingSecret) {
|
|
8581
|
+
throw new Error("Cloud /v1 auth requires a signing secret (HASNA_INSTRUCTIONS_API_SIGNING_KEY / HASNA_API_SIGNING_KEY / API_KEY_SIGNING_SECRET).");
|
|
8582
|
+
}
|
|
8583
|
+
const store = getApiKeyStore();
|
|
8584
|
+
const mw = honoApiKey({
|
|
8585
|
+
app: INSTRUCTIONS_APP_SLUG,
|
|
8586
|
+
signingSecret,
|
|
8587
|
+
keyStatus: store.keyStatus,
|
|
8588
|
+
requiredScopes
|
|
8589
|
+
});
|
|
8590
|
+
honoMiddlewareCache.set(key, mw);
|
|
8591
|
+
return mw;
|
|
8592
|
+
}
|
|
8593
|
+
async function pingCloud() {
|
|
8594
|
+
const client = getClient();
|
|
8595
|
+
const row = await client.get("SELECT 1 AS ok");
|
|
8596
|
+
return row?.ok === 1;
|
|
8597
|
+
}
|
|
8598
|
+
async function closeCloud() {
|
|
8599
|
+
if (cachedClient)
|
|
8600
|
+
await cachedClient.close();
|
|
8601
|
+
cachedClient = null;
|
|
8602
|
+
cachedStore = null;
|
|
8603
|
+
cachedVerifier = null;
|
|
8604
|
+
honoMiddlewareCache.clear();
|
|
8605
|
+
schemaEnsured = null;
|
|
8606
|
+
honoMiddlewareCache.clear();
|
|
8607
|
+
}
|
|
8608
|
+
|
|
8368
8609
|
// src/server/v1.ts
|
|
8369
8610
|
var JSON_HEADERS = { "Content-Type": "application/json" };
|
|
8611
|
+
var MAX_REQUEST_BODY_BYTES = 1024 * 1024;
|
|
8612
|
+
var MAX_SEARCH_QUERY_CHARS = 512;
|
|
8613
|
+
var MAX_IDEMPOTENCY_KEY_CHARS = 255;
|
|
8614
|
+
|
|
8615
|
+
class HttpInputError extends Error {
|
|
8616
|
+
status;
|
|
8617
|
+
code;
|
|
8618
|
+
constructor(status, code, message) {
|
|
8619
|
+
super(message);
|
|
8620
|
+
this.status = status;
|
|
8621
|
+
this.code = code;
|
|
8622
|
+
this.name = "HttpInputError";
|
|
8623
|
+
}
|
|
8624
|
+
}
|
|
8370
8625
|
function json(body, status = 200) {
|
|
8371
8626
|
return new Response(JSON.stringify(body), { status, headers: JSON_HEADERS });
|
|
8372
8627
|
}
|
|
8373
8628
|
function errorResponse(status, message, extra) {
|
|
8374
8629
|
return json({ error: message, ...extra ?? {} }, status);
|
|
8375
8630
|
}
|
|
8376
|
-
function
|
|
8631
|
+
function pagePayload(alias, page) {
|
|
8377
8632
|
return {
|
|
8378
|
-
|
|
8379
|
-
|
|
8380
|
-
|
|
8381
|
-
cursor: 0,
|
|
8382
|
-
next_cursor: null,
|
|
8383
|
-
has_more: false,
|
|
8384
|
-
complete: true,
|
|
8385
|
-
truncated: false,
|
|
8386
|
-
source_bounded: false
|
|
8633
|
+
...page,
|
|
8634
|
+
[alias]: page.items,
|
|
8635
|
+
count: page.items.length
|
|
8387
8636
|
};
|
|
8388
8637
|
}
|
|
8638
|
+
function authenticatedPrincipalAuthority(principal) {
|
|
8639
|
+
if (!principal)
|
|
8640
|
+
throw new Error("authenticated principal is unavailable");
|
|
8641
|
+
return [
|
|
8642
|
+
principal.app,
|
|
8643
|
+
`tenant:${principal.tid ?? "-"}`,
|
|
8644
|
+
`agent:${principal.agent ?? "-"}`,
|
|
8645
|
+
`kid:${principal.kid}`
|
|
8646
|
+
].join("|");
|
|
8647
|
+
}
|
|
8648
|
+
function readIdempotencyKey(req) {
|
|
8649
|
+
const raw2 = req.headers.get("idempotency-key");
|
|
8650
|
+
if (raw2 === null)
|
|
8651
|
+
return null;
|
|
8652
|
+
const key = raw2.trim();
|
|
8653
|
+
if (key.length < 1 || key.length > MAX_IDEMPOTENCY_KEY_CHARS || /[^\x21-\x7e]/.test(key)) {
|
|
8654
|
+
throw new HttpInputError(400, "INVALID_IDEMPOTENCY_KEY", `Idempotency-Key must contain 1-${MAX_IDEMPOTENCY_KEY_CHARS} visible ASCII characters`);
|
|
8655
|
+
}
|
|
8656
|
+
return key;
|
|
8657
|
+
}
|
|
8658
|
+
async function mutationResponse(req, client, context, operation, requestBody, status, perform) {
|
|
8659
|
+
const key = readIdempotencyKey(req);
|
|
8660
|
+
if (!key)
|
|
8661
|
+
return json(await perform(client), status);
|
|
8662
|
+
const result = await executeIdempotentRequest(client, {
|
|
8663
|
+
principal: authenticatedPrincipalAuthority(context.principal),
|
|
8664
|
+
operation,
|
|
8665
|
+
key,
|
|
8666
|
+
body: requestBody
|
|
8667
|
+
}, async (transaction) => ({ status, body: await perform(transaction) }));
|
|
8668
|
+
return json(result.body, result.status);
|
|
8669
|
+
}
|
|
8389
8670
|
async function readJson(req) {
|
|
8671
|
+
const declaredLength = req.headers.get("content-length");
|
|
8672
|
+
if (declaredLength !== null) {
|
|
8673
|
+
const parsed = Number(declaredLength);
|
|
8674
|
+
if (!Number.isSafeInteger(parsed) || parsed < 0)
|
|
8675
|
+
return null;
|
|
8676
|
+
if (parsed > MAX_REQUEST_BODY_BYTES) {
|
|
8677
|
+
throw new HttpInputError(413, "REQUEST_BODY_TOO_LARGE", "request body exceeds the 1 MiB limit");
|
|
8678
|
+
}
|
|
8679
|
+
}
|
|
8680
|
+
if (!req.body)
|
|
8681
|
+
return {};
|
|
8682
|
+
const reader = req.body.getReader();
|
|
8683
|
+
const chunks = [];
|
|
8684
|
+
let total = 0;
|
|
8390
8685
|
try {
|
|
8391
|
-
|
|
8392
|
-
|
|
8393
|
-
|
|
8394
|
-
|
|
8686
|
+
while (true) {
|
|
8687
|
+
const { done, value } = await reader.read();
|
|
8688
|
+
if (done)
|
|
8689
|
+
break;
|
|
8690
|
+
if (!value)
|
|
8691
|
+
continue;
|
|
8692
|
+
total += value.byteLength;
|
|
8693
|
+
if (total > MAX_REQUEST_BODY_BYTES) {
|
|
8694
|
+
await reader.cancel();
|
|
8695
|
+
throw new HttpInputError(413, "REQUEST_BODY_TOO_LARGE", "request body exceeds the 1 MiB limit");
|
|
8696
|
+
}
|
|
8697
|
+
chunks.push(value);
|
|
8698
|
+
}
|
|
8699
|
+
} finally {
|
|
8700
|
+
reader.releaseLock();
|
|
8701
|
+
}
|
|
8702
|
+
if (total === 0)
|
|
8703
|
+
return {};
|
|
8704
|
+
const bytes = new Uint8Array(total);
|
|
8705
|
+
let offset = 0;
|
|
8706
|
+
for (const chunk of chunks) {
|
|
8707
|
+
bytes.set(chunk, offset);
|
|
8708
|
+
offset += chunk.byteLength;
|
|
8709
|
+
}
|
|
8710
|
+
try {
|
|
8711
|
+
return JSON.parse(new TextDecoder().decode(bytes));
|
|
8395
8712
|
} catch {
|
|
8396
8713
|
return null;
|
|
8397
8714
|
}
|
|
8398
8715
|
}
|
|
8399
|
-
|
|
8716
|
+
function boundedSearchQuery(value) {
|
|
8717
|
+
if (value === null || value === "")
|
|
8718
|
+
return;
|
|
8719
|
+
if (value.length > MAX_SEARCH_QUERY_CHARS) {
|
|
8720
|
+
throw new HttpInputError(400, "SEARCH_QUERY_TOO_LONG", `search query exceeds ${MAX_SEARCH_QUERY_CHARS} characters`);
|
|
8721
|
+
}
|
|
8722
|
+
return value;
|
|
8723
|
+
}
|
|
8724
|
+
async function handleV1Request(req, url, context = {}) {
|
|
8400
8725
|
const path = url.pathname;
|
|
8401
8726
|
if (path !== "/v1" && !path.startsWith("/v1/"))
|
|
8402
8727
|
return null;
|
|
@@ -8404,7 +8729,8 @@ async function handleV1Request(req, url) {
|
|
|
8404
8729
|
try {
|
|
8405
8730
|
await ensureCloudSchema();
|
|
8406
8731
|
} catch (e) {
|
|
8407
|
-
|
|
8732
|
+
console.error("instructions /v1: database unavailable");
|
|
8733
|
+
return errorResponse(503, "Instructions database is unavailable", { code: "DATABASE_UNAVAILABLE" });
|
|
8408
8734
|
}
|
|
8409
8735
|
const client = getCloudClient();
|
|
8410
8736
|
const segments = path.split("/").filter(Boolean);
|
|
@@ -8415,25 +8741,40 @@ async function handleV1Request(req, url) {
|
|
|
8415
8741
|
if (resource === "configs") {
|
|
8416
8742
|
if (!id) {
|
|
8417
8743
|
if (method === "GET") {
|
|
8744
|
+
const search = boundedSearchQuery(url.searchParams.get("search"));
|
|
8418
8745
|
const filter = {
|
|
8419
8746
|
...url.searchParams.get("category") ? { category: url.searchParams.get("category") } : {},
|
|
8420
8747
|
...url.searchParams.get("agent") ? { agent: url.searchParams.get("agent") } : {},
|
|
8421
8748
|
...url.searchParams.get("kind") ? { kind: url.searchParams.get("kind") } : {},
|
|
8422
|
-
...
|
|
8749
|
+
...search ? { search } : {},
|
|
8750
|
+
...url.searchParams.getAll("tag").length ? { tags: url.searchParams.getAll("tag") } : {}
|
|
8751
|
+
};
|
|
8752
|
+
const options = {
|
|
8753
|
+
limit: url.searchParams.get("limit") ?? undefined,
|
|
8754
|
+
cursor: url.searchParams.get("cursor") ?? undefined
|
|
8423
8755
|
};
|
|
8424
|
-
const
|
|
8425
|
-
|
|
8756
|
+
const view = url.searchParams.get("view");
|
|
8757
|
+
if (view !== null && view !== "identity" && view !== "summary") {
|
|
8758
|
+
return errorResponse(400, "unsupported config view", { code: "INVALID_CONFIG_VIEW" });
|
|
8759
|
+
}
|
|
8760
|
+
if (view === "identity") {
|
|
8761
|
+
const page2 = await listConfigIdentitiesPage(client, filter, options);
|
|
8762
|
+
return json(pagePayload("configs", page2));
|
|
8763
|
+
}
|
|
8764
|
+
if (view === "summary") {
|
|
8765
|
+
const page2 = await listConfigSummariesPage(client, filter, options);
|
|
8766
|
+
return json({ ...page2, count: page2.items.length });
|
|
8767
|
+
}
|
|
8768
|
+
const page = await listConfigsPage(client, filter, options);
|
|
8769
|
+
return json(pagePayload("configs", page));
|
|
8426
8770
|
}
|
|
8427
8771
|
if (method === "POST") {
|
|
8428
8772
|
const body = await readJson(req);
|
|
8429
8773
|
if (!body)
|
|
8430
8774
|
return errorResponse(400, "invalid JSON body");
|
|
8431
|
-
|
|
8432
|
-
|
|
8433
|
-
|
|
8434
|
-
} catch (e) {
|
|
8435
|
-
return errorResponse(400, e.message);
|
|
8436
|
-
}
|
|
8775
|
+
return await mutationResponse(req, client, context, "POST /v1/configs", body, 201, async (transaction) => ({
|
|
8776
|
+
config: await createConfig(transaction, body)
|
|
8777
|
+
}));
|
|
8437
8778
|
}
|
|
8438
8779
|
return errorResponse(405, `method ${method} not allowed on /v1/configs`);
|
|
8439
8780
|
}
|
|
@@ -8455,13 +8796,17 @@ async function handleV1Request(req, url) {
|
|
|
8455
8796
|
return json({ snapshot });
|
|
8456
8797
|
}
|
|
8457
8798
|
if (method === "GET") {
|
|
8458
|
-
const
|
|
8459
|
-
|
|
8799
|
+
const page = await listSnapshotsPage(client, id, {
|
|
8800
|
+
limit: url.searchParams.get("limit") ?? undefined,
|
|
8801
|
+
cursor: url.searchParams.get("cursor") ?? undefined
|
|
8802
|
+
});
|
|
8803
|
+
return json(pagePayload("snapshots", page));
|
|
8460
8804
|
}
|
|
8461
8805
|
if (method === "POST") {
|
|
8462
8806
|
const body = await readJson(req);
|
|
8463
|
-
|
|
8464
|
-
|
|
8807
|
+
return await mutationResponse(req, client, context, "POST /v1/configs/:id/snapshots", { config_id: id, ...body ?? {} }, 201, async (transaction) => ({
|
|
8808
|
+
snapshot: body && typeof body.content === "string" && typeof body.version === "number" ? await createSnapshotContent(transaction, id, body.content, body.version) : await createSnapshot(transaction, id)
|
|
8809
|
+
}));
|
|
8465
8810
|
}
|
|
8466
8811
|
return errorResponse(405, `method ${method} not allowed on /v1/configs/:id/snapshots`);
|
|
8467
8812
|
}
|
|
@@ -8487,26 +8832,24 @@ async function handleV1Request(req, url) {
|
|
|
8487
8832
|
if (resource === "profiles") {
|
|
8488
8833
|
if (!id) {
|
|
8489
8834
|
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, {
|
|
8835
|
+
const options = {
|
|
8495
8836
|
limit: url.searchParams.get("limit") ?? undefined,
|
|
8496
8837
|
cursor: url.searchParams.get("cursor") ?? undefined
|
|
8497
|
-
}
|
|
8498
|
-
|
|
8838
|
+
};
|
|
8839
|
+
if (url.searchParams.get("view") === "identity") {
|
|
8840
|
+
const page2 = await listProfileIdentitiesPage(client, options);
|
|
8841
|
+
return json(pagePayload("profiles", page2));
|
|
8842
|
+
}
|
|
8843
|
+
const page = await listProfilesPage(client, options);
|
|
8844
|
+
return json(pagePayload("profiles", page));
|
|
8499
8845
|
}
|
|
8500
8846
|
if (method === "POST") {
|
|
8501
8847
|
const body = await readJson(req);
|
|
8502
8848
|
if (!body)
|
|
8503
8849
|
return errorResponse(400, "invalid JSON body");
|
|
8504
|
-
|
|
8505
|
-
|
|
8506
|
-
|
|
8507
|
-
} catch (e) {
|
|
8508
|
-
return errorResponse(400, e.message);
|
|
8509
|
-
}
|
|
8850
|
+
return await mutationResponse(req, client, context, "POST /v1/profiles", body, 201, async (transaction) => ({
|
|
8851
|
+
profile: await createProfile(transaction, body)
|
|
8852
|
+
}));
|
|
8510
8853
|
}
|
|
8511
8854
|
return errorResponse(405, `method ${method} not allowed on /v1/profiles`);
|
|
8512
8855
|
}
|
|
@@ -8523,8 +8866,11 @@ async function handleV1Request(req, url) {
|
|
|
8523
8866
|
if (action === "bindings") {
|
|
8524
8867
|
if (method !== "GET")
|
|
8525
8868
|
return errorResponse(405, `method ${method} not allowed on /v1/profiles/:id/bindings`);
|
|
8526
|
-
const
|
|
8527
|
-
|
|
8869
|
+
const page = await getProfileConfigBindingsPage(client, id, {
|
|
8870
|
+
limit: url.searchParams.get("limit") ?? undefined,
|
|
8871
|
+
cursor: url.searchParams.get("cursor") ?? undefined
|
|
8872
|
+
});
|
|
8873
|
+
return json(pagePayload("bindings", page));
|
|
8528
8874
|
}
|
|
8529
8875
|
if (action === "configs") {
|
|
8530
8876
|
const configId = segments[4] ? decodeURIComponent(segments[4]) : undefined;
|
|
@@ -8532,8 +8878,10 @@ async function handleV1Request(req, url) {
|
|
|
8532
8878
|
const body = await readJson(req);
|
|
8533
8879
|
if (!body?.config_id)
|
|
8534
8880
|
return errorResponse(400, "config_id is required");
|
|
8535
|
-
await
|
|
8536
|
-
|
|
8881
|
+
return await mutationResponse(req, client, context, "POST /v1/profiles/:id/configs", { profile_id: id, config_id: body.config_id }, 200, async (transaction) => {
|
|
8882
|
+
await addConfigToProfile(transaction, id, body.config_id);
|
|
8883
|
+
return { added: true };
|
|
8884
|
+
});
|
|
8537
8885
|
}
|
|
8538
8886
|
if (method === "DELETE" && configId) {
|
|
8539
8887
|
await removeConfigFromProfile(client, id, configId);
|
|
@@ -8543,30 +8891,36 @@ async function handleV1Request(req, url) {
|
|
|
8543
8891
|
const body = await readJson(req);
|
|
8544
8892
|
if (!body?.binding)
|
|
8545
8893
|
return errorResponse(400, "binding is required");
|
|
8546
|
-
|
|
8547
|
-
|
|
8894
|
+
return await mutationResponse(req, client, context, "PUT /v1/profiles/:id/configs/:configId", { profile_id: id, config_id: configId, binding: body.binding }, 200, async (transaction) => ({
|
|
8895
|
+
binding: await setProfileConfigBinding(transaction, id, configId, body.binding)
|
|
8896
|
+
}));
|
|
8548
8897
|
}
|
|
8549
8898
|
return errorResponse(405, `method ${method} not allowed on /v1/profiles/:id/configs`);
|
|
8550
8899
|
}
|
|
8551
8900
|
if (action === "assets") {
|
|
8552
8901
|
const assetKey = segments[4] ? decodeURIComponent(segments[4]) : undefined;
|
|
8553
8902
|
if (method === "GET" && !assetKey) {
|
|
8554
|
-
const
|
|
8555
|
-
|
|
8903
|
+
const page = await getProfileAssetBindingsPage(client, id, {
|
|
8904
|
+
limit: url.searchParams.get("limit") ?? undefined,
|
|
8905
|
+
cursor: url.searchParams.get("cursor") ?? undefined
|
|
8906
|
+
});
|
|
8907
|
+
return json(pagePayload("assets", page));
|
|
8556
8908
|
}
|
|
8557
8909
|
if (method === "POST" && !assetKey) {
|
|
8558
8910
|
const body = await readJson(req);
|
|
8559
8911
|
if (!body?.source_config_id || !body.binding)
|
|
8560
8912
|
return errorResponse(400, "source_config_id and binding are required");
|
|
8561
|
-
|
|
8562
|
-
|
|
8913
|
+
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) => ({
|
|
8914
|
+
asset: await addAssetToProfile(transaction, id, body.source_config_id, body.binding)
|
|
8915
|
+
}));
|
|
8563
8916
|
}
|
|
8564
8917
|
if (method === "PUT" && assetKey) {
|
|
8565
8918
|
const body = await readJson(req);
|
|
8566
8919
|
if (!body?.binding)
|
|
8567
8920
|
return errorResponse(400, "binding is required");
|
|
8568
|
-
|
|
8569
|
-
|
|
8921
|
+
return await mutationResponse(req, client, context, "PUT /v1/profiles/:id/assets/:assetKey", { profile_id: id, asset_key: assetKey, binding: body.binding }, 200, async (transaction) => ({
|
|
8922
|
+
asset: await setProfileAssetBinding(transaction, id, assetKey, body.binding)
|
|
8923
|
+
}));
|
|
8570
8924
|
}
|
|
8571
8925
|
if (method === "DELETE" && assetKey) {
|
|
8572
8926
|
await removeAssetFromProfile(client, id, assetKey);
|
|
@@ -8578,13 +8932,6 @@ async function handleV1Request(req, url) {
|
|
|
8578
8932
|
return errorResponse(404, `unknown profile action: ${action}`);
|
|
8579
8933
|
if (method === "GET") {
|
|
8580
8934
|
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
8935
|
const configs = await getProfileConfigsPage(client, id, {
|
|
8589
8936
|
limit: url.searchParams.get("limit") ?? undefined,
|
|
8590
8937
|
cursor: url.searchParams.get("cursor") ?? undefined
|
|
@@ -8629,15 +8976,20 @@ async function handleV1Request(req, url) {
|
|
|
8629
8976
|
}
|
|
8630
8977
|
if (!id) {
|
|
8631
8978
|
if (method === "GET") {
|
|
8632
|
-
const
|
|
8633
|
-
|
|
8979
|
+
const options = {
|
|
8980
|
+
limit: url.searchParams.get("limit") ?? undefined,
|
|
8981
|
+
cursor: url.searchParams.get("cursor") ?? undefined
|
|
8982
|
+
};
|
|
8983
|
+
const page = url.searchParams.get("view") === "identity" ? await listMachineIdentitiesPage(client, options) : await listMachinesPage(client, options);
|
|
8984
|
+
return json(pagePayload("machines", page));
|
|
8634
8985
|
}
|
|
8635
8986
|
if (method === "POST") {
|
|
8636
8987
|
const body = await readJson(req);
|
|
8637
8988
|
if (!body?.hostname)
|
|
8638
8989
|
return errorResponse(400, "hostname is required");
|
|
8639
|
-
|
|
8640
|
-
|
|
8990
|
+
return await mutationResponse(req, client, context, "POST /v1/machines", body, 201, async (transaction) => ({
|
|
8991
|
+
machine: await registerMachine(transaction, body.hostname, body.os ?? null, body.arch ?? null)
|
|
8992
|
+
}));
|
|
8641
8993
|
}
|
|
8642
8994
|
return errorResponse(405, `method ${method} not allowed on /v1/machines`);
|
|
8643
8995
|
}
|
|
@@ -8659,10 +9011,17 @@ async function handleV1Request(req, url) {
|
|
|
8659
9011
|
}
|
|
8660
9012
|
return errorResponse(404, `unknown /v1 resource: ${resource ?? "(root)"}`);
|
|
8661
9013
|
} catch (e) {
|
|
9014
|
+
if (e instanceof HttpInputError)
|
|
9015
|
+
return errorResponse(e.status, e.message, { code: e.code });
|
|
9016
|
+
if (e instanceof StoreValidationError)
|
|
9017
|
+
return errorResponse(400, e.message, { code: e.code });
|
|
9018
|
+
if (e instanceof IdempotencyConflictError)
|
|
9019
|
+
return errorResponse(409, e.message, { code: e.code });
|
|
8662
9020
|
if (e instanceof ConfigNotFoundError || e instanceof ProfileNotFoundError) {
|
|
8663
9021
|
return errorResponse(404, e.message);
|
|
8664
9022
|
}
|
|
8665
|
-
|
|
9023
|
+
console.error("instructions /v1: request failed");
|
|
9024
|
+
return errorResponse(500, "Instructions request failed", { code: "INTERNAL_ERROR" });
|
|
8666
9025
|
}
|
|
8667
9026
|
}
|
|
8668
9027
|
|
|
@@ -8689,6 +9048,27 @@ var configSchema = {
|
|
|
8689
9048
|
synced_at: { type: "string", nullable: true }
|
|
8690
9049
|
}
|
|
8691
9050
|
};
|
|
9051
|
+
var configSummarySchema = {
|
|
9052
|
+
type: "object",
|
|
9053
|
+
additionalProperties: false,
|
|
9054
|
+
required: ["id", "name", "slug", "kind", "category", "agent", "target_path", "format", "output_count", "description", "tags", "is_template", "version", "updated_at"],
|
|
9055
|
+
properties: {
|
|
9056
|
+
id: { type: "string" },
|
|
9057
|
+
name: { type: "string" },
|
|
9058
|
+
slug: { type: "string" },
|
|
9059
|
+
kind: { type: "string" },
|
|
9060
|
+
category: { type: "string" },
|
|
9061
|
+
agent: { type: "string" },
|
|
9062
|
+
target_path: { type: "string", nullable: true },
|
|
9063
|
+
format: { type: "string" },
|
|
9064
|
+
output_count: { type: "number" },
|
|
9065
|
+
description: { type: "string", nullable: true },
|
|
9066
|
+
tags: { type: "array", items: { type: "string" } },
|
|
9067
|
+
is_template: { type: "boolean" },
|
|
9068
|
+
version: { type: "number" },
|
|
9069
|
+
updated_at: { type: "string" }
|
|
9070
|
+
}
|
|
9071
|
+
};
|
|
8692
9072
|
var profileSchema = {
|
|
8693
9073
|
type: "object",
|
|
8694
9074
|
properties: {
|
|
@@ -8702,6 +9082,95 @@ var profileSchema = {
|
|
|
8702
9082
|
updated_at: { type: "string" }
|
|
8703
9083
|
}
|
|
8704
9084
|
};
|
|
9085
|
+
var configIdentitySchema = {
|
|
9086
|
+
type: "object",
|
|
9087
|
+
additionalProperties: false,
|
|
9088
|
+
required: ["id", "name", "slug", "kind", "category", "agent", "format", "is_template", "version", "created_at", "updated_at", "synced_at"],
|
|
9089
|
+
properties: {
|
|
9090
|
+
id: { type: "string" },
|
|
9091
|
+
name: { type: "string" },
|
|
9092
|
+
slug: { type: "string" },
|
|
9093
|
+
kind: { type: "string" },
|
|
9094
|
+
category: { type: "string" },
|
|
9095
|
+
agent: { type: "string" },
|
|
9096
|
+
format: { type: "string" },
|
|
9097
|
+
is_template: { type: "boolean" },
|
|
9098
|
+
version: { type: "number" },
|
|
9099
|
+
created_at: { type: "string" },
|
|
9100
|
+
updated_at: { type: "string" },
|
|
9101
|
+
synced_at: { type: "string", nullable: true }
|
|
9102
|
+
}
|
|
9103
|
+
};
|
|
9104
|
+
var profileIdentitySchema = {
|
|
9105
|
+
type: "object",
|
|
9106
|
+
required: ["id", "name", "slug", "created_at", "updated_at"],
|
|
9107
|
+
properties: {
|
|
9108
|
+
id: { type: "string" },
|
|
9109
|
+
name: { type: "string" },
|
|
9110
|
+
slug: { type: "string" },
|
|
9111
|
+
created_at: { type: "string" },
|
|
9112
|
+
updated_at: { type: "string" }
|
|
9113
|
+
}
|
|
9114
|
+
};
|
|
9115
|
+
var machineSchema = {
|
|
9116
|
+
type: "object",
|
|
9117
|
+
required: ["id", "hostname", "os", "arch", "last_applied_at", "created_at"],
|
|
9118
|
+
properties: {
|
|
9119
|
+
id: { type: "string" },
|
|
9120
|
+
hostname: { type: "string" },
|
|
9121
|
+
os: { type: "string", nullable: true },
|
|
9122
|
+
arch: { type: "string", nullable: true },
|
|
9123
|
+
last_applied_at: { type: "string", nullable: true },
|
|
9124
|
+
created_at: { type: "string" }
|
|
9125
|
+
}
|
|
9126
|
+
};
|
|
9127
|
+
var snapshotSchema = {
|
|
9128
|
+
type: "object",
|
|
9129
|
+
required: ["id", "config_id", "content", "version", "created_at"],
|
|
9130
|
+
properties: {
|
|
9131
|
+
id: { type: "string" },
|
|
9132
|
+
config_id: { type: "string" },
|
|
9133
|
+
content: { type: "string" },
|
|
9134
|
+
version: { type: "number" },
|
|
9135
|
+
created_at: { type: "string" }
|
|
9136
|
+
}
|
|
9137
|
+
};
|
|
9138
|
+
var limitParameter = { name: "limit", in: "query", schema: { type: "integer", minimum: 1, maximum: 100 } };
|
|
9139
|
+
var cursorParameter = { name: "cursor", in: "query", schema: { type: "integer", minimum: 0, maximum: 1e5 } };
|
|
9140
|
+
var configViewParameter = {
|
|
9141
|
+
name: "view",
|
|
9142
|
+
in: "query",
|
|
9143
|
+
description: "Use summary for a content-free list projection, or identity for the smallest metadata-only projection.",
|
|
9144
|
+
schema: { type: "string", enum: ["summary", "identity"] }
|
|
9145
|
+
};
|
|
9146
|
+
var identityViewParameter = {
|
|
9147
|
+
name: "view",
|
|
9148
|
+
in: "query",
|
|
9149
|
+
description: "Use identity for a metadata-only projection that omits instruction content, paths, outputs, and private profile fields.",
|
|
9150
|
+
schema: { type: "string", enum: ["identity"] }
|
|
9151
|
+
};
|
|
9152
|
+
var idempotencyKeyParameter = {
|
|
9153
|
+
name: "Idempotency-Key",
|
|
9154
|
+
in: "header",
|
|
9155
|
+
required: false,
|
|
9156
|
+
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.",
|
|
9157
|
+
schema: { type: "string", minLength: 1, maxLength: 255, pattern: "^[!-~]+$" }
|
|
9158
|
+
};
|
|
9159
|
+
var idempotencyConflictResponse = {
|
|
9160
|
+
description: "The Idempotency-Key was already used for a different request body.",
|
|
9161
|
+
content: {
|
|
9162
|
+
"application/json": {
|
|
9163
|
+
schema: {
|
|
9164
|
+
type: "object",
|
|
9165
|
+
required: ["error", "code"],
|
|
9166
|
+
properties: {
|
|
9167
|
+
error: { type: "string" },
|
|
9168
|
+
code: { type: "string", const: "IDEMPOTENCY_KEY_REUSED" }
|
|
9169
|
+
}
|
|
9170
|
+
}
|
|
9171
|
+
}
|
|
9172
|
+
}
|
|
9173
|
+
};
|
|
8705
9174
|
function buildV1OpenApiDocument(version = getPackageVersion()) {
|
|
8706
9175
|
return {
|
|
8707
9176
|
openapi: "3.1.0",
|
|
@@ -8717,7 +9186,12 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
|
|
|
8717
9186
|
},
|
|
8718
9187
|
schemas: {
|
|
8719
9188
|
Config: configSchema,
|
|
9189
|
+
ConfigSummary: configSummarySchema,
|
|
9190
|
+
ConfigIdentity: configIdentitySchema,
|
|
8720
9191
|
Profile: profileSchema,
|
|
9192
|
+
ProfileIdentity: profileIdentitySchema,
|
|
9193
|
+
Machine: machineSchema,
|
|
9194
|
+
ConfigSnapshot: snapshotSchema,
|
|
8721
9195
|
CreateConfigInput: {
|
|
8722
9196
|
type: "object",
|
|
8723
9197
|
required: ["name", "category", "content"],
|
|
@@ -8738,12 +9212,17 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
|
|
|
8738
9212
|
type: "object",
|
|
8739
9213
|
properties: {
|
|
8740
9214
|
name: { type: "string" },
|
|
9215
|
+
kind: { type: "string" },
|
|
8741
9216
|
category: { type: "string" },
|
|
8742
9217
|
agent: { type: "string" },
|
|
9218
|
+
target_path: { type: "string", nullable: true },
|
|
9219
|
+
outputs: { type: "array", items: { type: "object" } },
|
|
9220
|
+
format: { type: "string" },
|
|
8743
9221
|
content: { type: "string" },
|
|
8744
|
-
description: { type: "string" },
|
|
9222
|
+
description: { type: "string", nullable: true },
|
|
8745
9223
|
tags: { type: "array", items: { type: "string" } },
|
|
8746
|
-
is_template: { type: "boolean" }
|
|
9224
|
+
is_template: { type: "boolean" },
|
|
9225
|
+
synced_at: { type: "string", nullable: true }
|
|
8747
9226
|
}
|
|
8748
9227
|
},
|
|
8749
9228
|
CreateProfileInput: {
|
|
@@ -8756,6 +9235,38 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
|
|
|
8756
9235
|
variables: { type: "object" }
|
|
8757
9236
|
}
|
|
8758
9237
|
},
|
|
9238
|
+
UpdateProfileInput: {
|
|
9239
|
+
type: "object",
|
|
9240
|
+
properties: {
|
|
9241
|
+
name: { type: "string" },
|
|
9242
|
+
description: { type: "string", nullable: true },
|
|
9243
|
+
selectors: { type: "object" },
|
|
9244
|
+
variables: { type: "object" }
|
|
9245
|
+
}
|
|
9246
|
+
},
|
|
9247
|
+
PruneSnapshotsInput: {
|
|
9248
|
+
type: "object",
|
|
9249
|
+
properties: {
|
|
9250
|
+
keep: { type: "integer", minimum: 0, default: 10 }
|
|
9251
|
+
}
|
|
9252
|
+
},
|
|
9253
|
+
MachineAppliedInput: {
|
|
9254
|
+
type: "object",
|
|
9255
|
+
required: ["hostname"],
|
|
9256
|
+
properties: {
|
|
9257
|
+
hostname: { type: "string" }
|
|
9258
|
+
}
|
|
9259
|
+
},
|
|
9260
|
+
FeedbackInput: {
|
|
9261
|
+
type: "object",
|
|
9262
|
+
required: ["message"],
|
|
9263
|
+
properties: {
|
|
9264
|
+
message: { type: "string" },
|
|
9265
|
+
email: { type: "string" },
|
|
9266
|
+
category: { type: "string" },
|
|
9267
|
+
version: { type: "string" }
|
|
9268
|
+
}
|
|
9269
|
+
},
|
|
8759
9270
|
AddProfileConfigInput: {
|
|
8760
9271
|
type: "object",
|
|
8761
9272
|
required: ["config_id"],
|
|
@@ -8890,7 +9401,128 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
|
|
|
8890
9401
|
type: "object",
|
|
8891
9402
|
required: ["items", "total", "limit", "cursor", "next_cursor", "has_more", "complete", "truncated", "source_bounded"],
|
|
8892
9403
|
properties: {
|
|
9404
|
+
configs: { type: "array", items: { $ref: "#/components/schemas/Config" } },
|
|
8893
9405
|
items: { type: "array", items: { $ref: "#/components/schemas/Config" } },
|
|
9406
|
+
count: { type: "number" },
|
|
9407
|
+
total: { type: "number" },
|
|
9408
|
+
limit: { type: "number" },
|
|
9409
|
+
cursor: { type: "number" },
|
|
9410
|
+
next_cursor: { type: "number", nullable: true },
|
|
9411
|
+
has_more: { type: "boolean" },
|
|
9412
|
+
complete: { type: "boolean" },
|
|
9413
|
+
truncated: { type: "boolean", const: false },
|
|
9414
|
+
source_bounded: { type: "boolean" }
|
|
9415
|
+
}
|
|
9416
|
+
},
|
|
9417
|
+
BoundedConfigSummaryPage: {
|
|
9418
|
+
type: "object",
|
|
9419
|
+
required: ["items", "total", "limit", "cursor", "next_cursor", "has_more", "complete", "truncated", "source_bounded"],
|
|
9420
|
+
properties: {
|
|
9421
|
+
configs: { type: "array", items: { $ref: "#/components/schemas/ConfigSummary" } },
|
|
9422
|
+
items: { type: "array", items: { $ref: "#/components/schemas/ConfigSummary" } },
|
|
9423
|
+
count: { type: "number" },
|
|
9424
|
+
total: { type: "number" },
|
|
9425
|
+
limit: { type: "number" },
|
|
9426
|
+
cursor: { type: "number" },
|
|
9427
|
+
next_cursor: { type: "number", nullable: true },
|
|
9428
|
+
has_more: { type: "boolean" },
|
|
9429
|
+
complete: { type: "boolean" },
|
|
9430
|
+
truncated: { type: "boolean", const: false },
|
|
9431
|
+
source_bounded: { type: "boolean" }
|
|
9432
|
+
}
|
|
9433
|
+
},
|
|
9434
|
+
BoundedConfigIdentityPage: {
|
|
9435
|
+
type: "object",
|
|
9436
|
+
required: ["items", "total", "limit", "cursor", "next_cursor", "has_more", "complete", "truncated", "source_bounded"],
|
|
9437
|
+
properties: {
|
|
9438
|
+
configs: { type: "array", items: { $ref: "#/components/schemas/ConfigIdentity" } },
|
|
9439
|
+
items: { type: "array", items: { $ref: "#/components/schemas/ConfigIdentity" } },
|
|
9440
|
+
count: { type: "number" },
|
|
9441
|
+
total: { type: "number" },
|
|
9442
|
+
limit: { type: "number" },
|
|
9443
|
+
cursor: { type: "number" },
|
|
9444
|
+
next_cursor: { type: "number", nullable: true },
|
|
9445
|
+
has_more: { type: "boolean" },
|
|
9446
|
+
complete: { type: "boolean" },
|
|
9447
|
+
truncated: { type: "boolean", const: false },
|
|
9448
|
+
source_bounded: { type: "boolean" }
|
|
9449
|
+
}
|
|
9450
|
+
},
|
|
9451
|
+
BoundedProfileIdentityPage: {
|
|
9452
|
+
type: "object",
|
|
9453
|
+
required: ["items", "total", "limit", "cursor", "next_cursor", "has_more", "complete", "truncated", "source_bounded"],
|
|
9454
|
+
properties: {
|
|
9455
|
+
profiles: { type: "array", items: { $ref: "#/components/schemas/ProfileIdentity" } },
|
|
9456
|
+
items: { type: "array", items: { $ref: "#/components/schemas/ProfileIdentity" } },
|
|
9457
|
+
count: { type: "number" },
|
|
9458
|
+
total: { type: "number" },
|
|
9459
|
+
limit: { type: "number" },
|
|
9460
|
+
cursor: { type: "number" },
|
|
9461
|
+
next_cursor: { type: "number", nullable: true },
|
|
9462
|
+
has_more: { type: "boolean" },
|
|
9463
|
+
complete: { type: "boolean" },
|
|
9464
|
+
truncated: { type: "boolean", const: false },
|
|
9465
|
+
source_bounded: { type: "boolean" }
|
|
9466
|
+
}
|
|
9467
|
+
},
|
|
9468
|
+
BoundedSnapshotPage: {
|
|
9469
|
+
type: "object",
|
|
9470
|
+
required: ["items", "total", "limit", "cursor", "next_cursor", "has_more", "complete", "truncated", "source_bounded"],
|
|
9471
|
+
properties: {
|
|
9472
|
+
snapshots: { type: "array", items: { $ref: "#/components/schemas/ConfigSnapshot" } },
|
|
9473
|
+
items: { type: "array", items: { $ref: "#/components/schemas/ConfigSnapshot" } },
|
|
9474
|
+
count: { type: "number" },
|
|
9475
|
+
total: { type: "number" },
|
|
9476
|
+
limit: { type: "number" },
|
|
9477
|
+
cursor: { type: "number" },
|
|
9478
|
+
next_cursor: { type: "number", nullable: true },
|
|
9479
|
+
has_more: { type: "boolean" },
|
|
9480
|
+
complete: { type: "boolean" },
|
|
9481
|
+
truncated: { type: "boolean", const: false },
|
|
9482
|
+
source_bounded: { type: "boolean" }
|
|
9483
|
+
}
|
|
9484
|
+
},
|
|
9485
|
+
BoundedMachinePage: {
|
|
9486
|
+
type: "object",
|
|
9487
|
+
required: ["items", "total", "limit", "cursor", "next_cursor", "has_more", "complete", "truncated", "source_bounded"],
|
|
9488
|
+
properties: {
|
|
9489
|
+
machines: { type: "array", items: { $ref: "#/components/schemas/Machine" } },
|
|
9490
|
+
items: { type: "array", items: { $ref: "#/components/schemas/Machine" } },
|
|
9491
|
+
count: { type: "number" },
|
|
9492
|
+
total: { type: "number" },
|
|
9493
|
+
limit: { type: "number" },
|
|
9494
|
+
cursor: { type: "number" },
|
|
9495
|
+
next_cursor: { type: "number", nullable: true },
|
|
9496
|
+
has_more: { type: "boolean" },
|
|
9497
|
+
complete: { type: "boolean" },
|
|
9498
|
+
truncated: { type: "boolean", const: false },
|
|
9499
|
+
source_bounded: { type: "boolean" }
|
|
9500
|
+
}
|
|
9501
|
+
},
|
|
9502
|
+
BoundedProfileConfigBindingPage: {
|
|
9503
|
+
type: "object",
|
|
9504
|
+
required: ["items", "total", "limit", "cursor", "next_cursor", "has_more", "complete", "truncated", "source_bounded"],
|
|
9505
|
+
properties: {
|
|
9506
|
+
bindings: { type: "array", items: { $ref: "#/components/schemas/ProfileConfigBinding" } },
|
|
9507
|
+
items: { type: "array", items: { $ref: "#/components/schemas/ProfileConfigBinding" } },
|
|
9508
|
+
count: { type: "number" },
|
|
9509
|
+
total: { type: "number" },
|
|
9510
|
+
limit: { type: "number" },
|
|
9511
|
+
cursor: { type: "number" },
|
|
9512
|
+
next_cursor: { type: "number", nullable: true },
|
|
9513
|
+
has_more: { type: "boolean" },
|
|
9514
|
+
complete: { type: "boolean" },
|
|
9515
|
+
truncated: { type: "boolean", const: false },
|
|
9516
|
+
source_bounded: { type: "boolean" }
|
|
9517
|
+
}
|
|
9518
|
+
},
|
|
9519
|
+
BoundedProfileAssetBindingPage: {
|
|
9520
|
+
type: "object",
|
|
9521
|
+
required: ["items", "total", "limit", "cursor", "next_cursor", "has_more", "complete", "truncated", "source_bounded"],
|
|
9522
|
+
properties: {
|
|
9523
|
+
assets: { type: "array", items: { $ref: "#/components/schemas/ProfileAssetBinding" } },
|
|
9524
|
+
items: { type: "array", items: { $ref: "#/components/schemas/ProfileAssetBinding" } },
|
|
9525
|
+
count: { type: "number" },
|
|
8894
9526
|
total: { type: "number" },
|
|
8895
9527
|
limit: { type: "number" },
|
|
8896
9528
|
cursor: { type: "number" },
|
|
@@ -8934,18 +9566,22 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
|
|
|
8934
9566
|
{ name: "category", in: "query", schema: { type: "string" } },
|
|
8935
9567
|
{ name: "agent", in: "query", schema: { type: "string" } },
|
|
8936
9568
|
{ name: "kind", in: "query", schema: { type: "string" } },
|
|
8937
|
-
{ name: "search", in: "query", schema: { type: "string" } }
|
|
9569
|
+
{ name: "search", in: "query", schema: { type: "string", maxLength: 512 } },
|
|
9570
|
+
limitParameter,
|
|
9571
|
+
cursorParameter,
|
|
9572
|
+
{ name: "tag", in: "query", schema: { type: "array", items: { type: "string" } } },
|
|
9573
|
+
configViewParameter
|
|
8938
9574
|
],
|
|
8939
9575
|
responses: {
|
|
8940
9576
|
"200": {
|
|
8941
9577
|
content: {
|
|
8942
9578
|
"application/json": {
|
|
8943
9579
|
schema: {
|
|
8944
|
-
|
|
8945
|
-
|
|
8946
|
-
|
|
8947
|
-
|
|
8948
|
-
|
|
9580
|
+
anyOf: [
|
|
9581
|
+
{ $ref: "#/components/schemas/BoundedConfigPage" },
|
|
9582
|
+
{ $ref: "#/components/schemas/BoundedConfigSummaryPage" },
|
|
9583
|
+
{ $ref: "#/components/schemas/BoundedConfigIdentityPage" }
|
|
9584
|
+
]
|
|
8949
9585
|
}
|
|
8950
9586
|
}
|
|
8951
9587
|
}
|
|
@@ -8955,6 +9591,7 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
|
|
|
8955
9591
|
post: {
|
|
8956
9592
|
operationId: "createConfig",
|
|
8957
9593
|
summary: "Create a config",
|
|
9594
|
+
parameters: [idempotencyKeyParameter],
|
|
8958
9595
|
requestBody: {
|
|
8959
9596
|
required: true,
|
|
8960
9597
|
content: { "application/json": { schema: { $ref: "#/components/schemas/CreateConfigInput" } } }
|
|
@@ -8966,7 +9603,8 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
|
|
|
8966
9603
|
schema: { type: "object", properties: { config: { $ref: "#/components/schemas/Config" } } }
|
|
8967
9604
|
}
|
|
8968
9605
|
}
|
|
8969
|
-
}
|
|
9606
|
+
},
|
|
9607
|
+
"409": idempotencyConflictResponse
|
|
8970
9608
|
}
|
|
8971
9609
|
}
|
|
8972
9610
|
},
|
|
@@ -9003,6 +9641,24 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
|
|
|
9003
9641
|
}
|
|
9004
9642
|
}
|
|
9005
9643
|
},
|
|
9644
|
+
put: {
|
|
9645
|
+
operationId: "putConfig",
|
|
9646
|
+
summary: "Update a config via PUT",
|
|
9647
|
+
parameters: [{ name: "id", in: "path", required: true, schema: { type: "string" } }],
|
|
9648
|
+
requestBody: {
|
|
9649
|
+
required: true,
|
|
9650
|
+
content: { "application/json": { schema: { $ref: "#/components/schemas/UpdateConfigInput" } } }
|
|
9651
|
+
},
|
|
9652
|
+
responses: {
|
|
9653
|
+
"200": {
|
|
9654
|
+
content: {
|
|
9655
|
+
"application/json": {
|
|
9656
|
+
schema: { type: "object", properties: { config: { $ref: "#/components/schemas/Config" } } }
|
|
9657
|
+
}
|
|
9658
|
+
}
|
|
9659
|
+
}
|
|
9660
|
+
}
|
|
9661
|
+
},
|
|
9006
9662
|
delete: {
|
|
9007
9663
|
operationId: "deleteConfig",
|
|
9008
9664
|
summary: "Delete a config",
|
|
@@ -9022,29 +9678,72 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
|
|
|
9022
9678
|
get: {
|
|
9023
9679
|
operationId: "listSnapshots",
|
|
9024
9680
|
summary: "List a config's version snapshots",
|
|
9025
|
-
parameters: [
|
|
9026
|
-
|
|
9681
|
+
parameters: [
|
|
9682
|
+
{ name: "id", in: "path", required: true, schema: { type: "string" } },
|
|
9683
|
+
limitParameter,
|
|
9684
|
+
cursorParameter
|
|
9685
|
+
],
|
|
9686
|
+
responses: { "200": { content: { "application/json": { schema: { $ref: "#/components/schemas/BoundedSnapshotPage" } } } } }
|
|
9027
9687
|
},
|
|
9028
9688
|
post: {
|
|
9029
9689
|
operationId: "createSnapshot",
|
|
9030
9690
|
summary: "Snapshot a config's current content",
|
|
9691
|
+
parameters: [
|
|
9692
|
+
{ name: "id", in: "path", required: true, schema: { type: "string" } },
|
|
9693
|
+
idempotencyKeyParameter
|
|
9694
|
+
],
|
|
9695
|
+
requestBody: {
|
|
9696
|
+
required: false,
|
|
9697
|
+
content: { "application/json": { schema: { type: "object", properties: { content: { type: "string" }, version: { type: "integer" } } } } }
|
|
9698
|
+
},
|
|
9699
|
+
responses: {
|
|
9700
|
+
"201": { content: { "application/json": { schema: { type: "object", properties: { snapshot: { $ref: "#/components/schemas/ConfigSnapshot" } } } } } },
|
|
9701
|
+
"409": idempotencyConflictResponse
|
|
9702
|
+
}
|
|
9703
|
+
}
|
|
9704
|
+
},
|
|
9705
|
+
"/v1/configs/{id}/snapshots/prune": {
|
|
9706
|
+
post: {
|
|
9707
|
+
operationId: "pruneSnapshots",
|
|
9708
|
+
summary: "Prune older snapshots for a config",
|
|
9031
9709
|
parameters: [{ name: "id", in: "path", required: true, schema: { type: "string" } }],
|
|
9032
|
-
|
|
9710
|
+
requestBody: {
|
|
9711
|
+
required: false,
|
|
9712
|
+
content: { "application/json": { schema: { $ref: "#/components/schemas/PruneSnapshotsInput" } } }
|
|
9713
|
+
},
|
|
9714
|
+
responses: {
|
|
9715
|
+
"200": { content: { "application/json": { schema: { type: "object", required: ["pruned"], properties: { pruned: { type: "integer" } } } } } }
|
|
9716
|
+
}
|
|
9717
|
+
}
|
|
9718
|
+
},
|
|
9719
|
+
"/v1/configs/{id}/snapshots/{version}": {
|
|
9720
|
+
get: {
|
|
9721
|
+
operationId: "getSnapshotByVersion",
|
|
9722
|
+
summary: "Get one config snapshot by version",
|
|
9723
|
+
parameters: [
|
|
9724
|
+
{ name: "id", in: "path", required: true, schema: { type: "string" } },
|
|
9725
|
+
{ name: "version", in: "path", required: true, schema: { type: "integer" } }
|
|
9726
|
+
],
|
|
9727
|
+
responses: {
|
|
9728
|
+
"200": { content: { "application/json": { schema: { type: "object", properties: { snapshot: { $ref: "#/components/schemas/ConfigSnapshot" } } } } } }
|
|
9729
|
+
}
|
|
9033
9730
|
}
|
|
9034
9731
|
},
|
|
9035
9732
|
"/v1/profiles": {
|
|
9036
9733
|
get: {
|
|
9037
9734
|
operationId: "listProfiles",
|
|
9038
9735
|
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
|
-
],
|
|
9736
|
+
parameters: [limitParameter, cursorParameter, identityViewParameter],
|
|
9043
9737
|
responses: {
|
|
9044
9738
|
"200": {
|
|
9045
9739
|
content: {
|
|
9046
9740
|
"application/json": {
|
|
9047
|
-
schema: {
|
|
9741
|
+
schema: {
|
|
9742
|
+
oneOf: [
|
|
9743
|
+
{ $ref: "#/components/schemas/BoundedProfilePage" },
|
|
9744
|
+
{ $ref: "#/components/schemas/BoundedProfileIdentityPage" }
|
|
9745
|
+
]
|
|
9746
|
+
}
|
|
9048
9747
|
}
|
|
9049
9748
|
}
|
|
9050
9749
|
}
|
|
@@ -9053,11 +9752,15 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
|
|
|
9053
9752
|
post: {
|
|
9054
9753
|
operationId: "createProfile",
|
|
9055
9754
|
summary: "Create a profile",
|
|
9755
|
+
parameters: [idempotencyKeyParameter],
|
|
9056
9756
|
requestBody: {
|
|
9057
9757
|
required: true,
|
|
9058
9758
|
content: { "application/json": { schema: { $ref: "#/components/schemas/CreateProfileInput" } } }
|
|
9059
9759
|
},
|
|
9060
|
-
responses: {
|
|
9760
|
+
responses: {
|
|
9761
|
+
"201": { content: { "application/json": { schema: { type: "object", properties: { profile: { $ref: "#/components/schemas/Profile" } } } } } },
|
|
9762
|
+
"409": idempotencyConflictResponse
|
|
9763
|
+
}
|
|
9061
9764
|
}
|
|
9062
9765
|
},
|
|
9063
9766
|
"/v1/profiles/resolve": {
|
|
@@ -9092,6 +9795,31 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
|
|
|
9092
9795
|
],
|
|
9093
9796
|
responses: { "200": { content: { "application/json": { schema: { $ref: "#/components/schemas/ProfileShowResponse" } } } } }
|
|
9094
9797
|
},
|
|
9798
|
+
patch: {
|
|
9799
|
+
operationId: "updateProfile",
|
|
9800
|
+
summary: "Update a profile",
|
|
9801
|
+
parameters: [{ name: "id", in: "path", required: true, schema: { type: "string" } }],
|
|
9802
|
+
requestBody: {
|
|
9803
|
+
required: true,
|
|
9804
|
+
content: { "application/json": { schema: { $ref: "#/components/schemas/UpdateProfileInput" } } }
|
|
9805
|
+
},
|
|
9806
|
+
responses: {
|
|
9807
|
+
"200": { content: { "application/json": { schema: { type: "object", properties: { profile: { $ref: "#/components/schemas/Profile" } } } } } }
|
|
9808
|
+
}
|
|
9809
|
+
},
|
|
9810
|
+
put: {
|
|
9811
|
+
operationId: "putProfile",
|
|
9812
|
+
summary: "Update a profile via PUT",
|
|
9813
|
+
description: "Compatibility update route. Like PATCH, omitted properties remain unchanged.",
|
|
9814
|
+
parameters: [{ name: "id", in: "path", required: true, schema: { type: "string" } }],
|
|
9815
|
+
requestBody: {
|
|
9816
|
+
required: true,
|
|
9817
|
+
content: { "application/json": { schema: { $ref: "#/components/schemas/UpdateProfileInput" } } }
|
|
9818
|
+
},
|
|
9819
|
+
responses: {
|
|
9820
|
+
"200": { content: { "application/json": { schema: { type: "object", properties: { profile: { $ref: "#/components/schemas/Profile" } } } } } }
|
|
9821
|
+
}
|
|
9822
|
+
},
|
|
9095
9823
|
delete: {
|
|
9096
9824
|
operationId: "deleteProfile",
|
|
9097
9825
|
summary: "Delete a profile",
|
|
@@ -9106,7 +9834,8 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
|
|
|
9106
9834
|
description: "Requires an API key with the `instructions:write` scope.",
|
|
9107
9835
|
security: [{ apiKey: [] }],
|
|
9108
9836
|
parameters: [
|
|
9109
|
-
{ name: "id", in: "path", required: true, schema: { type: "string" } }
|
|
9837
|
+
{ name: "id", in: "path", required: true, schema: { type: "string" } },
|
|
9838
|
+
idempotencyKeyParameter
|
|
9110
9839
|
],
|
|
9111
9840
|
requestBody: {
|
|
9112
9841
|
required: true,
|
|
@@ -9123,7 +9852,8 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
|
|
|
9123
9852
|
schema: { $ref: "#/components/schemas/ProfileConfigAddedResponse" }
|
|
9124
9853
|
}
|
|
9125
9854
|
}
|
|
9126
|
-
}
|
|
9855
|
+
},
|
|
9856
|
+
"409": idempotencyConflictResponse
|
|
9127
9857
|
}
|
|
9128
9858
|
}
|
|
9129
9859
|
},
|
|
@@ -9134,13 +9864,17 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
|
|
|
9134
9864
|
security: [{ apiKey: [] }],
|
|
9135
9865
|
parameters: [
|
|
9136
9866
|
{ name: "id", in: "path", required: true, schema: { type: "string" } },
|
|
9137
|
-
{ name: "configId", in: "path", required: true, schema: { type: "string" } }
|
|
9867
|
+
{ name: "configId", in: "path", required: true, schema: { type: "string" } },
|
|
9868
|
+
idempotencyKeyParameter
|
|
9138
9869
|
],
|
|
9139
9870
|
requestBody: {
|
|
9140
9871
|
required: true,
|
|
9141
9872
|
content: { "application/json": { schema: { type: "object", required: ["binding"], properties: { binding: { $ref: "#/components/schemas/ProfileConfigBindingSpec" } } } } }
|
|
9142
9873
|
},
|
|
9143
|
-
responses: {
|
|
9874
|
+
responses: {
|
|
9875
|
+
"200": { content: { "application/json": { schema: { type: "object", properties: { binding: { $ref: "#/components/schemas/ProfileConfigBinding" } } } } } },
|
|
9876
|
+
"409": idempotencyConflictResponse
|
|
9877
|
+
}
|
|
9144
9878
|
},
|
|
9145
9879
|
delete: {
|
|
9146
9880
|
operationId: "removeConfigFromProfile",
|
|
@@ -9166,24 +9900,38 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
|
|
|
9166
9900
|
get: {
|
|
9167
9901
|
operationId: "getProfileConfigBindings",
|
|
9168
9902
|
summary: "List schema-versioned config bindings for a profile",
|
|
9169
|
-
parameters: [
|
|
9170
|
-
|
|
9903
|
+
parameters: [
|
|
9904
|
+
{ name: "id", in: "path", required: true, schema: { type: "string" } },
|
|
9905
|
+
limitParameter,
|
|
9906
|
+
cursorParameter
|
|
9907
|
+
],
|
|
9908
|
+
responses: { "200": { content: { "application/json": { schema: { $ref: "#/components/schemas/BoundedProfileConfigBindingPage" } } } } }
|
|
9171
9909
|
}
|
|
9172
9910
|
},
|
|
9173
9911
|
"/v1/profiles/{id}/assets": {
|
|
9174
9912
|
get: {
|
|
9175
9913
|
operationId: "getProfileAssetBindings",
|
|
9176
9914
|
summary: "List typed asset bindings for a profile",
|
|
9177
|
-
parameters: [
|
|
9178
|
-
|
|
9915
|
+
parameters: [
|
|
9916
|
+
{ name: "id", in: "path", required: true, schema: { type: "string" } },
|
|
9917
|
+
limitParameter,
|
|
9918
|
+
cursorParameter
|
|
9919
|
+
],
|
|
9920
|
+
responses: { "200": { content: { "application/json": { schema: { $ref: "#/components/schemas/BoundedProfileAssetBindingPage" } } } } }
|
|
9179
9921
|
},
|
|
9180
9922
|
post: {
|
|
9181
9923
|
operationId: "addAssetToProfile",
|
|
9182
9924
|
summary: "Add a content-addressed asset binding to a profile",
|
|
9183
9925
|
security: [{ apiKey: [] }],
|
|
9184
|
-
parameters: [
|
|
9926
|
+
parameters: [
|
|
9927
|
+
{ name: "id", in: "path", required: true, schema: { type: "string" } },
|
|
9928
|
+
idempotencyKeyParameter
|
|
9929
|
+
],
|
|
9185
9930
|
requestBody: { required: true, content: { "application/json": { schema: { $ref: "#/components/schemas/AddProfileAssetInput" } } } },
|
|
9186
|
-
responses: {
|
|
9931
|
+
responses: {
|
|
9932
|
+
"201": { content: { "application/json": { schema: { type: "object", properties: { asset: { $ref: "#/components/schemas/ProfileAssetBinding" } } } } } },
|
|
9933
|
+
"409": idempotencyConflictResponse
|
|
9934
|
+
}
|
|
9187
9935
|
}
|
|
9188
9936
|
},
|
|
9189
9937
|
"/v1/profiles/{id}/assets/{assetKey}": {
|
|
@@ -9193,10 +9941,14 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
|
|
|
9193
9941
|
security: [{ apiKey: [] }],
|
|
9194
9942
|
parameters: [
|
|
9195
9943
|
{ name: "id", in: "path", required: true, schema: { type: "string" } },
|
|
9196
|
-
{ name: "assetKey", in: "path", required: true, schema: { type: "string" } }
|
|
9944
|
+
{ name: "assetKey", in: "path", required: true, schema: { type: "string" } },
|
|
9945
|
+
idempotencyKeyParameter
|
|
9197
9946
|
],
|
|
9198
9947
|
requestBody: { required: true, content: { "application/json": { schema: { type: "object", required: ["binding"], properties: { binding: { $ref: "#/components/schemas/ProfileAssetBindingSpec" } } } } } },
|
|
9199
|
-
responses: {
|
|
9948
|
+
responses: {
|
|
9949
|
+
"200": { content: { "application/json": { schema: { type: "object", properties: { asset: { $ref: "#/components/schemas/ProfileAssetBinding" } } } } } },
|
|
9950
|
+
"409": idempotencyConflictResponse
|
|
9951
|
+
}
|
|
9200
9952
|
},
|
|
9201
9953
|
delete: {
|
|
9202
9954
|
operationId: "removeAssetFromProfile",
|
|
@@ -9209,6 +9961,77 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
|
|
|
9209
9961
|
responses: { "200": { content: { "application/json": { schema: { type: "object", properties: { removed: { type: "boolean", const: true } } } } } } }
|
|
9210
9962
|
}
|
|
9211
9963
|
},
|
|
9964
|
+
"/v1/snapshots/{id}": {
|
|
9965
|
+
get: {
|
|
9966
|
+
operationId: "getSnapshot",
|
|
9967
|
+
summary: "Get a snapshot by id",
|
|
9968
|
+
parameters: [{ name: "id", in: "path", required: true, schema: { type: "string" } }],
|
|
9969
|
+
responses: {
|
|
9970
|
+
"200": { content: { "application/json": { schema: { type: "object", properties: { snapshot: { $ref: "#/components/schemas/ConfigSnapshot" } } } } } }
|
|
9971
|
+
}
|
|
9972
|
+
}
|
|
9973
|
+
},
|
|
9974
|
+
"/v1/machines/applied": {
|
|
9975
|
+
post: {
|
|
9976
|
+
operationId: "markMachineApplied",
|
|
9977
|
+
summary: "Mark a machine as having applied its resolved instructions",
|
|
9978
|
+
requestBody: {
|
|
9979
|
+
required: true,
|
|
9980
|
+
content: { "application/json": { schema: { $ref: "#/components/schemas/MachineAppliedInput" } } }
|
|
9981
|
+
},
|
|
9982
|
+
responses: {
|
|
9983
|
+
"200": { content: { "application/json": { schema: { type: "object", required: ["updated"], properties: { updated: { type: "boolean", const: true } } } } } }
|
|
9984
|
+
}
|
|
9985
|
+
}
|
|
9986
|
+
},
|
|
9987
|
+
"/v1/machines": {
|
|
9988
|
+
get: {
|
|
9989
|
+
operationId: "listMachines",
|
|
9990
|
+
summary: "List registered machines with producer-side bounds",
|
|
9991
|
+
parameters: [limitParameter, cursorParameter, identityViewParameter],
|
|
9992
|
+
responses: {
|
|
9993
|
+
"200": { content: { "application/json": { schema: { $ref: "#/components/schemas/BoundedMachinePage" } } } }
|
|
9994
|
+
}
|
|
9995
|
+
},
|
|
9996
|
+
post: {
|
|
9997
|
+
operationId: "registerMachine",
|
|
9998
|
+
summary: "Register or refresh a machine",
|
|
9999
|
+
parameters: [idempotencyKeyParameter],
|
|
10000
|
+
requestBody: {
|
|
10001
|
+
required: true,
|
|
10002
|
+
content: {
|
|
10003
|
+
"application/json": {
|
|
10004
|
+
schema: {
|
|
10005
|
+
type: "object",
|
|
10006
|
+
required: ["hostname"],
|
|
10007
|
+
properties: {
|
|
10008
|
+
hostname: { type: "string" },
|
|
10009
|
+
os: { type: "string", nullable: true },
|
|
10010
|
+
arch: { type: "string", nullable: true }
|
|
10011
|
+
}
|
|
10012
|
+
}
|
|
10013
|
+
}
|
|
10014
|
+
}
|
|
10015
|
+
},
|
|
10016
|
+
responses: {
|
|
10017
|
+
"201": { content: { "application/json": { schema: { type: "object", properties: { machine: { $ref: "#/components/schemas/Machine" } } } } } },
|
|
10018
|
+
"409": idempotencyConflictResponse
|
|
10019
|
+
}
|
|
10020
|
+
}
|
|
10021
|
+
},
|
|
10022
|
+
"/v1/feedback": {
|
|
10023
|
+
post: {
|
|
10024
|
+
operationId: "createFeedback",
|
|
10025
|
+
summary: "Submit Instructions feedback",
|
|
10026
|
+
requestBody: {
|
|
10027
|
+
required: true,
|
|
10028
|
+
content: { "application/json": { schema: { $ref: "#/components/schemas/FeedbackInput" } } }
|
|
10029
|
+
},
|
|
10030
|
+
responses: {
|
|
10031
|
+
"201": { content: { "application/json": { schema: { type: "object", required: ["ok"], properties: { ok: { type: "boolean", const: true } } } } } }
|
|
10032
|
+
}
|
|
10033
|
+
}
|
|
10034
|
+
},
|
|
9212
10035
|
"/v1/stats": {
|
|
9213
10036
|
get: {
|
|
9214
10037
|
operationId: "getStats",
|
|
@@ -9241,7 +10064,7 @@ if (process.argv.includes("--version") || process.argv.includes("-V")) {
|
|
|
9241
10064
|
if (process.argv.includes("--help") || process.argv.includes("-h")) {
|
|
9242
10065
|
console.log(`Usage: instructions-serve [options]
|
|
9243
10066
|
|
|
9244
|
-
HTTP API server for @hasna/instructions (
|
|
10067
|
+
HTTP API server for @hasna/instructions (PostgreSQL is required for /v1).
|
|
9245
10068
|
|
|
9246
10069
|
Options:
|
|
9247
10070
|
-V, --version output the version number
|
|
@@ -9252,26 +10075,28 @@ Environment:
|
|
|
9252
10075
|
PORT / INSTRUCTIONS_PORT HTTP port (default: 3457)
|
|
9253
10076
|
HOST / INSTRUCTIONS_HOST bind host (default: localhost)
|
|
9254
10077
|
HASNA_INSTRUCTIONS_DATABASE_URL / INSTRUCTIONS_DATABASE_URL / DATABASE_URL
|
|
9255
|
-
|
|
10078
|
+
PostgreSQL backend URL (required for /v1)`);
|
|
9256
10079
|
process.exit(0);
|
|
9257
10080
|
}
|
|
9258
10081
|
var PORT = Number(process.env["PORT"] ?? process.env["INSTRUCTIONS_PORT"] ?? 3457);
|
|
9259
10082
|
var app = new Hono2;
|
|
9260
10083
|
app.use("*", cors());
|
|
9261
10084
|
function serviceBackend() {
|
|
9262
|
-
return isPostgresBackendEnabled() ? "postgresql" : "
|
|
10085
|
+
return isPostgresBackendEnabled() ? "postgresql" : "unconfigured";
|
|
9263
10086
|
}
|
|
9264
10087
|
app.get("/health", (c) => c.json({ status: "ok", version: getPackageVersion(), backend: serviceBackend(), name: "instructions" }));
|
|
9265
10088
|
app.get("/version", (c) => c.json({ status: "ok", version: getPackageVersion(), backend: serviceBackend(), name: "instructions" }));
|
|
9266
10089
|
app.get("/ready", async (c) => {
|
|
9267
10090
|
const version = getPackageVersion();
|
|
9268
10091
|
const backend2 = serviceBackend();
|
|
9269
|
-
if (backend2
|
|
9270
|
-
|
|
9271
|
-
|
|
9272
|
-
|
|
9273
|
-
|
|
9274
|
-
|
|
10092
|
+
if (backend2 !== "postgresql") {
|
|
10093
|
+
return c.json({ status: "unavailable", version, backend: backend2, code: "SERVER_BACKEND_UNCONFIGURED" }, 503);
|
|
10094
|
+
}
|
|
10095
|
+
try {
|
|
10096
|
+
await pingCloud();
|
|
10097
|
+
} catch {
|
|
10098
|
+
console.error("instructions ready: database unavailable");
|
|
10099
|
+
return c.json({ status: "unavailable", version, backend: backend2, code: "DATABASE_UNAVAILABLE" }, 503);
|
|
9275
10100
|
}
|
|
9276
10101
|
return c.json({ status: "ready", version, backend: backend2 });
|
|
9277
10102
|
});
|
|
@@ -9282,18 +10107,27 @@ app.use("/v1/*", async (c, next) => {
|
|
|
9282
10107
|
let mw;
|
|
9283
10108
|
try {
|
|
9284
10109
|
mw = getHonoAuthMiddleware([isWrite ? "instructions:write" : "instructions:read"]);
|
|
9285
|
-
} catch
|
|
9286
|
-
return c.json({ error:
|
|
10110
|
+
} catch {
|
|
10111
|
+
return c.json({ error: "Instructions API is unavailable", code: "API_AUTH_UNCONFIGURED" }, 503);
|
|
9287
10112
|
}
|
|
9288
10113
|
return mw(c, next);
|
|
9289
10114
|
});
|
|
9290
10115
|
app.all("/v1/*", async (c) => {
|
|
9291
|
-
const res = await handleV1Request(c.req.raw, new URL(c.req.url)
|
|
10116
|
+
const res = await handleV1Request(c.req.raw, new URL(c.req.url), {
|
|
10117
|
+
principal: c.get("apiKey")
|
|
10118
|
+
});
|
|
9292
10119
|
return res ?? c.json({ error: "Not found" }, 404);
|
|
9293
10120
|
});
|
|
9294
10121
|
var HOST = process.env["HOST"] ?? process.env["INSTRUCTIONS_HOST"] ?? "localhost";
|
|
9295
|
-
|
|
9296
|
-
var
|
|
10122
|
+
var serverOptions = { port: PORT, hostname: HOST, fetch: app.fetch };
|
|
10123
|
+
var server;
|
|
10124
|
+
if (import.meta.main) {
|
|
10125
|
+
console.log(`instructions-serve listening on http://${HOST}:${PORT} (backend: ${serviceBackend()})`);
|
|
10126
|
+
server = Bun.serve(serverOptions);
|
|
10127
|
+
}
|
|
9297
10128
|
export {
|
|
9298
|
-
|
|
10129
|
+
serviceBackend,
|
|
10130
|
+
serverOptions,
|
|
10131
|
+
server,
|
|
10132
|
+
app
|
|
9299
10133
|
};
|