Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions agent_docs/conventions.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
- **UPPER_SNAKE_CASE**: constants (`DEFAULT_PAGE_SIZE`, `TASK_ENDPOINTS`)
- **File names**: kebab-case for general files (`api-client.ts`), dot-separated for type/model files (`tasks.types.ts`, `tasks.models.ts`)
- Prefer `private` keyword over underscore prefix for private methods
- **Keep inline code comments concise** — say what is non-obvious in a single phrase. Avoid restating what the code already expresses or adding multi-sentence commentary where a short comment suffices.
- No `any` type — use `unknown` if truly unknown, then validate.
- **NEVER** use `as unknown as` type casts — refactor to make types flow naturally. Casts hide real type errors and break when upstream types change. **One accepted exception**: `transformData()` returns `Record<string, unknown>`, so `transformData(data, EntityMap) as unknown as TargetType` is permitted when no typed overload is available. Every other `as unknown as` must be refactored. **Refinement**: when `pascalToCamelCaseKeys()` precedes `transformData()` in the pipeline, `pascalToCamelCaseKeys()` returns `any`, which makes `transformData(camelCased, EntityMap)` also return `any`. In that case a single `as TargetType` cast is sufficient — drop the `as unknown` intermediate.
- **Generic constraints on utility functions: use `T extends object`, not `T extends Record<string, unknown>`** — interface types (e.g., `JobGetAllOptions`) do not structurally satisfy `Record<string, unknown>` because they lack an index signature, causing TS2345 at call sites. Use `T extends object` (matching the `transformRequest` pattern) and cast internally: `const result: Record<string, unknown> = { ...options as Record<string, unknown> }`. **NEVER** add a redundant `as T` cast when the function signature already declares the return type as `T` — the compiler infers it and the extra cast is noise.
Expand Down Expand Up @@ -101,8 +102,12 @@ Transform functions live in `src/utils/transform.ts`. Not every service uses eve
- Time: `creationTime`/`createdAt` → `createdTime`, `lastModificationTime` → `lastModifiedTime`, `startedTimeUtc` → `startedTime`, `completedTimeUtc` → `completedTime`, `expiryTimeUtc` → `expiredTime`
- Folder: `organizationUnitId` → `folderId`, `organizationUnitFullyQualifiedName` → `folderName`

**Timestamp field naming**: All timestamp fields exposed in SDK response types must use the `*Time` suffix (e.g., `endedTime`, `createdTime`). **NEVER** introduce `*At` suffixes (e.g., `endedAt`) — this breaks consistency with every other timestamp across the SDK and makes the API surface feel unpolished.

**Outbound requests** (SDK → API): use `transformRequest(data, {Entity}Map)` (auto-reverses field map) and `camelToPascalCaseKeys()`.

**Multi-domain responses:** When a `getById` response includes fields from multiple entity domains (e.g., an attachment response that also contains bucket fields), merge multiple field maps in step 2: `transformData(data, { ...PrimaryEntityMap, ...SecondaryEntityMap })`. Using only the primary entity's map silently skips rename entries from the secondary domain.

**Field maps vs case conversion:** `{Entity}Map` is for semantic renames only. Case conversion is handled by `pascalToCamelCaseKeys()`. **NEVER** add case-only entries to a field map — mixing them causes double-conversion bugs.

**Data Fabric exception:** Do NOT apply `pascalToCamelCaseKeys()` or any field-rename transforms to Data Fabric entity record data (`EntityRecord`, record fields returned by `getRecordById`, `getAllRecords`, etc.). DF entity field names are user-defined schema columns and must be returned exactly as the API sends them — casing is part of the schema contract. Only system-generated DF fields (e.g., `Id`, `CreatedBy`) use PascalCase, and those are also left untransformed to keep behavior consistent.
Expand Down
7 changes: 5 additions & 2 deletions agent_docs/rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ Every new method must also have an integration test in `tests/integration/shared
- Use `generateRandomString()` from `tests/integration/utils/helpers.ts` for unique test data
- Tests run in both `v0` and `v1` init modes via `describe.each(modes)` — **only if the service is registered in both modes in `unified-setup.ts`**. New services that only support `v1` init should use `['v1']` only.
- **Always `throw new Error()` when test preconditions are not met** — whether it's missing config (e.g., no `folderId`) or missing test data (e.g., no running jobs). Never use `console.warn()` + `return` to silently skip — silent skips hide unrunnable tests and make CI green when tests aren't actually exercised.
- **`describe.skip` is permitted only when the service does not support PAT auth** — e.g., `insightsrtm_` endpoints reject PAT tokens entirely (returns 401 regardless of scopes) and require OAuth. In this case, write the full test body as if it will eventually run, add a comment explaining the limitation (e.g., `// skip: insightsrtm_ endpoints do not support PAT auth — requires OAuth`), and use `describe.skip` rather than omitting the test entirely. This preserves the test structure for when PAT support is added. Do **not** use `describe.skip` for missing test data, missing config, or flakiness — those require a `beforeAll` guard or a `throw`.
- **`describe.skip` is permitted only when the service does not support PAT auth** — e.g., `insightsrtm_` endpoints reject PAT tokens entirely (returns 401 regardless of scopes) and require OAuth. In this case, write the full test body as if it will eventually run, add a comment explaining the limitation (e.g., `// skip: insightsrtm_ endpoints do not support PAT auth — requires OAuth`), and use `describe.skip` rather than omitting the test entirely. This preserves the test structure for when PAT support is added. Do **not** use `describe.skip` for missing test data, missing config, or flakiness — those require a `beforeAll` guard or a `throw`. Equivalently, **NEVER** exclude integration test files via `vitest.integration.config.ts` using env vars or file exclusion patterns — that is functionally equivalent to `describe.skip` across an entire file and has the same problem: tests appear to pass but are never actually exercised. Guard with `beforeAll` + `throw` inside the test file instead.
- **Use snapshot+restore for integration tests that mutate shared state** — when an operation has wide-reaching, hard-to-undo side effects (e.g., marking all notifications as read, clearing a queue), read the current state before the test, perform the operation, then restore the previous state in cleanup. This prevents one test from permanently altering the shared environment and corrupting subsequent tests.
- **NEVER** write redundant integration tests — each test must cover a distinct code path, error scenario, or response shape aspect.
- **NEVER** test client-side `ValidationError` in integration tests — a call that throws before making any HTTP request (e.g., `updateValueById('', ...)` failing on empty ID) belongs in unit tests, not integration tests. Integration tests must only exercise code paths that reach the actual API.
- **Extract shared data lookups to `beforeAll`** in integration tests — when multiple tests in a `describe` block need the same setup data (e.g., fetching an existing resource to run further operations against it), fetch it once in `beforeAll` and store in a `let variable!: Type` variable. Repeating `getAll` or equivalent calls inside each `it` block wastes API quota and slows the suite.
Expand Down Expand Up @@ -65,8 +66,10 @@ JSDoc comments in `src/models/{domain}/*.models.ts` are the **source of truth fo
- **NEVER** assign a `void` return to a variable in JSDoc examples — if a method returns `Promise<void>`, write `await service.method()` not `const result = await service.method()`. Assigning void implies the return value is usable, which misleads users.
- **When a new option or feature is added to one method, update related methods' `@example` blocks too** — if `create()` gains an `agentInput` option, `updateById()` (which accepts the same option) also needs a second `@example` showing it. Users look at examples to learn what's possible; a feature shown on one method but silently omitted from a parallel method is effectively invisible.
- **Add JSDoc to non-obvious enum values** — if an enum has values whose meaning isn't clear from the name alone, add a brief comment to each value.
- **NEVER use unexplained abbreviations or acronyms in JSDoc** — spell out the full form (e.g., write "Automation Governance Units" not "AGU"). If the abbreviated form is necessary for brevity, introduce it with the full form first: `Automation Governance Units (AGU)`.
- **NEVER use `@internal` on individual fields of a public interface** — TypeDoc omits the field from generated docs, but TypeScript consumers still see it in IDE autocomplete, creating a confusing experience. To hide a field from docs, move the type to an internal type file or restructure the public interface. `@internal` belongs only on whole declarations (methods, types, classes, interfaces), not on individual fields within a public type.
- **JSDoc `@example` blocks that reference named types must include the import statement** — if an example uses `EntityAggregateFunction` or any other imported type, open the block with the necessary import so the example is self-contained and copy-pasteable without IDE assistance.
- **Propagate `@internal` to every public layer that exposes the same API** — if an underlying service method is tagged `@internal`, the corresponding wrapper on the `UiPath` class (or any other public-facing class) must also carry `@internal`. TypeDoc runs directly on public class methods; a missing tag on one layer surfaces the method in generated docs even if the service layer correctly marks it.
- **Propagate `@internal` and `@experimental` to every public layer that exposes the same API** — if an underlying service method is tagged `@internal` or `@experimental`, the corresponding wrapper on the `UiPath` class (or any other public-facing class) must also carry the same tag. TypeDoc runs directly on public class methods; a missing tag on one layer surfaces the method in generated docs even if the service layer correctly marks it. This applies equally to `@experimental` — marking a service model `@experimental` without also marking the service class leaves the class-level JSDoc inconsistent with the published API status.
- **Do not duplicate backend validation SDK-side** — when an API parameter has backend-enforced constraints (e.g., character limits, allowed formats), document the constraint in JSDoc (`@param search - Search string (1–100 chars)`) rather than adding matching SDK-side validation. SDK-side checks risk drifting out of sync with backend rules as the API evolves, producing confusing double-error scenarios.
- **NEVER** expose internal implementation details in user-facing JSDoc — remove references to `OData`, internal API protocols, or internal endpoint names (e.g., "via the OData GetFiles endpoint", "with OData query options"). End users don't need to know which protocol or internal API mechanism the SDK uses; such details belong in code comments or internal docs, not in generated API documentation.

Expand Down
Loading