Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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: 37 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Graph Framework
# Hypergraph Framework

## Development

Expand All @@ -22,6 +22,18 @@ pnpm dev
# in another tab
cd apps/server
pnpm dev
# in another tab
cd apps/typesync
pnpm run dev:client
```

You can also run Typesync after building:

```sh
# Build all packages and apps first
pnpm build
# Then start Typesync
hypergraph typesync
```

Any time you make changes to the schema, you will need to run the following commands:
Expand All @@ -40,6 +52,30 @@ cd apps/next-example
pnpm dev
```

### Scaffolding a new Hypergraph application

```sh
# 1. Launch TypeSync (if it isn't already running)
hypergraph typesync

# 2. In the browser UI click **Generate App**, choose an app name (e.g. `my-app`).
# When the toast says "Application my-app generated at ./my-app" the scaffold
# is complete and all dependencies are already installed.

# 3. Run the app
cd my-app
pnpm dev
```

That's it – the generator automatically

* adds the app to `pnpm-workspace.yaml`,
* runs `pnpm install` inside the new folder, *and*
* re-installs at the workspace root so everything stays in sync.

You can immediately start hacking in [`src/routes`](my-app/src/routes) and the
Vite dev server will hot-reload your changes.

## Upgrading Dependencies

```sh
Expand Down
3 changes: 2 additions & 1 deletion apps/next-example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
"dev": "next dev --turbopack",
"build": "next build",
"start": "next start",
"lint": "next lint"
"lint": "next lint",
"prebuild": "pnpm --workspace-concurrency 1 --filter @graphprotocol/hypergraph run build && pnpm --workspace-concurrency 1 --filter @graphprotocol/hypergraph-react run build"
},
"type": "module",
"dependencies": {
Expand Down
1 change: 1 addition & 0 deletions apps/server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"scripts": {
"dev": "bun run --watch ./src/index.ts",
"prisma": "prisma",
"prebuild": "prisma generate",
"build": "tsup"
},
"dependencies": {
Expand Down
17 changes: 16 additions & 1 deletion apps/typesync/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,19 @@ hypergraph typesync --open --browser firefox
- `port` [OPTIONAL, default = 3000] port to run the application on
- example: `hypergraph typesync --port 3001`
- `browser` [OPTION, default 'browser'] browser to open the app in, if the `--open` flag is passed
- example: `hypergraph typesync --open --browser firefox`
- example: `hypergraph typesync --open --browser firefox`

## Generating & running a new app

1. Start TypeSync:
```bash
hypergraph typesync
```
2. In the UI click **Generate App** and choose a name (e.g. `awesome-app`). When the toast shows the path, the scaffold is ready and all dependencies are already installed.
3. Run it:
```bash
cd awesome-app
pnpm dev
```

No additional `pnpm install` is necessary – the generator takes care of adding the app to the workspace and installing its dependencies for you.
139 changes: 119 additions & 20 deletions apps/typesync/src/Generator.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { execSync } from 'node:child_process';
import * as NodeFileSystem from '@effect/platform-node/NodeFileSystem';
import * as FileSystem from '@effect/platform/FileSystem';
import * as Path from '@effect/platform/Path';
Expand Down Expand Up @@ -52,16 +53,68 @@ export class SchemaGenerator extends Effect.Service<SchemaGenerator>()('/typesyn
]);
// create the src directory inside
yield* fs.makeDirectory(path.join(directory, 'src'));
yield* fs.makeDirectory(path.join(directory, 'src', 'routes'));

// create the src files
yield* Effect.all([
fs.writeFileString(path.join(directory, 'src', 'index.css'), indexcss),
fs.writeFileString(path.join(directory, 'src', 'main.tsx'), mainTsx),
fs.writeFileString(path.join(directory, 'src', 'App.tsx'), appTsx),
fs.writeFileString(path.join(directory, 'src', 'vite-env.d.ts'), vitEnvDTs),
fs.writeFileString(path.join(directory, 'src', 'schema.ts'), buildSchemaFile(app)),
fs.writeFileString(path.join(directory, 'src', 'routes', '__root.tsx'), rootRouteTsx),
fs.writeFileString(path.join(directory, 'src', 'routes', 'index.tsx'), indexRouteTsx),
]);

// -----------------------------
// Post-generation helpers
// 1. Add the new directory to pnpm-workspace.yaml
// 2. Run `pnpm install` inside the new directory so deps are ready
// 3. Run `pnpm install` at repo root to update lockfile/hoist
// -----------------------------

const workspaceFile = 'pnpm-workspace.yaml';
const workspaceExists = yield* fs.exists(workspaceFile);
if (workspaceExists) {
const current = yield* fs.readFileString(workspaceFile);
const lines = current.split('\n');

const newPackageLine = ` - ${directory}`;
const alreadyExists = lines.some((line) => line.trim() === newPackageLine.trim());

if (!alreadyExists) {
const packagesLineIndex = lines.findIndex((line) => line.startsWith('packages:'));

if (packagesLineIndex !== -1) {
let lastPackageLineIndex = packagesLineIndex;
for (let i = packagesLineIndex + 1; i < lines.length; i++) {
if (lines[i].trim().startsWith('- ')) {
lastPackageLineIndex = i;
} else if (lines[i].trim() !== '') {
break;
}
}
lines.splice(lastPackageLineIndex + 1, 0, newPackageLine);
const updated = lines.join('\n');
yield* fs.writeFileString(workspaceFile, updated);
}
}
}

// helper to run a shell command synchronously (cross-platform)
const run = (cmd: string, cwd?: string) =>
Effect.sync(() => {
try {
execSync(cmd, { stdio: 'inherit', cwd });
} catch {
throw new Error(`command failed (${cmd})`);
}
});

// install deps within the new app folder
yield* run('pnpm install', directory);
// update lockfile/hoist at repo root
yield* run('pnpm install');

return { directory };
});
},
Expand Down Expand Up @@ -343,7 +396,11 @@ const prettierrc = {
singleQuote: true,
printWidth: 120,
};
const prettierignore = 'dist/';
const prettierignore = `
# Ignore artifacts:
build
dist
`;

// --------------------
// vite.config.ts
Expand Down Expand Up @@ -423,33 +480,75 @@ dist-ssr
// src/
// --------------------

const indexcss = `@import "tailwindcss";`;
const indexcss = `
@tailwind base;
@tailwind components;
@tailwind utilities;
`;

const vitEnvDTs = `/// <reference types="vite/client" />`;
const vitEnvDTs = `/// <reference types="vite/client" />
`;

const appTsx = `export default function App() {
return (
<div className="flex flex-col gap-y-8 h-full items-center justify-center py-16">
<h1>Vite + React + Hypergraph starter</h1>
const mainTsx = `import React from 'react';
import ReactDOM from 'react-dom/client';
import { RouterProvider, createRouter } from '@tanstack/react-router';
import './index.css';

<p>Import schema from '@/schema'</p>
</div>
)
// Import the generated route tree
import { routeTree } from './routeTree.gen';

// Create a new router instance
const router = createRouter({ routeTree });

// Register the router instance for type safety
declare module '@tanstack/react-router' {
interface Register {
router: typeof router;
}
}

// Render the app
const rootElement = document.getElementById('root');
if (rootElement && !rootElement.innerHTML) {
const root = ReactDOM.createRoot(rootElement);
root.render(
<React.StrictMode>
<RouterProvider router={router} />
</React.StrictMode>
);
}
`;

const mainTsx = `import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
const rootRouteTsx = `import { createRootRoute, Outlet } from '@tanstack/react-router';
import { TanStackRouterDevtools } from '@tanstack/react-router-devtools';

export const Route = createRootRoute({
component: () => (
<>
<div className="min-h-screen bg-gray-900 text-white p-4">
<h1 className="text-2xl font-bold mb-4">My Hypergraph App</h1>
<Outlet />
</div>
<TanStackRouterDevtools />
</>
),
});
`;

import './index.css'
const indexRouteTsx = `import { createFileRoute } from '@tanstack/react-router';

import App from './App.tsx'
export const Route = createFileRoute('/')({
component: Index,
});

createRoot(document.getElementById('root')!).render(
<StrictMode>
<App />
</StrictMode>,
)
function Index() {
return (
<div className="p-2">
<h3 className="text-xl">Welcome Home!</h3>
<p className="mt-2">This is your new application generated by Typesync.</p>
</div>
);
}
`;

// --------------------
Expand Down
10 changes: 8 additions & 2 deletions apps/typesync/src/Server.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
/** Defines the static file routes for serving the client dist directory with the built vite/react app */

import { dirname, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';

import * as HttpMiddleware from '@effect/platform/HttpMiddleware';
import * as HttpRouter from '@effect/platform/HttpRouter';
import * as HttpServer from '@effect/platform/HttpServer';
Expand All @@ -12,13 +15,16 @@ import * as Struct from 'effect/Struct';

import * as Api from './Api.js';

const __dirname = dirname(fileURLToPath(import.meta.url));
const clientDist = resolve(__dirname, '..', 'client', 'dist');

const FilesRouter = Effect.gen(function* () {
const path = yield* Path.Path;

return HttpRouter.empty.pipe(
HttpRouter.get(
'/',
HttpServerResponse.file(path.resolve('client', 'dist', 'index.html')).pipe(
HttpServerResponse.file(path.join(clientDist, 'index.html')).pipe(
Effect.orElse(() => HttpServerResponse.empty({ status: 404 })),
),
),
Expand All @@ -31,7 +37,7 @@ const FilesRouter = Effect.gen(function* () {
return HttpServerResponse.empty({ status: 404 });
}

const assets = path.resolve('client', 'dist', 'assets');
const assets = path.join(clientDist, 'assets');
const normalized = path.normalize(path.join(assets, ...file.value.split('/')));
if (!normalized.startsWith(assets)) {
return HttpServerResponse.empty({ status: 404 });
Expand Down
10 changes: 7 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,16 @@
"type": "module",
"packageManager": "[email protected]",
"scripts": {
"clean": "node scripts/clean.mjs",
"build": "tsc -b tsconfig.build.json && pnpm --recursive --parallel --filter \"./packages/*\" run build",
"clean": "rm -rf .turbo && rm -rf node_modules && pnpm --recursive --filter \"./packages/*\" exec rm -rf dist && pnpm --recursive --filter \"./packages/*\" exec rm -rf .turbo && pnpm --recursive --filter \"./packages/*\" exec rm -rf tsconfig.tsbuildinfo && pnpm --recursive --filter \"./apps/*\" exec rm -rf dist && pnpm --recursive --filter \"./apps/*\" exec rm -rf .turbo && pnpm --recursive --filter \"./apps/*\" exec rm -rf tsconfig.tsbuildinfo",
"dev": "pnpm --recursive --parallel --filter \"./apps/*\" run dev",
"build": "pnpm --recursive --filter \"./packages/*\" run build && pnpm --recursive --parallel --filter \"./apps/*\" run build",
"test": "vitest",
"lint": "biome check",
"lint:fix": "biome check --write --unsafe",
"check": "tsc --noEmit"
"check": "tsc --noEmit",
"db:migrate:dev": "pnpm --filter server db:migrate:dev",
"db:studio": "pnpm --filter server db:studio",
"graph": "pnpm --filter server-logic-ts graph"
},
"devDependencies": {
"@babel/cli": "^7.27.2",
Expand Down
2 changes: 1 addition & 1 deletion packages/hypergraph-react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
"types": "./dist/index.d.ts",
"sideEffects": [],
"scripts": {
"build": "tsc -b tsconfig.build.json && babel dist --plugins annotate-pure-calls --out-dir dist --source-maps && node ../../scripts/package.mjs",
"build": "tsc -b --force tsconfig.build.json && babel dist --plugins annotate-pure-calls --out-dir dist --source-maps && node ../../scripts/package.mjs",
"test": "vitest"
},
"peerDependencies": {
Expand Down
18 changes: 17 additions & 1 deletion packages/hypergraph/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,25 @@
"main": "./dist/index.js",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": "./dist/index.js",
"./connect": "./dist/connect/index.js",
"./content-addressing": "./dist/content-addressing/index.js",
"./crypto": "./dist/crypto/index.js",
"./entity": "./dist/entity/index.js",
"./identity": "./dist/identity/index.js",
"./inboxes": "./dist/inboxes/index.js",
"./key": "./dist/key/index.js",
"./messages": "./dist/messages/index.js",
"./space-events": "./dist/space-events/index.js",
"./space-info": "./dist/space-info/index.js",
"./store": "./dist/store.js",
"./store-connect": "./dist/store-connect.js",
"./types": "./dist/types.js"
},
"sideEffects": [],
"scripts": {
"build": "tsc -b tsconfig.build.json && babel dist --plugins annotate-pure-calls --out-dir dist --source-maps && node ../../scripts/package.mjs",
"build": "tsc -b --force tsconfig.build.json && babel dist --plugins annotate-pure-calls --out-dir dist --source-maps && node ../../scripts/package.mjs",
"test": "vitest"
},
"devDependencies": {
Expand Down
5 changes: 5 additions & 0 deletions pnpm-workspace.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ packages:
- apps/*
- packages/*
- docs

onlyBuiltDependencies:
- "@prisma/client"
- "@prisma/engines"
Expand All @@ -11,3 +12,7 @@ onlyBuiltDependencies:
- core-js
- core-js-pure
- prisma
- ./test3
- ./test4
- ./test5
- ./test6
Loading