npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

digital-objects

v1.0.0

Published

Unified storage primitive for AI primitives - nouns, verbs, things, actions

Readme

digital-objects

Unified storage primitive for AI primitives - a linguistically-aware entity and graph system.

Documentation: For comprehensive guides and examples, see the Digital Objects documentation.

Overview

digital-objects provides a coherent model for defining and managing entities (nouns/things) and relationships (verbs/actions). It automatically derives linguistic forms (pluralization, verb conjugation) and unifies events, graph edges, and audit trails into a single "action" concept.

Core Concepts

Nouns and Things

Nouns define entity types. Things are instances of nouns.

import { createMemoryProvider } from 'digital-objects'

const provider = createMemoryProvider()

// Define a noun (entity type)
const postNoun = await provider.defineNoun({
  name: 'Post',
  description: 'A blog post',
  schema: {
    title: 'string',
    body: 'markdown',
    publishedAt: 'datetime?',
  },
})

// Linguistic forms are auto-derived:
// postNoun.singular = 'post'
// postNoun.plural = 'posts'
// postNoun.slug = 'post'

// Create a thing (entity instance)
const post = await provider.create('Post', {
  title: 'Hello World',
  body: '# Welcome\n\nThis is my first post.',
})

Verbs and Actions

Verbs define action types. Actions represent events, graph edges, and audit records - unified.

// Define a verb
const publishVerb = await provider.defineVerb({
  name: 'publish',
  inverse: 'unpublish',
})

// Conjugations are auto-derived:
// publishVerb.action = 'publish'     (imperative)
// publishVerb.act = 'publishes'      (3rd person)
// publishVerb.activity = 'publishing' (gerund)
// publishVerb.event = 'published'     (past participle)
// publishVerb.reverseBy = 'publishedBy'
// publishVerb.reverseAt = 'publishedAt'

// Perform an action (creates an event + edge)
const action = await provider.perform(
  'publish',      // verb
  author.id,      // subject (who/from)
  post.id,        // object (what/to)
  { featured: true } // metadata
)

Actions as Graph Edges

Actions form a directed graph. Use related() and edges() for traversal:

// Get all things this author has published
const publishedPosts = await provider.related(author.id, 'publish', 'out')

// Get who published this post
const publishers = await provider.related(post.id, 'publish', 'in')

// Get all relationships (both directions)
const allRelated = await provider.related(post.id, undefined, 'both')

// Get edge details (the actions themselves)
const publishActions = await provider.edges(author.id, 'publish', 'out')

Linguistic Derivation

The package automatically derives linguistic forms from base words.

Noun Derivation

import { deriveNoun, pluralize, singularize } from 'digital-objects'

deriveNoun('Post')      // { singular: 'post', plural: 'posts', slug: 'post' }
deriveNoun('BlogPost')  // { singular: 'blog post', plural: 'blog posts', slug: 'blog-post' }
deriveNoun('Person')    // { singular: 'person', plural: 'people', slug: 'person' }

pluralize('category')   // 'categories'
pluralize('child')      // 'children'
singularize('posts')    // 'post'

Verb Derivation

import { deriveVerb } from 'digital-objects'

deriveVerb('create')
// {
//   action: 'create',
//   act: 'creates',
//   activity: 'creating',
//   event: 'created',
//   reverseBy: 'createdBy',
//   reverseAt: 'createdAt'
// }

deriveVerb('write')
// {
//   action: 'write',
//   act: 'writes',
//   activity: 'writing',
//   event: 'written',        (irregular)
//   reverseBy: 'writtenBy',
//   reverseAt: 'writtenAt'
// }

Providers

MemoryProvider

In-memory implementation for testing and development:

import { createMemoryProvider, MemoryProvider } from 'digital-objects'

// Factory function
const provider = createMemoryProvider()

// Or instantiate directly
const provider = new MemoryProvider()

// Clean up
await provider.close()

NS (Namespace Durable Object)

SQLite-backed implementation for Cloudflare Workers with multi-tenant support:

// In wrangler.toml:
// [[durable_objects.bindings]]
// name = "NS"
// class_name = "NS"

// Import for Workers
import { NS } from 'digital-objects/ns'

// Access via HTTP
export default {
  async fetch(request: Request, env: Env) {
    const namespace = 'tenant-123'
    const id = env.NS.idFromName(namespace)
    const stub = env.NS.get(id)
    return stub.fetch(request)
  }
}

// HTTP API endpoints:
// POST /nouns        - Define a noun
// GET  /nouns/:name  - Get a noun
// GET  /nouns        - List nouns
// POST /verbs        - Define a verb
// POST /things       - Create a thing
// GET  /things/:id   - Get a thing
// PATCH /things/:id  - Update a thing
// DELETE /things/:id - Delete a thing
// POST /actions      - Perform an action
// GET  /related/:id  - Get related things

NS Client

HTTP client for accessing NS from other services:

import { createNSClient } from 'digital-objects'

const client = createNSClient({
  baseUrl: 'https://ns.example.com',
  namespace: 'my-namespace',
})

const post = await client.create('Post', { title: 'Hello' })

R2 Persistence

Backup, restore, and export functionality using Cloudflare R2.

Snapshots

import { createSnapshot, restoreSnapshot } from 'digital-objects'

// Create a full snapshot
const result = await createSnapshot(provider, r2, 'my-namespace', {
  timestamp: true, // Include timestamp in filename
})
// result.key = 'snapshots/my-namespace/1234567890.json'

// Restore from snapshot
await restoreSnapshot(provider, r2, 'my-namespace')
// or specify a snapshot key:
await restoreSnapshot(provider, r2, 'my-namespace', 'snapshots/my-namespace/1234567890.json')

Write-Ahead Log (WAL)

import { appendWAL, replayWAL, compactWAL } from 'digital-objects'

// Append operation to WAL
await appendWAL(r2, 'my-namespace', {
  type: 'create',
  noun: 'Post',
  id: post.id,
  data: post.data,
  timestamp: Date.now(),
})

// Replay WAL entries
const replayed = await replayWAL(provider, r2, 'my-namespace', lastSnapshotTimestamp)

// Clean up old WAL entries
const deleted = await compactWAL(r2, 'my-namespace', snapshotTimestamp)

JSONL Export/Import

import { exportJSONL, importJSONL, exportToR2, importFromR2 } from 'digital-objects'

// Export to string
const jsonl = await exportJSONL(provider)

// Import from string
const stats = await importJSONL(provider, jsonl)
// stats = { nouns: 5, verbs: 3, things: 100, actions: 250 }

// Export directly to R2
await exportToR2(provider, r2, 'exports/backup.jsonl')

// Import directly from R2
await importFromR2(provider, r2, 'exports/backup.jsonl')

ai-database Adapter

Use digital-objects as a storage backend for ai-database:

import { createMemoryProvider, createDBProviderAdapter } from 'digital-objects'

const doProvider = createMemoryProvider()
const dbProvider = createDBProviderAdapter(doProvider)

// Now use ai-database's interface
const user = await dbProvider.create('User', undefined, {
  name: 'Alice',
  email: '[email protected]',
})
// Returns: { $id: 'uuid', $type: 'User', name: 'Alice', email: '...' }

// Get by ID
const found = await dbProvider.get('User', user.$id)

// List with filters
const admins = await dbProvider.list('User', {
  where: { role: 'admin' },
  orderBy: 'name',
  limit: 10,
})

// Create relationships
await dbProvider.relate('User', user.$id, 'author', 'Post', post.$id)

// Query relationships
const userPosts = await dbProvider.related('Post', user.$id, 'author')

Type Definitions

Noun

interface Noun {
  name: string        // 'Post'
  singular: string    // 'post'
  plural: string      // 'posts'
  slug: string        // 'post' (URL-safe)
  description?: string
  schema?: Record<string, FieldDefinition>
  createdAt: Date
}

Verb

interface Verb {
  name: string        // 'create'
  action: string      // 'create' (imperative)
  act: string         // 'creates' (3rd person)
  activity: string    // 'creating' (gerund)
  event: string       // 'created' (past participle)
  reverseBy?: string  // 'createdBy'
  reverseAt?: string  // 'createdAt'
  inverse?: string    // 'delete'
  description?: string
  createdAt: Date
}

Thing

interface Thing<T = Record<string, unknown>> {
  id: string
  noun: string        // References noun.name
  data: T
  createdAt: Date
  updatedAt: Date
}

Action

interface Action<T = Record<string, unknown>> {
  id: string
  verb: string        // References verb.name
  subject?: string    // Thing ID (actor/from)
  object?: string     // Thing ID (target/to)
  data?: T            // Payload/metadata
  status: ActionStatus
  createdAt: Date
  completedAt?: Date
}

type ActionStatus = 'pending' | 'active' | 'completed' | 'failed' | 'cancelled'

Field Types

type FieldDefinition =
  | 'string' | 'number' | 'boolean' | 'date' | 'datetime' | 'json' | 'markdown' | 'url'
  | `${string}.${string}`      // Relation: 'Author.posts'
  | `[${string}.${string}]`    // Array relation: '[Tag.posts]'
  | `${PrimitiveType}?`        // Optional field

Schema Validation

Digital Objects provides runtime schema validation with clear, actionable error messages.

Enable Validation

Validation is opt-in. Pass { validate: true } to create() or update():

// Define a noun with schema
await provider.defineNoun({
  name: 'User',
  schema: {
    email: { type: 'string', required: true },
    age: 'number',
    bio: 'string?', // Optional
  },
})

// Validation enabled - will throw on errors
await provider.create('User', { name: 'Alice' }, undefined, { validate: true })
// Error: Validation failed (1 error):
//   - Missing required field 'email'

Pre-flight Validation

Use validateOnly() to check data before attempting operations:

import { validateOnly } from 'digital-objects'

const schema = {
  email: { type: 'string', required: true },
  age: 'number',
}

const result = validateOnly({ age: '25' }, schema)

if (!result.valid) {
  console.log('Errors:', result.errors)
  // [
  //   {
  //     field: 'email',
  //     message: "Missing required field 'email'",
  //     code: 'REQUIRED_FIELD',
  //     expected: 'string',
  //     received: 'undefined'
  //   },
  //   {
  //     field: 'age',
  //     message: "Field 'age' has wrong type: expected number, got string",
  //     code: 'TYPE_MISMATCH',
  //     expected: 'number',
  //     received: 'string',
  //     suggestion: 'Convert to number: 25'
  //   }
  // ]
}

Error Codes

| Code | Description | |------|-------------| | REQUIRED_FIELD | A required field is missing or null | | TYPE_MISMATCH | Value has wrong type for the field | | INVALID_FORMAT | Value format doesn't match expected pattern | | UNKNOWN_FIELD | Field not defined in schema (future feature) |

Suggestions

Validation errors include helpful suggestions when possible:

// String that looks like a number
{ age: '25' }
// suggestion: "Convert to number: 25"

// Number when string expected
{ name: 42 }
// suggestion: 'Convert to string: "42"'

// String boolean
{ active: 'true' }
// suggestion: "Convert to boolean: true"

// Wrong type for array
{ tags: 'single-tag' }
// suggestion: "Wrap value in an array: [value]"

// Wrong type for date
{ createdAt: 1705312800000 }
// suggestion: "Provide a valid ISO 8601 date string"

Installation

npm install digital-objects

License

MIT