This commit is contained in:
Morten Olsen
2025-11-03 13:05:37 +01:00
commit 40187c9a8a
41 changed files with 6642 additions and 0 deletions

3
.dockerignore Normal file
View File

@@ -0,0 +1,3 @@
node_modules/
dist/

48
.github/release-drafter-config.yml vendored Normal file
View File

@@ -0,0 +1,48 @@
name-template: '$RESOLVED_VERSION 🌈'
tag-template: '$RESOLVED_VERSION'
categories:
- title: '🚀 Features'
labels:
- 'feature'
- 'enhancement'
- title: '🐛 Bug Fixes'
labels:
- 'fix'
- 'bugfix'
- 'bug'
- title: '🧰 Maintenance'
label: 'chore'
change-template: '- $TITLE @$AUTHOR (#$NUMBER)'
change-title-escapes: '\<*_&' # You can add # and @ to disable mentions, and add ` to disable code blocks.
version-resolver:
major:
labels:
- 'major'
minor:
labels:
- 'minor'
patch:
labels:
- 'patch'
default: patch
autolabeler:
- label: 'chore'
files:
- '*.md'
branch:
- '/docs{0,1}\/.+/'
- label: 'bug'
branch:
- '/fix\/.+/'
title:
- '/fix/i'
- label: 'enhancement'
branch:
- '/feature\/.+/'
- '/feat\/.+/'
title:
- '/feat:.+/'
template: |
## Changes
$CHANGES

21
.github/workflows/auto-labeler.yaml vendored Normal file
View File

@@ -0,0 +1,21 @@
name: Auto Labeler
on:
pull_request:
types: [opened, reopened, synchronize]
permissions:
contents: read
jobs:
auto-labeler:
permissions:
contents: write
pull-requests: write
runs-on: ubuntu-latest
steps:
- uses: release-drafter/release-drafter@v6
with:
config-name: release-drafter-config.yml
disable-releaser: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

55
.github/workflows/job-build.yaml vendored Normal file
View File

@@ -0,0 +1,55 @@
name: Build
on:
workflow_call:
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '${{ env.NODE_VERSION }}'
registry-url: '${{ env.NODE_REGISTRY }}'
- uses: pnpm/action-setup@v4
name: Install pnpm
with:
version: ${{ env.PNPM_VERSION }}
run_install: false
- name: Get pnpm store directory
shell: bash
run: |
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
- uses: actions/cache@v4
name: Setup pnpm cache
with:
path: ${{ env.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
- name: Install dependencies
run: pnpm install
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Build
run: pnpm build
- name: Run tests
run: pnpm test
- uses: actions/upload-artifact@v4
with:
name: lib
retention-days: 5
path: |
packages/*/dist
extensions/*/dist
server/*/dist
package.json
README.md

View File

@@ -0,0 +1,18 @@
name: Draft release
on:
workflow_call:
jobs:
draft-release:
name: Update release drafter
permissions:
contents: write
pull-requests: write
environment: release
runs-on: ubuntu-latest
steps:
- uses: release-drafter/release-drafter@v6
with:
config-name: release-drafter-config.yml
publish: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

115
.github/workflows/pipeline-default.yaml vendored Normal file
View File

@@ -0,0 +1,115 @@
name: Build and release
on:
push:
branches:
- main
pull_request:
types:
- opened
- synchronize
env:
environment: test
release_channel: latest
DO_NOT_TRACK: "1"
NODE_VERSION: "23.x"
NODE_REGISTRY: "https://registry.npmjs.org"
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
DOCKER_REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
PNPM_VERSION: 10.6.0
permissions:
contents: write
packages: read
pull-requests: write
id-token: write
actions: read
security-events: write
jobs:
build:
uses: ./.github/workflows/job-build.yaml
name: Build
update-release-draft:
needs: build
if: github.ref == 'refs/heads/main'
uses: ./.github/workflows/job-draft-release.yaml
release:
permissions:
contents: read
packages: write
attestations: write
id-token: write
pages: write
name: Release
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
needs: update-release-draft
environment: release
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
# - uses: actions/setup-node@v4
# with:
# node-version: '${{ env.NODE_VERSION }}'
# registry-url: '${{ env.NODE_REGISTRY }}'
#
# - uses: pnpm/action-setup@v4
# name: Install pnpm
# with:
# version: ${{ env.PNPM_VERSION }}
# run_install: false
#
# - name: Install dependencies
# run: pnpm install
# env:
# NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
#
# - uses: actions/download-artifact@v4
# with:
# name: lib
# path: ./
#
# - name: Publish to npm
# run: |
# git config user.name "Github Actions Bot"
# git config user.email "<>"
# node ./scripts/set-version.mjs $(git describe --tag --abbrev=0)
# pnpm publish -r --no-git-checks --access public
# env:
# NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Log in to the Container registry
uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1
with:
registry: ${{ env.DOCKER_REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7
with:
images: ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}
- name: Build and push Docker image
id: push
uses: docker/build-push-action@f2a1d5e99d037542a71f64918e516c093c6f3fc4
with:
context: .
file: ./packages/server/Dockerfile
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
# - name: Generate artifact attestation
# uses: actions/attest-build-provenance@v2
# with:
# subject-name: ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME}}
# subject-digest: ${{ steps.push.outputs.digest }}
# push-to-registry: true

4
.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
/node_modules
.turbo/
/.env
/coverage/

18
.prettierrc.json Normal file
View File

@@ -0,0 +1,18 @@
{
"arrowParens": "always",
"bracketSpacing": true,
"htmlWhitespaceSensitivity": "css",
"insertPragma": false,
"bracketSameLine": false,
"jsxSingleQuote": false,
"printWidth": 120,
"proseWrap": "preserve",
"quoteProps": "as-needed",
"requirePragma": false,
"semi": true,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "all",
"useTabs": false,
"singleAttributePerLine": false
}

41
.u8.json Normal file
View File

@@ -0,0 +1,41 @@
{
"values": {
"monoRepo": true,
"packagePrefix": "@morten-olsen/reservoir-",
"packageVersion": "1.0.0"
},
"entries": [
{
"timestamp": "2025-11-03T09:33:18.994Z",
"template": "monorepo",
"values": {
"monoRepo": true,
"packagePrefix": "@morten-olsen/reservoir-",
"packageVersion": "1.0.0"
}
},
{
"timestamp": "2025-11-03T09:33:32.155Z",
"template": "eslint",
"values": {
"monoRepo": true,
"packagePrefix": "@morten-olsen/reservoir-",
"packageVersion": "1.0.0",
"target-pkg": {
"name": "@morten-olsen/reservoir-repo",
"dir": "/Users/alice/Projects/private/incubator/reservoir"
}
}
},
{
"timestamp": "2025-11-03T09:33:49.149Z",
"template": "pkg",
"values": {
"monoRepo": true,
"packagePrefix": "@morten-olsen/reservoir-",
"packageVersion": "1.0.0",
"packageName": "server"
}
}
]
}

9
docker-compose.yaml Normal file
View File

@@ -0,0 +1,9 @@
name: reservoir
services:
app:
build:
context: .
dockerfile: ./packages/server/Dockerfile
ports:
- 9111:9111

52
eslint.config.mjs Normal file
View File

@@ -0,0 +1,52 @@
import { defineConfig } from 'eslint/config';
import { FlatCompat } from '@eslint/eslintrc';
import importPlugin from 'eslint-plugin-import';
import eslint from '@eslint/js';
import eslintConfigPrettier from 'eslint-config-prettier';
import tseslint from 'typescript-eslint';
const compat = new FlatCompat({
baseDirectory: import.meta.__dirname,
resolvePluginsRelativeTo: import.meta.__dirname,
});
export default defineConfig(
eslint.configs.recommended,
...tseslint.configs.recommended,
...tseslint.configs.stylistic,
eslintConfigPrettier,
{
files: ['**/*.{ts,tsxx}'],
extends: [importPlugin.flatConfigs.recommended, importPlugin.flatConfigs.typescript],
rules: {
'import/no-unresolved': 'off',
'import/extensions': ['error', 'ignorePackages'],
'import/exports-last': 'error',
'import/no-default-export': 'error',
'import/order': [
'error',
{
groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index'],
'newlines-between': 'always',
},
],
'import/no-duplicates': 'error',
},
},
{
rules: {
'@typescript-eslint/consistent-type-definitions': ['error', 'type'],
},
},
{
files: ['**.d.ts'],
rules: {
'@typescript-eslint/triple-slash-reference': 'off',
'@typescript-eslint/consistent-type-definitions': 'off',
},
},
...compat.extends('plugin:prettier/recommended'),
{
ignores: ['**/node_modules/', '**/dist/', '**/.turbo/', '**/generated/'],
},
);

33
package.json Normal file
View File

@@ -0,0 +1,33 @@
{
"version": "1.0.0",
"name": "@morten-olsen/reservoir-repo",
"private": true,
"type": "module",
"scripts": {
"test:lint": "eslint",
"build": "turbo build",
"build:dev": "tsc --build --watch",
"test:unit": "vitest --run --coverage --passWithNoTests",
"test": "pnpm run \"/^test:.+/\""
},
"packageManager": "pnpm@10.6.0",
"workspaces": [
"packages/*",
"apps/*"
],
"devDependencies": {
"turbo": "2.6.0",
"typescript": "5.9.3",
"vitest": "4.0.6",
"@vitest/coverage-v8": "4.0.6",
"@eslint/eslintrc": "3.3.1",
"@eslint/js": "9.39.0",
"@pnpm/find-workspace-packages": "6.0.9",
"eslint": "9.39.0",
"eslint-config-prettier": "10.1.8",
"eslint-plugin-import": "2.32.0",
"eslint-plugin-prettier": "5.5.4",
"prettier": "3.6.2",
"typescript-eslint": "8.46.2"
}
}

View File

@@ -0,0 +1,4 @@
{
"name": "@morten-olsen/reservoir-configs",
"version": "1.0.0"
}

View File

@@ -0,0 +1,20 @@
{
"compilerOptions": {
"target": "ESNext",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"strict": true,
"resolveJsonModule": true,
"allowSyntheticDefaultImports": true,
"skipLibCheck": true,
"noEmit": true,
"jsx": "react-jsx",
"isolatedModules": true,
"verbatimModuleSyntax": true,
"erasableSyntaxOnly": true,
"allowImportingTsExtensions": true
}
}

4
packages/server/.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
/node_modules/
/dist/
/coverage/
/.env

View File

@@ -0,0 +1,27 @@
FROM node:23-slim AS base
RUN corepack enable
WORKDIR /app
FROM base AS builder
RUN npm i -g turbo
COPY . .
RUN turbo prune @morten-olsen/reservoir-server --docker
FROM base AS installer
COPY --from=builder /app/out/json/ .
RUN pnpm install --prod --frozen-lockfile
COPY --from=builder /app/out/full/ .
FROM base AS runner
ENV \
SERVER_HOST=0.0.0.0 \
DB_URL=/data/db.sqlite
RUN \
addgroup --system --gid 1001 nodejs \
&& adduser --system --uid 1001 nodejs \
&& mkdir /data \
&& chown nodejs:nodejs /data
USER nodejs
COPY --from=installer /app /app
CMD ["node", "/app/packages/server/src/start.ts"]

View File

@@ -0,0 +1,42 @@
{
"type": "module",
"main": "dist/exports.js",
"scripts": {
"build": "tsc --build",
"test:unit": "vitest --run --passWithNoTests",
"test": "pnpm run \"/^test:/\""
},
"packageManager": "pnpm@10.6.0",
"files": [
"dist"
],
"exports": {
".": "./dist/exports.js"
},
"devDependencies": {
"@morten-olsen/reservoir-configs": "workspace:*",
"@morten-olsen/reservoir-tests": "workspace:*",
"@types/node": "24.10.0",
"@vitest/coverage-v8": "4.0.6",
"typescript": "5.9.3",
"vitest": "4.0.6"
},
"name": "@morten-olsen/reservoir-server",
"version": "1.0.0",
"imports": {
"#root/*": "./src/*"
},
"dependencies": {
"@fastify/swagger": "^9.5.2",
"@scalar/fastify-api-reference": "^1.38.1",
"better-sqlite3": "^12.4.1",
"fast-deep-equal": "^3.1.3",
"fastify": "^5.6.1",
"fastify-type-provider-zod": "^6.1.0",
"knex": "^3.1.0",
"pg": "^8.16.3",
"pino": "^10.1.0",
"pino-pretty": "^13.1.2",
"zod": "^4.1.12"
}
}

View File

@@ -0,0 +1,36 @@
import { z } from 'zod';
import type { FastifyPluginAsyncZod } from 'fastify-type-provider-zod';
import { DocumentsService } from '#root/services/documents/documents.ts';
import {
upsertDocumentRequestSchema,
upsertDocumentResponseSchema,
} from '#root/services/documents/documents.schemas.ts';
const documentsPlugin: FastifyPluginAsyncZod = async (app) => {
app.route({
method: 'POST',
url: '',
schema: {
operationId: 'v1.documents.put',
tags: ['documents'],
summary: 'Upsert documents',
body: z.object({
items: z.array(upsertDocumentRequestSchema),
}),
response: {
200: z.object({
items: z.array(upsertDocumentResponseSchema),
}),
},
},
handler: async (req, reply) => {
const documentsService = app.services.get(DocumentsService);
const { items } = req.body;
const results = await Promise.all(items.map((item) => documentsService.upsert(item)));
return reply.send({ items: results });
},
});
};
export { documentsPlugin };

View File

@@ -0,0 +1,52 @@
import fastify from 'fastify';
import fastifySwagger from '@fastify/swagger';
import fastifyScalar from '@scalar/fastify-api-reference';
import { jsonSchemaTransform, serializerCompiler, validatorCompiler } from 'fastify-type-provider-zod';
import { documentsPlugin } from './api.documents.ts';
import { Services } from '#root/utils/utils.services.ts';
import { DatabaseService } from '#root/database/database.ts';
const createApi = async (services: Services = new Services()) => {
const db = services.get(DatabaseService);
await db.ready();
const app = fastify({
logger: {
level: 'warn',
transport: {
target: 'pino-pretty',
},
},
});
app.setValidatorCompiler(validatorCompiler);
app.setSerializerCompiler(serializerCompiler);
app.decorate('services', services);
await app.register(fastifySwagger, {
openapi: {
info: {
title: 'Reservoir',
version: '1.0.0',
},
servers: [],
},
transform: jsonSchemaTransform,
});
await app.register(fastifyScalar, {
routePrefix: '/docs',
});
await app.register(documentsPlugin, {
prefix: '/api/v1/documents',
});
app.addHook('onReady', async () => {
app.swagger();
});
await app.ready();
return app;
};
export { createApi };

View File

@@ -0,0 +1,10 @@
class ConfigService {
public get database() {
return {
client: process.env.DB_CLIENT || 'better-sqlite3',
connection: process.env.DB_URL || ':memory:',
};
}
}
export { ConfigService };

View File

@@ -0,0 +1,52 @@
import knex, { type Knex } from 'knex';
import { migrationSource } from './migrations/migrations.ts';
import { destroy, Services } from '#root/utils/utils.services.ts';
import { ConfigService } from '#root/config/config.ts';
class DatabaseService {
#services: Services;
#instance?: Promise<Knex>;
constructor(services: Services) {
this.#services = services;
}
#setup = async () => {
const configService = this.#services.get(ConfigService);
const db = knex({
client: configService.database.client,
connection: configService.database.connection,
useNullAsDefault: true,
});
await db.migrate.latest({
migrationSource,
});
return db;
};
public getInstance = () => {
if (!this.#instance) {
this.#instance = this.#setup();
}
return this.#instance;
};
[destroy] = async () => {
if (!this.#instance) {
return;
}
const instance = await this.#instance;
await instance.destroy();
};
public ready = async () => {
await this.getInstance();
};
}
export { tableNames, type Tables } from './migrations/migrations.ts';
export { DatabaseService };

View File

@@ -0,0 +1,40 @@
import type { Migration } from './migrations.types.ts';
const tableNames = {
documents: 'documents',
};
const init: Migration = {
name: 'init',
up: async (knex) => {
await knex.schema.createTable(tableNames.documents, (table) => {
table.string('id').notNullable();
table.string('type').notNullable();
table.string('source').nullable();
table.jsonb('data').notNullable();
table.datetime('createdAt').notNullable();
table.datetime('updatedAt').notNullable();
table.datetime('deletedAt').nullable();
});
},
down: async (knex) => {
await knex.schema.dropTableIfExists(tableNames.documents);
},
};
type DocumentRow = {
id: string;
type: string;
source: string | null;
data: string;
createdAt: string;
updatedAt: string;
deletedAt: string | null;
};
type Tables = {
document: DocumentRow;
};
export type { Tables };
export { init, tableNames };

View File

@@ -0,0 +1,15 @@
import type { Knex } from 'knex';
import { init, tableNames, type Tables } from './migrations.001-init.ts';
import type { Migration } from './migrations.types.ts';
const migrations = [init];
const migrationSource: Knex.MigrationSource<Migration> = {
getMigration: async (migration) => migration,
getMigrationName: (migration: Migration) => migration.name,
getMigrations: async () => migrations,
};
export { tableNames, type Tables };
export { migrationSource };

View File

@@ -0,0 +1,9 @@
import type { Knex } from 'knex';
type Migration = {
name: string;
up: (knex: Knex) => Promise<void>;
down: (knex: Knex) => Promise<void>;
};
export type { Migration };

9
packages/server/src/global.d.ts vendored Normal file
View File

@@ -0,0 +1,9 @@
import 'fastify';
import type { Services } from './utils/utils.services.ts';
declare module 'fastify' {
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
interface FastifyInstance {
services: Services;
}
}

View File

@@ -0,0 +1,22 @@
import { z } from 'zod';
const upsertDocumentRequestSchema = z.object({
id: z.string().min(1).optional(),
type: z.string().min(1),
source: z.string().min(1).nullable(),
data: z.unknown(),
});
type UpsertDocumentRequest = z.infer<typeof upsertDocumentRequestSchema>;
const upsertDocumentResponseSchema = upsertDocumentRequestSchema.extend({
createdAt: z.iso.datetime(),
updatedAt: z.iso.datetime(),
deletedAt: z.iso.datetime().nullable(),
action: z.enum(['inserted', 'updated', 'skipped']),
});
type UpsertDocumentResponse = z.input<typeof upsertDocumentResponseSchema>;
export type { UpsertDocumentRequest, UpsertDocumentResponse };
export { upsertDocumentRequestSchema, upsertDocumentResponseSchema };

View File

@@ -0,0 +1,77 @@
import equal from 'fast-deep-equal';
import type { UpsertDocumentRequest, UpsertDocumentResponse } from './documents.schemas.ts';
import { DatabaseService, tableNames, type Tables } from '#root/database/database.ts';
import type { Services } from '#root/utils/utils.services.ts';
class DocumentsService {
#services: Services;
constructor(services: Services) {
this.#services = services;
}
public upsert = async (document: UpsertDocumentRequest): Promise<UpsertDocumentResponse> => {
const dbService = this.#services.get(DatabaseService);
const db = await dbService.getInstance();
const id = document.id || crypto.randomUUID();
const [current] = await db<Tables['document']>(tableNames).where({
id,
type: document.type,
});
const now = new Date();
if (!current) {
await db<Tables['document']>(tableNames.documents).insert({
id,
type: document.type,
createdAt: now.toISOString(),
updatedAt: now.toISOString(),
data: JSON.stringify(document.data),
});
return {
data: document.data,
id,
type: document.type,
source: document.source || null,
createdAt: now.toISOString(),
updatedAt: now.toISOString(),
deletedAt: null,
action: 'inserted',
};
}
const currentData = JSON.parse(current.data);
if (equal(currentData, document.data)) {
return {
...current,
data: currentData,
id,
createdAt: current.createdAt,
updatedAt: current.updatedAt,
deletedAt: current.deletedAt || null,
action: 'skipped',
};
}
await db<Tables['document']>(tableNames.documents)
.update({
source: document.source,
data: JSON.stringify(document.data),
updatedAt: now.toISOString(),
})
.where({ id, type: document.type });
return {
...current,
id,
data: document.data,
createdAt: current.createdAt,
updatedAt: now.toISOString(),
deletedAt: current.deletedAt || null,
action: 'updated',
};
};
}
export { DocumentsService };

View File

@@ -0,0 +1,7 @@
import { createApi } from './api/api.ts';
const app = await createApi();
await app.listen({
port: 9111,
host: process.env.SERVER_HOST,
});

View File

@@ -0,0 +1,51 @@
const destroy = Symbol('destroy');
const instanceKey = Symbol('instances');
type ServiceDependency<T> = new (services: Services) => T & {
[destroy]?: () => Promise<void> | void;
};
class Services {
[instanceKey]: Map<ServiceDependency<unknown>, unknown>;
constructor() {
this[instanceKey] = new Map();
}
public get = <T>(service: ServiceDependency<T>) => {
if (!this[instanceKey].has(service)) {
this[instanceKey].set(service, new service(this));
}
const instance = this[instanceKey].get(service);
if (!instance) {
throw new Error('Could not generate instance');
}
return instance as T;
};
public set = <T>(service: ServiceDependency<T>, instance: Partial<T>) => {
this[instanceKey].set(service, instance);
};
public clone = () => {
const services = new Services();
services[instanceKey] = Object.fromEntries(this[instanceKey].entries());
};
public destroy = async () => {
await Promise.all(
this[instanceKey].values().map(async (instance) => {
if (
typeof instance === 'object' &&
instance &&
destroy in instance &&
typeof instance[destroy] === 'function'
) {
await instance[destroy]();
}
}),
);
};
}
export { Services, destroy };

View File

@@ -0,0 +1,10 @@
{
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src"
},
"include": [
"src/**/*.ts"
],
"extends": "@morten-olsen/reservoir-configs/tsconfig.json"
}

View File

@@ -0,0 +1,12 @@
import { defineConfig } from 'vitest/config';
import { getAliases } from '@morten-olsen/reservoir-tests/vitest';
// eslint-disable-next-line import/no-default-export
export default defineConfig(async () => {
const aliases = await getAliases();
return {
resolve: {
alias: aliases,
},
};
});

4
packages/tests/.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
/node_modules
/dist
/coverage
/.env

View File

@@ -0,0 +1,27 @@
{
"type": "module",
"main": "dist/exports.js",
"scripts": {
"build": "tsc --build"
},
"packageManager": "pnpm@10.6.0",
"files": [
"dist"
],
"exports": {
".": "./dist/exports.js",
"./vitest": "./dist/vitest.js"
},
"devDependencies": {
"@types/node": "24.10.0",
"@vitest/coverage-v8": "4.0.6",
"typescript": "5.9.3",
"vitest": "4.0.6",
"@morten-olsen/reservoir-configs": "workspace:*"
},
"dependencies": {
"@pnpm/find-workspace-packages": "6.0.9"
},
"name": "@morten-olsen/reservoir-tests",
"version": "1.0.0"
}

View File

@@ -0,0 +1 @@
console.log('Hello World');

View File

@@ -0,0 +1,10 @@
import { resolve } from 'node:path';
import { findWorkspacePackages } from '@pnpm/find-workspace-packages';
const getAliases = async () => {
const packages = await findWorkspacePackages(process.cwd());
return Object.fromEntries(packages.map((pkg) => [pkg.manifest.name, resolve(pkg.dir, 'src', 'exports.ts')]));
};
export { getAliases };

View File

@@ -0,0 +1,9 @@
{
"compilerOptions": {
"outDir": "./dist"
},
"include": [
"src/**/*.ts"
],
"extends": "@morten-olsen/reservoir-configs/tsconfig.json"
}

5603
pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

5
pnpm-workspace.yaml Normal file
View File

@@ -0,0 +1,5 @@
packages:
- ./packages/*
- ./apps/*
onlyBuiltDependencies:
- better-sqlite3

16
scripts/set-version.mjs Normal file
View File

@@ -0,0 +1,16 @@
import { readFile, writeFile } from 'fs/promises';
import { join } from 'path';
import process from 'process';
import { findWorkspacePackages } from '@pnpm/find-workspace-packages';
const packages = await findWorkspacePackages(process.cwd());
for (const pkg of packages) {
const pkgPath = join(pkg.dir, 'package.json');
const pkgJson = JSON.parse(await readFile(pkgPath, 'utf-8'));
pkgJson.version = process.argv[2];
await writeFile(pkgPath, JSON.stringify(pkgJson, null, 2) + '\n');
}

37
turbo.json Normal file
View File

@@ -0,0 +1,37 @@
{
"$schema": "https://turbo.build/schema.json",
"tasks": {
"build": {
"dependsOn": [
"^build"
],
"outputs": [
"dist/**",
"public/**"
],
"inputs": [
"src/**/*.tsx",
"src/**/*.ts",
"./tsconfig.*",
"../../pnpm-lock.yaml"
]
},
"test": {
"cache": false
},
"clean": {},
"dev": {
"dependsOn": [
"^build"
],
"cache": false,
"persistent": true
},
"demo": {
"dependsOn": [
"^build"
],
"persistent": true
}
}
}

14
vitest.config.ts Normal file
View File

@@ -0,0 +1,14 @@
import { defineConfig, type UserConfigExport } from 'vitest/config';
// eslint-disable-next-line import/no-default-export
export default defineConfig(async () => {
const config: UserConfigExport = {
test: {
coverage: {
provider: 'v8',
include: ['packages/**/src/**/*.ts'],
},
},
};
return config;
});