
Security News
Vite+ Joins the Push to Consolidate JavaScript Tooling
Evan You announces Vite+, a commercial, Rust-powered toolchain built on the Vite ecosystem to unify JavaScript development and fund open source.
@skedulo/pulse-solutions-framework
Advanced tools
A comprehensive TypeScript service library for GraphQL-based data operations with support for complex object relationships, batch processing, and advanced querying capabilities.
npm install @skedulo/pulse-solutions-framework
import { createObjectDefinition, MappingType, ObjectDefinition } from '@skedulo/pulse-solutions-framework'
export const JobsDefinition: ObjectDefinition = createObjectDefinition({
objectName: 'Jobs',
fieldConfigs: [
{ fieldName: 'UID' }, //mappingType: MappingType.string as default
{ fieldName: 'Name', readonly: true },
{ fieldName: 'Account', mappingType: MappingType.reference, referenceObject: 'Accounts' },
{
fieldName: 'JobAllocations',
mappingType: MappingType.relatedList,
referenceObject: 'JobAllocations',
parentField: 'Job'
},
{ fieldName: 'JobAllocationTimeSource', mappingType: MappingType.boolean },
{ fieldName: 'Quantity', mappingType: MappingType.number },
{ fieldName: 'Start', mappingType: MappingType.datetime }
]
})
Property | Type | Default | Description |
---|---|---|---|
objectName | string | Required | Name of the primary object |
objectDefinitions | Record<string, ObjectDefinition> | Required | Collection of object definitions |
import { createBaseService, ServiceSetting } from '@skedulo/pulse-solutions-framework'
import * as objectDefinitions from './my-object-definitions'
interface Jobs extends BaseModel {
AccountId: string
JobStatus: string
// ... other fields
}
const settings: ServiceSetting = {
objectName: 'Jobs',
objectDefinitions: objectDefinitions
}
const jobsService = createBaseService<Jobs>(settings)
import { QueryModel, Operator } from '@skedulo/pulse-solutions-framework'
// Simple query
const queryModels: QueryModel[] = [
{
conditions: [
['AccountId', Operator.EQUAL, 'account-uid'],
['JobStatus', Operator.NOT_IN, ['Cancelled']]
],
orderBy: 'CreatedDate DESC'
}
]
const result = await jobsService.query(queryModels)
// Insert records
const recordsToInsert: Jobs[] = [
{ AccountId: 'account-uid-1', JobStatus: 'Queued' },
{ AccountId: 'account-uid-2', JobStatus: 'Queued' }
]
// Update records
const recordsToUpdate: Jobs[] = [{ UID: 'existing-id', AccountId: 'account-uid', JobStatus: 'Pending Dispatch' }]
// Delete records
const recordsToDelete: Jobs[] = [{ UID: 'existing-id', _IsDeleted: true }]
await jobsService.save([...recordsToInsert, ...recordsToUpdate, ...recordsToDelete])
const complexQuery: QueryModel[] = [
{
conditions: [
['Start', Operator.GREATER_OR_EQUAL, '2025-01-01T00:00:00.000Z'],
['End', Operator.LESS_OR_EQUAL, '2025-12-31T00:00:00.000Z'],
['JobStatus', Operator.IN, ['Pending Allocation', 'Pending Dispatch']]
],
customLogic: '(1 AND 2) AND 3', // Reference conditions by index
orderBy: 'CreatedDate DESC'
}
]
const periodQuery: QueryModel[] = [
{
conditions: [[['Start', 'End'], Operator.PERIOD, ['2025-01-01T00:00:00.000Z', '2025-12-31T00:00:00.000Z']]]
}
]
const customQuery: QueryModel[] = [
{
conditions: [['my_custom_query', Operator.CUSTOM, 'ContactId != null AND ContactId != ""']]
}
]
const jobsWithJobAllocationsQuery: QueryModel[] = [
{
conditions: [['JobStatus', Operator.NOT_IN, ['Cancelled']]],
orderBy: 'CreatedDate DESC'
},
{
relatedList: 'JobAllocations',
conditions: [['Status', Operator.NOT_IN, ['Deleted', 'Declined']]]
}
]
const options: QueryOptions = {
readOnly: true // Optimize for read-only operations
}
const result = await jobsService.query(queryModels, options)
const options: SaveOptions = {
sourceRecords: [], // Original records for change detection
bulkOperation: true, // Enable bulk operation mode
suppressChangeEvents: true // Suppress change events
}
const result = await jobsService.save(records, options)
Operator | GraphQL Equivalent | Description |
---|---|---|
EQUAL | == | Equality comparison |
NOT_EQUAL | != | Inequality comparison |
IN | IN | Value in array |
NOT_IN | NOTIN | Value not in array |
GREATER | > | Greater than |
GREATER_OR_EQUAL | >= | Greater than or equal |
LESS | < | Less than |
LESS_OR_EQUAL | <= | Less than or equal |
INCLUDES | INCLUDES | String value included |
EXCLUDES | EXCLUDES | String value excluded |
INCLUDES_ALL | Custom | All string values included |
INCLUDES_ANY | Custom | Any string included |
EXCLUDES_ALL | Custom | All string values excluded |
PERIOD | Custom | Date range overlap |
PERIOD_INCLUDE_NULL | Custom | Date range overlap including records have period fields value is null |
CUSTOM | Custom | Raw GraphQL condition |
interface DataService<T extends BaseModel> {
newQueryBuilder(queryModels: QueryModel[]): GraphQLQueryBuilder
query(queryModels: QueryModel[], options?: QueryOptions): Promise<GetListResponse<T>>
save(objects: T[], options?: SaveOptions<T>): Promise<SaveResult[]>
}
interface QueryModel {
conditions?: [string | string[], Operator, any][]
customLogic?: string
orderBy?: string
limit?: number
relatedList?: string
overriddenFields?: string
}
sourceRecords
for optimal update performancebulkOperation: true
for large datasets. This helps optimize large-scale mutationssuppressChangeEvents: true
for large datasets. This disables change history tracking, helping to minimize delays.The recurring-service
module provides utilities for generating recurring dates based on customizable patterns. It supports daily, weekly, monthly, and yearly recurrence rules with advanced options like skipping specific dates, handling timezones, and limiting occurrences.
luxon
.The following generators are available for creating recurring dates:
Each generator implements the RecurringGenerator
interface and provides the following methods:
generateDates(pattern: RecurringPattern): DateTime[]
getRepeatMode(): RepeatMode
The module defines the following key types in types.ts:
RecurringPattern
: Configuration object for defining recurrence rules.RecurringGenerator
: Interface for generators.import { dailyRecurringGenerator } from './generators/daily-generator'
import { RepeatMode, EndMode, RecurringPattern } from './types'
const generator = dailyRecurringGenerator()
const pattern: RecurringPattern = {
startDate: '2024-01-01',
timezoneSidId: 'UTC',
repeatMode: RepeatMode.Daily,
every: 1,
endMode: EndMode.After,
endAfterNumberOccurrences: 5
}
const dates = generator.generateDates(pattern)
console.log(dates.map(date => date.toISODate()))
// Output: ['2024-01-01', '2024-01-02', '2024-01-03', '2024-01-04', '2024-01-05']
import { monthlyRecurringGenerator } from './generators/monthly-generator'
import { RepeatMode, EndMode, RecurringPattern } from './types'
const generator = monthlyRecurringGenerator()
const pattern: RecurringPattern = {
startDate: '2024-01-15',
timezoneSidId: 'UTC',
repeatMode: RepeatMode.Monthly,
every: 1,
endMode: EndMode.After,
endAfterNumberOccurrences: 3,
repeatOnDayOfMonth: 15,
skippedDates: ['2024-02-15']
}
const dates = generator.generateDates(pattern)
console.log(dates.map(date => date.toISODate()))
// Output: ['2024-01-15', '2024-03-15', '2024-04-15']
import { yearlyRecurringGenerator } from './generators/yearly-generator'
import { RepeatMode, EndMode, RecurringPattern } from './types'
const generator = yearlyRecurringGenerator()
const pattern: RecurringPattern = {
startDate: '2024-02-29',
timezoneSidId: 'UTC',
repeatMode: RepeatMode.Yearly,
every: 1,
endMode: EndMode.After,
endAfterNumberOccurrences: 2
}
const dates = generator.generateDates(pattern)
console.log(dates.map(date => date.toISODate()))
// Output: ['2024-02-29', '2025-02-28']
Unit tests for the recurring service are located in the unit-test/recurring-service
folder. Each generator and helper function is thoroughly tested with various edge cases.
The template-service
module provides a flexible and extensible system for applying configuration templates to objects. It supports dynamic field processing, custom field handlers, and object-specific template engines with caching for optimal performance.
import { createTemplateService } from './base-service'
const templateService = createTemplateService()
// Register custom field handlers
templateService.registerObjectFieldHandlers('MyObject', {
customField: (fieldValue, targetObject) => {
targetObject.customField = fieldValue.toUpperCase()
}
})
// Create object-specific template engine
const engine = templateService.createObjectTemplateEngine('MyObject')
// Apply template to object
const myObject = { name: 'test' }
const result = await engine.applyObjectTemplate(myObject, 'myTemplate')
import { createJobTemplateService } from './job-template-service'
const jobTemplateService = createJobTemplateService()
// Generate job from template
const job = {
Name: 'Installation Job',
Start: '2024-01-01T09:00:00.000Z',
Timezone: 'UTC'
}
const jobWithTemplate = await jobTemplateService.generateJobByTemplate(
job,
'InstallationTemplate',
false // multipleRequirementEnabled
)
Automatically calculates job end time based on duration:
// Template field: Duration = 120 (minutes)
// Input job with Start time
const job = {
Start: '2024-01-01T09:00:00.000Z',
Timezone: 'UTC'
}
// Result will include calculated End time
// End: '2024-01-01T11:00:00.000Z'
Automatically generates job tasks from template:
// Template field: JobTasks
const templateTasks = [
{ Name: 'Setup', Description: 'Initial setup' },
{ Name: 'Installation', Description: 'Main installation' }
]
// Generated JobTasks with sequence numbers
// JobTasks: [
// { Name: 'Setup', Description: 'Initial setup', Seq: 1 },
// { Name: 'Installation', Description: 'Main installation', Seq: 2 }
// ]
Supports both single and multiple resource requirements:
const job = {}
const result = await jobTemplateService.generateJobByTemplate(
job,
'template',
false // single requirement mode
)
// Sets job.Quantity and job.JobTags
const job = {}
const result = await jobTemplateService.generateJobByTemplate(
job,
'template-name',
true // multiple requirements mode
)
// Sets job.ResourceRequirements array
Automatically handles tag weighting and required flags:
// Template requirement with tags
const requirement = {
qty: 2,
tags: [
{ name: 'Electrician', tagId: 'tag-1', weighting: JobTagWeighting.Required },
{ name: 'Senior', tagId: 'tag-2', weighting: JobTagWeighting.High }
]
}
// Generated tags with proper Required/Weighting fields
// Tags: [
// { TagId: 'tag-1', Required: true, Weighting: null },
// { TagId: 'tag-2', Required: false, Weighting: 3 }
// ]
import { FieldProcessor } from './types'
const customProcessor: FieldProcessor<MyObject> = (fieldName, fieldValue, targetObject, ...args) => {
// Custom processing logic
if (fieldName === 'specialField') {
targetObject.processedField = processSpecialValue(fieldValue)
} else {
targetObject[fieldName] = fieldValue
}
}
const engine = createTemplateEngine({
fieldProcessor: customProcessor,
additionalArgs: ['arg1', 'arg2']
})
The service automatically caches templates and template details for performance:
objectName:templateName
keyconst handlers: ObjectFieldHandlers = {
duration: (value, record) => {
record.Duration = value
// Additional duration processing
},
priority: (value, record) => {
record.Priority = value.toUpperCase()
}
}
templateService.registerObjectFieldHandlers('Jobs', handlers)
When extending the template service:
FAQs
Pulse Solutions Framework
The npm package @skedulo/pulse-solutions-framework receives a total of 274 weekly downloads. As such, @skedulo/pulse-solutions-framework popularity was classified as not popular.
We found that @skedulo/pulse-solutions-framework demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 33 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Evan You announces Vite+, a commercial, Rust-powered toolchain built on the Vite ecosystem to unify JavaScript development and fund open source.
Security News
Ruby Central’s incident report on the RubyGems.org access dispute sparks backlash from former maintainers and renewed debate over project governance.
Research
/Security News
Socket researchers uncover how threat actors weaponize Discord across the npm, PyPI, and RubyGems ecosystems to exfiltrate sensitive data.