ministry-platform-provider
Version:
TypeScript client library for Ministry Platform API integration
798 lines (633 loc) • 20.1 kB
Markdown
# MPHelper - Main Public API
## Overview
The `MPHelper` class is the primary public interface for all Ministry Platform operations. It provides a simplified, type-safe API that abstracts the complexity of the underlying provider system while maintaining full access to Ministry Platform functionality.
## Class Structure
```typescript
export class MPHelper {
private provider: ministryPlatformProvider;
constructor() {
this.provider = ministryPlatformProvider.getInstance();
}
}
```
## Design Philosophy
The `MPHelper` follows these principles:
1. **Simplified API**: Complex parameter objects are flattened into simple method parameters
2. **Type Safety**: All methods are generic and strongly typed
3. **Consistent Interface**: Similar operations follow the same patterns
4. **Developer Experience**: Comprehensive logging and error handling
5. **Abstraction**: Hides provider complexity while maintaining full functionality
## Table Operations
### getTableRecords
```typescript
public async getTableRecords<T>(params: {
table: string,
select?: string,
filter?: string,
orderBy?: string,
groupBy?: string,
having?: string,
top?: number,
skip?: number,
distinct?: boolean,
userId?: number,
globalFilterId?: number
}): Promise<T[]>
```
**Purpose**: Retrieves records from any Ministry Platform table with comprehensive query capabilities
**Parameters**:
- `table`: Table name (e.g., 'Contacts', 'Contact_Log')
- `select`: Column list (e.g., 'Contact_ID,Display_Name,Email_Address')
- `filter`: WHERE clause (e.g., 'Contact_Status_ID=1')
- `orderBy`: ORDER BY clause (e.g., 'Last_Name,First_Name')
- `groupBy`: GROUP BY clause
- `having`: HAVING clause for grouped results
- `top`: Maximum number of records to return
- `skip`: Number of records to skip (for pagination)
- `distinct`: Remove duplicate records
- `userId`: User context for security
- `globalFilterId`: Apply global filter
**Returns**: Array of records typed as `T`
**Example Usage**:
```typescript
// Get active contacts with basic info
const contacts = await mp.getTableRecords<Contact>({
table: 'Contacts',
select: 'Contact_ID,Display_Name,Email_Address,Mobile_Phone',
filter: 'Contact_Status_ID=1',
orderBy: 'Last_Name,First_Name',
top: 100
});
// Get contact logs for a specific person
const logs = await mp.getTableRecords<ContactLog>({
table: 'Contact_Log',
filter: 'Contact_ID=12345',
orderBy: 'Contact_Date DESC',
top: 50
});
```
**Query Parameter Mapping**:
```typescript
// Internal mapping to Ministry Platform format
const queryParams: TableQueryParams = {
$select: params.select,
$filter: params.filter,
$orderby: params.orderBy,
$groupby: params.groupBy,
$having: params.having,
$top: params.top,
$skip: params.skip,
$distinct: params.distinct,
$userId: params.userId,
$globalFilterId: params.globalFilterId,
};
```
### createTableRecords
```typescript
public async createTableRecords<T extends TableRecord = TableRecord>(
table: string,
records: T[],
params?: Pick<TableQueryParams, '$select' | '$userId'>
): Promise<T[]>
```
**Purpose**: Creates new records in the specified table
**Parameters**:
- `table`: Table name where records will be created
- `records`: Array of record objects to create
- `params`: Optional parameters for selection and user context
**Returns**: Array of created records with generated IDs
**Example Usage**:
```typescript
// Create a new contact
const newContacts = await mp.createTableRecords('Contacts', [{
First_Name: 'John',
Last_Name: 'Doe',
Email_Address: 'john.doe@example.com',
Contact_Status_ID: 1
}]);
// Create multiple contact logs
const contactLogs = await mp.createTableRecords('Contact_Log', [
{
Contact_ID: 12345,
Contact_Date: new Date().toISOString(),
Made_By: 1,
Notes: 'Initial contact made via phone',
Contact_Log_Type_ID: 1
},
{
Contact_ID: 12346,
Contact_Date: new Date().toISOString(),
Made_By: 1,
Notes: 'Follow-up email sent',
Contact_Log_Type_ID: 2
}
]);
```
**Enhanced Logging**:
```typescript
console.log('MPHELPER: createTableRecords called');
console.log('MPHELPER: table:', table);
console.log('MPHELPER: records:', JSON.stringify(records, null, 2));
console.log('MPHELPER: params:', JSON.stringify(params, null, 2));
```
### updateTableRecords
```typescript
public async updateTableRecords<T extends TableRecord = TableRecord>(
table: string,
records: T[],
params?: Pick<TableQueryParams, '$select' | '$userId' | '$allowCreate'>
): Promise<T[]>
```
**Purpose**: Updates existing records in the specified table
**Parameters**:
- `table`: Table name where records will be updated
- `records`: Array of record objects with IDs to update
- `params`: Optional parameters including create permission
**Returns**: Array of updated records
**Example Usage**:
```typescript
// Update contact information
const updatedContacts = await mp.updateTableRecords('Contacts', [{
Contact_ID: 12345,
First_Name: 'John',
Last_Name: 'Smith', // Changed last name
Email_Address: 'john.smith@example.com',
Mobile_Phone: '555-0123'
}]);
// Update with create permission (upsert)
const upsertedRecords = await mp.updateTableRecords('Contact_Log', [
{
Contact_Log_ID: 67890,
Notes: 'Updated notes'
}
], {
$allowCreate: true, // Will create if doesn't exist
$userId: 1
});
```
### deleteTableRecords
```typescript
public async deleteTableRecords<T extends TableRecord = TableRecord>(
table: string,
ids: number[],
params?: Pick<TableQueryParams, '$select' | '$userId'>
): Promise<T[]>
```
**Purpose**: Deletes multiple records from the specified table
**Parameters**:
- `table`: Table name where records will be deleted
- `ids`: Array of record IDs to delete
- `params`: Optional parameters for selection and user context
**Returns**: Array of deleted records
**Example Usage**:
```typescript
// Delete specific contact logs
const deletedLogs = await mp.deleteTableRecords('Contact_Log', [67890, 67891], {
$userId: 1
});
// Delete inactive contacts
const inactiveContacts = await mp.getTableRecords<Contact>({
table: 'Contacts',
filter: 'Contact_Status_ID=3',
select: 'Contact_ID'
});
const contactIds = inactiveContacts.map(c => c.Contact_ID);
const deletedContacts = await mp.deleteTableRecords('Contacts', contactIds);
```
## Domain Operations
### getDomainInfo
```typescript
public async getDomainInfo(): Promise<DomainInfo>
```
**Purpose**: Retrieves basic information about the current Ministry Platform domain
**Returns**: Domain configuration object
**Example Usage**:
```typescript
const domainInfo = await mp.getDomainInfo();
console.log('Domain:', domainInfo.DisplayName);
console.log('Time Zone:', domainInfo.TimeZoneName);
console.log('Culture:', domainInfo.CultureName);
```
### getGlobalFilters
```typescript
public async getGlobalFilters(params?: GlobalFilterParams): Promise<GlobalFilterItem[]>
```
**Purpose**: Retrieves available global filters for the domain
**Parameters**:
- `params`: Optional parameters for filter request
**Returns**: Array of global filter items
**Example Usage**:
```typescript
const filters = await mp.getGlobalFilters();
filters.forEach(filter => {
console.log(`Filter ${filter.Key}: ${filter.Value}`);
});
```
## Metadata Operations
### refreshMetadata
```typescript
public async refreshMetadata(): Promise<void>
```
**Purpose**: Triggers metadata cache refresh on all servers
**Example Usage**:
```typescript
// Refresh metadata after schema changes
await mp.refreshMetadata();
console.log('Metadata cache refreshed');
```
### getTables
```typescript
public async getTables(search?: string): Promise<TableMetadata[]>
```
**Purpose**: Retrieves list of available tables with metadata
**Parameters**:
- `search`: Optional search term to filter tables
**Returns**: Array of table metadata objects
**Example Usage**:
```typescript
// Get all tables
const allTables = await mp.getTables();
// Search for contact-related tables
const contactTables = await mp.getTables('contact');
contactTables.forEach(table => {
console.log(`Table: ${table.Name} - ${table.AccessLevel}`);
});
```
## Procedure Operations
### getProcedures
```typescript
public async getProcedures(search?: string): Promise<ProcedureInfo[]>
```
**Purpose**: Retrieves list of available stored procedures
**Parameters**:
- `search`: Optional search term to filter procedures
**Returns**: Array of procedure metadata objects
**Example Usage**:
```typescript
// Get all procedures
const procedures = await mp.getProcedures();
// Search for API procedures
const apiProcedures = await mp.getProcedures('api_');
apiProcedures.forEach(proc => {
console.log(`Procedure: ${proc.Name}`);
proc.Parameters.forEach(param => {
console.log(` - ${param.Name}: ${param.DataType}`);
});
});
```
### executeProcedure
```typescript
public async executeProcedure(
procedure: string,
params?: QueryParams
): Promise<unknown[][]>
```
**Purpose**: Executes stored procedure with query string parameters
**Parameters**:
- `procedure`: Stored procedure name
- `params`: Query parameters object
**Returns**: Array of result sets
**Example Usage**:
```typescript
// Execute procedure with parameters
const results = await mp.executeProcedure('api_GetContactsByHousehold', {
HouseholdId: 123,
IncludeInactive: false
});
// Process first result set
const contacts = results[0] as Contact[];
```
### executeProcedureWithBody
```typescript
public async executeProcedureWithBody(
procedure: string,
parameters: Record<string, unknown>
): Promise<unknown[][]>
```
**Purpose**: Executes stored procedure with request body parameters
**Parameters**:
- `procedure`: Stored procedure name
- `parameters`: Request body parameters object
**Returns**: Array of result sets
**Example Usage**:
```typescript
// Execute complex procedure with body
const results = await mp.executeProcedureWithBody('api_ComplexSearch', {
SearchCriteria: {
FirstName: 'John',
LastName: 'Doe',
Email: 'john@example.com'
},
Options: {
IncludeInactive: false,
MaxResults: 100
}
});
```
## Communication Operations
### createCommunication
```typescript
public async createCommunication(
communication: CommunicationInfo,
attachments?: File[]
): Promise<Communication>
```
**Purpose**: Creates and schedules a new communication
**Parameters**:
- `communication`: Communication configuration object
- `attachments`: Optional file attachments
**Returns**: Created communication object
**Example Usage**:
```typescript
// Create email communication
const communication = await mp.createCommunication({
AuthorUserId: 1,
FromContactId: 1,
ReplyToContactId: 1,
Subject: 'Weekly Newsletter',
Body: '<h1>This Week at Church</h1><p>Join us for...</p>',
CommunicationType: 'Email',
Contacts: [12345, 67890, 13579],
IsBulkEmail: true,
SendToContactParents: false,
StartDate: new Date().toISOString()
});
```
### sendMessage
```typescript
public async sendMessage(
message: MessageInfo,
attachments?: File[]
): Promise<Communication>
```
**Purpose**: Sends a direct message immediately
**Parameters**:
- `message`: Message configuration object
- `attachments`: Optional file attachments
**Returns**: Created communication object
**Example Usage**:
```typescript
// Send direct email
const message = await mp.sendMessage({
FromAddress: { DisplayName: 'Pastor John', Address: 'pastor@church.com' },
ToAddresses: [{ DisplayName: 'John Doe', Address: 'john@example.com' }],
Subject: 'Thank you for visiting',
Body: 'It was great to meet you last Sunday!'
});
```
## File Operations
### getFilesByRecord
```typescript
public async getFilesByRecord(params: {
table: string,
recordId: number,
defaultOnly?: boolean
}): Promise<FileDescription[]>
```
**Purpose**: Retrieves files attached to a specific record
**Parameters**:
- `table`: Table name
- `recordId`: Record ID
- `defaultOnly`: Return only default files
**Returns**: Array of file descriptions
**Example Usage**:
```typescript
// Get all files for a contact
const files = await mp.getFilesByRecord({
table: 'Contacts',
recordId: 12345
});
// Get only default image
const defaultImage = await mp.getFilesByRecord({
table: 'Contacts',
recordId: 12345,
defaultOnly: true
});
```
### uploadFiles
```typescript
public async uploadFiles(params: {
table: string,
recordId: number,
files: File[],
uploadParams?: FileUploadParams
}): Promise<FileDescription[]>
```
**Purpose**: Uploads multiple files to a record
**Parameters**:
- `table`: Table name
- `recordId`: Record ID
- `files`: Array of File objects
- `uploadParams`: Optional upload configuration
**Returns**: Array of uploaded file descriptions
**Example Usage**:
```typescript
// Upload profile photos
const uploadedFiles = await mp.uploadFiles({
table: 'Contacts',
recordId: 12345,
files: [profilePhoto, backgroundCheck],
uploadParams: {
description: 'Profile documentation',
isDefaultImage: true,
userId: 1
}
});
```
### updateFile
```typescript
public async updateFile(params: {
fileId: number,
file?: File,
updateParams?: FileUpdateParams
}): Promise<FileDescription>
```
**Purpose**: Updates file content and/or metadata
**Parameters**:
- `fileId`: File ID to update
- `file`: Optional new file content
- `updateParams`: Optional update configuration
**Returns**: Updated file description
**Example Usage**:
```typescript
// Update file metadata
const updatedFile = await mp.updateFile({
fileId: 67890,
updateParams: {
description: 'Updated profile photo',
isDefaultImage: true
}
});
// Replace file content
const replacedFile = await mp.updateFile({
fileId: 67890,
file: newProfilePhoto,
updateParams: {
fileName: 'new-profile.jpg'
}
});
```
### deleteFile
```typescript
public async deleteFile(params: {
fileId: number,
userId?: number
}): Promise<void>
```
**Purpose**: Deletes a file
**Parameters**:
- `fileId`: File ID to delete
- `userId`: Optional user ID for auditing
**Example Usage**:
```typescript
// Delete file
await mp.deleteFile({
fileId: 67890,
userId: 1
});
```
### getFileContentByUniqueId
```typescript
public async getFileContentByUniqueId(params: {
uniqueFileId: string,
thumbnail?: boolean
}): Promise<Blob>
```
**Purpose**: Downloads file content by unique ID (no authentication required)
**Parameters**:
- `uniqueFileId`: Globally unique file identifier
- `thumbnail`: Return thumbnail version
**Returns**: File content as Blob
**Example Usage**:
```typescript
// Download file content
const fileBlob = await mp.getFileContentByUniqueId({
uniqueFileId: 'abc123-def456-ghi789'
});
// Create download link
const url = URL.createObjectURL(fileBlob);
const link = document.createElement('a');
link.href = url;
link.download = 'downloaded-file.pdf';
link.click();
```
### getFileMetadata
```typescript
public async getFileMetadata(params: {
fileId: number
}): Promise<FileDescription>
```
**Purpose**: Retrieves file metadata by database ID
**Parameters**:
- `fileId`: Database file ID
**Returns**: File description object
**Example Usage**:
```typescript
// Get file metadata
const metadata = await mp.getFileMetadata({
fileId: 67890
});
console.log(`File: ${metadata.FileName}`);
console.log(`Size: ${metadata.FileSize} bytes`);
console.log(`Type: ${metadata.FileExtension}`);
```
### getFileMetadataByUniqueId
```typescript
public async getFileMetadataByUniqueId(params: {
uniqueFileId: string
}): Promise<FileDescription>
```
**Purpose**: Retrieves file metadata by unique ID
**Parameters**:
- `uniqueFileId`: Globally unique file identifier
**Returns**: File description object
**Example Usage**:
```typescript
// Get file metadata by unique ID
const metadata = await mp.getFileMetadataByUniqueId({
uniqueFileId: 'abc123-def456-ghi789'
});
```
## Error Handling
The `MPHelper` includes comprehensive error handling with detailed logging:
```typescript
try {
console.log('MPHELPER: createTableRecords called');
console.log('MPHELPER: table:', table);
console.log('MPHELPER: records:', JSON.stringify(records, null, 2));
const result = await this.provider.createTableRecords<T>(table, records, params);
console.log('MPHELPER: Operation completed successfully');
return result;
} catch (error) {
console.error('MPHELPER: Error in createTableRecords:');
console.error('MPHELPER: Error type:', error?.constructor?.name);
console.error('MPHELPER: Error message:', (error as Error)?.message);
console.error('MPHELPER: Error stack:', (error as Error)?.stack);
throw error; // Re-throw the original error
}
```
## Usage Patterns
### Type-Safe Operations
```typescript
// Define your types
interface MyContact {
Contact_ID: number;
Display_Name: string;
Email_Address: string;
Mobile_Phone: string;
}
// Use with type safety
const mp = new MPHelper();
const contacts = await mp.getTableRecords<MyContact>({
table: 'Contacts',
select: 'Contact_ID,Display_Name,Email_Address,Mobile_Phone',
filter: 'Contact_Status_ID=1'
});
// TypeScript knows the shape of contacts
contacts.forEach(contact => {
console.log(`${contact.Display_Name}: ${contact.Email_Address}`);
});
```
### Batch Operations
```typescript
// Create multiple records efficiently
const newContacts = await mp.createTableRecords('Contacts', [
{ First_Name: 'John', Last_Name: 'Doe', Email_Address: 'john@example.com' },
{ First_Name: 'Jane', Last_Name: 'Smith', Email_Address: 'jane@example.com' },
{ First_Name: 'Bob', Last_Name: 'Johnson', Email_Address: 'bob@example.com' }
]);
// Update multiple records
const updatedContacts = await mp.updateTableRecords('Contacts',
newContacts.map(contact => ({
...contact,
Contact_Status_ID: 1
}))
);
```
### Pagination
```typescript
// Implement pagination
const pageSize = 50;
let page = 0;
let allContacts: Contact[] = [];
while (true) {
const contacts = await mp.getTableRecords<Contact>({
table: 'Contacts',
filter: 'Contact_Status_ID=1',
orderBy: 'Contact_ID',
top: pageSize,
skip: page * pageSize
});
if (contacts.length === 0) break;
allContacts = allContacts.concat(contacts);
page++;
}
```
## Best Practices
1. **Use Type Safety**: Always specify generic types for better IDE support
2. **Handle Errors**: Wrap operations in try-catch blocks
3. **Batch Operations**: Use arrays for bulk operations when possible
4. **Logging**: Enable console logging for debugging
5. **Parameter Validation**: Validate inputs before making API calls
6. **Resource Management**: Dispose of Blob objects after use
The `MPHelper` class provides a powerful, type-safe interface for all Ministry Platform operations while maintaining simplicity and developer productivity.