Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 37 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@ on:
workflow_dispatch:

jobs:
# The repo as people clone it: public classes plus SOQL Lib and DML Lib from dependencies/
salesforce-ci:
uses: beyond-the-cloud-dev/cicd-template/.github/workflows/salesforce-ci.yml@main
uses: beyond-the-cloud-dev/cicd-template/.github/workflows/salesforce-ci.yml@feat/deps-from-sfdx-project
with:
# package/ needs SOQL Lib and DML Lib installed; force-app/ vendors both
source-dirs: 'force-app examples'
template-ref: feat/deps-from-sfdx-project
source-dirs: 'force-app'
node-version: '22'
sf-cli-version: 'latest'
scratch-org-duration: 1
Expand All @@ -22,3 +23,36 @@ jobs:
secrets:
SFDX_AUTH_URL_DEVHUB: ${{ secrets.SFDX_AUTH_URL_DEVHUB }}
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}

# The package as it gets built: `// global` declarations made global, SOQL Lib and
# DML Lib from the tags pinned in sfdx-project.json
package-check:
uses: beyond-the-cloud-dev/cicd-template/.github/workflows/salesforce-ci.yml@feat/deps-from-sfdx-project
with:
template-ref: feat/deps-from-sfdx-project
pre-deploy-command: 'node scripts/prepare-package.mjs'
deploy-dependencies: true
source-dirs: 'force-app'
pr-comment: false
node-version: '22'
sf-cli-version: 'latest'
scratch-org-duration: 1
test-level: 'RunLocalTests'
secrets:
SFDX_AUTH_URL_DEVHUB: ${{ secrets.SFDX_AUTH_URL_DEVHUB }}

dependencies:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '22'
- run: npm run build:dependencies
- name: Check dependencies/ matches sfdx-project.json
run: |
if [ -n "$(git status --porcelain -- force-app/main/default/dependencies)" ]; then
git status --short -- force-app/main/default/dependencies
echo "::error::force-app/main/default/dependencies/ does not match the dependencies in sfdx-project.json. Run npm run build:dependencies and commit the result."
exit 1
fi
51 changes: 51 additions & 0 deletions .github/workflows/package.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
name: Package version

# Builds a new Trigger Lib package version from force-app/: declarations marked
# `// global` become global and dependencies/ is left out (SOQL Lib and DML Lib are
# package dependencies). Bump versionNumber in sfdx-project.json first. Promote the
# version by hand once it is tested.

on:
workflow_dispatch:

jobs:
create:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: '22'

- name: Install Salesforce CLI
run: npm install @salesforce/cli --global

- name: Authorize Dev Hub
env:
SFDX_AUTH_URL_DEVHUB: ${{ secrets.SFDX_AUTH_URL_DEVHUB }}
run: echo "$SFDX_AUTH_URL_DEVHUB" | sf org login sfdx-url --alias DevHub --set-default-dev-hub --sfdx-url-stdin

- name: Prepare package source
run: node scripts/prepare-package.mjs

- name: Create package version
run: |
sf package version create \
--package "Trigger Lib" \
--installation-key-bypass \
--code-coverage \
--wait 60 \
--json > result.json || { cat result.json; exit 1; }
{
echo "## Trigger Lib package version"
echo ""
echo "- Subscriber package version: \`$(jq -r '.result.SubscriberPackageVersionId' result.json)\`"
echo "- Install: https://login.salesforce.com/packaging/installPackage.apexp?p0=$(jq -r '.result.SubscriberPackageVersionId' result.json)"
echo ""
echo "Add the new alias to \`packageAliases\` in \`sfdx-project.json\`:"
echo ""
echo '```diff'
git diff -U0 sfdx-project.json | grep '^[+-] ' || true
echo '```'
} >> "$GITHUB_STEP_SUMMARY"
2 changes: 2 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
force-app/main/default/dependencies/
README.md
21 changes: 20 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,9 +133,28 @@ npm run docs:dev

# Create scratch org for testing
sf org create scratch -f config/project-scratch-def.json -a dev
sf project deploy start -o dev

# Deploy Trigger Lib (SOQL Lib and DML Lib come along from force-app/main/default/dependencies/)
sf project deploy start -d force-app -o dev
```

### Project layout

- `force-app/` is the source, with `public` classes, so a plain clone deploys to any org.
- `force-app/main/default/dependencies/` holds the SOQL Lib and DML Lib classes Trigger Lib needs, at the versions pinned in `sfdx-project.json`. It is generated: never edit it by hand. After bumping a dependency, run `npm run build:dependencies` and commit the result. CI fails when it does not match.
- `examples/` holds the example handlers used in the docs. It is not deployed by CI.

### Package API

The package is built from `force-app/` by `scripts/prepare-package.mjs`: it leaves out `dependencies/` and turns every declaration with a `// global` line right above it into `global`. Those markers are the package API. When you add a type or member that orgs installing the package must use, put `// global` above it:

```apex
// global
public interface Writer extends Handler {
```

The `package-check` CI job builds the package form on every PR and runs the tests on it, so a missing marker on a type used in a global signature fails there. New versions are created with the **Package version** workflow (Actions tab).

## 📚 Resources

- [Full Documentation](https://trigger.beyondthecloud.dev/)
Expand Down
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,15 @@ public with sharing class AccountWelcomeTaskWriter implements AfterInsert.Writer
}
```

## Dependencies

Trigger Lib is built on two other Apex Fluently libs, so they have to be in the org too:

- [SOQL Lib](https://soql.beyondthecloud.dev) 6.12.0
- [DML Lib](https://dml.beyondthecloud.dev) 4.0.0

Installing the packages? Install SOQL Lib and DML Lib first. The [installation guide](https://trigger.beyondthecloud.dev/installation) has the links in the right order. The button below and a plain `git clone` deploy both together with Trigger Lib, from `force-app/main/default/dependencies/`.

## Deploy to Salesforce

<a href="https://githubsfdeploy.herokuapp.com?owner=beyond-the-cloud-dev&repo=trigger-lib&ref=main">
Expand Down
5 changes: 5 additions & 0 deletions codecov.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Coverage counts Trigger Lib only. dependencies/ is a generated copy of SOQL Lib
# and DML Lib, examples/ is docs code.
ignore:
- 'force-app/main/default/dependencies/**'
- 'examples/**'
11 changes: 11 additions & 0 deletions force-app/main/default/classes/AfterDelete.cls
Original file line number Diff line number Diff line change
@@ -1,48 +1,59 @@
// global
public class AfterDelete {
// Roles - pick one

// global
public interface Writer extends Handler {
Boolean writeOnAfterDeleteWhen(TriggerTypes.DeleteRecord record);
void writeOnAfterDelete(TriggerTypes.DeleteRecord record, TriggerTypes.UnitOfWork unitOfWork);
}

// global
public interface Dispatcher extends Handler {
Boolean dispatchOnAfterDeleteWhen(TriggerTypes.DeleteRecord record);
void dispatchOnAfterDelete(TriggerTypes.DeleteRecords records);
}

// Add-ons - pick as many as you need

// global
public interface PriorParentQuery {
Map<SObjectField, TriggerTypes.ParentFields> queryPriorParentsOnAfterDelete();
}

// global
public interface RelatedQuery {
Map<String, AfterDelete.RecordsProvider> queryRelatedOnAfterDelete();
}

// global
public interface RecordsProvider {
List<SObject> query(TriggerTypes.DeleteRecords records);
String keyOf(SObject row);
}

// global
public interface OwnUnitOfWork {
DML.Committable ownUnitOfWorkOnAfterDelete();
}

// global
public interface Bypassable {
Boolean bypassOnAfterDeleteWhen();
}

// global
public interface Finalizer {
void finalizeOnAfterDelete(TriggerTypes.DeleteRecords records);
}

// global
public interface ContinueOnError {
}

// Marker

// global
public interface Handler {
}

Expand Down
11 changes: 11 additions & 0 deletions force-app/main/default/classes/AfterInsert.cls
Original file line number Diff line number Diff line change
@@ -1,48 +1,59 @@
// global
public class AfterInsert {
// Roles - pick one

// global
public interface Writer extends Handler {
Boolean writeOnAfterInsertWhen(TriggerTypes.InsertRecord record);
void writeOnAfterInsert(TriggerTypes.InsertRecord record, TriggerTypes.UnitOfWork unitOfWork);
}

// global
public interface Dispatcher extends Handler {
Boolean dispatchOnAfterInsertWhen(TriggerTypes.InsertRecord record);
void dispatchOnAfterInsert(TriggerTypes.InsertRecords records);
}

// Add-ons - pick as many as you need

// global
public interface ParentQuery {
Map<SObjectField, TriggerTypes.ParentFields> queryParentsOnAfterInsert();
}

// global
public interface RelatedQuery {
Map<String, AfterInsert.RecordsProvider> queryRelatedOnAfterInsert();
}

// global
public interface RecordsProvider {
List<SObject> query(TriggerTypes.InsertRecords records);
String keyOf(SObject row);
}

// global
public interface OwnUnitOfWork {
DML.Committable ownUnitOfWorkOnAfterInsert();
}

// global
public interface Bypassable {
Boolean bypassOnAfterInsertWhen();
}

// global
public interface Finalizer {
void finalizeOnAfterInsert(TriggerTypes.InsertRecords records);
}

// global
public interface ContinueOnError {
}

// Marker

// global
public interface Handler {
}

Expand Down
11 changes: 11 additions & 0 deletions force-app/main/default/classes/AfterUndelete.cls
Original file line number Diff line number Diff line change
@@ -1,48 +1,59 @@
// global
public class AfterUndelete {
// Roles - pick one

// global
public interface Writer extends Handler {
Boolean writeOnAfterUndeleteWhen(TriggerTypes.UndeleteRecord record);
void writeOnAfterUndelete(TriggerTypes.UndeleteRecord record, TriggerTypes.UnitOfWork unitOfWork);
}

// global
public interface Dispatcher extends Handler {
Boolean dispatchOnAfterUndeleteWhen(TriggerTypes.UndeleteRecord record);
void dispatchOnAfterUndelete(TriggerTypes.UndeleteRecords records);
}

// Add-ons - pick as many as you need

// global
public interface ParentQuery {
Map<SObjectField, TriggerTypes.ParentFields> queryParentsOnAfterUndelete();
}

// global
public interface RelatedQuery {
Map<String, AfterUndelete.RecordsProvider> queryRelatedOnAfterUndelete();
}

// global
public interface RecordsProvider {
List<SObject> query(TriggerTypes.UndeleteRecords records);
String keyOf(SObject row);
}

// global
public interface OwnUnitOfWork {
DML.Committable ownUnitOfWorkOnAfterUndelete();
}

// global
public interface Bypassable {
Boolean bypassOnAfterUndeleteWhen();
}

// global
public interface Finalizer {
void finalizeOnAfterUndelete(TriggerTypes.UndeleteRecords records);
}

// global
public interface ContinueOnError {
}

// Marker

// global
public interface Handler {
}

Expand Down
Loading
Loading