Soql Lib Query Builder
beyond-the-cloud-dev/soql-lib
Builds Salesforce SOQL queries using the SOQL Lib fluent builder API (SOQL.cls).
Enforces Effect-TS patterns for services, errors, layers, atoms, and Effect.pipe composition.
$ npx skills add forcedotcom/salesforcedx-vscode --skill effect-best-practices -a claude-codeProject install by default; add -g for ~/.claude/skills/.
$ gh skill install forcedotcom/salesforcedx-vscode effect-best-practices --agent claude-codeProject scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).
$ git clone --depth 1 https://github.com/forcedotcom/salesforcedx-vscode.git skills-src && mkdir -p .claude/skills && cp -r skills-src/.claude/skills/effect-best-practices .claude/skills/effect-best-practices && rm -rf skills-srcUse ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.
Claude Code skills documentation · loads skills from .claude/skills/
Install the "effect-best-practices" agent skill from https://github.com/forcedotcom/salesforcedx-vscode/tree/develop/.claude/skills/effect-best-practices into .claude/skills/effect-best-practices/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "effect-best-practices", then confirm the skill loads.Claude Code copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$skill-installer install https://github.com/forcedotcom/salesforcedx-vscode/tree/develop/.claude/skills/effect-best-practicesType this inside Codex. $skill-installer <name> installs a curated skill from openai/skills. The installer writes to $CODEX_HOME/skills (default ~/.codex/skills). Restart Codex if the skill does not show up.
$ npx skills add forcedotcom/salesforcedx-vscode --skill effect-best-practices -a codexProject install goes to .agents/skills/; add -g for ~/.codex/skills/.
$ gh skill install forcedotcom/salesforcedx-vscode effect-best-practices --agent codexProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/forcedotcom/salesforcedx-vscode.git skills-src && mkdir -p .agents/skills && cp -r skills-src/.claude/skills/effect-best-practices .agents/skills/effect-best-practices && rm -rf skills-srcUse ~/.agents/skills/ instead of .agents/skills for a personal install.
Codex skills documentation · loads skills from .agents/skills/
Install the "effect-best-practices" agent skill from https://github.com/forcedotcom/salesforcedx-vscode/tree/develop/.claude/skills/effect-best-practices into .agents/skills/effect-best-practices/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "effect-best-practices", then confirm the skill loads.Codex copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add forcedotcom/salesforcedx-vscode --skill effect-best-practices -a cursorProject install goes to .agents/skills/; add -g for ~/.cursor/skills/.
$ gh skill install forcedotcom/salesforcedx-vscode effect-best-practices --agent cursorProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/forcedotcom/salesforcedx-vscode.git skills-src && mkdir -p .cursor/skills && cp -r skills-src/.claude/skills/effect-best-practices .cursor/skills/effect-best-practices && rm -rf skills-srcUse ~/.cursor/skills/ instead of .cursor/skills for a personal install.
Cursor skills documentation · loads skills from .cursor/skills/, .agents/skills/, .claude/skills/, .codex/skills/
Install the "effect-best-practices" agent skill from https://github.com/forcedotcom/salesforcedx-vscode/tree/develop/.claude/skills/effect-best-practices into .cursor/skills/effect-best-practices/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "effect-best-practices", then confirm the skill loads.Cursor copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gemini skills install https://github.com/forcedotcom/salesforcedx-vscode.git --path .claude/skills/effect-best-practices--scope user (default) or --scope workspace; --path is the subfolder of the repo that holds the skill; --consent skips the security confirmation prompt.
$ npx skills add forcedotcom/salesforcedx-vscode --skill effect-best-practices -a gemini-cliProject install goes to .agents/skills/; add -g for ~/.gemini/skills/.
$ gh skill install forcedotcom/salesforcedx-vscode effect-best-practices --agent gemini-cliProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/forcedotcom/salesforcedx-vscode.git skills-src && mkdir -p .gemini/skills && cp -r skills-src/.claude/skills/effect-best-practices .gemini/skills/effect-best-practices && rm -rf skills-srcUse ~/.gemini/skills/ instead of .gemini/skills for a personal install, then run /skills reload.
Gemini CLI skills documentation · loads skills from .gemini/skills/, .agents/skills/
Install the "effect-best-practices" agent skill from https://github.com/forcedotcom/salesforcedx-vscode/tree/develop/.claude/skills/effect-best-practices into .gemini/skills/effect-best-practices/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "effect-best-practices", then confirm the skill loads.Gemini CLI copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gh skill install forcedotcom/salesforcedx-vscode effect-best-practicesInstalls for Copilot at project scope by default; add --scope user for a personal install. Preview a skill first with gh skill preview. Needs GitHub CLI 2.90.0 or later (public preview).
$ npx skills add forcedotcom/salesforcedx-vscode --skill effect-best-practices -a github-copilotProject install goes to .agents/skills/; add -g for ~/.copilot/skills/.
$ git clone --depth 1 https://github.com/forcedotcom/salesforcedx-vscode.git skills-src && mkdir -p .github/skills && cp -r skills-src/.claude/skills/effect-best-practices .github/skills/effect-best-practices && rm -rf skills-srcUse ~/.copilot/skills/ instead of .github/skills for a personal install. Commit .github/skills so cloud agent and code review can use it.
GitHub Copilot skills documentation · loads skills from .github/skills/, .claude/skills/, .agents/skills/
Install the "effect-best-practices" agent skill from https://github.com/forcedotcom/salesforcedx-vscode/tree/develop/.claude/skills/effect-best-practices into .github/skills/effect-best-practices/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "effect-best-practices", then confirm the skill loads.GitHub Copilot copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add forcedotcom/salesforcedx-vscode --skill effect-best-practices -a opencodeOpenCode documents no install command of its own. Project install goes to .agents/skills/; add -g for ~/.config/opencode/skills/.
$ gh skill install forcedotcom/salesforcedx-vscode effect-best-practices --agent opencodeProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/forcedotcom/salesforcedx-vscode.git skills-src && mkdir -p .opencode/skills && cp -r skills-src/.claude/skills/effect-best-practices .opencode/skills/effect-best-practices && rm -rf skills-srcUse ~/.config/opencode/skills/ instead of .opencode/skills for a personal install.
OpenCode skills documentation · loads skills from .opencode/skills/, .claude/skills/, .agents/skills/
Install the "effect-best-practices" agent skill from https://github.com/forcedotcom/salesforcedx-vscode/tree/develop/.claude/skills/effect-best-practices into .opencode/skills/effect-best-practices/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "effect-best-practices", then confirm the skill loads.OpenCode copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
effect-best-practicesEnforces Effect-TS patterns for services, errors, layers, atoms, and Effect.pipe composition.
Effect Best Practices is an agent skill from forcedotcom/salesforcedx-vscode. Enforces Effect-TS patterns for services, errors, layers, atoms, and Effect.pipe composition. Use when writing Effect.Service, Schema.TaggedError, Layer, effect-atom, Effect.fn/.pipe, or yield pipelines.
Its SKILL.md is about 6.2k tokens, which your agent loads only when the skill is triggered. The skill folder holds 9 other files, including reference files (for example `references/anti-patterns.md`, `references/composition-style.md` and `references/diagnostics-findings.md`).
It sits in Sales & Support. It works with Salesforce. The repository describes itself as: Salesforce Extensions for VS Code. The licence is BSD-3-Clause.
Read from SKILL.md and the folder at commit f7bb6fe. It shows what the files ask for, not the result of running them.
Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.
From allowed-tools in the SKILL.md frontmatter.
Shell commands in SKILL.md call:
npxFrom the folder's file list and the shell code blocks in SKILL.md.
No URLs in SKILL.md. Its commands use npx, which can reach the network depending on how they are called.
From URLs in SKILL.md, links to its own repository left out.
Names these keys or tokens, usually read from environment variables:
API_KEYFrom names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.
Effect Best Practices loads about 6.2k tokens when it runs, and up to ~29k if it reads all its reference files. Until then it costs about 58 tokens; SKILL.md has 1,476 words of instructions outside code blocks.
Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.
The automated check found no risky patterns in SKILL.md.
Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.
The full file from forcedotcom/salesforcedx-vscode at commit f7bb6fe, republished under its BSD-3-Clause licence (© forcedotcom). 1,476 words, ~6,240 tokens.
.claude/skills/effect-best-practices/SKILL.md (or your agent's skills folder). This skill also uses 8 other files; get the full folder from GitHub.For diff/plan review against these patterns, invoke the effect-advocate subagent (.claude/agents/effect-advocate.md).
Cursor's read_lints does not surface Effect Language Server diagnostics. Use the CLI:
npx effect-language-service diagnostics --file <path>
# or whole project:
npx effect-language-service diagnostics --project tsconfig.jsonverify-on-edit.sh hook auto-runs --file <edited> on every .ts Edit/Write and surfaces output as followup_message. Address what it reports.references/diagnostics-findings.md maps each common finding to its fix; config/effect-diagnostics.json enforcedRules is the build gate.enforcedRules entry — see references/diagnostics-findings.md.--project tsconfig.json for the affected package to catch cross-file issues.effect-language-service quickfixes shows proposed code changes.| Category | DO | DON'T |
|---|---|---|
| Services | Effect.Service with accessors: true | Context.Tag for business logic |
| Dependencies | dependencies: [Dep.Default] in service | Manual Layer.provide at usage sites |
| Errors | Schema.TaggedError with message field | Plain classes or generic Error |
| Error Specificity | Split tags when catch arms or field shapes differ; telemetry dimensions are fields on one tag | Extra tags that all print message / fire the same span |
| Error Handling | catchTag/catchTags; catch only when needed | catchAll; swallowing; catching "just in case" |
| IDs | Salesforce record/org: SalesforceId/OrgId (core/schemas/salesforceId.ts). DefaultOrgInfoSchema.orgId/devHubOrgId: Schema.optional(OrgId) like cliId. Else Schema.UUID.pipe(Schema.brand("@App/EntityId")) | Plain string; getAuthInfoFields().orgId ad hoc; optionalWith as Option on DefaultOrgInfo |
| Functions | Effect.fn over Effect.gen; .gen only for shared pipes | Anonymous generators; nested Effect.gen to attach recovery; .gen for business logic |
| Composition | .pipe; const only if read ≥2×. Details: references/composition-style.md | single-use const x = yield* then f(x) |
| Params vs deps | Params = runtime data; dependencies = yield from context | Passing Ref/PubSub/service as params |
| Naming | FooCommand for commands, domain names for helpers | FooEffect suffix (redundant; TS/Effect.fn already convey type) |
| Logging | Effect.log with structured data | console.log |
| Config | Config.* with validation | process.env directly (except build-time vars like ESBUILD_*) |
| Time values | Duration.seconds(30), Duration.millis(5000); params as Duration.DurationInput. Enforced by local/no-raw-duration for a numeric arg/prop in Duration / DurationInput position only. bigint, template strings, number params, and TIMEOUT_MS constants stay outside the rule. | Numeric milliseconds as number params or TIMEOUT_MS = 30_000 constants |
| Options | Option.match with both cases | Option.getOrThrow |
| Nullability | Option<T> in domain types | null/undefined |
| Atoms | Atom.make outside components | Creating atoms inside render |
| Atom State | Atom.keepAlive for global state | Forgetting keepAlive for persistent state |
| Atom Updates | useAtomSet in React components | Atom.update imperatively from React |
| Atom Cleanup | get.addFinalizer() for side effects | Missing cleanup for event listeners |
| Resource Cleanup | Scoped service/layer + Effect.addFinalizer | Returning dispose; delegating Effect-owned resources to callers |
| Atom Results | Result.builder with onErrorTag | Ignoring loading/error states |
| Grouping | Arr.groupBy (effect/Array) | Object.groupBy, whose Partial<Record> forces a filter |
Always use Effect.Service for business logic services. This provides automatic accessors, built-in Default layer, and proper dependency declaration.
import { Effect } from 'effect';
export class UserService extends Effect.Service<UserService>()('UserService', {
accessors: true,
dependencies: [UserRepo.Default, CacheService.Default],
effect: Effect.gen(function* () {
const repo = yield* UserRepo;
const cache = yield* CacheService;
const findById = Effect.fn('UserService.findById')(function* (id: UserId) {
const cached = yield* cache.get(id);
if (Option.isSome(cached)) return cached.value;
const user = yield* repo.findById(id);
yield* cache.set(id, user);
return user;
});
const create = Effect.fn('UserService.create')(function* (data: CreateUserInput) {
const user = yield* repo.create(data);
yield* Effect.log('User created', { userId: user.id });
return user;
});
return { findById, create };
})
}) {}
// Usage - dependencies are already wired
const program = UserService.findById(userId);
// At app root
const MainLive = Layer.mergeAll(UserService.Default, OtherService.Default);When Context.Tag is acceptable:
.Default (e.g. SoqlBuilderService, implemented once by the VS Code host and once by a test fake); see references/service-patterns.mdbuildAllServicesLayer); consumers yield them, don't receive as params// WRONG - passing shared infra as params
const createStatusBar = (pubsub: PubSub.PubSub<void>, stateRef: SubscriptionRef.SubscriptionRef<State>) =>
Effect.gen(...)
// Caller must create and pass; wiring scattered at call sites
// CORRECT - yield inside, build in layer
const PubSubTag = Context.GenericTag<PubSub.PubSub<void>>("PubSub")
const createStatusBar = Effect.gen(function* () {
const pubsub = yield* PubSubTag
const stateRef = yield* StateRefTag
// ...
})
// Layer: Layer.effect(PubSubTag, PubSub.sliding<void>(1))See references/service-patterns.md for detailed patterns.
Always use Schema.TaggedError for errors. This makes them serializable (required for RPC) and provides consistent structure.
import { Schema } from 'effect';
import { HttpApiSchema } from '@effect/platform';
export class UserNotFoundError extends Schema.TaggedError<UserNotFoundError>()(
'UserNotFoundError',
{
userId: UserId,
message: Schema.String
},
HttpApiSchema.annotations({ status: 404 })
) {}
export class UserCreateError extends Schema.TaggedError<UserCreateError>()(
'UserCreateError',
{
message: Schema.String,
cause: Schema.optional(Schema.String)
},
HttpApiSchema.annotations({ status: 400 })
) {}Error handling - use catchTag/catchTags:
// CORRECT - preserves type information
yield *
repo.findById(id).pipe(
Effect.catchTag('DatabaseError', err =>
Effect.fail(new UserNotFoundError({ userId: id, message: 'Lookup failed' }))
),
Effect.catchTag('ConnectionError', err =>
Effect.fail(new ServiceUnavailableError({ message: 'Database unreachable' }))
)
);
// CORRECT - multiple tags at once
yield *
effect.pipe(
Effect.catchTags({
DatabaseError: err => Effect.fail(new UserNotFoundError({ userId: id, message: err.message })),
ValidationError: err => Effect.fail(new InvalidEmailError({ email: input.email, message: err.message }))
})
);Most errors surface to the user (message/toast at runtime). Only catch when:
Expected skip (missing optional plugin, incompatible version): write nls on the success path (guard / Match branch). fail+catchTag is for unexpected recovery — a handler that isn't "print this string."
Catch sparingly. No catchAll or "swallow to be safe." Use catchTag/catchTags; log or fail with improved error.
Split tags when catch arms or field shapes differ — e.g. message+cause vs message+cause+setting. Same catch work (print message, one span) → one tag; telemetry dimensions are fields, not tags. nls variance lives in message. Frontend/RPC still splits when the UI branches (UserNotFoundError vs ChannelNotFoundError). See references/error-patterns.md.
To continue past failures instead of short-circuiting on the first, don't hand-roll Either + catchTag + a re-loop. Use Effect.partition (both buckets), Effect.validateAll (all-or-nothing), or Effect.validateFirst. These recover the typed error channel per item but do NOT capture interruption — so a Cancel still aborts the whole loop.
See references/error-patterns.md for the accumulation/interruption nuance, error remapping, and retry patterns.
Brand all entity IDs for type safety across service boundaries.
This repo — Salesforce record/org ids are not UUIDs. Use SalesforceId/OrgId and orgIdFrom/orgIdFromConnection (getFields() / Connection; Option; references/schema-patterns.md). Other AuthFields: authFieldsFrom/authFieldsFromConnection. DefaultOrgInfoSchema.orgId/devHubOrgId: Schema.optional(OrgId) like cliId — not Option.
import { Schema } from 'effect';
// Entity IDs - always branded
export const UserId = Schema.UUID.pipe(Schema.brand('@App/UserId'));
export type UserId = Schema.Schema.Type<typeof UserId>;
export const TenantId = Schema.UUID.pipe(Schema.brand('@App/TenantId'));
export type TenantId = Schema.Schema.Type<typeof TenantId>;
// Domain types - use Schema.Struct
export const User = Schema.Struct({
id: UserId,
email: Schema.String,
name: Schema.String,
tenantId: TenantId,
createdAt: Schema.DateTimeUtc
});
export type User = Schema.Schema.Type<typeof User>;
// Input types for mutations
export const CreateUserInput = Schema.Struct({
email: Schema.String.pipe(Schema.pattern(/^[^\s@]+@[^\s@]+\.[^\s@]+$/)),
name: Schema.String.pipe(Schema.minLength(1)),
tenantId: TenantId
});
export type CreateUserInput = Schema.Schema.Type<typeof CreateUserInput>;When NOT to brand:
See references/schema-patterns.md for transforms and advanced patterns.
Prefer Effect.fn for effectful code. Provides automatic tracing with proper span names. Span name required; enforced by local/require-effect-fn-span-name. Effect.tap / *SuccessNotification middleware before catch middleware: local/effect-fn-catch-middleware-last (later catch and non-success guards stay valid). Details: services-extension-consumption Success handling.
Use Effect.gen only when you need a shared effect with common .pipe attached so multiple consumers don't each pipe the same things — e.g. provided dependencies, common error handlers, retries. (Less common with Runtimes.) Service definition bodies are a valid use (shared wiring).
// CORRECT - Effect.fn with descriptive name
const findById = Effect.fn('UserService.findById')(function* (id: UserId) {
yield* Effect.annotateCurrentSpan('userId', id);
return yield* repo.findById(id);
});
// CORRECT - Effect.fn with multiple parameters
const transfer = Effect.fn('AccountService.transfer')(function* (fromId: AccountId, toId: AccountId, amount: number) {
yield* Effect.annotateCurrentSpan('fromId', fromId);
yield* Effect.annotateCurrentSpan('toId', toId);
yield* Effect.annotateCurrentSpan('amount', amount);
// ...
});
// WRONG - params on wrapper arrow, generator has none (closure capture)
// Enforced by local/no-effect-fn-wrapper
const findByIdBad = (id: UserId) =>
Effect.fn('UserService.findById')(function* () {
yield* repo.findById(id); // id from closure
});
// WRONG - Effect.fn invoked immediately (config-enforced effectFnIife). Effect.fn builds a reusable
// function; for one-shot use write Effect.gen and keep the span with a piped withSpan.
const opened = Effect.fn('FsService.open')(function* () {
yield* fs.showTextDocument(uri);
})();
// CORRECT
const openedOk = Effect.gen(function* () {
yield* fs.showTextDocument(uri);
}).pipe(Effect.withSpan('FsService.open'));
// Naming: Don't append Effect. For commands use FooCommand; for helpers/lifecycle use domain names.
// WRONG: logGetEffect, executeAnonymousDocumentEffect, activateEffect
// CORRECT: logGetCommand, executeAnonymousCommand, executeAnonymous (helper), activation (lifecycle)See references/composition-style.md: .pipe, const only if read ≥2×, terminal runner, point-free safety, Match dispatch, guard clauses, recovery on a subsequence.
Declare dependencies in the service, not at usage sites:
// CORRECT - dependencies in service definition
export class OrderService extends Effect.Service<OrderService>()('OrderService', {
accessors: true,
dependencies: [UserService.Default, ProductService.Default, PaymentService.Default],
effect: Effect.gen(function* () {
const users = yield* UserService;
const products = yield* ProductService;
const payments = yield* PaymentService;
// ...
})
}) {}
// At app root - simple merge
const AppLive = Layer.mergeAll(
OrderService.Default,
// Infrastructure layers (intentionally not in dependencies)
DatabaseLive,
RedisLive
);See references/layer-patterns.md for testing layers and config-dependent layers.
Resources created inside an Effect service/layer belong to its scope. Prefer scoped plus
Effect.addFinalizer or Effect.acquireRelease; don't expose dispose or delegate cleanup to a host lifecycle.
export class StatusService extends Effect.Service<StatusService>()('StatusService', {
accessors: true,
scoped: Effect.gen(function* () {
const item = vscode.window.createStatusBarItem();
yield* Effect.addFinalizer(() => Effect.sync(() => item.dispose()));
return { show: Effect.sync(() => item.show()) };
})
}) {}The layer owner must close its scope. A ManagedRuntime owns its layers' scopes; dispose it during extension
deactivation. Use context.subscriptions only for resources created outside Effect ownership.
Never use Option.getOrThrow. Always handle both cases explicitly:
// CORRECT - explicit handling
yield *
Option.match(maybeUser, {
onNone: () => Effect.fail(new UserNotFoundError({ userId, message: 'Not found' })),
onSome: user => Effect.succeed(user)
});
// CORRECT - with getOrElse for defaults
const name = Option.getOrElse(maybeName, () => 'Anonymous');
// CORRECT - Option.map for transformations
const upperName = Option.map(maybeName, n => n.toUpperCase());Reactive React state via @effect-atom/atom-react. Define atoms OUTSIDE components; keepAlive for state that must persist; useAtomSet to write; Result.builder to render effectful results; get.addFinalizer to clean up listeners.
import { Atom, Result, useAtomValue, useAtomSet } from '@effect-atom/atom-react';
const countAtom = Atom.make(0); // outside the component
const prefsAtom = Atom.make({ theme: 'dark' }).pipe(Atom.keepAlive); // persistent
function Counter() {
const count = useAtomValue(countAtom);
const setCount = useAtomSet(countAtom);
return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
}
// Effectful atom → Result; handle loading/error/success
function UserProfile() {
return Result.builder(useAtomValue(userAtom))
.onInitial(() => <div>Loading...</div>)
.onErrorTag('NotFoundError', () => <div>User not found</div>)
.onError(error => <div>Error: {error.message}</div>)
.onSuccess(user => <div>Hello, {user.name}</div>)
.render();
}SubscriptionRef<A> is a mutable ref whose .changes stream always emits the current value as element 0, then all future mutations.
Implemented as (from effect/src/internal/subscriptionRef.ts):
stream.concat(stream.make(currentValue), stream.fromPubSub(pubsub))The Ref.get + pubsub subscription happen atomically under a semaphore — no events are missed.
// WRONG — prepended get is always redundant
Stream.concat(Stream.fromEffect(SubscriptionRef.get(ref)), ref.changes)
Stream.concat(Stream.make(yield* SubscriptionRef.get(ref)), ref.changes)
Stream.merge(Stream.fromEffect(SubscriptionRef.get(ref)), ref.changes)
// CORRECT — .changes already provides the snapshot
ref.changes.pipe(...)To skip the initial snapshot (e.g. avoid a spurious refresh on activation), use Stream.drop(1).
Object.groupBy is typed Partial<Record<K, V[]>>, so every consumer filters or defaults the undefined. Arr.groupBy returns Record<K, NonEmptyArray<V>> — total, so Object.entries needs no guard.
import * as Arr from 'effect/Array';
const grouped = Arr.groupBy(ids, id => id.slice(0, 3)); // Record<string, NonEmptyArray<string>>
Object.entries(grouped).map(([prefix, group]) => query(prefix, group));
// vs Object.groupBy, where the same line needs:
// .filter((entry): entry is [string, string[]] => isNotUndefined(entry[1]))Keep Object.groupBy only when the Partial is load-bearing — e.g. destructuring absent keys with defaults, const { deploys = [], deleted = [] } = ....
The DON'T column above names each forbidden pattern. references/anti-patterns.md
has the complete list — each with rationale and the correct alternative.
== null / != null are banned in effect packages (noNullCompare in eslint.config.mjs).
Pick the effect/Predicate guard by what the declared type actually admits — one guard per
union shape, no exceptions:
| declared type | guard |
|---|---|
T | undefined | isUndefined / isNotUndefined |
T | null | isNull / isNotNull |
T | null | undefined | isNullable / isNotNullable |
import * as Schema from 'effect/Schema';
import { isNotNull, isNotUndefined, isNullable, isUndefined } from 'effect/Predicate';
// T | undefined — the common case (Optional<T>, optional props, ?? sources)
if (isUndefined(maybeValue)) return;
Effect.filterOrFail(isNotUndefined, () => new NotFoundError({ message: '...' }));
// non-id string that must be non-blank — not `isNotUndefined && length > 0`
Effect.filterOrFail(Schema.is(Schema.NonEmptyString), () => new NotFoundError({ message: '...' }));
// AuthFields org id: `orgIdFrom` / `orgIdFromConnection` → `Option<OrgId>`; fail-if-missing via `Option.match`
// DefaultOrgInfo orgId: already `OrgId | undefined` (`Schema.optional(OrgId)` like cliId)
// T | null — e.g. RegExp.exec, JSON payload fields
const match = scriptRegex.exec(html);
if (isNotNull(match)) { … }
// T | null | undefined — e.g. `exclude?: vscode.GlobPattern | null` (optional AND nullable)
const arr = isNullable(exclude) ? undefined : [exclude];Runtime semantics: isUndefined is x === undefined, isNull is x === null, isNullable
is either (node_modules/effect/src/Predicate.ts:611,649,925). A wider guard than the type
needs still compiles — it just implies a case the type can't produce, so it reads as a lie
about the value. Note typeof x === 'object' && x !== null object-narrowing stays as-is;
that's a structural check, not an emptiness check.
Use Predicate.not() for filter negations; combines point-free and readability.
import { not } from 'effect/Predicate';
// CORRECT — point-free negation
arr.filter(not(isFlowTest))
// AVOID — arrow wrapper around negation
arr.filter(x => !isFlowTest(x))Works with any predicate — built-in (isString, isError, isNotUndefined, isNotNullable) or
custom. Only the wrapped predicate can be receiver-sensitive: not(obj.method)
detaches the receiver, so verify the impl uses no this first (see
composition-style).
Keep ! where the predicate isn't element-level (!isCollectionType(decl.type))
unless a named element predicate already exists or is worth adding.
Barrel imports from @effect/platform bundle HttpApiSwagger (Swagger UI), which esbuild cannot tree-shake. This bloats web/desktop bundles by ~5.5MB per output and can trigger security scanner false positives (e.g., ClamAV signatures).
Always import the submodule as a namespace (the submodule is the namespace — it has no self-named export):
// CORRECT — deep namespace import, tree-shakes unused
import * as FetchHttpClient from '@effect/platform/FetchHttpClient';
// use: FetchHttpClient.layer
// WRONG — barrel import drags in HttpApiSwagger
import { FetchHttpClient } from '@effect/platform';Matches the repo's import * as Effect from 'effect/Effect' style. Applies to all @effect/* packages. Check import source before committing.
// Structured logging
yield * Effect.log('Processing order', { orderId, userId, amount });
// Metrics
const orderCounter = Metric.counter('orders_processed');
yield * Metric.increment(orderCounter);
// Config with validation
const config = Config.all({
port: Config.integer('PORT').pipe(Config.withDefault(3000)),
apiKey: Config.secret('API_KEY'),
maxRetries: Config.integer('MAX_RETRIES').pipe(
Config.validate({ message: 'Must be positive', validation: n => n > 0 })
)
});See references/observability-patterns.md for metrics and tracing patterns.
For detailed patterns, consult these reference files in the references/ directory:
composition-style.md - .pipe; const only if read ≥2×; terminal runner; point-free safety; tap for side effects; Match dispatch; guard clauses; linear body as point-free pipe vs generator; recovery on a subsequence (pipe from the first Effect — not a nested Effect.gen)service-patterns.md - Service definition, Effect.fn, Context.Tag exceptionserror-patterns.md - Schema.TaggedError, error remapping, retry patternsschema-patterns.md - Branded types, transforms, Schema.Classlayer-patterns.md - Dependency composition, testing layersanti-patterns.md - Complete list of forbidden patternsdiagnostics-findings.md - Effect LS finding → fix, per ruleobservability-patterns.md - Logging, metrics, config patterns© forcedotcom, BSD-3-Clause. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file
SKILL.md and 8 other files (references) in .claude/skills/effect-best-practices of forcedotcom/salesforcedx-vscode.
Open the folder on GitHubat commit f7bb6fe
Effect Best Practices next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.
| Skill | Stars | Used in | Tokens | Auto-check | Licence | Repo updated |
|---|---|---|---|---|---|---|
| Effect Best Practices this skillforcedotcom/salesforcedx-vscode | 1k | — | ~6.2k | Automated safety check: Pass | BSD-3-Clause | |
| Soql Lib Query Builderbeyond-the-cloud-dev/soql-lib | 154 | — | ~4.3k | Automated safety check: Pass | MIT | |
| Sf DatacloudJaganpro/sf-skills | 424 | — | ~2.7k | Automated safety check: Pass | MIT | |
| Soql Lib Selectorbeyond-the-cloud-dev/soql-lib | 154 | — | ~2k | Automated safety check: Pass | MIT | |
| Dev SetupPortwood-Global-Solutions/Portwood | 125 | — | ~1.1k | Automated safety check: Pass | Apache-2.0 | |
| Sf FlowJaganpro/sf-skills | 424 | — | ~1.8k | Automated safety check: Pass | MIT |
beyond-the-cloud-dev/soql-lib
Builds Salesforce SOQL queries using the SOQL Lib fluent builder API (SOQL.cls).
Jaganpro/sf-skills
Salesforce Data Cloud product orchestrator for connect→prepare→harmonize→segment→act workflows.
beyond-the-cloud-dev/soql-lib
Creates Salesforce Apex selector classes using the SOQL Lib selector pattern.
Portwood-Global-Solutions/Portwood
Get from a fresh clone of Portwood to a working, fully-tested Salesforce org.
Jaganpro/sf-skills
Creates and validates Salesforce Flows with 110-point scoring.
forcedotcom/sf-skills
Apply a Salesforce sandbox post-copy automation JSON config against a target org.
forcedotcom/salesforcedx-vscode
Command palette, CodeLens, context menus, package.nls titles, and NotificationModeService.
forcedotcom/salesforcedx-vscode
Consume the salesforcedx-vscode-services extension API. An agent skill from forcedotcom/salesforcedx-vscode.
forcedotcom/salesforcedx-vscode
Polish the automated CHANGELOG on develop before the next stable build.
forcedotcom/salesforcedx-vscode
Public API exported by salesforcedx-vscode-core activate(). An agent skill from forcedotcom/salesforcedx-vscode.
forcedotcom/salesforcedx-vscode
Operate a real VS Code instance through drivable-vscode. An agent skill from forcedotcom/salesforcedx-vscode.
forcedotcom/salesforcedx-vscode
Known external consumers of APIs from this monorepo's extensions.
Works with
Categories
Enforces Effect-TS patterns for services, errors, layers, atoms, and Effect.pipe composition. Effect Best Practices is an agent skill from forcedotcom/salesforcedx-vscode.pipe composition.
Effect Best Practices fits situations like: writing Effect.Service; schema.TaggedError; effect.fn/.pipe; yield pipelines.
Run `npx skills add forcedotcom/salesforcedx-vscode --skill effect-best-practices -a claude-code`. Or copy the skill folder (.claude/skills/effect-best-practices in forcedotcom/salesforcedx-vscode) into .claude/skills/effect-best-practices in your project. Claude Code loads it when a task matches its description.
Run `npx skills add forcedotcom/salesforcedx-vscode --skill effect-best-practices -a codex`. Or copy the skill folder (.claude/skills/effect-best-practices in forcedotcom/salesforcedx-vscode) into .agents/skills/effect-best-practices in your project. Codex loads it when a task matches its description.
Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add forcedotcom/salesforcedx-vscode --skill effect-best-practices -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/effect-best-practices, .gemini/skills/effect-best-practices, .github/skills/effect-best-practices and .opencode/skills/effect-best-practices in your project.
Going by SKILL.md and its folder, Effect Best Practices needs the command-line tools its instructions call (npx) and credentials named API_KEY. Our summary lists: Node.js.
SKILL.md contains no URLs. Its commands use npx, which can reach the network depending on how they are called. This is read from the text; nothing was executed.
Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.
Effect Best Practices is published under the BSD-3-Clause licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.
About 6.2k tokens (SKILL.md is roughly 25k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full. Its references folder adds about 23k tokens, read only when the agent opens those files.
Skills that share tags, products or a category with Effect Best Practices: Soql Lib Query Builder (beyond-the-cloud-dev/soql-lib, 154 stars), Sf Datacloud (Jaganpro/sf-skills, 424 stars), Soql Lib Selector (beyond-the-cloud-dev/soql-lib, 154 stars) and Dev Setup (Portwood-Global-Solutions/Portwood, 125 stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.
forcedotcom (a GitHub organization) maintains it in forcedotcom/salesforcedx-vscode, which has 1,035 GitHub stars. The repository holds 37 skills in this directory. The repository was last updated on October 7, 2026.
Source: forcedotcom/salesforcedx-vscode on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.