Executables
server resource への完全な access を持つ backend function を client に公開します。
Executables を使用する理由
frontend で、secret API key へのアクセス、database の query、browser に公開したくない business logic の実行など、server-side resource を必要とする処理を実行する必要があります。
executables を使用しない場合、route の定義、serialization の処理、CORS の管理、個別 server の deploy を含む API layer 全体をセットアップする必要があります。executables では、function を記述して呼び出すだけです。
// Backend: just a decorated method
@executable()
async processPayment(orderId: string, amount: number): Promise<Receipt> {
const apiKey = this.secrets['PAYMENT_API_KEY']; // Access secrets securely
return await paymentService.charge(orderId, amount, apiKey);
}
// Frontend: call it like a local function
const receipt = await squid.executeFunction('processPayment', orderId, 99.99);
route は不要です。API boilerplate も不要です。必要なのは function だけです。
概要
Executables は、client から直接呼び出せる backend function です。secret、database、external API、複雑な business logic など、server-side resource を必要とする operation 向けのシンプルな RPC-style interface を提供します。
Executables を使用する場合
| ユースケース | 推奨 |
|---|---|
| server-side logic を持つ function を client から呼び出す | ✅ Executable |
| database の変更に反応する | Triggers を使用 |
| schedule に沿って code を実行する | Schedulers を使用 |
| external service に HTTP endpoint を公開する | Webhooks を使用 |
| real-time data synchronization | Database を直接使用 |
仕組み
SquidServiceを拡張する class 内で、method を@executable()で decorate します- Squid が deploy 時に function を検出・登録します
- client は
squid.executeFunction('functionName', ...args)を使用して function を呼び出します - backend は secret、context、integration への完全な access を持って function を実行します
- result は serialize され、client に返されます
クイックスタート
前提条件
squid initで初期化された Squid backend project- NPM からインストールされた
@squidcloud/backendpackage
ステップ 1: executable function を作成する
SquidService を拡張する service class を作成し、executable function を追加します。
import { executable, SquidService } from '@squidcloud/backend';
export class ExampleService extends SquidService {
@executable()
async greet(name: string): Promise<string> {
return `Hello, ${name}!`;
}
}
ステップ 2: service を export する
service が service index file から export されていることを確認します。
export * from './example-service';
ステップ 3: backend を開始または deploy する
ローカル開発では、Squid CLI を使用して backend をローカルで実行します。
squid start
cloud に deploy するには、backend の deployを参照してください。
ステップ 4: client から呼び出す
const greeting = await squid.executeFunction('greet', 'World');
console.log(greeting); // Output: "Hello, World!"
Authentication と Authorization
Executables は、secret、database、integration を含む backend resource に無制限に accessできます。caller が要求された action を実行する権限を持つことを常に validate してください。
resource への access を自動的に保護する security rules とは異なり、executables では function code 内で authentication を手動で確認する必要があります。
Authentication の確認
authentication を要求するには this.assertIsAuthenticated() を使用します(認証されていない場合は 'UNAUTHORIZED' を throw します)。または、手動で確認するには this.isAuthenticated() を使用します。
import { executable, SquidService } from '@squidcloud/backend';
export class SecureService extends SquidService {
@executable()
async getSecretData(): Promise<string> {
// Throws UNAUTHORIZED if not authenticated
this.assertIsAuthenticated();
// Access user details for authorization decisions
const userAuth = this.getUserAuth();
if (!userAuth?.attributes?.['role']?.includes('admin')) {
throw new Error('Admin access required');
}
return 'Secret data';
}
}
authentication method と backend の保護に関する詳細は、backend で auth を使用するを参照してください。
コアコンセプト
Request context
すべての executable は this.context を介して request context に access できます。
@executable()
async logRequestInfo(): Promise<void> {
const ctx = this.context;
console.log('App ID:', ctx.appId);
console.log('Client ID:', ctx.clientId); // Unique client identifier
console.log('Source IP:', ctx.sourceIp); // Client IP address
console.log('Headers:', ctx.headers); // Request headers (lowercase keys)
}
| Property | Type | 説明 |
|---|---|---|
appId | string | application ID |
clientId | string | undefined | 呼び出し元 client の unique identifier |
sourceIp | string | undefined | caller の IP address |
headers | Record<string, any> | undefined | HTTP header(key は lowercase) |
Secrets と API keys
Squid Console で定義したapplication secretに access します。
@executable()
async callExternalApi(): Promise<any> {
const apiKey = this.secrets['EXTERNAL_API_KEY'];
const response = await fetch('https://api.example.com/data', {
headers: { 'Authorization': `Bearer ${apiKey}` }
});
return response.json();
}
File upload(SquidFile)
Executables は client から upload された file を受け取れます。parameter として送信された file は、自動的に SquidFile object に変換されます。
Client-side:
// Single file
const fileInput = document.querySelector('input[type="file"]') as HTMLInputElement;
const file = fileInput.files[0];
const result = await squid.executeFunction('uploadDocument', file, 'My Document');
// Multiple files
const files = Array.from(fileInput.files);
const result = await squid.executeFunction('uploadDocuments', files);
Backend:
import { executable, SquidService } from '@squidcloud/backend';
import { SquidFile } from '@squidcloud/backend';
export class FileService extends SquidService {
@executable()
async uploadDocument(file: SquidFile, title: string): Promise<string> {
console.log('Original filename:', file.originalName);
console.log('MIME type:', file.mimetype);
console.log('Size (bytes):', file.size);
// Access file content as Uint8Array
const content = file.data;
// Process the file...
return `Uploaded: ${title} (${file.size} bytes)`;
}
@executable()
async uploadDocuments(files: SquidFile[]): Promise<string[]> {
return files.map((f) => `Processed: ${f.originalName}`);
}
}
Squid client の使用
this.squid を使用して executable 内から他の Squid service に access します。これにより、client SDK で利用可能な同じ Database および storage API に access できます。
@executable()
async createUserWithData(userData: UserData): Promise<void> {
const usersCollection = this.squid.collection<User>('users');
const userRef = usersCollection.doc(userData.id);
await userRef.insert(userData);
}
database operation の詳細は、Database documentationを参照してください。
Client-Side Advanced Options
Custom headers
this.context.headers から access できる custom header を送信します。
const result = await squid.executeFunctionWithHeaders(
'processOrder',
{
'x-idempotency-key': 'order-123-attempt-1',
'x-client-version': '2.0.0',
},
orderData
);
@executable()
async processOrder(orderData: OrderData): Promise<Order> {
const idempotencyKey = this.context.headers?.['x-idempotency-key'];
const clientVersion = this.context.headers?.['x-client-version'];
// Use idempotency key to prevent duplicate processing
// ...
}
executeFunctionWithHeaders は executeFunction と同じ signature を持ちますが、function parameter の前、2 番目の argument として headers object が挿入されます。留意すべき動作は以下のとおりです。
- header 名は送信時に lowercase 化されるため、
this.context.headersでは lowercase key を使用して読み取ります。 - custom header は Squid 独自の request header(authorization など)と merge され、置き換えることはありません。
executeFunctionは、empty headers object を指定したexecuteFunctionWithHeadersと同等です。- custom header は TypeScript Client SDK で利用できます。Python client の
execute_functionは header を受け取りません。
Result の Caching
冗長な request を避けるため、client で高コストな function call を cache します。
import { LastUsedValueExecuteFunctionCache } from '@squidcloud/client';
// Create a cache that stores results for 5 minutes
const weatherCache = new LastUsedValueExecuteFunctionCache<WeatherData>({
valueExpirationMillis: 5 * 60 * 1000,
});
// Use the cache
const weather = await squid.executeFunction(
{
functionName: 'getWeather',
caching: { cache: weatherCache },
},
'New York'
);
Concurrent call の Deduplication
同じ argument による concurrent request の重複を防止します。
// Using default reference comparison
const result = await squid.executeFunction(
{
functionName: 'expensiveCalculation',
deduplication: true,
},
inputData
);
// Using serialized value comparison (for object arguments)
import { compareArgsBySerializedValue } from '@squidcloud/client';
const result = await squid.executeFunction(
{
functionName: 'expensiveCalculation',
deduplication: { argsComparator: compareArgsBySerializedValue },
},
inputData
);
Rate Limiting
@limits decorator を使用して executable を不正利用から保護します。global、user ごと、IP address ごとの scope で、rate limit(1 秒あたりの query 数)と quota limit(期間あたりの合計 call 数)を定義できます。
import { executable, limits, SquidService } from '@squidcloud/backend';
export class RateLimitedService extends SquidService {
@executable()
@limits({
rateLimit: 5,
quotaLimit: { value: 100, scope: 'user', renewPeriod: 'monthly' },
})
async limitedAction(): Promise<void> {
// ...
}
}
scope option、enforcement behavior、renewal period を含む rate と quota limiting の詳細は、Rate and quota limitingを参照してください。
Error Handling
Error の Throw
標準 JavaScript error を throw します。これらは serialize され、client に返されます。
@executable()
async riskyOperation(data: InputData): Promise<Result> {
if (!data.requiredField) {
throw new Error('requiredField is missing');
}
try {
return await this.performOperation(data);
} catch (error) {
// Log server-side for debugging
console.error('Operation failed:', error);
// Throw a user-friendly message
throw new Error('Operation failed. Please try again.');
}
}
Client での Error の処理
try {
const result = await squid.executeFunction('riskyOperation', data);
console.log('Success:', result);
} catch (error) {
console.error('Function failed:', error.message);
// Handle the error appropriately
}
一般的な Error
| Error | 原因 | 解決策 |
|---|---|---|
Function not found | function 名がどの @executable とも一致しない | spelling を確認し、service が export されていることを確認する |
UNAUTHORIZED | assertIsAuthenticated() が失敗した | 呼び出し前に user が log in していることを確認する |
Rate limit exceeded | @limits threshold に到達した | backoff を伴う retry を実装するか、limit を調整する |
Network error | connectivity issue | retry logic を実装する |
Best Practices
Security
- sensitive operation に対しては常に authentication を validate する
- 処理前にすべての input parameter を validate する
- internal error detail を client に公開しない
- public-facing executable ではrate limiting を使用する
- type、size、content を確認して、file upload を sanitize する
@executable()
@limits({
rateLimit: 10,
quotaLimit: { value: 100, scope: 'user', renewPeriod: 'monthly' },
})
async secureAction(input: UserInput): Promise<Result> {
// 1. Authenticate
this.assertIsAuthenticated();
// 2. Validate input
if (!input || typeof input.value !== 'string' || input.value.length > 1000) {
throw new Error('Invalid input');
}
// 3. Authorize (check permissions)
const user = this.getUserAuth();
if (!user?.attributes?.['canPerformAction']) {
throw new Error('Permission denied');
}
// 4. Execute with error handling
try {
return await this.doAction(input);
} catch (error) {
console.error('Action failed:', error);
throw new Error('Action failed');
}
}
Performance
- 高コストで idempotent な operation にはclient-side caching を使用する
- 冗長な concurrent call を防止するためdeduplication を有効にする
- 必要な data のみを返し、payload を小さく保つ
- 長時間実行 task の heavy processing は、scheduler または queue にoffload する
Naming conventions
- function 名には camelCase を使用します
- 説明的な verb を使用します(例:
createOrder、updateProfile、deleteDocument) - 関連する function を同じ service class に group 化します
コード例
Database operation
import { executable, SquidService } from '@squidcloud/backend';
interface Product {
id: string;
name: string;
price: number;
stock: number;
}
export class InventoryService extends SquidService {
@executable()
async updateStock(productId: string, quantity: number): Promise<Product> {
this.assertIsAuthenticated();
const products = this.squid.collection<Product>('products');
const productRef = products.doc(productId);
const product = await productRef.snapshot();
if (!product) {
throw new Error(`Product ${productId} not found`);
}
const newStock = product.stock + quantity;
if (newStock < 0) {
throw new Error('Insufficient stock');
}
await productRef.update({ stock: newStock });
return { ...product, stock: newStock };
}
}
query、transaction、join など、その他の database operation については、Database documentationを参照してください。
External API の呼び出し
import { executable, SquidService } from '@squidcloud/backend';
interface WeatherData {
temperature: number;
conditions: string;
}
export class WeatherService extends SquidService {
@executable()
async getWeather(city: string): Promise<WeatherData> {
const apiKey = this.secrets['WEATHER_API_KEY'];
const response = await fetch(`https://api.weather.example.com/v1/current?city=${encodeURIComponent(city)}`, {
headers: { 'X-API-Key': apiKey },
});
if (!response.ok) {
throw new Error(`Weather API error: ${response.status}`);
}
return response.json();
}
}
File upload の処理
この例では、upload された file を validate し、Squid storage を使用して保存します。
import { executable, SquidService } from '@squidcloud/backend';
import { SquidFile } from '@squidcloud/backend';
export class ImageService extends SquidService {
@executable()
async processImage(image: SquidFile): Promise<{ id: string; url: string }> {
this.assertIsAuthenticated();
// Validate file type
if (!image.mimetype.startsWith('image/')) {
throw new Error('Only image files are allowed');
}
// Validate file size (max 5MB)
const maxSize = 5 * 1024 * 1024;
if (image.size > maxSize) {
throw new Error('File size exceeds 5MB limit');
}
// Store in Squid storage
const storage = this.squid.storage('images');
const imageId = crypto.randomUUID();
const dirPath = 'uploads';
const filePath = `${dirPath}/${imageId}-${image.originalName}`;
// Convert SquidFile to File for upload
const file = new File([image.data], image.originalName, { type: image.mimetype });
await storage.uploadFile(dirPath, file);
const { url } = await storage.getDownloadUrl(filePath);
return { id: imageId, url };
}
}
関連項目
- Triggers - database の変更に反応する
- Schedulers - schedule に沿って code を実行する
- Webhooks - HTTP endpoint を公開する
- Rate and quota limiting - backend function を保護する
- Security Rules - backend を保護する