Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 35 additions & 3 deletions src/database/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { DatabaseSync, type StatementSync } from "node:sqlite";

import { LruCache } from "@std/cache";
import { monotonicUlid, ulid } from "@std/ulid";
import { ulid } from "@std/ulid";

import { constantPathDatabaseFile } from "#/global.ts";
import { migrations } from "#db/migration.ts";
Expand All @@ -24,6 +24,8 @@ export class Database {
private readonly database: DatabaseSync;
private readonly store = new LruCache<string, StatementSync>(200);

private savepointId = 0;

public constructor(options: Options = {}) {
options.ephemeral ??= env.JSPB_DEBUG_DATABASE_EPHEMERAL;

Expand Down Expand Up @@ -53,7 +55,7 @@ export class Database {

for (const [delta, migration] of migrations.slice(query.user_version).entries()) {
try {
await this.transaction(async () => {
await this.transactionAsync(async () => {
await migration.preMigration?.(this);
this.exec(migration.sql);
await migration.postMigration?.(this);
Expand Down Expand Up @@ -109,7 +111,7 @@ export class Database {

public transaction<T>(callback: () => T): T {
if (this.database.isTransaction) {
const name = `_${monotonicUlid()}`;
const name = `_${++this.savepointId}`;

this.exec(`SAVEPOINT ${name};`);
Comment thread
inetol marked this conversation as resolved.
try {
Expand Down Expand Up @@ -137,6 +139,36 @@ export class Database {
}
}

public async transactionAsync<T>(callback: () => Promise<T>): Promise<T> {
if (this.database.isTransaction) {
const name = `_${++this.savepointId}`;

this.exec(`SAVEPOINT ${name};`);
try {
return await callback();
} catch (error) {
this.exec(`ROLLBACK TO ${name};`);

throw error;
} finally {
this.exec(`RELEASE ${name};`);
}
}

this.exec("BEGIN IMMEDIATE;");
try {
const result = await callback();

this.exec("COMMIT;");

return result;
} catch (error) {
this.exec("ROLLBACK;");

throw error;
}
}

public [Symbol.dispose](): void {
this.database.close();
}
Expand Down
4 changes: 2 additions & 2 deletions src/database/migration.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { mapNotNullish } from "@std/collections";
import { ulid } from "@std/ulid";

import { mutable } from "#/global.ts";
import { mutableDatabase } from "#/global.ts";
import type { Database } from "#db/index.ts";
import { Logger } from "#util/console.ts";
import { generateHash } from "#util/crypto.ts";
Expand Down Expand Up @@ -62,7 +62,7 @@ export const migrations: Migration[] = [

database.user.delete("id", userRootIdOld);

const userRootId = mutable.database.user.getRoot()?.id;
const userRootId = mutableDatabase.user.getRoot()?.id;
if (userRootId) {
database.user.update("id", userRootId, "token", userRootToken);
}
Comment thread
inetol marked this conversation as resolved.
Expand Down
6 changes: 3 additions & 3 deletions src/endpoints/document/v1/drop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { describeRoute, validator } from "@hono/openapi";
import { type } from "arktype";
import { Hono } from "hono/tiny";

import { constantHttpStatusCodes, mutable } from "#/global.ts";
import { constantHttpStatusCodes, mutableDatabase } from "#/global.ts";
import type { Env } from "#http/handler.ts";
import { authMiddleware } from "#http/middleware/authorization.ts";
import { isOwner } from "#util/document.ts";
Expand Down Expand Up @@ -41,7 +41,7 @@ export default new Hono<Env>().delete(
// @ts-expect-error upstream
} = ctx.req.valid("param") as typeof schemaParam.infer;

const document = mutable.database.document.get("name", name);
const document = mutableDatabase.document.get("name", name);
if (!document?.id) {
return errorThrow(ErrorCode.DocumentNotFound);
}
Expand All @@ -52,7 +52,7 @@ export default new Hono<Env>().delete(
return errorThrow(ErrorCode.UserInvalidToken);
}

mutable.database.document.delete("name", name);
mutableDatabase.document.delete("name", name);
void fsDelete(document);

return ctx.body(null);
Expand Down
9 changes: 4 additions & 5 deletions src/endpoints/document/v1/get.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { describeRoute, resolver, validator } from "@hono/openapi";
import { decodeTime } from "@std/ulid";
import { type } from "arktype";
import { stream } from "hono/streaming";
import { Hono } from "hono/tiny";

import { constantHttpStatusCodes, mutable } from "#/global.ts";
import { constantHttpStatusCodes, mutableDatabase } from "#/global.ts";
import type { Env } from "#http/handler.ts";
import { verifyHash } from "#util/crypto.ts";
import { ErrorCode, errorThrow, genericErrorResponse } from "#util/error.ts";
Expand Down Expand Up @@ -62,7 +61,7 @@ Note: If you only need to query the document metadata, you should use HEAD metho
validator("param", schemaParam, validatorHandler),
validator("header", schemaHeader, validatorHandler),
validator("query", schemaQuery, validatorHandler),
(ctx) => {
async (ctx) => {
const {
name
// @ts-expect-error upstream
Expand All @@ -76,7 +75,7 @@ Note: If you only need to query the document metadata, you should use HEAD metho
// @ts-expect-error upstream
} = ctx.req.valid("query") as typeof schemaQuery.infer;

const document = mutable.database.document.get("name", name);
const document = mutableDatabase.document.get("name", name);
if (!document?.id) {
return errorThrow(ErrorCode.DocumentNotFound);
}
Expand Down Expand Up @@ -108,6 +107,6 @@ Note: If you only need to query the document metadata, you should use HEAD metho

ctx.res.headers.set("transfer-encoding", "chunked");

return stream(ctx, async (stream) => await stream.pipe(await fsRead(ctx, document)));
return ctx.body(await fsRead(ctx, document));
}
);
4 changes: 2 additions & 2 deletions src/endpoints/document/v1/list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { describeRoute, resolver } from "@hono/openapi";
import { decodeTime } from "@std/ulid";
import { Hono } from "hono/tiny";

import { constantHttpStatusCodes, mutable } from "#/global.ts";
import { constantHttpStatusCodes, mutableDatabase } from "#/global.ts";
import type { Env } from "#http/handler.ts";
import { authMiddleware } from "#http/middleware/authorization.ts";
import { ErrorCode, errorThrow, genericErrorResponse } from "#util/error.ts";
Expand Down Expand Up @@ -45,7 +45,7 @@ export default new Hono<Env>().get(
return ctx.body(null);
}

const documents = mutable.database.user.getDocuments(userId).map((document) => {
const documents = mutableDatabase.user.getDocuments(userId).map((document) => {
return {
name: document.name,
created: Temporal.Instant.fromEpochMilliseconds(decodeTime(document.id)).toString()
Expand Down
14 changes: 7 additions & 7 deletions src/endpoints/document/v1/patch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { describeRoute, resolver, validator } from "@hono/openapi";
import { type } from "arktype";
import { Hono } from "hono/tiny";

import { constantHttpStatusCodes, mutable } from "#/global.ts";
import { constantHttpStatusCodes, mutableDatabase } from "#/global.ts";
import type { Env } from "#http/handler.ts";
import { authMiddleware } from "#http/middleware/authorization.ts";
import { bodyStream } from "#http/middleware/bodyStream.ts";
Expand Down Expand Up @@ -83,7 +83,7 @@ Note: To remove (nullify) a value, send the header with an empty value`,
// @ts-expect-error upstream
} = ctx.req.valid("header") as typeof schemaHeader.infer;

const document = mutable.database.document.get("name", actualName);
const document = mutableDatabase.document.get("name", actualName);
if (!document?.id) {
return errorThrow(ErrorCode.DocumentNotFound);
}
Expand All @@ -96,27 +96,27 @@ Note: To remove (nullify) a value, send the header with an empty value`,

if (newPassword !== undefined) {
if (newPassword === "") {
mutable.database.document.update("name", actualName, "password", null);
mutableDatabase.document.update("name", actualName, "password", null);
} else {
const hash = generateHash(newPassword);

mutable.database.document.update("name", actualName, "password", hash.combo);
mutableDatabase.document.update("name", actualName, "password", hash.combo);
}
}

// keep newName last thing to alter in case of race conditions
if (newName) {
if (mutable.database.document.get("name", newName)?.name) {
if (mutableDatabase.document.get("name", newName)?.name) {
return errorThrow(ErrorCode.DocumentNameAlreadyExists);
}

mutable.database.document.update("name", actualName, "name", newName);
mutableDatabase.document.update("name", actualName, "name", newName);

actualName = newName;
}

if (ctx.get("hasBody")) {
mutable.database.document.update("name", actualName, "version", env.JSPB_DOCUMENT_COMPRESSION);
mutableDatabase.document.update("name", actualName, "version", env.JSPB_DOCUMENT_COMPRESSION);
await fsWrite(ctx, document);
}

Expand Down
8 changes: 4 additions & 4 deletions src/endpoints/document/v1/post.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { monotonicUlid } from "@std/ulid";
import { type } from "arktype";
import { Hono } from "hono/tiny";

import { constantHttpStatusCodes, mutable } from "#/global.ts";
import { constantHttpStatusCodes, mutableDatabase } from "#/global.ts";
import type { Env } from "#http/handler.ts";
import { authMiddleware } from "#http/middleware/authorization.ts";
import { bodyStream } from "#http/middleware/bodyStream.ts";
Expand Down Expand Up @@ -87,7 +87,7 @@ export default new Hono<Env>().post(

let setName: string;
if (name) {
if (mutable.database.document.get("name", name)?.name) {
if (mutableDatabase.document.get("name", name)?.name) {
return errorThrow(ErrorCode.DocumentNameAlreadyExists);
}

Expand All @@ -105,7 +105,7 @@ export default new Hono<Env>().post(
hashCombo = null;
}

mutable.database.document.create({
mutableDatabase.document.create({
id: setId,
user_id: ctx.get("userId") ?? null,
version: env.JSPB_DOCUMENT_COMPRESSION,
Expand All @@ -116,7 +116,7 @@ export default new Hono<Env>().post(
try {
await fsWrite(ctx, { id: setId });
} catch (why) {
mutable.database.document.delete("id", setId);
mutableDatabase.document.delete("id", setId);

throw why;
}
Expand Down
4 changes: 2 additions & 2 deletions src/endpoints/legacy/v2/documents/access.route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { toText } from "@std/streams";
import { type } from "arktype";
import { Hono } from "hono/tiny";

import { constantHttpStatusCodes, mutable } from "#/global.ts";
import { constantHttpStatusCodes, mutableDatabase } from "#/global.ts";
import type { Env } from "#http/handler.ts";
import { verifyHash } from "#util/crypto.ts";
import { ErrorCode, errorThrow, genericErrorResponse } from "#util/error.ts";
Expand Down Expand Up @@ -74,7 +74,7 @@ export default new Hono<Env>().get(
// @ts-expect-error upstream
const header = ctx.req.valid("header") as typeof schemaHeader.infer;

const document = mutable.database.document.get("name", param.name);
const document = mutableDatabase.document.get("name", param.name);
if (!document?.id) {
return errorThrow(ErrorCode.DocumentNotFound);
}
Expand Down
9 changes: 4 additions & 5 deletions src/endpoints/legacy/v2/documents/accessRaw.route.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { describeRoute, resolver, validator } from "@hono/openapi";
import { type } from "arktype";
import { stream } from "hono/streaming";
import { Hono } from "hono/tiny";

import { constantHttpStatusCodes, mutable } from "#/global.ts";
import { constantHttpStatusCodes, mutableDatabase } from "#/global.ts";
import type { Env } from "#http/handler.ts";
import { verifyHash } from "#util/crypto.ts";
import { ErrorCode, errorThrow, genericErrorResponse } from "#util/error.ts";
Expand Down Expand Up @@ -50,7 +49,7 @@ export default new Hono<Env>().get(
validator("param", schemaParam, validatorHandler),
validator("header", schemaHeader, validatorHandler),
validator("query", schemaQuery, validatorHandler),
(ctx) => {
async (ctx) => {
// https://github.com/honojs/hono/issues/1130
if (ctx.req.method === "HEAD") {
return ctx.body(null);
Expand All @@ -66,7 +65,7 @@ export default new Hono<Env>().get(
password: header.password || query.p
};

const document = mutable.database.document.get("name", param.name);
const document = mutableDatabase.document.get("name", param.name);
if (!document?.id) {
return errorThrow(ErrorCode.DocumentNotFound);
}
Expand All @@ -83,6 +82,6 @@ export default new Hono<Env>().get(
ctx.res.headers.set("content-type", "text/plain");
ctx.res.headers.set("transfer-encoding", "chunked");

return stream(ctx, async (stream) => await stream.pipe(await fsRead(ctx, document, true)));
return ctx.body(await fsRead(ctx, document, true));
}
);
6 changes: 3 additions & 3 deletions src/endpoints/legacy/v2/documents/edit.route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { describeRoute, resolver, validator } from "@hono/openapi";
import { type } from "arktype";
import { Hono } from "hono/tiny";

import { constantHttpStatusCodes, mutable } from "#/global.ts";
import { constantHttpStatusCodes, mutableDatabase } from "#/global.ts";
import type { Env } from "#http/handler.ts";
import { bodyStream } from "#http/middleware/bodyStream.ts";
import { env } from "#util/env.ts";
Expand Down Expand Up @@ -63,12 +63,12 @@ export default new Hono<Env>().patch(
// @ts-expect-error upstream
const param = ctx.req.valid("param") as typeof schemaParam.infer;

const document = mutable.database.document.get("name", param.name);
const document = mutableDatabase.document.get("name", param.name);
if (!document?.id || document.user_id) {
return errorThrow(ErrorCode.DocumentNotFound);
}

mutable.database.document.update("name", param.name, "version", env.JSPB_DOCUMENT_COMPRESSION);
mutableDatabase.document.update("name", param.name, "version", env.JSPB_DOCUMENT_COMPRESSION);
await fsWrite(ctx, document);

return ctx.json({
Expand Down
4 changes: 2 additions & 2 deletions src/endpoints/legacy/v2/documents/exists.route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { describeRoute, resolver, validator } from "@hono/openapi";
import { type } from "arktype";
import { Hono } from "hono/tiny";

import { constantHttpStatusCodes, mutable } from "#/global.ts";
import { constantHttpStatusCodes, mutableDatabase } from "#/global.ts";
import type { Env } from "#http/handler.ts";
import { genericErrorResponse } from "#util/error.ts";
import { validatorDocumentName } from "#util/validator/document.ts";
Expand Down Expand Up @@ -43,6 +43,6 @@ export default new Hono<Env>().get(
// @ts-expect-error upstream
const param = ctx.req.valid("param") as typeof schemaParam.infer;

return ctx.text(mutable.database.document.get("name", param.name)?.name ? "true" : "false");
return ctx.text(mutableDatabase.document.get("name", param.name)?.name ? "true" : "false");
}
);
6 changes: 3 additions & 3 deletions src/endpoints/legacy/v2/documents/publish.route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
constantDocumentNameLengthMax,
constantDocumentNameLengthMin,
constantHttpStatusCodes,
mutable
mutableDatabase
} from "#/global.ts";
import type { Env } from "#http/handler.ts";
import { bodyStream } from "#http/middleware/bodyStream.ts";
Expand Down Expand Up @@ -81,7 +81,7 @@ export default new Hono<Env>().post(

let setName: string;
if (name) {
if (mutable.database.document.get("name", name)?.name) {
if (mutableDatabase.document.get("name", name)?.name) {
return errorThrow(ErrorCode.DocumentNameAlreadyExists);
}

Expand All @@ -99,7 +99,7 @@ export default new Hono<Env>().post(
hashCombo = null;
}

mutable.database.document.create({
mutableDatabase.document.create({
id: id,
user_id: null,
version: env.JSPB_DOCUMENT_COMPRESSION,
Expand Down
Loading