refactor(authority): complete v0.6-only sql/blob/jobs rollout

This commit is contained in:
Youzini-afk
2026-04-28 21:45:21 +08:00
parent a5b822682a
commit a7e2edac88
23 changed files with 1816 additions and 212 deletions

View File

@@ -1,6 +1,7 @@
import { normalizeAuthorityBaseUrl } from "../runtime/authority-capabilities.js";
import { AuthorityHttpClient } from "../runtime/authority-http-client.js";
export const AUTHORITY_BLOB_ENDPOINT = "/v1/blob";
export const AUTHORITY_BLOB_ENDPOINT = "/fs/private";
function toPlainData(value, fallbackValue = null) {
if (value == null) return fallbackValue;
@@ -97,7 +98,8 @@ function normalizeBlobRecordSource(input = null) {
export function normalizeAuthorityBlobReadResult(input = null, fallbackPath = "") {
const source = normalizeBlobRecordSource(input);
const path = normalizeAuthorityBlobPath(source.path || source.name || fallbackPath);
const entry = source.entry && typeof source.entry === "object" ? source.entry : null;
const path = normalizeAuthorityBlobPath(entry?.path || source.path || source.name || fallbackPath);
const missing =
source.exists === false ||
source.found === false ||
@@ -118,21 +120,22 @@ export function normalizeAuthorityBlobReadResult(input = null, fallbackPath = ""
payload: normalizeBlobPayload(input),
contentType: String(source.contentType || source.type || "application/json"),
etag: String(source.etag || source.hash || ""),
updatedAt: source.updatedAt || source.updated_at || source.lastModified || "",
updatedAt: source.updatedAt || source.updated_at || source.lastModified || entry?.updatedAt || "",
raw: toPlainData(input, input),
};
}
export function normalizeAuthorityBlobWriteResult(input = null, fallbackPath = "") {
const source = normalizeBlobRecordSource(input);
const path = normalizeAuthorityBlobPath(source.path || source.name || fallbackPath);
const entry = source.entry && typeof source.entry === "object" ? source.entry : null;
const path = normalizeAuthorityBlobPath(entry?.path || source.path || source.name || fallbackPath);
return {
ok: input == null ? true : source.ok !== false && source.error == null,
path,
url: String(source.url || source.href || ""),
size: normalizeInteger(source.size || source.bytes, 0, 0),
size: normalizeInteger(source.size || source.bytes || entry?.sizeBytes, 0, 0),
etag: String(source.etag || source.hash || ""),
updatedAt: source.updatedAt || source.updated_at || source.lastModified || "",
updatedAt: source.updatedAt || source.updated_at || source.lastModified || entry?.updatedAt || "",
raw: toPlainData(input, input),
};
}
@@ -170,49 +173,56 @@ export function normalizeAuthorityBlobConfig(settings = {}, overrides = {}) {
export class AuthorityBlobHttpClient {
constructor(options = {}) {
this.baseUrl = normalizeAuthorityBaseUrl(options.baseUrl);
this.fetchImpl = options.fetchImpl || (typeof fetch === "function" ? fetch.bind(globalThis) : null);
this.headerProvider = typeof options.headerProvider === "function" ? options.headerProvider : null;
this.http = new AuthorityHttpClient({
...options,
baseUrl: normalizeAuthorityBaseUrl(options.baseUrl),
});
}
async request(action, payload = {}) {
if (typeof this.fetchImpl !== "function") {
throw new Error("Authority Blob fetch unavailable");
}
const response = await this.fetchImpl(`${this.baseUrl}${AUTHORITY_BLOB_ENDPOINT}`, {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
...(this.headerProvider ? this.headerProvider() || {} : {}),
},
body: JSON.stringify({ action, ...payload }),
async request(path, payload = {}, options = {}) {
return await this.http.requestJson(path, {
method: options.method || "POST",
body: payload,
session: true,
signal: options.signal,
});
if (!response?.ok) {
const text = await response?.text?.().catch(() => "");
throw new Error(text || `Authority Blob HTTP ${response?.status || "unknown"}`);
}
return await response.json().catch(() => ({}));
}
async writeJson(payload = {}) {
return await this.request("writeJson", payload);
return await this.writeText({
...payload,
contentType: payload.contentType || "application/json",
text: JSON.stringify(toPlainData(payload.payload ?? payload.data, payload.payload ?? payload.data)),
});
}
async writeText(payload = {}) {
return await this.request("writeText", payload);
return await this.request(`${AUTHORITY_BLOB_ENDPOINT}/write-file`, {
path: normalizeAuthorityBlobPath(payload.path || payload.name),
content: String(payload.text ?? payload.data ?? payload.content ?? ""),
encoding: "utf8",
createParents: true,
}, { signal: payload.signal });
}
async readJson(payload = {}) {
return await this.request("readJson", payload);
return await this.request(`${AUTHORITY_BLOB_ENDPOINT}/read-file`, {
path: normalizeAuthorityBlobPath(payload.path || payload.name),
encoding: "utf8",
}, { signal: payload.signal });
}
async delete(payload = {}) {
return await this.request("delete", payload);
return await this.request(`${AUTHORITY_BLOB_ENDPOINT}/delete`, {
path: normalizeAuthorityBlobPath(payload.path || payload.name),
recursive: false,
}, { signal: payload.signal });
}
async stat(payload = {}) {
return await this.request("stat", payload);
return await this.request(`${AUTHORITY_BLOB_ENDPOINT}/stat`, {
path: normalizeAuthorityBlobPath(payload.path || payload.name),
}, { signal: payload.signal });
}
}
@@ -249,6 +259,10 @@ function throwIfAborted(signal) {
}
}
function isMissingAuthorityBlobError(error) {
return Number(error?.status || 0) === 404;
}
export class AuthorityBlobAdapter {
constructor(config = {}, options = {}) {
this.config = normalizeAuthorityBlobConfig(config, options.configOverrides || {});
@@ -291,36 +305,57 @@ export class AuthorityBlobAdapter {
throwIfAborted(options.signal);
const normalizedPath = normalizeAuthorityBlobPath(path);
if (!normalizedPath) return normalizeAuthorityBlobReadResult({ exists: false }, "");
const result = await callClient(this.client, ["readJson", "getJson", "readFile", "get"], "readJson", {
namespace: options.namespace || this.config.namespace,
path: normalizedPath,
name: normalizedPath,
});
return normalizeAuthorityBlobReadResult(result, normalizedPath);
try {
const result = await callClient(this.client, ["readJson", "getJson", "readFile", "get"], "readJson", {
namespace: options.namespace || this.config.namespace,
path: normalizedPath,
name: normalizedPath,
});
return normalizeAuthorityBlobReadResult(result, normalizedPath);
} catch (error) {
if (isMissingAuthorityBlobError(error)) {
return normalizeAuthorityBlobReadResult({ exists: false, status: 404 }, normalizedPath);
}
throw error;
}
}
async delete(path, options = {}) {
throwIfAborted(options.signal);
const normalizedPath = normalizeAuthorityBlobPath(path);
if (!normalizedPath) return normalizeAuthorityBlobDeleteResult({ exists: false }, "");
const result = await callClient(this.client, ["delete", "deleteFile", "remove", "unlink"], "delete", {
namespace: options.namespace || this.config.namespace,
path: normalizedPath,
name: normalizedPath,
});
return normalizeAuthorityBlobDeleteResult(result, normalizedPath);
try {
const result = await callClient(this.client, ["delete", "deleteFile", "remove", "unlink"], "delete", {
namespace: options.namespace || this.config.namespace,
path: normalizedPath,
name: normalizedPath,
});
return normalizeAuthorityBlobDeleteResult(result, normalizedPath);
} catch (error) {
if (isMissingAuthorityBlobError(error)) {
return normalizeAuthorityBlobDeleteResult({ exists: false, status: 404 }, normalizedPath);
}
throw error;
}
}
async stat(path, options = {}) {
throwIfAborted(options.signal);
const normalizedPath = normalizeAuthorityBlobPath(path);
if (!normalizedPath) return normalizeAuthorityBlobReadResult({ exists: false }, "");
const result = await callClient(this.client, ["stat", "head", "metadata"], "stat", {
namespace: options.namespace || this.config.namespace,
path: normalizedPath,
name: normalizedPath,
});
return normalizeAuthorityBlobReadResult(result, normalizedPath);
try {
const result = await callClient(this.client, ["stat", "head", "metadata"], "stat", {
namespace: options.namespace || this.config.namespace,
path: normalizedPath,
name: normalizedPath,
});
return normalizeAuthorityBlobReadResult(result, normalizedPath);
} catch (error) {
if (isMissingAuthorityBlobError(error)) {
return normalizeAuthorityBlobReadResult({ exists: false, status: 404 }, normalizedPath);
}
throw error;
}
}
}

View File

@@ -1,6 +1,7 @@
import { normalizeAuthorityBaseUrl } from "../runtime/authority-capabilities.js";
import { AuthorityHttpClient } from "../runtime/authority-http-client.js";
export const AUTHORITY_JOB_ENDPOINT = "/v1/jobs";
export const AUTHORITY_JOB_ENDPOINT = "/jobs";
export const AUTHORITY_JOB_STATUS_TERMINAL = new Set([
"completed",
"succeeded",
@@ -158,8 +159,8 @@ export function normalizeAuthorityJobList(payload = null) {
const jobs = readJobRows(payload).map(normalizeAuthorityJobRecord).filter((job) => job.id);
return {
jobs,
nextCursor: String(source.nextCursor || source.next_cursor || source.cursor?.next || ""),
hasMore: Boolean(source.hasMore || source.has_more || source.cursor?.hasMore),
nextCursor: String(source.nextCursor || source.next_cursor || source.page?.nextCursor || source.cursor?.next || ""),
hasMore: Boolean(source.hasMore || source.has_more || source.page?.hasMore || source.cursor?.hasMore),
raw: toPlainData(payload, payload),
};
}
@@ -251,49 +252,68 @@ export function normalizeAuthorityJobConfig(settings = {}, overrides = {}) {
export class AuthorityJobHttpClient {
constructor(options = {}) {
this.baseUrl = normalizeAuthorityBaseUrl(options.baseUrl);
this.fetchImpl = options.fetchImpl || (typeof fetch === "function" ? fetch.bind(globalThis) : null);
this.headerProvider = typeof options.headerProvider === "function" ? options.headerProvider : null;
this.http = new AuthorityHttpClient({
...options,
baseUrl: normalizeAuthorityBaseUrl(options.baseUrl),
});
}
async request(action, payload = {}) {
if (typeof this.fetchImpl !== "function") {
throw new Error("Authority Jobs fetch unavailable");
}
const response = await this.fetchImpl(`${this.baseUrl}${AUTHORITY_JOB_ENDPOINT}`, {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
...(this.headerProvider ? this.headerProvider() || {} : {}),
},
body: JSON.stringify({ action, ...payload }),
async request(path, payload = {}, options = {}) {
return await this.http.requestJson(path, {
method: options.method || "POST",
body: payload,
session: true,
signal: options.signal,
});
if (!response?.ok) {
const text = await response?.text?.().catch(() => "");
throw new Error(text || `Authority Jobs HTTP ${response?.status || "unknown"}`);
}
return await response.json().catch(() => ({}));
}
async submit(payload = {}) {
return await this.request("submit", payload);
return await this.request(`${AUTHORITY_JOB_ENDPOINT}/create`, {
type: String(payload.type || payload.kind || "").trim(),
payload: toPlainData(payload.payload, payload.payload),
...(payload.timeoutMs != null ? { timeoutMs: normalizeInteger(payload.timeoutMs, 0, 0, 3600000) } : {}),
...(payload.idempotencyKey ? { idempotencyKey: String(payload.idempotencyKey) } : {}),
...(payload.maxAttempts != null ? { maxAttempts: normalizeInteger(payload.maxAttempts, 1, 1, 1000) } : {}),
});
}
async listPage(payload = {}) {
return await this.request("listPage", payload);
return await this.request(`${AUTHORITY_JOB_ENDPOINT}/list`, {
page: {
...(payload.cursor ? { cursor: String(payload.cursor) } : {}),
limit: normalizeInteger(payload.limit, 20, 1, 100),
},
...(payload.filter && typeof payload.filter === "object" && !Array.isArray(payload.filter) && Object.keys(payload.filter).length > 0
? { filter: toPlainData(payload.filter, {}) }
: {}),
});
}
async waitForCompletion(payload = {}) {
return await this.request("waitForCompletion", payload);
async get(payload = {}) {
const id = encodeURIComponent(normalizeRecordId(payload.jobId || payload.id));
if (!id) {
throw new Error("Authority Jobs get requires job id");
}
return await this.request(`${AUTHORITY_JOB_ENDPOINT}/${id}`, undefined, {
method: "GET",
signal: payload.signal,
});
}
async requeue(payload = {}) {
return await this.request("requeue", payload);
const id = encodeURIComponent(normalizeRecordId(payload.jobId || payload.id));
if (!id) {
throw new Error("Authority Jobs requeue requires job id");
}
return await this.request(`${AUTHORITY_JOB_ENDPOINT}/${id}/requeue`, {}, { signal: payload.signal });
}
async cancel(payload = {}) {
return await this.request("cancel", payload);
const id = encodeURIComponent(normalizeRecordId(payload.jobId || payload.id));
if (!id) {
throw new Error("Authority Jobs cancel requires job id");
}
return await this.request(`${AUTHORITY_JOB_ENDPOINT}/${id}/cancel`, {}, { signal: payload.signal });
}
}