Appearance
Services
Services are server-side dependencies injected into action contexts. They give your actions access to platform capabilities like AI, blob storage, permissions, and cross-store communication.
PlatformServices
All synced-store apps share a single PlatformServices type. Every field is available on every action invocation — apps that don't need a particular service simply ignore it.
Poe Employee Note
Services are very rough right now — the API we launch with will be different from what's documented below. In particular, ctx.services will likely be renamed to ctx.platform, ctx.environment, or ctx.poe. See the type definition and the implementation.
Available Services
| Service | Type | Description |
|---|---|---|
getPoeApiKey | () => Promise<string | null> | Resolve the Poe API key for the current user |
systemTools | ExecutableTool[] | Executable tools the LLM can call during streaming (e.g. bash tool) |
callStoreAction | (storeTypeId, storeInstanceId, actionName, actionInput) => Promise<...> | Call an action on another store instance |
getStoreSchema | (storeTypeId) => Promise<...> | Get the schema for a store type (used to build tool definitions) |
blobStorage | IBlobStorage | Content-addressable blob storage |
env | { BASE_URL, BLOB_HOST } | Environment variables available to actions |
publishApp | (input) => Promise<...> | Publish an app to the marketplace |
permissions | { check, listGranted, listAvailableScopes } | Manage Poe permissions (e.g. point usage) and access to connected third-party accounts (e.g. GitHub, Google Drive) |
Full Type
typescript
import type { IBlobStorage } from "@ai-app/blob-storage";
import type { ExecutableTool } from "@ai-app/poe-api-js";
type PlatformServices = {
getPoeApiKey: () => Promise<string | null>;
systemTools: ExecutableTool[];
callStoreAction: (
storeTypeId: string,
storeInstanceId: string,
actionName: string,
actionInput: unknown,
) => Promise<{
success: boolean;
result?: unknown;
error?: { type: string; message: string };
}>;
getStoreSchema: (storeTypeId: string) => Promise<{
success: boolean;
result?: StoreSchemaInfo;
error?: { type: string; message: string };
}>;
blobStorage: IBlobStorage;
env: { BASE_URL: string; BLOB_HOST: string };
publishApp: (input: { handle: string; html: string }) => Promise<
| { success: true; app: { id: string; handle: string; /* ... */ } }
| { success: false; error: string }
>;
permissions: {
check: (scope: string) => Promise<boolean>;
listGranted: () => Promise<string[]>;
listAvailableScopes: () => Array<{
scope: string;
displayName: string;
description: string;
requiresOAuth: boolean;
}>;
};
};Using Services in Your App
1. Alias the type in your schema
Each app aliases PlatformServices to a local name for readability:
typescript
// schema.ts
import type { PlatformServices } from "@ai-app/slop-poe-platform-types";
export type MyAppServices = PlatformServices;2. Pass it to your backend config
typescript
// backend-config.ts
import { defineBackendConfig } from "@synced-store/backend";
import { mySchema } from "./schema";
import type { MyAppServices } from "./schema";
import { myMutators } from "./mutators";
import { myActions } from "./actions";
export const myBackendConfig = defineBackendConfig<
typeof mySchema,
MyAppServices
>({
schema: mySchema,
mutators: myMutators,
actions: myActions,
});3. Access services in actions
Services are available via ctx.services in action handlers:
typescript
// actions.ts
export const myActions = {
callBot: async (ctx, input) => {
const apiKey = await ctx.services.getPoeApiKey();
// Use apiKey to make Poe API calls...
},
saveFile: async (ctx, input) => {
const hash = await ctx.services.blobStorage.put(input.content);
// Store the hash in your synced-store table...
},
checkAccess: async (ctx, input) => {
const allowed = await ctx.services.permissions.check("files:write");
if (!allowed) throw new Error("Permission denied");
// Proceed with the operation...
},
};TIP
Services are only available in actions, not mutators. Mutators run on both client and server, so they cannot access server-side dependencies.
Testing with Mock Services
For tests, create a mock implementation of PlatformServices:
typescript
import type { PlatformServices } from "@ai-app/slop-poe-platform-types";
import { MemoryBlobStorage } from "@ai-app/blob-storage";
function createMockPlatformServices(
overrides?: Partial<PlatformServices>,
): PlatformServices {
return {
getPoeApiKey: async () => "test-api-key",
systemTools: [],
callStoreAction: async () => ({ success: true }),
getStoreSchema: async () => ({ success: false }),
blobStorage: new MemoryBlobStorage(),
env: { BASE_URL: "http://localhost:8787", BLOB_HOST: "http://localhost:8788" },
publishApp: async () => ({ success: false, error: "not implemented" }),
permissions: {
check: async () => true,
listGranted: async () => [],
listAvailableScopes: () => [],
},
...overrides,
};
}Then pass it via createServices in your test setup:
typescript
const runner = createLocalStoreFunctionRunner({
...myBackendConfig,
createServices: () => createMockPlatformServices(),
});