feat!: @rudder/sdk 0.1.0: createClient, idempotent modules.call retries, storage save(type, data), drop resources and projectStorage

This commit is contained in:
edmand46
2026-09-17 15:26:42 +03:00
parent c25b1c2c61
commit 1e4e0d928d
35 changed files with 474 additions and 467 deletions
+52 -4
View File
@@ -1,11 +1,59 @@
import type { RudderContext } from '../core/context.js';
import { api } from '../generated/api.js';
import type { CallModuleFunctionClientResponse } from '../generated/modules.js';
import { uuidv4 } from '../core/uuid.js';
import { RudderHttpError, RudderModuleError } from '../client/RudderError.js';
import type {
CallModuleFunctionClientResponse,
CallModuleFunctionRequest,
ModuleErrorResponse,
} from '../generated/modules.js';
export interface ModuleCallOptions {
/** Version the caller was built against; sent as `X-Rudder-Module-Version: <module>@<version>`. */
moduleVersion?: string;
}
/**
* ModulesDomain — calls module functions. Usually reached through a client
* generated by `rudder client generate`, not directly.
*/
export class ModulesDomain {
constructor(private readonly ctx: RudderContext) {}
call(module: string, fn: string, args?: unknown): Promise<CallModuleFunctionClientResponse> {
return api.callModuleFunction(this.ctx, { module, fn }, { args });
/**
* Calls `module.fn` and returns its `result`. Each call gets one
* Idempotency-Key reused across retries (network errors, 5xx, 409 `conflict`).
* Rejects with {@link RudderModuleError} on 422.
*/
async call<T = unknown>(
module: string,
fn: string,
args?: unknown,
options?: ModuleCallOptions,
): Promise<T> {
const headers: Record<string, string> = { 'Idempotency-Key': uuidv4() };
if (options?.moduleVersion) {
headers['X-Rudder-Module-Version'] = `${module}@${options.moduleVersion}`;
}
const path = `/sdk/v1/modules/${encodeURIComponent(module)}/${encodeURIComponent(fn)}`;
try {
const response = await this.ctx.request<CallModuleFunctionClientResponse>(
'POST',
path,
{ args: args ?? {} } satisfies CallModuleFunctionRequest,
{ headers, retry: true },
);
return response?.result as T;
} catch (err) {
if (err instanceof RudderHttpError && err.status === 422 && err.code) {
let payload: Partial<ModuleErrorResponse> = {};
try {
payload = JSON.parse(err.body);
} catch {
// err.code came from this body, so it is JSON; keep the fallback anyway.
}
throw new RudderModuleError(err.statusText, err.body, { ...payload, code: err.code });
}
throw err;
}
}
}
+2 -1
View File
@@ -4,7 +4,8 @@ import { api } from '../generated/api.js';
import type { PlayerProfile } from '../generated/player.js';
/**
* PlayerDomain — the observable player profile (identity + wallets).
* PlayerDomain — the observable player profile (player, identities,
* wallets, counters).
* Synced under revision key `profile`.
*/
export class PlayerDomain extends SyncedState<PlayerProfile> {
-26
View File
@@ -1,26 +0,0 @@
import { SyncedState } from '../state/SyncedState.js';
import type { RudderContext } from '../core/context.js';
import { api } from '../generated/api.js';
import type {
GetProjectStorageResponse,
ProjectStorageUpdateItem,
} from '../generated/project-storage.js';
/**
* ProjectStorageDomain — project-wide key/value storage as an observable
* entity, plus its mutations. Synced under revision key `projectStorage`;
* mutations invalidate it so subscribers observe fresh data.
*/
export class ProjectStorageDomain extends SyncedState<GetProjectStorageResponse> {
readonly syncKey = 'projectStorage';
constructor(private readonly ctx: RudderContext) {
super(() => api.getProjectStorage(ctx, { limit: 100 }));
}
/** Saves project storage items, then invalidates. */
async save(items: ProjectStorageUpdateItem[]): Promise<void> {
await api.updateProjectStorage(this.ctx, { items });
this.invalidate();
}
}
-43
View File
@@ -1,43 +0,0 @@
import type { RudderContext } from '../core/context.js';
import { api } from '../generated/api.js';
import type {
CreateResourceRowRequest,
ListResourceRowsResponse,
ResourceRow,
UpdateResourceRowRequest,
} from '../generated/resources.js';
export class ResourceHandle {
constructor(
private readonly ctx: RudderContext,
private readonly slug: string,
) {}
list(): Promise<ListResourceRowsResponse> {
return api.listResourceRows(this.ctx, { slug: this.slug });
}
get(id: string): Promise<ResourceRow> {
return api.getResourceRow(this.ctx, { slug: this.slug, id });
}
create(body: CreateResourceRowRequest): Promise<ResourceRow> {
return api.createResourceRow(this.ctx, { slug: this.slug }, body);
}
update(id: string, body: UpdateResourceRowRequest): Promise<ResourceRow> {
return api.updateResourceRow(this.ctx, { slug: this.slug, id }, body);
}
delete(id: string): Promise<void> {
return api.deleteResourceRow(this.ctx, { slug: this.slug, id });
}
}
export class ResourcesDomain {
constructor(private readonly ctx: RudderContext) {}
of(slug: string): ResourceHandle {
return new ResourceHandle(this.ctx, slug);
}
}
+7 -7
View File
@@ -1,11 +1,11 @@
import { SyncedState } from '../state/SyncedState.js';
import type { RudderContext } from '../core/context.js';
import { api } from '../generated/api.js';
import type { GetStorageResponse, StorageItem } from '../generated/storage.js';
import type { GetStorageResponse } from '../generated/storage.js';
/**
* StorageDomain — player key/value storage as an observable entity, plus its
* mutations. Synced under revision key `storage`; mutations invalidate it so
* StorageDomain — player storage (one item per type) as an observable entity,
* plus its mutations. Synced under revision key `storage`; mutations invalidate it so
* subscribers observe fresh data.
*/
export class StorageDomain extends SyncedState<GetStorageResponse> {
@@ -15,13 +15,13 @@ export class StorageDomain extends SyncedState<GetStorageResponse> {
super(() => api.getStorage(ctx, { limit: 100 }));
}
/** Saves player storage items, then invalidates. */
async save(items: StorageItem[]): Promise<void> {
await api.updateStorage(this.ctx, { items });
/** Upserts the item of `type` with raw string `data`, then invalidates. */
async save(type: string, data: string): Promise<void> {
await api.updateStorage(this.ctx, { items: [{ type, data }] });
this.invalidate();
}
/** Deletes player storage items of the given type, then invalidates. */
/** Deletes the player storage item of `type`, then invalidates. */
async delete(type: string): Promise<void> {
await api.deleteStorage(this.ctx, { type });
this.invalidate();