No description
  • TypeScript 98%
  • JavaScript 2%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-05-24 18:32:34 +02:00
src Refactor schema and ID exports; remove deprecated files and add UI annotations 2026-05-24 18:24:58 +02:00
test Add unit tests for schema components and pagination utilities 2026-05-24 18:27:58 +02:00
.gitignore Main project setup, ulid and urn management created 2026-01-22 10:57:00 +01:00
bun.lock Main project setup, ulid and urn management created 2026-01-22 10:57:00 +01:00
bun.lockb Replace ESLint configuration with new flat config format; remove deprecated .eslintrc.cjs file 2026-05-24 18:27:03 +02:00
bunfig.toml Main project setup, ulid and urn management created 2026-01-22 10:57:00 +01:00
eslint.config.mjs Replace ESLint configuration with new flat config format; remove deprecated .eslintrc.cjs file 2026-05-24 18:27:03 +02:00
LICENSE Initial commit 2026-01-21 20:10:15 +01:00
package.json Replace ESLint configuration with new flat config format; remove deprecated .eslintrc.cjs file 2026-05-24 18:27:03 +02:00
README.md Update README.md to include detailed module descriptions, installation instructions, and examples for ULID, URN, entity schemas, pagination, and error handling. 2026-05-24 18:32:34 +02:00
tsconfig.json Main project setup, ulid and urn management created 2026-01-22 10:57:00 +01:00
tsconfig.test.json Add unit tests for schema components and pagination utilities 2026-05-24 18:27:58 +02:00
tsup.config.ts Replace ESLint configuration with new flat config format; remove deprecated .eslintrc.cjs file 2026-05-24 18:27:03 +02:00
vitest.config.ts Add unit tests for schema components and pagination utilities 2026-05-24 18:27:58 +02:00

@tesserakit/core

Core primitives for building entity-driven applications with TesseraKit — a full-stack Bun + Effect-TS framework.

Installation

bun add @tesserakit/core

Modules

The package is tree-shakeable. Prefer sub-path imports:

Import Contents
@tesserakit/core/id ULID and URN utilities
@tesserakit/core/schema Entity, CRUD, and pagination schemas
@tesserakit/core/errors Domain error classes

@tesserakit/core/id

ULID — sortable, time-encoded unique IDs

import { Ulid } from "@tesserakit/core/id";

// Generate
const id = Ulid.generate();              // "01ARZ3NDEKTSV4RRFFQ69G5FAV"

// Validate
Ulid.isValid(id);                        // true

// Extract time
Ulid.timestamp(id);                      // Unix ms
Ulid.date(id);                           // Date object

// Sort-safe comparison
[id3, id1, id2].sort(Ulid.compare);      // chronological order

// Guaranteed ordering within same millisecond
const gen = Ulid.monotonic();
const [a, b, c] = [gen(), gen(), gen()]; // a < b < c always

// Effect-based API
import { Effect } from "effect";
const id2 = yield* Ulid.generateEffect();
const parsed = yield* Ulid.parseEffect("01ARZ3NDEKTSV4RRFFQ69G5FAV");

URN — namespaced entity identifiers

Format: {app}:{entity}:{ulid} — e.g. myapp:person:01ARZ3NDEKTSV4RRFFQ69G5FAV

import { Urn } from "@tesserakit/core/id";

// Create
const id = Urn.create("myapp", "person");

// Parse
const { app, entity, id: ulid } = Urn.parse(id);

// Validate
Urn.isValid(id);                         // true

// Type guards
Urn.isApp(id, "myapp");                  // true
Urn.isEntity(id, "person");              // true
Urn.matches(id, "myapp", "person");      // true

// Scoped generator
const myappUrn = Urn.forApp("myapp");
const personId = myappUrn("person");
const productId = myappUrn("product");

// Effect-based API
const safeId = yield* Urn.createEffect("myapp", "person");
const components = yield* Urn.parseEffect(id);
const opt = Urn.parseSafe(id);           // Option.Option<UrnComponents>

@tesserakit/core/schema

Entity schemas

import { createEntitySchema, createCrudSchemas } from "@tesserakit/core/schema";
import { Schema as S } from "effect";

// 1. Define the entity (extends BaseEntity automatically)
const PersonSchema = createEntitySchema("Person", {
  firstName: S.String.pipe(S.minLength(1)),
  lastName:  S.String.pipe(S.minLength(1)),
  email:     S.String.pipe(S.minLength(1)),
  bio:       S.optional(S.String),
}, {
  softDelete: true,                       // adds deletedAt / deletedBy / isDeleted
  description: "A person in the system",
});

export type Person = S.Schema.Type<typeof PersonSchema>;

// 2. Generate CRUD schemas (no boilerplate)
export const PersonCrud = createCrudSchemas(PersonSchema, "Person");
// Produces: CreateInput, UpdateInput, FindByIdInput, FindManyInput, DeleteInput, PaginatedResponse

BaseEntity fields

Every entity automatically includes:

Field Type Description
id UrnType Auto-generated URN identifier
createdAt Date Creation timestamp
updatedAt Date Last-updated timestamp
createdBy UrnType? Actor who created the entity
updatedBy UrnType? Actor who last updated the entity

With softDelete: true, also adds: deletedAt, deletedBy, isDeleted.

Pagination

import { PaginationParams, createPaginatedResponse, normalizePaginationParams } from "@tesserakit/core/schema";

// Normalize with defaults
const params = normalizePaginationParams({ limit: 200 }); // clamped to MAX_LIMIT

// Build a paginated response
const response = createPaginatedResponse(items, totalCount, params);
// { data: [...], meta: { total, limit, offset, hasMore, totalPages, currentPage } }

Query filters

FindManyInput supports structured filters:

const query = {
  filters: [
    { field: "firstName", operator: "eq",         value: "Alice" },
    { field: "createdAt", operator: "gte",        value: "2024-01-01T00:00:00Z" },
    { field: "bio",       operator: "isNotNull",  value: null },
  ],
  pagination: { limit: 20, offset: 0, sortBy: "createdAt", sortDirection: "desc" },
};

Supported operators: eq neq gt gte lt lte in nin contains startsWith endsWith isNull isNotNull


@tesserakit/core/errors

All errors extend Data.TaggedError — they are first-class Effect values.

import {
  EntityNotFoundError,
  EntityConflictError,
  EntityValidationError,
  RepositoryError,
  UnauthorizedError,
  ForbiddenError,
} from "@tesserakit/core/errors";
import { Effect } from "effect";

// Catch with Effect.catchTag
const person = yield* personRepo.findById(id).pipe(
  Effect.catchTag("EntityNotFoundError", () => Effect.succeed(null)),
);

// Catch multiple
yield* personRepo.create(input).pipe(
  Effect.catchTags({
    EntityConflictError:   (e) => Effect.fail(new ApiError({ message: `${e.field} already exists` })),
    EntityValidationError: (e) => Effect.fail(new ApiError({ message: e.message })),
    RepositoryError:       (e) => { console.error(e.cause); return Effect.fail(e); },
  }),
);
Error Tag Use when
EntityNotFoundError "EntityNotFoundError" findById / update / delete misses
EntityConflictError "EntityConflictError" unique constraint violations
EntityValidationError "EntityValidationError" business rule violations
RepositoryError "RepositoryError" persistence layer failures
UnauthorizedError "UnauthorizedError" missing / invalid credentials
ForbiddenError "ForbiddenError" authenticated but not permitted

Design principles

  • Effect-first: every async or fallible operation has an Effect-based variant (createEffect, parseEffect, etc.)
  • URN IDs: entity IDs encode app namespace + entity type + time-sortable ULID — enabling efficient range queries and multi-app deployments
  • Schema-first: define your entity once; CRUD schemas, pagination, and filters are all derived
  • Transport-agnostic errors: @tesserakit/core/errors has no knowledge of HTTP status codes or RPC error codes — that mapping lives in @tesserakit/server

License

GPL-3.0-only