fcode-javascript
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseFactorial Code — JavaScript
Factorial Code — JavaScript
Guidelines for writing JavaScript that runs on Factorial Code. Runtime is
Node.js v22. For the platform model (processes, modules, datastore) see
.
fcode-core-concepts| Aspect | Guideline |
|---|---|
| Runtime | Node.js v22 |
| Process entry file | |
| Entry point | |
| Export (required) | |
| Parameters | |
| Variables | |
| Import a module | |
在 Factorial Code 上运行 JavaScript 的编写指南。运行时为
Node.js v22。有关平台模型(process、module、datastore),请参阅
。
fcode-core-concepts| 方面 | 指南 |
|---|---|
| 运行时 | Node.js v22 |
| Process 入口文件 | |
| 入口点 | |
| 导出(必需) | |
| 参数 | |
| 变量 | |
| 导入 module | |
Gotchas
易错点
- Always export :
main. Without it the process won't run.module.exports = { main } - Never call yourself — Factorial Code invokes it.
main() - names must be hardcoded string literals, never variables:
fcode.import()✅,fcode.import("shopify-client")❌.fcode.import(name) - Never alias — call it literally (
fcode.i18n✅,fcode.i18n("key")❌): an aliased call throws "i18n is disabled" at runtime. Seeconst t = fcode.i18n.fcode-i18n - Datastore stores only strings/numbers — objects before
JSON.stringify, parse afterset.get - Encrypt secrets you keep in the datastore — pass as the third argument of
true(set);fcode.datastore.set(key, token, true)decrypts transparently. Plaingetstores the value in clear.set - Use for all async work; wrap the main flow in
async/await, log the caught error with context viatry/catch(see Logging), and throw actionable errors. Usefcode-logs/const, neverlet.var - Never hardcode or log secrets — read them from .
process.env
- 务必导出 :
main。如果不导出,process 将不会运行。module.exports = { main } - 切勿自行调用 ——Factorial Code 会调用它。
main() - 的名称必须是硬编码字符串字面量,绝不能使用变量:
fcode.import()✅,fcode.import("shopify-client")❌。fcode.import(name) - 切勿为 创建别名——应按原名调用(
fcode.i18n✅,fcode.i18n("key")❌):通过别名调用时,运行时将抛出 "i18n is disabled"。 参见const t = fcode.i18n。fcode-i18n - Datastore 只能存储字符串/数字——在 之前对对象执行
set, 在JSON.stringify之后进行解析。get - 对保存在 datastore 中的 secrets 进行加密——将 作为
true的第三个参数传入 (set);fcode.datastore.set(key, token, true)会透明地解密。普通get会以明文存储该值。set - 对所有异步工作使用 ;使用
async/await包裹主要流程,通过try/catch记录捕获到的错误及其上下文(参见 Logging),然后抛出便于定位和处理的错误。 使用fcode-logs/const,切勿使用let。var - 切勿硬编码或记录 secrets——应从 读取。
process.env
Process template
Process 模板
javascript
async function main() {
const { parameters } = fcode.context;
// Your code here
return { message: "Success!" };
}
module.exports = { main };javascript
async function main() {
const { parameters } = fcode.context;
// Your code here
return { message: "Success!" };
}
module.exports = { main };Helpers
辅助工具
javascript
// Execution / process / schedule metadata
const { id, comment } = fcode.execution;
const { id: processId, name: processName } = fcode.execution.process;
const { id: scheduleId } = fcode.execution.schedule; // when run from a schedule
const timezone = fcode.execution.timezone;
// Workspace (team) metadata
const teamSlug = fcode.team.slug;
// Environment variables (secrets/config)
const apiKey = process.env.API_KEY; // or fcode.env.API_KEY
// Import a Factorial Code module (hardcoded name only)
const { myFunc } = fcode.import("module-name");
const { myFunc: v1 } = fcode.import("module-name", "v1.0.0"); // pinned version tag or alias
// Run another process
await fcode.processes.run("process-identifier", options);
// Translations (workspace locales — see fcode-i18n)
const greeting = fcode.i18n("greetings.hello", { name: "Ada" }); // %{name} filled in
fcode.i18n("greetings.hello", { name: "Ada" }, { locale: "es" }); // another locale, this lookup only (value may be dynamic)
const locale = fcode.i18n.locale; // the execution's localejavascript
// Execution / process / schedule metadata
const { id, comment } = fcode.execution;
const { id: processId, name: processName } = fcode.execution.process;
const { id: scheduleId } = fcode.execution.schedule; // when run from a schedule
const timezone = fcode.execution.timezone;
// Workspace (team) metadata
const teamSlug = fcode.team.slug;
// Environment variables (secrets/config)
const apiKey = process.env.API_KEY; // or fcode.env.API_KEY
// Import a Factorial Code module (hardcoded name only)
const { myFunc } = fcode.import("module-name");
const { myFunc: v1 } = fcode.import("module-name", "v1.0.0"); // pinned version tag or alias
// Run another process
await fcode.processes.run("process-identifier", options);
// Translations (workspace locales — see fcode-i18n)
const greeting = fcode.i18n("greetings.hello", { name: "Ada" }); // %{name} filled in
fcode.i18n("greetings.hello", { name: "Ada" }, { locale: "es" }); // another locale, this lookup only (value may be dynamic)
const locale = fcode.i18n.locale; // the execution's localeLogging
日志记录
Log through the shared module — level-gated logging inherited by
every workspace. It reads the team variable
(, default ) and forwards to the matching
, so call sites read like bare calls:
fcode-logsLOG_LEVELdebug | info | warn | errorinfoconsole.*consolejavascript
const log = fcode.import("fcode-logs"); // import once; reuse `log` everywhere, including `catch`
log.info("sync started", { processSlug }); // console.log when LOG_LEVEL ≤ info
log.debug({ requestPayload }); // console.debug only when LOG_LEVEL=debug
log.warn("token missing — skipping"); // console.warn when LOG_LEVEL ≤ warn
log.error("sync failed", err.message); // console.error — always emittedBe verbose — the logging policy (start/end, external calls and decisions at
; payloads at ; always log inside with context before
re-throwing) is in §General rules. Set
in a local or dev workspace to trace a full run; production stays at .
Never log secrets. Each execution keeps at most 500 log lines / 500 KB — later entries
are dropped ( › Plans & limits).
infodebugcatchfcode-core-conceptsLOG_LEVEL=debuginfofcode-core-concepts通过共享的 module 记录日志——这是所有 workspace 共享并继承的分级日志机制。
它会读取团队变量
(,默认值为 ),并转发到对应的
,因此调用点的写法与普通 调用相同:
fcode-logsLOG_LEVELdebug | info | warn | errorinfoconsole.*consolejavascript
const log = fcode.import("fcode-logs"); // import once; reuse `log` everywhere, including `catch`
log.info("sync started", { processSlug }); // console.log when LOG_LEVEL ≤ info
log.debug({ requestPayload }); // console.debug only when LOG_LEVEL=debug
log.warn("token missing — skipping"); // console.warn when LOG_LEVEL ≤ warn
log.error("sync failed", err.message); // console.error — always emitted应记录充分的信息——日志策略(开始/结束、外部调用和决策记录为 ;
payload 记录为 ;始终在 中记录上下文,然后重新抛出)见
§General rules。在本地或开发 workspace 中设置 ,
以跟踪完整运行过程;生产环境保持 。
切勿记录 secrets。每次执行最多保留 500 条日志/500 KB——后续条目会被丢弃
( › Plans & limits)。
infodebugcatchfcode-core-conceptsLOG_LEVEL=debuginfofcode-core-conceptsDependencies
依赖项
External npm packages install automatically — just them. When the
import name differs from the package name, declare it with :
require@add-packagejavascript
// @add-package axios
const axios = require("axios");Packages a parent workspace provides are already installed here — don't
redeclare them (see ).
fcode-core-concepts外部 npm 包会自动安装——直接 即可。当导入名称与包名不同时,使用
声明:
require@add-packagejavascript
// @add-package axios
const axios = require("axios");父级 workspace 提供的包已在此处安装——无需重复声明
(参见 )。
fcode-core-conceptsDatastore & storage
Datastore 与存储
javascript
// Datastore (strings/numbers only)
await fcode.datastore.set("key", "value");
await fcode.datastore.set("key", JSON.stringify({ name: "John", age: 30 }));
const value = await fcode.datastore.get("key");
await fcode.datastore.del("key");
// Encrypted at rest with a key owned by the workspace — for tokens, credentials,
// personal data. The third argument must be a boolean; get() is transparent.
// Keys (entry names) are never encrypted.
await fcode.datastore.set("oauth.token", accessToken, true);
const token = await fcode.datastore.get("oauth.token");
// Storage (files)
const fs = require("node:fs");
const path = require("node:path");
const localPath = path.join(process.env.TMP_DATA_DIR, "localfile.txt");
await fcode.storage.upload("path/myfile.txt", fs.createReadStream(localPath));
const files = await fcode.storage.list();
const stream = await fcode.storage.download("path/myfile.txt");
stream.pipe(fs.createWriteStream(localPath));
// Form file params arrive as "fcode.storage://…" references — strip the
// prefix before download; see fcode-forms.
// Signed download URL — { url, expiresAt }. A real HTTPS link in the cloud,
// a file:// URL locally (same shape, no special-casing).
const signed = await fcode.storage.createSignedUrl("path/myfile.txt");
await fcode.storage.delete("path/myfile.txt");Local disk: write temp files under .
process.env.TMP_DATA_DIRjavascript
// Datastore (strings/numbers only)
await fcode.datastore.set("key", "value");
await fcode.datastore.set("key", JSON.stringify({ name: "John", age: 30 }));
const value = await fcode.datastore.get("key");
await fcode.datastore.del("key");
// Encrypted at rest with a key owned by the workspace — for tokens, credentials,
// personal data. The third argument must be a boolean; get() is transparent.
// Keys (entry names) are never encrypted.
await fcode.datastore.set("oauth.token", accessToken, true);
const token = await fcode.datastore.get("oauth.token");
// Storage (files)
const fs = require("node:fs");
const path = require("node:path");
const localPath = path.join(process.env.TMP_DATA_DIR, "localfile.txt");
await fcode.storage.upload("path/myfile.txt", fs.createReadStream(localPath));
const files = await fcode.storage.list();
const stream = await fcode.storage.download("path/myfile.txt");
stream.pipe(fs.createWriteStream(localPath));
// Form file params arrive as "fcode.storage://…" references — strip the
// prefix before download; see fcode-forms.
// Signed download URL — { url, expiresAt }. A real HTTPS link in the cloud,
// a file:// URL locally (same shape, no special-casing).
const signed = await fcode.storage.createSignedUrl("path/myfile.txt");
await fcode.storage.delete("path/myfile.txt");**本地磁盘:**将临时文件写入 下。
process.env.TMP_DATA_DIRVariables, schedules & OAuth
变量、计划与 OAuth
Read/write team variables and manage process schedules at runtime — scoped to
your own team, no API token needed (like datastore/storage):
javascript
// Team variables (config/secrets)
// Default is SENSITIVE: fcode.variables.set(key, value) creates a sensitive
// (masked, immutable-sensitivity) variable. Pass { sensitive: false } for
// plain config values.
await fcode.variables.set("API_KEY", "secret"); // sensitive by default
await fcode.variables.set("BASE_URL", "https://api.acme.com", {
sensitive: false, // required for non-secret config
});
const v = await fcode.variables.get("API_KEY"); // { key, value, resolvingTeamSlug, ... } or undefined
const all = await fcode.variables.list(); // includes variables inherited from parents
await fcode.variables.delete("API_KEY"); // no-op on an inherited variable
// Schedules (cron or one-off dateTime) for a process
const schedule = await fcode.schedule.create("my-process", {
cron: "0 0 6 * * SUN", // or: dateTime: "2026-04-24T12:30:00.000"
input: { parameters: { foo: "bar" } },
allowConcurrentExecutions: false, // optional
});
const schedules = await fcode.schedule.list({
processId: fcode.execution.process.id,
});
const current = await fcode.schedule.get(schedule.id);
await fcode.schedule.update(schedule.id, { cron: "0 0 7 * * SUN" });
await fcode.schedule.pause(schedule.id);
await fcode.schedule.resume(schedule.id);
await fcode.schedule.delete(schedule.id);
// delete every schedule for a process (pass the process UUID)
await fcode.schedule.deleteForProcess(fcode.execution.process.id);
// Start a third-party OAuth authorization. The platform holds the state, the PKCE
// verifier and the one redirect_uri registered with the provider, and invokes
// `onComplete` when the provider redirects back — so never build an authorization
// URL or a state by hand, and never expose a callback webhook.
const flow = await fcode.oauth.start({
authorizeUrl: "https://login.example.com/authorize",
clientId: fcode.env.PROVIDER_CLIENT_ID,
scope: ["openid", "offline_access"],
onComplete: "oauth-callback", // the process that receives the code
data: { companyId }, // carried back untouched; never a secret
extraParams: { nonce }, // optional, provider-specific
pkce: true, // default
});
flow.authorizationUrl; // hand this to the form's oauth widgetonCompletecodecodeVerifierredirectUridatastatefcode.context.parameterserrorerrorDescriptionredirectUrifcode-examplesreferences/oauth-connect.mdfcode.variables.set/deletefcode.envfcode.env在运行时读写团队变量并管理 process 计划——这些操作的范围仅限于你自己的团队,
且无需 API token(与 datastore/storage 类似):
javascript
// Team variables (config/secrets)
// Default is SENSITIVE: fcode.variables.set(key, value) creates a sensitive
// (masked, immutable-sensitivity) variable. Pass { sensitive: false } for
// plain config values.
await fcode.variables.set("API_KEY", "secret"); // sensitive by default
await fcode.variables.set("BASE_URL", "https://api.acme.com", {
sensitive: false, // required for non-secret config
});
const v = await fcode.variables.get("API_KEY"); // { key, value, resolvingTeamSlug, ... } or undefined
const all = await fcode.variables.list(); // includes variables inherited from parents
await fcode.variables.delete("API_KEY"); // no-op on an inherited variable
// Schedules (cron or one-off dateTime) for a process
const schedule = await fcode.schedule.create("my-process", {
cron: "0 0 6 * * SUN", // or: dateTime: "2026-04-24T12:30:00.000"
input: { parameters: { foo: "bar" } },
allowConcurrentExecutions: false, // optional
});
const schedules = await fcode.schedule.list({
processId: fcode.execution.process.id,
});
const current = await fcode.schedule.get(schedule.id);
await fcode.schedule.update(schedule.id, { cron: "0 0 7 * * SUN" });
await fcode.schedule.pause(schedule.id);
await fcode.schedule.resume(schedule.id);
await fcode.schedule.delete(schedule.id);
// delete every schedule for a process (pass the process UUID)
await fcode.schedule.deleteForProcess(fcode.execution.process.id);
// Start a third-party OAuth authorization. The platform holds the state, the PKCE
// verifier and the one redirect_uri registered with the provider, and invokes
// `onComplete` when the provider redirects back — so never build an authorization
// URL or a state by hand, and never expose a callback webhook.
const flow = await fcode.oauth.start({
authorizeUrl: "https://login.example.com/authorize",
clientId: fcode.env.PROVIDER_CLIENT_ID,
scope: ["openid", "offline_access"],
onComplete: "oauth-callback", // the process that receives the code
data: { companyId }, // carried back untouched; never a secret
extraParams: { nonce }, // optional, provider-specific
pkce: true, // default
});
flow.authorizationUrl; // hand this to the form's oauth widget调用 时,、、、 和
会通过 传入;如果提供方拒绝授权,则传入
/。在 token exchange 中原样重放 ——提供方会逐字节比较它。
参见 的 。
onCompletecodecodeVerifierredirectUridatastatefcode.context.parameterserrorerrorDescriptionredirectUrifcode-examplesreferences/oauth-connect.mdfcode.variables.set/deletefcode.envfcode.envInherited variables
继承变量
list()get()fcode-core-conceptsresolvingTeamSlugset()delete()resolvingTeamSlugfcode.env.setEnvVardelEnvVarlist()get()fcode-core-conceptsresolvingTeamSlugset()delete()resolvingTeamSlugfcode.env.setEnvVardelEnvVarSending email
发送电子邮件
Send email with the built-in — no SMTP setup required (model
in ):
fcode.sendMailfcode-core-conceptsjavascript
const info = await fcode.sendMail({
to: "user@example.com", // string or string[]
subject: "Report ready",
text: "Plain-text body", // provide text, html, or both
html: "<b>HTML body</b>",
});
// info => { messageId, accepted, rejected }- The address is fixed by the platform; a
Fromyou pass is ignored.from - Each execution can send up to 3 emails by default; once the limit is reached, further calls throw.
- Locally () there is no manager, so the email is logged, not sent.
fcode run - Acceptable use — who may be a recipient, what the body may carry — is a
platform rule that blocks a release when broken; see §Sending email.
fcode-core-concepts
使用内置的 发送电子邮件——无需配置 SMTP(模型见
):
fcode.sendMailfcode-core-conceptsjavascript
const info = await fcode.sendMail({
to: "user@example.com", // string or string[]
subject: "Report ready",
text: "Plain-text body", // provide text, html, or both
html: "<b>HTML body</b>",
});
// info => { messageId, accepted, rejected }- 地址由平台固定;传入的
From会被忽略。from - 每次执行默认最多可发送 3 封电子邮件;达到上限后,后续调用将抛出异常。
- 在本地()运行时不存在 manager,因此电子邮件只会被记录而不会实际发送。
fcode run - 可接受的使用方式——哪些用户可以成为收件人、正文可以包含哪些内容——由平台规则约束;
如果违反这些规则,发布将被阻止。参见 §Sending email。
fcode-core-concepts
Return values
返回值
javascript
// Standard
return { message: "Success!" };
// Custom HTTP status (webhooks)
return { status: 404, body: { message: "Not found" }, headers: { "Content-Type": "application/json" } };
// Transient (not persisted in execution results)
return { transient: true, data: sensitiveData };
// Synchronous UI trigger button inside Factorial (see fcode-ui-triggers).
// Returned unchanged to the page; signal a failure with an error, not in the body.
return { synced: 42 };The return value is capped at 500 KB — larger results fail the execution; put them
in storage and return a reference ( › Plans & limits).
fcode-core-conceptsjavascript
// Standard
return { message: "Success!" };
// Custom HTTP status (webhooks)
return { status: 404, body: { message: "Not found" }, headers: { "Content-Type": "application/json" } };
// Transient (not persisted in execution results)
return { transient: true, data: sensitiveData };
// Synchronous UI trigger button inside Factorial (see fcode-ui-triggers).
// Returned unchanged to the page; signal a failure with an error, not in the body.
return { synced: 42 };返回值上限为 500 KB——更大的结果会导致执行失败;应将其放入 storage 并返回引用
( › Plans & limits)。
fcode-core-concepts