ParqueDB
API Reference

Collection Class

Fluent API for working with entities in a specific namespace. Provides find, create, update, delete, and upsert operations.

The Collection class provides a fluent API for working with entities in a specific namespace. Collections are obtained via db.collection(namespace) or through proxy-based access like db.Posts.

Overview

interface Collection<T = Record<string, unknown>> {
  readonly namespace: string
  find(filter?: Filter, options?: FindOptions): Promise<PaginatedResult<Entity<T>>>
  findOne(filter?: Filter, options?: FindOptions): Promise<Entity<T> | null>
  get(id: string, options?: GetOptions): Promise<Entity<T> | null>
  create(data: CreateInput<T>, options?: CreateOptions): Promise<Entity<T>>
  update(id: string, update: UpdateInput<T>, options?: UpdateOptions): Promise<Entity<T> | null>
  delete(id: string, options?: DeleteOptions): Promise<DeleteResult>
  deleteMany(filter: Filter, options?: DeleteOptions): Promise<DeleteResult>
  upsert(filter: Filter, update: UpdateInput<T>, options?: UpsertOptions): Promise<Entity<T> | null>
  upsertMany(items: UpsertManyItem<T>[], options?: UpsertManyOptions): Promise<UpsertManyResult>
}

Obtaining a Collection

Explicit Access

import { ParqueDB, MemoryBackend } from 'parquedb'

const db = new ParqueDB({ storage: new MemoryBackend() })

// Get a typed collection
interface Post {
  title: string
  content: string
  status: 'draft' | 'published'
}

const posts = db.collection<Post>('posts')

Proxy-based Access

// Access via PascalCase property
const posts = db.Posts

// Access via lowercase property
const users = db.users

Properties

namespace

The namespace identifier for this collection.

readonly namespace: string

Example

const posts = db.collection('posts')
console.log(posts.namespace) // 'posts'

Methods

find

Find entities matching a filter with optional pagination and sorting.

find(filter?: Filter, options?: FindOptions): Promise<PaginatedResult<Entity<T>>>

Parameters

NameTypeRequiredDescription
filterFilterNoMongoDB-style filter query
optionsFindOptionsNoQuery options for pagination, sorting, etc.

FindOptions

PropertyTypeDescription
sortSortSpecSort order, e.g., { createdAt: -1 } for descending
limitnumberMaximum number of results to return
skipnumberNumber of results to skip (offset-based pagination)
cursorstringCursor for cursor-based pagination
projectProjectionField projection (include/exclude fields)
populatePopulateSpecPopulate related entities
includeDeletedbooleanInclude soft-deleted entities (default: false)
asOfDateTime-travel: query as of a specific timestamp
explainbooleanReturn query plan instead of executing
hintstring | objectHint for index selection
maxTimeMsnumberMaximum execution time in milliseconds

Returns

interface PaginatedResult<T> {
  items: T[]           // Array of matching entities
  total?: number       // Total count (if available)
  nextCursor?: string  // Cursor for next page
  hasMore: boolean     // Whether more results exist
}

Examples

// Find all entities
const all = await posts.find()

// Find with simple equality filter
const published = await posts.find({ status: 'published' })

// Find with operators
const popular = await posts.find({
  status: 'published',
  viewCount: { $gte: 1000 },
})

// Find with logical operators
const featured = await posts.find({
  $or: [
    { featured: true },
    { viewCount: { $gte: 10000 } },
  ],
})

// Find with pagination
const page1 = await posts.find({}, { limit: 20 })
const page2 = await posts.find({}, {
  limit: 20,
  cursor: page1.nextCursor
})

// Find with sorting
const recent = await posts.find({}, {
  sort: { createdAt: -1 },
  limit: 10,
})

// Find with projection
const titles = await posts.find({}, {
  project: { title: 1, status: 1 },
})

// Find with time-travel
const pastState = await posts.find({}, {
  asOf: new Date('2024-01-01'),
})

// Find including deleted
const withDeleted = await posts.find({}, {
  includeDeleted: true,
})

findOne

Find a single entity matching a filter.

findOne(filter?: Filter, options?: FindOptions): Promise<Entity<T> | null>

Parameters

NameTypeRequiredDescription
filterFilterNoMongoDB-style filter query
optionsFindOptionsNoQuery options

Returns

Entity<T> | null - The first matching entity or null if not found.

Examples

// Find first matching entity
const post = await posts.findOne({ slug: 'hello-world' })

// Find with sorting (get the most recent)
const latest = await posts.findOne({}, {
  sort: { createdAt: -1 },
})

// Find by email (unique field)
const user = await users.findOne({ email: 'alice@example.com' })

get

Get a single entity by its ID.

get(id: string, options?: GetOptions): Promise<Entity<T> | null>

Parameters

NameTypeRequiredDescription
idstringYesEntity ID (full 'ns/id' or just 'id')
optionsGetOptionsNoGet options

GetOptions

PropertyTypeDescription
includeDeletedbooleanInclude if soft-deleted
asOfDateTime-travel: get state at specific time
hydratestring[]Hydrate related entities
maxInboundnumberMaximum inbound references to inline
projectProjectionField projection

Returns

Entity<T> | null - The entity or null if not found.

Examples

// Get by full ID
const post = await posts.get('posts/abc123')

// Get by short ID (namespace is inferred)
const post = await posts.get('abc123')

// Get with hydrated relationships
const post = await posts.get('abc123', {
  hydrate: ['author', 'categories'],
})

// Get historical state
const oldPost = await posts.get('abc123', {
  asOf: new Date('2024-01-01'),
})

// Get including deleted
const deletedPost = await posts.get('abc123', {
  includeDeleted: true,
})

// Get with projection
const titleOnly = await posts.get('abc123', {
  project: { title: 1 },
})

create

Create a new entity in the collection.

create(data: CreateInput<T>, options?: CreateOptions): Promise<Entity<T>>

Parameters

NameTypeRequiredDescription
dataCreateInput<T>YesEntity data to create
optionsCreateOptionsNoCreate options

CreateInput

PropertyTypeRequiredDescription
$typestringYesEntity type name (e.g., 'Post', 'User')
namestringYesHuman-readable display name
...fieldsunknownNoAdditional data fields

CreateOptions

PropertyTypeDescription
actorEntityIdWho is creating the entity (for audit trail)
skipValidationbooleanSkip schema validation
returnDocumentbooleanReturn created entity (default: true)

Returns

Entity<T> - The created entity with:

  • $id: Generated unique identifier (format: 'namespace/id')
  • createdAt / updatedAt: Timestamps
  • createdBy / updatedBy: Actor references
  • version: 1 (initial version)

Examples

// Create a basic entity
const post = await posts.create({
  $type: 'Post',
  name: 'My First Post',
  title: 'Hello World',
  content: 'This is my first post!',
  status: 'draft',
})

console.log(post.$id)       // 'posts/abc123'
console.log(post.version)   // 1
console.log(post.createdAt) // Date

// Create with actor for audit
const post = await posts.create({
  $type: 'Post',
  name: 'Admin Post',
  title: 'Announcement',
  content: 'Important news...',
}, {
  actor: 'users/admin' as EntityId,
})

// Create with relationships
const post = await posts.create({
  $type: 'Post',
  name: 'Tech Article',
  title: 'Database Design',
  author: { 'Alice': 'users/alice' },
  categories: {
    'Technology': 'categories/tech',
    'Databases': 'categories/db',
  },
})

update

Update an existing entity.

update(id: string, update: UpdateInput<T>, options?: UpdateOptions): Promise<Entity<T> | null>

Parameters

NameTypeRequiredDescription
idstringYesEntity ID to update
updateUpdateInput<T>YesUpdate operations
optionsUpdateOptionsNoUpdate options

UpdateInput

Update operations using MongoDB-style operators:

OperatorDescriptionExample
$setSet field values{ $set: { status: 'published' } }
$unsetRemove fields{ $unset: { tempField: '' } }
$incIncrement numeric field{ $inc: { viewCount: 1 } }
$mulMultiply numeric field{ $mul: { price: 1.1 } }
$minSet to minimum value{ $min: { lowScore: score } }
$maxSet to maximum value{ $max: { highScore: score } }
$pushPush to array{ $push: { tags: 'new' } }
$pullRemove from array{ $pull: { tags: 'old' } }
$addToSetAdd unique to array{ $addToSet: { tags: 'unique' } }
$popRemove first/last from array{ $pop: { queue: 1 } }
$currentDateSet to current date{ $currentDate: { updatedAt: true } }
$linkAdd relationship{ $link: { author: 'users/123' } }
$unlinkRemove relationship{ $unlink: { author: 'users/123' } }
$setOnInsertSet only on insert (with upsert){ $setOnInsert: { createdBy: 'system' } }

UpdateOptions

PropertyTypeDescription
actorEntityIdWho is updating (for audit trail)
expectedVersionnumberExpected version for optimistic locking
upsertbooleanCreate if entity doesn't exist
returnDocument'before' | 'after'Return state before or after update
skipValidationbooleanSkip schema validation
arrayFiltersFilter[]Filters for positional array updates

Returns

Entity<T> | null - Updated entity, or null if not found (and upsert is false).

Examples

// Simple field update
const post = await posts.update('abc123', {
  $set: { status: 'published', publishedAt: new Date() },
})

// Increment counter
await posts.update('abc123', {
  $inc: { viewCount: 1 },
})

// Multiple operators
await posts.update('abc123', {
  $set: { status: 'featured' },
  $inc: { featureCount: 1 },
  $currentDate: { lastFeaturedAt: true },
})

// Array operations
await posts.update('abc123', {
  $push: { tags: 'featured' },
  $addToSet: { categories: 'popular' },
})

// Push with modifiers
await posts.update('abc123', {
  $push: {
    comments: {
      $each: [newComment1, newComment2],
      $position: 0,  // Insert at beginning
      $slice: 100,   // Keep only 100 items
    },
  },
})

// Relationship operations
await posts.update('abc123', {
  $link: {
    author: 'users/alice' as EntityId,
    categories: ['categories/tech', 'categories/db'] as EntityId[],
  },
})

// Unlink relationships
await posts.update('abc123', {
  $unlink: { categories: 'categories/old' as EntityId },
})

// With optimistic concurrency
try {
  await posts.update('abc123', {
    $set: { title: 'New Title' },
  }, {
    expectedVersion: 5,
  })
} catch (error) {
  if (error instanceof VersionConflictError) {
    console.log('Conflict detected, please retry')
  }
}

// Upsert (create if not exists)
await posts.update('new-post', {
  $set: { title: 'New Post' },
  $setOnInsert: {
    $type: 'Post',
    name: 'New Post',
    status: 'draft',
  },
}, {
  upsert: true,
})

// Return document before update
const original = await posts.update('abc123', {
  $set: { status: 'archived' },
}, {
  returnDocument: 'before',
})

delete

Delete an entity (soft delete by default).

delete(id: string, options?: DeleteOptions): Promise<DeleteResult>

Parameters

NameTypeRequiredDescription
idstringYesEntity ID to delete
optionsDeleteOptionsNoDelete options

DeleteOptions

PropertyTypeDescription
actorEntityIdWho is deleting (for audit trail)
hardbooleanPermanently delete (skip soft delete)
expectedVersionnumberExpected version for optimistic locking

Returns

interface DeleteResult {
  deletedCount: number  // Number of entities deleted (0 or 1)
}

Examples

// Soft delete (default)
const result = await posts.delete('abc123')
console.log(result.deletedCount) // 1

// Hard delete (permanent)
await posts.delete('abc123', { hard: true })

// With optimistic concurrency
await posts.delete('abc123', {
  expectedVersion: 5,
})

// With actor for audit trail
await posts.delete('abc123', {
  actor: 'users/admin' as EntityId,
})

deleteMany

Delete multiple entities matching a filter.

deleteMany(filter: Filter, options?: DeleteOptions): Promise<DeleteResult>

Parameters

NameTypeRequiredDescription
filterFilterYesFilter to match entities to delete
optionsDeleteOptionsNoDelete options

Returns

interface DeleteResult {
  deletedCount: number  // Total number of entities deleted
}

Examples

// Delete all drafts
const result = await posts.deleteMany({ status: 'draft' })
console.log(`Deleted ${result.deletedCount} drafts`)

// Delete old posts
await posts.deleteMany({
  createdAt: { $lt: new Date('2023-01-01') },
})

// Hard delete spam
await posts.deleteMany(
  { spam: true },
  { hard: true }
)

upsert

Find and update an entity, or create it if not found.

upsert(
  filter: Filter,
  update: UpdateInput<T>,
  options?: { returnDocument?: 'before' | 'after' }
): Promise<Entity<T> | null>

Parameters

NameTypeRequiredDescription
filterFilterYesFilter to find existing entity
updateUpdateInput<T>YesUpdate operations
optionsobjectNoOptions

Returns

Entity<T> | null - The updated or created entity.

Behavior

  1. Find entity matching filter
  2. If found: apply update operations
  3. If not found: create new entity with:
    • Non-operator fields from filter
    • Values from $set
    • Values from $setOnInsert

Examples

// Upsert user by email
const user = await users.upsert(
  { email: 'alice@example.com' },
  {
    $set: { lastLogin: new Date() },
    $setOnInsert: {
      $type: 'User',
      name: 'Alice',
      role: 'member',
    },
  }
)

// Upsert with increment
const stats = await stats.upsert(
  { date: today },
  {
    $inc: { pageViews: 1 },
    $setOnInsert: {
      $type: 'DailyStats',
      name: `Stats ${today}`,
    },
  }
)

upsertMany

Upsert multiple entities in a single operation.

upsertMany(
  items: UpsertManyItem<T>[],
  options?: UpsertManyOptions
): Promise<UpsertManyResult>

Parameters

NameTypeRequiredDescription
itemsUpsertManyItem<T>[]YesArray of upsert items
optionsUpsertManyOptionsNoBatch options

UpsertManyItem

interface UpsertManyItem<T> {
  filter: Filter                    // Filter to find existing
  update: UpdateInput<T>            // Update operations
  options?: {
    expectedVersion?: number        // Optimistic concurrency
  }
}

UpsertManyOptions

PropertyTypeDescription
orderedbooleanStop on first error (default: true)
actorEntityIdActor for all operations

UpsertManyResult

interface UpsertManyResult {
  ok: boolean                 // All operations succeeded
  insertedCount: number       // New entities created
  modifiedCount: number       // Existing entities updated
  matchedCount: number        // Entities matched by filters
  upsertedCount: number       // Total upserted (inserted)
  upsertedIds: EntityId[]     // IDs of new entities
  errors: UpsertManyError[]   // Errors that occurred
}

interface UpsertManyError {
  index: number               // Index of failed item
  filter: Filter              // Filter that was used
  error: Error                // The error
}

Examples

// Bulk upsert users
const result = await users.upsertMany([
  {
    filter: { email: 'alice@example.com' },
    update: {
      $set: { name: 'Alice Smith' },
      $setOnInsert: { $type: 'User', role: 'member' },
    },
  },
  {
    filter: { email: 'bob@example.com' },
    update: {
      $set: { name: 'Bob Jones' },
      $setOnInsert: { $type: 'User', role: 'member' },
    },
  },
])

console.log(`Inserted: ${result.insertedCount}`)
console.log(`Modified: ${result.modifiedCount}`)

// Continue on errors
const result = await users.upsertMany(items, {
  ordered: false,  // Don't stop on first error
})

if (!result.ok) {
  for (const error of result.errors) {
    console.error(`Item ${error.index} failed:`, error.error.message)
  }
}

Filter Operators

Collections support MongoDB-style filter operators:

Comparison Operators

OperatorDescriptionExample
$eqEqual{ status: { $eq: 'published' } }
$neNot equal{ status: { $ne: 'draft' } }
$gtGreater than{ score: { $gt: 100 } }
$gteGreater than or equal{ score: { $gte: 100 } }
$ltLess than{ score: { $lt: 50 } }
$lteLess than or equal{ score: { $lte: 50 } }
$inIn array{ status: { $in: ['draft', 'review'] } }
$ninNot in array{ status: { $nin: ['deleted'] } }

Logical Operators

OperatorDescriptionExample
$andAll conditions match{ $and: [{ a: 1 }, { b: 2 }] }
$orAny condition matches{ $or: [{ a: 1 }, { b: 2 }] }
$notNegation{ $not: { status: 'draft' } }
$norNone match{ $nor: [{ a: 1 }, { b: 2 }] }

String Operators

OperatorDescriptionExample
$regexRegular expression{ title: { $regex: '^Hello' } }
$startsWithStarts with prefix{ title: { $startsWith: 'Hello' } }
$endsWithEnds with suffix{ title: { $endsWith: '!' } }
$containsContains substring{ title: { $contains: 'world' } }

Array Operators

OperatorDescriptionExample
$allArray contains all{ tags: { $all: ['a', 'b'] } }
$elemMatchElement matches{ items: { $elemMatch: { qty: { $gt: 5 } } } }
$sizeArray size{ tags: { $size: 3 } }

Existence Operators

OperatorDescriptionExample
$existsField exists{ email: { $exists: true } }
$typeField type check{ data: { $type: 'object' } }

Special Operators

OperatorDescriptionExample
$textFull-text search{ $text: { $search: 'hello world' } }
$vectorVector similarity{ $vector: { $near: vec, $k: 10 } }
$geoGeospatial query{ $geo: { $near: { lng, lat } } }

Type Definitions

Entity

// Entity type properly uses TData for typed data fields
type Entity<TData = Record<string, unknown>> = EntityRef & AuditFields & TData & {
  [key: string]: unknown
}

interface EntityRef {
  $id: EntityId          // Full entity ID (namespace/id)
  $type: string          // Entity type name
  name: string           // Display name
}

interface AuditFields {
  createdAt: Date        // Creation timestamp
  createdBy: EntityId    // Creator ID
  updatedAt: Date        // Last update timestamp
  updatedBy: EntityId    // Last updater ID
  deletedAt?: Date       // Soft delete timestamp
  deletedBy?: EntityId   // Who deleted
  version: number        // Version number
}

SortSpec

interface SortSpec {
  [field: string]: 1 | -1 | 'asc' | 'desc'
}

// Examples:
{ createdAt: -1 }           // Descending
{ createdAt: 'desc' }       // Same as above
{ name: 1, createdAt: -1 }  // Multi-field sort

Projection

interface Projection {
  [field: string]: 0 | 1 | boolean
}

// Examples:
{ title: 1, content: 1 }  // Include only these
{ password: 0 }           // Exclude this field

PopulateSpec

type PopulateSpec =
  | string[]                                    // ['author', 'categories']
  | { [predicate: string]: boolean | PopulateOptions }

interface PopulateOptions {
  limit?: number
  sort?: SortSpec
  cursor?: string
  filter?: Filter
  populate?: PopulateSpec  // Nested populate
}

Error Handling

VersionConflictError

Thrown when optimistic concurrency check fails.

import { VersionConflictError } from 'parquedb'

try {
  await posts.update('abc123',
    { $set: { title: 'New' } },
    { expectedVersion: 5 }
  )
} catch (error) {
  if (error instanceof VersionConflictError) {
    console.log(`Expected ${error.expectedVersion}, got ${error.actualVersion}`)
    // Handle conflict (retry, merge, etc.)
  }
}

Validation Errors

Thrown when entity data fails schema validation.

try {
  await posts.create({
    $type: 'Post',
    name: 'Test',
    // Missing required 'title' field
  })
} catch (error) {
  console.log(error.message) // 'Missing required field: title'
}

Best Practices

Use TypeScript Generics

interface Post {
  title: string
  content: string
  status: 'draft' | 'published'
  viewCount: number
}

const posts = db.collection<Post>('posts')

// Now you get type safety
const post = await posts.create({
  $type: 'Post',
  name: 'My Post',
  title: 'Hello',
  content: 'World',
  status: 'draft',  // TypeScript validates this
  viewCount: 0,
})

Handle Pagination

async function* getAllPosts() {
  let cursor: string | undefined

  while (true) {
    const result = await posts.find({}, {
      limit: 100,
      cursor,
      sort: { createdAt: -1 },
    })

    for (const post of result.items) {
      yield post
    }

    if (!result.hasMore) break
    cursor = result.nextCursor
  }
}

for await (const post of getAllPosts()) {
  console.log(post.title)
}

Optimistic Concurrency

async function updateWithRetry(id: string, update: UpdateInput, maxRetries = 3) {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    const current = await posts.get(id)
    if (!current) throw new Error('Not found')

    try {
      return await posts.update(id, update, {
        expectedVersion: current.version,
      })
    } catch (error) {
      if (error instanceof VersionConflictError && attempt < maxRetries - 1) {
        continue // Retry
      }
      throw error
    }
  }
}

Batch Operations

// Prefer upsertMany for bulk operations
const result = await posts.upsertMany(
  items.map(item => ({
    filter: { externalId: item.id },
    update: {
      $set: item.data,
      $setOnInsert: { $type: 'Post', name: item.title },
    },
  })),
  { ordered: false }  // Continue on errors
)

On this page