| name | services-extension-consumption |
| description | Guidelines for consuming salesforcedx-vscode-services extension API. Use when working with extensions that have extensionDependency on salesforcedx-vscode-services, registering commands, using Workspace/Connection/Project/Settings/FS/Channel/Media services, quickpick/quickInput, or implementing file/config watchers. Use when this capability is needed. |
| metadata | {"author":"forcedotcom"} |
Consuming salesforcedx-vscode-services
Extensions depending on salesforcedx-vscode-services. Examples: salesforcedx-vscode-metadata, salesforcedx-vscode-org-browser.
Getting the API
Use ExtensionProviderService from @salesforce/effect-ext-utils:
import { ExtensionProviderService, getServicesApi } from '@salesforce/effect-ext-utils';
const ExtensionProviderServiceLive = Layer.effect(
ExtensionProviderService,
Effect.sync(() => ({
getServicesApi
}))
);
const api = yield * (yield * ExtensionProviderService).getServicesApi;
Prebuilt vs Per-Extension Services
api.services.prebuiltServicesDependencies — pre-built Context.Context from services extension activation. Wrap with Layer.succeedContext(...).
Shares singleton instances (caches, watchers) across extensions; avoids re-building stateful services.
Per-extension layers (must build yourself):
| Layer | Why |
|---|
ChannelServiceLayer(displayName) | Own output channel |
ErrorHandlerService.Default | Depends on own ChannelService |
ExtensionContextServiceLayer(context) | Own ExtensionContext |
SdkLayerFor(context) | Own tracer (extension name/version in resource attributes) |
ExtensionProviderServiceLive | Local singleton |
ExtensionContext Setup
Factory function building services layer with ExtensionContext:
export const buildAllServicesLayer = (context: ExtensionContext) =>
Layer.unwrapEffect(
Effect.gen(function* () {
const extensionProvider = yield* ExtensionProviderService;
const api = yield* extensionProvider.getServicesApi;
const channelLayer = api.services.ChannelServiceLayer(
context.extension.packageJSON.displayName ?? 'My Extension'
);
const errorHandlerWithChannel = Layer.provide(api.services.ErrorHandlerService.Default, channelLayer);
return Layer.mergeAll(
Layer.succeedContext(api.services.prebuiltServicesDependencies),
ExtensionProviderServiceLive,
errorHandlerWithChannel,
api.services.ExtensionContextServiceLayer(context),
api.services.SdkLayerFor(context),
channelLayer
);
}).pipe(Effect.provide(ExtensionProviderServiceLive))
);
In activate:
export const activate = async (context: vscode.ExtensionContext): Promise<void> => {
const extensionScope = Effect.runSync(getExtensionScope());
setAllServicesLayer(buildAllServicesLayer(context));
await getRuntime().runPromise(activateEffect(context).pipe(Scope.extend(extensionScope)));
};
Runtime vs provide
- Do: Build
ManagedRuntime.make(AllServicesLayer) and export getRuntime().
- Do: Use
getRuntime().runPromise(effect) / runFork(effect) for ad-hoc execution.
- Don't: Use
Effect.provide(AllServicesLayer) at call sites — use the runtime instead.
- Exception:
registerCommandWithLayer(AllServicesLayer) — keep passing the Layer; it internally uses provide.
Registering Commands
Use registerCommandWithLayer (for layers) or registerCommandWithRuntime (for runtimes):
import { myCommandEffect } from './commands/myCommand';
const api = yield * (yield * ExtensionProviderService).getServicesApi;
const registerCommand = api.services.registerCommandWithLayer(AllServicesLayer);
yield * registerCommand('sf.my.command', myCommandEffect);
const registerCommand = api.services.registerCommandWithRuntime(getRuntime());
yield * registerCommand('sf.my.command', myCommandEffect);
Commands auto:
- Register with ExtensionContext subscriptions
- Wrap with error handling
- Trace with observability spans
- Handle Cancellation
Basic Services
Accessor pattern: call methods directly, don't assign to variable first.
Watchers
File Watching
FileWatcherService exposes a PubSub of all workspace file changes (**/*). Subscribe and filter:
import * as PubSub from 'effect/PubSub';
import * as Stream from 'effect/Stream';
const fileWatcher = yield * api.services.FileWatcherService;
const dequeue = yield * PubSub.subscribe(fileWatcher.pubsub);
yield *
Stream.fromQueue(dequeue).pipe(
Stream.filter(event => ),
Stream.runForEach(event =>
Effect.sync(() => {
})
)
);
Config Watching
Watch VS Code config changes:
import * as PubSub from 'effect/PubSub';
import * as Stream from 'effect/Stream';
import * as Duration from 'effect/Duration';
const pubsub = yield * PubSub.sliding<vscode.ConfigurationChangeEvent>(100);
const disposable = vscode.workspace.onDidChangeConfiguration(event => {
Effect.runSync(PubSub.publish(pubsub, event));
});
yield *
Effect.addFinalizer(() =>
Effect.sync(() => {
disposable?.dispose();
})
);
yield *
Stream.fromPubSub(pubsub).pipe(
Stream.filter(event => event.affectsConfiguration('section.setting')),
Stream.debounce(Duration.()),
.( {
})
);
Target Org Changes
Watch org changes via TargetOrgRef (SubscriptionRef):
const ref = yield * api.services.TargetOrgRef();
yield *
ref.changes.pipe(
Stream.map(org => org.orgId),
Stream.changes,
Stream.tap(orgId => {
}),
Stream.runForEach(() => {
})
);
Complete Example Pattern
import * as ManagedRuntime from 'effect/ManagedRuntime';
export const buildAllServicesLayer = (context: ExtensionContext) =>
Layer.unwrapEffect(
Effect.gen(function* () {
const extensionProvider = yield* ExtensionProviderService;
const api = yield* extensionProvider.getServicesApi;
const channelLayer = api.services.ChannelServiceLayer(
context.extension.packageJSON.displayName ?? 'My Extension'
);
const errorHandlerWithChannel = Layer.provide(api.services.ErrorHandlerService.Default, channelLayer);
return Layer.mergeAll(
Layer.succeedContext(api.services.prebuiltServicesDependencies),
ExtensionProviderServiceLive,
errorHandlerWithChannel,
api.services.ExtensionContextServiceLayer(context),
api.services.(context),
channelLayer
);
}).(.())
);
: < buildAllServicesLayer>;
= () => {
= layer;
};
= () => .();
: < createRuntime> | ;
= () => {
_runtime ??= ();
_runtime;
};
{ myCommandEffect } ;
activateEffect = .()(* (: vscode.) {
api = * (* ).;
* api...();
registerCommand = api..();
* (, myCommandEffect);
* api...();
});
Common Patterns
- Start with
Layer.succeedContext(api.services.prebuiltServicesDependencies) — don't add individual *.Default for services already there
- Only add per-extension layers on top
import { ICONS } outside Effect; MediaService inside Effect
ChannelServiceLayer before ErrorHandlerService
- Pass
context to SdkLayerFor (extracts name/version from ExtensionContext)
Effect.forkIn(..., yield* getExtensionScope()) for watcher cleanup on deactivation
registerCommandWithLayer for all commands (tracing + error handling)
- Use
getRuntime().runPromise / runFork instead of Effect.provide(AllServicesLayer) for execution
Converted and distributed by TomeVault — claim your Tome and manage your conversions.