Files
makelore/resources/coding-skills/data-service/assets/makelore-data.js

370 lines
14 KiB
JavaScript

const runtimeGlobal = globalThis;
const COLLECTION_PATTERN = /^[a-z][a-z0-9_-]{0,47}$/;
const DOCUMENT_ID_PATTERN = /^[A-Za-z0-9._~-]{1,128}$/;
const MAX_CURSOR_LENGTH = 1024;
const MAX_ERROR_MESSAGE_LENGTH = 256;
const MAX_RETRY_AFTER_SECONDS = 86400;
const ERROR_STATUS = {
runtime_unavailable: 503,
upstream_invalid_response: 502,
invalid_request: [400, 422],
invalid_content_type: 415,
invalid_revision: 422,
invalid_cursor: 400,
cursor_expired: 410,
invalid_collection_name: 422,
invalid_document_id: 422,
invalid_document_data: 422,
invalid_project_id: 422,
authentication_required: 401,
origin_not_allowed: 403,
method_not_allowed: 405,
route_not_found: 404,
instance_not_found: 404,
collection_not_found: 404,
document_not_found: 404,
revision_conflict: 409,
document_too_large: 413,
quota_exceeded: 409,
rate_limited: 429,
data_service_unavailable: 503,
request_too_large: 413
};
const ERROR_MESSAGE = {
runtime_unavailable: "Preview data runtime is unavailable",
upstream_invalid_response: "Data Service returned an invalid response",
invalid_request: "Data Service request is invalid",
invalid_content_type: "Data Service request content type is invalid",
invalid_revision: "Data Service document revision is invalid",
invalid_cursor: "Data Service cursor is invalid",
cursor_expired: "Data Service cursor has expired",
invalid_collection_name: "Data Service collection is invalid",
invalid_document_id: "Data Service document ID is invalid",
invalid_document_data: "Data Service document data is invalid",
invalid_project_id: "Data Service project ID is invalid",
authentication_required: "Preview data authorization is required",
origin_not_allowed: "Preview data Origin is not allowed",
method_not_allowed: "Preview data method is not allowed",
route_not_found: "Preview data route was not found",
instance_not_found: "Data Service project instance was not found",
collection_not_found: "Data Service collection was not found",
document_not_found: "Data Service document was not found",
revision_conflict: "Data Service document revision conflicts",
document_too_large: "Data Service document is too large",
quota_exceeded: "Data Service quota exceeded",
rate_limited: "Preview data request rate limit exceeded",
data_service_unavailable: "Data Service is temporarily unavailable",
request_too_large: "Preview data request is too large"
};
const CONTEXT_KEYS = /* @__PURE__ */ new Set([
"resource",
"limit",
"current",
"attempted",
"actual",
"allowed",
"current_revision",
"retry_after_seconds"
]);
const CONTEXT_RESOURCES = /* @__PURE__ */ new Set(["instances", "collections", "documents", "bytes"]);
function isRecord(value) {
return typeof value === "object" && value !== null && !Array.isArray(value);
}
function positiveInteger(value) {
return Number.isSafeInteger(value) && value > 0;
}
function boundedText(value, maximum) {
return typeof value === "string" && value.length > 0 && value.length <= maximum;
}
function boundedTimestamp(value) {
return boundedText(value, 64) && /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,6})?Z$/.test(value) && Number.isFinite(Date.parse(value));
}
function hasOnlyKeys(value, required, optional = []) {
const allowed = /* @__PURE__ */ new Set([...required, ...optional]);
return required.every((key) => Object.prototype.hasOwnProperty.call(value, key)) && Object.keys(value).every((key) => allowed.has(key));
}
function statusMatches(code, status) {
const expected = ERROR_STATUS[code];
return Array.isArray(expected) ? expected.includes(status) : expected === status;
}
function frozenContext(value) {
return value === void 0 ? void 0 : Object.freeze({ ...value });
}
class DataServiceError extends Error {
code;
status;
retryable;
retryAfterSeconds;
context;
constructor(code, status, retryable, retryAfterSeconds, context) {
super(ERROR_MESSAGE[code]);
Object.defineProperty(this, "name", { value: "DataServiceError", enumerable: false });
this.code = code;
this.status = status;
this.retryable = retryable;
if (retryAfterSeconds !== void 0) this.retryAfterSeconds = retryAfterSeconds;
const safeContext = frozenContext(context);
if (safeContext !== void 0) this.context = safeContext;
Object.freeze(this);
}
}
function runtimeUnavailable() {
return new DataServiceError("runtime_unavailable", 503, true);
}
function invalidRequest() {
return new DataServiceError("invalid_request", 422, false);
}
function upstreamInvalidResponse() {
return new DataServiceError("upstream_invalid_response", 502, false);
}
function readRuntime() {
const candidate = globalThis.__MAKELORE_DATA__;
if (!isRecord(candidate) || candidate.contractVersion !== 1 || !boundedText(candidate.token, 256) || !/^[A-Za-z0-9_-]+$/.test(candidate.token) || !boundedText(candidate.endpoint, 256)) {
throw runtimeUnavailable();
}
let endpoint;
try {
endpoint = new URL(candidate.endpoint);
} catch {
throw runtimeUnavailable();
}
if (endpoint.protocol !== "http:" || endpoint.hostname !== "127.0.0.1" || !endpoint.port || endpoint.username || endpoint.password || endpoint.pathname !== "/api/runtime/data/v1" || endpoint.search || endpoint.hash) {
throw runtimeUnavailable();
}
return {
endpoint: `${endpoint.origin}${endpoint.pathname}`,
token: candidate.token,
contractVersion: 1
};
}
function requireCollection(value) {
if (typeof value !== "string" || !COLLECTION_PATTERN.test(value)) throw invalidRequest();
return value;
}
function requireDocumentId(value) {
if (typeof value !== "string" || value === "." || value === ".." || !DOCUMENT_ID_PATTERN.test(value)) {
throw invalidRequest();
}
return value;
}
function requireData(value) {
if (!isRecord(value)) throw invalidRequest();
try {
if (JSON.stringify(value) === void 0) throw new Error("data is not JSON");
} catch {
throw invalidRequest();
}
return value;
}
function requireOptions(value, allowed) {
if (value === void 0) return {};
if (!isRecord(value) || Object.keys(value).some((key) => !allowed.includes(key))) throw invalidRequest();
return value;
}
function readIfRevision(value) {
if (value === void 0) return void 0;
if (!positiveInteger(value)) throw invalidRequest();
return value;
}
function buildDocumentPath(endpoint, collection, documentId) {
return `${endpoint}/collections/${encodeURIComponent(collection)}/documents/${encodeURIComponent(documentId)}`;
}
function parseContext(value) {
if (value === void 0) return void 0;
if (!isRecord(value)) throw upstreamInvalidResponse();
const context = {};
for (const [key, item] of Object.entries(value)) {
if (!CONTEXT_KEYS.has(key)) throw upstreamInvalidResponse();
if (key === "resource") {
if (!boundedText(item, 16) || !CONTEXT_RESOURCES.has(item)) throw upstreamInvalidResponse();
context.resource = item;
} else {
if (!positiveInteger(item) && item !== 0) throw upstreamInvalidResponse();
context[key] = item;
}
}
return Object.keys(context).length === 0 ? void 0 : context;
}
function readRetryAfter(response) {
const raw = response.headers?.get("retry-after")?.trim();
if (!raw || !/^\d+$/.test(raw)) return void 0;
const seconds = Number(raw);
return Number.isSafeInteger(seconds) && seconds <= MAX_RETRY_AFTER_SECONDS ? seconds : void 0;
}
async function parseError(response) {
try {
const payload = await response.json();
if (!isRecord(payload) || !isRecord(payload.detail)) throw upstreamInvalidResponse();
const detail = payload.detail;
if (!hasOnlyKeys(detail, ["code", "message", "retryable"], ["context"]) || !boundedText(detail.code, 64) || !Object.prototype.hasOwnProperty.call(ERROR_STATUS, detail.code) || !boundedText(detail.message, MAX_ERROR_MESSAGE_LENGTH) || typeof detail.retryable !== "boolean" || !statusMatches(detail.code, response.status)) {
throw upstreamInvalidResponse();
}
const context = parseContext(detail.context);
return new DataServiceError(
detail.code,
response.status,
detail.retryable,
readRetryAfter(response),
context
);
} catch (error) {
if (error instanceof DataServiceError) throw error;
throw upstreamInvalidResponse();
}
}
async function parseJson(response) {
try {
return await response.json();
} catch {
throw upstreamInvalidResponse();
}
}
function parseDocument(value) {
if (!isRecord(value) || !hasOnlyKeys(value, ["id", "data", "revision", "created_at", "updated_at"]) || typeof value.id !== "string" || value.id === "." || value.id === ".." || !DOCUMENT_ID_PATTERN.test(value.id) || !isRecord(value.data) || !positiveInteger(value.revision) || !boundedTimestamp(value.created_at) || !boundedTimestamp(value.updated_at)) {
throw upstreamInvalidResponse();
}
return {
id: value.id,
data: value.data,
revision: value.revision,
created_at: value.created_at,
updated_at: value.updated_at
};
}
function parsePage(value) {
if (!isRecord(value) || !hasOnlyKeys(value, ["items", "next_cursor", "limit"]) || !Array.isArray(value.items) || value.items.length > 100 || !positiveInteger(value.limit) || value.limit > 100 || value.next_cursor !== null && !boundedText(value.next_cursor, MAX_CURSOR_LENGTH)) {
throw upstreamInvalidResponse();
}
const items = value.items.map(parseDocument);
if (new Set(items.map((item) => item.id)).size !== items.length) throw upstreamInvalidResponse();
return {
items,
next_cursor: value.next_cursor === null ? null : value.next_cursor,
limit: value.limit
};
}
async function request(method, url, expectedStatus, parse, body, ifRevision) {
const runtime = readRuntime();
const fetchImpl = runtimeGlobal.fetch;
if (typeof fetchImpl !== "function") throw runtimeUnavailable();
const headers = {
Accept: "application/json",
Authorization: `Bearer ${runtime.token}`
};
const serializedBody = body === void 0 ? void 0 : (() => {
try {
const encoded = JSON.stringify(body);
return encoded === void 0 ? null : encoded;
} catch {
return null;
}
})();
if (serializedBody === null) throw invalidRequest();
if (serializedBody !== void 0) headers["Content-Type"] = "application/json";
if (ifRevision !== void 0) headers["If-Match"] = `"${ifRevision}"`;
let response;
try {
response = await fetchImpl(url, {
method,
headers,
...serializedBody === void 0 ? {} : { body: serializedBody }
});
} catch {
throw runtimeUnavailable();
}
if (!response || !Number.isSafeInteger(response.status)) throw upstreamInvalidResponse();
if (response.status !== expectedStatus) throw await parseError(response);
if (expectedStatus === 204) return void 0;
return parse(await parseJson(response));
}
function listPath(endpoint, collection, options) {
const params = new URLSearchParams();
if (options.limit !== void 0) params.set("limit", String(options.limit));
if (options.cursor !== void 0) params.set("cursor", options.cursor);
const query = params.toString();
return `${endpoint}/collections/${encodeURIComponent(collection)}/documents${query ? `?${query}` : ""}`;
}
async function getDocument(collection, documentId) {
const runtime = readRuntime();
const safeCollection = requireCollection(collection);
const safeDocumentId = requireDocumentId(documentId);
return await request(
"GET",
buildDocumentPath(runtime.endpoint, safeCollection, safeDocumentId),
200,
parseDocument
);
}
async function listDocuments(collection, input = {}) {
const runtime = readRuntime();
const safeCollection = requireCollection(collection);
const options = requireOptions(input, ["limit", "cursor"]);
const limit = options.limit;
if (limit !== void 0 && (!positiveInteger(limit) || limit > 100)) throw invalidRequest();
const cursor = options.cursor;
if (cursor !== void 0 && !boundedText(cursor, MAX_CURSOR_LENGTH)) throw invalidRequest();
return await request(
"GET",
listPath(runtime.endpoint, safeCollection, {
...limit === void 0 ? {} : { limit },
...cursor === void 0 ? {} : { cursor }
}),
200,
parsePage
);
}
async function putDocument(collection, documentId, value, input = {}) {
const runtime = readRuntime();
const safeCollection = requireCollection(collection);
const safeDocumentId = requireDocumentId(documentId);
const data2 = requireData(value);
const options = requireOptions(input, ["ifRevision"]);
const ifRevision = readIfRevision(options.ifRevision);
return await request(
"PUT",
buildDocumentPath(runtime.endpoint, safeCollection, safeDocumentId),
200,
parseDocument,
{ data: data2 },
ifRevision
);
}
async function deleteDocument(collection, documentId, input = {}) {
const runtime = readRuntime();
const safeCollection = requireCollection(collection);
const safeDocumentId = requireDocumentId(documentId);
const options = requireOptions(input, ["ifRevision"]);
const ifRevision = readIfRevision(options.ifRevision);
await request(
"DELETE",
buildDocumentPath(runtime.endpoint, safeCollection, safeDocumentId),
204,
() => void 0,
void 0,
ifRevision
);
}
async function addDocument(collection, value) {
readRuntime();
requireCollection(collection);
requireData(value);
const randomUuid = runtimeGlobal.crypto?.randomUUID;
if (typeof randomUuid !== "function") throw runtimeUnavailable();
const id = randomUuid();
if (!boundedText(id, 128) || !DOCUMENT_ID_PATTERN.test(id)) throw runtimeUnavailable();
return await putDocument(collection, id, value);
}
const data = Object.freeze({
get: getDocument,
list: listDocuments,
put: putDocument,
delete: deleteDocument,
add: addDocument
});
var makelore_data_default = data;
export {
DataServiceError,
data,
makelore_data_default as default
};