@rebasepro/server 0.9.1-canary.ad25bc0 → 0.9.1-canary.ad870eb
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/dist/api/rest/api-generator.d.ts +1 -4
- package/dist/env.d.ts +0 -1
- package/dist/index.es.js +21 -318
- package/dist/index.es.js.map +1 -1
- package/dist/init/middlewares.d.ts +0 -1
- package/dist/init.d.ts +0 -14
- package/dist/{src-CsHhSKbi.js → src-B4OLmNVa.js} +2 -60
- package/dist/src-B4OLmNVa.js.map +1 -0
- package/package.json +7 -8
- package/dist/src-CsHhSKbi.js.map +0 -1
- package/dist/utils/compression.d.ts +0 -16
|
@@ -5,15 +5,12 @@ import { HonoEnv } from "../types";
|
|
|
5
5
|
* Lightweight REST API generator that leverages existing Rebase DataDriver.
|
|
6
6
|
* Supports `include` query parameter for eager-loading relations via Drizzle.
|
|
7
7
|
*/
|
|
8
|
-
/** Rows accepted by a single POST /<collection>/bulk. See `maxBulkRows`. */
|
|
9
|
-
export declare const DEFAULT_MAX_BULK_ROWS = 1000;
|
|
10
8
|
export declare class RestApiGenerator {
|
|
11
9
|
private collections;
|
|
12
10
|
private router;
|
|
13
11
|
private driver;
|
|
14
|
-
private maxBulkRows;
|
|
15
12
|
private authAdapter?;
|
|
16
|
-
constructor(collections: CollectionConfig[], driver: DataDriver, authAdapter?: AuthAdapter
|
|
13
|
+
constructor(collections: CollectionConfig[], driver: DataDriver, authAdapter?: AuthAdapter);
|
|
17
14
|
/**
|
|
18
15
|
* Generate REST routes using existing DataDriver
|
|
19
16
|
*/
|
package/dist/env.d.ts
CHANGED
package/dist/index.es.js
CHANGED
|
@@ -2,7 +2,7 @@ import { createRequire as __createRequire } from "module";
|
|
|
2
2
|
import process from "process";
|
|
3
3
|
__createRequire(import.meta.url);
|
|
4
4
|
import { a as __require, i as __exportAll, n as __commonJSMin, s as __toESM } from "./ms-DnYXB-Wd.js";
|
|
5
|
-
import { _ as
|
|
5
|
+
import { _ as RebaseClientError, a as getCollectionDataPath, c as CANONICAL_TO_REST, d as toCanonicalOp, f as EntityReference, g as RebaseApiError, h as Vector, i as isSQLAdmin, l as NULL_OPS, m as GeoPoint, o as getDeclaredSubcollections, p as EntityRelation, r as getDataSourceCapabilities, s as isPostgresCollectionConfig, t as DEFAULT_STORAGE_SOURCE_KEY, u as REST_TO_CANONICAL } from "./src-B4OLmNVa.js";
|
|
6
6
|
import { t as logger } from "./logger-BYU66ENZ.js";
|
|
7
7
|
import { a as getAccessTokenExpiry, d as require_jsonwebtoken, i as generateRefreshToken, l as verifyAccessToken, n as generateAccessToken, o as getRefreshTokenExpiry, r as generateDownloadToken, s as hashRefreshToken, t as configureJwt, u as verifyDownloadToken } from "./jwt-BwIn8xmk.js";
|
|
8
8
|
import { t as nativeDynamicImport } from "./dynamic-import-Dvh-K5fl.js";
|
|
@@ -18,7 +18,6 @@ import * as crypto from "crypto";
|
|
|
18
18
|
import { createCipheriv, createDecipheriv, createHash as createHash$1, createHmac, randomBytes as randomBytes$1, randomUUID as randomUUID$1, scrypt, timingSafeEqual } from "crypto";
|
|
19
19
|
import { bodyLimit } from "hono/body-limit";
|
|
20
20
|
import { csrf } from "hono/csrf";
|
|
21
|
-
import { compress } from "hono/compress";
|
|
22
21
|
import fs$1 from "node:fs";
|
|
23
22
|
import fsp from "node:fs/promises";
|
|
24
23
|
import { mkdir, open, unlink, writeFile } from "fs/promises";
|
|
@@ -1109,65 +1108,6 @@ function normalizeToEntityRelation(value, propertyType) {
|
|
|
1109
1108
|
if (!(obj.__type === "relation" || obj.__type === "reference" || typeof obj.isEntityRelation === "function" && obj.isEntityRelation() || typeof obj.isEntityReference === "function" && obj.isEntityReference() || propertyType === "relation" && typeof obj.id !== "undefined" && typeof obj.path === "string")) return null;
|
|
1110
1109
|
return new EntityRelation(obj.id, obj.path, obj.data);
|
|
1111
1110
|
}
|
|
1112
|
-
/**
|
|
1113
|
-
* Derive a row's address from its key columns.
|
|
1114
|
-
*
|
|
1115
|
-
* Single key → the value as a string. Composite → each part joined by
|
|
1116
|
-
* {@link COMPOSITE_ID_SEPARATOR}, in primary-key order, which is what
|
|
1117
|
-
* {@link parseIdValues} expects to invert.
|
|
1118
|
-
*/
|
|
1119
|
-
function buildCompositeId(values, primaryKeys) {
|
|
1120
|
-
if (primaryKeys.length === 0) return "";
|
|
1121
|
-
if (primaryKeys.length === 1) return String(values[primaryKeys[0].fieldName] ?? "");
|
|
1122
|
-
return primaryKeys.map((pk) => String(values[pk.fieldName] ?? "")).join(":::");
|
|
1123
|
-
}
|
|
1124
|
-
/**
|
|
1125
|
-
* The primary keys of a collection, as declared by its properties.
|
|
1126
|
-
*
|
|
1127
|
-
* The driver can also infer keys from the Drizzle schema, which the browser
|
|
1128
|
-
* cannot see — so the server normalizes what it resolved onto the config it
|
|
1129
|
-
* serves (see `stampPrimaryKeys`), and this reads that back. Returns an empty
|
|
1130
|
-
* array when a collection declares none, which callers must treat as "not
|
|
1131
|
-
* addressable" rather than defaulting to `id`: guessing a key that is not the
|
|
1132
|
-
* real one produces confidently wrong addresses.
|
|
1133
|
-
*/
|
|
1134
|
-
function getDeclaredPrimaryKeys(collection) {
|
|
1135
|
-
const properties = collection.properties;
|
|
1136
|
-
if (!properties) return [];
|
|
1137
|
-
const keys = [];
|
|
1138
|
-
for (const [fieldName, propRaw] of Object.entries(properties)) {
|
|
1139
|
-
const prop = propRaw;
|
|
1140
|
-
if (!prop || typeof prop !== "object") continue;
|
|
1141
|
-
if (!("isId" in prop) || !prop.isId) continue;
|
|
1142
|
-
keys.push({
|
|
1143
|
-
fieldName,
|
|
1144
|
-
type: prop.type === "number" ? "number" : "string",
|
|
1145
|
-
isUUID: prop.isId === "uuid"
|
|
1146
|
-
});
|
|
1147
|
-
}
|
|
1148
|
-
return keys;
|
|
1149
|
-
}
|
|
1150
|
-
/**
|
|
1151
|
-
* The keys to address a collection's rows with, resolved the way the driver
|
|
1152
|
-
* resolves them — minus the tier the browser cannot reach.
|
|
1153
|
-
*
|
|
1154
|
-
* The postgres driver tries, in order: properties marked `isId`; the primary
|
|
1155
|
-
* keys of the Drizzle schema; and finally a column literally named `id`. Only
|
|
1156
|
-
* the first and last are visible in a `CollectionConfig`, which is what both
|
|
1157
|
-
* sides share — so a collection whose key is *only* known to Drizzle, and is
|
|
1158
|
-
* not named `id`, resolves to nothing here. That is reported rather than
|
|
1159
|
-
* guessed: inventing a key produces addresses that look right and route wrong.
|
|
1160
|
-
*/
|
|
1161
|
-
function resolvePrimaryKeys(collection) {
|
|
1162
|
-
const declared = getDeclaredPrimaryKeys(collection);
|
|
1163
|
-
if (declared.length > 0) return declared;
|
|
1164
|
-
const idProp = collection.properties?.id;
|
|
1165
|
-
if (idProp && typeof idProp === "object") return [{
|
|
1166
|
-
fieldName: "id",
|
|
1167
|
-
type: idProp.type === "number" ? "number" : "string"
|
|
1168
|
-
}];
|
|
1169
|
-
return [];
|
|
1170
|
-
}
|
|
1171
1111
|
//#endregion
|
|
1172
1112
|
//#region ../common/src/util/enums.ts
|
|
1173
1113
|
function enumToObjectEntries(enumValues) {
|
|
@@ -1380,7 +1320,6 @@ function getSubcollections(collection) {
|
|
|
1380
1320
|
}
|
|
1381
1321
|
return [];
|
|
1382
1322
|
}
|
|
1383
|
-
policy.or(policy.serverContext(), policy.rolesOverlap(["admin"]));
|
|
1384
1323
|
(/* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
1385
1324
|
(function(root, factory) {
|
|
1386
1325
|
if (typeof define === "function" && define.amd) define(factory);
|
|
@@ -2813,45 +2752,18 @@ function deserializeLogicalCondition(str) {
|
|
|
2813
2752
|
}
|
|
2814
2753
|
//#endregion
|
|
2815
2754
|
//#region ../common/src/data/buildRebaseData.ts
|
|
2816
|
-
function createPrimaryKeyResolver(options) {
|
|
2817
|
-
const cache = /* @__PURE__ */ new Map();
|
|
2818
|
-
const warned = /* @__PURE__ */ new Set();
|
|
2819
|
-
return function primaryKeysFor(slug) {
|
|
2820
|
-
const cached = cache.get(slug);
|
|
2821
|
-
if (cached) return cached;
|
|
2822
|
-
const collection = options?.resolveCollection?.(slug);
|
|
2823
|
-
if (!collection) return [];
|
|
2824
|
-
const keys = resolvePrimaryKeys(collection);
|
|
2825
|
-
if (keys.length > 0) {
|
|
2826
|
-
cache.set(slug, keys);
|
|
2827
|
-
return keys;
|
|
2828
|
-
}
|
|
2829
|
-
if (!warned.has(slug)) {
|
|
2830
|
-
warned.add(slug);
|
|
2831
|
-
console.warn(`[rebase] Collection '${slug}' declares no primary key, so its rows have no address: detail links, caching and relations will not work for it. Mark the key property with \`isId\` in its collection config.`);
|
|
2832
|
-
}
|
|
2833
|
-
return keys;
|
|
2834
|
-
};
|
|
2835
|
-
}
|
|
2836
2755
|
/**
|
|
2837
|
-
*
|
|
2838
|
-
*
|
|
2839
|
-
* The address is *derived here* — it is not a column, and the row it came from
|
|
2840
|
-
* does not contain one. Rows carry exactly what the table has, with the types
|
|
2841
|
-
* Postgres returned; the id is this layer's invention, and this is the only
|
|
2842
|
-
* place it is minted.
|
|
2843
|
-
*
|
|
2844
|
-
* `primaryKeys` empty falls back to a literal `id` on the row: drivers other
|
|
2845
|
-
* than postgres still serve rows with one, and this keeps them working.
|
|
2756
|
+
* Convert a flat REST record (e.g. from RestFetchService) to Entity<M> format.
|
|
2757
|
+
* Mirrors the client SDK's rowToEntity conversion.
|
|
2846
2758
|
*/
|
|
2847
|
-
function rowToEntity(row, slug
|
|
2759
|
+
function rowToEntity(row, slug) {
|
|
2848
2760
|
return {
|
|
2849
|
-
id:
|
|
2761
|
+
id: row.id,
|
|
2850
2762
|
path: slug,
|
|
2851
2763
|
values: row
|
|
2852
2764
|
};
|
|
2853
2765
|
}
|
|
2854
|
-
function createDriverAccessor(driver, slug
|
|
2766
|
+
function createDriverAccessor(driver, slug) {
|
|
2855
2767
|
const accessor = {
|
|
2856
2768
|
async find(params) {
|
|
2857
2769
|
const filter = params?.where ? deserializeFilter(params.where) : void 0;
|
|
@@ -2884,7 +2796,7 @@ function createDriverAccessor(driver, slug, getPks = () => []) {
|
|
|
2884
2796
|
hasMore = offset + rows.length < total;
|
|
2885
2797
|
}
|
|
2886
2798
|
return {
|
|
2887
|
-
data: rows.map((row) => rowToEntity(row, slug
|
|
2799
|
+
data: rows.map((row) => rowToEntity(row, slug)),
|
|
2888
2800
|
meta: {
|
|
2889
2801
|
total,
|
|
2890
2802
|
limit,
|
|
@@ -2898,7 +2810,7 @@ function createDriverAccessor(driver, slug, getPks = () => []) {
|
|
|
2898
2810
|
path: slug,
|
|
2899
2811
|
id
|
|
2900
2812
|
});
|
|
2901
|
-
return row ? rowToEntity(row, slug
|
|
2813
|
+
return row ? rowToEntity(row, slug) : void 0;
|
|
2902
2814
|
},
|
|
2903
2815
|
async create(data, id) {
|
|
2904
2816
|
return rowToEntity(await driver.save({
|
|
@@ -2906,22 +2818,15 @@ function createDriverAccessor(driver, slug, getPks = () => []) {
|
|
|
2906
2818
|
values: data,
|
|
2907
2819
|
id,
|
|
2908
2820
|
status: "new"
|
|
2909
|
-
}), slug
|
|
2821
|
+
}), slug);
|
|
2910
2822
|
},
|
|
2911
|
-
createMany: driver.saveMany ? async (data, options) => {
|
|
2912
|
-
return (await driver.saveMany({
|
|
2913
|
-
path: slug,
|
|
2914
|
-
rows: data,
|
|
2915
|
-
upsert: options?.upsert
|
|
2916
|
-
})).map((row) => rowToEntity(row, slug, getPks()));
|
|
2917
|
-
} : void 0,
|
|
2918
2823
|
async update(id, data) {
|
|
2919
2824
|
return rowToEntity(await driver.save({
|
|
2920
2825
|
path: slug,
|
|
2921
2826
|
values: data,
|
|
2922
2827
|
id,
|
|
2923
2828
|
status: "existing"
|
|
2924
|
-
}), slug
|
|
2829
|
+
}), slug);
|
|
2925
2830
|
},
|
|
2926
2831
|
async delete(id) {
|
|
2927
2832
|
return driver.delete({ row: {
|
|
@@ -2950,7 +2855,7 @@ function createDriverAccessor(driver, slug, getPks = () => []) {
|
|
|
2950
2855
|
searchString: params?.searchString,
|
|
2951
2856
|
onUpdate: (entities) => {
|
|
2952
2857
|
onUpdate({
|
|
2953
|
-
data: entities.map((row) => rowToEntity(row, slug
|
|
2858
|
+
data: entities.map((row) => rowToEntity(row, slug)),
|
|
2954
2859
|
meta: {
|
|
2955
2860
|
total: entities.length,
|
|
2956
2861
|
limit,
|
|
@@ -2966,7 +2871,7 @@ function createDriverAccessor(driver, slug, getPks = () => []) {
|
|
|
2966
2871
|
return driver.listenOne({
|
|
2967
2872
|
path: slug,
|
|
2968
2873
|
id,
|
|
2969
|
-
onUpdate: (entity) => onUpdate(entity ? rowToEntity(entity, slug
|
|
2874
|
+
onUpdate: (entity) => onUpdate(entity ? rowToEntity(entity, slug) : void 0),
|
|
2970
2875
|
onError
|
|
2971
2876
|
});
|
|
2972
2877
|
} : void 0,
|
|
@@ -3005,13 +2910,12 @@ function createDriverAccessor(driver, slug, getPks = () => []) {
|
|
|
3005
2910
|
* await data.products.create({ name: "Camera", price: 299 });
|
|
3006
2911
|
* const { data: items } = await data.products.find({ where: { status: ["==", "published"] } });
|
|
3007
2912
|
*/
|
|
3008
|
-
function buildRebaseData(driver
|
|
2913
|
+
function buildRebaseData(driver) {
|
|
3009
2914
|
const cache = /* @__PURE__ */ new Map();
|
|
3010
|
-
const primaryKeysFor = createPrimaryKeyResolver(options);
|
|
3011
2915
|
function getAccessor(slug) {
|
|
3012
2916
|
let accessor = cache.get(slug);
|
|
3013
2917
|
if (!accessor) {
|
|
3014
|
-
accessor = createDriverAccessor(driver, slug
|
|
2918
|
+
accessor = createDriverAccessor(driver, slug);
|
|
3015
2919
|
cache.set(slug, accessor);
|
|
3016
2920
|
}
|
|
3017
2921
|
return accessor;
|
|
@@ -3024,9 +2928,8 @@ function buildRebaseData(driver, options) {
|
|
|
3024
2928
|
} });
|
|
3025
2929
|
}
|
|
3026
2930
|
/**
|
|
3027
|
-
* Unwrap a Entity
|
|
3028
|
-
*
|
|
3029
|
-
* the wrapper is the whole operation — the address was never part of the row.
|
|
2931
|
+
* Unwrap a Entity into a flat row. `rowToEntity` stores the whole flat row
|
|
2932
|
+
* (id included) under `.values`, so this is just that payload.
|
|
3030
2933
|
*/
|
|
3031
2934
|
function entityToRow(entity) {
|
|
3032
2935
|
return entity.values;
|
|
@@ -3113,12 +3016,6 @@ function toSdkCollectionClient(snap) {
|
|
|
3113
3016
|
async create(data, id) {
|
|
3114
3017
|
return entityToRow(await snap.create(data, id));
|
|
3115
3018
|
},
|
|
3116
|
-
async createMany(data, options) {
|
|
3117
|
-
if (!Array.isArray(data)) throw new TypeError("createMany expects an array of records.");
|
|
3118
|
-
if (data.length === 0) return [];
|
|
3119
|
-
if (!snap.createMany) throw new Error("Bulk writes are not supported by this collection's data source. Fall back to create() per record.");
|
|
3120
|
-
return (await snap.createMany(data, options)).map(entityToRow);
|
|
3121
|
-
},
|
|
3122
3019
|
async update(id, data) {
|
|
3123
3020
|
return entityToRow(await snap.update(id, data));
|
|
3124
3021
|
},
|
|
@@ -3827,19 +3724,15 @@ async function parseJsonBody(c) {
|
|
|
3827
3724
|
* Lightweight REST API generator that leverages existing Rebase DataDriver.
|
|
3828
3725
|
* Supports `include` query parameter for eager-loading relations via Drizzle.
|
|
3829
3726
|
*/
|
|
3830
|
-
/** Rows accepted by a single POST /<collection>/bulk. See `maxBulkRows`. */
|
|
3831
|
-
var DEFAULT_MAX_BULK_ROWS = 1e3;
|
|
3832
3727
|
var RestApiGenerator = class {
|
|
3833
3728
|
collections;
|
|
3834
3729
|
router;
|
|
3835
3730
|
driver;
|
|
3836
|
-
maxBulkRows;
|
|
3837
3731
|
authAdapter;
|
|
3838
|
-
constructor(collections, driver, authAdapter
|
|
3732
|
+
constructor(collections, driver, authAdapter) {
|
|
3839
3733
|
this.collections = collections;
|
|
3840
3734
|
this.driver = driver;
|
|
3841
3735
|
this.authAdapter = authAdapter;
|
|
3842
|
-
this.maxBulkRows = maxBulkRows;
|
|
3843
3736
|
this.router = new Hono();
|
|
3844
3737
|
}
|
|
3845
3738
|
/**
|
|
@@ -3924,32 +3817,6 @@ var RestApiGenerator = class {
|
|
|
3924
3817
|
if (!entity) throw ApiError.notFound("Entity not found");
|
|
3925
3818
|
return c.json(entity);
|
|
3926
3819
|
});
|
|
3927
|
-
this.router.post(`${basePath}/bulk`, async (c) => {
|
|
3928
|
-
this.enforceApiKeyPermission(c, collection.slug);
|
|
3929
|
-
const driver = this.getScopedDriver(c);
|
|
3930
|
-
const path = collection.slug;
|
|
3931
|
-
const body = await parseJsonBody(c);
|
|
3932
|
-
if (!Array.isArray(body?.rows)) throw ApiError.badRequest("Expected a JSON body of { rows: [...] }.", "INVALID_BULK_BODY");
|
|
3933
|
-
if (body.rows.length === 0) return c.json({
|
|
3934
|
-
data: [],
|
|
3935
|
-
meta: { written: 0 }
|
|
3936
|
-
});
|
|
3937
|
-
if (body.rows.some((row) => typeof row !== "object" || row === null || Array.isArray(row))) throw ApiError.badRequest("Every entry in `rows` must be an object.", "INVALID_BULK_BODY");
|
|
3938
|
-
if (body.upsert !== void 0 && typeof body.upsert !== "boolean") throw ApiError.badRequest("`upsert` must be a boolean.", "INVALID_BULK_BODY");
|
|
3939
|
-
const maxRows = this.maxBulkRows;
|
|
3940
|
-
if (body.rows.length > maxRows) throw ApiError.badRequest(`Too many rows: ${body.rows.length} exceeds the ${maxRows}-row limit for a single bulk write. Send it in chunks of ${maxRows} or fewer.`, "BULK_TOO_LARGE");
|
|
3941
|
-
if (!driver.saveMany) throw ApiError.badRequest("This collection's data source does not support bulk writes.", "BULK_UNSUPPORTED");
|
|
3942
|
-
const rows = await driver.saveMany({
|
|
3943
|
-
path,
|
|
3944
|
-
rows: body.rows,
|
|
3945
|
-
collection: resolvedCollection,
|
|
3946
|
-
upsert: body.upsert === true
|
|
3947
|
-
});
|
|
3948
|
-
return c.json({
|
|
3949
|
-
data: rows.map((row) => this.formatResponse(row)),
|
|
3950
|
-
meta: { written: rows.length }
|
|
3951
|
-
});
|
|
3952
|
-
});
|
|
3953
3820
|
this.router.post(basePath, async (c) => {
|
|
3954
3821
|
try {
|
|
3955
3822
|
this.enforceApiKeyPermission(c, collection.slug);
|
|
@@ -10996,33 +10863,6 @@ function configureLogLevel(logLevel) {
|
|
|
10996
10863
|
if (currentLevel < 0) console.error = () => {};
|
|
10997
10864
|
}
|
|
10998
10865
|
//#endregion
|
|
10999
|
-
//#region src/utils/compression.ts
|
|
11000
|
-
/**
|
|
11001
|
-
* Response compression (gzip/deflate), negotiated from `Accept-Encoding`.
|
|
11002
|
-
*
|
|
11003
|
-
* Wraps Hono's `compress` with two corrections it does not make itself:
|
|
11004
|
-
*
|
|
11005
|
-
* - **`Vary: Accept-Encoding`** on every response it guards. Without it a shared
|
|
11006
|
-
* cache may hand a gzipped body to a client that asked for identity.
|
|
11007
|
-
* - **Range responses are left alone.** `Content-Range` describes offsets into
|
|
11008
|
-
* the identity body, so compressing a 206 desyncs the framing from the bytes
|
|
11009
|
-
* actually sent.
|
|
11010
|
-
*
|
|
11011
|
-
* No brotli: `CompressionStream` has no "br", so a br-only client would fall
|
|
11012
|
-
* back to identity — every real client sends gzip too.
|
|
11013
|
-
*/
|
|
11014
|
-
function responseCompression() {
|
|
11015
|
-
const gzip = compress();
|
|
11016
|
-
return async (c, next) => {
|
|
11017
|
-
await next();
|
|
11018
|
-
const vary = c.res.headers.get("Vary");
|
|
11019
|
-
if (!vary) c.res.headers.set("Vary", "Accept-Encoding");
|
|
11020
|
-
else if (!/\baccept-encoding\b/i.test(vary)) c.res.headers.append("Vary", "Accept-Encoding");
|
|
11021
|
-
if (c.res.status === 206 || c.res.headers.has("Content-Range")) return;
|
|
11022
|
-
await gzip(c, async () => {});
|
|
11023
|
-
};
|
|
11024
|
-
}
|
|
11025
|
-
//#endregion
|
|
11026
10866
|
//#region src/utils/request-id.ts
|
|
11027
10867
|
/**
|
|
11028
10868
|
* X-Request-ID Middleware for Hono.
|
|
@@ -11085,105 +10925,9 @@ function requestLogger(options) {
|
|
|
11085
10925
|
};
|
|
11086
10926
|
}
|
|
11087
10927
|
//#endregion
|
|
11088
|
-
//#region src/api/logs-routes.ts
|
|
11089
|
-
var logs_routes_exports = /* @__PURE__ */ __exportAll({
|
|
11090
|
-
addLog: () => addLog,
|
|
11091
|
-
default: () => app,
|
|
11092
|
-
logBuffer: () => logBuffer,
|
|
11093
|
-
logMiddleware: () => logMiddleware
|
|
11094
|
-
});
|
|
11095
|
-
var LogRingBuffer = class {
|
|
11096
|
-
buffer = [];
|
|
11097
|
-
maxSize;
|
|
11098
|
-
idCounter = 0;
|
|
11099
|
-
constructor(maxSize = 1e4) {
|
|
11100
|
-
this.maxSize = maxSize;
|
|
11101
|
-
}
|
|
11102
|
-
push(entry) {
|
|
11103
|
-
const id = `log_${++this.idCounter}`;
|
|
11104
|
-
this.buffer.push({
|
|
11105
|
-
...entry,
|
|
11106
|
-
id
|
|
11107
|
-
});
|
|
11108
|
-
if (this.buffer.length > this.maxSize) this.buffer.shift();
|
|
11109
|
-
}
|
|
11110
|
-
query(options) {
|
|
11111
|
-
let filtered = this.buffer;
|
|
11112
|
-
if (options.level) filtered = filtered.filter((e) => e.level === options.level);
|
|
11113
|
-
if (options.source) filtered = filtered.filter((e) => e.source === options.source);
|
|
11114
|
-
if (options.search) {
|
|
11115
|
-
const searchLower = options.search.toLowerCase();
|
|
11116
|
-
filtered = filtered.filter((e) => e.message.toLowerCase().includes(searchLower));
|
|
11117
|
-
}
|
|
11118
|
-
if (options.since) {
|
|
11119
|
-
const sinceValue = options.since;
|
|
11120
|
-
filtered = filtered.filter((e) => e.timestamp >= sinceValue);
|
|
11121
|
-
}
|
|
11122
|
-
const sorted = [...filtered].reverse();
|
|
11123
|
-
const total = sorted.length;
|
|
11124
|
-
const limit = options.limit || 100;
|
|
11125
|
-
const offset = options.offset || 0;
|
|
11126
|
-
return {
|
|
11127
|
-
entries: sorted.slice(offset, offset + limit),
|
|
11128
|
-
total
|
|
11129
|
-
};
|
|
11130
|
-
}
|
|
11131
|
-
getLatest(count = 50) {
|
|
11132
|
-
return this.buffer.slice(-count).reverse();
|
|
11133
|
-
}
|
|
11134
|
-
};
|
|
11135
|
-
var logBuffer = new LogRingBuffer();
|
|
11136
|
-
/** Add a log entry */
|
|
11137
|
-
function addLog(level, source, message, metadata) {
|
|
11138
|
-
logBuffer.push({
|
|
11139
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
11140
|
-
level,
|
|
11141
|
-
source,
|
|
11142
|
-
message,
|
|
11143
|
-
metadata
|
|
11144
|
-
});
|
|
11145
|
-
}
|
|
11146
|
-
/** Hono middleware to log API requests */
|
|
11147
|
-
function logMiddleware() {
|
|
11148
|
-
return async (c, next) => {
|
|
11149
|
-
const start = Date.now();
|
|
11150
|
-
await next();
|
|
11151
|
-
const duration = Date.now() - start;
|
|
11152
|
-
const reqId = c.get("requestId");
|
|
11153
|
-
addLog("info", "api", `${c.req.method} ${c.req.path} ${c.res.status} ${duration}ms`, {
|
|
11154
|
-
method: c.req.method,
|
|
11155
|
-
path: c.req.path,
|
|
11156
|
-
status: c.res.status,
|
|
11157
|
-
duration,
|
|
11158
|
-
...reqId && { requestId: reqId }
|
|
11159
|
-
});
|
|
11160
|
-
};
|
|
11161
|
-
}
|
|
11162
|
-
var app = new Hono();
|
|
11163
|
-
app.get("/", (c) => {
|
|
11164
|
-
const query = c.req.query();
|
|
11165
|
-
const result = logBuffer.query({
|
|
11166
|
-
level: query.level,
|
|
11167
|
-
source: query.source,
|
|
11168
|
-
search: query.search,
|
|
11169
|
-
limit: query.limit ? parseInt(query.limit) : void 0,
|
|
11170
|
-
offset: query.offset ? parseInt(query.offset) : void 0,
|
|
11171
|
-
since: query.since
|
|
11172
|
-
});
|
|
11173
|
-
return c.json(result);
|
|
11174
|
-
});
|
|
11175
|
-
app.get("/latest", (c) => {
|
|
11176
|
-
const count = parseInt(c.req.query("count") || "50");
|
|
11177
|
-
return c.json({ entries: logBuffer.getLatest(count) });
|
|
11178
|
-
});
|
|
11179
|
-
//#endregion
|
|
11180
10928
|
//#region src/init/middlewares.ts
|
|
11181
10929
|
function configureMiddlewares(app, basePath, isProduction, config) {
|
|
11182
10930
|
app.use(`${basePath}/*`, requestId());
|
|
11183
|
-
if (config.compression !== false) {
|
|
11184
|
-
app.use(`${basePath}/*`, responseCompression());
|
|
11185
|
-
logger.info("Response compression enabled");
|
|
11186
|
-
}
|
|
11187
10931
|
const maxBodySize = config.maxBodySize ?? 10 * 1024 * 1024;
|
|
11188
10932
|
if (maxBodySize > 0) {
|
|
11189
10933
|
app.use(`${basePath}/*`, bodyLimit({
|
|
@@ -11203,7 +10947,6 @@ function configureMiddlewares(app, basePath, isProduction, config) {
|
|
|
11203
10947
|
}
|
|
11204
10948
|
if (!isProduction && !process.env.CORS_ORIGINS && !process.env.FRONTEND_URL) logger.warn("No CORS configuration detected (CORS_ORIGINS / FRONTEND_URL not set). The API will accept requests from any origin. Set CORS_ORIGINS in your .env file to restrict access.");
|
|
11205
10949
|
app.use(`${basePath}/*`, requestLogger());
|
|
11206
|
-
app.use(`${basePath}/*`, logMiddleware());
|
|
11207
10950
|
}
|
|
11208
10951
|
//#endregion
|
|
11209
10952
|
//#region src/storage/types.ts
|
|
@@ -15190,7 +14933,7 @@ var SDKQueryBuilder = class {
|
|
|
15190
14933
|
* Listen to realtime updates matching this query.
|
|
15191
14934
|
*/
|
|
15192
14935
|
listen(onUpdate, onError) {
|
|
15193
|
-
if (!this.collection.listen) throw new Error("Listen is only available when RebaseClient is configured with a websocketUrl
|
|
14936
|
+
if (!this.collection.listen) throw new Error("Listen is only available when RebaseClient is configured with a websocketUrl.");
|
|
15194
14937
|
return this.collection.listen(this.params, onUpdate, onError);
|
|
15195
14938
|
}
|
|
15196
14939
|
};
|
|
@@ -15223,17 +14966,6 @@ function createCollectionClient(transport, slug, ws) {
|
|
|
15223
14966
|
body: JSON.stringify(body)
|
|
15224
14967
|
});
|
|
15225
14968
|
},
|
|
15226
|
-
async createMany(data, options) {
|
|
15227
|
-
if (!Array.isArray(data)) throw new TypeError("createMany expects an array of records.");
|
|
15228
|
-
if (data.length === 0) return [];
|
|
15229
|
-
return (await transport.request(`${basePath}/bulk`, {
|
|
15230
|
-
method: "POST",
|
|
15231
|
-
body: JSON.stringify({
|
|
15232
|
-
rows: data,
|
|
15233
|
-
...options?.upsert ? { upsert: true } : {}
|
|
15234
|
-
})
|
|
15235
|
-
})).data || [];
|
|
15236
|
-
},
|
|
15237
14969
|
async update(id, data) {
|
|
15238
14970
|
return await transport.request(`${basePath}/${encodeURIComponent(String(id))}`, {
|
|
15239
14971
|
method: "PUT",
|
|
@@ -16485,7 +16217,7 @@ function createRebaseClient(options) {
|
|
|
16485
16217
|
});
|
|
16486
16218
|
return storageSourcesPromise;
|
|
16487
16219
|
};
|
|
16488
|
-
const resolvedWsUrl = options.
|
|
16220
|
+
const resolvedWsUrl = options.websocketUrl ?? deriveWebSocketUrl(options.baseUrl);
|
|
16489
16221
|
let ws;
|
|
16490
16222
|
if (resolvedWsUrl) {
|
|
16491
16223
|
ws = new RebaseWebSocketClient({
|
|
@@ -16597,16 +16329,6 @@ function createRebaseClient(options) {
|
|
|
16597
16329
|
createStorageSource,
|
|
16598
16330
|
fetchStorageSources,
|
|
16599
16331
|
ws,
|
|
16600
|
-
/**
|
|
16601
|
-
* Release the realtime socket and its reconnect timer.
|
|
16602
|
-
*
|
|
16603
|
-
* Until this returns, the open socket keeps the Node event loop alive
|
|
16604
|
-
* and the process will not exit on its own. Safe to call when realtime
|
|
16605
|
-
* was never started, and safe to call twice.
|
|
16606
|
-
*/
|
|
16607
|
-
close: () => {
|
|
16608
|
-
ws?.disconnect();
|
|
16609
|
-
},
|
|
16610
16332
|
setToken: transport.setToken,
|
|
16611
16333
|
setAuthTokenGetter: transport.setAuthTokenGetter,
|
|
16612
16334
|
setOnUnauthorized: transport.setOnUnauthorized,
|
|
@@ -17415,17 +17137,6 @@ async function _initializeRebaseBackend(config) {
|
|
|
17415
17137
|
config.app.route(`${basePath}/admin/backups`, backupRouter);
|
|
17416
17138
|
logger.info("Backup admin routes mounted", { path: `${basePath}/admin/backups` });
|
|
17417
17139
|
}
|
|
17418
|
-
{
|
|
17419
|
-
const { default: logsRoutes } = await Promise.resolve().then(() => logs_routes_exports);
|
|
17420
|
-
const logsRouter = new Hono();
|
|
17421
|
-
if (authAdapter && !isAuthAdapter(config.auth)) {
|
|
17422
|
-
const safeAuth = config.auth;
|
|
17423
|
-
if (safeAuth.requireAuth !== false && !!safeAuth.jwtSecret) logsRouter.use("/*", requireAuth, requireAdmin);
|
|
17424
|
-
} else if (authAdapter) logsRouter.use("/*", requireAuth, requireAdmin);
|
|
17425
|
-
logsRouter.route("/", logsRoutes);
|
|
17426
|
-
config.app.route(`${basePath}/logs`, logsRouter);
|
|
17427
|
-
logger.info("Logs routes mounted", { path: `${basePath}/logs` });
|
|
17428
|
-
}
|
|
17429
17140
|
const effectiveRealtimeService = Object.keys(realtimeServices).length > 1 ? createRoutedRealtimeService({
|
|
17430
17141
|
providers: realtimeServices,
|
|
17431
17142
|
defaultKey: defaultDriverId,
|
|
@@ -18536,11 +18247,7 @@ var rebaseEnvSchema = object({
|
|
|
18536
18247
|
DATABASE_DIRECT_URL: string().url().optional(),
|
|
18537
18248
|
DATABASE_READ_URL: string().url().optional(),
|
|
18538
18249
|
FORCE_LOCAL_STORAGE: optionalBoolString,
|
|
18539
|
-
STORAGE_TYPE: _enum([
|
|
18540
|
-
"local",
|
|
18541
|
-
"s3",
|
|
18542
|
-
"gcs"
|
|
18543
|
-
]).default("local"),
|
|
18250
|
+
STORAGE_TYPE: _enum(["local", "s3"]).default("local"),
|
|
18544
18251
|
STORAGE_PATH: string().optional(),
|
|
18545
18252
|
S3_BUCKET: string().optional(),
|
|
18546
18253
|
S3_REGION: string().optional(),
|
|
@@ -18729,11 +18436,7 @@ function serveSPA(app, config) {
|
|
|
18729
18436
|
logger.warn(" SPA serving is disabled. Build your frontend first.");
|
|
18730
18437
|
return;
|
|
18731
18438
|
}
|
|
18732
|
-
app.use("/*",
|
|
18733
|
-
app.use("/*", serveStatic({
|
|
18734
|
-
root: path$1.relative(process.cwd(), frontendPath),
|
|
18735
|
-
precompressed: true
|
|
18736
|
-
}));
|
|
18439
|
+
app.use("/*", serveStatic({ root: path$1.relative(process.cwd(), frontendPath) }));
|
|
18737
18440
|
const allExcludePaths = [apiBasePath, ...excludePaths];
|
|
18738
18441
|
let cachedHtml = null;
|
|
18739
18442
|
app.get("*", async (c, next) => {
|