Skip to content

Latest commit

 

History

31 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

PotatoDB

PotatoDB is a small TypeScript repository library for local development, tests, and hobby projects. It keeps application code domain-first while handling persistence behind the scenes.

Install

bun add potato-db

Tiny Application Example

PotatoDB is meant to feel small in application code. You construct a repository once for the current context, then work with domain entities and logical ids.

const userRepository = repositoryFactory.createUserRepository(request);

await userRepository.create({
  displayName: "Ada",
  role: "parent",
  createdAt: new Date().toISOString(),
});

const users = await userRepository.findAll();
const ada = await userRepository.findByDisplayName("Ada");

That is the main idea: application code stays focused on the domain operation, not on storage records, table names, or persistence wiring.

What PotatoDB Gives You

  • Small repository classes with domain-specific methods.
  • Logical ids instead of storage-level ids in application code.
  • in-memory mode and json file-backed mode for different use cases, while keeping the same repository API
  • Scope-aware repositories for tenant-owned data and application-wide data.
  • Simple runtime sharing controls for normal app usage, isolated tests, or namespaced local runs.

Repository Boundaries

PotatoDB separates repository access boundaries from runtime sharing.

  • ScopedRepository is bound to one scopeId at construction time and only sees data for that scope. Perfect for multi-tenant applications with strict tenant boundaries.
  • GlobalRepository is for application-wide shared data such as reference records.
  • CrossScopeRepository is a query-only escape hatch for explicit reporting or directory-style reads across many scopes.

Runtime Modes

  • application: normal app behavior
  • isolated: private state per repository instance, mainly for tests
  • namespace: shared state within one named namespace, useful for parallel test runs or separate local apps

Omitting dataScope defaults to application.

File-Backed Mode

Use mode: "json-file" when you want repository state to persist across process runs.

const repository = new PublicHolidayRepository({
  mode: "json-file",
  dataScope: "namespace",
  namespaceKey: "family-app",
});

The repository API stays the same in file-backed mode. You still work with domain entities and logical ids, and PotatoDB handles persistence for you.

Repository Authoring

When you define a repository, you describe domain identity and let the base class handle persistence.

class UserRepository extends ScopedRepository<User, { userId: UserId }, UserCreateInput, FamilyId> {
  constructor(familyId: FamilyId, options: PotatoDBOptions = {}) {
    super({
      typePrefix: "user",
      scopeId: familyId,
      createEntity: (createInput, generatedId) => ({
        ...createInput,
        familyId,
        userId: brandId(generatedId),
      }),
      idOf: (user) => ({ userId: user.userId }),
      partitionKeyOf: () => familyId,
      sortKeyOf: ({ userId }) => `User#${userId}#User`,
    }, options);
  }
}

Recommended App Structure

Keep repository construction and runtime configuration in one application-level factory so handlers do not need to know which request fields or options are required to build each repository.

const factory = new FamilyAppRepositoryFactory();
const request = {
  authorizer: {
    claims: {
      familyId,
    },
  },
  countryCode: "US",
  date: "2026-04-18",
};

const userRepository = factory.createUserRepository(request);
const publicHolidayRepository = factory.createPublicHolidayRepository(request);

That factory lives in this repository's example app and is not part of the published library API. It is included to demonstrate the recommended pattern in real application code.

Low-Level Writes

Use put(...) when you already have a complete entity, such as during fixture loading or test setup.

await repository.put({
  holidayId: brandId<"HolidayId">("holiday-1"),
  region: "US",
  name: "Independence Day",
});

For normal application code, prefer create(...) and update(...) so the repository can own defaults, id generation, and entity-shape rules.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages