19 Commits

Author SHA1 Message Date
09d85c8f22 lalalalal 2026-02-02 19:06:47 -05:00
a0ce5183b2 dkkdkdkdkd 2026-02-02 19:02:40 -05:00
c83202b681 fafafafa 2026-02-02 18:59:54 -05:00
2c1d297be1 fda 2026-02-02 18:55:31 -05:00
2d697c1e61 Move package.json files around 2026-02-02 18:47:54 -05:00
410bb671f1 fads 2026-02-02 18:41:26 -05:00
0ae197f939 fdas 2026-02-02 18:39:49 -05:00
370bea5d98 asdf 2026-02-02 18:37:09 -05:00
9d34768051 Add file list 2026-02-02 18:35:37 -05:00
b752eb5080 Make shfmt happier 2026-02-02 18:32:53 -05:00
1ed5aa4b33 Reorder some imports 2026-02-02 18:32:39 -05:00
4d1c30b874 Fix some stragglers 2026-02-02 18:31:03 -05:00
02edf259f0 Rename framework/ to diachron/ and update all references
Update paths in .gitignore, cmd, develop, mgmt, sync.sh, check.sh,
fixup.sh, CLAUDE.md, docs/new-project.md, and backend/*.sh scripts.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-02 18:10:32 -05:00
db1f2151de Rename express/ to backend/ and update references
Update paths in sync.sh, master/main.go, and CLAUDE.md to reflect
the directory rename.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-02 17:54:44 -05:00
6e669d025a Move many files to diachron subdir 2026-02-02 17:22:08 -05:00
a1dbf71de4 Rename directory 2026-02-02 16:53:22 -05:00
0afc3efa5d Fix test script to work on macOS default bash
Replace globstar (bash 4.0+) with find for portability.
macOS ships with bash 3.2 which doesn't support globstar.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-02 12:25:26 -05:00
6f2ca2c15d Tweak marketing blurb 2026-02-02 11:37:05 -05:00
6a41273835 Add macOS x86_64 platform support
Platform detection now happens in framework/platform, sourced by both
sync.sh and the node shim. Uses shasum on macOS, sha256sum on Linux.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-02 11:21:41 -05:00
122 changed files with 386 additions and 121 deletions

8
.gitignore vendored
View File

@@ -1,5 +1,5 @@
**/node_modules **/node_modules
framework/downloads diachron/downloads
framework/binaries diachron/binaries
framework/.nodejs diachron/.nodejs
framework/.nodejs-config diachron/.nodejs-config

View File

@@ -38,7 +38,7 @@ master process. Key design principles:
**Format TypeScript code:** **Format TypeScript code:**
```bash ```bash
cd express && ../cmd pnpm biome check --write . cd backend && ../cmd pnpm biome check --write .
``` ```
**Build Go master process:** **Build Go master process:**
@@ -54,9 +54,9 @@ cd master && go build
### Components ### Components
- **express/** - TypeScript/Express.js backend application - **backend/** - TypeScript/Express.js backend application
- **master/** - Go-based master process for file watching and process management - **master/** - Go-based master process for file watching and process management
- **framework/** - Managed binaries (Node.js, pnpm), command wrappers, and - **diachron/** - Managed binaries (Node.js, pnpm), command wrappers, and
framework-specific library code framework-specific library code
- **monitor/** - Go file watcher that triggers rebuilds (experimental) - **monitor/** - Go file watcher that triggers rebuilds (experimental)
@@ -68,7 +68,7 @@ Responsibilities:
- Proxy web requests to backend workers - Proxy web requests to backend workers
- Behaves identically in all environments (no dev/prod distinction) - Behaves identically in all environments (no dev/prod distinction)
### Express App Structure ### Backend App Structure
- `app.ts` - Main Express application setup with route matching - `app.ts` - Main Express application setup with route matching
- `routes.ts` - Route definitions - `routes.ts` - Route definitions
@@ -78,7 +78,7 @@ Responsibilities:
### Framework Command System ### Framework Command System
Commands flow through: `./cmd``framework/cmd.d/*``framework/shims/*` → managed binaries in `framework/binaries/` Commands flow through: `./cmd``diachron/cmd.d/*``diachron/shims/*` → managed binaries in `diachron/binaries/`
This ensures consistent tooling versions across the team without system-wide installations. This ensures consistent tooling versions across the team without system-wide installations.
@@ -94,8 +94,8 @@ This ensures consistent tooling versions across the team without system-wide ins
## Platform Requirements ## Platform Requirements
Linux x86_64 only (currently). Requires: Linux or macOS on x86_64. Requires:
- Modern libc for Go binaries - Modern libc for Go binaries (Linux)
- docker compose (for full stack) - docker compose (for full stack)
- fd, shellcheck, shfmt (for development) - fd, shellcheck, shfmt (for development)

View File

@@ -2,16 +2,13 @@ diachron
## Introduction ## Introduction
Is your answer to some of these questions "yes"? If so, you might like
diachron. (When it comes to that dev/test/prod one, hear us out first, ok?)
- Do you want to share a lot of backend and frontend code? - Do you want to share a lot of backend and frontend code?
- Are you tired of your web stack breaking when you blink too hard? - Are you tired of your web stack breaking when you blink too hard?
- Have you read [Taking PHP - Have you read [Taking PHP
Seriously](https://slack.engineering/taking-php-seriously/) and wish you had Seriously](https://slack.engineering/taking-php-seriously/) and do you wish
something similar for Typescript? you had something similar for Typescript?
- Do you think that ORMs are not all that? Do you wish you had first class - Do you think that ORMs are not all that? Do you wish you had first class
unmediated access to your database? And do you think that database unmediated access to your database? And do you think that database
@@ -35,6 +32,9 @@ diachron. (When it comes to that dev/test/prod one, hear us out first, ok?)
you're trying to fix? We're talking authentication, authorization, XSS, you're trying to fix? We're talking authentication, authorization, XSS,
https, nested paths, all that stuff. https, nested paths, all that stuff.
Is your answer to some of these questions "yes"? If so, you might like
diachron. (When it comes to that dev/test/prod one, hear us out first, ok?)
## Getting started ## Getting started
Different situations require different getting started docs. Different situations require different getting started docs.
@@ -44,9 +44,8 @@ Different situations require different getting started docs.
## Requirements ## Requirements
To run diachron, you currently need to have a Linux box running x86_64 with a To run diachron, you need Linux or macOS on x86_64. Linux requires a new
new enough libc to run golang binaries. Support for other platforms will come enough libc to run golang binaries.
eventually.
To run a more complete system, you also need to have docker compose installed. To run a more complete system, you also need to have docker compose installed.

View File

@@ -3,15 +3,13 @@ import express, {
type Response as ExpressResponse, type Response as ExpressResponse,
} from "express"; } from "express";
import { match } from "path-to-regexp"; import { match } from "path-to-regexp";
import { Session } from "./auth"; import { Session } from "./diachron/auth";
import { cli } from "./cli"; import { cli } from "./diachron/cli";
import { contentTypes } from "./content-types"; import { contentTypes } from "./diachron/content-types";
import { runWithContext } from "./context"; import { runWithContext } from "./diachron/context";
import { core } from "./core"; import { core } from "./diachron/core";
import { httpCodes } from "./http-codes"; import { httpCodes } from "./diachron/http-codes";
import { request } from "./request"; import { request } from "./diachron/request";
import { routes } from "./routes";
// import { URLPattern } from 'node:url'; // import { URLPattern } from 'node:url';
import { import {
AuthenticationRequired, AuthenticationRequired,
@@ -25,7 +23,8 @@ import {
type ProcessedRoute, type ProcessedRoute,
type Result, type Result,
type Route, type Route,
} from "./types"; } from "./diachron/types";
import { routes } from "./routes";
const app = express(); const app = express();

View File

@@ -8,7 +8,7 @@ check_dir="$DIR"
out_dir="$check_dir/out" out_dir="$check_dir/out"
source "$check_dir"/../framework/shims/common source "$check_dir"/../diachron/shims/common
source "$check_dir"/../framework/shims/node.common source "$check_dir"/../diachron/shims/node.common
$ROOT/cmd pnpm tsc --outDir "$out_dir" $ROOT/cmd pnpm tsc --outDir "$out_dir"

View File

@@ -1,5 +1,5 @@
import { DateTime } from "ts-luxon"; import { DateTime } from "ts-luxon";
import { get, User } from "../framework/hydrators/user"; import { get, User } from "../hydrators/user";
import { request } from "../request"; import { request } from "../request";
import { html, render } from "../request/util"; import { html, render } from "../request/util";
import type { Call, Result, Route } from "../types"; import type { Call, Result, Route } from "../types";

View File

@@ -113,7 +113,7 @@ async function raw<T = unknown>(
// //
// Migrations directory: express/migrations/ // Migrations directory: express/migrations/
const FRAMEWORK_MIGRATIONS_DIR = path.join(__dirname, "framework/migrations"); const FRAMEWORK_MIGRATIONS_DIR = path.join(__dirname, "diachron/migrations");
const APP_MIGRATIONS_DIR = path.join(__dirname, "migrations"); const APP_MIGRATIONS_DIR = path.join(__dirname, "migrations");
const MIGRATIONS_TABLE = "_migrations"; const MIGRATIONS_TABLE = "_migrations";

View File

@@ -1,7 +1,7 @@
import { Kysely, PostgresDialect } from "kysely"; import { Kysely, PostgresDialect } from "kysely";
import { Pool } from "pg"; import { Pool } from "pg";
import { connectionConfig } from "../../database";
import type { DB } from "../../generated/db"; import type { DB } from "../../generated/db";
import { connectionConfig } from "../database";
const db = new Kysely<DB>({ const db = new Kysely<DB>({
dialect: new PostgresDialect({ dialect: new PostgresDialect({

View File

@@ -0,0 +1 @@
export type Hydrators = {};

View File

@@ -2,7 +2,7 @@
// Run: DB_PORT=5433 DB_USER=diachron_test DB_PASSWORD=diachron_test DB_NAME=diachron_test npx tsx --test tests/*.test.ts // Run: DB_PORT=5433 DB_USER=diachron_test DB_PASSWORD=diachron_test DB_NAME=diachron_test npx tsx --test tests/*.test.ts
import { Pool } from "pg"; import { Pool } from "pg";
import { connectionConfig, migrate } from "../../../database"; import { connectionConfig, migrate } from "../../database";
const pool = new Pool(connectionConfig); const pool = new Pool(connectionConfig);

View File

@@ -1,5 +1,5 @@
// Tests for user hydrator // Tests for user hydrator
// Run with: cd express && DB_PORT=5433 DB_USER=diachron_test DB_PASSWORD=diachron_test DB_NAME=diachron_test ../cmd npx tsx --test framework/hydrators/tests/user.test.ts // Run with: cd express && DB_PORT=5433 DB_USER=diachron_test DB_PASSWORD=diachron_test DB_NAME=diachron_test ../cmd npx tsx --test diachron/hydrators/tests/user.test.ts
import assert from "node:assert/strict"; import assert from "node:assert/strict";
import { after, before, beforeEach, describe, it } from "node:test"; import { after, before, beforeEach, describe, it } from "node:test";

View File

@@ -0,0 +1,59 @@
import {
ColumnType,
Generated,
Insertable,
JSONColumnType,
Selectable,
Updateable,
} from "kysely";
import type { TypeID } from "typeid-js";
import { z } from "zod";
import { db, Hydrator } from "./hydrator";
const parser = z.object({
// id: z.uuidv7(),
id: z.uuid(),
display_name: z.string(),
// FIXME: status is duplicated elsewhere
status: z.union([
z.literal("active"),
z.literal("suspended"),
z.literal("pending"),
]),
email: z.email(),
});
const tp = parser.parse({
id: "cfae0a19-6515-4813-bc2d-1e032b72b203",
display_name: "foo",
status: "active",
email: "mw@philologue.net",
});
export type User = z.infer<typeof parser>;
const get = async (id: string): Promise<null | User> => {
const ret = await db
.selectFrom("users")
.where("users.id", "=", id)
.innerJoin("user_emails", "user_emails.user_id", "users.id")
.select([
"users.id",
"users.status",
"users.display_name",
"user_emails.email",
])
.executeTakeFirst();
if (ret === undefined) {
return null;
}
console.dir(ret);
const parsed = parser.parse(ret);
return parsed;
};
export { get };

109
backend/generated/db.d.ts vendored Normal file
View File

@@ -0,0 +1,109 @@
/**
* This file was generated by kysely-codegen.
* Please do not edit it manually.
*/
import type { ColumnType } from "kysely";
export type Generated<T> =
T extends ColumnType<infer S, infer I, infer U>
? ColumnType<S, I | undefined, U>
: ColumnType<T, T | undefined, T>;
export type Timestamp = ColumnType<Date, Date | string, Date | string>;
export interface _Migrations {
applied_at: Generated<Timestamp>;
id: Generated<number>;
name: string;
}
export interface Capabilities {
description: string | null;
id: string;
name: string;
}
export interface Groups {
created_at: Generated<Timestamp>;
id: string;
name: string;
}
export interface RoleCapabilities {
capability_id: string;
granted_at: Generated<Timestamp>;
revoked_at: Timestamp | null;
role_id: string;
}
export interface Roles {
description: string | null;
id: string;
name: string;
}
export interface Sessions {
auth_method: string;
created_at: Generated<Timestamp>;
expires_at: Timestamp;
id: Generated<string>;
ip_address: string | null;
is_used: Generated<boolean | null>;
revoked_at: Timestamp | null;
token_hash: string;
token_type: string;
user_agent: string | null;
user_email_id: string | null;
user_id: string;
}
export interface UserCredentials {
created_at: Generated<Timestamp>;
credential_type: Generated<string>;
id: string;
password_hash: string | null;
updated_at: Generated<Timestamp>;
user_id: string;
}
export interface UserEmails {
created_at: Generated<Timestamp>;
email: string;
id: string;
is_primary: Generated<boolean>;
is_verified: Generated<boolean>;
normalized_email: string;
revoked_at: Timestamp | null;
user_id: string;
verified_at: Timestamp | null;
}
export interface UserGroupRoles {
granted_at: Generated<Timestamp>;
group_id: string;
revoked_at: Timestamp | null;
role_id: string;
user_id: string;
}
export interface Users {
created_at: Generated<Timestamp>;
display_name: string | null;
id: string;
status: Generated<string>;
updated_at: Generated<Timestamp>;
}
export interface DB {
_migrations: _Migrations;
capabilities: Capabilities;
groups: Groups;
role_capabilities: RoleCapabilities;
roles: Roles;
sessions: Sessions;
user_credentials: UserCredentials;
user_emails: UserEmails;
user_group_roles: UserGroupRoles;
users: Users;
}

View File

@@ -0,0 +1 @@
CREATE TABLE test_application_table ();

View File

@@ -0,0 +1 @@
CREATE TABLE test_application_table ();

20
backend/package.json Normal file
View File

@@ -0,0 +1,20 @@
{
"name": "my app",
"version": "0.0.1",
"description": "",
"main": "index.js",
"scripts": {
"test": "DB_PORT=5433 DB_USER=diachron_test DB_PASSWORD=diachron_test DB_NAME=diachron_test tsx --test '**/*.{test,spec}.ts'",
"test:watch": "DB_PORT=5433 DB_USER=diachron_test DB_PASSWORD=diachron_test DB_NAME=diachron_test tsx --test --watch '**/*.{test,spec}.ts'",
"nodemon": "nodemon dist/index.js",
"kysely-codegen": "kysely-codegen"
},
"keywords": [],
"author": "",
"license": "ISC",
"packageManager": "pnpm@10.12.4",
"dependencies": {
},
"devDependencies": {
}
}

View File

@@ -0,0 +1,2 @@
packages:
- 'diachron'

View File

@@ -2,13 +2,13 @@
import nunjucks from "nunjucks"; import nunjucks from "nunjucks";
import { DateTime } from "ts-luxon"; import { DateTime } from "ts-luxon";
import { authRoutes } from "./auth/routes"; import { authRoutes } from "./diachron/auth/routes";
import { routes as basicRoutes } from "./basic/routes"; import { routes as basicRoutes } from "./diachron/basic/routes";
import { contentTypes } from "./content-types"; import { contentTypes } from "./diachron/content-types";
import { core } from "./core"; import { core } from "./diachron/core";
import { multiHandler } from "./handlers"; import { multiHandler } from "./diachron/handlers";
import { httpCodes } from "./http-codes"; import { httpCodes } from "./diachron/http-codes";
import type { Call, Result, Route } from "./types"; import type { Call, Result, Route } from "./diachron/types";
// FIXME: Obviously put this somewhere else // FIXME: Obviously put this somewhere else
const okText = (result: string): Result => { const okText = (result: string): Result => {

View File

@@ -6,7 +6,7 @@ DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
check_dir="$DIR" check_dir="$DIR"
source "$check_dir"/../framework/shims/common source "$check_dir"/../diachron/shims/common
source "$check_dir"/../framework/shims/node.common source "$check_dir"/../diachron/shims/node.common
$ROOT/cmd pnpm tsc --showConfig $ROOT/cmd pnpm tsc --showConfig

View File

@@ -9,5 +9,6 @@
"strict": true, "strict": true,
"types": ["node"], "types": ["node"],
"outDir": "out" "outDir": "out"
} },
"exclude": ["**/*.spec.ts", "**/*.test.ts"]
} }

View File

@@ -6,8 +6,8 @@ DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
check_dir="$DIR" check_dir="$DIR"
source "$check_dir"/../framework/shims/common source "$check_dir"/../diachron/shims/common
source "$check_dir"/../framework/shims/node.common source "$check_dir"/../diachron/shims/node.common
# $ROOT/cmd pnpm tsc --lib ES2023 --esModuleInterop -w $check_dir/app.ts # $ROOT/cmd pnpm tsc --lib ES2023 --esModuleInterop -w $check_dir/app.ts
# $ROOT/cmd pnpm tsc -w $check_dir/app.ts # $ROOT/cmd pnpm tsc -w $check_dir/app.ts

View File

@@ -10,7 +10,7 @@ cd "$DIR"
# #
exclusions="SC2002" exclusions="SC2002"
source "$DIR/framework/versions" source "$DIR/diachron/versions"
if [[ $# -ne 0 ]]; then if [[ $# -ne 0 ]]; then
shellcheck --exclude="$exclusions" "$@" shellcheck --exclude="$exclusions" "$@"
@@ -20,10 +20,10 @@ fi
shell_scripts="$(fd .sh | xargs)" shell_scripts="$(fd .sh | xargs)"
# The files we need to check all either end in .sh or else they're the files # The files we need to check all either end in .sh or else they're the files
# in framework/cmd.d and framework/shims. -x instructs shellcheck to also # in diachron/cmd.d and diachron/shims. -x instructs shellcheck to also
# check `source`d files. # check `source`d files.
shellcheck -x --exclude="$exclusions" "$DIR/cmd" "$DIR"/framework/cmd.d/* "$DIR"/framework/shims/* "$shell_scripts" shellcheck -x --exclude="$exclusions" "$DIR/cmd" "$DIR"/diachron/cmd.d/* "$DIR"/diachron/shims/* "$shell_scripts"
pushd "$DIR/master" pushd "$DIR/master"
docker run --rm -v $(pwd):/app -w /app golangci/golangci-lint:$golangci_lint golangci-lint run docker run --rm -v $(pwd):/app -w /app golangci/golangci-lint:$golangci_lint golangci-lint run

4
cmd
View File

@@ -13,7 +13,7 @@ if [ $# -lt 1 ]; then
echo "Usage: ./cmd <command> [args...]" echo "Usage: ./cmd <command> [args...]"
echo "" echo ""
echo "Available commands:" echo "Available commands:"
for cmd in "$DIR"/framework/cmd.d/*; do for cmd in "$DIR"/diachron/cmd.d/*; do
if [ -x "$cmd" ]; then if [ -x "$cmd" ]; then
basename "$cmd" basename "$cmd"
fi fi
@@ -24,4 +24,4 @@ fi
subcmd="$1" subcmd="$1"
shift shift
exec "$DIR"/framework/cmd.d/"$subcmd" "$@" exec "$DIR"/diachron/cmd.d/"$subcmd" "$@"

View File

@@ -13,7 +13,7 @@ if [ $# -lt 1 ]; then
echo "Usage: ./develop <command> [args...]" echo "Usage: ./develop <command> [args...]"
echo "" echo ""
echo "Available commands:" echo "Available commands:"
for cmd in "$DIR"/framework/develop.d/*; do for cmd in "$DIR"/diachron/develop.d/*; do
if [ -x "$cmd" ]; then if [ -x "$cmd" ]; then
basename "$cmd" basename "$cmd"
fi fi
@@ -24,4 +24,4 @@ fi
subcmd="$1" subcmd="$1"
shift shift
exec "$DIR"/framework/develop.d/"$subcmd" "$@" exec "$DIR"/diachron/develop.d/"$subcmd" "$@"

0
diachron/.nodejs/.gitignore vendored Normal file
View File

0
diachron/binaries/.gitignore vendored Normal file
View File

15
diachron/cmd.d/test Executable file
View File

@@ -0,0 +1,15 @@
#!/bin/bash
set -eu
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$DIR/../../backend"
if [ $# -eq 0 ]; then
# Find all test files - use -print0/xargs to handle filenames safely
find . -type f \( -name '*.spec.ts' -o -name '*.test.ts' \) -print0 |
xargs -0 "$DIR"/../shims/pnpm tsx --test
else
"$DIR"/../shims/pnpm tsx --test "$@"
fi

View File

@@ -5,5 +5,5 @@ set -eu
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOT="$DIR/../.." ROOT="$DIR/../.."
cd "$ROOT/express" cd "$ROOT/backend"
"$DIR"/tsx migrate.ts "$@" "$DIR"/tsx migrate.ts "$@"

Some files were not shown because too many files have changed in this diff Show More