Skip to content

ccs-files-client changelog

Versions follow semver. For the package itself see ccs-files-client. The reference is at /sdks/ccs-files-client/reference/.

The File Service’s address is two variables, FILES_API_URL and FILES_API_RESOURCE, the same shape as every CCS service.

  • Default endpoints, per value: the option (apiUrl / resource) > FILES_API_URL / FILES_API_RESOURCE > the CCS_ENVIRONMENT preset (local | staging | production, default production). When only one of the two is given (option or variable), the other is derived from it if it has the standard shape (…/api/v1 ↔ …/api), else it is the preset’s; a lone apiUrl option now carries its resource with it. Each value must be an absolute http(s) URL: a malformed one (options included) throws at construction, naming it.
  • filesEndpoints({ apiUrl?, resource?, env? }) returns { apiUrl, resource }.
  • The single File Service origin variable of 1.4.0, the origin option of filesEndpoints() and FilesEndpoints.origin: set FILES_API_URL + FILES_API_RESOURCE (or CCS_ENVIRONMENT).
  • FilesEndpoints.deprecation.
  • FileLock.user_id, which the service never sent; the holder is holder_user_id.

Default endpoints. Source-compatible with 1.3.0: every option you pass today keeps its meaning.

  • issuerUrl, apiUrl and resource are optional. Omitted, they are the production Identity Server and File Service, unless the environment says otherwise: CCS_ENVIRONMENT (local | staging | production), a File Service origin variable (API <origin>/api/v1, resource <origin>/api; replaced in 2.0.0 by FILES_API_URL + FILES_API_RESOURCE), CCS_ISSUER_URL. The package still has no runtime dependencies. FILES_API_URL + FILES_API_RESOURCE were honoured when both were set and agreed. An explicit option always wins.
  • FilesClientOptions.env: where those variables are read. Default process.env; {} ignores the environment.
  • FilesClient.endpoints: the { issuerUrl, apiUrl, resource } the client uses, after the defaults.
  • The addresses themselves, exported from the package root: FILES_ORIGINS and IDENTITY_ORIGINS (production, staging, local), FILES_AUDIENCE (files-api), filesEndpoints({ origin?, env? }) → { origin, apiUrl, resource, deprecation } (precedence: origin > the origin variable > the FILES_API_URL + FILES_API_RESOURCE pair > CCS_ENVIRONMENT > production; a malformed origin, an unknown CCS_ENVIRONMENT or an inconsistent pair throws) and defaultIssuerUrl(env?) (CCS_ISSUER_URL, else the preset). Neither reads process.env itself: pass env. A backend that only needs the File Service’s resource or apiUrl (a token route, a consumer configuration) calls filesEndpoints({ env: process.env }).
  • A blank issuerUrl, apiUrl or resource (an empty string) still throws, now with “must not be blank (omit it for the platform default)”; only an omitted (undefined) field takes the default. A malformed origin variable or CCS_ENVIRONMENT throws at construction.

Application-managed folders, as the File Service now serves them. Source-compatible with 1.2.0.

  • Application-managed folders. folders.create({ ..., managed: true }) creates a folder already managed by your application, folders.update(folderId, { managed }) marks (true) or unmarks (false) one (managed on CreateFolderInput and UpdateFolderInput; machine clients with files:manage only, never the root). For people a managed folder and its subtree are read-only; your machine clients change them as before. Audited folder.managed and folder.unmanaged.
  • File and Folder gain managed (true for the marked folder and everything below it) and managed_by (ManagedBy: { client_id, name, managed_folder_id, managed_at }, null when not managed; the nearest marked folder with nested marks).
  • Errors. folder_managed (403, a FilesForbiddenError with body.managed_folder_id): what people cannot do inside a managed subtree, share links included (your machine client still creates them). FilesForbiddenError.managedFolderId reads the managing folder id (null on other codes) and isFolderManagedError(error) narrows to it. principal_not_allowed (403) also answers a user token sending managed.

Legal hold, usage trends and platform administration, as the File Service now serves them. Source-compatible with 1.1.0.

  • Legal hold. files.hold.place(fileId, { reason, until?, acting_user_id? }) (PUT P/files/:file_id/hold: places a hold, or changes the reason and the end of the one in place), files.hold.release(fileId, { reason?, acting_user_id? }) (DELETE P/files/:file_id/hold, a body only when a field is given) and holds.list({ placed_by?, after?, limit? }) (GET P/holds: every held file of the application, newest hold first). They need files:manage. FileHold gains until (null: until released); PlaceHoldInput, ReleaseHoldInput, HoldsQuery.
  • Usage trend. usage() types daily (DailyUsage[]: the last 30 days, oldest first, { date, bytes, rendition_bytes, trashed_bytes, files, versions, quota_bytes }; a day without a snapshot is absent, never zero).
  • Platform administration (client.platform, no context; only for a client of the Files platform application itself with files:config.manage, every other client answers 403 platform_admin_required): tenants, tenant, setTenantSettings, defaults, setDefaults; usage trends usage({ days, sort, limit }) and tenantUsage(tenantId, { days }); a tenant’s audit log across its applications tenantAudit(tenantId, query) (the filters of audit.list plus application_id); the exports exportTenantAudit(tenantId, { ...filters, format }) and exportTenantUsage(tenantId, { from, to, format }), which resolve with a FilesExport (content_type, filename, truncated, body, text(), lines()); storage storage.tenant(tenantId), storage.assign(tenantId, { storage_account_id }), storage.accounts (list, create, get, update, delete, verify) and storage.migrations (start, list, get), with their types (PlatformTenant, PlatformUsageTrends, PlatformTenantUsageTrend, TenantSettings, StorageAccount, TenantStorage, StorageMigration, …). Creating an account and starting a move are never retried; storage.accounts.verify is retried like a read.
  • Errors. file_on_hold (409, a FilesConflictError with body.hold) answers files.purge and files.versions.delete while a hold is in force; platform_admin_required (403); storage_account_not_found and storage_migration_not_found (404); storage_account_in_use, storage_account_not_verified and storage_migration_in_progress (409); storage_account_verification_failed and storage_adapter_not_allowed (422).
  • AuditRow.target.type includes tenant, platform, taxonomy and trash.
  • Search wording. SearchQuery.in: name matches names that start with the query, case-insensitively (a prefix, not a substring: IMG finds IMG_5913.jpg, 5913 does not); titles and other words match whole words through keywords; with the default ['name', 'keywords'], a query no whole word matches falls back to the name prefix. SearchResult.total is exact up to 10,000 and null above.
  • URLs: treat public_url and every signed URL as opaque. A file’s public_url can change when its tenant’s files move to another storage; read it from the file again rather than keeping it for good.

Processing, content search and webhooks, as the File Service now serves them, and the response types brought in line with the service’s OpenAPI document. Source-compatible with 1.0.0 except where a 1.0.0 type described a field the service never sent (listed under Fixes).

  • Processing. ScanStatus (pending, clean, infected, skipped, failed) and the documented meaning of processing.scan and processing.text; RenditionStatus (pending, ready, skipped, failed); renditions mp4_720p and mp4_1080p with duration_ms, and error on a skipped or failed rendition (source_smaller, duration_exceeds_limit, size_exceeds_limit, duration_unknown); files.renditions.regenerate takes RenditionNames. FileMedia types media: captured_at and camera for images, title and author for PDFs, next to the dimensions, duration, codec, rotation and page count.
  • Content search. SearchQuery.in documents content; SearchHighlight ({ field, snippet }) on SearchHit.
  • Webhooks. WebhookConfig gains secret_set, consecutive_failures, last_delivery_at, disabled_at and updated_at; WebhookDelivery gains event_id, error, duration_ms and next_attempt_at, with WebhookDeliveryStatus (delivered, retrying, failed). webhooks.set documents 400 webhook_url_not_allowed (a FilesApiError) and 422 webhook_unreachable.
  • Settings. scan_policy, scan_failed_policy, scan_oversize_policy and text_extraction_enabled are served and documented on Settings and SettingsUpdate.
  • CreatedShare (what shares.create resolves with: token and url are required there), TaxonomyInput (what taxonomies.set sends), EntryPrincipal, EffectiveEntry, FileHold, PreviewKind; trash_reason on a trashed File; external_ref on UpdateFolderInput; by_kind and by_folder on Usage.
  • files.waitUntilReady resolves when processing ran to its last step (notify done) even though a step that does not stop processing failed on the way (a rendition, the media metadata, text extraction, or a scan the application lets through under scan_failed_policy: allow); check processing on the file it resolves with. It still rejects with processing_failed when a step failed and processing stopped there (a scan that failed under scan_failed_policy: block, or a step that ran out of attempts). In 1.0.0 any failed step rejected.
  • Share: target: { type, id }, password_protected, downloads, access_count, created_by: CreatedBy, expires_at: string, revoked_at and last_accessed_at always present. The 1.0.0 fields target_type, target_id and password_required were never sent and are removed.
  • AccessEntry.granted_by is a CreatedBy ({ user_id, client_id }), not a string; expires_at is always present; a role principal carries the role’s name. The 1.0.0 display_name on the entry was never sent and is removed. ItemPermissions.effective is EffectiveEntry[] ({ principal, actions }), not AccessEntry[].
  • EntryRole (permissions.roles()) has display_name; Capabilities.roles[].role_id may be null and scope is TENANT or APPLICATION.
  • Preview.kind is PreviewKind (image, video, audio, pdf, text, none), not a FileKind; rendition width and height may be null.
  • UploadTicketStatus includes failed; UploadTicket.url, part_size, parts_total and parts are null when they do not apply.
  • Folder has depth, state (available, trashed) and busy; created_by, created_at and updated_at are null for the root; metadata is a string map, never null.
  • File: custom is a string map; media is never null; hold is FileHold | null; lock has kind, holder_user_id, holder_client_id and note (it is null until check-out exists).
  • Version has state (available, rejected, quarantined) and source (upload, restore, edit, copy).
  • RenditionInfo and Rendition: status is a RenditionStatus, mime_type, size, width, height and duration_ms are always present (null when unknown), error only on a skipped or failed rendition.
  • Job: progress is { done, total }, error is { code, message }, with created_at and updated_at.
  • SearchResult.total is always present; Taxonomy has name; AuditRow tenant_id, application_id, ip and user_agent may be null; Settings.updated_at is null before the first change.

First release: the Node.js backend SDK for the File Service.

  • FilesClient with folders (root, create, get, listByExternalRef, children, path, update, move, copy, delete, restore, usage), uploads (create, status, parts, complete, abort), files (get, listByExternalRef, update, move, copy, delete, restore, purge, downloadUrl, urls, preview, waitUntilReady, effectivePermissions, effectivePermissionsBatch, checkout, checkin, discardCheckout), files.versions (list, create, get, restore, pin, delete), files.renditions (list, regenerate), permissions (get, replace, grant, revoke, roles), search, tags, taxonomies (get, set), shares (create, list, listAll, revoke), trash (list, empty), settings (public, get, update), usage, tenantUsage, audit (list, forFile, forFolder), webhooks (get, set, deliveries), jobs (get, wait) and capabilities.
  • upload(source, input, { concurrency, onProgress, signal }): ticket, single PUT with exactly the ticket’s headers or multipart with 4 parts in flight, part URLs beyond the first 100 in batches of 100, each PUT retried up to 5 times with full-jitter backoff, one URL refresh on a 403, etags from ETag, completion; the ticket is aborted on give-up (FilesUploadError with partNumber) and on abort. Sources: Buffer, Uint8Array, Blob, Readable (with input.size) and { path }.
  • files.waitUntilReady(fileId, { timeoutMs, pollMs, signal }) and jobs.wait(jobId, ...) poll until the file is processed or the job is done.
  • FilesClient.verifyWebhookSignature({ rawBody, signatureHeader, secret, toleranceSeconds, now }): X-Files-Signature (t=…,v1=…, HMAC-SHA256 of "<t>.<raw body>"), constant-time, 300 s tolerance by default.
  • Request and response types are the service’s snake_case wire format (File, Folder, Version, UploadRequest with on_name_conflict, UploadTicket, AccessEntry, Share, Settings, PublicSettings, SearchQuery, SearchResult, Preview, AuditRow, Usage, Job, Page<T>, …); constructor options, the per-call context ({ tenantId, applicationId }) and the SDK’s option objects are camelCase.
  • client_credentials tokens over HTTP Basic with scope (default files:manage) and resource, cached until exp - 60 s, one in-flight token request, one refresh and replay on a 401.
  • Retries for 5xx (not 501) and network errors (maxRetries, default 3) with exponential backoff and jitter, on reads and idempotent writes only; the writes that create carry an Idempotency-Key generated once and reused across retries; If-Match through { ifMatch } on files.update and folders.update.
  • Typed errors: FilesApiError (status, code, body, retryAfterSeconds), FilesUnauthorizedError, FilesForbiddenError, FilesNotFoundError, FilesConflictError, FilesPreconditionFailedError, FilesPayloadTooLargeError, FilesUnprocessableError, FilesRateLimitedError, FilesNotImplementedError, FilesContextError, FilesUploadError, and toFilesApiError.
  • ESM and CommonJS builds with declarations, zero runtime dependencies, Node.js 20 or newer (built and tested on Node.js 24).