diff --git a/package.json b/package.json index 051625c8..dc2a333f 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,15 @@ "url": "https://github.com/Fairmint/ocp-canton-sdk.git" }, "license": "GPL-3.0-only", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js", + "require": "./dist/index.js", + "default": "./dist/index.js" + }, + "./package.json": "./package.json" + }, "main": "dist/index.js", "types": "dist/index.d.ts", "files": [ diff --git a/scripts/check-declarations.ts b/scripts/check-declarations.ts index 0788895f..07cac7d1 100644 --- a/scripts/check-declarations.ts +++ b/scripts/check-declarations.ts @@ -5,7 +5,11 @@ import ts from 'typescript'; const projectRoot = process.cwd(); const configPath = path.join(projectRoot, 'tsconfig.json'); const declarationEntryPoint = path.join(projectRoot, 'dist', 'index.d.ts'); +const strictConsumerEntryPoint = path.join(projectRoot, 'test', 'declarations', 'publicApi.types.ts'); const declarationRoot = `${path.dirname(declarationEntryPoint)}${path.sep}`; +const generatedDamlPackage = '@fairmint/open-captable-protocol-daml-js'; +const cantonTransactionTreeOperationsModule = '@fairmint/canton-node-sdk/build/src/clients/ledger-json-api/operations'; +const commonTypesDeclaration = path.join(declarationRoot, 'types', 'common.d.ts'); const diagnosticHost: ts.FormatDiagnosticsHost = { getCanonicalFileName: (fileName) => fileName, getCurrentDirectory: () => projectRoot, @@ -30,16 +34,41 @@ if (parsedConfig.errors.length > 0) { } const program = ts.createProgram({ - rootNames: [declarationEntryPoint], - options: parsedConfig.options, + rootNames: [declarationEntryPoint, strictConsumerEntryPoint], + options: { ...parsedConfig.options, rootDir: projectRoot }, }); -const sdkDiagnostics = ts - .getPreEmitDiagnostics(program) - .filter((diagnostic) => diagnostic.file?.fileName.startsWith(declarationRoot)); +const diagnostics = ts.getPreEmitDiagnostics(program); -if (sdkDiagnostics.length > 0) { +if (diagnostics.length > 0) { throw new Error( - `SDK declaration validation failed:\n${ts.formatDiagnosticsWithColorAndContext(sdkDiagnostics, diagnosticHost)}` + `Strict consumer declaration validation failed:\n${ts.formatDiagnosticsWithColorAndContext(diagnostics, diagnosticHost)}` + ); +} + +const generatedDamlLeaks = program + .getSourceFiles() + .filter((sourceFile) => sourceFile.fileName.startsWith(declarationRoot)) + .filter((sourceFile) => sourceFile.text.includes(generatedDamlPackage)) + .map((sourceFile) => path.relative(projectRoot, sourceFile.fileName)); + +if (generatedDamlLeaks.length > 0) { + throw new Error( + `Public declaration graph references ${generatedDamlPackage}:\n${generatedDamlLeaks.map((file) => `- ${file}`).join('\n')}` + ); +} + +const duplicatedTransactionTreeResponseImports = program + .getSourceFiles() + .filter((sourceFile) => sourceFile.fileName.startsWith(declarationRoot)) + .filter((sourceFile) => sourceFile.fileName !== commonTypesDeclaration) + .filter((sourceFile) => sourceFile.text.includes(cantonTransactionTreeOperationsModule)) + .map((sourceFile) => path.relative(projectRoot, sourceFile.fileName)); + +if (duplicatedTransactionTreeResponseImports.length > 0) { + throw new Error( + `Public declarations must import transaction-tree response types through src/types/common:\n${duplicatedTransactionTreeResponseImports + .map((file) => `- ${file}`) + .join('\n')}` ); } diff --git a/src/OcpClient.ts b/src/OcpClient.ts index 6117e055..c3e91060 100644 --- a/src/OcpClient.ts +++ b/src/OcpClient.ts @@ -68,35 +68,38 @@ import { type OcpEnvironment, } from './environment'; import { OcpErrorCodes, OcpValidationError } from './errors'; -import { - authorizeIssuer, - buildCreateIssuerCommand, - getEntityAsOcf, - withdrawAuthorization, - type AuthorizeIssuerParams, - type AuthorizeIssuerResult, - type CreateIssuerParams, - type WithdrawAuthorizationParams, - type WithdrawAuthorizationResult, -} from './functions'; import { archiveCapTable, - CapTableBatch, - classifyIssuerCapTables, - getCapTableState, - mapOcfObjectTypeToEntityType, type ArchiveCapTableParams, type ArchiveCapTableResult, - type CapTableState, - type IssuerCapTableClassification, +} from './functions/OpenCapTable/capTable/archiveCapTable'; +import { CapTableBatch, type CapTableBatchParams } from './functions/OpenCapTable/capTable/CapTableBatch'; +import { getEntityAsOcf } from './functions/OpenCapTable/capTable/damlToOcf'; +import { + mapOcfObjectTypeToEntityType, type OcfDataTypeFor, type OcfEntityType, type OcfReadableDataForObjectType, type OcfReadableObjectType, -} from './functions/OpenCapTable/capTable'; +} from './functions/OpenCapTable/capTable/entityTypes'; +import { + classifyIssuerCapTables, + getCapTableState, + type CapTableState, + type IssuerCapTableClassification, +} from './functions/OpenCapTable/capTable/getCapTableState'; +import { buildCreateIssuerCommand } from './functions/OpenCapTable/issuer/api'; +import type { CreateIssuerParams } from './functions/OpenCapTable/issuer/types'; +import { authorizeIssuer } from './functions/OpenCapTable/issuerAuthorization/authorizeIssuer'; +import type { + AuthorizeIssuerParams, + AuthorizeIssuerResult, + WithdrawAuthorizationParams, + WithdrawAuthorizationResult, +} from './functions/OpenCapTable/issuerAuthorization/types'; +import { withdrawAuthorization } from './functions/OpenCapTable/issuerAuthorization/withdrawAuthorization'; import { mergeCommandContext, type CommandObservabilityOptions, type OcpObservabilityOptions } from './observability'; -import type { CommandWithDisclosedContracts } from './types'; -import type { ContractResult, GetByContractIdParams } from './types/common'; +import type { CommandWithDisclosedContracts, ContractResult, GetByContractIdParams } from './types/common'; import type { OcfConvertibleAcceptanceOutput, OcfConvertibleCancellationOutput, @@ -827,21 +830,7 @@ export interface GetByObjectTypeParams< } /** Parameters for creating a batch cap table update */ -interface CapTableUpdateParams extends CommandObservabilityOptions { - /** The contract ID of the CapTable to update */ - capTableContractId: string; - /** Optional contract details for the CapTable (used to get correct templateId from ledger) */ - capTableContractDetails?: { templateId: string }; - /** - * Optional deterministic command ID for idempotent retry handling. - * Takes precedence over `defaultContext.commandId` and `context.commandId`. - */ - commandId?: string; - /** Party IDs to act as (signatories) */ - actAs: string[]; - /** Optional additional party IDs for read access */ - readAs?: string[]; -} +type CapTableUpdateParams = CapTableBatchParams; /** Entity namespace with a single `get()` method */ interface EntityReader { diff --git a/src/functions/OpenCapTable/capTable/CapTableBatch.ts b/src/functions/OpenCapTable/capTable/CapTableBatch.ts index fb870967..9d9f43ab 100644 --- a/src/functions/OpenCapTable/capTable/CapTableBatch.ts +++ b/src/functions/OpenCapTable/capTable/CapTableBatch.ts @@ -7,38 +7,35 @@ import type { LedgerJsonApiClient } from '@fairmint/canton-node-sdk/build/src/clients/ledger-json-api'; import type { Command } from '@fairmint/canton-node-sdk/build/src/clients/ledger-json-api/schemas/api/commands'; -import { Fairmint, OCP_TEMPLATES } from '@fairmint/open-captable-protocol-daml-js'; +import { OCP_TEMPLATES } from '@fairmint/open-captable-protocol-daml-js'; import { OcpContractError, OcpErrorCodes, OcpValidationError } from '../../../errors'; import { mergeCommandContext, submitObservedTransactionTree, type CommandObservabilityOptions, } from '../../../observability'; -import type { CommandWithDisclosedContracts } from '../../../types'; +import type { CommandWithDisclosedContracts } from '../../../types/common'; +import { type OcfCreateData, type OcfDeleteData, type OcfEditData, type UpdateCapTableResult } from './batchTypes'; import { - ENTITY_TAG_MAP, - isOcfCreatableEntityType, isOcfDeletableEntityType, - isOcfEditableEntityType, type CapTableBatchExecuteResult, type CapTableBatchOperations, type OcfCreateArguments, - type OcfCreateData, - type OcfCreateDataFor, type OcfCreateOperation, type OcfDataTypeFor, type OcfDeletableEntityType, - type OcfDeleteData, - type OcfDeleteDataFor, type OcfDeleteOperation, type OcfEditArguments, - type OcfEditData, - type OcfEditDataFor, type OcfEditOperation, type OcfEntityType, - type UpdateCapTableResult, -} from './batchTypes'; -import { convertOperationToDaml, convertToDaml } from './ocfToDaml'; +} from './entityTypes'; +import { + buildOcfCreateData, + buildOcfCreateDataFromOperation, + buildOcfDeleteData, + buildOcfEditData, + buildOcfEditDataFromOperation, +} from './generatedBatchOperations'; /** Parameters for initializing a batch update. */ export interface CapTableBatchParams extends CommandObservabilityOptions { @@ -71,108 +68,6 @@ export interface BatchItemMeta { securityId?: string; } -function decodeGeneratedOperation( - decoder: { runWithException: (input: unknown) => T }, - input: unknown, - operation: 'create' | 'edit' | 'delete', - entityType: OcfEntityType -): T { - try { - return decoder.runWithException(input); - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - throw new OcpValidationError( - `batch.${operation}.${entityType}`, - `Converter output does not match the generated DAML ${operation} variant: ${message}`, - { - code: OcpErrorCodes.INVALID_FORMAT, - context: { operation, entityType }, - } - ); - } -} - -function buildOcfCreateDataWith(type: OcfEntityType, convert: () => unknown): OcfCreateData { - if (!isOcfCreatableEntityType(type)) { - throw new OcpValidationError('type', `Create operation not supported for entity type: ${String(type)}`, { - code: OcpErrorCodes.INVALID_TYPE, - }); - } - - const tag = ENTITY_TAG_MAP[type].create; - return decodeGeneratedOperation( - Fairmint.OpenCapTable.CapTable.OcfCreateData.decoder, - { tag, value: convert() }, - 'create', - type - ); -} - -/** Build and validate one generated DAML create variant from a correlated entity-kind/data pair. */ -export function buildOcfCreateData( - ...args: Arguments -): OcfCreateDataFor; -export function buildOcfCreateData(...args: OcfCreateArguments): OcfCreateData { - const [type] = args; - return buildOcfCreateDataWith(type, () => convertToDaml(...args)); -} - -function buildOcfCreateDataFromOperation(operation: OcfCreateOperation): OcfCreateData { - const { type } = operation; - return buildOcfCreateDataWith(type, () => convertOperationToDaml(operation)); -} - -function buildOcfEditDataWith(type: OcfEntityType, convert: () => unknown): OcfEditData { - if (!isOcfEditableEntityType(type)) { - throw new OcpValidationError('type', `Edit operation not supported for entity type: ${String(type)}`, { - code: OcpErrorCodes.INVALID_TYPE, - }); - } - - const tag = ENTITY_TAG_MAP[type].edit; - return decodeGeneratedOperation( - Fairmint.OpenCapTable.CapTable.OcfEditData.decoder, - { tag, value: convert() }, - 'edit', - type - ); -} - -/** Build and validate one generated DAML edit variant from a correlated entity-kind/data pair. */ -export function buildOcfEditData( - ...args: Arguments -): OcfEditDataFor; -export function buildOcfEditData(...args: OcfEditArguments): OcfEditData { - const [type] = args; - return buildOcfEditDataWith(type, () => convertToDaml(...args)); -} - -function buildOcfEditDataFromOperation(operation: OcfEditOperation): OcfEditData { - const { type } = operation; - return buildOcfEditDataWith(type, () => convertOperationToDaml(operation)); -} - -/** Build and validate one generated DAML delete variant. */ -export function buildOcfDeleteData( - type: EntityType, - id: string -): OcfDeleteDataFor; -export function buildOcfDeleteData(type: OcfDeletableEntityType, id: string): OcfDeleteData { - if (!isOcfDeletableEntityType(type)) { - throw new OcpValidationError('type', `Delete operation not supported for entity type: ${String(type)}`, { - code: OcpErrorCodes.INVALID_TYPE, - }); - } - - const tag = ENTITY_TAG_MAP[type].delete; - return decodeGeneratedOperation( - Fairmint.OpenCapTable.CapTable.OcfDeleteData.decoder, - { tag, value: id }, - 'delete', - type - ); -} - /** * Fluent batch builder for cap table updates. * diff --git a/src/functions/OpenCapTable/capTable/archiveCapTable.ts b/src/functions/OpenCapTable/capTable/archiveCapTable.ts index 07d031cd..5996b618 100644 --- a/src/functions/OpenCapTable/capTable/archiveCapTable.ts +++ b/src/functions/OpenCapTable/capTable/archiveCapTable.ts @@ -11,7 +11,7 @@ import type { LedgerJsonApiClient } from '@fairmint/canton-node-sdk/build/src/clients/ledger-json-api'; import { OcpErrorCodes, OcpValidationError } from '../../../errors'; import { submitObservedTransactionTree, type CommandObservabilityOptions } from '../../../observability'; -import type { CommandWithDisclosedContracts } from '../../../types'; +import type { CommandWithDisclosedContracts } from '../../../types/common'; import { buildCapTableCommand } from './buildCapTableCommand'; /** Parameters for archiving a CapTable contract. */ diff --git a/src/functions/OpenCapTable/capTable/archiveFullCapTable.ts b/src/functions/OpenCapTable/capTable/archiveFullCapTable.ts index 14d80d7b..51a21c66 100644 --- a/src/functions/OpenCapTable/capTable/archiveFullCapTable.ts +++ b/src/functions/OpenCapTable/capTable/archiveFullCapTable.ts @@ -17,8 +17,8 @@ import type { LedgerJsonApiClient } from '@fairmint/canton-node-sdk/build/src/cl import { OcpErrorCodes } from '../../../errors/codes'; import { OcpContractError } from '../../../errors/OcpContractError'; import { archiveCapTable } from './archiveCapTable'; -import type { OcfEntityType } from './batchTypes'; import { CapTableBatch } from './CapTableBatch'; +import type { OcfEntityType } from './entityTypes'; import { classifyIssuerCapTables, type CapTableWithArchiveContext } from './getCapTableState'; /** Minimal contract state needed for the archive operation. */ diff --git a/src/functions/OpenCapTable/capTable/batchTypes.ts b/src/functions/OpenCapTable/capTable/batchTypes.ts index e24fd022..81554aec 100644 --- a/src/functions/OpenCapTable/capTable/batchTypes.ts +++ b/src/functions/OpenCapTable/capTable/batchTypes.ts @@ -6,58 +6,41 @@ */ import { Fairmint } from '@fairmint/open-captable-protocol-daml-js'; +import type { OcfObjectType } from '../../../types/native'; import type { - OcfConvertibleAcceptance, - OcfConvertibleCancellation, - OcfConvertibleConversion, - OcfConvertibleIssuance, - OcfConvertibleRetraction, - OcfConvertibleTransfer, - OcfDocument, - OcfEquityCompensationAcceptance, - OcfEquityCompensationCancellation, - OcfEquityCompensationExercise, - OcfEquityCompensationIssuance, - OcfEquityCompensationRelease, - OcfEquityCompensationRepricing, - OcfEquityCompensationRetraction, - OcfEquityCompensationTransfer, - OcfFinancing, - OcfIssuer, - OcfIssuerAuthorizedSharesAdjustment, - OcfObjectType, - OcfStakeholder, - OcfStakeholderRelationshipChangeEvent, - OcfStakeholderStatusChangeEvent, - OcfStockAcceptance, - OcfStockCancellation, - OcfStockClass, - OcfStockClassAuthorizedSharesAdjustment, - OcfStockClassConversionRatioAdjustment, - OcfStockClassSplit, - OcfStockConsolidation, - OcfStockConversion, - OcfStockIssuance, - OcfStockLegendTemplate, - OcfStockPlan, - OcfStockPlanPoolAdjustment, - OcfStockPlanReturnToPool, - OcfStockReissuance, - OcfStockRepurchase, - OcfStockRetraction, - OcfStockTransfer, - OcfValuation, - OcfVestingAcceleration, - OcfVestingEvent, - OcfVestingStart, - OcfVestingTerms, - OcfWarrantAcceptance, - OcfWarrantCancellation, - OcfWarrantExercise, - OcfWarrantIssuance, - OcfWarrantRetraction, - OcfWarrantTransfer, -} from '../../../types'; + OcfCreatableEntityType, + OcfDataTypeFor, + OcfDeletableEntityType, + OcfEditableEntityType, + OcfEntityDataMap, + OcfEntityType, +} from './entityTypes'; + +export { + isOcfCreatableEntityType, + isOcfDeletableEntityType, + isOcfEditableEntityType, + isOcfEntityType, + isOcfReadableObjectType, + mapOcfObjectTypeToEntityType, + OCF_OBJECT_TYPE_TO_ENTITY_TYPE, + type CapTableBatchExecuteResult, + type CapTableBatchOperations, + type OcfCreatableEntityType, + type OcfCreateArguments, + type OcfCreateOperation, + type OcfDataTypeFor, + type OcfDeletableEntityType, + type OcfDeleteOperation, + type OcfEditableEntityType, + type OcfEditArguments, + type OcfEditOperation, + type OcfEntityDataMap, + type OcfEntityType, + type OcfEntityTypeForObjectType, + type OcfReadableDataForObjectType, + type OcfReadableObjectType, +} from './entityTypes'; // Re-export DAML types for convenience export type OcfCreateData = Fairmint.OpenCapTable.CapTable.OcfCreateData; @@ -65,87 +48,6 @@ export type OcfEditData = Fairmint.OpenCapTable.CapTable.OcfEditData; export type OcfDeleteData = Fairmint.OpenCapTable.CapTable.OcfDeleteData; export type UpdateCapTableResult = Fairmint.OpenCapTable.CapTable.UpdateCapTableResult; -/** - * Result of executing a CapTableBatch. - * - * Extends the DAML choice result with transaction metadata from the ledger. - * This includes all properties from UpdateCapTableResult plus the updateId. - */ -export type CapTableBatchExecuteResult = UpdateCapTableResult & { - /** The update ID (transaction ID) from the Canton ledger */ - updateId: string; -}; - -/** - * Type mapping from entity type string to native OCF data type. - */ -// A closed alias prevents module augmentation from widening OcfEntityType beyond ENTITY_REGISTRY. -// eslint-disable-next-line @typescript-eslint/consistent-type-definitions -export type OcfEntityDataMap = { - convertibleAcceptance: OcfConvertibleAcceptance; - convertibleCancellation: OcfConvertibleCancellation; - convertibleConversion: OcfConvertibleConversion; - convertibleIssuance: OcfConvertibleIssuance; - convertibleRetraction: OcfConvertibleRetraction; - convertibleTransfer: OcfConvertibleTransfer; - document: OcfDocument; - equityCompensationAcceptance: OcfEquityCompensationAcceptance; - equityCompensationCancellation: OcfEquityCompensationCancellation; - equityCompensationExercise: OcfEquityCompensationExercise; - equityCompensationIssuance: OcfEquityCompensationIssuance; - equityCompensationRelease: OcfEquityCompensationRelease; - equityCompensationRepricing: OcfEquityCompensationRepricing; - equityCompensationRetraction: OcfEquityCompensationRetraction; - equityCompensationTransfer: OcfEquityCompensationTransfer; - financing: OcfFinancing; - /** Issuer is edit-only (no create/delete) - created with CapTable via IssuerAuthorization */ - issuer: OcfIssuer; - issuerAuthorizedSharesAdjustment: OcfIssuerAuthorizedSharesAdjustment; - stakeholder: OcfStakeholder; - stakeholderRelationshipChangeEvent: OcfStakeholderRelationshipChangeEvent; - stakeholderStatusChangeEvent: OcfStakeholderStatusChangeEvent; - stockAcceptance: OcfStockAcceptance; - stockCancellation: OcfStockCancellation; - stockClass: OcfStockClass; - stockClassAuthorizedSharesAdjustment: OcfStockClassAuthorizedSharesAdjustment; - stockClassConversionRatioAdjustment: OcfStockClassConversionRatioAdjustment; - stockClassSplit: OcfStockClassSplit; - stockConsolidation: OcfStockConsolidation; - stockConversion: OcfStockConversion; - stockIssuance: OcfStockIssuance; - stockLegendTemplate: OcfStockLegendTemplate; - stockPlan: OcfStockPlan; - stockPlanPoolAdjustment: OcfStockPlanPoolAdjustment; - stockPlanReturnToPool: OcfStockPlanReturnToPool; - stockReissuance: OcfStockReissuance; - stockRepurchase: OcfStockRepurchase; - stockRetraction: OcfStockRetraction; - stockTransfer: OcfStockTransfer; - valuation: OcfValuation; - vestingAcceleration: OcfVestingAcceleration; - vestingEvent: OcfVestingEvent; - vestingStart: OcfVestingStart; - vestingTerms: OcfVestingTerms; - warrantAcceptance: OcfWarrantAcceptance; - warrantCancellation: OcfWarrantCancellation; - warrantExercise: OcfWarrantExercise; - warrantIssuance: OcfWarrantIssuance; - warrantRetraction: OcfWarrantRetraction; - warrantTransfer: OcfWarrantTransfer; -}; - -/** - * All supported OCF entity types for batch operations. - * - * Derived from {@link OcfEntityDataMap} so an entity kind cannot be added to the - * data model without also becoming part of the SDK's canonical entity-kind union. - * Operation-specific subsets are derived from {@link ENTITY_REGISTRY} below. - */ -export type OcfEntityType = keyof OcfEntityDataMap; - -/** Helper type to get the native OCF data type for a given entity type. */ -export type OcfDataTypeFor = OcfEntityDataMap[T]; - type OcfCreateTag = OcfCreateData['tag']; type OcfEditTag = OcfEditData['tag']; type OcfDeleteTag = OcfDeleteData['tag']; @@ -576,21 +478,6 @@ export const ENTITY_REGISTRY = { }, } as const satisfies OcfEntityRegistry; -type EntityTypeWithCapability = { - [EntityType in OcfEntityType]: (typeof ENTITY_REGISTRY)[EntityType]['operations'] extends Record - ? EntityType - : never; -}[OcfEntityType]; - -/** Entity kinds that can be created through UpdateCapTable. */ -export type OcfCreatableEntityType = EntityTypeWithCapability<'create'>; - -/** Entity kinds that can be edited through UpdateCapTable. */ -export type OcfEditableEntityType = EntityTypeWithCapability<'edit'>; - -/** Entity kinds that can be deleted through UpdateCapTable. */ -export type OcfDeletableEntityType = EntityTypeWithCapability<'delete'>; - type OcfOperationTagFor = (typeof ENTITY_REGISTRY)[EntityType]['operations'] extends Readonly> ? Tag @@ -630,133 +517,6 @@ export type OcfEntityArguments = { [EntityType in OcfEntityType]: readonly [type: EntityType, data: OcfDataTypeFor]; }[OcfEntityType]; -/** Correlated argument tuples accepted by {@link CapTableBatch.create}. */ -export type OcfCreateArguments = { - [EntityType in OcfCreatableEntityType]: readonly [type: EntityType, data: OcfDataTypeFor]; -}[OcfCreatableEntityType]; - -/** Correlated argument tuples accepted by {@link CapTableBatch.edit}. */ -export type OcfEditArguments = { - [EntityType in OcfEditableEntityType]: readonly [type: EntityType, data: OcfDataTypeFor]; -}[OcfEditableEntityType]; - -/** A create operation whose entity kind and payload type are correlated. */ -export type OcfCreateOperation = - EntityType extends OcfCreatableEntityType - ? Readonly<{ - type: EntityType; - data: OcfDataTypeFor; - }> - : never; - -/** An edit operation whose entity kind and payload type are correlated. */ -export type OcfEditOperation = - EntityType extends OcfEditableEntityType - ? Readonly<{ - type: EntityType; - data: OcfDataTypeFor; - }> - : never; - -/** A delete operation limited to entity kinds that support deletion. */ -export type OcfDeleteOperation = - EntityType extends OcfDeletableEntityType - ? Readonly<{ - type: EntityType; - id: string; - }> - : never; - -/** Operations accepted by {@link buildUpdateCapTableCommand}. */ -export interface CapTableBatchOperations { - readonly creates?: readonly OcfCreateOperation[]; - readonly edits?: readonly OcfEditOperation[]; - readonly deletes?: readonly OcfDeleteOperation[]; -} - -/** Runtime guard for SDK entity kinds. */ -export function isOcfEntityType(entityType: string): entityType is OcfEntityType { - return Object.prototype.hasOwnProperty.call(ENTITY_REGISTRY, entityType); -} - -/** Runtime guard for entity kinds that support create operations. */ -export function isOcfCreatableEntityType(entityType: string): entityType is OcfCreatableEntityType { - if (!isOcfEntityType(entityType)) return false; - const entry: OcfEntityRegistryEntry = ENTITY_REGISTRY[entityType]; - return entry.operations.create !== undefined; -} - -/** Runtime guard for entity kinds that support edit operations. */ -export function isOcfEditableEntityType(entityType: string): entityType is OcfEditableEntityType { - if (!isOcfEntityType(entityType)) return false; - const entry: OcfEntityRegistryEntry = ENTITY_REGISTRY[entityType]; - return entry.operations.edit !== undefined; -} - -/** Runtime guard for entity kinds that support delete operations. */ -export function isOcfDeletableEntityType(entityType: string): entityType is OcfDeletableEntityType { - if (!isOcfEntityType(entityType)) return false; - const entry: OcfEntityRegistryEntry = ENTITY_REGISTRY[entityType]; - return entry.operations.delete !== undefined; -} - -type OcfLedgerBackedObjectType = (typeof ENTITY_REGISTRY)[OcfEntityType]['objectType']; - -type OcfEntityTypeForRegistryObjectType = { - [EntityType in OcfEntityType]: (typeof ENTITY_REGISTRY)[EntityType]['objectType'] extends ObjectType - ? EntityType - : never; -}[OcfEntityType]; - -type OcfObjectTypeToEntityTypeMap = { - readonly [ObjectType in OcfLedgerBackedObjectType]: OcfEntityTypeForRegistryObjectType; -}; - -function buildOcfObjectTypeToEntityTypeMap(): OcfObjectTypeToEntityTypeMap { - const result: Partial> = {}; - for (const entityType of Object.keys(ENTITY_REGISTRY) as OcfEntityType[]) { - const { objectType } = ENTITY_REGISTRY[entityType]; - if (Object.prototype.hasOwnProperty.call(result, objectType)) { - throw new Error(`Duplicate OCF object type ${objectType} in ENTITY_REGISTRY`); - } - result[objectType] = entityType; - } - return Object.freeze(result) as OcfObjectTypeToEntityTypeMap; -} - -/** - * Canonical ledger-backed OCF `object_type` to SDK entity reader mapping. - * - * This is derived from {@link ENTITY_REGISTRY}; it must not become a second - * hand-maintained entity registry. Schema-supported PlanSecurity wrappers normalize - * to EquityCompensation before lookup. - */ -export const OCF_OBJECT_TYPE_TO_ENTITY_TYPE = buildOcfObjectTypeToEntityTypeMap(); - -/** OCF object types that can be read through OpenCapTable entity readers. */ -export type OcfReadableObjectType = keyof typeof OCF_OBJECT_TYPE_TO_ENTITY_TYPE; - -/** SDK entity reader name for a given OCF object type. */ -export type OcfEntityTypeForObjectType = (typeof OCF_OBJECT_TYPE_TO_ENTITY_TYPE)[T]; - -/** Canonical entity data returned for a readable OCF object type. */ -export type OcfReadableDataForObjectType = OcfDataTypeFor< - OcfEntityTypeForObjectType ->; - -export function mapOcfObjectTypeToEntityType( - objectType: T -): OcfEntityTypeForObjectType; -export function mapOcfObjectTypeToEntityType(objectType: string): OcfEntityType | null; -export function mapOcfObjectTypeToEntityType(objectType: string): OcfEntityType | null { - return isOcfReadableObjectType(objectType) ? OCF_OBJECT_TYPE_TO_ENTITY_TYPE[objectType] : null; -} - -/** Runtime guard for OCF object types supported by OpenCapTable readers. */ -export function isOcfReadableObjectType(objectType: string): objectType is OcfReadableObjectType { - return Object.prototype.hasOwnProperty.call(OCF_OBJECT_TYPE_TO_ENTITY_TYPE, objectType); -} - function entityRegistryEntries(): Array<[OcfEntityType, OcfEntityRegistryEntry]> { return Object.entries(ENTITY_REGISTRY) as Array<[OcfEntityType, OcfEntityRegistryEntry]>; } diff --git a/src/functions/OpenCapTable/capTable/buildCapTableCommand.ts b/src/functions/OpenCapTable/capTable/buildCapTableCommand.ts index 8ccaa25e..95e61686 100644 --- a/src/functions/OpenCapTable/capTable/buildCapTableCommand.ts +++ b/src/functions/OpenCapTable/capTable/buildCapTableCommand.ts @@ -1,6 +1,6 @@ import type { Command } from '@fairmint/canton-node-sdk/build/src/clients/ledger-json-api/schemas/api/commands'; import { OCP_TEMPLATES } from '@fairmint/open-captable-protocol-daml-js'; -import type { CommandWithDisclosedContracts } from '../../../types'; +import type { CommandWithDisclosedContracts } from '../../../types/common'; /** * Interface for JSON API choice arguments - matches what the SDK's Zod schema accepts. diff --git a/src/functions/OpenCapTable/capTable/entityTypes.ts b/src/functions/OpenCapTable/capTable/entityTypes.ts new file mode 100644 index 00000000..95f767e9 --- /dev/null +++ b/src/functions/OpenCapTable/capTable/entityTypes.ts @@ -0,0 +1,278 @@ +/** + * Public, protocol-native entity and batch types. + * + * This module deliberately depends only on the SDK's canonical OCF model. The + * generated DAML codecs are an implementation detail and must not become part + * of the package's root declaration graph. + */ + +import type { + OcfConvertibleAcceptance, + OcfConvertibleCancellation, + OcfConvertibleConversion, + OcfConvertibleIssuance, + OcfConvertibleRetraction, + OcfConvertibleTransfer, + OcfDocument, + OcfEquityCompensationAcceptance, + OcfEquityCompensationCancellation, + OcfEquityCompensationExercise, + OcfEquityCompensationIssuance, + OcfEquityCompensationRelease, + OcfEquityCompensationRepricing, + OcfEquityCompensationRetraction, + OcfEquityCompensationTransfer, + OcfFinancing, + OcfIssuer, + OcfIssuerAuthorizedSharesAdjustment, + OcfStakeholder, + OcfStakeholderRelationshipChangeEvent, + OcfStakeholderStatusChangeEvent, + OcfStockAcceptance, + OcfStockCancellation, + OcfStockClass, + OcfStockClassAuthorizedSharesAdjustment, + OcfStockClassConversionRatioAdjustment, + OcfStockClassSplit, + OcfStockConsolidation, + OcfStockConversion, + OcfStockIssuance, + OcfStockLegendTemplate, + OcfStockPlan, + OcfStockPlanPoolAdjustment, + OcfStockPlanReturnToPool, + OcfStockReissuance, + OcfStockRepurchase, + OcfStockRetraction, + OcfStockTransfer, + OcfValuation, + OcfVestingAcceleration, + OcfVestingEvent, + OcfVestingStart, + OcfVestingTerms, + OcfWarrantAcceptance, + OcfWarrantCancellation, + OcfWarrantExercise, + OcfWarrantIssuance, + OcfWarrantRetraction, + OcfWarrantTransfer, +} from '../../../types/native'; + +/** Canonical native OCF data for every entity handled by the SDK. */ +// A closed alias prevents module augmentation from widening the supported kinds. +// eslint-disable-next-line @typescript-eslint/consistent-type-definitions +export type OcfEntityDataMap = { + convertibleAcceptance: OcfConvertibleAcceptance; + convertibleCancellation: OcfConvertibleCancellation; + convertibleConversion: OcfConvertibleConversion; + convertibleIssuance: OcfConvertibleIssuance; + convertibleRetraction: OcfConvertibleRetraction; + convertibleTransfer: OcfConvertibleTransfer; + document: OcfDocument; + equityCompensationAcceptance: OcfEquityCompensationAcceptance; + equityCompensationCancellation: OcfEquityCompensationCancellation; + equityCompensationExercise: OcfEquityCompensationExercise; + equityCompensationIssuance: OcfEquityCompensationIssuance; + equityCompensationRelease: OcfEquityCompensationRelease; + equityCompensationRepricing: OcfEquityCompensationRepricing; + equityCompensationRetraction: OcfEquityCompensationRetraction; + equityCompensationTransfer: OcfEquityCompensationTransfer; + financing: OcfFinancing; + /** Issuer is edit-only; it is created with the CapTable. */ + issuer: OcfIssuer; + issuerAuthorizedSharesAdjustment: OcfIssuerAuthorizedSharesAdjustment; + stakeholder: OcfStakeholder; + stakeholderRelationshipChangeEvent: OcfStakeholderRelationshipChangeEvent; + stakeholderStatusChangeEvent: OcfStakeholderStatusChangeEvent; + stockAcceptance: OcfStockAcceptance; + stockCancellation: OcfStockCancellation; + stockClass: OcfStockClass; + stockClassAuthorizedSharesAdjustment: OcfStockClassAuthorizedSharesAdjustment; + stockClassConversionRatioAdjustment: OcfStockClassConversionRatioAdjustment; + stockClassSplit: OcfStockClassSplit; + stockConsolidation: OcfStockConsolidation; + stockConversion: OcfStockConversion; + stockIssuance: OcfStockIssuance; + stockLegendTemplate: OcfStockLegendTemplate; + stockPlan: OcfStockPlan; + stockPlanPoolAdjustment: OcfStockPlanPoolAdjustment; + stockPlanReturnToPool: OcfStockPlanReturnToPool; + stockReissuance: OcfStockReissuance; + stockRepurchase: OcfStockRepurchase; + stockRetraction: OcfStockRetraction; + stockTransfer: OcfStockTransfer; + valuation: OcfValuation; + vestingAcceleration: OcfVestingAcceleration; + vestingEvent: OcfVestingEvent; + vestingStart: OcfVestingStart; + vestingTerms: OcfVestingTerms; + warrantAcceptance: OcfWarrantAcceptance; + warrantCancellation: OcfWarrantCancellation; + warrantExercise: OcfWarrantExercise; + warrantIssuance: OcfWarrantIssuance; + warrantRetraction: OcfWarrantRetraction; + warrantTransfer: OcfWarrantTransfer; +}; + +/** All canonical entity kinds supported by batch operations and readers. */ +export type OcfEntityType = keyof OcfEntityDataMap; + +/** Canonical OCF data for one entity kind. */ +export type OcfDataTypeFor = OcfEntityDataMap[T]; + +/** Entity kinds that can be created through UpdateCapTable. */ +export type OcfCreatableEntityType = Exclude; + +/** Every canonical entity can be edited through UpdateCapTable. */ +export type OcfEditableEntityType = OcfEntityType; + +/** Entity kinds that can be deleted through UpdateCapTable. */ +export type OcfDeletableEntityType = Exclude; + +/** Correlated argument tuples accepted by {@link import('./CapTableBatch').CapTableBatch.create}. */ +export type OcfCreateArguments = { + [EntityType in OcfCreatableEntityType]: readonly [type: EntityType, data: OcfDataTypeFor]; +}[OcfCreatableEntityType]; + +/** Correlated argument tuples accepted by {@link import('./CapTableBatch').CapTableBatch.edit}. */ +export type OcfEditArguments = { + [EntityType in OcfEditableEntityType]: readonly [type: EntityType, data: OcfDataTypeFor]; +}[OcfEditableEntityType]; + +/** A create operation whose kind and payload remain correlated. */ +export type OcfCreateOperation = + EntityType extends OcfCreatableEntityType ? Readonly<{ type: EntityType; data: OcfDataTypeFor }> : never; + +/** An edit operation whose kind and payload remain correlated. */ +export type OcfEditOperation = + EntityType extends OcfEditableEntityType ? Readonly<{ type: EntityType; data: OcfDataTypeFor }> : never; + +/** A delete operation limited to deletable entity kinds. */ +export type OcfDeleteOperation = + EntityType extends OcfDeletableEntityType ? Readonly<{ type: EntityType; id: string }> : never; + +/** Native operations accepted by {@link import('./CapTableBatch').buildUpdateCapTableCommand}. */ +export interface CapTableBatchOperations { + readonly creates?: readonly OcfCreateOperation[]; + readonly edits?: readonly OcfEditOperation[]; + readonly deletes?: readonly OcfDeleteOperation[]; +} + +/** Contract ID returned for one OCF entity created or edited by a batch. */ +export type OcfContractId = { + [EntityType in OcfEntityType]: Readonly<{ + tag: `Cid${Capitalize}`; + value: string; + }>; +}[OcfEntityType]; + +/** Result of executing a fluent cap-table batch. */ +export interface CapTableBatchExecuteResult { + readonly updatedCapTableCid: string; + readonly createdCids: readonly OcfContractId[]; + readonly editedCids: readonly OcfContractId[]; + readonly updateId: string; +} + +/** Canonical OCF `object_type` to typed reader namespace. */ +export const OCF_OBJECT_TYPE_TO_ENTITY_TYPE = Object.freeze({ + CE_STAKEHOLDER_RELATIONSHIP: 'stakeholderRelationshipChangeEvent', + CE_STAKEHOLDER_STATUS: 'stakeholderStatusChangeEvent', + DOCUMENT: 'document', + FINANCING: 'financing', + ISSUER: 'issuer', + STAKEHOLDER: 'stakeholder', + STOCK_CLASS: 'stockClass', + STOCK_LEGEND_TEMPLATE: 'stockLegendTemplate', + STOCK_PLAN: 'stockPlan', + TX_CONVERTIBLE_ACCEPTANCE: 'convertibleAcceptance', + TX_CONVERTIBLE_CANCELLATION: 'convertibleCancellation', + TX_CONVERTIBLE_CONVERSION: 'convertibleConversion', + TX_CONVERTIBLE_ISSUANCE: 'convertibleIssuance', + TX_CONVERTIBLE_RETRACTION: 'convertibleRetraction', + TX_CONVERTIBLE_TRANSFER: 'convertibleTransfer', + TX_EQUITY_COMPENSATION_ACCEPTANCE: 'equityCompensationAcceptance', + TX_EQUITY_COMPENSATION_CANCELLATION: 'equityCompensationCancellation', + TX_EQUITY_COMPENSATION_EXERCISE: 'equityCompensationExercise', + TX_EQUITY_COMPENSATION_ISSUANCE: 'equityCompensationIssuance', + TX_EQUITY_COMPENSATION_RELEASE: 'equityCompensationRelease', + TX_EQUITY_COMPENSATION_REPRICING: 'equityCompensationRepricing', + TX_EQUITY_COMPENSATION_RETRACTION: 'equityCompensationRetraction', + TX_EQUITY_COMPENSATION_TRANSFER: 'equityCompensationTransfer', + TX_ISSUER_AUTHORIZED_SHARES_ADJUSTMENT: 'issuerAuthorizedSharesAdjustment', + TX_STOCK_ACCEPTANCE: 'stockAcceptance', + TX_STOCK_CANCELLATION: 'stockCancellation', + TX_STOCK_CLASS_AUTHORIZED_SHARES_ADJUSTMENT: 'stockClassAuthorizedSharesAdjustment', + TX_STOCK_CLASS_CONVERSION_RATIO_ADJUSTMENT: 'stockClassConversionRatioAdjustment', + TX_STOCK_CLASS_SPLIT: 'stockClassSplit', + TX_STOCK_CONSOLIDATION: 'stockConsolidation', + TX_STOCK_CONVERSION: 'stockConversion', + TX_STOCK_ISSUANCE: 'stockIssuance', + TX_STOCK_PLAN_POOL_ADJUSTMENT: 'stockPlanPoolAdjustment', + TX_STOCK_PLAN_RETURN_TO_POOL: 'stockPlanReturnToPool', + TX_STOCK_REISSUANCE: 'stockReissuance', + TX_STOCK_REPURCHASE: 'stockRepurchase', + TX_STOCK_RETRACTION: 'stockRetraction', + TX_STOCK_TRANSFER: 'stockTransfer', + TX_VESTING_ACCELERATION: 'vestingAcceleration', + TX_VESTING_EVENT: 'vestingEvent', + TX_VESTING_START: 'vestingStart', + TX_WARRANT_ACCEPTANCE: 'warrantAcceptance', + TX_WARRANT_CANCELLATION: 'warrantCancellation', + TX_WARRANT_EXERCISE: 'warrantExercise', + TX_WARRANT_ISSUANCE: 'warrantIssuance', + TX_WARRANT_RETRACTION: 'warrantRetraction', + TX_WARRANT_TRANSFER: 'warrantTransfer', + VALUATION: 'valuation', + VESTING_TERMS: 'vestingTerms', +} as const satisfies Record); + +type MappedOcfEntityType = (typeof OCF_OBJECT_TYPE_TO_ENTITY_TYPE)[keyof typeof OCF_OBJECT_TYPE_TO_ENTITY_TYPE]; +const ALL_OCF_ENTITY_TYPES_ARE_MAPPED: [OcfEntityType] extends [MappedOcfEntityType] ? true : never = true; +void ALL_OCF_ENTITY_TYPES_ARE_MAPPED; + +/** OCF object types supported by the high-level entity readers. */ +export type OcfReadableObjectType = keyof typeof OCF_OBJECT_TYPE_TO_ENTITY_TYPE; + +/** Reader namespace for one canonical OCF object type. */ +export type OcfEntityTypeForObjectType = (typeof OCF_OBJECT_TYPE_TO_ENTITY_TYPE)[T]; + +/** Canonical data returned by one object-type reader. */ +export type OcfReadableDataForObjectType = OcfDataTypeFor< + OcfEntityTypeForObjectType +>; + +const OCF_ENTITY_TYPES: ReadonlySet = new Set(Object.values(OCF_OBJECT_TYPE_TO_ENTITY_TYPE)); + +/** Runtime guard for canonical SDK entity kinds. */ +export function isOcfEntityType(entityType: string): entityType is OcfEntityType { + return OCF_ENTITY_TYPES.has(entityType); +} + +/** Runtime guard for entity kinds that support creation. */ +export function isOcfCreatableEntityType(entityType: string): entityType is OcfCreatableEntityType { + return entityType !== 'issuer' && isOcfEntityType(entityType); +} + +/** Runtime guard for entity kinds that support edits. */ +export function isOcfEditableEntityType(entityType: string): entityType is OcfEditableEntityType { + return isOcfEntityType(entityType); +} + +/** Runtime guard for entity kinds that support deletion. */ +export function isOcfDeletableEntityType(entityType: string): entityType is OcfDeletableEntityType { + return entityType !== 'issuer' && isOcfEntityType(entityType); +} + +export function mapOcfObjectTypeToEntityType( + objectType: T +): OcfEntityTypeForObjectType; +export function mapOcfObjectTypeToEntityType(objectType: string): OcfEntityType | null; +export function mapOcfObjectTypeToEntityType(objectType: string): OcfEntityType | null { + return isOcfReadableObjectType(objectType) ? OCF_OBJECT_TYPE_TO_ENTITY_TYPE[objectType] : null; +} + +/** Runtime guard for OCF object types supported by high-level readers. */ +export function isOcfReadableObjectType(objectType: string): objectType is OcfReadableObjectType { + return Object.prototype.hasOwnProperty.call(OCF_OBJECT_TYPE_TO_ENTITY_TYPE, objectType); +} diff --git a/src/functions/OpenCapTable/capTable/generatedBatchOperations.ts b/src/functions/OpenCapTable/capTable/generatedBatchOperations.ts new file mode 100644 index 00000000..c0e66753 --- /dev/null +++ b/src/functions/OpenCapTable/capTable/generatedBatchOperations.ts @@ -0,0 +1,158 @@ +/** @internal Generated DAML operation construction for CapTableBatch. */ + +import { Fairmint } from '@fairmint/open-captable-protocol-daml-js'; +import { OcpErrorCodes, OcpValidationError } from '../../../errors'; +import { + ENTITY_TAG_MAP, + type OcfCreateData, + type OcfCreateDataFor, + type OcfDeleteData, + type OcfDeleteDataFor, + type OcfEditData, + type OcfEditDataFor, +} from './batchTypes'; +import type { + OcfCreatableEntityType, + OcfCreateArguments, + OcfCreateOperation, + OcfDeletableEntityType, + OcfDeleteOperation, + OcfEditableEntityType, + OcfEditArguments, + OcfEditOperation, + OcfEntityType, +} from './entityTypes'; +import { isOcfCreatableEntityType, isOcfDeletableEntityType, isOcfEditableEntityType } from './entityTypes'; +import { convertOperationToDaml, convertToDaml } from './ocfToDaml'; + +function decodeGeneratedOperation( + decoder: { runWithException: (input: unknown) => T }, + input: unknown, + operation: 'create' | 'edit' | 'delete', + entityType: OcfEntityType +): T { + try { + return decoder.runWithException(input); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + throw new OcpValidationError( + `batch.${operation}.${entityType}`, + `Converter output does not match the generated DAML ${operation} variant: ${message}`, + { + code: OcpErrorCodes.INVALID_FORMAT, + context: { operation, entityType }, + } + ); + } +} + +interface GeneratedOperationDataMap { + readonly create: OcfCreateData; + readonly edit: OcfEditData; + readonly delete: OcfDeleteData; +} + +interface GeneratedOperationEntityTypeMap { + readonly create: OcfCreatableEntityType; + readonly edit: OcfEditableEntityType; + readonly delete: OcfDeletableEntityType; +} + +type GeneratedOperation = keyof GeneratedOperationDataMap; + +interface GeneratedOperationBuilder { + readonly operation: Operation; + readonly displayName: Capitalize; + readonly supports: (type: string) => type is GeneratedOperationEntityTypeMap[Operation]; + readonly tagFor: (type: GeneratedOperationEntityTypeMap[Operation]) => GeneratedOperationDataMap[Operation]['tag']; + readonly decoder: { runWithException: (input: unknown) => GeneratedOperationDataMap[Operation] }; +} + +const CREATE_OPERATION_BUILDER: GeneratedOperationBuilder<'create'> = { + operation: 'create', + displayName: 'Create', + supports: isOcfCreatableEntityType, + tagFor: (type) => ENTITY_TAG_MAP[type].create, + decoder: Fairmint.OpenCapTable.CapTable.OcfCreateData.decoder, +}; + +const EDIT_OPERATION_BUILDER: GeneratedOperationBuilder<'edit'> = { + operation: 'edit', + displayName: 'Edit', + supports: isOcfEditableEntityType, + tagFor: (type) => ENTITY_TAG_MAP[type].edit, + decoder: Fairmint.OpenCapTable.CapTable.OcfEditData.decoder, +}; + +const DELETE_OPERATION_BUILDER: GeneratedOperationBuilder<'delete'> = { + operation: 'delete', + displayName: 'Delete', + supports: isOcfDeletableEntityType, + tagFor: (type) => ENTITY_TAG_MAP[type].delete, + decoder: Fairmint.OpenCapTable.CapTable.OcfDeleteData.decoder, +}; + +function buildGeneratedOperationData( + type: OcfEntityType, + convert: () => unknown, + builder: GeneratedOperationBuilder +): GeneratedOperationDataMap[Operation] { + if (!builder.supports(type)) { + throw new OcpValidationError( + 'type', + `${builder.displayName} operation not supported for entity type: ${String(type)}`, + { + code: OcpErrorCodes.INVALID_TYPE, + } + ); + } + + return decodeGeneratedOperation( + builder.decoder, + { tag: builder.tagFor(type), value: convert() }, + builder.operation, + type + ); +} + +/** @internal Build and validate one generated DAML create variant. */ +export function buildOcfCreateData( + ...args: Arguments +): OcfCreateDataFor; +export function buildOcfCreateData(...args: OcfCreateArguments): OcfCreateData { + const [type] = args; + return buildGeneratedOperationData(type, () => convertToDaml(...args), CREATE_OPERATION_BUILDER); +} + +export function buildOcfCreateDataFromOperation(operation: OcfCreateOperation): OcfCreateData { + const { type } = operation; + return buildGeneratedOperationData(type, () => convertOperationToDaml(operation), CREATE_OPERATION_BUILDER); +} + +/** @internal Build and validate one generated DAML edit variant. */ +export function buildOcfEditData( + ...args: Arguments +): OcfEditDataFor; +export function buildOcfEditData(...args: OcfEditArguments): OcfEditData { + const [type] = args; + return buildGeneratedOperationData(type, () => convertToDaml(...args), EDIT_OPERATION_BUILDER); +} + +export function buildOcfEditDataFromOperation(operation: OcfEditOperation): OcfEditData { + const { type } = operation; + return buildGeneratedOperationData(type, () => convertOperationToDaml(operation), EDIT_OPERATION_BUILDER); +} + +/** @internal Build and validate one generated DAML delete variant. */ +export function buildOcfDeleteData( + type: EntityType, + id: string +): OcfDeleteDataFor; +export function buildOcfDeleteData(type: OcfDeletableEntityType, id: string): OcfDeleteData { + return buildGeneratedOperationData(type, () => id, DELETE_OPERATION_BUILDER); +} + +export function buildOcfDeleteDataFromOperation(operation: OcfDeleteOperation): OcfDeleteData { + const { type } = operation; + return buildGeneratedOperationData(type, () => operation.id, DELETE_OPERATION_BUILDER); +} diff --git a/src/functions/OpenCapTable/capTable/getCapTableState.ts b/src/functions/OpenCapTable/capTable/getCapTableState.ts index aa13c41f..889478f6 100644 --- a/src/functions/OpenCapTable/capTable/getCapTableState.ts +++ b/src/functions/OpenCapTable/capTable/getCapTableState.ts @@ -35,8 +35,8 @@ import { } from '../../../utils/contractReadDiagnostics'; import { ledgerReadScope } from '../../../utils/readScope'; import { parseDamlMap } from '../../../utils/typeConversions'; -import { FIELD_TO_ENTITY_TYPE, SECURITY_ID_FIELD_TO_ENTITY_TYPE, type OcfEntityType } from './batchTypes'; -export { FIELD_TO_ENTITY_TYPE, SECURITY_ID_FIELD_TO_ENTITY_TYPE } from './batchTypes'; +import { FIELD_TO_ENTITY_TYPE, SECURITY_ID_FIELD_TO_ENTITY_TYPE } from './batchTypes'; +import type { OcfEntityType } from './entityTypes'; /** CapTable template ID this SDK treats as current (package-name symbolic form; used for ledger queries). */ const CURRENT_CAP_TABLE_TEMPLATE_ID = OCP_TEMPLATES.capTable; diff --git a/src/functions/OpenCapTable/capTable/index.ts b/src/functions/OpenCapTable/capTable/index.ts index 6e8e8892..13d6f1e4 100644 --- a/src/functions/OpenCapTable/capTable/index.ts +++ b/src/functions/OpenCapTable/capTable/index.ts @@ -20,14 +20,12 @@ export { export * from './batchTypes'; export { CapTableBatch, - buildOcfCreateData, - buildOcfDeleteData, - buildOcfEditData, buildUpdateCapTableCommand, type BatchItemDetails, type BatchItemMeta, type CapTableBatchParams, } from './CapTableBatch'; +export { buildOcfCreateData, buildOcfDeleteData, buildOcfEditData } from './generatedBatchOperations'; export { convertToDaml } from './ocfToDaml'; // DAML to OCF conversion (read operations) @@ -43,9 +41,8 @@ export { } from './damlToOcf'; // CapTable state reader (for replication) +export { FIELD_TO_ENTITY_TYPE, SECURITY_ID_FIELD_TO_ENTITY_TYPE } from './batchTypes'; export { - FIELD_TO_ENTITY_TYPE, - SECURITY_ID_FIELD_TO_ENTITY_TYPE, classifyIssuerCapTables, getCapTableState, type CapTableState, diff --git a/src/functions/OpenCapTable/issuer/api.ts b/src/functions/OpenCapTable/issuer/api.ts new file mode 100644 index 00000000..e1f75716 --- /dev/null +++ b/src/functions/OpenCapTable/issuer/api.ts @@ -0,0 +1,12 @@ +/** Public CreateIssuer API, isolated from generated DAML converter declarations. */ + +import type { CommandWithDisclosedContracts } from '../../../types/common'; +import { buildCreateIssuerCommand as buildGeneratedCreateIssuerCommand } from './createIssuer'; +import type { CreateIssuerParams } from './types'; + +export type { CreateIssuerParams, IssuerDataInput } from './types'; + +/** Build the CreateCapTable command that creates an issuer and its CapTable. */ +export function buildCreateIssuerCommand(params: CreateIssuerParams): CommandWithDisclosedContracts { + return buildGeneratedCreateIssuerCommand(params); +} diff --git a/src/functions/OpenCapTable/issuer/createIssuer.ts b/src/functions/OpenCapTable/issuer/createIssuer.ts index 956351cb..77c914a4 100644 --- a/src/functions/OpenCapTable/issuer/createIssuer.ts +++ b/src/functions/OpenCapTable/issuer/createIssuer.ts @@ -3,7 +3,8 @@ import type { DisclosedContract, } from '@fairmint/canton-node-sdk/build/src/clients/ledger-json-api/schemas/api/commands'; import type { Fairmint } from '@fairmint/open-captable-protocol-daml-js'; -import type { CommandWithDisclosedContracts, OcfIssuer } from '../../../types'; +import type { CommandWithDisclosedContracts } from '../../../types/common'; +import type { OcfIssuer } from '../../../types/native'; import { validateIssuerData } from '../../../utils/entityValidators'; import { emailTypeToDaml, phoneTypeToDaml } from '../../../utils/enumConversions'; import { parseOcfEntityInput } from '../../../utils/ocfZodSchemas'; @@ -15,6 +16,7 @@ import { initialSharesAuthorizedToDaml, optionalString, } from '../../../utils/typeConversions'; +import type { CreateIssuerParams, IssuerDataInput } from './types'; function emailToDaml(email: OcfIssuer['email']): Fairmint.OpenCapTable.Types.Contact.OcfEmail | null { if (!email) return null; @@ -36,10 +38,7 @@ function phoneToDaml(phone: OcfIssuer['phone']): Fairmint.OpenCapTable.Types.Con * Input type for issuer data that may have missing array fields. * The SDK normalizes these to empty arrays automatically. */ -export type IssuerDataInput = Omit & { - /** Tax IDs - normalized to empty array if null/undefined */ - tax_ids?: OcfIssuer['tax_ids'] | null; -}; +export type { CreateIssuerParams, IssuerDataInput } from './types'; /** * Normalize issuer data by ensuring array fields are arrays (not null/undefined). @@ -122,31 +121,6 @@ function issuerDataToDamlInternal( }; } -export interface CreateIssuerParams { - /** Details of the IssuerAuthorization contract for disclosed contracts */ - issuerAuthorizationContractDetails: DisclosedContract; - issuerParty: string; - /** - * Issuer data to create - * - * Schema: https://schema.opencaptablecoalition.com/v/1.2.0/objects/Issuer.schema.json - * - * - Legal_name: Legal name of the issuer - * - Formation_date: Date of formation (YYYY-MM-DD) - * - Country_of_formation: Country of formation (ISO 3166-1 alpha-2) - * - Dba (optional): Doing Business As name - * - Country_subdivision_of_formation (optional): Subdivision code of formation (ISO 3166-2) - * - Country_subdivision_name_of_formation (optional): Text name of subdivision of formation - * - Tax_ids (optional): Issuer tax IDs (normalized to empty array if null/undefined) - * - Email (optional): Work email - * - Phone (optional): Phone number in ITU E.123 format - * - Address (optional): Headquarters address - * - Initial_shares_authorized (optional): Initial authorized shares (enum or numeric) - * - Comments (optional): Additional comments - */ - issuerData: IssuerDataInput; -} - /** * Build the ledger command to exercise **CreateCapTable** on an IssuerAuthorization contract. * diff --git a/src/functions/OpenCapTable/issuer/index.ts b/src/functions/OpenCapTable/issuer/index.ts index 2f332fc6..61ea4947 100644 --- a/src/functions/OpenCapTable/issuer/index.ts +++ b/src/functions/OpenCapTable/issuer/index.ts @@ -1,2 +1,3 @@ -export * from './createIssuer'; +export * from './api'; +export { issuerDataToDaml, normalizeIssuerData } from './createIssuer'; export * from './getIssuerAsOcf'; diff --git a/src/functions/OpenCapTable/issuer/types.ts b/src/functions/OpenCapTable/issuer/types.ts new file mode 100644 index 00000000..d970c16b --- /dev/null +++ b/src/functions/OpenCapTable/issuer/types.ts @@ -0,0 +1,18 @@ +import type { DisclosedContract } from '../../../types/common'; +import type { OcfIssuer } from '../../../types/native'; + +/** Issuer input accepted by the high-level CreateCapTable command builder. */ +export type IssuerDataInput = Omit & { + /** Tax IDs are normalized to an empty array when omitted or null. */ + tax_ids?: OcfIssuer['tax_ids'] | null; +}; + +/** Parameters for creating the issuer and its CapTable. */ +export interface CreateIssuerParams { + /** IssuerAuthorization contract disclosed to the CreateCapTable choice. */ + issuerAuthorizationContractDetails: DisclosedContract; + /** Issuer party authorizing creation. */ + issuerParty: string; + /** Canonical OCF issuer payload. */ + issuerData: IssuerDataInput; +} diff --git a/src/functions/OpenCapTable/issuerAuthorization/authorizeIssuer.ts b/src/functions/OpenCapTable/issuerAuthorization/authorizeIssuer.ts index db65d91f..57bedc9e 100644 --- a/src/functions/OpenCapTable/issuerAuthorization/authorizeIssuer.ts +++ b/src/functions/OpenCapTable/issuerAuthorization/authorizeIssuer.ts @@ -1,24 +1,12 @@ import type { LedgerJsonApiClient } from '@fairmint/canton-node-sdk'; -import type { SubmitAndWaitForTransactionTreeResponse } from '@fairmint/canton-node-sdk/build/src/clients/ledger-json-api/operations'; -import type { DisclosedContract } from '@fairmint/canton-node-sdk/build/src/clients/ledger-json-api/schemas'; import { findCreatedEventByTemplateId } from '@fairmint/canton-node-sdk/build/src/utils/contracts/findCreatedEvent'; import { OCP_TEMPLATES, type Fairmint } from '@fairmint/open-captable-protocol-daml-js'; import factoryContractIdData from '@fairmint/open-captable-protocol-daml-js/ocp-factory-contract-id.json'; import { OcpContractError, OcpErrorCodes, OcpValidationError } from '../../../errors'; -import { submitObservedTransactionTree, type CommandObservabilityOptions } from '../../../observability'; +import { submitObservedTransactionTree } from '../../../observability'; +import type { AuthorizeIssuerParams, AuthorizeIssuerResult } from './types'; -export interface AuthorizeIssuerParams extends CommandObservabilityOptions { - issuer: string; // Party ID of the issuer to authorize - /** Override: factory contract ID (e.g. for staging). Requires factoryTemplateId. */ - factoryContractId?: string; - /** Override: factory template ID (e.g. for staging). Required when factoryContractId is set. */ - factoryTemplateId?: string; -} - -export interface AuthorizeIssuerResult extends DisclosedContract { - updateId: string; - response: SubmitAndWaitForTransactionTreeResponse; -} +export type { AuthorizeIssuerParams, AuthorizeIssuerResult } from './types'; /** * Authorize an issuer using the OCP Factory contract diff --git a/src/functions/OpenCapTable/issuerAuthorization/index.ts b/src/functions/OpenCapTable/issuerAuthorization/index.ts index 60f79215..e6223834 100644 --- a/src/functions/OpenCapTable/issuerAuthorization/index.ts +++ b/src/functions/OpenCapTable/issuerAuthorization/index.ts @@ -1,2 +1,3 @@ export * from './authorizeIssuer'; +export * from './types'; export * from './withdrawAuthorization'; diff --git a/src/functions/OpenCapTable/issuerAuthorization/types.ts b/src/functions/OpenCapTable/issuerAuthorization/types.ts new file mode 100644 index 00000000..1b03dd92 --- /dev/null +++ b/src/functions/OpenCapTable/issuerAuthorization/types.ts @@ -0,0 +1,29 @@ +import type { CommandObservabilityOptions } from '../../../observability'; +import type { DisclosedContract, SubmitAndWaitForTransactionTreeResponse } from '../../../types/common'; + +/** Parameters for authorizing an issuer through the OCP Factory. */ +export interface AuthorizeIssuerParams extends CommandObservabilityOptions { + issuer: string; + /** Factory contract override. Must be paired with `factoryTemplateId`. */ + factoryContractId?: string; + /** Factory template override. Must be paired with `factoryContractId`. */ + factoryTemplateId?: string; +} + +/** Result of authorizing an issuer. */ +export interface AuthorizeIssuerResult extends DisclosedContract { + updateId: string; + response: SubmitAndWaitForTransactionTreeResponse; +} + +/** Parameters for withdrawing an issuer authorization. */ +export interface WithdrawAuthorizationParams extends CommandObservabilityOptions { + issuerAuthorizationContractId: string; + systemOperatorParty: string; +} + +/** Result of withdrawing an issuer authorization. */ +export interface WithdrawAuthorizationResult { + updateId: string; + response: SubmitAndWaitForTransactionTreeResponse; +} diff --git a/src/functions/OpenCapTable/issuerAuthorization/withdrawAuthorization.ts b/src/functions/OpenCapTable/issuerAuthorization/withdrawAuthorization.ts index 11cf56f1..3d8346ad 100644 --- a/src/functions/OpenCapTable/issuerAuthorization/withdrawAuthorization.ts +++ b/src/functions/OpenCapTable/issuerAuthorization/withdrawAuthorization.ts @@ -1,17 +1,9 @@ import type { LedgerJsonApiClient } from '@fairmint/canton-node-sdk'; -import type { SubmitAndWaitForTransactionTreeResponse } from '@fairmint/canton-node-sdk/build/src/clients/ledger-json-api/operations'; import { OCP_TEMPLATES } from '@fairmint/open-captable-protocol-daml-js'; -import { submitObservedTransactionTree, type CommandObservabilityOptions } from '../../../observability'; +import { submitObservedTransactionTree } from '../../../observability'; +import type { WithdrawAuthorizationParams, WithdrawAuthorizationResult } from './types'; -export interface WithdrawAuthorizationParams extends CommandObservabilityOptions { - issuerAuthorizationContractId: string; - systemOperatorParty: string; -} - -export interface WithdrawAuthorizationResult { - updateId: string; - response: SubmitAndWaitForTransactionTreeResponse; -} +export type { WithdrawAuthorizationParams, WithdrawAuthorizationResult } from './types'; export async function withdrawAuthorization( client: LedgerJsonApiClient, diff --git a/src/functions/OpenCapTable/stockIssuance/createStockIssuance.ts b/src/functions/OpenCapTable/stockIssuance/createStockIssuance.ts index 876d8d3d..3d7c7930 100644 --- a/src/functions/OpenCapTable/stockIssuance/createStockIssuance.ts +++ b/src/functions/OpenCapTable/stockIssuance/createStockIssuance.ts @@ -1,10 +1,6 @@ import { OcpErrorCodes, OcpParseError } from '../../../errors'; -import type { - OcfStockIssuance, - PkgStockIssuanceOcfData, - PkgStockIssuanceType, - StockIssuanceType, -} from '../../../types'; +import type { PkgStockIssuanceOcfData, PkgStockIssuanceType } from '../../../types/daml'; +import type { OcfStockIssuance, StockIssuanceType } from '../../../types/native'; import { validateStockIssuanceData } from '../../../utils/entityValidators'; import { cleanComments, diff --git a/src/index.ts b/src/index.ts index f26ec0e8..09ad1a62 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,7 +1,48 @@ +/** + * Curated public API for the OCP Canton SDK. + * + * Generated DAML codecs and low-level conversion/read helpers intentionally stay + * behind this boundary. Public declarations are expressed only in canonical OCF + * and Canton client types so strict consumers do not inherit code-generator + * implementation details. + */ + export * from './environment'; export * from './errors'; -export * from './functions'; export * from './observability'; export * from './OcpClient'; -export * from './types'; -export * from './utils'; + +export * from './types/branded'; +export * from './types/common'; +export * from './types/native'; +export * from './types/output'; + +export type { ArchiveCapTableParams, ArchiveCapTableResult } from './functions/OpenCapTable/capTable/archiveCapTable'; +export { + CapTableBatch, + buildUpdateCapTableCommand, + type BatchItemDetails, + type BatchItemMeta, + type CapTableBatchParams, +} from './functions/OpenCapTable/capTable/CapTableBatch'; +export * from './functions/OpenCapTable/capTable/entityTypes'; +export type { + CapTableState, + CapTableWithArchiveContext, + IssuerCapTableClassification, + IssuerCapTableStatus, +} from './functions/OpenCapTable/capTable/getCapTableState'; + +export { + buildCreateIssuerCommand, + type CreateIssuerParams, + type IssuerDataInput, +} from './functions/OpenCapTable/issuer/api'; +export { authorizeIssuer } from './functions/OpenCapTable/issuerAuthorization/authorizeIssuer'; +export type { + AuthorizeIssuerParams, + AuthorizeIssuerResult, + WithdrawAuthorizationParams, + WithdrawAuthorizationResult, +} from './functions/OpenCapTable/issuerAuthorization/types'; +export { withdrawAuthorization } from './functions/OpenCapTable/issuerAuthorization/withdrawAuthorization'; diff --git a/src/types/common.ts b/src/types/common.ts index f199aebb..087f8c9b 100644 --- a/src/types/common.ts +++ b/src/types/common.ts @@ -22,6 +22,12 @@ export type { ClientConfig, LedgerJsonApiClient, ValidatorApiClient } from '@fai export type { SubmitAndWaitForTransactionTreeResponse } from '@fairmint/canton-node-sdk/build/src/clients/ledger-json-api/operations'; +/** A ledger command together with the contracts disclosed for its execution. */ +export interface CommandWithDisclosedContracts { + command: Command; + disclosedContracts: DisclosedContract[]; +} + // ===== Common Params Types ===== /** diff --git a/src/types/index.ts b/src/types/index.ts index 795f9c18..31b49103 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -1,16 +1,4 @@ export * from './branded'; export * from './common'; -export * from './daml'; export * from './native'; export * from './output'; - -import type { Command, DisclosedContract } from './common'; - -/** - * Return type for all buildCreate*Command functions. Contains a command and its associated disclosed contracts for - * Canton cross-domain interactions. - */ -export interface CommandWithDisclosedContracts { - command: Command; - disclosedContracts: DisclosedContract[]; -} diff --git a/src/utils/cantonOcfExtractor.ts b/src/utils/cantonOcfExtractor.ts index 26d9fb45..677d1a83 100644 --- a/src/utils/cantonOcfExtractor.ts +++ b/src/utils/cantonOcfExtractor.ts @@ -13,9 +13,9 @@ import type { LedgerJsonApiClient } from '@fairmint/canton-node-sdk'; import { OcpErrorCodes } from '../errors/codes'; import { OcpValidationError } from '../errors/OcpValidationError'; -import type { OcfEntityType } from '../functions/OpenCapTable/capTable/batchTypes'; import type { SupportedOcfReadType } from '../functions/OpenCapTable/capTable/damlToOcf'; import { getEntityAsOcf } from '../functions/OpenCapTable/capTable/damlToOcf'; +import type { OcfEntityType } from '../functions/OpenCapTable/capTable/entityTypes'; import type { CapTableState } from '../functions/OpenCapTable/capTable/getCapTableState'; import { getConvertibleIssuanceAsOcf } from '../functions/OpenCapTable/convertibleIssuance'; import { getDocumentAsOcf } from '../functions/OpenCapTable/document'; diff --git a/src/utils/replicationHelpers.ts b/src/utils/replicationHelpers.ts index 730beaa0..e5f6bd6b 100644 --- a/src/utils/replicationHelpers.ts +++ b/src/utils/replicationHelpers.ts @@ -9,14 +9,14 @@ * @module replicationHelpers */ -import type { OcfEntityType } from '../functions/OpenCapTable/capTable/batchTypes'; +import type { OcfEntityType } from '../functions/OpenCapTable/capTable/entityTypes'; import type { CapTableState } from '../functions/OpenCapTable/capTable/getCapTableState'; import type { OcfManifest } from './cantonOcfExtractor'; import { DEFAULT_DEPRECATED_FIELDS, DEFAULT_INTERNAL_FIELDS, ocfDeepEqual } from './ocfComparison'; import { normalizeObjectType, normalizeOcfData } from './planSecurityAliases'; -// Preserve the public utils import path while keeping the registry as the single implementation. -export { isOcfEntityType } from '../functions/OpenCapTable/capTable/batchTypes'; +// Preserve the public utils import path while keeping the protocol-native guard implementation centralized. +export { isOcfEntityType } from '../functions/OpenCapTable/capTable/entityTypes'; // ============================================================================ // Categorized Type Mapping diff --git a/test/batch/batchTypes.test.ts b/test/batch/batchTypes.test.ts index 6cf51ce0..1aa98dc1 100644 --- a/test/batch/batchTypes.test.ts +++ b/test/batch/batchTypes.test.ts @@ -1,15 +1,34 @@ import { - ENTITY_REGISTRY, isOcfCreatableEntityType, isOcfDeletableEntityType, isOcfEditableEntityType, isOcfEntityType, + isOcfReadableObjectType, + mapOcfObjectTypeToEntityType, + OCF_OBJECT_TYPE_TO_ENTITY_TYPE, type OcfEntityType, } from '../../src'; +import { ENTITY_REGISTRY } from '../../src/functions/OpenCapTable/capTable/batchTypes'; const entityTypes = Object.keys(ENTITY_REGISTRY) as OcfEntityType[]; describe('batch entity capabilities', () => { + it('maps every registered entity type from a canonical OCF object type', () => { + expect(new Set(Object.values(OCF_OBJECT_TYPE_TO_ENTITY_TYPE))).toEqual(new Set(entityTypes)); + }); + + it('keeps the public object-type mapping immutable at runtime', () => { + expect(Object.isFrozen(OCF_OBJECT_TYPE_TO_ENTITY_TYPE)).toBe(true); + + expect(Reflect.set(OCF_OBJECT_TYPE_TO_ENTITY_TYPE, 'STOCK_CLASS', 'issuer')).toBe(false); + expect(Reflect.set(OCF_OBJECT_TYPE_TO_ENTITY_TYPE, 'SYNTHETIC', 'stakeholder')).toBe(false); + + expect(OCF_OBJECT_TYPE_TO_ENTITY_TYPE.STOCK_CLASS).toBe('stockClass'); + expect(mapOcfObjectTypeToEntityType('STOCK_CLASS')).toBe('stockClass'); + expect(isOcfReadableObjectType('SYNTHETIC')).toBe(false); + expect(mapOcfObjectTypeToEntityType('SYNTHETIC')).toBeNull(); + }); + it.each(entityTypes)('recognizes %s as a supported entity type', (entityType) => { expect(isOcfEntityType(entityType)).toBe(true); }); diff --git a/test/batch/generatedOperationConstruction.test.ts b/test/batch/generatedOperationConstruction.test.ts index ab5ecce1..a3a4f89a 100644 --- a/test/batch/generatedOperationConstruction.test.ts +++ b/test/batch/generatedOperationConstruction.test.ts @@ -1,25 +1,25 @@ import type { LedgerJsonApiClient } from '@fairmint/canton-node-sdk'; import { Fairmint } from '@fairmint/open-captable-protocol-daml-js'; import { - buildOcfCreateData, - buildOcfDeleteData, - buildOcfEditData, buildUpdateCapTableCommand, - ENTITY_REGISTRY, - ENTITY_TAG_MAP, - getEntityAsOcf, isOcfCreatableEntityType, isOcfDeletableEntityType, isOcfEditableEntityType, OcpClient, - parseOcfEntityInput, - parseOcfObject, type OcfCreateArguments, type OcfDataTypeFor, type OcfEditArguments, type OcfEntityType, } from '../../src'; import { OcpValidationError } from '../../src/errors'; +import { ENTITY_REGISTRY, ENTITY_TAG_MAP } from '../../src/functions/OpenCapTable/capTable/batchTypes'; +import { getEntityAsOcf } from '../../src/functions/OpenCapTable/capTable/damlToOcf'; +import { + buildOcfCreateData, + buildOcfDeleteData, + buildOcfEditData, +} from '../../src/functions/OpenCapTable/capTable/generatedBatchOperations'; +import { parseOcfEntityInput, parseOcfObject } from '../../src/utils/ocfZodSchemas'; import { loadFixture, stripSourceMetadata } from '../utils/productionFixtures'; function loadEntityFixture( diff --git a/test/client/OcpClient.test.ts b/test/client/OcpClient.test.ts index eb764097..521a014a 100644 --- a/test/client/OcpClient.test.ts +++ b/test/client/OcpClient.test.ts @@ -1,7 +1,6 @@ import { Canton, type ClientConfig } from '@fairmint/canton-node-sdk'; import { Fairmint } from '@fairmint/open-captable-protocol-daml-js'; import { OcpErrorCodes } from '../../src/errors'; -import * as openCapTableCapTable from '../../src/functions/OpenCapTable/capTable'; import { ENTITY_REGISTRY, OCF_OBJECT_TYPE_TO_ENTITY_TYPE, @@ -9,6 +8,7 @@ import { type OcfEntityType, type OcfReadableObjectType, } from '../../src/functions/OpenCapTable/capTable'; +import * as capTableState from '../../src/functions/OpenCapTable/capTable/getCapTableState'; import { authorizeIssuer, type AuthorizeIssuerResult, @@ -338,15 +338,15 @@ describe('OcpClient OpenCapTable.issuerAuthorization.authorize', () => { describe('OcpClient OpenCapTable.capTable facade', () => { const config: ClientConfig = { network: 'devnet' }; - let classifySpy: jest.SpiedFunction; - let getStateSpy: jest.SpiedFunction; + let classifySpy: jest.SpiedFunction; + let getStateSpy: jest.SpiedFunction; beforeEach(() => { - classifySpy = jest.spyOn(openCapTableCapTable, 'classifyIssuerCapTables').mockResolvedValue({ + classifySpy = jest.spyOn(capTableState, 'classifyIssuerCapTables').mockResolvedValue({ status: 'none', current: null, }); - getStateSpy = jest.spyOn(openCapTableCapTable, 'getCapTableState').mockResolvedValue(null); + getStateSpy = jest.spyOn(capTableState, 'getCapTableState').mockResolvedValue(null); }); afterEach(() => { diff --git a/test/converters/coreObjectReadValidation.test.ts b/test/converters/coreObjectReadValidation.test.ts index cc2675c3..de562a53 100644 --- a/test/converters/coreObjectReadValidation.test.ts +++ b/test/converters/coreObjectReadValidation.test.ts @@ -1,4 +1,6 @@ -import { damlDocumentDataToNative, damlStakeholderDataToNative, OcpValidationError } from '../../src'; +import { OcpValidationError } from '../../src'; +import { damlDocumentDataToNative } from '../../src/functions/OpenCapTable/document/getDocumentAsOcf'; +import { damlStakeholderDataToNative } from '../../src/functions/OpenCapTable/stakeholder/getStakeholderAsOcf'; const minimalStakeholder = { id: 'stakeholder-read-1', diff --git a/test/converters/financingConverters.test.ts b/test/converters/financingConverters.test.ts index e33c02ce..726c7a56 100644 --- a/test/converters/financingConverters.test.ts +++ b/test/converters/financingConverters.test.ts @@ -1,10 +1,8 @@ -import { - buildOcfCreateData, - convertToOcf, - damlFinancingToNative, - financingDataToDaml, - type OcfFinancing, -} from '../../src'; +import type { OcfFinancing } from '../../src'; +import { convertToOcf } from '../../src/functions/OpenCapTable/capTable/damlToOcf'; +import { buildOcfCreateData } from '../../src/functions/OpenCapTable/capTable/generatedBatchOperations'; +import { damlFinancingToNative } from '../../src/functions/OpenCapTable/financing/damlToOcf'; +import { financingDataToDaml } from '../../src/functions/OpenCapTable/financing/financingDataToDaml'; describe('Financing converters', () => { const financing: OcfFinancing = { diff --git a/test/declarations/damlReadDispatch.types.ts b/test/declarations/damlReadDispatch.types.ts index 2b92afef..05f6114c 100644 --- a/test/declarations/damlReadDispatch.types.ts +++ b/test/declarations/damlReadDispatch.types.ts @@ -1,13 +1,9 @@ /** Built-declaration contracts for generated DAML read dispatch. */ import type { Fairmint } from '@fairmint/open-captable-protocol-daml-js'; -import { - convertToOcf, - decodeDamlEntityData, - ENTITY_TEMPLATE_ID_MAP, - type DamlDataTypeFor, - type OcfStakeholder, -} from '../../dist'; +import { ENTITY_TEMPLATE_ID_MAP, type DamlDataTypeFor } from '../../dist/functions/OpenCapTable/capTable/batchTypes'; +import { convertToOcf, decodeDamlEntityData } from '../../dist/functions/OpenCapTable/capTable/damlToOcf'; +import type { OcfStakeholder } from '../../dist/types/native'; declare const stakeholderDamlData: DamlDataTypeFor<'stakeholder'>; declare const stockClassDamlData: DamlDataTypeFor<'stockClass'>; diff --git a/test/declarations/generatedOperationConstruction.types.ts b/test/declarations/generatedOperationConstruction.types.ts index 10577e2c..1e84c1eb 100644 --- a/test/declarations/generatedOperationConstruction.types.ts +++ b/test/declarations/generatedOperationConstruction.types.ts @@ -1,17 +1,17 @@ /** Built-declaration contracts for exact generated DAML batch operation variants. */ import type { Fairmint } from '@fairmint/open-captable-protocol-daml-js'; +import type { + OcfCreateDataFor, + OcfDeleteDataFor, + OcfEditDataFor, +} from '../../dist/functions/OpenCapTable/capTable/batchTypes'; import { buildOcfCreateData, buildOcfDeleteData, buildOcfEditData, - type OcfCreateDataFor, - type OcfDeleteDataFor, - type OcfEditDataFor, - type OcfFinancing, - type OcfIssuer, - type OcfStakeholder, -} from '../../dist'; +} from '../../dist/functions/OpenCapTable/capTable/generatedBatchOperations'; +import type { OcfFinancing, OcfIssuer, OcfStakeholder } from '../../dist/types/native'; declare const stakeholder: OcfStakeholder; declare const financing: OcfFinancing; diff --git a/test/declarations/normalization.types.ts b/test/declarations/normalization.types.ts index 55126897..3cf7264a 100644 --- a/test/declarations/normalization.types.ts +++ b/test/declarations/normalization.types.ts @@ -1,13 +1,13 @@ /** Compile-time contracts for canonicalization helpers in the built SDK declarations. */ +import type { OcfPlanSecurityIssuance } from '../../dist/types/native'; import { deepNormalizeNumericStrings, normalizeEntityType, normalizeObjectType, normalizeOcfData, - normalizeZeroUuidSentinels, - type OcfPlanSecurityIssuance, -} from '../../dist'; +} from '../../dist/utils/planSecurityAliases'; +import { normalizeZeroUuidSentinels } from '../../dist/utils/zeroUuidNormalization'; const normalizedNumericString: string = deepNormalizeNumericStrings('1.00' as const); void normalizedNumericString; diff --git a/test/declarations/publicApi.types.ts b/test/declarations/publicApi.types.ts index 884a6c71..0fbda385 100644 --- a/test/declarations/publicApi.types.ts +++ b/test/declarations/publicApi.types.ts @@ -1,12 +1,18 @@ /* eslint @typescript-eslint/no-redundant-type-constituents: off */ -/** Compile-time smoke tests for declarations exported by the built SDK. */ +/** Compile-time smoke tests for the curated declarations exported by the built SDK. */ import { - convertToDaml, - normalizeZeroUuidSentinels, - ZERO_UUID, - type CapTableBatch, + authorizeIssuer, + buildCreateIssuerCommand, + CapTableBatch, + OcpClient, + OcpValidationError, + withdrawAuthorization, + type AuthorizeIssuerResult, + type CapTableBatchExecuteResult, type CapTableBatchOperations, + type CreateIssuerParams, + type OcfContractId, type OcfCreateOperation, type OcfEntityDataMap, type OcfEntityType, @@ -16,49 +22,60 @@ import { type OcfStakeholder, type OcfStockAcceptance, type OcfStockClass, - type OcfVestingEvent, type OcfVestingStart, - type OcfWarrantAcceptance, + type SubmitAndWaitForTransactionTreeResponse, + type WithdrawAuthorizationResult, } from '../../dist'; -import { isOcfEntityType as isOcfEntityTypeFromUtils } from '../../dist/utils'; -import { - normalizeZeroUuidSentinels as normalizeSourceZeroUuidSentinels, - ZERO_UUID as SOURCE_ZERO_UUID, -} from '../../src'; type Assert = T; type IsExactly = [A] extends [B] ? ([B] extends [A] ? true : false) : false; +type RemovedRootValue = Extract< + keyof typeof import('../../dist'), + | 'convertToDaml' + | 'convertToOcf' + | 'decodeDamlEntityData' + | 'ENTITY_REGISTRY' + | 'ENTITY_TAG_MAP' + | 'getIssuerAsOcf' + | 'getStakeholderAsOcf' +>; type IntendedCanonicalOcfObject = OcfEntityDataMap[OcfEntityType]; -type SchemaSupportedPlanSecurityObjectType = - | 'TX_PLAN_SECURITY_ACCEPTANCE' - | 'TX_PLAN_SECURITY_CANCELLATION' - | 'TX_PLAN_SECURITY_EXERCISE' - | 'TX_PLAN_SECURITY_ISSUANCE' - | 'TX_PLAN_SECURITY_RELEASE' - | 'TX_PLAN_SECURITY_RETRACTION' - | 'TX_PLAN_SECURITY_TRANSFER'; const publishedOcfObjectIsExact: Assert> = true; -const publishedOcfObjectExcludesPlanSecurityWrappers: Assert< - IsExactly, never> +const generatedValuesAreNotRootExports: Assert> = true; +const authorizeIssuerResponseUsesPublicLedgerType: Assert< + IsExactly > = true; - -void publishedOcfObjectIsExact; -void publishedOcfObjectExcludesPlanSecurityWrappers; - -const publishedZeroUuidLiteral: '00000000-0000-0000-0000-000000000000' = ZERO_UUID; -const sourceZeroUuidLiteral: typeof ZERO_UUID = SOURCE_ZERO_UUID; -const normalizedPublishedZeroUuid: string | undefined = normalizeZeroUuidSentinels(ZERO_UUID); -const normalizedSourceZeroUuid: string | undefined = normalizeSourceZeroUuidSentinels(SOURCE_ZERO_UUID); -const zeroUuidNormalizerSourceAndDistMatch: Assert< - IsExactly +const withdrawAuthorizationResponseUsesPublicLedgerType: Assert< + IsExactly > = true; -void publishedZeroUuidLiteral; -void sourceZeroUuidLiteral; -void normalizedPublishedZeroUuid; -void normalizedSourceZeroUuid; -void zeroUuidNormalizerSourceAndDistMatch; +void publishedOcfObjectIsExact; +void generatedValuesAreNotRootExports; +void authorizeIssuerResponseUsesPublicLedgerType; +void withdrawAuthorizationResponseUsesPublicLedgerType; +void authorizeIssuer; +void buildCreateIssuerCommand; +void CapTableBatch; +void OcpClient; +void OcpValidationError; +void withdrawAuthorization; + +declare const createIssuerParams: CreateIssuerParams; +buildCreateIssuerCommand(createIssuerParams); + +// @ts-expect-error generated DAML wire unions are intentionally not root exports +type RemovedGeneratedWireType = import('../../dist').OcfCreateData; +declare const removedGeneratedWireType: RemovedGeneratedWireType; +void removedGeneratedWireType; + +declare const executeResult: CapTableBatchExecuteResult; +const returnedContractIds: readonly OcfContractId[] = executeResult.editedCids; +const issuerContractId: OcfContractId = { tag: 'CidIssuer', value: 'issuer-cid' }; +const financingContractId: OcfContractId = { tag: 'CidFinancing', value: 'financing-cid' }; +void returnedContractIds; +void issuerContractId; +void financingContractId; function verifyPublishedBatchApi( batch: CapTableBatch, @@ -67,9 +84,7 @@ function verifyPublishedBatchApi( financing: OcfFinancing, issuer: OcfIssuer, stockAcceptance: OcfStockAcceptance, - warrantAcceptance: OcfWarrantAcceptance, - vestingStart: OcfVestingStart, - vestingEvent: OcfVestingEvent + vestingStart: OcfVestingStart ): void { batch.create('stakeholder', stakeholder); batch.create('stockClass', stockClass); @@ -96,21 +111,12 @@ function verifyPublishedBatchApi( // @ts-expect-error a union-valued kind cannot bypass edit payload correlation batch.edit(widenedKind, stakeholder); - // @ts-expect-error a union-valued kind cannot bypass converter payload correlation - convertToDaml(widenedKind, stakeholder); - - // @ts-expect-error published types preserve stock vs warrant identity even with identical fields + // @ts-expect-error published types preserve entity identity even with structurally similar fields batch.create('warrantAcceptance', stockAcceptance); // @ts-expect-error published types preserve vesting start vs vesting event identity batch.create('vestingEvent', vestingStart); - // @ts-expect-error converter declarations cannot reinterpret a warrant acceptance as stock - convertToDaml('stockAcceptance', warrantAcceptance); - - // @ts-expect-error converter declarations cannot reinterpret a vesting event as vesting start - convertToDaml('vestingStart', vestingEvent); - // @ts-expect-error published entity declarations require object_type const missingObjectType: OcfStockAcceptance = { id: 'acceptance-1', @@ -119,17 +125,11 @@ function verifyPublishedBatchApi( }; void missingObjectType; - const wrongObjectType: OcfStockAcceptance = { - // @ts-expect-error published literal rejects another entity discriminator - object_type: 'TX_WARRANT_ACCEPTANCE', - id: 'acceptance-2', - date: '2026-01-01', - security_id: 'security-2', - }; - void wrongObjectType; - const operations: CapTableBatchOperations = { - creates: [{ type: 'stakeholder', data: stakeholder }], + creates: [ + { type: 'stakeholder', data: stakeholder }, + { type: 'financing', data: financing }, + ], edits: [{ type: 'issuer', data: issuer }], deletes: [{ type: 'stockClass', id: stockClass.id }], }; @@ -143,12 +143,4 @@ function verifyPublishedBatchApi( void invalidIdentityOperation; } -function verifyPublishedUtilsApi(candidateEntityType: string): void { - if (isOcfEntityTypeFromUtils(candidateEntityType)) { - const narrowedEntityType: OcfEntityType = candidateEntityType; - void narrowedEntityType; - } -} - void verifyPublishedBatchApi; -void verifyPublishedUtilsApi; diff --git a/test/integration/utils/setupTestData.ts b/test/integration/utils/setupTestData.ts index 9ffe1873..aa1e4529 100644 --- a/test/integration/utils/setupTestData.ts +++ b/test/integration/utils/setupTestData.ts @@ -1007,7 +1007,7 @@ export function createTestConvertibleIssuanceData( } /** Helper to extract a contract ID from a createdCids array by type tag. */ -function extractCreatedCid(createdCids: Array>, tagPrefix: string): string { +function extractCreatedCid(createdCids: ReadonlyArray>, tagPrefix: string): string { for (const cid of createdCids) { const { tag, value: taggedValue } = cid; if (typeof tag === 'string' && tag.startsWith(tagPrefix) && typeof taggedValue === 'string') { diff --git a/test/publicApi/rootExports.test.ts b/test/publicApi/rootExports.test.ts new file mode 100644 index 00000000..4ae065d2 --- /dev/null +++ b/test/publicApi/rootExports.test.ts @@ -0,0 +1,71 @@ +import packageJson from '../../package.json'; +import * as sdk from '../../src'; + +describe('package root exports', () => { + it('exports only the SDK root and package metadata subpaths', () => { + expect(packageJson.exports).toEqual({ + '.': { + types: './dist/index.d.ts', + import: './dist/index.js', + require: './dist/index.js', + default: './dist/index.js', + }, + './package.json': './package.json', + }); + }); + + it('exposes only the curated high-level runtime surface', () => { + expect(Object.keys(sdk).sort()).toEqual([ + 'CUSTOM_PRESET', + 'CapTableBatch', + 'DEVNET_PRESET', + 'ENVIRONMENT_PRESETS', + 'LOCALNET_PRESET', + 'MAINNET_PRESET', + 'OCF_OBJECT_TYPE_TO_ENTITY_TYPE', + 'OcpClient', + 'OcpContextManager', + 'OcpContractError', + 'OcpError', + 'OcpErrorCodes', + 'OcpNetworkError', + 'OcpParseError', + 'OcpValidationError', + 'SCRATCHNET_PRESET', + 'TESTNET_PRESET', + 'applyCommandContext', + 'authorizeIssuer', + 'buildCreateIssuerCommand', + 'buildUpdateCapTableCommand', + 'createSharedSecretTokenGenerator', + 'detectEnvironment', + 'isContractId', + 'isOcfCreatableEntityType', + 'isOcfDeletableEntityType', + 'isOcfEditableEntityType', + 'isOcfEntityType', + 'isOcfId', + 'isOcfReadableObjectType', + 'isPartyId', + 'isSecurityId', + 'loadEnvironmentConfigFromEnv', + 'mapOcfObjectTypeToEntityType', + 'mergeCommandContext', + 'resolveEnvironmentConfig', + 'submitObservedTransactionTree', + 'toCantonConfig', + 'toCantonNetwork', + 'toContractId', + 'toOcfId', + 'toPartyId', + 'toResolvedCantonConfig', + 'toSecurityId', + 'unsafeToContractId', + 'unsafeToOcfId', + 'unsafeToPartyId', + 'unsafeToSecurityId', + 'validateConfig', + 'withdrawAuthorization', + ]); + }); +}); diff --git a/test/types/capTableBatch.types.ts b/test/types/capTableBatch.types.ts index 002ceed7..21f195d4 100644 --- a/test/types/capTableBatch.types.ts +++ b/test/types/capTableBatch.types.ts @@ -9,7 +9,6 @@ import { type CapTableBatch, type CapTableBatchOperations, - convertToDaml, type OcfCreateOperation, type OcfEntityDataMap, type OcfEntityType, @@ -23,6 +22,7 @@ import { type OcfVestingStart, type OcfWarrantAcceptance, } from '../../src'; +import { convertToDaml } from '../../src/functions/OpenCapTable/capTable/ocfToDaml'; type Assert = T; type IsExactly = [A] extends [B] ? ([B] extends [A] ? true : false) : false; diff --git a/test/types/damlReadDispatch.types.ts b/test/types/damlReadDispatch.types.ts index 2b24a664..99391305 100644 --- a/test/types/damlReadDispatch.types.ts +++ b/test/types/damlReadDispatch.types.ts @@ -6,8 +6,8 @@ import { decodeDamlEntityData, ENTITY_TEMPLATE_ID_MAP, type DamlDataTypeFor, - type OcfStakeholder, -} from '../../src'; +} from '../../src/functions/OpenCapTable/capTable'; +import type { OcfStakeholder } from '../../src/types/native'; declare const stakeholderDamlData: DamlDataTypeFor<'stakeholder'>; declare const stockClassDamlData: DamlDataTypeFor<'stockClass'>; diff --git a/test/types/generatedOperationConstruction.types.ts b/test/types/generatedOperationConstruction.types.ts index 5577583d..43ec4bc6 100644 --- a/test/types/generatedOperationConstruction.types.ts +++ b/test/types/generatedOperationConstruction.types.ts @@ -1,21 +1,20 @@ /** Compile-time contracts for cast-free generated DAML operation builders. */ import type { Fairmint } from '@fairmint/open-captable-protocol-daml-js'; +import type { + OcfCreateData, + OcfCreateDataFor, + OcfDeleteData, + OcfDeleteDataFor, + OcfEditData, + OcfEditDataFor, +} from '../../src/functions/OpenCapTable/capTable/batchTypes'; import { buildOcfCreateData, buildOcfDeleteData, buildOcfEditData, - type OcfCreateData, - type OcfCreateDataFor, - type OcfDeleteData, - type OcfDeleteDataFor, - type OcfEditData, - type OcfEditDataFor, - type OcfFinancing, - type OcfIssuer, - type OcfStakeholder, - type OcfStockClass, -} from '../../src'; +} from '../../src/functions/OpenCapTable/capTable/generatedBatchOperations'; +import type { OcfFinancing, OcfIssuer, OcfStakeholder, OcfStockClass } from '../../src/types/native'; function verifyGeneratedOperationBuilders( stakeholder: OcfStakeholder, diff --git a/test/types/normalization.types.ts b/test/types/normalization.types.ts index 13d06c01..58764b46 100644 --- a/test/types/normalization.types.ts +++ b/test/types/normalization.types.ts @@ -1,13 +1,13 @@ /** Compile-time contracts for canonicalization helpers exported from source. */ +import type { OcfPlanSecurityIssuance } from '../../src/types/native'; import { deepNormalizeNumericStrings, normalizeEntityType, normalizeObjectType, normalizeOcfData, - normalizeZeroUuidSentinels, - type OcfPlanSecurityIssuance, -} from '../../src'; +} from '../../src/utils/planSecurityAliases'; +import { normalizeZeroUuidSentinels } from '../../src/utils/zeroUuidNormalization'; const normalizedNumericString: string = deepNormalizeNumericStrings('1.00' as const); void normalizedNumericString;