メインコンテンツまでスキップ

Triggers

database の変更に応じて backend function を自動的に実行します。​

Triggers を使用する理由​

data の変更に対応する必要があります。たとえば、新しい user の signup 時に notification を送信する、product が変更されたときに search index を更新する、record が削除されたときに audit trail を log に記録する、といった場合です。

trigger を使用しない場合、この logic を data を書き込むすべての場所に分散させるか、変更を database で polling する必要があります。trigger を使用すると、監視する collection を宣言するだけで、Squid が function を自動的に呼び出します。

// Backend: runs automatically when a document changes
@trigger('onNewOrder', 'orders')
async handleNewOrder(request: TriggerRequest<Order>): Promise<void> {
if (request.mutationType === 'insert') {
await this.sendOrderConfirmation(request.docAfter);
}
}

polling は不要です。duplicated logic も不要です。data が変更されたときにのみ、code が正確に実行されます。

概要​

Triggers は、database collection 内の document が insert、update、delete されたときに自動的に実行される backend function です。mutation の commit 後に実行されるため、reactive workflow を構築する信頼性の高い方法を提供します。

Triggers を使用する場合​

ユースケース推奨
database の変更に自動的に対応する✅ Trigger
client から function を呼び出すExecutables を使用
schedule に従って code を実行するSchedulers を使用
external service に HTTP endpoint を公開するWebhooks を使用

仕組み​

  1. SquidService を拡張する class の method を @trigger で decorate します
  2. Squid が deploy 時に trigger を登録します
  3. 指定 collection 内の document が insert、update、delete されると、Squid が function を呼び出します
  4. function は mutation type、変更前後の document、document ID を持つ TriggerRequest を受け取ります

クイックスタート​

前提条件​

  • squid init で初期化された Squid backend project
  • NPM からインストールされた @squidcloud/backend package

ステップ 1: trigger function を作成する​

SquidService を拡張する service class を作成し、trigger を追加します。

Backend code
import { SquidService, trigger, TriggerRequest } from '@squidcloud/backend';

export class ExampleService extends SquidService {
@trigger('userChange', 'users')
async handleUserChange(request: TriggerRequest): Promise<void> {
console.log(`User ${request.docId} was ${request.mutationType}d`);
console.log('Before:', request.docBefore);
console.log('After:', request.docAfter);
}
}

ステップ 2: service を export する​

service が service index file から export されていることを確認します。

service/index.ts
export * from './example-service';

ステップ 3: backend を開始または deploy する​

ローカル開発では、Squid CLI を使用して backend をローカルで実行します。

squid start

cloud に deploy するには、backend の deployを参照してください。

これで、users collection 内の document が insert、update、delete されるたびに trigger が自動的に発火します。

コアコンセプト​

@trigger decorator​

@trigger decorator には、positional parameter と options object の 2 つの形式があります。

Positional parameter:

Backend code
@trigger('userChange', 'users')              // id, collection (uses built-in DB)
@trigger('userChange', 'users', 'myDatabase') // id, collection, connectorId
ParameterType説明
idstringこの trigger の unique identifier
collectionNamestring監視する collection の name
integrationId?stringconnector ID。default は built-in database です

Options object:

Backend code
@trigger({ collection: 'users', mutationTypes: ['insert', 'update'] })
PropertyType説明
id?stringunique identifier。省略した場合、default は ClassName.FunctionName
collectionstring監視する collection の name
integrationId?stringconnector ID。default は built-in database
mutationTypes?MutationType[]function を trigger する mutation type を filter します。省略時はすべての mutation が trigger されます

TriggerRequest​

trigger function に渡される TriggerRequest<T> object は、変更に関する detail を提供します。

PropertyType説明
docIdstring | Record<string, any>document ID(single-field key では string、composite key では object)
collectionNamestring影響を受けた collection の name
integrationIdstring影響を受けた database の connector ID
mutationTypeMutationTypemutation の type: 'insert'、'update'、または 'delete'
docBefore?Tmutation 前の document state。update と delete で利用可能
docAfter?Tmutation 後の document state。insert と update で利用可能

generic type parameter T を使用すると、document data に type を付けられます。

Backend code
interface User {
id: string;
name: string;
email: string;
}

@trigger('userChange', 'users')
async handleUserChange(request: TriggerRequest<User>): Promise<void> {
// request.docAfter is typed as User | undefined
const user = request.docAfter;
if (user) {
console.log(user.name); // type-safe access
}
}

Mutation type​

Trigger は 3 種類の mutation に対応します。

Mutation TypedocBeforedocAfter説明
'insert'undefined存在する新しい document が作成された
'update'存在する存在する既存 document が変更された
'delete'存在するundefineddocument が削除された

Mutation type による Filtering​

options object 形式を使用すると、特定の mutation type に対してのみ trigger できます。insert、update、delete ごとに別々の logic が必要な場合に役立ちます。

Backend code
import { SquidService, trigger, TriggerRequest } from '@squidcloud/backend';

export class OrderService extends SquidService {
@trigger({ collection: 'orders', mutationTypes: ['insert'] })
async onNewOrder(request: TriggerRequest): Promise<void> {
// Only runs on insert
await this.sendOrderConfirmation(request.docAfter);
}

@trigger({ collection: 'orders', mutationTypes: ['update'] })
async onOrderUpdate(request: TriggerRequest): Promise<void> {
// Only runs on update
await this.notifyOrderStatusChange(request.docBefore, request.docAfter);
}

@trigger({ collection: 'orders', mutationTypes: ['delete'] })
async onOrderCancelled(request: TriggerRequest): Promise<void> {
// Only runs on delete
await this.processRefund(request.docBefore);
}
}

External database connector の使用​

default では、trigger は built-in database を監視します。external database connector 内の collection を監視するには、connector ID を指定します。

Backend code
// Positional form
@trigger('userSync', 'users', 'myPostgresDb')

// Options object form
@trigger({ collection: 'users', integrationId: 'myPostgresDb' })

Trigger は、PostgreSQL、MySQL、MongoDB など mutation をサポートする任意の database connector で機能します。

Squid client の使用​

this.squid を使用して trigger 内から他の Squid service に access します。これにより、client SDK で利用できる同じ Database operation に access できます。

Backend code
@trigger('auditLog', 'orders')
async logOrderChange(request: TriggerRequest): Promise<void> {
const auditCollection = this.squid.collection('audit-log');
await auditCollection.doc().insert({
collection: request.collectionName,
docId: request.docId,
mutationType: request.mutationType,
timestamp: new Date(),
before: request.docBefore,
after: request.docAfter,
});
}

Error Handling​

Trigger は mutation の commit 後に実行されます。trigger が error を throw しても、元の mutation は rollback されません。変更に関する情報を失わないために、trigger では error を適切に処理する必要があります。

Backend code
@trigger('processChange', 'payments')
async handlePaymentChange(request: TriggerRequest): Promise<void> {
try {
await this.processPaymentUpdate(request);
} catch (error) {
console.error(`Trigger failed for doc ${request.docId}:`, error);
// Log the failure for manual review
await this.squid.collection('failed-triggers').doc().insert({
docId: request.docId,
mutationType: request.mutationType,
error: String(error),
timestamp: new Date(),
});
}
}

ベストプラクティス​

  1. trigger を高速に保つ。 trigger は各 mutation 後に asynchronous に実行されます。long-running operation は subsequent change の processing を遅延させます。heavy work は、trigger で queue collection に書き込み、別途処理してください。

  2. error を適切に処理する。 trigger error は元の mutation を rollback しないため、failure を log に記録し、trigger された action が重要な場合は retry mechanism を検討してください。

  3. mutation type filtering を使用する。 logic が特定の operation にのみ適用される場合は、不要な trigger execution を避けるために mutationTypes option を使用します。

  4. circular trigger を避ける。 trigger が監視対象と同じ collection に書き込むと、loop 内で自身を trigger します。別の collection に書き込むか、mutation type filtering を使用して cycle を防止してください。

  5. TriggerRequest に type を付ける。 document data に type-safe に access するには、generic parameter(TriggerRequest<MyType>)を使用します。

コード例​

新規 record で notification を送信する​

Backend code
import { SquidService, trigger, TriggerRequest } from '@squidcloud/backend';

interface User {
id: string;
name: string;
email: string;
}

export class NotificationService extends SquidService {
@trigger({ collection: 'users', mutationTypes: ['insert'] })
async welcomeNewUser(request: TriggerRequest<User>): Promise<void> {
const user = request.docAfter;
if (!user) return;

await this.squid
.collection('notifications')
.doc()
.insert({
userId: user.id,
message: `Welcome, ${user.name}!`,
createdAt: new Date(),
});
}
}

Derived collection を維持する​

Backend code
import { SquidService, trigger, TriggerRequest } from '@squidcloud/backend';

interface Product {
id: string;
name: string;
price: number;
category: string;
}

export class AnalyticsService extends SquidService {
@trigger('productSync', 'products')
async syncProductStats(request: TriggerRequest<Product>): Promise<void> {
const statsCollection = this.squid.collection('category-stats');

if (request.mutationType === 'insert') {
const product = request.docAfter!;
const statsDoc = statsCollection.doc(product.category);
const stats = await statsDoc.snapshot();
await statsDoc.insert({
category: product.category,
count: (stats?.count ?? 0) + 1,
});
}

if (request.mutationType === 'delete') {
const product = request.docBefore!;
const statsDoc = statsCollection.doc(product.category);
const stats = await statsDoc.snapshot();
if (stats) {
await statsDoc.update({ count: Math.max(0, stats.count - 1) });
}
}
}
}

関連項目​