@jaypie/dynamodb 0.1.3 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","sources":["../../../src/client.ts","../../../src/constants.ts","../../../src/keyBuilders.ts","../../../src/entities.ts","../../../src/queries.ts","../../../src/seedExport.ts"],"sourcesContent":["import { DynamoDBClient } from \"@aws-sdk/client-dynamodb\";\nimport { DynamoDBDocumentClient } from \"@aws-sdk/lib-dynamodb\";\nimport { ConfigurationError } from \"@jaypie/errors\";\n\nimport type { DynamoClientConfig } from \"./types.js\";\n\n// Environment variable names\nconst ENV_AWS_REGION = \"AWS_REGION\";\nconst ENV_DYNAMODB_TABLE_NAME = \"DYNAMODB_TABLE_NAME\";\n\n// Defaults\nconst DEFAULT_REGION = \"us-east-1\";\nconst LOCAL_CREDENTIALS = {\n accessKeyId: \"local\",\n secretAccessKey: \"local\",\n};\n\n// Module-level state\nlet docClient: DynamoDBDocumentClient | null = null;\nlet tableName: string | null = null;\n\n/**\n * Check if endpoint indicates local development mode\n */\nfunction isLocalEndpoint(endpoint?: string): boolean {\n if (!endpoint) return false;\n return endpoint.includes(\"127.0.0.1\") || endpoint.includes(\"localhost\");\n}\n\n/**\n * Initialize the DynamoDB client\n * Must be called once at application startup before using query functions\n *\n * @param config - Client configuration\n */\nexport function initClient(config: DynamoClientConfig = {}): void {\n const { endpoint } = config;\n const region =\n config.region ?? process.env[ENV_AWS_REGION] ?? DEFAULT_REGION;\n\n // Auto-detect local mode and use dummy credentials\n const credentials =\n config.credentials ??\n (isLocalEndpoint(endpoint) ? LOCAL_CREDENTIALS : undefined);\n\n const dynamoClient = new DynamoDBClient({\n ...(credentials && { credentials }),\n ...(endpoint && { endpoint }),\n region,\n });\n\n docClient = DynamoDBDocumentClient.from(dynamoClient, {\n marshallOptions: {\n removeUndefinedValues: true,\n },\n });\n\n tableName = config.tableName ?? process.env[ENV_DYNAMODB_TABLE_NAME] ?? null;\n}\n\n/**\n * Get the initialized DynamoDB Document Client\n * @throws ConfigurationError if client has not been initialized\n */\nexport function getDocClient(): DynamoDBDocumentClient {\n if (!docClient) {\n throw new ConfigurationError(\n \"DynamoDB client not initialized. Call initClient() first.\",\n );\n }\n return docClient;\n}\n\n/**\n * Get the configured table name\n * @throws ConfigurationError if client has not been initialized\n */\nexport function getTableName(): string {\n if (!tableName) {\n throw new ConfigurationError(\n \"DynamoDB client not initialized. Call initClient() first.\",\n );\n }\n return tableName;\n}\n\n/**\n * Check if the client has been initialized\n */\nexport function isInitialized(): boolean {\n return docClient !== null && tableName !== null;\n}\n\n/**\n * Reset the client state (primarily for testing)\n */\nexport function resetClient(): void {\n docClient = null;\n tableName = null;\n}\n","// Primary markers\nexport const APEX = \"@\"; // Root-level marker (DynamoDB prohibits empty strings)\nexport const SEPARATOR = \"#\"; // Composite key separator\n\n// GSI names\nexport const INDEX_ALIAS = \"indexAlias\";\nexport const INDEX_CLASS = \"indexClass\";\nexport const INDEX_OU = \"indexOu\";\nexport const INDEX_TYPE = \"indexType\";\nexport const INDEX_XID = \"indexXid\";\n\n// Index suffixes for soft state\nexport const ARCHIVED_SUFFIX = \"#archived\";\nexport const DELETED_SUFFIX = \"#deleted\";\n","import { APEX, SEPARATOR } from \"./constants.js\";\nimport type { FabricEntity, ParentReference } from \"./types.js\";\n\n/**\n * Build the indexOu key for hierarchical queries\n * @param ou - The organizational unit (APEX or \"{parent.model}#{parent.id}\")\n * @param model - The entity model name\n * @returns Composite key: \"{ou}#{model}\"\n */\nexport function buildIndexOu(ou: string, model: string): string {\n return `${ou}${SEPARATOR}${model}`;\n}\n\n/**\n * Build the indexAlias key for human-friendly lookups\n * @param ou - The organizational unit\n * @param model - The entity model name\n * @param alias - The human-friendly alias\n * @returns Composite key: \"{ou}#{model}#{alias}\"\n */\nexport function buildIndexAlias(\n ou: string,\n model: string,\n alias: string,\n): string {\n return `${ou}${SEPARATOR}${model}${SEPARATOR}${alias}`;\n}\n\n/**\n * Build the indexClass key for category filtering\n * @param ou - The organizational unit\n * @param model - The entity model name\n * @param recordClass - The category classification\n * @returns Composite key: \"{ou}#{model}#{class}\"\n */\nexport function buildIndexClass(\n ou: string,\n model: string,\n recordClass: string,\n): string {\n return `${ou}${SEPARATOR}${model}${SEPARATOR}${recordClass}`;\n}\n\n/**\n * Build the indexType key for type filtering\n * @param ou - The organizational unit\n * @param model - The entity model name\n * @param type - The type classification\n * @returns Composite key: \"{ou}#{model}#{type}\"\n */\nexport function buildIndexType(\n ou: string,\n model: string,\n type: string,\n): string {\n return `${ou}${SEPARATOR}${model}${SEPARATOR}${type}`;\n}\n\n/**\n * Build the indexXid key for external ID lookups\n * @param ou - The organizational unit\n * @param model - The entity model name\n * @param xid - The external ID\n * @returns Composite key: \"{ou}#{model}#{xid}\"\n */\nexport function buildIndexXid(ou: string, model: string, xid: string): string {\n return `${ou}${SEPARATOR}${model}${SEPARATOR}${xid}`;\n}\n\n/**\n * Calculate the organizational unit from a parent reference\n * @param parent - Optional parent entity reference\n * @returns APEX (\"@\") if no parent, otherwise \"{parent.model}#{parent.id}\"\n */\nexport function calculateOu(parent?: ParentReference): string {\n if (!parent) {\n return APEX;\n }\n return `${parent.model}${SEPARATOR}${parent.id}`;\n}\n\n/**\n * Auto-populate GSI index keys on an entity\n * - indexOu is always populated from ou + model\n * - indexAlias is populated only when alias is present\n * - indexClass is populated only when class is present\n * - indexType is populated only when type is present\n * - indexXid is populated only when xid is present\n *\n * @param entity - The entity to populate index keys for\n * @param suffix - Optional suffix to append to all index keys (e.g., \"#deleted\", \"#archived\")\n * @returns The entity with populated index keys\n */\nexport function indexEntity<T extends FabricEntity>(\n entity: T,\n suffix: string = \"\",\n): T {\n const result = { ...entity };\n\n // indexOu is always set (from ou + model)\n result.indexOu = buildIndexOu(entity.ou, entity.model) + suffix;\n\n // Optional indexes - only set when the source field is present\n if (entity.alias !== undefined) {\n result.indexAlias =\n buildIndexAlias(entity.ou, entity.model, entity.alias) + suffix;\n }\n\n if (entity.class !== undefined) {\n result.indexClass =\n buildIndexClass(entity.ou, entity.model, entity.class) + suffix;\n }\n\n if (entity.type !== undefined) {\n result.indexType =\n buildIndexType(entity.ou, entity.model, entity.type) + suffix;\n }\n\n if (entity.xid !== undefined) {\n result.indexXid =\n buildIndexXid(entity.ou, entity.model, entity.xid) + suffix;\n }\n\n return result;\n}\n","import { DeleteCommand, GetCommand, PutCommand } from \"@aws-sdk/lib-dynamodb\";\nimport { serviceHandler } from \"@jaypie/vocabulary\";\n\nimport { getDocClient, getTableName } from \"./client.js\";\nimport { ARCHIVED_SUFFIX, DELETED_SUFFIX } from \"./constants.js\";\nimport { indexEntity } from \"./keyBuilders.js\";\nimport type { FabricEntity } from \"./types.js\";\n\n/**\n * Calculate suffix based on entity's archived/deleted state\n */\nfunction calculateEntitySuffix(entity: {\n archivedAt?: string;\n deletedAt?: string;\n}): string {\n const hasArchived = Boolean(entity.archivedAt);\n const hasDeleted = Boolean(entity.deletedAt);\n\n if (hasArchived && hasDeleted) {\n return ARCHIVED_SUFFIX + DELETED_SUFFIX;\n }\n if (hasArchived) {\n return ARCHIVED_SUFFIX;\n }\n if (hasDeleted) {\n return DELETED_SUFFIX;\n }\n return \"\";\n}\n\n/**\n * Get a single entity by primary key\n */\nexport const getEntity = serviceHandler({\n alias: \"getEntity\",\n description: \"Get a single entity by primary key\",\n input: {\n id: { type: String, description: \"Entity ID (sort key)\" },\n model: { type: String, description: \"Entity model (partition key)\" },\n },\n service: async ({ id, model }): Promise<FabricEntity | null> => {\n const docClient = getDocClient();\n const tableName = getTableName();\n\n const command = new GetCommand({\n Key: { id, model },\n TableName: tableName,\n });\n\n const response = await docClient.send(command);\n return (response.Item as FabricEntity) ?? null;\n },\n});\n\n/**\n * Put (create or replace) an entity\n * Auto-populates GSI index keys via indexEntity\n *\n * Note: This is a regular async function (not serviceHandler) because it accepts\n * complex FabricEntity objects that can't be coerced by vocabulary's type system.\n */\nexport async function putEntity({\n entity,\n}: {\n entity: FabricEntity;\n}): Promise<FabricEntity> {\n const docClient = getDocClient();\n const tableName = getTableName();\n\n // Auto-populate index keys\n const indexedEntity = indexEntity(entity);\n\n const command = new PutCommand({\n Item: indexedEntity,\n TableName: tableName,\n });\n\n await docClient.send(command);\n return indexedEntity;\n}\n\n/**\n * Update an existing entity\n * Auto-populates GSI index keys and sets updatedAt\n *\n * Note: This is a regular async function (not serviceHandler) because it accepts\n * complex FabricEntity objects that can't be coerced by vocabulary's type system.\n */\nexport async function updateEntity({\n entity,\n}: {\n entity: FabricEntity;\n}): Promise<FabricEntity> {\n const docClient = getDocClient();\n const tableName = getTableName();\n\n // Update timestamp and re-index\n const updatedEntity = indexEntity({\n ...entity,\n updatedAt: new Date().toISOString(),\n });\n\n const command = new PutCommand({\n Item: updatedEntity,\n TableName: tableName,\n });\n\n await docClient.send(command);\n return updatedEntity;\n}\n\n/**\n * Soft delete an entity by setting deletedAt timestamp\n * Re-indexes with appropriate suffix based on archived/deleted state\n */\nexport const deleteEntity = serviceHandler({\n alias: \"deleteEntity\",\n description: \"Soft delete an entity (sets deletedAt timestamp)\",\n input: {\n id: { type: String, description: \"Entity ID (sort key)\" },\n model: { type: String, description: \"Entity model (partition key)\" },\n },\n service: async ({ id, model }): Promise<boolean> => {\n const docClient = getDocClient();\n const tableName = getTableName();\n\n // Fetch the current entity\n const existing = await getEntity({ id, model });\n if (!existing) {\n return false;\n }\n\n const now = new Date().toISOString();\n\n // Build updated entity with deletedAt\n const updatedEntity = {\n ...existing,\n deletedAt: now,\n updatedAt: now,\n };\n\n // Calculate suffix based on combined state (may already be archived)\n const suffix = calculateEntitySuffix(updatedEntity);\n const deletedEntity = indexEntity(updatedEntity, suffix);\n\n const command = new PutCommand({\n Item: deletedEntity,\n TableName: tableName,\n });\n\n await docClient.send(command);\n return true;\n },\n});\n\n/**\n * Archive an entity by setting archivedAt timestamp\n * Re-indexes with appropriate suffix based on archived/deleted state\n */\nexport const archiveEntity = serviceHandler({\n alias: \"archiveEntity\",\n description: \"Archive an entity (sets archivedAt timestamp)\",\n input: {\n id: { type: String, description: \"Entity ID (sort key)\" },\n model: { type: String, description: \"Entity model (partition key)\" },\n },\n service: async ({ id, model }): Promise<boolean> => {\n const docClient = getDocClient();\n const tableName = getTableName();\n\n // Fetch the current entity\n const existing = await getEntity({ id, model });\n if (!existing) {\n return false;\n }\n\n const now = new Date().toISOString();\n\n // Build updated entity with archivedAt\n const updatedEntity = {\n ...existing,\n archivedAt: now,\n updatedAt: now,\n };\n\n // Calculate suffix based on combined state (may already be deleted)\n const suffix = calculateEntitySuffix(updatedEntity);\n const archivedEntity = indexEntity(updatedEntity, suffix);\n\n const command = new PutCommand({\n Item: archivedEntity,\n TableName: tableName,\n });\n\n await docClient.send(command);\n return true;\n },\n});\n\n/**\n * Hard delete an entity (permanently removes from table)\n * Use with caution - prefer deleteEntity for soft delete\n */\nexport const destroyEntity = serviceHandler({\n alias: \"destroyEntity\",\n description: \"Hard delete an entity (permanently removes from table)\",\n input: {\n id: { type: String, description: \"Entity ID (sort key)\" },\n model: { type: String, description: \"Entity model (partition key)\" },\n },\n service: async ({ id, model }): Promise<boolean> => {\n const docClient = getDocClient();\n const tableName = getTableName();\n\n const command = new DeleteCommand({\n Key: { id, model },\n TableName: tableName,\n });\n\n await docClient.send(command);\n return true;\n },\n});\n","import { QueryCommand } from \"@aws-sdk/lib-dynamodb\";\nimport { serviceHandler } from \"@jaypie/vocabulary\";\n\nimport { getDocClient, getTableName } from \"./client.js\";\nimport {\n ARCHIVED_SUFFIX,\n DELETED_SUFFIX,\n INDEX_ALIAS,\n INDEX_CLASS,\n INDEX_OU,\n INDEX_TYPE,\n INDEX_XID,\n} from \"./constants.js\";\nimport {\n buildIndexAlias,\n buildIndexClass,\n buildIndexOu,\n buildIndexType,\n buildIndexXid,\n} from \"./keyBuilders.js\";\nimport type { BaseQueryOptions, FabricEntity, QueryResult } from \"./types.js\";\n\n/**\n * Calculate the suffix based on archived/deleted flags\n * When both are true, returns combined suffix (archived first, alphabetically)\n */\nfunction calculateSuffix({\n archived,\n deleted,\n}: {\n archived?: boolean;\n deleted?: boolean;\n}): string {\n if (archived && deleted) {\n return ARCHIVED_SUFFIX + DELETED_SUFFIX;\n }\n if (archived) {\n return ARCHIVED_SUFFIX;\n }\n if (deleted) {\n return DELETED_SUFFIX;\n }\n return \"\";\n}\n\n/**\n * Execute a GSI query with common options\n */\nasync function executeQuery<T extends FabricEntity>(\n indexName: string,\n keyValue: string,\n options: BaseQueryOptions = {},\n): Promise<QueryResult<T>> {\n const { ascending = false, limit, startKey } = options;\n\n const docClient = getDocClient();\n const tableName = getTableName();\n\n const command = new QueryCommand({\n ExclusiveStartKey: startKey as Record<string, unknown> | undefined,\n ExpressionAttributeNames: {\n \"#pk\": indexName,\n },\n ExpressionAttributeValues: {\n \":pkValue\": keyValue,\n },\n IndexName: indexName,\n KeyConditionExpression: \"#pk = :pkValue\",\n ...(limit && { Limit: limit }),\n ScanIndexForward: ascending,\n TableName: tableName,\n });\n\n const response = await docClient.send(command);\n\n return {\n items: (response.Items ?? []) as T[],\n lastEvaluatedKey: response.LastEvaluatedKey,\n };\n}\n\n/**\n * Query parameters for queryByOu\n */\ninterface QueryByOuParams extends BaseQueryOptions {\n model: string;\n ou: string;\n}\n\n/**\n * Query entities by organizational unit (parent hierarchy)\n * Uses indexOu GSI\n *\n * Note: This is a regular async function (not serviceHandler) because it accepts\n * complex startKey objects that can't be coerced by vocabulary's type system.\n */\nexport async function queryByOu({\n archived = false,\n ascending = false,\n deleted = false,\n limit,\n model,\n ou,\n startKey,\n}: QueryByOuParams): Promise<QueryResult<FabricEntity>> {\n const suffix = calculateSuffix({ archived, deleted });\n const keyValue = buildIndexOu(ou, model) + suffix;\n return executeQuery<FabricEntity>(INDEX_OU, keyValue, {\n ascending,\n limit,\n startKey,\n });\n}\n\n/**\n * Query a single entity by human-friendly alias\n * Uses indexAlias GSI\n */\nexport const queryByAlias = serviceHandler({\n alias: \"queryByAlias\",\n description: \"Query a single entity by human-friendly alias\",\n input: {\n alias: { type: String, description: \"Human-friendly alias\" },\n archived: {\n type: Boolean,\n default: false,\n required: false,\n description: \"Query archived entities instead of active ones\",\n },\n deleted: {\n type: Boolean,\n default: false,\n required: false,\n description: \"Query deleted entities instead of active ones\",\n },\n model: { type: String, description: \"Entity model name\" },\n ou: { type: String, description: \"Organizational unit (@ for root)\" },\n },\n service: async ({\n alias,\n archived,\n deleted,\n model,\n ou,\n }): Promise<FabricEntity | null> => {\n const aliasStr = alias as string;\n const archivedBool = archived as boolean | undefined;\n const deletedBool = deleted as boolean | undefined;\n const modelStr = model as string;\n const ouStr = ou as string;\n\n const suffix = calculateSuffix({ archived: archivedBool, deleted: deletedBool });\n const keyValue = buildIndexAlias(ouStr, modelStr, aliasStr) + suffix;\n const result = await executeQuery<FabricEntity>(INDEX_ALIAS, keyValue, {\n limit: 1,\n });\n return result.items[0] ?? null;\n },\n});\n\n/**\n * Query parameters for queryByClass\n */\ninterface QueryByClassParams extends BaseQueryOptions {\n model: string;\n ou: string;\n recordClass: string;\n}\n\n/**\n * Query entities by category classification\n * Uses indexClass GSI\n *\n * Note: This is a regular async function (not serviceHandler) because it accepts\n * complex startKey objects that can't be coerced by vocabulary's type system.\n */\nexport async function queryByClass({\n archived = false,\n ascending = false,\n deleted = false,\n limit,\n model,\n ou,\n recordClass,\n startKey,\n}: QueryByClassParams): Promise<QueryResult<FabricEntity>> {\n const suffix = calculateSuffix({ archived, deleted });\n const keyValue = buildIndexClass(ou, model, recordClass) + suffix;\n return executeQuery<FabricEntity>(INDEX_CLASS, keyValue, {\n ascending,\n limit,\n startKey,\n });\n}\n\n/**\n * Query parameters for queryByType\n */\ninterface QueryByTypeParams extends BaseQueryOptions {\n model: string;\n ou: string;\n type: string;\n}\n\n/**\n * Query entities by type classification\n * Uses indexType GSI\n *\n * Note: This is a regular async function (not serviceHandler) because it accepts\n * complex startKey objects that can't be coerced by vocabulary's type system.\n */\nexport async function queryByType({\n archived = false,\n ascending = false,\n deleted = false,\n limit,\n model,\n ou,\n startKey,\n type,\n}: QueryByTypeParams): Promise<QueryResult<FabricEntity>> {\n const suffix = calculateSuffix({ archived, deleted });\n const keyValue = buildIndexType(ou, model, type) + suffix;\n return executeQuery<FabricEntity>(INDEX_TYPE, keyValue, {\n ascending,\n limit,\n startKey,\n });\n}\n\n/**\n * Query a single entity by external ID\n * Uses indexXid GSI\n */\nexport const queryByXid = serviceHandler({\n alias: \"queryByXid\",\n description: \"Query a single entity by external ID\",\n input: {\n archived: {\n type: Boolean,\n default: false,\n required: false,\n description: \"Query archived entities instead of active ones\",\n },\n deleted: {\n type: Boolean,\n default: false,\n required: false,\n description: \"Query deleted entities instead of active ones\",\n },\n model: { type: String, description: \"Entity model name\" },\n ou: { type: String, description: \"Organizational unit (@ for root)\" },\n xid: { type: String, description: \"External ID\" },\n },\n service: async ({\n archived,\n deleted,\n model,\n ou,\n xid,\n }): Promise<FabricEntity | null> => {\n const archivedBool = archived as boolean | undefined;\n const deletedBool = deleted as boolean | undefined;\n const modelStr = model as string;\n const ouStr = ou as string;\n const xidStr = xid as string;\n\n const suffix = calculateSuffix({ archived: archivedBool, deleted: deletedBool });\n const keyValue = buildIndexXid(ouStr, modelStr, xidStr) + suffix;\n const result = await executeQuery<FabricEntity>(INDEX_XID, keyValue, {\n limit: 1,\n });\n return result.items[0] ?? null;\n },\n});\n","import { putEntity } from \"./entities.js\";\nimport { queryByAlias, queryByOu } from \"./queries.js\";\nimport type { FabricEntity } from \"./types.js\";\n\n/**\n * Result of a seed operation\n */\nexport interface SeedResult {\n /** Aliases of entities that were created */\n created: string[];\n /** Error details for failed seed operations */\n errors: Array<{ alias: string; error: string }>;\n /** Aliases of entities that were skipped (already exist) */\n skipped: string[];\n}\n\n/**\n * Options for seed operations\n */\nexport interface SeedOptions {\n /** Preview without writing (default: false) */\n dryRun?: boolean;\n /** Overwrite existing entities (default: false) */\n replace?: boolean;\n}\n\n/**\n * Result of an export operation\n */\nexport interface ExportResult<T extends FabricEntity = FabricEntity> {\n /** Number of entities exported */\n count: number;\n /** Exported entities */\n entities: T[];\n}\n\n/**\n * Seed a single entity if it doesn't already exist\n *\n * @param entity - Partial entity with at least alias, model, and ou\n * @returns true if entity was created, false if it already exists\n */\nexport async function seedEntityIfNotExists<T extends Partial<FabricEntity>>(\n entity: T,\n): Promise<boolean> {\n if (!entity.alias || !entity.model || !entity.ou) {\n throw new Error(\n \"Entity must have alias, model, and ou to check existence\",\n );\n }\n\n // Check if entity already exists\n const existing = await queryByAlias({\n alias: entity.alias,\n model: entity.model,\n ou: entity.ou,\n });\n\n if (existing) {\n return false;\n }\n\n // Generate required fields if missing\n const now = new Date().toISOString();\n const completeEntity: FabricEntity = {\n createdAt: entity.createdAt ?? now,\n id: entity.id ?? crypto.randomUUID(),\n model: entity.model,\n name: entity.name ?? entity.alias,\n ou: entity.ou,\n sequence: entity.sequence ?? Date.now(),\n updatedAt: entity.updatedAt ?? now,\n ...entity,\n } as FabricEntity;\n\n await putEntity({ entity: completeEntity });\n return true;\n}\n\n/**\n * Seed multiple entities (idempotent)\n *\n * - Checks existence by alias (via queryByAlias) before creating\n * - Auto-generates id (UUID), createdAt, updatedAt if missing\n * - Skip existing unless replace: true\n * - Returns counts of created/skipped/errors\n *\n * @param entities - Array of partial entities to seed\n * @param options - Seed options\n * @returns Result with created, skipped, and errors arrays\n */\nexport async function seedEntities<T extends Partial<FabricEntity>>(\n entities: T[],\n options: SeedOptions = {},\n): Promise<SeedResult> {\n const { dryRun = false, replace = false } = options;\n const result: SeedResult = {\n created: [],\n errors: [],\n skipped: [],\n };\n\n for (const entity of entities) {\n const alias = entity.alias ?? entity.name ?? \"unknown\";\n\n try {\n if (!entity.model || !entity.ou) {\n throw new Error(\"Entity must have model and ou\");\n }\n\n // For entities with alias, check existence\n if (entity.alias) {\n const existing = await queryByAlias({\n alias: entity.alias,\n model: entity.model,\n ou: entity.ou,\n });\n\n if (existing && !replace) {\n result.skipped.push(alias);\n continue;\n }\n\n // If replacing, use existing ID to update rather than create new\n if (existing && replace) {\n entity.id = existing.id;\n }\n }\n\n if (dryRun) {\n result.created.push(alias);\n continue;\n }\n\n // Generate required fields if missing\n const now = new Date().toISOString();\n const completeEntity: FabricEntity = {\n createdAt: entity.createdAt ?? now,\n id: entity.id ?? crypto.randomUUID(),\n model: entity.model,\n name: entity.name ?? entity.alias ?? \"Unnamed\",\n ou: entity.ou,\n sequence: entity.sequence ?? Date.now(),\n updatedAt: entity.updatedAt ?? now,\n ...entity,\n } as FabricEntity;\n\n await putEntity({ entity: completeEntity });\n result.created.push(alias);\n } catch (error) {\n const errorMessage =\n error instanceof Error ? error.message : String(error);\n result.errors.push({ alias, error: errorMessage });\n }\n }\n\n return result;\n}\n\n/**\n * Export entities by model and organizational unit\n *\n * - Paginates through all matching entities via queryByOu\n * - Returns entities sorted by sequence (ascending)\n *\n * @param model - The entity model name\n * @param ou - The organizational unit\n * @param limit - Optional maximum number of entities to export\n * @returns Export result with entities and count\n */\nexport async function exportEntities<T extends FabricEntity>(\n model: string,\n ou: string,\n limit?: number,\n): Promise<ExportResult<T>> {\n const entities: T[] = [];\n let startKey: Record<string, unknown> | undefined;\n let remaining = limit;\n\n do {\n const batchLimit =\n remaining !== undefined ? Math.min(remaining, 100) : undefined;\n\n const { items, lastEvaluatedKey } = await queryByOu({\n ascending: true,\n limit: batchLimit,\n model,\n ou,\n startKey,\n });\n\n entities.push(...(items as T[]));\n startKey = lastEvaluatedKey;\n\n if (remaining !== undefined) {\n remaining -= items.length;\n }\n } while (startKey && (remaining === undefined || remaining > 0));\n\n return {\n count: entities.length,\n entities,\n };\n}\n\n/**\n * Export entities as a JSON string\n *\n * @param model - The entity model name\n * @param ou - The organizational unit\n * @param pretty - Format JSON with indentation (default: true)\n * @returns JSON string of exported entities\n */\nexport async function exportEntitiesToJson(\n model: string,\n ou: string,\n pretty: boolean = true,\n): Promise<string> {\n const { entities } = await exportEntities(model, ou);\n return pretty ? JSON.stringify(entities, null, 2) : JSON.stringify(entities);\n}\n"],"names":["DynamoDBClient","DynamoDBDocumentClient","ConfigurationError","serviceHandler","GetCommand","PutCommand","DeleteCommand","QueryCommand"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","sources":["../../../src/client.ts","../../../src/constants.ts","../../../src/keyBuilders.ts","../../../src/entities.ts","../../../src/queries.ts","../../../src/query.ts","../../../src/seedExport.ts"],"sourcesContent":["import { DynamoDBClient } from \"@aws-sdk/client-dynamodb\";\nimport { DynamoDBDocumentClient } from \"@aws-sdk/lib-dynamodb\";\nimport { ConfigurationError } from \"@jaypie/errors\";\n\nimport type { DynamoClientConfig } from \"./types.js\";\n\n// Environment variable names\nconst ENV_AWS_REGION = \"AWS_REGION\";\nconst ENV_DYNAMODB_TABLE_NAME = \"DYNAMODB_TABLE_NAME\";\n\n// Defaults\nconst DEFAULT_REGION = \"us-east-1\";\nconst LOCAL_CREDENTIALS = {\n accessKeyId: \"local\",\n secretAccessKey: \"local\",\n};\n\n// Module-level state\nlet docClient: DynamoDBDocumentClient | null = null;\nlet tableName: string | null = null;\n\n/**\n * Check if endpoint indicates local development mode\n */\nfunction isLocalEndpoint(endpoint?: string): boolean {\n if (!endpoint) return false;\n return endpoint.includes(\"127.0.0.1\") || endpoint.includes(\"localhost\");\n}\n\n/**\n * Initialize the DynamoDB client\n * Must be called once at application startup before using query functions\n *\n * @param config - Client configuration\n */\nexport function initClient(config: DynamoClientConfig = {}): void {\n const { endpoint } = config;\n const region =\n config.region ?? process.env[ENV_AWS_REGION] ?? DEFAULT_REGION;\n\n // Auto-detect local mode and use dummy credentials\n const credentials =\n config.credentials ??\n (isLocalEndpoint(endpoint) ? LOCAL_CREDENTIALS : undefined);\n\n const dynamoClient = new DynamoDBClient({\n ...(credentials && { credentials }),\n ...(endpoint && { endpoint }),\n region,\n });\n\n docClient = DynamoDBDocumentClient.from(dynamoClient, {\n marshallOptions: {\n removeUndefinedValues: true,\n },\n });\n\n tableName = config.tableName ?? process.env[ENV_DYNAMODB_TABLE_NAME] ?? null;\n}\n\n/**\n * Get the initialized DynamoDB Document Client\n * @throws ConfigurationError if client has not been initialized\n */\nexport function getDocClient(): DynamoDBDocumentClient {\n if (!docClient) {\n throw new ConfigurationError(\n \"DynamoDB client not initialized. Call initClient() first.\",\n );\n }\n return docClient;\n}\n\n/**\n * Get the configured table name\n * @throws ConfigurationError if client has not been initialized\n */\nexport function getTableName(): string {\n if (!tableName) {\n throw new ConfigurationError(\n \"DynamoDB client not initialized. Call initClient() first.\",\n );\n }\n return tableName;\n}\n\n/**\n * Check if the client has been initialized\n */\nexport function isInitialized(): boolean {\n return docClient !== null && tableName !== null;\n}\n\n/**\n * Reset the client state (primarily for testing)\n */\nexport function resetClient(): void {\n docClient = null;\n tableName = null;\n}\n","// Re-export shared constants from vocabulary\nexport { APEX, ARCHIVED_SUFFIX, DELETED_SUFFIX, SEPARATOR } from \"@jaypie/vocabulary\";\n\n// GSI names (derived from DEFAULT_INDEXES)\nexport const INDEX_ALIAS = \"indexAlias\";\nexport const INDEX_CLASS = \"indexClass\";\nexport const INDEX_OU = \"indexOu\";\nexport const INDEX_TYPE = \"indexType\";\nexport const INDEX_XID = \"indexXid\";\n","import {\n buildCompositeKey as vocabBuildCompositeKey,\n calculateOu as vocabCalculateOu,\n DEFAULT_INDEXES,\n getModelIndexes,\n type IndexableEntity,\n populateIndexKeys as vocabPopulateIndexKeys,\n} from \"@jaypie/vocabulary\";\n\nimport { APEX, SEPARATOR } from \"./constants.js\";\nimport type { ParentReference, StorableEntity } from \"./types.js\";\n\n// =============================================================================\n// Key Builders\n// =============================================================================\n\n/**\n * Build the indexOu key for hierarchical queries\n * @param ou - The organizational unit (APEX or \"{parent.model}#{parent.id}\")\n * @param model - The entity model name\n * @returns Composite key: \"{ou}#{model}\"\n */\nexport function buildIndexOu(ou: string, model: string): string {\n return `${ou}${SEPARATOR}${model}`;\n}\n\n/**\n * Build the indexAlias key for human-friendly lookups\n * @param ou - The organizational unit\n * @param model - The entity model name\n * @param alias - The human-friendly alias\n * @returns Composite key: \"{ou}#{model}#{alias}\"\n */\nexport function buildIndexAlias(\n ou: string,\n model: string,\n alias: string,\n): string {\n return `${ou}${SEPARATOR}${model}${SEPARATOR}${alias}`;\n}\n\n/**\n * Build the indexClass key for category filtering\n * @param ou - The organizational unit\n * @param model - The entity model name\n * @param recordClass - The category classification\n * @returns Composite key: \"{ou}#{model}#{class}\"\n */\nexport function buildIndexClass(\n ou: string,\n model: string,\n recordClass: string,\n): string {\n return `${ou}${SEPARATOR}${model}${SEPARATOR}${recordClass}`;\n}\n\n/**\n * Build the indexType key for type filtering\n * @param ou - The organizational unit\n * @param model - The entity model name\n * @param type - The type classification\n * @returns Composite key: \"{ou}#{model}#{type}\"\n */\nexport function buildIndexType(\n ou: string,\n model: string,\n type: string,\n): string {\n return `${ou}${SEPARATOR}${model}${SEPARATOR}${type}`;\n}\n\n/**\n * Build the indexXid key for external ID lookups\n * @param ou - The organizational unit\n * @param model - The entity model name\n * @param xid - The external ID\n * @returns Composite key: \"{ou}#{model}#{xid}\"\n */\nexport function buildIndexXid(ou: string, model: string, xid: string): string {\n return `${ou}${SEPARATOR}${model}${SEPARATOR}${xid}`;\n}\n\n// =============================================================================\n// New Vocabulary-Based Functions\n// =============================================================================\n\n/**\n * Build a composite key from entity fields\n *\n * @param entity - Entity with fields to extract\n * @param fields - Field names to combine with SEPARATOR\n * @param suffix - Optional suffix to append (e.g., \"#deleted\")\n * @returns Composite key string\n */\nexport function buildCompositeKey(\n entity: IndexableEntity,\n fields: string[],\n suffix?: string,\n): string {\n return vocabBuildCompositeKey(entity, fields, suffix);\n}\n\n/**\n * Calculate the organizational unit from a parent reference\n * @param parent - Optional parent entity reference\n * @returns APEX (\"@\") if no parent, otherwise \"{parent.model}#{parent.id}\"\n */\nexport function calculateOu(parent?: ParentReference): string {\n if (!parent) {\n return APEX;\n }\n return vocabCalculateOu(parent);\n}\n\n/**\n * Auto-populate GSI index keys on an entity\n *\n * Uses the model's registered indexes (from vocabulary registry) or\n * DEFAULT_INDEXES if no custom indexes are registered.\n *\n * - indexOu is always populated from ou + model\n * - indexAlias is populated only when alias is present\n * - indexClass is populated only when class is present\n * - indexType is populated only when type is present\n * - indexXid is populated only when xid is present\n *\n * @param entity - The entity to populate index keys for\n * @param suffix - Optional suffix to append to all index keys (e.g., \"#deleted\", \"#archived\")\n * @returns The entity with populated index keys\n */\nexport function indexEntity<T extends StorableEntity>(\n entity: T,\n suffix: string = \"\",\n): T {\n const indexes = getModelIndexes(entity.model);\n // Cast through unknown to bridge the type gap between StorableEntity and IndexableEntity\n return vocabPopulateIndexKeys(entity as unknown as IndexableEntity, indexes, suffix) as unknown as T;\n}\n\n// Re-export DEFAULT_INDEXES for convenience\nexport { DEFAULT_INDEXES };\n","import { DeleteCommand, GetCommand, PutCommand } from \"@aws-sdk/lib-dynamodb\";\nimport { serviceHandler } from \"@jaypie/vocabulary\";\n\nimport { getDocClient, getTableName } from \"./client.js\";\nimport { ARCHIVED_SUFFIX, DELETED_SUFFIX } from \"./constants.js\";\nimport { indexEntity } from \"./keyBuilders.js\";\nimport type { StorableEntity } from \"./types.js\";\n\n/**\n * Calculate suffix based on entity's archived/deleted state\n */\nfunction calculateEntitySuffix(entity: {\n archivedAt?: string;\n deletedAt?: string;\n}): string {\n const hasArchived = Boolean(entity.archivedAt);\n const hasDeleted = Boolean(entity.deletedAt);\n\n if (hasArchived && hasDeleted) {\n return ARCHIVED_SUFFIX + DELETED_SUFFIX;\n }\n if (hasArchived) {\n return ARCHIVED_SUFFIX;\n }\n if (hasDeleted) {\n return DELETED_SUFFIX;\n }\n return \"\";\n}\n\n/**\n * Get a single entity by primary key\n */\nexport const getEntity = serviceHandler({\n alias: \"getEntity\",\n description: \"Get a single entity by primary key\",\n input: {\n id: { type: String, description: \"Entity ID (sort key)\" },\n model: { type: String, description: \"Entity model (partition key)\" },\n },\n service: async ({ id, model }): Promise<StorableEntity | null> => {\n const docClient = getDocClient();\n const tableName = getTableName();\n\n const command = new GetCommand({\n Key: { id, model },\n TableName: tableName,\n });\n\n const response = await docClient.send(command);\n return (response.Item as StorableEntity) ?? null;\n },\n});\n\n/**\n * Put (create or replace) an entity\n * Auto-populates GSI index keys via indexEntity\n *\n * Note: This is a regular async function (not serviceHandler) because it accepts\n * complex StorableEntity objects that can't be coerced by vocabulary's type system.\n */\nexport async function putEntity({\n entity,\n}: {\n entity: StorableEntity;\n}): Promise<StorableEntity> {\n const docClient = getDocClient();\n const tableName = getTableName();\n\n // Auto-populate index keys\n const indexedEntity = indexEntity(entity);\n\n const command = new PutCommand({\n Item: indexedEntity,\n TableName: tableName,\n });\n\n await docClient.send(command);\n return indexedEntity;\n}\n\n/**\n * Update an existing entity\n * Auto-populates GSI index keys and sets updatedAt\n *\n * Note: This is a regular async function (not serviceHandler) because it accepts\n * complex StorableEntity objects that can't be coerced by vocabulary's type system.\n */\nexport async function updateEntity({\n entity,\n}: {\n entity: StorableEntity;\n}): Promise<StorableEntity> {\n const docClient = getDocClient();\n const tableName = getTableName();\n\n // Update timestamp and re-index\n const updatedEntity = indexEntity({\n ...entity,\n updatedAt: new Date().toISOString(),\n });\n\n const command = new PutCommand({\n Item: updatedEntity,\n TableName: tableName,\n });\n\n await docClient.send(command);\n return updatedEntity;\n}\n\n/**\n * Soft delete an entity by setting deletedAt timestamp\n * Re-indexes with appropriate suffix based on archived/deleted state\n */\nexport const deleteEntity = serviceHandler({\n alias: \"deleteEntity\",\n description: \"Soft delete an entity (sets deletedAt timestamp)\",\n input: {\n id: { type: String, description: \"Entity ID (sort key)\" },\n model: { type: String, description: \"Entity model (partition key)\" },\n },\n service: async ({ id, model }): Promise<boolean> => {\n const docClient = getDocClient();\n const tableName = getTableName();\n\n // Fetch the current entity\n const existing = await getEntity({ id, model });\n if (!existing) {\n return false;\n }\n\n const now = new Date().toISOString();\n\n // Build updated entity with deletedAt\n const updatedEntity = {\n ...existing,\n deletedAt: now,\n updatedAt: now,\n };\n\n // Calculate suffix based on combined state (may already be archived)\n const suffix = calculateEntitySuffix(updatedEntity);\n const deletedEntity = indexEntity(updatedEntity, suffix);\n\n const command = new PutCommand({\n Item: deletedEntity,\n TableName: tableName,\n });\n\n await docClient.send(command);\n return true;\n },\n});\n\n/**\n * Archive an entity by setting archivedAt timestamp\n * Re-indexes with appropriate suffix based on archived/deleted state\n */\nexport const archiveEntity = serviceHandler({\n alias: \"archiveEntity\",\n description: \"Archive an entity (sets archivedAt timestamp)\",\n input: {\n id: { type: String, description: \"Entity ID (sort key)\" },\n model: { type: String, description: \"Entity model (partition key)\" },\n },\n service: async ({ id, model }): Promise<boolean> => {\n const docClient = getDocClient();\n const tableName = getTableName();\n\n // Fetch the current entity\n const existing = await getEntity({ id, model });\n if (!existing) {\n return false;\n }\n\n const now = new Date().toISOString();\n\n // Build updated entity with archivedAt\n const updatedEntity = {\n ...existing,\n archivedAt: now,\n updatedAt: now,\n };\n\n // Calculate suffix based on combined state (may already be deleted)\n const suffix = calculateEntitySuffix(updatedEntity);\n const archivedEntity = indexEntity(updatedEntity, suffix);\n\n const command = new PutCommand({\n Item: archivedEntity,\n TableName: tableName,\n });\n\n await docClient.send(command);\n return true;\n },\n});\n\n/**\n * Hard delete an entity (permanently removes from table)\n * Use with caution - prefer deleteEntity for soft delete\n */\nexport const destroyEntity = serviceHandler({\n alias: \"destroyEntity\",\n description: \"Hard delete an entity (permanently removes from table)\",\n input: {\n id: { type: String, description: \"Entity ID (sort key)\" },\n model: { type: String, description: \"Entity model (partition key)\" },\n },\n service: async ({ id, model }): Promise<boolean> => {\n const docClient = getDocClient();\n const tableName = getTableName();\n\n const command = new DeleteCommand({\n Key: { id, model },\n TableName: tableName,\n });\n\n await docClient.send(command);\n return true;\n },\n});\n","import { QueryCommand } from \"@aws-sdk/lib-dynamodb\";\nimport { serviceHandler } from \"@jaypie/vocabulary\";\n\nimport { getDocClient, getTableName } from \"./client.js\";\nimport {\n ARCHIVED_SUFFIX,\n DELETED_SUFFIX,\n INDEX_ALIAS,\n INDEX_CLASS,\n INDEX_OU,\n INDEX_TYPE,\n INDEX_XID,\n} from \"./constants.js\";\nimport {\n buildIndexAlias,\n buildIndexClass,\n buildIndexOu,\n buildIndexType,\n buildIndexXid,\n} from \"./keyBuilders.js\";\nimport type { BaseQueryOptions, StorableEntity, QueryResult } from \"./types.js\";\n\n/**\n * Calculate the suffix based on archived/deleted flags\n * When both are true, returns combined suffix (archived first, alphabetically)\n */\nfunction calculateSuffix({\n archived,\n deleted,\n}: {\n archived?: boolean;\n deleted?: boolean;\n}): string {\n if (archived && deleted) {\n return ARCHIVED_SUFFIX + DELETED_SUFFIX;\n }\n if (archived) {\n return ARCHIVED_SUFFIX;\n }\n if (deleted) {\n return DELETED_SUFFIX;\n }\n return \"\";\n}\n\n/**\n * Execute a GSI query with common options\n */\nasync function executeQuery<T extends StorableEntity>(\n indexName: string,\n keyValue: string,\n options: BaseQueryOptions = {},\n): Promise<QueryResult<T>> {\n const { ascending = false, limit, startKey } = options;\n\n const docClient = getDocClient();\n const tableName = getTableName();\n\n const command = new QueryCommand({\n ExclusiveStartKey: startKey as Record<string, unknown> | undefined,\n ExpressionAttributeNames: {\n \"#pk\": indexName,\n },\n ExpressionAttributeValues: {\n \":pkValue\": keyValue,\n },\n IndexName: indexName,\n KeyConditionExpression: \"#pk = :pkValue\",\n ...(limit && { Limit: limit }),\n ScanIndexForward: ascending,\n TableName: tableName,\n });\n\n const response = await docClient.send(command);\n\n return {\n items: (response.Items ?? []) as T[],\n lastEvaluatedKey: response.LastEvaluatedKey,\n };\n}\n\n/**\n * Query parameters for queryByOu\n */\ninterface QueryByOuParams extends BaseQueryOptions {\n model: string;\n ou: string;\n}\n\n/**\n * Query entities by organizational unit (parent hierarchy)\n * Uses indexOu GSI\n *\n * Note: This is a regular async function (not serviceHandler) because it accepts\n * complex startKey objects that can't be coerced by vocabulary's type system.\n */\nexport async function queryByOu({\n archived = false,\n ascending = false,\n deleted = false,\n limit,\n model,\n ou,\n startKey,\n}: QueryByOuParams): Promise<QueryResult<StorableEntity>> {\n const suffix = calculateSuffix({ archived, deleted });\n const keyValue = buildIndexOu(ou, model) + suffix;\n return executeQuery<StorableEntity>(INDEX_OU, keyValue, {\n ascending,\n limit,\n startKey,\n });\n}\n\n/**\n * Query a single entity by human-friendly alias\n * Uses indexAlias GSI\n */\nexport const queryByAlias = serviceHandler({\n alias: \"queryByAlias\",\n description: \"Query a single entity by human-friendly alias\",\n input: {\n alias: { type: String, description: \"Human-friendly alias\" },\n archived: {\n type: Boolean,\n default: false,\n required: false,\n description: \"Query archived entities instead of active ones\",\n },\n deleted: {\n type: Boolean,\n default: false,\n required: false,\n description: \"Query deleted entities instead of active ones\",\n },\n model: { type: String, description: \"Entity model name\" },\n ou: { type: String, description: \"Organizational unit (@ for root)\" },\n },\n service: async ({\n alias,\n archived,\n deleted,\n model,\n ou,\n }): Promise<StorableEntity | null> => {\n const aliasStr = alias as string;\n const archivedBool = archived as boolean | undefined;\n const deletedBool = deleted as boolean | undefined;\n const modelStr = model as string;\n const ouStr = ou as string;\n\n const suffix = calculateSuffix({ archived: archivedBool, deleted: deletedBool });\n const keyValue = buildIndexAlias(ouStr, modelStr, aliasStr) + suffix;\n const result = await executeQuery<StorableEntity>(INDEX_ALIAS, keyValue, {\n limit: 1,\n });\n return result.items[0] ?? null;\n },\n});\n\n/**\n * Query parameters for queryByClass\n */\ninterface QueryByClassParams extends BaseQueryOptions {\n model: string;\n ou: string;\n recordClass: string;\n}\n\n/**\n * Query entities by category classification\n * Uses indexClass GSI\n *\n * Note: This is a regular async function (not serviceHandler) because it accepts\n * complex startKey objects that can't be coerced by vocabulary's type system.\n */\nexport async function queryByClass({\n archived = false,\n ascending = false,\n deleted = false,\n limit,\n model,\n ou,\n recordClass,\n startKey,\n}: QueryByClassParams): Promise<QueryResult<StorableEntity>> {\n const suffix = calculateSuffix({ archived, deleted });\n const keyValue = buildIndexClass(ou, model, recordClass) + suffix;\n return executeQuery<StorableEntity>(INDEX_CLASS, keyValue, {\n ascending,\n limit,\n startKey,\n });\n}\n\n/**\n * Query parameters for queryByType\n */\ninterface QueryByTypeParams extends BaseQueryOptions {\n model: string;\n ou: string;\n type: string;\n}\n\n/**\n * Query entities by type classification\n * Uses indexType GSI\n *\n * Note: This is a regular async function (not serviceHandler) because it accepts\n * complex startKey objects that can't be coerced by vocabulary's type system.\n */\nexport async function queryByType({\n archived = false,\n ascending = false,\n deleted = false,\n limit,\n model,\n ou,\n startKey,\n type,\n}: QueryByTypeParams): Promise<QueryResult<StorableEntity>> {\n const suffix = calculateSuffix({ archived, deleted });\n const keyValue = buildIndexType(ou, model, type) + suffix;\n return executeQuery<StorableEntity>(INDEX_TYPE, keyValue, {\n ascending,\n limit,\n startKey,\n });\n}\n\n/**\n * Query a single entity by external ID\n * Uses indexXid GSI\n */\nexport const queryByXid = serviceHandler({\n alias: \"queryByXid\",\n description: \"Query a single entity by external ID\",\n input: {\n archived: {\n type: Boolean,\n default: false,\n required: false,\n description: \"Query archived entities instead of active ones\",\n },\n deleted: {\n type: Boolean,\n default: false,\n required: false,\n description: \"Query deleted entities instead of active ones\",\n },\n model: { type: String, description: \"Entity model name\" },\n ou: { type: String, description: \"Organizational unit (@ for root)\" },\n xid: { type: String, description: \"External ID\" },\n },\n service: async ({\n archived,\n deleted,\n model,\n ou,\n xid,\n }): Promise<StorableEntity | null> => {\n const archivedBool = archived as boolean | undefined;\n const deletedBool = deleted as boolean | undefined;\n const modelStr = model as string;\n const ouStr = ou as string;\n const xidStr = xid as string;\n\n const suffix = calculateSuffix({ archived: archivedBool, deleted: deletedBool });\n const keyValue = buildIndexXid(ouStr, modelStr, xidStr) + suffix;\n const result = await executeQuery<StorableEntity>(INDEX_XID, keyValue, {\n limit: 1,\n });\n return result.items[0] ?? null;\n },\n});\n","/**\n * Unified Query Function with Auto-Detect Index Selection\n *\n * The query() function automatically selects the best index based on\n * the filter fields provided. This simplifies query construction by\n * removing the need to know which specific GSI to use.\n */\n\nimport { QueryCommand } from \"@aws-sdk/lib-dynamodb\";\nimport { ConfigurationError } from \"@jaypie/errors\";\nimport {\n DEFAULT_INDEXES,\n getModelIndexes,\n type IndexDefinition,\n} from \"@jaypie/vocabulary\";\n\nimport { getDocClient, getTableName } from \"./client.js\";\nimport { ARCHIVED_SUFFIX, DELETED_SUFFIX } from \"./constants.js\";\nimport { buildCompositeKey } from \"./keyBuilders.js\";\nimport type { QueryResult, StorableEntity } from \"./types.js\";\n\n// =============================================================================\n// Types\n// =============================================================================\n\n/**\n * Query parameters for the unified query function\n */\nexport interface QueryParams<T = StorableEntity> {\n /** Whether to query archived entities instead of active ones */\n archived?: boolean;\n /** Whether to sort ascending (oldest first). Default: false */\n ascending?: boolean;\n /** Whether to query deleted entities instead of active ones */\n deleted?: boolean;\n /** Filter object with field values to match. Used for index auto-detection. */\n filter?: Partial<T>;\n /** Maximum number of items to return */\n limit?: number;\n /** Model name (required) */\n model: string;\n /** Organizational unit (APEX or \"{parent.model}#{parent.id}\") */\n ou?: string;\n /** Pagination cursor from previous query */\n startKey?: Record<string, unknown>;\n}\n\n/**\n * Score for an index based on filter field matching\n */\ninterface IndexScore {\n index: IndexDefinition;\n /** Whether all pk fields are present in the filter */\n pkComplete: boolean;\n /** Number of pk fields matched */\n matchedFields: number;\n}\n\n// =============================================================================\n// Helper Functions\n// =============================================================================\n\n/**\n * Calculate the suffix based on archived/deleted flags\n */\nfunction calculateSuffix(archived?: boolean, deleted?: boolean): string {\n if (archived && deleted) {\n return ARCHIVED_SUFFIX + DELETED_SUFFIX;\n }\n if (archived) {\n return ARCHIVED_SUFFIX;\n }\n if (deleted) {\n return DELETED_SUFFIX;\n }\n return \"\";\n}\n\n/**\n * Build a combined filter object from params\n */\nfunction buildFilterObject<T>(params: QueryParams<T>): Record<string, unknown> {\n const result: Record<string, unknown> = {\n model: params.model,\n };\n\n if (params.ou !== undefined) {\n result.ou = params.ou;\n }\n\n if (params.filter) {\n Object.assign(result, params.filter);\n }\n\n return result;\n}\n\n/**\n * Score an index based on how well it matches the filter fields\n */\nfunction scoreIndex(\n index: IndexDefinition,\n filterFields: Record<string, unknown>,\n): IndexScore {\n let matchedFields = 0;\n let pkComplete = true;\n\n for (const field of index.pk) {\n if (filterFields[field] !== undefined) {\n matchedFields++;\n } else {\n pkComplete = false;\n }\n }\n\n return {\n index,\n matchedFields,\n pkComplete,\n };\n}\n\n/**\n * Select the best index for the given filter\n *\n * Scoring criteria:\n * 1. Index must have all pk fields present (pkComplete)\n * 2. Prefer indexes with more matched fields\n * 3. Prefer more specific indexes (more pk fields)\n */\nfunction selectBestIndex(\n indexes: IndexDefinition[],\n filterFields: Record<string, unknown>,\n): IndexDefinition {\n const scores = indexes.map((index) => scoreIndex(index, filterFields));\n\n // Filter to only complete matches\n const completeMatches = scores.filter((s) => s.pkComplete);\n\n if (completeMatches.length === 0) {\n const availableIndexes = indexes\n .map((i) => i.name ?? `[${i.pk.join(\", \")}]`)\n .join(\", \");\n const providedFields = Object.keys(filterFields).join(\", \");\n throw new ConfigurationError(\n `No index matches filter fields. ` +\n `Provided: ${providedFields}. ` +\n `Available indexes: ${availableIndexes}`,\n );\n }\n\n // Sort by:\n // 1. More matched fields first (descending)\n // 2. More pk fields (more specific) first (descending)\n completeMatches.sort((a, b) => {\n const fieldDiff = b.matchedFields - a.matchedFields;\n if (fieldDiff !== 0) return fieldDiff;\n return b.index.pk.length - a.index.pk.length;\n });\n\n return completeMatches[0].index;\n}\n\n// =============================================================================\n// Main Query Function\n// =============================================================================\n\n/**\n * Query entities with automatic index selection\n *\n * The query function automatically selects the best GSI based on\n * the filter fields provided. This removes the need to know which\n * specific query function (queryByOu, queryByAlias, etc.) to use.\n *\n * @example\n * // Uses indexOu (pk: [\"ou\", \"model\"])\n * const allMessages = await query({ model: \"message\", ou: `chat#${chatId}` });\n *\n * @example\n * // Uses indexAlias (pk: [\"ou\", \"model\", \"alias\"])\n * const byAlias = await query({\n * model: \"record\",\n * ou: \"@\",\n * filter: { alias: \"my-record\" },\n * });\n *\n * @example\n * // Uses a custom registered index if model has one\n * const byChat = await query({\n * model: \"message\",\n * filter: { chatId: \"abc-123\" },\n * });\n */\nexport async function query<T extends StorableEntity = StorableEntity>(\n params: QueryParams<T>,\n): Promise<QueryResult<T>> {\n const {\n archived = false,\n ascending = false,\n deleted = false,\n limit,\n model,\n startKey,\n } = params;\n\n // Build the combined filter object\n const filterFields = buildFilterObject(params);\n\n // Get indexes for this model (custom or DEFAULT_INDEXES)\n const indexes = getModelIndexes(model);\n\n // Select the best matching index\n const selectedIndex = selectBestIndex(indexes, filterFields);\n const indexName = selectedIndex.name ?? generateIndexName(selectedIndex.pk);\n\n // Build the partition key value\n const suffix = calculateSuffix(archived, deleted);\n const keyValue = buildCompositeKey(\n filterFields as Record<string, unknown> & { model: string },\n selectedIndex.pk as string[],\n suffix,\n );\n\n // Execute the query\n const docClient = getDocClient();\n const tableName = getTableName();\n\n const command = new QueryCommand({\n ExclusiveStartKey: startKey as Record<string, unknown> | undefined,\n ExpressionAttributeNames: {\n \"#pk\": indexName,\n },\n ExpressionAttributeValues: {\n \":pkValue\": keyValue,\n },\n IndexName: indexName,\n KeyConditionExpression: \"#pk = :pkValue\",\n ...(limit && { Limit: limit }),\n ScanIndexForward: ascending,\n TableName: tableName,\n });\n\n const response = await docClient.send(command);\n\n return {\n items: (response.Items ?? []) as T[],\n lastEvaluatedKey: response.LastEvaluatedKey,\n };\n}\n\n/**\n * Generate an index name from pk fields\n */\nfunction generateIndexName(pk: string[]): string {\n const suffix = pk\n .map((field) => field.charAt(0).toUpperCase() + field.slice(1))\n .join(\"\");\n return `index${suffix}`;\n}\n\n// Re-export for convenience\nexport { DEFAULT_INDEXES, getModelIndexes };\n","import { putEntity } from \"./entities.js\";\nimport { queryByAlias, queryByOu } from \"./queries.js\";\nimport type { StorableEntity } from \"./types.js\";\n\n/**\n * Result of a seed operation\n */\nexport interface SeedResult {\n /** Aliases of entities that were created */\n created: string[];\n /** Error details for failed seed operations */\n errors: Array<{ alias: string; error: string }>;\n /** Aliases of entities that were skipped (already exist) */\n skipped: string[];\n}\n\n/**\n * Options for seed operations\n */\nexport interface SeedOptions {\n /** Preview without writing (default: false) */\n dryRun?: boolean;\n /** Overwrite existing entities (default: false) */\n replace?: boolean;\n}\n\n/**\n * Result of an export operation\n */\nexport interface ExportResult<T extends StorableEntity = StorableEntity> {\n /** Number of entities exported */\n count: number;\n /** Exported entities */\n entities: T[];\n}\n\n/**\n * Seed a single entity if it doesn't already exist\n *\n * @param entity - Partial entity with at least alias, model, and ou\n * @returns true if entity was created, false if it already exists\n */\nexport async function seedEntityIfNotExists<T extends Partial<StorableEntity>>(\n entity: T,\n): Promise<boolean> {\n if (!entity.alias || !entity.model || !entity.ou) {\n throw new Error(\n \"Entity must have alias, model, and ou to check existence\",\n );\n }\n\n // Check if entity already exists\n const existing = await queryByAlias({\n alias: entity.alias,\n model: entity.model,\n ou: entity.ou,\n });\n\n if (existing) {\n return false;\n }\n\n // Generate required fields if missing\n const now = new Date().toISOString();\n const completeEntity: StorableEntity = {\n createdAt: entity.createdAt ?? now,\n id: entity.id ?? crypto.randomUUID(),\n model: entity.model,\n name: entity.name ?? entity.alias,\n ou: entity.ou,\n sequence: entity.sequence ?? Date.now(),\n updatedAt: entity.updatedAt ?? now,\n ...entity,\n } as StorableEntity;\n\n await putEntity({ entity: completeEntity });\n return true;\n}\n\n/**\n * Seed multiple entities (idempotent)\n *\n * - Checks existence by alias (via queryByAlias) before creating\n * - Auto-generates id (UUID), createdAt, updatedAt if missing\n * - Skip existing unless replace: true\n * - Returns counts of created/skipped/errors\n *\n * @param entities - Array of partial entities to seed\n * @param options - Seed options\n * @returns Result with created, skipped, and errors arrays\n */\nexport async function seedEntities<T extends Partial<StorableEntity>>(\n entities: T[],\n options: SeedOptions = {},\n): Promise<SeedResult> {\n const { dryRun = false, replace = false } = options;\n const result: SeedResult = {\n created: [],\n errors: [],\n skipped: [],\n };\n\n for (const entity of entities) {\n const alias = entity.alias ?? entity.name ?? \"unknown\";\n\n try {\n if (!entity.model || !entity.ou) {\n throw new Error(\"Entity must have model and ou\");\n }\n\n // For entities with alias, check existence\n if (entity.alias) {\n const existing = await queryByAlias({\n alias: entity.alias,\n model: entity.model,\n ou: entity.ou,\n });\n\n if (existing && !replace) {\n result.skipped.push(alias);\n continue;\n }\n\n // If replacing, use existing ID to update rather than create new\n if (existing && replace) {\n entity.id = existing.id;\n }\n }\n\n if (dryRun) {\n result.created.push(alias);\n continue;\n }\n\n // Generate required fields if missing\n const now = new Date().toISOString();\n const completeEntity: StorableEntity = {\n createdAt: entity.createdAt ?? now,\n id: entity.id ?? crypto.randomUUID(),\n model: entity.model,\n name: entity.name ?? entity.alias ?? \"Unnamed\",\n ou: entity.ou,\n sequence: entity.sequence ?? Date.now(),\n updatedAt: entity.updatedAt ?? now,\n ...entity,\n } as StorableEntity;\n\n await putEntity({ entity: completeEntity });\n result.created.push(alias);\n } catch (error) {\n const errorMessage =\n error instanceof Error ? error.message : String(error);\n result.errors.push({ alias, error: errorMessage });\n }\n }\n\n return result;\n}\n\n/**\n * Export entities by model and organizational unit\n *\n * - Paginates through all matching entities via queryByOu\n * - Returns entities sorted by sequence (ascending)\n *\n * @param model - The entity model name\n * @param ou - The organizational unit\n * @param limit - Optional maximum number of entities to export\n * @returns Export result with entities and count\n */\nexport async function exportEntities<T extends StorableEntity>(\n model: string,\n ou: string,\n limit?: number,\n): Promise<ExportResult<T>> {\n const entities: T[] = [];\n let startKey: Record<string, unknown> | undefined;\n let remaining = limit;\n\n do {\n const batchLimit =\n remaining !== undefined ? Math.min(remaining, 100) : undefined;\n\n const { items, lastEvaluatedKey } = await queryByOu({\n ascending: true,\n limit: batchLimit,\n model,\n ou,\n startKey,\n });\n\n entities.push(...(items as T[]));\n startKey = lastEvaluatedKey;\n\n if (remaining !== undefined) {\n remaining -= items.length;\n }\n } while (startKey && (remaining === undefined || remaining > 0));\n\n return {\n count: entities.length,\n entities,\n };\n}\n\n/**\n * Export entities as a JSON string\n *\n * @param model - The entity model name\n * @param ou - The organizational unit\n * @param pretty - Format JSON with indentation (default: true)\n * @returns JSON string of exported entities\n */\nexport async function exportEntitiesToJson(\n model: string,\n ou: string,\n pretty: boolean = true,\n): Promise<string> {\n const { entities } = await exportEntities(model, ou);\n return pretty ? JSON.stringify(entities, null, 2) : JSON.stringify(entities);\n}\n"],"names":["DynamoDBClient","DynamoDBDocumentClient","ConfigurationError","SEPARATOR","vocabBuildCompositeKey","APEX","vocabCalculateOu","getModelIndexes","vocabPopulateIndexKeys","ARCHIVED_SUFFIX","DELETED_SUFFIX","serviceHandler","GetCommand","PutCommand","DeleteCommand","calculateSuffix","QueryCommand"],"mappings":"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"}
@@ -1,8 +1,10 @@
1
1
  export { getDocClient, getTableName, initClient, isInitialized, resetClient, } from "./client.js";
2
2
  export { APEX, ARCHIVED_SUFFIX, DELETED_SUFFIX, INDEX_ALIAS, INDEX_CLASS, INDEX_OU, INDEX_TYPE, INDEX_XID, SEPARATOR, } from "./constants.js";
3
3
  export { archiveEntity, deleteEntity, destroyEntity, getEntity, putEntity, updateEntity, } from "./entities.js";
4
- export { buildIndexAlias, buildIndexClass, buildIndexOu, buildIndexType, buildIndexXid, calculateOu, indexEntity, } from "./keyBuilders.js";
4
+ export { buildCompositeKey, buildIndexAlias, buildIndexClass, buildIndexOu, buildIndexType, buildIndexXid, calculateOu, DEFAULT_INDEXES, indexEntity, } from "./keyBuilders.js";
5
5
  export { queryByAlias, queryByClass, queryByOu, queryByType, queryByXid, } from "./queries.js";
6
+ export { query } from "./query.js";
7
+ export type { QueryParams } from "./query.js";
6
8
  export { exportEntities, exportEntitiesToJson, seedEntities, seedEntityIfNotExists, } from "./seedExport.js";
7
- export type { BaseQueryOptions, DynamoClientConfig, FabricEntity, ParentReference, QueryResult, } from "./types.js";
9
+ export type { BaseQueryOptions, DynamoClientConfig, ParentReference, QueryResult, StorableEntity, } from "./types.js";
8
10
  export type { ExportResult, SeedOptions, SeedResult } from "./seedExport.js";
@@ -1,4 +1,5 @@
1
- import type { FabricEntity, ParentReference } from "./types.js";
1
+ import { DEFAULT_INDEXES, type IndexableEntity } from "@jaypie/vocabulary";
2
+ import type { ParentReference, StorableEntity } from "./types.js";
2
3
  /**
3
4
  * Build the indexOu key for hierarchical queries
4
5
  * @param ou - The organizational unit (APEX or "{parent.model}#{parent.id}")
@@ -38,6 +39,15 @@ export declare function buildIndexType(ou: string, model: string, type: string):
38
39
  * @returns Composite key: "{ou}#{model}#{xid}"
39
40
  */
40
41
  export declare function buildIndexXid(ou: string, model: string, xid: string): string;
42
+ /**
43
+ * Build a composite key from entity fields
44
+ *
45
+ * @param entity - Entity with fields to extract
46
+ * @param fields - Field names to combine with SEPARATOR
47
+ * @param suffix - Optional suffix to append (e.g., "#deleted")
48
+ * @returns Composite key string
49
+ */
50
+ export declare function buildCompositeKey(entity: IndexableEntity, fields: string[], suffix?: string): string;
41
51
  /**
42
52
  * Calculate the organizational unit from a parent reference
43
53
  * @param parent - Optional parent entity reference
@@ -46,6 +56,10 @@ export declare function buildIndexXid(ou: string, model: string, xid: string): s
46
56
  export declare function calculateOu(parent?: ParentReference): string;
47
57
  /**
48
58
  * Auto-populate GSI index keys on an entity
59
+ *
60
+ * Uses the model's registered indexes (from vocabulary registry) or
61
+ * DEFAULT_INDEXES if no custom indexes are registered.
62
+ *
49
63
  * - indexOu is always populated from ou + model
50
64
  * - indexAlias is populated only when alias is present
51
65
  * - indexClass is populated only when class is present
@@ -56,4 +70,5 @@ export declare function calculateOu(parent?: ParentReference): string;
56
70
  * @param suffix - Optional suffix to append to all index keys (e.g., "#deleted", "#archived")
57
71
  * @returns The entity with populated index keys
58
72
  */
59
- export declare function indexEntity<T extends FabricEntity>(entity: T, suffix?: string): T;
73
+ export declare function indexEntity<T extends StorableEntity>(entity: T, suffix?: string): T;
74
+ export { DEFAULT_INDEXES };
@@ -77,18 +77,17 @@ function isInitialized() {
77
77
  return docClient !== null && tableName !== null;
78
78
  }
79
79
 
80
- // Primary markers
81
- const SEPARATOR = "#"; // Composite key separator
82
- // GSI names
80
+ // Re-export shared constants from vocabulary
81
+ // GSI names (derived from DEFAULT_INDEXES)
83
82
  const INDEX_ALIAS = "indexAlias";
84
83
  const INDEX_CLASS = "indexClass";
85
84
  const INDEX_OU = "indexOu";
86
85
  const INDEX_TYPE = "indexType";
87
86
  const INDEX_XID = "indexXid";
88
- // Index suffixes for soft state
89
- const ARCHIVED_SUFFIX = "#archived";
90
- const DELETED_SUFFIX = "#deleted";
91
87
 
88
+ // =============================================================================
89
+ // Key Builders
90
+ // =============================================================================
92
91
  /**
93
92
  * Build the indexOu key for hierarchical queries
94
93
  * @param ou - The organizational unit (APEX or "{parent.model}#{parent.id}")
@@ -96,7 +95,7 @@ const DELETED_SUFFIX = "#deleted";
96
95
  * @returns Composite key: "{ou}#{model}"
97
96
  */
98
97
  function buildIndexOu(ou, model) {
99
- return `${ou}${SEPARATOR}${model}`;
98
+ return `${ou}${vocabulary.SEPARATOR}${model}`;
100
99
  }
101
100
  /**
102
101
  * Build the indexAlias key for human-friendly lookups
@@ -106,7 +105,7 @@ function buildIndexOu(ou, model) {
106
105
  * @returns Composite key: "{ou}#{model}#{alias}"
107
106
  */
108
107
  function buildIndexAlias(ou, model, alias) {
109
- return `${ou}${SEPARATOR}${model}${SEPARATOR}${alias}`;
108
+ return `${ou}${vocabulary.SEPARATOR}${model}${vocabulary.SEPARATOR}${alias}`;
110
109
  }
111
110
  /**
112
111
  * Build the indexClass key for category filtering
@@ -116,7 +115,7 @@ function buildIndexAlias(ou, model, alias) {
116
115
  * @returns Composite key: "{ou}#{model}#{class}"
117
116
  */
118
117
  function buildIndexClass(ou, model, recordClass) {
119
- return `${ou}${SEPARATOR}${model}${SEPARATOR}${recordClass}`;
118
+ return `${ou}${vocabulary.SEPARATOR}${model}${vocabulary.SEPARATOR}${recordClass}`;
120
119
  }
121
120
  /**
122
121
  * Build the indexType key for type filtering
@@ -126,7 +125,7 @@ function buildIndexClass(ou, model, recordClass) {
126
125
  * @returns Composite key: "{ou}#{model}#{type}"
127
126
  */
128
127
  function buildIndexType(ou, model, type) {
129
- return `${ou}${SEPARATOR}${model}${SEPARATOR}${type}`;
128
+ return `${ou}${vocabulary.SEPARATOR}${model}${vocabulary.SEPARATOR}${type}`;
130
129
  }
131
130
  /**
132
131
  * Build the indexXid key for external ID lookups
@@ -136,10 +135,14 @@ function buildIndexType(ou, model, type) {
136
135
  * @returns Composite key: "{ou}#{model}#{xid}"
137
136
  */
138
137
  function buildIndexXid(ou, model, xid) {
139
- return `${ou}${SEPARATOR}${model}${SEPARATOR}${xid}`;
138
+ return `${ou}${vocabulary.SEPARATOR}${model}${vocabulary.SEPARATOR}${xid}`;
140
139
  }
141
140
  /**
142
141
  * Auto-populate GSI index keys on an entity
142
+ *
143
+ * Uses the model's registered indexes (from vocabulary registry) or
144
+ * DEFAULT_INDEXES if no custom indexes are registered.
145
+ *
143
146
  * - indexOu is always populated from ou + model
144
147
  * - indexAlias is populated only when alias is present
145
148
  * - indexClass is populated only when class is present
@@ -151,27 +154,9 @@ function buildIndexXid(ou, model, xid) {
151
154
  * @returns The entity with populated index keys
152
155
  */
153
156
  function indexEntity(entity, suffix = "") {
154
- const result = { ...entity };
155
- // indexOu is always set (from ou + model)
156
- result.indexOu = buildIndexOu(entity.ou, entity.model) + suffix;
157
- // Optional indexes - only set when the source field is present
158
- if (entity.alias !== undefined) {
159
- result.indexAlias =
160
- buildIndexAlias(entity.ou, entity.model, entity.alias) + suffix;
161
- }
162
- if (entity.class !== undefined) {
163
- result.indexClass =
164
- buildIndexClass(entity.ou, entity.model, entity.class) + suffix;
165
- }
166
- if (entity.type !== undefined) {
167
- result.indexType =
168
- buildIndexType(entity.ou, entity.model, entity.type) + suffix;
169
- }
170
- if (entity.xid !== undefined) {
171
- result.indexXid =
172
- buildIndexXid(entity.ou, entity.model, entity.xid) + suffix;
173
- }
174
- return result;
157
+ const indexes = vocabulary.getModelIndexes(entity.model);
158
+ // Cast through unknown to bridge the type gap between StorableEntity and IndexableEntity
159
+ return vocabulary.populateIndexKeys(entity, indexes, suffix);
175
160
  }
176
161
 
177
162
  /**
@@ -181,13 +166,13 @@ function calculateEntitySuffix(entity) {
181
166
  const hasArchived = Boolean(entity.archivedAt);
182
167
  const hasDeleted = Boolean(entity.deletedAt);
183
168
  if (hasArchived && hasDeleted) {
184
- return ARCHIVED_SUFFIX + DELETED_SUFFIX;
169
+ return vocabulary.ARCHIVED_SUFFIX + vocabulary.DELETED_SUFFIX;
185
170
  }
186
171
  if (hasArchived) {
187
- return ARCHIVED_SUFFIX;
172
+ return vocabulary.ARCHIVED_SUFFIX;
188
173
  }
189
174
  if (hasDeleted) {
190
- return DELETED_SUFFIX;
175
+ return vocabulary.DELETED_SUFFIX;
191
176
  }
192
177
  return "";
193
178
  }
@@ -217,7 +202,7 @@ const getEntity = vocabulary.serviceHandler({
217
202
  * Auto-populates GSI index keys via indexEntity
218
203
  *
219
204
  * Note: This is a regular async function (not serviceHandler) because it accepts
220
- * complex FabricEntity objects that can't be coerced by vocabulary's type system.
205
+ * complex StorableEntity objects that can't be coerced by vocabulary's type system.
221
206
  */
222
207
  async function putEntity({ entity, }) {
223
208
  const docClient = getDocClient();
@@ -236,7 +221,7 @@ async function putEntity({ entity, }) {
236
221
  * Auto-populates GSI index keys and sets updatedAt
237
222
  *
238
223
  * Note: This is a regular async function (not serviceHandler) because it accepts
239
- * complex FabricEntity objects that can't be coerced by vocabulary's type system.
224
+ * complex StorableEntity objects that can't be coerced by vocabulary's type system.
240
225
  */
241
226
  async function updateEntity({ entity, }) {
242
227
  const docClient = getDocClient();
@@ -356,13 +341,13 @@ const destroyEntity = vocabulary.serviceHandler({
356
341
  */
357
342
  function calculateSuffix({ archived, deleted, }) {
358
343
  if (archived && deleted) {
359
- return ARCHIVED_SUFFIX + DELETED_SUFFIX;
344
+ return vocabulary.ARCHIVED_SUFFIX + vocabulary.DELETED_SUFFIX;
360
345
  }
361
346
  if (archived) {
362
- return ARCHIVED_SUFFIX;
347
+ return vocabulary.ARCHIVED_SUFFIX;
363
348
  }
364
349
  if (deleted) {
365
- return DELETED_SUFFIX;
350
+ return vocabulary.DELETED_SUFFIX;
366
351
  }
367
352
  return "";
368
353
  }
@@ -521,65 +506,106 @@ const queryByXid = vocabulary.serviceHandler({
521
506
  const DEFAULT_ENDPOINT = "http://127.0.0.1:8000";
522
507
  const DEFAULT_REGION$1 = "us-east-1";
523
508
  const DEFAULT_TABLE_NAME$1 = "jaypie-local";
509
+ // =============================================================================
510
+ // Index to GSI Conversion
511
+ // =============================================================================
512
+ /**
513
+ * Generate an index name from pk fields (if not provided)
514
+ */
515
+ function generateIndexName(pk) {
516
+ const suffix = pk
517
+ .map((field) => field.charAt(0).toUpperCase() + field.slice(1))
518
+ .join("");
519
+ return `index${suffix}`;
520
+ }
521
+ /**
522
+ * Collect all unique indexes from DEFAULT_INDEXES and registered models
523
+ */
524
+ function collectAllIndexes() {
525
+ const indexMap = new Map();
526
+ // Add DEFAULT_INDEXES first
527
+ for (const index of vocabulary.DEFAULT_INDEXES) {
528
+ const name = index.name ?? generateIndexName(index.pk);
529
+ indexMap.set(name, { ...index, name });
530
+ }
531
+ // Add registered model indexes (will not overwrite if name already exists)
532
+ for (const index of vocabulary.getAllRegisteredIndexes()) {
533
+ const name = index.name ?? generateIndexName(index.pk);
534
+ if (!indexMap.has(name)) {
535
+ indexMap.set(name, { ...index, name });
536
+ }
537
+ }
538
+ return Array.from(indexMap.values());
539
+ }
540
+ /**
541
+ * Build attribute definitions from indexes
542
+ */
543
+ function buildAttributeDefinitions(indexes) {
544
+ const attrs = new Map();
545
+ // Primary key attributes
546
+ attrs.set("model", "S");
547
+ attrs.set("id", "S");
548
+ attrs.set("sequence", "N");
549
+ // GSI attributes (partition keys are always strings)
550
+ for (const index of indexes) {
551
+ const indexName = index.name ?? generateIndexName(index.pk);
552
+ attrs.set(indexName, "S");
553
+ }
554
+ // Sort keys (sequence is always a number, others would be strings)
555
+ // Note: Currently all indexes use sequence as SK, so this is mostly future-proofing
556
+ for (const index of indexes) {
557
+ const sk = index.sk ?? ["sequence"];
558
+ for (const skField of sk) {
559
+ if (!attrs.has(skField)) {
560
+ // Assume string unless it's sequence
561
+ attrs.set(skField, skField === "sequence" ? "N" : "S");
562
+ }
563
+ }
564
+ }
565
+ return Array.from(attrs.entries())
566
+ .sort(([a], [b]) => a.localeCompare(b))
567
+ .map(([name, type]) => ({
568
+ AttributeName: name,
569
+ AttributeType: type,
570
+ }));
571
+ }
572
+ /**
573
+ * Build GSI definitions from indexes
574
+ */
575
+ function buildGSIs(indexes) {
576
+ const gsiProjection = { ProjectionType: "ALL" };
577
+ return indexes.map((index) => {
578
+ const indexName = index.name ?? generateIndexName(index.pk);
579
+ const sk = index.sk ?? ["sequence"];
580
+ // For GSIs, the partition key attribute name IS the index name
581
+ // (e.g., indexOu stores the composite key value "@#record")
582
+ return {
583
+ IndexName: indexName,
584
+ KeySchema: [
585
+ { AttributeName: indexName, KeyType: "HASH" },
586
+ // Use first SK field as the range key attribute
587
+ { AttributeName: sk[0], KeyType: "RANGE" },
588
+ ],
589
+ Projection: gsiProjection,
590
+ };
591
+ });
592
+ }
593
+ // =============================================================================
594
+ // Table Creation
595
+ // =============================================================================
524
596
  /**
525
597
  * DynamoDB table schema with Jaypie GSI pattern
598
+ *
599
+ * Collects indexes from:
600
+ * 1. DEFAULT_INDEXES (5 standard GSIs)
601
+ * 2. Any custom indexes registered via registerModel()
526
602
  */
527
603
  function createTableParams(tableName, billingMode) {
528
- const gsiProjection = { ProjectionType: "ALL" };
604
+ const allIndexes = collectAllIndexes();
529
605
  return {
530
- AttributeDefinitions: [
531
- { AttributeName: "id", AttributeType: "S" },
532
- { AttributeName: "indexAlias", AttributeType: "S" },
533
- { AttributeName: "indexClass", AttributeType: "S" },
534
- { AttributeName: "indexOu", AttributeType: "S" },
535
- { AttributeName: "indexType", AttributeType: "S" },
536
- { AttributeName: "indexXid", AttributeType: "S" },
537
- { AttributeName: "model", AttributeType: "S" },
538
- { AttributeName: "sequence", AttributeType: "N" },
539
- ],
606
+ AttributeDefinitions: buildAttributeDefinitions(allIndexes),
540
607
  BillingMode: billingMode,
541
- GlobalSecondaryIndexes: [
542
- {
543
- IndexName: "indexOu",
544
- KeySchema: [
545
- { AttributeName: "indexOu", KeyType: "HASH" },
546
- { AttributeName: "sequence", KeyType: "RANGE" },
547
- ],
548
- Projection: gsiProjection,
549
- },
550
- {
551
- IndexName: "indexAlias",
552
- KeySchema: [
553
- { AttributeName: "indexAlias", KeyType: "HASH" },
554
- { AttributeName: "sequence", KeyType: "RANGE" },
555
- ],
556
- Projection: gsiProjection,
557
- },
558
- {
559
- IndexName: "indexClass",
560
- KeySchema: [
561
- { AttributeName: "indexClass", KeyType: "HASH" },
562
- { AttributeName: "sequence", KeyType: "RANGE" },
563
- ],
564
- Projection: gsiProjection,
565
- },
566
- {
567
- IndexName: "indexType",
568
- KeySchema: [
569
- { AttributeName: "indexType", KeyType: "HASH" },
570
- { AttributeName: "sequence", KeyType: "RANGE" },
571
- ],
572
- Projection: gsiProjection,
573
- },
574
- {
575
- IndexName: "indexXid",
576
- KeySchema: [
577
- { AttributeName: "indexXid", KeyType: "HASH" },
578
- { AttributeName: "sequence", KeyType: "RANGE" },
579
- ],
580
- Projection: gsiProjection,
581
- },
582
- ],
608
+ GlobalSecondaryIndexes: buildGSIs(allIndexes),
583
609
  KeySchema: [
584
610
  { AttributeName: "model", KeyType: "HASH" },
585
611
  { AttributeName: "id", KeyType: "RANGE" },