@grapity/grapity 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +47 -14
- package/dist/assets/index-JAhtTTW2.js +336 -0
- package/dist/assets/index-JAhtTTW2.js.map +1 -0
- package/dist/assets/index-LDlidn22.css +1 -0
- package/dist/cli/index.js +1315 -181
- package/dist/core/index.d.ts +391 -23
- package/dist/hub/index.js +34 -7
- package/dist/hub/serve.d.ts +9 -1
- package/dist/hub/serve.js +34 -7
- package/dist/index.html +3 -2
- package/dist/registry/{index-Baj_sSgl.d.ts → index-Bx-7YlUF.d.ts} +18 -11
- package/dist/registry/index.d.ts +1 -1
- package/dist/registry/index.js +152 -6
- package/dist/registry/serve.d.ts +96 -5
- package/dist/registry/serve.js +714 -21
- package/package.json +13 -15
- package/dist/assets/index-Dq5tdnlb.js +0 -326
- package/dist/assets/index-Dq5tdnlb.js.map +0 -1
- package/dist/assets/index-NJpHAonA.css +0 -1
package/dist/registry/serve.js
CHANGED
|
@@ -1635,9 +1635,9 @@ var RegistryService = class {
|
|
|
1635
1635
|
return { spec, version, compatReport, isNewSpec };
|
|
1636
1636
|
}
|
|
1637
1637
|
async listSpecs(filters) {
|
|
1638
|
-
const
|
|
1638
|
+
const specs3 = await this.store.listSpecs(filters);
|
|
1639
1639
|
return Promise.all(
|
|
1640
|
-
|
|
1640
|
+
specs3.map(async (spec) => {
|
|
1641
1641
|
const latestVersion = await this.store.getLatestVersion(spec.name);
|
|
1642
1642
|
return { ...spec, latestVersion: latestVersion ?? void 0 };
|
|
1643
1643
|
})
|
|
@@ -1756,6 +1756,7 @@ var pushRoute = new Hono().post("/", async (c) => {
|
|
|
1756
1756
|
}
|
|
1757
1757
|
const store = c.get("store");
|
|
1758
1758
|
const service = new RegistryService(store);
|
|
1759
|
+
const actor = c.get("actor") ?? body.pushedBy;
|
|
1759
1760
|
try {
|
|
1760
1761
|
const result = await service.pushSpec(body.content, body.name, {
|
|
1761
1762
|
type: body.type,
|
|
@@ -1764,7 +1765,7 @@ var pushRoute = new Hono().post("/", async (c) => {
|
|
|
1764
1765
|
sourceRepo: body.sourceRepo,
|
|
1765
1766
|
tags: Array.isArray(body.tags) ? body.tags : void 0,
|
|
1766
1767
|
gitRef: body.gitRef,
|
|
1767
|
-
pushedBy:
|
|
1768
|
+
pushedBy: actor,
|
|
1768
1769
|
prerelease: body.prerelease,
|
|
1769
1770
|
force: body.force,
|
|
1770
1771
|
reason: body.reason
|
|
@@ -1895,8 +1896,8 @@ var listRoute = new Hono3().get("/", async (c) => {
|
|
|
1895
1896
|
const type = c.req.query("type");
|
|
1896
1897
|
const owner = c.req.query("owner");
|
|
1897
1898
|
const tags = c.req.query("tags")?.split(",");
|
|
1898
|
-
const
|
|
1899
|
-
return c.json({ data:
|
|
1899
|
+
const specs3 = await service.listSpecs({ type, owner, tags });
|
|
1900
|
+
return c.json({ data: specs3 });
|
|
1900
1901
|
});
|
|
1901
1902
|
|
|
1902
1903
|
// src/registry/routes/get-spec.ts
|
|
@@ -1929,7 +1930,8 @@ var deleteSpecRoute = new Hono5().delete(
|
|
|
1929
1930
|
const name = c.req.param("name");
|
|
1930
1931
|
const store = c.get("store");
|
|
1931
1932
|
const service = new RegistryService(store);
|
|
1932
|
-
const
|
|
1933
|
+
const actor = c.get("actor");
|
|
1934
|
+
const deleted = await service.deleteSpec(name, actor);
|
|
1933
1935
|
if (!deleted) {
|
|
1934
1936
|
return c.json({ error: "not_found", message: `Spec "${name}" not found`, statusCode: 404 }, 404);
|
|
1935
1937
|
}
|
|
@@ -2530,8 +2532,7 @@ function switchTab(tab) {
|
|
|
2530
2532
|
}
|
|
2531
2533
|
var welcomeRoute = new Hono12().get("/", (c) => {
|
|
2532
2534
|
const config = c.get("config");
|
|
2533
|
-
|
|
2534
|
-
return c.html(buildPage(config.port, mode));
|
|
2535
|
+
return c.html(buildPage(config.port, "local"));
|
|
2535
2536
|
});
|
|
2536
2537
|
|
|
2537
2538
|
// src/registry/routes/push-gateway-config.ts
|
|
@@ -2671,6 +2672,7 @@ var pushGatewayConfigRoute = new Hono13().post("/", async (c) => {
|
|
|
2671
2672
|
}
|
|
2672
2673
|
const store = c.get("store");
|
|
2673
2674
|
const service = new GatewayService(store, store);
|
|
2675
|
+
const actor = c.get("actor") ?? body.pushedBy;
|
|
2674
2676
|
try {
|
|
2675
2677
|
const result = await service.pushGatewayConfig({
|
|
2676
2678
|
name: body.name,
|
|
@@ -2681,7 +2683,7 @@ var pushGatewayConfigRoute = new Hono13().post("/", async (c) => {
|
|
|
2681
2683
|
environments: body.environments ?? {},
|
|
2682
2684
|
callerIdentification: body.callerIdentification,
|
|
2683
2685
|
content: body.content,
|
|
2684
|
-
pushedBy:
|
|
2686
|
+
pushedBy: actor
|
|
2685
2687
|
});
|
|
2686
2688
|
return c.json({ data: result }, 201);
|
|
2687
2689
|
} catch (err) {
|
|
@@ -2872,7 +2874,6 @@ var ingestGatewayLogRoute = new Hono18().post("/ingest/:provider/:environment",
|
|
|
2872
2874
|
await service.ingestLog(provider, environment, payload);
|
|
2873
2875
|
return c.json({ status: "ok" }, 201);
|
|
2874
2876
|
} catch (err) {
|
|
2875
|
-
console.error("Gateway log ingest error:", err);
|
|
2876
2877
|
return c.json({
|
|
2877
2878
|
error: "bad_request",
|
|
2878
2879
|
message: err instanceof Error ? err.message : "Invalid log payload",
|
|
@@ -2947,7 +2948,137 @@ var gatewayLogStatsRoute = new Hono21().get("/stats", async (c) => {
|
|
|
2947
2948
|
});
|
|
2948
2949
|
});
|
|
2949
2950
|
|
|
2951
|
+
// src/registry/auth/middleware.ts
|
|
2952
|
+
import { createRemoteJWKSet, jwtVerify } from "jose";
|
|
2953
|
+
var AuthError = class extends Error {
|
|
2954
|
+
constructor(statusCode, code, message) {
|
|
2955
|
+
super(message);
|
|
2956
|
+
this.statusCode = statusCode;
|
|
2957
|
+
this.code = code;
|
|
2958
|
+
this.name = "AuthError";
|
|
2959
|
+
}
|
|
2960
|
+
statusCode;
|
|
2961
|
+
code;
|
|
2962
|
+
};
|
|
2963
|
+
function buildKeycloakUrls(config) {
|
|
2964
|
+
const base = `${config.serverUrl}/realms/${config.realm}`;
|
|
2965
|
+
return {
|
|
2966
|
+
issuer: base,
|
|
2967
|
+
jwksUri: `${base}/protocol/openid-connect/certs`,
|
|
2968
|
+
tokenUrl: `${base}/protocol/openid-connect/token`
|
|
2969
|
+
};
|
|
2970
|
+
}
|
|
2971
|
+
function createAuthMiddleware(config, routeScopes) {
|
|
2972
|
+
if (config.auth?.mode !== "keycloak") {
|
|
2973
|
+
return async (_c, next) => await next();
|
|
2974
|
+
}
|
|
2975
|
+
const authConfig = config.auth;
|
|
2976
|
+
const { issuer, jwksUri } = buildKeycloakUrls(authConfig);
|
|
2977
|
+
const jwks = createRemoteJWKSet(new URL(jwksUri));
|
|
2978
|
+
const scopeByRoute = /* @__PURE__ */ new Map();
|
|
2979
|
+
for (const route of routeScopes) {
|
|
2980
|
+
const key = `${route.method.toUpperCase()}:${route.path}`;
|
|
2981
|
+
scopeByRoute.set(key, {
|
|
2982
|
+
operationId: route.operationId,
|
|
2983
|
+
scopes: route.scopes
|
|
2984
|
+
});
|
|
2985
|
+
}
|
|
2986
|
+
return async (c, next) => {
|
|
2987
|
+
const matchedPath = c.req.matchedRoutes.map((r) => r.path).filter((p) => p !== "/*").pop();
|
|
2988
|
+
const routeKey = matchedPath ? `${c.req.method}:${matchedPath}` : `${c.req.method}:${c.req.routePath}`;
|
|
2989
|
+
const required = scopeByRoute.get(routeKey);
|
|
2990
|
+
if (!required || required.scopes.length === 0) {
|
|
2991
|
+
return await next();
|
|
2992
|
+
}
|
|
2993
|
+
const authHeader = c.req.header("Authorization");
|
|
2994
|
+
if (!authHeader || !authHeader.startsWith("Bearer ")) {
|
|
2995
|
+
throw new AuthError(401, "unauthorized", "Bearer token required");
|
|
2996
|
+
}
|
|
2997
|
+
const token = authHeader.slice("Bearer ".length).trim();
|
|
2998
|
+
if (!token) {
|
|
2999
|
+
throw new AuthError(401, "unauthorized", "Bearer token required");
|
|
3000
|
+
}
|
|
3001
|
+
let payload;
|
|
3002
|
+
try {
|
|
3003
|
+
const result = await jwtVerify(token, jwks, {
|
|
3004
|
+
issuer,
|
|
3005
|
+
audience: authConfig.audience
|
|
3006
|
+
});
|
|
3007
|
+
payload = result.payload;
|
|
3008
|
+
} catch (err) {
|
|
3009
|
+
const message = err instanceof Error ? err.message : "Invalid token";
|
|
3010
|
+
throw new AuthError(401, "unauthorized", `Invalid or expired token: ${message}`);
|
|
3011
|
+
}
|
|
3012
|
+
const subject = payload.sub;
|
|
3013
|
+
if (!subject) {
|
|
3014
|
+
throw new AuthError(401, "unauthorized", "Token missing subject claim");
|
|
3015
|
+
}
|
|
3016
|
+
const grantedScopes = extractScopes(payload, authConfig.roleSource ?? "scope");
|
|
3017
|
+
const missing = required.scopes.filter((s) => !grantedScopes.has(s));
|
|
3018
|
+
if (missing.length > 0) {
|
|
3019
|
+
throw new AuthError(
|
|
3020
|
+
403,
|
|
3021
|
+
"forbidden",
|
|
3022
|
+
`Missing required scope${missing.length > 1 ? "s" : ""}: ${missing.join(", ")}`
|
|
3023
|
+
);
|
|
3024
|
+
}
|
|
3025
|
+
c.set("actor", subject);
|
|
3026
|
+
c.set("claims", payload);
|
|
3027
|
+
await next();
|
|
3028
|
+
};
|
|
3029
|
+
}
|
|
3030
|
+
function extractScopes(payload, source) {
|
|
3031
|
+
if (source === "realm_access.roles") {
|
|
3032
|
+
const roles = payload.realm_access?.roles;
|
|
3033
|
+
return new Set(roles ?? []);
|
|
3034
|
+
}
|
|
3035
|
+
const scopeValue = payload.scope;
|
|
3036
|
+
if (typeof scopeValue === "string") {
|
|
3037
|
+
return new Set(scopeValue.split(/\s+/).filter(Boolean));
|
|
3038
|
+
}
|
|
3039
|
+
return /* @__PURE__ */ new Set();
|
|
3040
|
+
}
|
|
3041
|
+
function parseRouteScopes(spec) {
|
|
3042
|
+
const routes = [];
|
|
3043
|
+
const paths = spec.paths;
|
|
3044
|
+
if (!paths) return routes;
|
|
3045
|
+
for (const [path2, operations] of Object.entries(paths)) {
|
|
3046
|
+
for (const [method, operation] of Object.entries(operations)) {
|
|
3047
|
+
if (typeof operation !== "object" || operation === null) continue;
|
|
3048
|
+
const op = operation;
|
|
3049
|
+
const operationId = op.operationId;
|
|
3050
|
+
if (!operationId) continue;
|
|
3051
|
+
const security = op.security;
|
|
3052
|
+
const scopes = [];
|
|
3053
|
+
if (security) {
|
|
3054
|
+
for (const sec of security) {
|
|
3055
|
+
for (const [name, required] of Object.entries(sec)) {
|
|
3056
|
+
if (name === "keycloak" && Array.isArray(required)) {
|
|
3057
|
+
scopes.push(...required);
|
|
3058
|
+
}
|
|
3059
|
+
}
|
|
3060
|
+
}
|
|
3061
|
+
}
|
|
3062
|
+
routes.push({
|
|
3063
|
+
method: method.toUpperCase(),
|
|
3064
|
+
path: path2.replace(/\{([^}]+)\}/g, ":$1"),
|
|
3065
|
+
operationId,
|
|
3066
|
+
scopes
|
|
3067
|
+
});
|
|
3068
|
+
}
|
|
3069
|
+
}
|
|
3070
|
+
return routes;
|
|
3071
|
+
}
|
|
3072
|
+
|
|
2950
3073
|
// src/registry/server.ts
|
|
3074
|
+
import { readFileSync } from "fs";
|
|
3075
|
+
import { fileURLToPath } from "url";
|
|
3076
|
+
import yaml6 from "js-yaml";
|
|
3077
|
+
function loadOpenApiSpec() {
|
|
3078
|
+
const path2 = fileURLToPath(new URL("../../openapi.yaml", import.meta.url));
|
|
3079
|
+
const content = readFileSync(path2, "utf-8");
|
|
3080
|
+
return yaml6.load(content);
|
|
3081
|
+
}
|
|
2951
3082
|
function createApp(config, store) {
|
|
2952
3083
|
const app = new Hono22();
|
|
2953
3084
|
app.use("*", logger());
|
|
@@ -2958,6 +3089,21 @@ function createApp(config, store) {
|
|
|
2958
3089
|
c.set("config", config);
|
|
2959
3090
|
await next();
|
|
2960
3091
|
});
|
|
3092
|
+
const routeScopes = parseRouteScopes(loadOpenApiSpec());
|
|
3093
|
+
app.use("*", createAuthMiddleware(config, routeScopes));
|
|
3094
|
+
app.onError((err, c) => {
|
|
3095
|
+
if (err instanceof AuthError) {
|
|
3096
|
+
return c.json(
|
|
3097
|
+
{ error: err.code, message: err.message, statusCode: err.statusCode },
|
|
3098
|
+
err.statusCode
|
|
3099
|
+
);
|
|
3100
|
+
}
|
|
3101
|
+
console.error("Unhandled error:", err);
|
|
3102
|
+
return c.json(
|
|
3103
|
+
{ error: "internal_error", message: "Internal server error", statusCode: 500 },
|
|
3104
|
+
500
|
|
3105
|
+
);
|
|
3106
|
+
});
|
|
2961
3107
|
app.route("/v1/specs", pushRoute);
|
|
2962
3108
|
app.route("/v1/specs", validateRoute);
|
|
2963
3109
|
app.route("/v1/specs", listRoute);
|
|
@@ -2986,8 +3132,7 @@ function createApp(config, store) {
|
|
|
2986
3132
|
var defaultConfig = {
|
|
2987
3133
|
port: 3750,
|
|
2988
3134
|
database: "sqlite",
|
|
2989
|
-
|
|
2990
|
-
gracePeriodDays: 30
|
|
3135
|
+
auth: { mode: "none" }
|
|
2991
3136
|
};
|
|
2992
3137
|
|
|
2993
3138
|
// src/registry/storage/sqlite.ts
|
|
@@ -3493,27 +3638,575 @@ var SQLiteSpecStore = class {
|
|
|
3493
3638
|
}
|
|
3494
3639
|
};
|
|
3495
3640
|
|
|
3641
|
+
// src/registry/storage/postgresql.ts
|
|
3642
|
+
import { Pool } from "pg";
|
|
3643
|
+
import { drizzle as drizzle2 } from "drizzle-orm/node-postgres";
|
|
3644
|
+
import { migrate as migrate2 } from "drizzle-orm/node-postgres/migrator";
|
|
3645
|
+
import { eq as eq2, and as and2, desc as desc2, sql as sql2 } from "drizzle-orm";
|
|
3646
|
+
|
|
3647
|
+
// src/registry/storage/schema-pg.ts
|
|
3648
|
+
import { pgTable, text as text2, timestamp, boolean, jsonb, index as index2, integer as integer2 } from "drizzle-orm/pg-core";
|
|
3649
|
+
var specs2 = pgTable("specs", {
|
|
3650
|
+
id: text2("id").primaryKey(),
|
|
3651
|
+
name: text2("name").notNull().unique(),
|
|
3652
|
+
type: text2("type", { enum: ["openapi", "asyncapi"] }).notNull(),
|
|
3653
|
+
description: text2("description"),
|
|
3654
|
+
owner: text2("owner"),
|
|
3655
|
+
sourceRepo: text2("source_repo"),
|
|
3656
|
+
tags: jsonb("tags").$type().default([]),
|
|
3657
|
+
createdAt: timestamp("created_at").notNull(),
|
|
3658
|
+
updatedAt: timestamp("updated_at").notNull()
|
|
3659
|
+
});
|
|
3660
|
+
var specVersions2 = pgTable("spec_versions", {
|
|
3661
|
+
id: text2("id").primaryKey(),
|
|
3662
|
+
specId: text2("spec_id").notNull().references(() => specs2.id),
|
|
3663
|
+
semver: text2("semver").notNull(),
|
|
3664
|
+
content: text2("content").notNull(),
|
|
3665
|
+
checksum: text2("checksum").notNull(),
|
|
3666
|
+
gitRef: text2("git_ref"),
|
|
3667
|
+
pushedBy: text2("pushed_by"),
|
|
3668
|
+
compatibility: jsonb("compatibility").$type(),
|
|
3669
|
+
previousVersion: text2("previous_version"),
|
|
3670
|
+
forceReason: text2("force_reason"),
|
|
3671
|
+
isPrerelease: boolean("is_prerelease").notNull().default(false),
|
|
3672
|
+
createdAt: timestamp("created_at").notNull()
|
|
3673
|
+
}, (table) => [
|
|
3674
|
+
index2("idx_spec_versions_spec_id").on(table.specId),
|
|
3675
|
+
index2("idx_spec_versions_semver").on(table.specId, table.semver)
|
|
3676
|
+
]);
|
|
3677
|
+
var auditLog2 = pgTable("audit_log", {
|
|
3678
|
+
id: text2("id").primaryKey(),
|
|
3679
|
+
action: text2("action", { enum: ["spec.push", "spec.push.force", "spec.delete"] }).notNull(),
|
|
3680
|
+
actor: text2("actor").notNull(),
|
|
3681
|
+
specName: text2("spec_name").notNull(),
|
|
3682
|
+
version: text2("version"),
|
|
3683
|
+
details: jsonb("details").$type(),
|
|
3684
|
+
createdAt: timestamp("created_at").notNull()
|
|
3685
|
+
}, (table) => [
|
|
3686
|
+
index2("idx_audit_log_spec_name").on(table.specName),
|
|
3687
|
+
index2("idx_audit_log_created_at").on(table.createdAt)
|
|
3688
|
+
]);
|
|
3689
|
+
var gatewayConfigs2 = pgTable("gateway_configs", {
|
|
3690
|
+
id: text2("id").primaryKey(),
|
|
3691
|
+
name: text2("name").notNull().unique(),
|
|
3692
|
+
provider: text2("provider", { enum: ["kong"] }).notNull(),
|
|
3693
|
+
specName: text2("spec_name").notNull(),
|
|
3694
|
+
specSemver: text2("spec_semver").notNull(),
|
|
3695
|
+
createdAt: timestamp("created_at").notNull(),
|
|
3696
|
+
updatedAt: timestamp("updated_at").notNull()
|
|
3697
|
+
});
|
|
3698
|
+
var gatewayConfigVersions2 = pgTable("gateway_config_versions", {
|
|
3699
|
+
id: text2("id").primaryKey(),
|
|
3700
|
+
gatewayConfigId: text2("gateway_config_id").notNull().references(() => gatewayConfigs2.id),
|
|
3701
|
+
routes: jsonb("routes").$type().notNull(),
|
|
3702
|
+
environments: jsonb("environments").$type().notNull(),
|
|
3703
|
+
callerIdentification: jsonb("caller_identification").$type(),
|
|
3704
|
+
content: text2("content").notNull(),
|
|
3705
|
+
checksum: text2("checksum").notNull(),
|
|
3706
|
+
pushedBy: text2("pushed_by"),
|
|
3707
|
+
createdAt: timestamp("created_at").notNull()
|
|
3708
|
+
}, (table) => [
|
|
3709
|
+
index2("idx_gateway_config_versions_config_id").on(table.gatewayConfigId)
|
|
3710
|
+
]);
|
|
3711
|
+
var httpLogs2 = pgTable("http_logs", {
|
|
3712
|
+
id: text2("id").primaryKey(),
|
|
3713
|
+
provider: text2("provider").notNull(),
|
|
3714
|
+
gatewayConfigName: text2("gateway_config_name").notNull(),
|
|
3715
|
+
environment: text2("environment").notNull(),
|
|
3716
|
+
method: text2("method").notNull(),
|
|
3717
|
+
path: text2("path").notNull(),
|
|
3718
|
+
routePath: text2("route_path"),
|
|
3719
|
+
status: integer2("status").notNull(),
|
|
3720
|
+
callerId: text2("caller_id"),
|
|
3721
|
+
callerSource: text2("caller_source"),
|
|
3722
|
+
callerConfidence: text2("caller_confidence").notNull(),
|
|
3723
|
+
occurredAt: timestamp("occurred_at").notNull(),
|
|
3724
|
+
createdAt: timestamp("created_at").notNull()
|
|
3725
|
+
}, (table) => [
|
|
3726
|
+
index2("idx_http_logs_config_env").on(table.gatewayConfigName, table.environment),
|
|
3727
|
+
index2("idx_http_logs_occurred_at").on(table.occurredAt),
|
|
3728
|
+
index2("idx_http_logs_caller").on(table.gatewayConfigName, table.environment, table.callerId)
|
|
3729
|
+
]);
|
|
3730
|
+
var provisions2 = pgTable("provisions", {
|
|
3731
|
+
id: text2("id").primaryKey(),
|
|
3732
|
+
gatewayConfigName: text2("gateway_config_name").notNull(),
|
|
3733
|
+
gatewayConfigVersion: text2("gateway_config_version").notNull(),
|
|
3734
|
+
environment: text2("environment").notNull(),
|
|
3735
|
+
provider: text2("provider", { enum: ["kong"] }).notNull(),
|
|
3736
|
+
synced: boolean("synced").notNull().default(false),
|
|
3737
|
+
actor: text2("actor").notNull(),
|
|
3738
|
+
details: jsonb("details").$type(),
|
|
3739
|
+
createdAt: timestamp("created_at").notNull()
|
|
3740
|
+
}, (table) => [
|
|
3741
|
+
index2("idx_provisions_config_name").on(table.gatewayConfigName),
|
|
3742
|
+
index2("idx_provisions_created_at").on(table.createdAt)
|
|
3743
|
+
]);
|
|
3744
|
+
|
|
3745
|
+
// src/registry/storage/postgresql.ts
|
|
3746
|
+
import { v4 as uuid6 } from "uuid";
|
|
3747
|
+
var MIGRATIONS_FOLDER2 = PG_MIGRATIONS_FOLDER;
|
|
3748
|
+
var DatabaseConnectionError = class extends Error {
|
|
3749
|
+
constructor(message, postgresUrl, options) {
|
|
3750
|
+
super(message, options);
|
|
3751
|
+
this.postgresUrl = postgresUrl;
|
|
3752
|
+
this.name = "DatabaseConnectionError";
|
|
3753
|
+
}
|
|
3754
|
+
postgresUrl;
|
|
3755
|
+
};
|
|
3756
|
+
function isConnectionError(err) {
|
|
3757
|
+
if (typeof err !== "object" || err === null) return false;
|
|
3758
|
+
if ("code" in err) {
|
|
3759
|
+
const code = err.code;
|
|
3760
|
+
if (code === "ECONNREFUSED" || code === "ETIMEDOUT" || code === "ENOTFOUND") {
|
|
3761
|
+
return true;
|
|
3762
|
+
}
|
|
3763
|
+
}
|
|
3764
|
+
if (err instanceof AggregateError) {
|
|
3765
|
+
if (err.errors.some(isConnectionError)) return true;
|
|
3766
|
+
}
|
|
3767
|
+
if ("cause" in err && err.cause) {
|
|
3768
|
+
return isConnectionError(err.cause);
|
|
3769
|
+
}
|
|
3770
|
+
return false;
|
|
3771
|
+
}
|
|
3772
|
+
function maskPostgresPassword(url) {
|
|
3773
|
+
try {
|
|
3774
|
+
const parsed = new URL(url);
|
|
3775
|
+
if (parsed.password) parsed.password = "***";
|
|
3776
|
+
return parsed.toString();
|
|
3777
|
+
} catch {
|
|
3778
|
+
return url;
|
|
3779
|
+
}
|
|
3780
|
+
}
|
|
3781
|
+
var PostgreSQLSpecStore = class {
|
|
3782
|
+
db;
|
|
3783
|
+
pool;
|
|
3784
|
+
postgresUrl;
|
|
3785
|
+
constructor(postgresUrl) {
|
|
3786
|
+
this.postgresUrl = postgresUrl;
|
|
3787
|
+
this.pool = new Pool({ connectionString: postgresUrl });
|
|
3788
|
+
this.db = drizzle2(this.pool);
|
|
3789
|
+
}
|
|
3790
|
+
async migrate() {
|
|
3791
|
+
try {
|
|
3792
|
+
await migrate2(this.db, {
|
|
3793
|
+
migrationsFolder: MIGRATIONS_FOLDER2
|
|
3794
|
+
});
|
|
3795
|
+
} catch (err) {
|
|
3796
|
+
if (isConnectionError(err)) {
|
|
3797
|
+
throw new DatabaseConnectionError(
|
|
3798
|
+
`PostgreSQL is not reachable at ${maskPostgresPassword(this.postgresUrl)}`,
|
|
3799
|
+
this.postgresUrl,
|
|
3800
|
+
{ cause: err }
|
|
3801
|
+
);
|
|
3802
|
+
}
|
|
3803
|
+
throw err;
|
|
3804
|
+
}
|
|
3805
|
+
}
|
|
3806
|
+
async end() {
|
|
3807
|
+
await this.pool.end();
|
|
3808
|
+
}
|
|
3809
|
+
async getSpec(name) {
|
|
3810
|
+
const rows = await this.db.select().from(specs2).where(eq2(specs2.name, name)).limit(1);
|
|
3811
|
+
if (rows.length === 0) return null;
|
|
3812
|
+
return this.mapSpecRow(rows[0]);
|
|
3813
|
+
}
|
|
3814
|
+
async getSpecVersion(name, semver) {
|
|
3815
|
+
const spec = await this.getSpec(name);
|
|
3816
|
+
if (!spec) return null;
|
|
3817
|
+
const rows = await this.db.select().from(specVersions2).where(and2(eq2(specVersions2.specId, spec.id), eq2(specVersions2.semver, semver))).limit(1);
|
|
3818
|
+
if (rows.length === 0) return null;
|
|
3819
|
+
return this.mapVersionRow(rows[0]);
|
|
3820
|
+
}
|
|
3821
|
+
async getLatestVersion(name) {
|
|
3822
|
+
const spec = await this.getSpec(name);
|
|
3823
|
+
if (!spec) return null;
|
|
3824
|
+
const rows = await this.db.select().from(specVersions2).where(eq2(specVersions2.specId, spec.id)).orderBy(desc2(specVersions2.createdAt), desc2(specVersions2.id)).limit(1);
|
|
3825
|
+
if (rows.length === 0) return null;
|
|
3826
|
+
return this.mapVersionRow(rows[0]);
|
|
3827
|
+
}
|
|
3828
|
+
async listSpecs(filters) {
|
|
3829
|
+
const conditions = [];
|
|
3830
|
+
if (filters?.type) conditions.push(eq2(specs2.type, filters.type));
|
|
3831
|
+
if (filters?.owner) conditions.push(eq2(specs2.owner, filters.owner));
|
|
3832
|
+
let rows = conditions.length > 0 ? await this.db.select().from(specs2).where(and2(...conditions)) : await this.db.select().from(specs2);
|
|
3833
|
+
if (filters?.tags && filters.tags.length > 0) {
|
|
3834
|
+
rows = rows.filter((row) => {
|
|
3835
|
+
const rowTags = row.tags ?? [];
|
|
3836
|
+
return filters.tags.every((tag) => rowTags.includes(tag));
|
|
3837
|
+
});
|
|
3838
|
+
}
|
|
3839
|
+
return rows.map((r) => this.mapSpecRow(r));
|
|
3840
|
+
}
|
|
3841
|
+
async listVersions(name, options) {
|
|
3842
|
+
const spec = await this.getSpec(name);
|
|
3843
|
+
if (!spec) return { versions: [], total: 0 };
|
|
3844
|
+
const limit = options?.limit ?? 10;
|
|
3845
|
+
const offset = options?.offset ?? 0;
|
|
3846
|
+
const [countRow] = await this.db.select({ count: sql2`count(*)` }).from(specVersions2).where(eq2(specVersions2.specId, spec.id));
|
|
3847
|
+
const total = Number(countRow?.count ?? 0);
|
|
3848
|
+
const rows = await this.db.select().from(specVersions2).where(eq2(specVersions2.specId, spec.id)).orderBy(desc2(specVersions2.createdAt), desc2(specVersions2.id)).limit(limit).offset(offset);
|
|
3849
|
+
return { versions: rows.map((r) => this.mapVersionRow(r)), total };
|
|
3850
|
+
}
|
|
3851
|
+
async pushSpecVersion(spec, version) {
|
|
3852
|
+
const existingSpec = await this.getSpec(spec.name);
|
|
3853
|
+
if (!existingSpec) {
|
|
3854
|
+
await this.db.insert(specs2).values({
|
|
3855
|
+
id: spec.id,
|
|
3856
|
+
name: spec.name,
|
|
3857
|
+
type: spec.type,
|
|
3858
|
+
description: spec.description ?? null,
|
|
3859
|
+
owner: spec.owner ?? null,
|
|
3860
|
+
sourceRepo: spec.sourceRepo ?? null,
|
|
3861
|
+
tags: spec.tags ?? [],
|
|
3862
|
+
createdAt: spec.createdAt,
|
|
3863
|
+
updatedAt: spec.updatedAt
|
|
3864
|
+
});
|
|
3865
|
+
} else {
|
|
3866
|
+
await this.db.update(specs2).set({ updatedAt: /* @__PURE__ */ new Date() }).where(eq2(specs2.id, existingSpec.id));
|
|
3867
|
+
}
|
|
3868
|
+
const specId = existingSpec?.id ?? spec.id;
|
|
3869
|
+
await this.db.insert(specVersions2).values({
|
|
3870
|
+
id: version.id,
|
|
3871
|
+
specId,
|
|
3872
|
+
semver: version.semver,
|
|
3873
|
+
content: version.content,
|
|
3874
|
+
checksum: version.checksum,
|
|
3875
|
+
gitRef: version.gitRef ?? null,
|
|
3876
|
+
pushedBy: version.pushedBy ?? null,
|
|
3877
|
+
compatibility: version.compatibility ?? null,
|
|
3878
|
+
previousVersion: version.previousVersion ?? null,
|
|
3879
|
+
forceReason: version.forceReason ?? null,
|
|
3880
|
+
isPrerelease: version.isPrerelease,
|
|
3881
|
+
createdAt: version.createdAt
|
|
3882
|
+
});
|
|
3883
|
+
return version;
|
|
3884
|
+
}
|
|
3885
|
+
async deleteSpec(name) {
|
|
3886
|
+
const existingSpec = await this.getSpec(name);
|
|
3887
|
+
if (!existingSpec) return false;
|
|
3888
|
+
await this.db.delete(specVersions2).where(eq2(specVersions2.specId, existingSpec.id));
|
|
3889
|
+
await this.db.delete(specs2).where(eq2(specs2.id, existingSpec.id));
|
|
3890
|
+
return true;
|
|
3891
|
+
}
|
|
3892
|
+
async getCompatReport(name, semver) {
|
|
3893
|
+
const version = await this.getSpecVersion(name, semver);
|
|
3894
|
+
return version?.compatibility ?? null;
|
|
3895
|
+
}
|
|
3896
|
+
async logAudit(action, actor, specName, version, details) {
|
|
3897
|
+
await this.db.insert(auditLog2).values({
|
|
3898
|
+
id: uuid6(),
|
|
3899
|
+
action,
|
|
3900
|
+
actor,
|
|
3901
|
+
specName,
|
|
3902
|
+
version: version ?? null,
|
|
3903
|
+
details: details ?? null,
|
|
3904
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
3905
|
+
});
|
|
3906
|
+
}
|
|
3907
|
+
mapSpecRow(row) {
|
|
3908
|
+
return {
|
|
3909
|
+
id: row.id,
|
|
3910
|
+
name: row.name,
|
|
3911
|
+
type: row.type,
|
|
3912
|
+
description: row.description ?? void 0,
|
|
3913
|
+
owner: row.owner ?? void 0,
|
|
3914
|
+
sourceRepo: row.sourceRepo ?? void 0,
|
|
3915
|
+
tags: row.tags ?? [],
|
|
3916
|
+
createdAt: row.createdAt,
|
|
3917
|
+
updatedAt: row.updatedAt
|
|
3918
|
+
};
|
|
3919
|
+
}
|
|
3920
|
+
mapVersionRow(row) {
|
|
3921
|
+
return {
|
|
3922
|
+
id: row.id,
|
|
3923
|
+
specId: row.specId,
|
|
3924
|
+
semver: row.semver,
|
|
3925
|
+
content: row.content,
|
|
3926
|
+
checksum: row.checksum,
|
|
3927
|
+
gitRef: row.gitRef ?? void 0,
|
|
3928
|
+
pushedBy: row.pushedBy ?? void 0,
|
|
3929
|
+
compatibility: row.compatibility ?? void 0,
|
|
3930
|
+
previousVersion: row.previousVersion ?? void 0,
|
|
3931
|
+
forceReason: row.forceReason ?? void 0,
|
|
3932
|
+
isPrerelease: row.isPrerelease,
|
|
3933
|
+
createdAt: row.createdAt
|
|
3934
|
+
};
|
|
3935
|
+
}
|
|
3936
|
+
// GatewayConfigStore implementation
|
|
3937
|
+
async getGatewayConfig(name) {
|
|
3938
|
+
const rows = await this.db.select().from(gatewayConfigs2).where(eq2(gatewayConfigs2.name, name)).limit(1);
|
|
3939
|
+
if (rows.length === 0) return null;
|
|
3940
|
+
return this.mapGatewayConfigRow(rows[0]);
|
|
3941
|
+
}
|
|
3942
|
+
async listGatewayConfigs() {
|
|
3943
|
+
const rows = await this.db.select().from(gatewayConfigs2);
|
|
3944
|
+
return rows.map((r) => this.mapGatewayConfigRow(r));
|
|
3945
|
+
}
|
|
3946
|
+
async getGatewayConfigVersion(name, versionId) {
|
|
3947
|
+
const config = await this.getGatewayConfig(name);
|
|
3948
|
+
if (!config) return null;
|
|
3949
|
+
const rows = await this.db.select().from(gatewayConfigVersions2).where(and2(eq2(gatewayConfigVersions2.gatewayConfigId, config.id), eq2(gatewayConfigVersions2.id, versionId))).limit(1);
|
|
3950
|
+
if (rows.length === 0) return null;
|
|
3951
|
+
return this.mapGatewayConfigVersionRow(rows[0]);
|
|
3952
|
+
}
|
|
3953
|
+
async getLatestGatewayConfigVersion(name) {
|
|
3954
|
+
const config = await this.getGatewayConfig(name);
|
|
3955
|
+
if (!config) return null;
|
|
3956
|
+
const rows = await this.db.select().from(gatewayConfigVersions2).where(eq2(gatewayConfigVersions2.gatewayConfigId, config.id)).orderBy(desc2(gatewayConfigVersions2.createdAt), desc2(gatewayConfigVersions2.id)).limit(1);
|
|
3957
|
+
if (rows.length === 0) return null;
|
|
3958
|
+
return this.mapGatewayConfigVersionRow(rows[0]);
|
|
3959
|
+
}
|
|
3960
|
+
async listGatewayConfigVersions(name) {
|
|
3961
|
+
const config = await this.getGatewayConfig(name);
|
|
3962
|
+
if (!config) return [];
|
|
3963
|
+
const rows = await this.db.select().from(gatewayConfigVersions2).where(eq2(gatewayConfigVersions2.gatewayConfigId, config.id)).orderBy(desc2(gatewayConfigVersions2.createdAt), desc2(gatewayConfigVersions2.id)).limit(5);
|
|
3964
|
+
return rows.map((r) => this.mapGatewayConfigVersionRow(r));
|
|
3965
|
+
}
|
|
3966
|
+
async pushGatewayConfigVersion(config, version) {
|
|
3967
|
+
const existingConfig = await this.getGatewayConfig(config.name);
|
|
3968
|
+
if (!existingConfig) {
|
|
3969
|
+
await this.db.insert(gatewayConfigs2).values({
|
|
3970
|
+
id: config.id,
|
|
3971
|
+
name: config.name,
|
|
3972
|
+
provider: config.provider,
|
|
3973
|
+
specName: config.specName,
|
|
3974
|
+
specSemver: config.specSemver,
|
|
3975
|
+
createdAt: config.createdAt,
|
|
3976
|
+
updatedAt: config.updatedAt
|
|
3977
|
+
});
|
|
3978
|
+
} else {
|
|
3979
|
+
await this.db.update(gatewayConfigs2).set({ updatedAt: /* @__PURE__ */ new Date(), specSemver: config.specSemver }).where(eq2(gatewayConfigs2.id, existingConfig.id));
|
|
3980
|
+
}
|
|
3981
|
+
const configId = existingConfig?.id ?? config.id;
|
|
3982
|
+
await this.db.insert(gatewayConfigVersions2).values({
|
|
3983
|
+
id: version.id,
|
|
3984
|
+
gatewayConfigId: configId,
|
|
3985
|
+
routes: version.routes,
|
|
3986
|
+
environments: version.environments,
|
|
3987
|
+
callerIdentification: version.callerIdentification ?? null,
|
|
3988
|
+
content: version.content,
|
|
3989
|
+
checksum: version.checksum,
|
|
3990
|
+
pushedBy: version.pushedBy ?? null,
|
|
3991
|
+
createdAt: version.createdAt
|
|
3992
|
+
});
|
|
3993
|
+
const versions = await this.db.select().from(gatewayConfigVersions2).where(eq2(gatewayConfigVersions2.gatewayConfigId, configId)).orderBy(desc2(gatewayConfigVersions2.createdAt), desc2(gatewayConfigVersions2.id));
|
|
3994
|
+
if (versions.length > 5) {
|
|
3995
|
+
const toDelete = versions.slice(5);
|
|
3996
|
+
for (const v of toDelete) {
|
|
3997
|
+
await this.db.delete(gatewayConfigVersions2).where(eq2(gatewayConfigVersions2.id, v.id));
|
|
3998
|
+
}
|
|
3999
|
+
}
|
|
4000
|
+
return version;
|
|
4001
|
+
}
|
|
4002
|
+
async recordProvision(provision) {
|
|
4003
|
+
await this.db.insert(provisions2).values({
|
|
4004
|
+
id: provision.id,
|
|
4005
|
+
gatewayConfigName: provision.gatewayConfigName,
|
|
4006
|
+
gatewayConfigVersion: provision.gatewayConfigVersion,
|
|
4007
|
+
environment: provision.environment,
|
|
4008
|
+
provider: provision.provider,
|
|
4009
|
+
synced: provision.synced,
|
|
4010
|
+
actor: provision.actor,
|
|
4011
|
+
details: provision.details ?? null,
|
|
4012
|
+
createdAt: provision.createdAt
|
|
4013
|
+
});
|
|
4014
|
+
}
|
|
4015
|
+
async listProvisions(gatewayConfigName) {
|
|
4016
|
+
const rows = gatewayConfigName ? await this.db.select().from(provisions2).where(eq2(provisions2.gatewayConfigName, gatewayConfigName)).orderBy(desc2(provisions2.createdAt)) : await this.db.select().from(provisions2).orderBy(desc2(provisions2.createdAt));
|
|
4017
|
+
return rows.map((r) => ({
|
|
4018
|
+
id: r.id,
|
|
4019
|
+
gatewayConfigName: r.gatewayConfigName,
|
|
4020
|
+
gatewayConfigVersion: r.gatewayConfigVersion,
|
|
4021
|
+
environment: r.environment,
|
|
4022
|
+
provider: r.provider,
|
|
4023
|
+
synced: r.synced,
|
|
4024
|
+
actor: r.actor,
|
|
4025
|
+
details: r.details ?? void 0,
|
|
4026
|
+
createdAt: r.createdAt
|
|
4027
|
+
}));
|
|
4028
|
+
}
|
|
4029
|
+
mapGatewayConfigRow(row) {
|
|
4030
|
+
return {
|
|
4031
|
+
id: row.id,
|
|
4032
|
+
name: row.name,
|
|
4033
|
+
provider: row.provider,
|
|
4034
|
+
specName: row.specName,
|
|
4035
|
+
specSemver: row.specSemver,
|
|
4036
|
+
createdAt: row.createdAt,
|
|
4037
|
+
updatedAt: row.updatedAt
|
|
4038
|
+
};
|
|
4039
|
+
}
|
|
4040
|
+
mapGatewayConfigVersionRow(row) {
|
|
4041
|
+
return {
|
|
4042
|
+
id: row.id,
|
|
4043
|
+
gatewayConfigId: row.gatewayConfigId,
|
|
4044
|
+
routes: row.routes,
|
|
4045
|
+
environments: row.environments,
|
|
4046
|
+
callerIdentification: row.callerIdentification ?? void 0,
|
|
4047
|
+
content: row.content,
|
|
4048
|
+
checksum: row.checksum,
|
|
4049
|
+
pushedBy: row.pushedBy ?? void 0,
|
|
4050
|
+
createdAt: row.createdAt
|
|
4051
|
+
};
|
|
4052
|
+
}
|
|
4053
|
+
async recordGatewayLog(log) {
|
|
4054
|
+
await this.db.insert(httpLogs2).values({
|
|
4055
|
+
id: log.id,
|
|
4056
|
+
provider: log.provider,
|
|
4057
|
+
gatewayConfigName: log.gatewayConfigName,
|
|
4058
|
+
environment: log.environment,
|
|
4059
|
+
method: log.method,
|
|
4060
|
+
path: log.path,
|
|
4061
|
+
routePath: log.routePath ?? null,
|
|
4062
|
+
status: log.status,
|
|
4063
|
+
callerId: log.callerId ?? null,
|
|
4064
|
+
callerSource: log.callerSource ?? null,
|
|
4065
|
+
callerConfidence: log.callerConfidence,
|
|
4066
|
+
occurredAt: log.occurredAt,
|
|
4067
|
+
createdAt: log.createdAt
|
|
4068
|
+
});
|
|
4069
|
+
}
|
|
4070
|
+
async listGatewayLogs(filters) {
|
|
4071
|
+
const limit = filters.limit ?? 50;
|
|
4072
|
+
const offset = filters.offset ?? 0;
|
|
4073
|
+
let query = this.db.select().from(httpLogs2);
|
|
4074
|
+
const conditions = [];
|
|
4075
|
+
if (filters.gatewayConfigName) {
|
|
4076
|
+
conditions.push(eq2(httpLogs2.gatewayConfigName, filters.gatewayConfigName));
|
|
4077
|
+
}
|
|
4078
|
+
if (filters.environment) {
|
|
4079
|
+
conditions.push(eq2(httpLogs2.environment, filters.environment));
|
|
4080
|
+
}
|
|
4081
|
+
if (filters.path) {
|
|
4082
|
+
conditions.push(eq2(httpLogs2.path, filters.path));
|
|
4083
|
+
}
|
|
4084
|
+
if (filters.method) {
|
|
4085
|
+
conditions.push(eq2(httpLogs2.method, filters.method));
|
|
4086
|
+
}
|
|
4087
|
+
if (filters.status !== void 0) {
|
|
4088
|
+
conditions.push(eq2(httpLogs2.status, filters.status));
|
|
4089
|
+
}
|
|
4090
|
+
if (filters.from) {
|
|
4091
|
+
conditions.push(sql2`${httpLogs2.occurredAt} >= ${filters.from}`);
|
|
4092
|
+
}
|
|
4093
|
+
if (filters.to) {
|
|
4094
|
+
conditions.push(sql2`${httpLogs2.occurredAt} <= ${filters.to}`);
|
|
4095
|
+
}
|
|
4096
|
+
if (conditions.length > 0) {
|
|
4097
|
+
query = query.where(and2(...conditions));
|
|
4098
|
+
}
|
|
4099
|
+
const countResult = await this.db.select({ count: sql2`count(*)` }).from(httpLogs2).where(conditions.length > 0 ? and2(...conditions) : void 0);
|
|
4100
|
+
const total = countResult[0]?.count ?? 0;
|
|
4101
|
+
const rows = await query.orderBy(desc2(httpLogs2.occurredAt)).limit(limit).offset(offset);
|
|
4102
|
+
return {
|
|
4103
|
+
logs: rows.map((r) => ({
|
|
4104
|
+
id: r.id,
|
|
4105
|
+
provider: r.provider,
|
|
4106
|
+
gatewayConfigName: r.gatewayConfigName,
|
|
4107
|
+
environment: r.environment,
|
|
4108
|
+
method: r.method,
|
|
4109
|
+
path: r.path,
|
|
4110
|
+
routePath: r.routePath ?? void 0,
|
|
4111
|
+
status: r.status,
|
|
4112
|
+
callerId: r.callerId ?? void 0,
|
|
4113
|
+
callerSource: r.callerSource ?? void 0,
|
|
4114
|
+
callerConfidence: r.callerConfidence,
|
|
4115
|
+
occurredAt: r.occurredAt,
|
|
4116
|
+
createdAt: r.createdAt
|
|
4117
|
+
})),
|
|
4118
|
+
total
|
|
4119
|
+
};
|
|
4120
|
+
}
|
|
4121
|
+
async getGatewayLog(id) {
|
|
4122
|
+
const rows = await this.db.select().from(httpLogs2).where(eq2(httpLogs2.id, id)).limit(1);
|
|
4123
|
+
if (rows.length === 0) return null;
|
|
4124
|
+
const r = rows[0];
|
|
4125
|
+
return {
|
|
4126
|
+
id: r.id,
|
|
4127
|
+
provider: r.provider,
|
|
4128
|
+
gatewayConfigName: r.gatewayConfigName,
|
|
4129
|
+
environment: r.environment,
|
|
4130
|
+
method: r.method,
|
|
4131
|
+
path: r.path,
|
|
4132
|
+
routePath: r.routePath ?? void 0,
|
|
4133
|
+
status: r.status,
|
|
4134
|
+
callerId: r.callerId ?? void 0,
|
|
4135
|
+
callerSource: r.callerSource ?? void 0,
|
|
4136
|
+
callerConfidence: r.callerConfidence,
|
|
4137
|
+
occurredAt: r.occurredAt,
|
|
4138
|
+
createdAt: r.createdAt
|
|
4139
|
+
};
|
|
4140
|
+
}
|
|
4141
|
+
async getGatewayLogStats(_filters) {
|
|
4142
|
+
const conditions = [];
|
|
4143
|
+
if (_filters.gatewayConfigName) {
|
|
4144
|
+
conditions.push(eq2(httpLogs2.gatewayConfigName, _filters.gatewayConfigName));
|
|
4145
|
+
}
|
|
4146
|
+
if (_filters.environment) {
|
|
4147
|
+
conditions.push(eq2(httpLogs2.environment, _filters.environment));
|
|
4148
|
+
}
|
|
4149
|
+
if (_filters.from) {
|
|
4150
|
+
conditions.push(sql2`${httpLogs2.occurredAt} >= ${_filters.from}`);
|
|
4151
|
+
}
|
|
4152
|
+
if (_filters.to) {
|
|
4153
|
+
conditions.push(sql2`${httpLogs2.occurredAt} <= ${_filters.to}`);
|
|
4154
|
+
}
|
|
4155
|
+
const whereClause = conditions.length > 0 ? and2(...conditions) : void 0;
|
|
4156
|
+
const rows = await this.db.select({
|
|
4157
|
+
gatewayConfigName: httpLogs2.gatewayConfigName,
|
|
4158
|
+
environment: httpLogs2.environment,
|
|
4159
|
+
method: httpLogs2.method,
|
|
4160
|
+
routePath: httpLogs2.routePath,
|
|
4161
|
+
lastSeenAt: sql2`max(${httpLogs2.occurredAt})`,
|
|
4162
|
+
totalCalls: sql2`count(*)`,
|
|
4163
|
+
uniqueCallerIds: sql2`count(distinct ${httpLogs2.callerId})`
|
|
4164
|
+
}).from(httpLogs2).where(whereClause).groupBy(httpLogs2.gatewayConfigName, httpLogs2.environment, httpLogs2.method, httpLogs2.routePath);
|
|
4165
|
+
return rows.map((r) => ({
|
|
4166
|
+
gatewayConfigName: r.gatewayConfigName,
|
|
4167
|
+
environment: r.environment,
|
|
4168
|
+
method: r.method,
|
|
4169
|
+
routePath: r.routePath ?? "/",
|
|
4170
|
+
lastSeenAt: new Date(r.lastSeenAt),
|
|
4171
|
+
totalCalls: r.totalCalls,
|
|
4172
|
+
uniqueCallerIds: r.uniqueCallerIds
|
|
4173
|
+
}));
|
|
4174
|
+
}
|
|
4175
|
+
async deleteGatewayLogsOlderThan(days) {
|
|
4176
|
+
const cutoff = /* @__PURE__ */ new Date();
|
|
4177
|
+
cutoff.setDate(cutoff.getDate() - days);
|
|
4178
|
+
await this.db.delete(httpLogs2).where(sql2`${httpLogs2.occurredAt} < ${cutoff}`);
|
|
4179
|
+
}
|
|
4180
|
+
};
|
|
4181
|
+
|
|
3496
4182
|
// src/registry/serve.ts
|
|
3497
4183
|
async function startServer(userConfig) {
|
|
3498
4184
|
const config = { ...defaultConfig, ...userConfig };
|
|
3499
|
-
|
|
3500
|
-
|
|
3501
|
-
|
|
3502
|
-
|
|
3503
|
-
|
|
3504
|
-
|
|
4185
|
+
let store;
|
|
4186
|
+
if (config.database === "postgresql") {
|
|
4187
|
+
if (!config.postgresUrl) {
|
|
4188
|
+
throw new Error("PostgreSQL database requested but no postgresUrl provided.");
|
|
4189
|
+
}
|
|
4190
|
+
store = new PostgreSQLSpecStore(config.postgresUrl);
|
|
4191
|
+
} else {
|
|
4192
|
+
const sqlitePath = config.sqlitePath ?? path.join(
|
|
4193
|
+
process.env.HOME || process.env.USERPROFILE || ".",
|
|
4194
|
+
".grapity",
|
|
4195
|
+
"registry.db"
|
|
4196
|
+
);
|
|
4197
|
+
const dir = path.dirname(sqlitePath);
|
|
3505
4198
|
if (!fs.existsSync(dir)) {
|
|
3506
4199
|
fs.mkdirSync(dir, { recursive: true });
|
|
3507
4200
|
}
|
|
4201
|
+
store = new SQLiteSpecStore(sqlitePath);
|
|
3508
4202
|
}
|
|
3509
|
-
const store = new SQLiteSpecStore(config.sqlitePath);
|
|
3510
4203
|
await store.migrate();
|
|
3511
4204
|
const app = createApp(config, store);
|
|
3512
|
-
serve({
|
|
4205
|
+
const server = serve({
|
|
3513
4206
|
fetch: app.fetch,
|
|
3514
4207
|
port: config.port
|
|
3515
4208
|
});
|
|
3516
|
-
return app;
|
|
4209
|
+
return { app, store, server };
|
|
3517
4210
|
}
|
|
3518
4211
|
if (process.argv[1] === new URL(import.meta.url).pathname) {
|
|
3519
4212
|
startServer();
|