Package-source copy · Generated into the site so runtime docs are searchable and linkable.
API Reference (TypeScript)
This reference is generated from declaration files emitted from ts/src. Do not hand-edit signatures here; run make generate-api-reference.
Drift check
make verify-api-reference
Public modules
errors
export type ErrorCode = 'ErrItemNotFound' | 'ErrConditionFailed' | 'ErrVersionConflict' | 'ErrLeaseHeld' | 'ErrLeaseNotOwned' | 'ErrInvalidModel' | 'ErrMissingPrimaryKey' | 'ErrInvalidOperator' | 'ErrTableNotFound' | 'ErrEncryptedFieldNotQueryable' | 'ErrEncryptionNotConfigured' | 'ErrInvalidEncryptedEnvelope' | 'ErrImmutableModelMutation' | 'ErrProtectedFieldMutation' | 'ErrRejectedDeployAuthorityEvidence';
export declare class TheorydbError extends Error {
readonly code: ErrorCode;
readonly codes: readonly ErrorCode[];
constructor(code: ErrorCode, message: string, options?: {
cause?: unknown;
codes?: readonly ErrorCode[];
});
}
export declare function isTheorydbError(value: unknown): value is TheorydbError;
export declare function theorydbErrorCodes(value: unknown): readonly ErrorCode[];
export declare function hasTheorydbErrorCode(value: unknown, code: ErrorCode): boolean;
client
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
import { type BatchGetResult, type BatchWriteResult, type RetryOptions } from './batch.js';
import type { Model, ModelItem } from './model.js';
import type { SendOptions } from './send-options.js';
import { type NumberUnmarshalMode } from './marshal.js';
import { QueryBuilder, ScanBuilder } from './query.js';
import type { TransactAction, TransactGetAction } from './transaction.js';
import { UpdateBuilder } from './update-builder.js';
import { type EncryptionProvider } from './encryption.js';
import { type WritePolicyOptions } from './write-policy.js';
export interface TheorydbClientOptions {
encryption?: EncryptionProvider;
now?: () => string;
sendOptions?: SendOptions;
/**
* Controls how DynamoDB N/NS values are unmarshaled.
*
* The default, 'string', returns canonical DynamoDB decimal strings so reads
* are precision-safe for integers outside the safe range and high-precision
* decimals. Use 'number' only when lossy JavaScript Number conversion is
* acceptable for a specific client.
*/
numberUnmarshalMode?: NumberUnmarshalMode;
}
type ModelKey<TItem extends Record<string, unknown>> = Partial<TItem>;
type ModelField<TItem extends Record<string, unknown>> = Extract<keyof TItem, string>;
export declare class ModelRepository<TItem extends Record<string, unknown> = Record<string, unknown>> {
private readonly client;
private readonly modelDef;
constructor(client: TheorydbClient, modelDef: Model<TItem>);
create(item: TItem, opts?: {
ifNotExists?: boolean;
}): Promise<void>;
save(item: TItem): Promise<void>;
get(key: ModelKey<TItem>): Promise<TItem>;
getOrNull(key: ModelKey<TItem>): Promise<TItem | null>;
update(item: TItem, fields: readonly ModelField<TItem>[], opts?: WritePolicyOptions): Promise<void>;
delete(key: ModelKey<TItem>): Promise<void>;
batchGet(keys: Array<ModelKey<TItem>>, opts?: RetryOptions & {
consistentRead?: boolean;
}): Promise<BatchGetResult<TItem>>;
batchWrite(req: {
puts?: TItem[];
deletes?: Array<ModelKey<TItem>>;
}, opts?: RetryOptions): Promise<BatchWriteResult>;
query(): QueryBuilder<TItem>;
scan(): ScanBuilder<TItem>;
updateBuilder(key: ModelKey<TItem>): UpdateBuilder;
}
export declare class TheorydbClient {
private readonly ddb;
private readonly models;
private encryption;
private readonly now;
private readonly sendOptions;
private readonly unmarshalOptions;
constructor(ddb: DynamoDBClient, opts?: TheorydbClientOptions);
withEncryption(provider: EncryptionProvider): this;
withSendOptions(sendOptions?: SendOptions): TheorydbClient;
withDynamoDBClient(ddb: DynamoDBClient): TheorydbClient;
register(...models: Model[]): this;
model<M extends Model>(model: M): ModelRepository<ModelItem<M>>;
model(modelName: string): ModelRepository<Record<string, unknown>>;
private requireModel;
private requireEncryption;
create(modelName: string, item: Record<string, unknown>, opts?: {
ifNotExists?: boolean;
}): Promise<void>;
save(modelName: string, item: Record<string, unknown>): Promise<void>;
get(modelName: string, key: Record<string, unknown>): Promise<Record<string, unknown>>;
getOrNull(modelName: string, key: Record<string, unknown>): Promise<Record<string, unknown> | null>;
update(modelName: string, item: Record<string, unknown>, fields: string[], opts?: WritePolicyOptions): Promise<void>;
delete(modelName: string, key: Record<string, unknown>): Promise<void>;
batchGet(modelName: string, keys: Array<Record<string, unknown>>, opts?: RetryOptions & {
consistentRead?: boolean;
}): Promise<BatchGetResult>;
batchWrite(modelName: string, req: {
puts?: Array<Record<string, unknown>>;
deletes?: Array<Record<string, unknown>>;
}, opts?: RetryOptions): Promise<BatchWriteResult>;
transactGet(actions: TransactGetAction[]): Promise<Array<Record<string, unknown> | undefined>>;
transactWrite(actions: TransactAction[]): Promise<void>;
query(modelName: string): QueryBuilder;
scan(modelName: string): ScanBuilder;
updateBuilder(modelName: string, key: Record<string, unknown>): UpdateBuilder;
}
export {};
cursor
import type { AttributeValue } from '@aws-sdk/client-dynamodb';
export type CursorSort = 'ASC' | 'DESC';
export interface Cursor {
lastKey: Record<string, AttributeValue>;
index?: string;
sort?: CursorSort;
}
export declare function encodeCursor(cursor: Cursor): string;
export declare function decodeCursor(encoded: string): Cursor;
dms
import { type Model, type ModelSchema } from './model.js';
export interface DmsDocument {
dms_version: string;
namespace?: string;
models: ModelSchema[];
}
export interface DmsCompareOptions {
ignoreTableName?: boolean;
}
export declare function parseDmsDocument(raw: string): DmsDocument;
export declare function getDmsModel(doc: DmsDocument, name: string): ModelSchema;
export declare function modelToDmsModel(model: Model | ModelSchema): Readonly<ModelSchema>;
export declare function assertModelsEquivalent(got: Model | ModelSchema, want: ModelSchema, options?: DmsCompareOptions): void;
model
export type ScalarType = 'S' | 'N' | 'B' | 'BOOL' | 'NULL' | 'M' | 'L' | 'SS' | 'NS' | 'BS';
export type KeyType = 'S' | 'N' | 'B';
export interface ValueConverter {
toDynamoValue(value: unknown): unknown;
fromDynamoValue(value: unknown): unknown;
}
export interface KeySchema {
attribute: string;
type: KeyType;
}
export interface AttributeSchema {
attribute: string;
type: ScalarType;
required?: boolean;
optional?: boolean;
omit_empty?: boolean;
json?: boolean;
binary?: boolean;
format?: string;
roles?: readonly string[];
encryption?: unknown;
converter?: ValueConverter;
}
export interface IndexSchema {
name: string;
type: 'GSI' | 'LSI';
partition: KeySchema;
sort?: KeySchema;
projection?: {
type: 'ALL' | 'KEYS_ONLY' | 'INCLUDE';
fields?: readonly string[];
};
}
export interface WritePolicySchema {
mode?: 'mutable' | 'write_once';
protected_attributes?: readonly string[];
}
export interface WritePolicy {
mode: 'mutable' | 'write_once';
protectedAttributes: readonly string[];
}
export interface ModelSchema {
name: string;
table: {
name: string;
};
naming?: {
convention?: 'camelCase' | 'snake_case' | 'dynamorm';
};
keys: {
partition: KeySchema;
sort?: KeySchema;
};
write_policy?: WritePolicySchema;
attributes: readonly AttributeSchema[];
indexes?: readonly IndexSchema[];
}
export interface ModelRoles {
pk: string;
sk?: string;
createdAt?: string;
updatedAt?: string;
version?: string;
ttl?: string;
}
export interface Model<TItem extends Record<string, unknown> = Record<string, unknown>> {
readonly name: string;
readonly tableName: string;
readonly schema: Readonly<ModelSchema>;
readonly attributes: ReadonlyMap<string, Readonly<AttributeSchema>>;
readonly indexes: ReadonlyMap<string, Readonly<IndexSchema>>;
readonly roles: Readonly<ModelRoles>;
readonly writePolicy: Readonly<WritePolicy>;
readonly __itemType?: TItem;
}
type AttributeValueFor<A extends AttributeSchema> = A['type'] extends 'S' ? string : A['type'] extends 'N' ? number : A['type'] extends 'B' ? Uint8Array : A['type'] extends 'BOOL' ? boolean : A['type'] extends 'NULL' ? null : A['type'] extends 'SS' ? string[] : A['type'] extends 'NS' ? number[] : A['type'] extends 'BS' ? Uint8Array[] : A['type'] extends 'L' ? unknown[] : A['type'] extends 'M' ? Record<string, unknown> : unknown;
type AttributeHasRole<A extends AttributeSchema, R extends string> = A['roles'] extends readonly string[] ? R extends A['roles'][number] ? true : false : false;
type IsOptionalAttribute<A extends AttributeSchema> = A extends {
optional: true;
} ? true : A extends {
omit_empty: true;
} ? true : AttributeHasRole<A, 'created_at'> extends true ? true : AttributeHasRole<A, 'updated_at'> extends true ? true : AttributeHasRole<A, 'version'> extends true ? true : AttributeHasRole<A, 'ttl'> extends true ? true : false;
type AttributeName<A extends AttributeSchema> = A['attribute'] extends string ? A['attribute'] : never;
type RequiredAttributes<S extends ModelSchema> = {
[A in S['attributes'][number] as IsOptionalAttribute<A> extends true ? never : AttributeName<A>]: AttributeValueFor<A>;
};
type OptionalAttributes<S extends ModelSchema> = {
[A in S['attributes'][number] as IsOptionalAttribute<A> extends true ? AttributeName<A> : never]?: AttributeValueFor<A>;
};
export type InferModelItem<S extends ModelSchema> = RequiredAttributes<S> & OptionalAttributes<S>;
export type ModelItem<M extends Model> = M extends Model<infer TItem> ? TItem : Record<string, unknown>;
export declare function defineModel<const S extends ModelSchema>(schema: S): Model<InferModelItem<S>>;
export {};
query
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
import type { AggregateResult } from './aggregates.js';
import { GroupByQuery } from './aggregates.js';
import { type CursorSort } from './cursor.js';
import { type EncryptionProvider } from './encryption.js';
import { type UnmarshalOptions } from './marshal.js';
import type { Model } from './model.js';
import type { BuilderShape } from './optimizer.js';
import type { SendOptions } from './send-options.js';
import type { Page, QueryOperator, QueryRetryOptions } from './query-types.js';
export { unsafeOperator } from './query-types.js';
export type { KnownOperator, OperatorEscape, Page, QueryOperator, QueryRetryOptions, } from './query-types.js';
export interface FilterGroupBuilder {
filter(field: string, op: QueryOperator, ...values: unknown[]): this;
orFilter(field: string, op: QueryOperator, ...values: unknown[]): this;
filterGroup(fn: (b: FilterGroupBuilder) => void): this;
orFilterGroup(fn: (b: FilterGroupBuilder) => void): this;
}
export declare class QueryBuilder<TItem extends Record<string, unknown> = Record<string, unknown>> {
private readonly ddb;
private readonly model;
private readonly encryption?;
private readonly sendOptions?;
private readonly unmarshalOptions;
private indexName?;
private pkValue?;
private skCondition?;
private limitCount?;
private projectionFields?;
private consistentReadEnabled;
private cursorToken;
private sortDir;
private readonly filters;
constructor(ddb: DynamoDBClient, model: Model, encryption?: EncryptionProvider | undefined, sendOptions?: SendOptions | undefined, unmarshalOptions?: UnmarshalOptions);
usingIndex(name: string): this;
sort(direction: CursorSort): this;
consistentRead(enabled?: boolean): this;
limit(n: number): this;
projection(fields: string[]): this;
filter(field: string, op: QueryOperator, ...values: unknown[]): this;
orFilter(field: string, op: QueryOperator, ...values: unknown[]): this;
filterGroup(fn: (b: FilterGroupBuilder) => void): this;
orFilterGroup(fn: (b: FilterGroupBuilder) => void): this;
cursor(encoded: string): this;
partitionKey(value: unknown): this;
sortKey(op: '=' | '<' | '<=' | '>' | '>=' | 'between' | 'begins_with', ...values: unknown[]): this;
page(): Promise<Page<TItem>>;
count(): Promise<number>;
private countPage;
all(): Promise<TItem[]>;
pages(): AsyncGenerator<Page<TItem>>;
items(): AsyncGenerator<TItem>;
/**
* Client-side aggregation: calls `all()` and materializes every matching item before summing.
* Use only for bounded result sets; use native `count()` for count-only reads.
*/
sum(field: string): Promise<number>;
/**
* Client-side aggregation: calls `all()` and materializes every matching item before averaging.
* Use only for bounded result sets; use native `count()` for count-only reads.
*/
average(field: string): Promise<number>;
/**
* Client-side aggregation: calls `all()` and materializes every matching item before selecting the minimum.
* Use only for bounded result sets; use native `count()` for count-only reads.
*/
min(field: string): Promise<unknown>;
/**
* Client-side aggregation: calls `all()` and materializes every matching item before selecting the maximum.
* Use only for bounded result sets; use native `count()` for count-only reads.
*/
max(field: string): Promise<unknown>;
/**
* Client-side aggregation: calls `all()` and materializes every matching item before computing the aggregate.
* Use only for bounded result sets; use native `count()` for count-only reads.
*/
aggregate(...fields: string[]): Promise<AggregateResult>;
/**
* Client-side aggregation: calls `all()` and materializes every matching item before counting distinct values.
* Use only for bounded result sets; use native `count()` for count-only reads.
*/
countDistinct(field: string): Promise<number>;
/**
* Client-side aggregation: the returned group query calls `all()` during `execute()` and keeps groups in memory.
* Use only for bounded result sets; use native `count()` for count-only reads.
*/
groupBy(field: string): GroupByQuery<TItem>;
describe(): BuilderShape;
pageWithRetry(opts?: QueryRetryOptions<TItem>): Promise<Page<TItem>>;
private resolveKeySchema;
}
export declare class ScanBuilder<TItem extends Record<string, unknown> = Record<string, unknown>> {
private readonly ddb;
private readonly model;
private readonly encryption?;
private readonly sendOptions?;
private readonly unmarshalOptions;
private indexName?;
private limitCount?;
private projectionFields?;
private consistentReadEnabled;
private cursorToken;
private readonly filters;
private segment?;
private totalSegments?;
constructor(ddb: DynamoDBClient, model: Model, encryption?: EncryptionProvider | undefined, sendOptions?: SendOptions | undefined, unmarshalOptions?: UnmarshalOptions);
usingIndex(name: string): this;
consistentRead(enabled?: boolean): this;
limit(n: number): this;
projection(fields: string[]): this;
filter(field: string, op: QueryOperator, ...values: unknown[]): this;
orFilter(field: string, op: QueryOperator, ...values: unknown[]): this;
filterGroup(fn: (b: FilterGroupBuilder) => void): this;
orFilterGroup(fn: (b: FilterGroupBuilder) => void): this;
cursor(encoded: string): this;
parallelScan(segment: number, totalSegments: number): this;
scanAllSegments(totalSegments: number, opts?: {
concurrency?: number;
}): Promise<TItem[]>;
count(): Promise<number>;
private countPage;
all(): Promise<TItem[]>;
pages(): AsyncGenerator<Page<TItem>>;
items(): AsyncGenerator<TItem>;
/**
* Client-side aggregation: calls `all()` and materializes every matching item before summing.
* Use only for bounded result sets; use native `count()` for count-only reads.
*/
sum(field: string): Promise<number>;
/**
* Client-side aggregation: calls `all()` and materializes every matching item before averaging.
* Use only for bounded result sets; use native `count()` for count-only reads.
*/
average(field: string): Promise<number>;
/**
* Client-side aggregation: calls `all()` and materializes every matching item before selecting the minimum.
* Use only for bounded result sets; use native `count()` for count-only reads.
*/
min(field: string): Promise<unknown>;
/**
* Client-side aggregation: calls `all()` and materializes every matching item before selecting the maximum.
* Use only for bounded result sets; use native `count()` for count-only reads.
*/
max(field: string): Promise<unknown>;
/**
* Client-side aggregation: calls `all()` and materializes every matching item before computing the aggregate.
* Use only for bounded result sets; use native `count()` for count-only reads.
*/
aggregate(...fields: string[]): Promise<AggregateResult>;
/**
* Client-side aggregation: calls `all()` and materializes every matching item before counting distinct values.
* Use only for bounded result sets; use native `count()` for count-only reads.
*/
countDistinct(field: string): Promise<number>;
/**
* Client-side aggregation: the returned group query calls `all()` during `execute()` and keeps groups in memory.
* Use only for bounded result sets; use native `count()` for count-only reads.
*/
groupBy(field: string): GroupByQuery<TItem>;
describe(): BuilderShape;
page(): Promise<Page<TItem>>;
pageWithRetry(opts?: QueryRetryOptions<TItem>): Promise<Page<TItem>>;
}
batch
import type { AttributeValue, WriteRequest } from '@aws-sdk/client-dynamodb';
export interface RetryOptions {
maxAttempts?: number;
baseDelayMs?: number;
}
export interface BatchGetResult<T = Record<string, unknown>> {
items: T[];
unprocessedKeys: Array<Record<string, AttributeValue>>;
}
export interface BatchWriteResult {
unprocessed: WriteRequest[];
}
export declare function chunk<T>(items: T[], size: number): T[][];
export declare function sleep(ms: number): Promise<void>;
transaction
import type { AttributeValue } from '@aws-sdk/client-dynamodb';
import type { UpdateBuilder } from './update-builder.js';
/**
* Raw transaction updates bypass UpdateBuilder encryption/validation and are
* therefore only valid for models without encrypted attributes. Encrypted
* models must use `updateFn`.
*/
type TransactUpdateRaw = {
kind: 'update';
model: string;
key: Record<string, unknown>;
updateExpression: string;
conditionExpression?: string;
expressionAttributeNames?: Record<string, string>;
expressionAttributeValues?: Record<string, AttributeValue>;
updateFn?: never;
};
type TransactUpdateWithBuilder = {
kind: 'update';
model: string;
key: Record<string, unknown>;
updateFn: (builder: UpdateBuilder) => void | Promise<void>;
updateExpression?: never;
conditionExpression?: never;
expressionAttributeNames?: never;
expressionAttributeValues?: never;
};
export type TransactAction = {
kind: 'put';
model: string;
item: Record<string, unknown>;
ifNotExists?: boolean;
} | TransactUpdateRaw | TransactUpdateWithBuilder | {
kind: 'delete';
model: string;
key: Record<string, unknown>;
conditionExpression?: string;
expressionAttributeNames?: Record<string, string>;
expressionAttributeValues?: Record<string, AttributeValue>;
} | {
kind: 'condition';
model: string;
key: Record<string, unknown>;
conditionExpression: string;
expressionAttributeNames?: Record<string, string>;
expressionAttributeValues?: Record<string, AttributeValue>;
};
export type TransactGetAction = {
model: string;
key: Record<string, unknown>;
projection?: string[];
};
export {};
streams
import type { Model } from './model.js';
export interface StreamRecord {
dynamodb?: {
Keys?: Record<string, unknown>;
NewImage?: Record<string, unknown>;
OldImage?: Record<string, unknown>;
};
}
export declare function unmarshalStreamImage(model: Model, image: Record<string, unknown>): Record<string, unknown>;
export declare function unmarshalStreamRecord(model: Model, record: StreamRecord): {
keys?: Record<string, unknown>;
newImage?: Record<string, unknown>;
oldImage?: Record<string, unknown>;
};
aggregates
/** Result of an in-memory client-side aggregation over an already materialized item array. */
export interface AggregateResult {
min?: unknown;
max?: unknown;
count: number;
sum: number;
average: number;
}
/** Group produced by `GroupByQuery.execute()` after all source items have been materialized in memory. */
export interface GroupedResult<T = Record<string, unknown>> {
key: unknown;
count: number;
items: T[];
aggregates: Record<string, AggregateResult>;
}
/**
* Client-side group-by helper. `execute()` loads the full source item set into memory,
* then keeps grouped items and aggregates in memory; use only for bounded result sets.
*/
export declare class GroupByQuery<T extends Record<string, unknown>> {
private readonly items;
private readonly groupByField;
private readonly aggregates;
private readonly havingClauses;
constructor(items: () => Promise<T[]>, groupByField: string);
/** Adds a count aggregate computed in memory after the full source set is materialized. */
count(alias: string): this;
/** Adds a sum aggregate computed in memory after the full source set is materialized. */
sum(field: string, alias: string): this;
/** Adds an average aggregate computed in memory after the full source set is materialized. */
avg(field: string, alias: string): this;
/** Adds a minimum aggregate computed in memory after the full source set is materialized. */
min(field: string, alias: string): this;
/** Adds a maximum aggregate computed in memory after the full source set is materialized. */
max(field: string, alias: string): this;
/** Adds an in-memory having filter evaluated after groups and aggregates are materialized. */
having(aggregate: string, operator: string, value: unknown): this;
/** Materializes the full source set, groups it in memory, computes aggregates, and returns all groups. */
execute(): Promise<Array<GroupedResult<T>>>;
}
/** Client-side sum over an already materialized item array; use only for bounded result sets. */
export declare function sumField<T extends Record<string, unknown>>(items: T[], field: string): number;
/** Client-side average over an already materialized item array; use only for bounded result sets. */
export declare function averageField<T extends Record<string, unknown>>(items: T[], field: string): number;
/** Client-side minimum over an already materialized item array; use only for bounded result sets. */
export declare function minField<T extends Record<string, unknown>>(items: T[], field: string): unknown;
/** Client-side maximum over an already materialized item array; use only for bounded result sets. */
export declare function maxField<T extends Record<string, unknown>>(items: T[], field: string): unknown;
/** Client-side aggregate over an already materialized item array; use only for bounded result sets. */
export declare function aggregateField<T extends Record<string, unknown>>(items: T[], field?: string): AggregateResult;
/** Client-side distinct count over an already materialized item array; use only for bounded result sets. */
export declare function countDistinct<T extends Record<string, unknown>>(items: T[], field: string): number;
optimizer
export type QueryOperation = 'Query' | 'Scan';
export interface QueryPlan {
id: string;
operation: QueryOperation;
indexName?: string;
projections?: string[];
parallelSegments?: number;
optimizationHints: string[];
}
export interface OptimizationOptions {
enableParallel?: boolean;
maxParallelism?: number;
}
export interface OptimizerCondition {
field: string;
operator: string;
}
export interface OptimizerIndexShape {
name?: string;
type: 'PRIMARY' | 'GSI' | 'LSI';
partition: string;
sort?: string;
projectionType?: 'ALL' | 'KEYS_ONLY' | 'INCLUDE';
}
export type BuilderShape = {
kind: 'query';
modelName: string;
tableName: string;
indexName?: string;
indexType?: 'GSI' | 'LSI';
hasPartitionKey: boolean;
hasSortKey: boolean;
hasSortKeyCondition: boolean;
hasFilters: boolean;
projections?: string[];
consistentRead: boolean;
sort: 'ASC' | 'DESC';
indexes?: OptimizerIndexShape[];
conditions?: OptimizerCondition[];
} | {
kind: 'scan';
modelName: string;
tableName: string;
indexName?: string;
indexType?: 'GSI' | 'LSI';
hasFilters: boolean;
projections?: string[];
consistentRead: boolean;
parallelScanConfigured: boolean;
totalSegments?: number;
indexes?: OptimizerIndexShape[];
conditions?: OptimizerCondition[];
};
export interface RequiredKeys {
partitionKey: string;
sortKey: string;
sortKeyOp: string;
}
export declare class QueryOptimizer {
private readonly enableParallel;
private readonly maxParallelism;
constructor(opts?: OptimizationOptions);
explain(shape: BuilderShape): QueryPlan;
}
export declare function analyzeConditions(conditions: readonly OptimizerCondition[]): RequiredKeys;
export declare function selectOptimalIndex(required: RequiredKeys, indexes: readonly OptimizerIndexShape[]): OptimizerIndexShape | undefined;
encryption
import type { AttributeValue } from '@aws-sdk/client-dynamodb';
import type { AttributeSchema, Model } from './model.js';
export interface EncryptionContext {
model: string;
attribute: string;
}
export interface EncryptedEnvelope {
v: 1;
edk: Uint8Array;
nonce: Uint8Array;
ct: Uint8Array;
}
export interface EncryptionProvider {
encrypt(plaintext: Uint8Array, ctx: EncryptionContext): Promise<EncryptedEnvelope>;
decrypt(envelope: EncryptedEnvelope, ctx: EncryptionContext): Promise<Uint8Array>;
}
export declare function modelHasEncryptedAttributes(model: Model): boolean;
export declare function marshalPutItemEncrypted(model: Model, item: Record<string, unknown>, provider: EncryptionProvider, opts?: {
now?: string;
}): Promise<Record<string, AttributeValue>>;
export declare function decryptItemAttributes(model: Model, item: Record<string, AttributeValue>, provider: EncryptionProvider): Promise<Record<string, AttributeValue>>;
export declare function encryptAttributeValue(schema: Readonly<AttributeSchema>, value: unknown, provider: EncryptionProvider, ctx: EncryptionContext): Promise<AttributeValue>;
encryption-kms
import { type KMSClient } from '@aws-sdk/client-kms';
import type { EncryptedEnvelope, EncryptionContext, EncryptionProvider } from './encryption.js';
export interface AwsKmsEncryptionProviderOptions {
keyArn: string;
randomBytes?: (size: number) => Uint8Array;
}
export declare class AwsKmsEncryptionProvider implements EncryptionProvider {
private readonly kms;
private readonly keyArn;
private readonly randomBytes;
constructor(kms: Pick<KMSClient, 'send'>, opts: AwsKmsEncryptionProviderOptions);
encrypt(plaintext: Uint8Array, ctx: EncryptionContext): Promise<EncryptedEnvelope>;
decrypt(envelope: EncryptedEnvelope, ctx: EncryptionContext): Promise<Uint8Array>;
}
schema
import { type DynamoDBClient, type TableDescription } from '@aws-sdk/client-dynamodb';
import type { Model } from './model.js';
export type BillingMode = 'PAY_PER_REQUEST' | 'PROVISIONED';
export interface ProvisionedThroughput {
readCapacityUnits: number;
writeCapacityUnits: number;
}
export interface CreateTableOptions {
tableName?: string;
billingMode?: BillingMode;
provisionedThroughput?: ProvisionedThroughput;
waitForActive?: boolean;
waitTimeoutSeconds?: number;
pollIntervalMs?: number;
}
export interface DeleteTableOptions {
tableName?: string;
waitForDelete?: boolean;
waitTimeoutSeconds?: number;
pollIntervalMs?: number;
ignoreMissing?: boolean;
}
export interface DescribeTableOptions {
tableName?: string;
}
export interface UpdateTimeToLiveOptions {
tableName?: string;
attributeName?: string;
enabled?: boolean;
}
export declare function createTable(ddb: DynamoDBClient, model: Model, opts?: CreateTableOptions): Promise<void>;
export declare function ensureTable(ddb: DynamoDBClient, model: Model, opts?: CreateTableOptions): Promise<void>;
export declare function deleteTable(ddb: DynamoDBClient, model: Model, opts?: DeleteTableOptions): Promise<void>;
export declare function describeTable(ddb: DynamoDBClient, model: Model, opts?: DescribeTableOptions): Promise<TableDescription>;
export declare function resolveTimeToLiveAttribute(model: Model): string | undefined;
export declare function updateTimeToLive(ddb: DynamoDBClient, model: Model, opts?: UpdateTimeToLiveOptions): Promise<void>;
schema-migration
import { type AttributeValue, type DynamoDBClient } from '@aws-sdk/client-dynamodb';
import type { Model } from './model.js';
/** A raw DynamoDB item (attribute-name to AttributeValue). */
export type MigrationItem = Record<string, AttributeValue>;
/**
* A transform applied to each item during a data-copying migration. It mirrors
* the Go `schema.TransformFunc` AttributeValue-level transforms.
*/
export type MigrationTransform = (item: MigrationItem) => MigrationItem;
export interface AutoMigrateOptions {
/** Migrate into a different model/table than the source. Defaults to the source. */
targetModel?: Model;
/** Applied to each item when copying data. */
transform?: MigrationTransform;
/** When set, copy the source table into this backup table before migrating. */
backupTable?: string;
/** Scan/batch page size. Defaults to 25 (the DynamoDB BatchWriteItem limit). */
batchSize?: number;
/** Copy data from the source table into the target table. */
dataCopy?: boolean;
}
/**
* autoMigrate ensures the target table exists and, when requested, copies data
* from the source table into it (optionally through a transform), mirroring the
* Go `Manager.AutoMigrateWithOptions` story.
*/
export declare function autoMigrate(ddb: DynamoDBClient, sourceModel: Model, opts?: AutoMigrateOptions): Promise<void>;
/** copyAllFields returns a transform that passes every attribute through unchanged. */
export declare function copyAllFields(): MigrationTransform;
/** renameField returns a transform that renames one attribute. */
export declare function renameField(oldName: string, newName: string): MigrationTransform;
/** addField returns a transform that adds an attribute (overwriting if present). */
export declare function addField(name: string, value: AttributeValue): MigrationTransform;
/** removeField returns a transform that drops an attribute. */
export declare function removeField(name: string): MigrationTransform;
/** chainTransforms composes transforms left to right. */
export declare function chainTransforms(...transforms: MigrationTransform[]): MigrationTransform;
update-builder
import { type AttributeValue, type DynamoDBClient } from '@aws-sdk/client-dynamodb';
import { type EncryptionProvider } from './encryption.js';
import { type UnmarshalOptions } from './marshal.js';
import type { Model } from './model.js';
import type { SendOptions } from './send-options.js';
import type { QueryOperator } from './query-types.js';
export type ReturnValuesOption = 'NONE' | 'ALL_OLD' | 'UPDATED_OLD' | 'ALL_NEW' | 'UPDATED_NEW';
export declare class UpdateBuilder {
private readonly ddb;
private readonly model;
private readonly key;
private readonly encryption?;
private readonly sendOptions?;
private readonly unmarshalOptions;
private readonly updateOps;
private readonly conditionOps;
private returnValuesOpt;
private hasVersionCondition;
constructor(ddb: DynamoDBClient, model: Model, key: Record<string, unknown>, encryption?: EncryptionProvider | undefined, sendOptions?: SendOptions | undefined, unmarshalOptions?: UnmarshalOptions);
set(field: string, value: unknown): this;
setIfNotExists(field: string, _value: unknown, defaultValue: unknown): this;
add(field: string, value: unknown): this;
increment(field: string): this;
decrement(field: string): this;
remove(field: string): this;
delete(field: string, value: unknown): this;
appendToList(field: string, values: unknown[]): this;
prependToList(field: string, values: unknown[]): this;
removeFromListAt(field: string, index: number): this;
setListElement(field: string, index: number, value: unknown): this;
condition(field: string, operator: QueryOperator, value?: unknown): this;
orCondition(field: string, operator: QueryOperator, value?: unknown): this;
conditionExists(field: string): this;
conditionNotExists(field: string): this;
conditionVersion(currentVersion: number): this;
returnValues(option: ReturnValuesOption): this;
build(): Promise<{
updateExpression: string;
conditionExpression?: string;
expressionAttributeNames?: Record<string, string>;
expressionAttributeValues?: Record<string, AttributeValue>;
}>;
execute(): Promise<Record<string, unknown> | undefined>;
private buildConditionExpression;
private buildUpdateExpression;
private assertUpdatableField;
}
lambda
import { DynamoDBClient, type DynamoDBClientConfig } from '@aws-sdk/client-dynamodb';
import { TheorydbClient } from './client.js';
export type LambdaContextLike = {
getRemainingTimeInMillis(): number;
};
export type LambdaMetric = {
service: 'dynamodb';
command: string;
ms: number;
ok: boolean;
};
export type LambdaTimeoutOptions = {
bufferMs?: number;
};
export declare const DEFAULT_LAMBDA_TIMEOUT_BUFFER_MS = 1000;
export declare function isLambdaEnvironment(env?: NodeJS.ProcessEnv): boolean;
export declare function createLambdaTimeoutSignal(ctx: LambdaContextLike, opts?: LambdaTimeoutOptions): {
signal: AbortSignal;
cleanup: () => void;
};
export declare function withLambdaTimeout(client: TheorydbClient, ctx: LambdaContextLike, opts?: LambdaTimeoutOptions): {
client: TheorydbClient;
cleanup: () => void;
};
export declare function createLambdaDynamoDBClient(opts?: DynamoDBClientConfig & {
connectionTimeoutMs?: number;
socketTimeoutMs?: number;
metrics?: (m: LambdaMetric) => void;
}): DynamoDBClient;
export declare function getLambdaDynamoDBClient(opts?: Parameters<typeof createLambdaDynamoDBClient>[0]): DynamoDBClient;
multiaccount
import { DynamoDBClient, type DynamoDBClientConfig } from '@aws-sdk/client-dynamodb';
import { AssumeRoleCommand, type AssumeRoleCommandOutput } from '@aws-sdk/client-sts';
import type { AwsCredentialIdentityProvider } from '@aws-sdk/types';
export type StsClientLike = {
send(cmd: AssumeRoleCommand): Promise<AssumeRoleCommandOutput>;
};
export type AssumeRoleCredentialsProviderOptions = {
roleArn: string;
externalId?: string;
region?: string;
durationSeconds?: number;
sessionName?: string;
refreshBeforeMs?: number;
now?: () => number;
sts?: StsClientLike;
};
export declare function createAssumeRoleCredentialsProvider(opts: AssumeRoleCredentialsProviderOptions): AwsCredentialIdentityProvider;
export type AssumeRoleDynamoDBClientOptions = {
roleArn: string;
externalId?: string;
region: string;
durationSeconds?: number;
sessionName?: string;
refreshBeforeMs?: number;
now?: () => number;
sts?: StsClientLike;
dynamo?: Omit<DynamoDBClientConfig, 'credentials' | 'region'>;
};
export declare function createAssumeRoleDynamoDBClient(opts: AssumeRoleDynamoDBClientOptions): DynamoDBClient;
export type AccountConfig = {
roleArn: string;
externalId?: string;
region: string;
durationSeconds?: number;
};
export declare class MultiAccountDynamoDBClients {
private readonly opts;
private readonly accounts;
private readonly clients;
constructor(accounts: Record<string, AccountConfig>, opts?: Omit<AssumeRoleDynamoDBClientOptions, 'roleArn' | 'externalId' | 'region'> & {
dynamo?: Omit<DynamoDBClientConfig, 'credentials' | 'region'>;
});
client(partnerId: string): DynamoDBClient;
}
send-options
export type SendOptions = {
abortSignal?: AbortSignal;
};
validation
declare const MaxFieldNameLength = 255;
declare const MaxOperatorLength = 20;
declare const MaxValueStringLength = 400000;
declare const MaxNestedDepth = 32;
declare const MaxExpressionLength = 4096;
type SecurityValidationErrorType = 'InjectionAttempt' | 'InvalidField' | 'InvalidOperator' | 'InvalidValue' | 'InvalidExpression' | 'InvalidTableName' | 'InvalidIndexName';
export declare class SecurityValidationError extends Error {
readonly type: SecurityValidationErrorType;
readonly detail: string;
constructor(type: SecurityValidationErrorType, detail: string);
}
export declare function isSecurityValidationError(value: unknown): value is SecurityValidationError;
export { MaxExpressionLength, MaxFieldNameLength, MaxNestedDepth, MaxOperatorLength, MaxValueStringLength, };
export declare function validateFieldName(field: string): void;
export declare function validateOperator(op: string): void;
export declare function validateValue(value: unknown): void;
export declare function validateExpression(expression: string): void;
export declare function validateTableName(name: string): void;
export declare function validateIndexName(name: string): void;
protection
export declare class SimpleLimiter {
private lastRefillMs;
private tokens;
private readonly maxTokens;
private readonly refillIntervalMs;
private readonly now;
constructor(rps: number, burst: number, opts?: {
now?: () => number;
});
allow(): boolean;
}
export declare class Semaphore {
private readonly capacity;
private inUse;
private readonly waiters;
constructor(capacity: number);
tryAcquire(): (() => void) | null;
acquire(opts?: {
signal?: AbortSignal;
}): Promise<() => void>;
private release;
}
write-policy
import type { Model } from './model.js';
export interface WritePolicyOptions {
protectedAttributes?: readonly string[];
}
export declare function isWriteOnceModel(model: Model): boolean;
export declare function assertMutableWritePolicy(model: Model, operation: string): void;
export declare function assertProtectedFieldsCanMutate(model: Model, fields: readonly string[], extraProtected?: readonly string[]): void;
export declare function assertRawUpdateExpressionAllowed(model: Model, updateExpression: string, expressionAttributeNames?: Record<string, string>): void;
release-state
import type { TheorydbClient } from './client.js';
/**
* Input for a release-state registry transition. The actual-state update and
* event-history append are committed through one DynamoDB transaction by the
* supplied TableTheory client.
*
* External side effects such as Lambda alias flips or CodePipeline executions
* are intentionally outside this helper's atomicity boundary. Callers should
* pair those side effects with explicit retry/reconciliation/outbox behavior.
*/
export interface ReleaseStateTransitionInput {
actualModel: string;
actualKey: Record<string, unknown>;
set: Record<string, unknown>;
eventModel: string;
eventItem: Record<string, unknown>;
expectedVersion?: number;
versionAttribute?: string;
}
/**
* Transactionally update the release-state actual row and append one immutable
* event-history row.
*/
export declare function transitionReleaseState(client: TheorydbClient, input: ReleaseStateTransitionInput): Promise<void>;
/**
* Validate deterministic provenance/confidence metadata for a
* deploy-authoritative release-state actual row.
*
* Ambiguous/conflicting or low-confidence evidence is rejected so it cannot be
* persisted as deploy authority. Preserve that evidence as separate immutable
* visibility/event records instead.
*/
export declare function validateDeployAuthorityMetadata(item: Record<string, unknown>): void;
lease
import { type DynamoDBClient } from '@aws-sdk/client-dynamodb';
import type { SendOptions } from './send-options.js';
export type LeaseKey = {
pk: string;
sk: string;
};
export type Lease = {
key: LeaseKey;
token: string;
expiresAt: number;
};
export declare class LeaseManager {
private readonly ddb;
private readonly tableName;
private readonly now;
private readonly token;
private readonly pkAttr;
private readonly skAttr;
private readonly tokenAttr;
private readonly expiresAtAttr;
private readonly ttlAttr;
private readonly ttlBufferSeconds;
private readonly sendOptions;
constructor(ddb: DynamoDBClient, tableName: string, opts?: {
now?: () => number;
token?: () => string;
pkAttr?: string;
skAttr?: string;
tokenAttr?: string;
expiresAtAttr?: string;
ttlAttr?: string;
ttlBufferSeconds?: number;
sendOptions?: SendOptions;
});
lockKey(pk: string, sk?: string): LeaseKey;
acquire(key: LeaseKey, opts: {
leaseSeconds: number;
}): Promise<Lease>;
refresh(lease: Lease, opts: {
leaseSeconds: number;
}): Promise<Lease>;
release(lease: Lease): Promise<void>;
}
facetheory-isr
import type { DynamoDBClient } from '@aws-sdk/client-dynamodb';
import { TheorydbClient } from './client.js';
import { type Model } from './model.js';
export type FaceTheoryIsrMeta = {
htmlPointer: string;
generatedAtMs: number;
revalidateSeconds: number;
etag?: string;
};
export type FaceTheoryIsrLease = {
leaseToken: string;
leaseExpiresAtMs: number;
};
export type FaceTheoryIsrMetaStoreGetArgs = {
cacheKey: string;
};
export type FaceTheoryIsrMetaStoreTryAcquireLeaseArgs = {
cacheKey: string;
leaseOwner?: string;
nowMs?: number;
leaseDurationMs: number;
};
export type FaceTheoryIsrMetaStoreCommitGenerationArgs = {
cacheKey: string;
leaseOwner?: string;
leaseToken: string;
nowMs?: number;
htmlPointer: string;
generatedAtMs: number;
revalidateSeconds: number;
etag?: string;
ttlUnixSeconds?: number;
};
export type FaceTheoryIsrMetaStoreReleaseLeaseArgs = {
cacheKey: string;
leaseOwner?: string;
leaseToken: string;
};
export type FaceTheoryIsrMetaStoreConfig = {
ddb: DynamoDBClient;
tableName: string;
client?: TheorydbClient;
pkFromCacheKey?: (cacheKey: string) => string;
pkPrefix?: string;
leaseTtlBufferSeconds?: number;
leaseToken?: () => string;
metaTtlSeconds?: number;
};
export declare function defineFaceTheoryCacheMetadataModel(tableName: string): Model;
export declare function defineFaceTheoryCacheLeaseModel(tableName: string): Model;
export declare class FaceTheoryIsrMetaStore {
private readonly ddb;
private readonly tableName;
private readonly client;
private readonly pkFromCacheKey;
private readonly leaseTtlBufferSeconds;
private readonly leaseToken;
private readonly metaTtlSeconds;
constructor(cfg: FaceTheoryIsrMetaStoreConfig);
get(args: FaceTheoryIsrMetaStoreGetArgs): Promise<FaceTheoryIsrMeta | null>;
tryAcquireLease(args: FaceTheoryIsrMetaStoreTryAcquireLeaseArgs): Promise<FaceTheoryIsrLease | null>;
commitGeneration(args: FaceTheoryIsrMetaStoreCommitGenerationArgs): Promise<void>;
releaseLease(args: FaceTheoryIsrMetaStoreReleaseLeaseArgs): Promise<void>;
}
export declare function createFaceTheoryIsrMetaStore(cfg: FaceTheoryIsrMetaStoreConfig): FaceTheoryIsrMetaStore;
key-contract
export type KeyContractInputValue = string | number | boolean;
export type KeyContractTransform = 'trim' | 'wildcard_empty' | 'lowercase' | 'url_encode';
export interface DerivedKeyContract {
tabletheory_model_contract_version: '0.1' | '0.2';
namespace?: string;
dms_version?: string;
models?: DerivedKeyModelContract[];
derived_keys: DerivedKeyDefinition[];
}
export interface DerivedKeyModelContract {
name: string;
dms_model?: string;
table?: string;
derived_keys?: string[];
}
export interface DerivedKeyDefinition {
name: string;
helper?: string;
description?: string;
join: string;
inputs?: DerivedKeyInput[];
segments: DerivedKeySegment[];
fixtures?: DerivedKeyFixture[];
}
export interface DerivedKeyInput {
name: string;
ts_name?: string;
type?: 'string' | 'number' | 'boolean' | 'scalar';
optional?: boolean;
}
export interface DerivedKeySegment {
name?: string;
prefix?: string;
suffix?: string;
literal?: string;
value?: DerivedKeyValueSource;
transforms?: KeyContractTransform[];
default?: string;
optional?: boolean;
omit_when?: DerivedKeyOmitWhen;
}
export interface DerivedKeyValueSource {
input?: string;
literal?: string;
}
export interface DerivedKeyOmitWhen {
empty?: boolean;
default?: boolean;
values?: string[];
}
export interface DerivedKeyFixture {
name: string;
description?: string;
input: Record<string, KeyContractInputValue>;
expect: string;
}
export declare function parseDerivedKeyContract(raw: string): DerivedKeyContract;
export declare function evaluateDerivedKey(contract: DerivedKeyContract, name: string, input?: Record<string, KeyContractInputValue>): string;
export declare function evaluateDerivedKeyDefinition(key: DerivedKeyDefinition, input?: Record<string, KeyContractInputValue>): string;
export declare function verifyDerivedKeyFixtures(contract: DerivedKeyContract): void;
export declare function validateDerivedKeyContract(value: unknown): asserts value is DerivedKeyContract;
export declare function validateDerivedKeyDefinition(value: unknown): asserts value is DerivedKeyDefinition;