Skip to main content

action() operation definition

gate.action() keeps the security and reliability decisions for a write in one object. The definition produces an immutable DefinedAction; execution is performed by gate.execute() or an HTTP adapter.

guild-settings.ts
const updateSettings = gate.action({
name: "guild.settings.update",
authentication: "required",
csrf: "required",
timeoutMs: 5_000,

parse(value) {
return parseSettingsInput(value);
},

resource: (input) => ({
type: "guild",
id: input.guildId,
}),

authorize: async (context, input) => {
const allowed = await canManageGuild(context.userId!, input.guildId);
return allowed ? { allowed: true } : { allowed: false, code: "missing_permission" };
},

rateLimit: {
limit: 20,
windowMs: 60_000,
key: (context, input) => `${context.userId}:${input.guildId}`,
},

idempotency: {
required: true,
ttlMs: 10 * 60_000,
scope: (context, input) => `${context.userId}:${input.guildId}`,
},

optimistic: {
expected: (input) => input.expectedRevision,
current: (input) => readRevision(input.guildId),
resource: (input) => `guild:${input.guildId}`,
},

concurrency: {
key: (_context, input) => `guild-settings:${input.guildId}`,
ttlMs: 8_000,
renewEveryMs: 2_000,
waitMs: 250,
},

transaction: {
required: true,
isolation: "serializable",
},

async execute(context, input) {
context.signal.throwIfAborted();
return saveGuildSettings({
transaction: context.transaction,
fencingToken: context.fencingToken,
input,
});
},

cache: {
invalidateTags: (result) => [`guild:${result.guildId}`],
},

realtime: {
delivery: "outbox",
events: (result) => [{
event: "guild.settings.updated",
channel: `guild:${result.guildId}`,
data: result,
}],
},

audit: {
enabled: true,
changes: (result) => ({ revision: result.revision }),
},
});

Authentication and CSRF​

authentication may be required, optional, or none. When an action weakens the default guarded behavior, record the reason in route review.

Use csrf: "required" for unsafe HTTP methods. Disable CSRF only for an endpoint without a browser session that is authenticated by another signature mechanism.

parse and resource​

parse narrows unknown input into an application type. Validate lengths, enums, numeric ranges, and allowed fields here.

resource makes it easier to reuse one verified identity for authorization, optimistic revision, audit, locking, and realtime channels.

authorize​

Returning nothing means access is allowed. An explicit decision is easier to review:

return {
allowed: false,
code: "missing_manage_guild",
details: { guildId: input.guildId },
};

Do not narrow Discord permission values to JavaScript number; use BigInt or discord.js permission helpers.

Idempotency reservation​

1.1.0 creates a reservationId for every inflight record. A store must enforce these rules:

  • return the stored response when the same key and request hash completed
  • report a conflict when the same key has a different hash
  • do not start a second execution while an active reservation exists
  • compare the owner token during renew, complete, and fail

When an action commits after its HTTP deadline, the kernel can complete the result under the same reservation and replay it to a later request.

Concurrency and fencing​

ttlMs is the lease lifetime; renewEveryMs must be shorter. waitMs controls how long acquisition may wait.

When the store supports acquireLease(), context.fencingToken is available. The database update should reject a token that is not newer than the stored token; do not rely only on lock ownership.

Transaction hooks​

transaction: {
required: true,
isolation: "serializable",
hooks: {
before: async (scope, input) => {},
beforeCommit: async (scope, result, input) => {},
afterCommit: async (result, input) => {},
afterRollback: async (error, input) => {},
},
}

afterCommit runs after the domain write is final. A failure there does not roll back the commit and is not a reason to execute the action twice.

Retry and circuit breaker​

Enable retries only for work that can safely repeat. If an external write is not idempotent, action-level idempotency alone does not make the third-party side effect safe.

The circuit breaker temporarily stops calls after the configured failure threshold. Its name is derived from the action when one is not supplied.

Cache, realtime, and audit​

cache.invalidateTags derives tags from the committed result. realtime.delivery: "outbox" queues events at the transaction boundary. Do not include secrets, tokens, or unnecessary full objects in audit changes.

Result metadata​

The HTTP layer must process ActionExecutionResult.meta. In particular, preserve replacement cookies, cookie-clearing instructions, and postCommitIssues.

Verification console
This console does not open a shell or send your input to a server. It checks GuildGate’s documented decision rules in your browser.
Console ready.
Run `help` or choose one of the examples below.
240 characters · 20/min · no network requests

Use action test guild.settings.update in the console to inspect the control order.