@seliseblocks/cli-os 0.2.2 → 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/AI_USAGE_GUIDE.md +551 -546
  2. package/LICENSE +21 -21
  3. package/README.md +171 -171
  4. package/bin/run.js +2 -2
  5. package/dist/commands/data/files/delete.js +5 -4
  6. package/dist/commands/data/files/get-many.js +1 -1
  7. package/dist/commands/data/files/get.js +1 -1
  8. package/dist/commands/data/files/info.js +1 -1
  9. package/dist/commands/data/files/object-tree.d.ts +23 -0
  10. package/dist/commands/data/files/object-tree.js +238 -0
  11. package/dist/commands/data/files/presigned-upload-url.js +9 -2
  12. package/dist/commands/data/files/update-additional-info.js +2 -2
  13. package/dist/commands/data/files/upload-to-local-storage.js +2 -2
  14. package/dist/commands/data/files/upload.d.ts +2 -4
  15. package/dist/commands/data/files/upload.js +14 -28
  16. package/dist/index.js +685 -647
  17. package/dist/skills/blocks-data-gateway-configuration/SKILL.md +204 -204
  18. package/dist/skills/blocks-data-gateway-crud/SKILL.md +223 -223
  19. package/dist/skills/blocks-data-storage/SKILL.md +253 -161
  20. package/dist/skills/blocks-data-storage/flows/object-management.md +124 -0
  21. package/dist/skills/blocks-frontend-local-https/SKILL.md +100 -100
  22. package/dist/skills/blocks-iam-account/SKILL.md +169 -169
  23. package/dist/skills/blocks-iam-sso-oidc-implementation/SKILL.md +80 -80
  24. package/dist/skills/blocks-iam-users/SKILL.md +131 -131
  25. package/dist/skills/blocks-localization-configuration/SKILL.md +149 -149
  26. package/dist/skills/blocks-localization-implementation/SKILL.md +63 -63
  27. package/dist/skills/blocks-onboarding/SKILL.md +77 -77
  28. package/dist/skills/blocks-storage-configuration/SKILL.md +4 -4
  29. package/package.json +47 -47
  30. package/dist/commands/data/files/create-folder.d.ts +0 -1
  31. package/dist/commands/data/files/create-folder.js +0 -34
  32. package/dist/commands/data/files/delete-folder.d.ts +0 -1
  33. package/dist/commands/data/files/delete-folder.js +0 -25
  34. package/dist/commands/data/files/dms-list.d.ts +0 -1
  35. package/dist/commands/data/files/dms-list.js +0 -27
  36. package/dist/commands/data/files/dms-upload.d.ts +0 -6
  37. package/dist/commands/data/files/dms-upload.js +0 -41
@@ -1,161 +1,253 @@
1
- ---
2
- name: blocks-data-storage
3
- description: "Store and serve files on a SELISE Blocks project: presigned/local-storage upload, download, folder browsing, tags/metadata, and delete, via the blocks CLI ('data files *') for admin/scripting or the @seliseblocks/client SDK ('data.files'/'data.dms') for in-app upload/download flows. Use for prompts like upload a file and get a download link, attach an image to a record, create a folder, let users download a file, tag or delete an uploaded file. Separate from the data model schemas/records live in blocks-data-gateway-configuration/-crud; this covers files/DMS only, implementation-mode, SDK-driven."
4
- ---
5
-
6
- # Blocks Data — Storage (Files / DMS)
7
-
8
- Storage is DMS (document management system). Two ways to reach it, pick based on what the user is actually doing:
9
-
10
- - **`blocks data files *` (CLI)** — admin tasks, one-off scripts, or anything the user is doing from a terminal/agent context rather than inside a running app.
11
- - **`@seliseblocks/client`'s `data.files` / `data.dms` namespaces (SDK)** — wiring upload/download/browse into actual app code (a React component, a form submit handler).
12
-
13
- Both surfaces do the same work; which one to use is about *where the code runs*, not a capability gap — unlike some other Data resources, this one has full CLI coverage.
14
-
15
- **Prerequisite:** a project is selected (`blocks use <tenantId>`). For the SDK path, a frontend also needs to be scaffolded. If login/project state is unknown, or there's no app to write SDK code into yet, run the blocks-onboarding skill first — it gets `blocks new web` scaffolding in place (React 18 + TypeScript + Vite + Tailwind + Radix + TanStack Query + a single `@seliseblocks/client` instance). The SDK examples below assume that scaffold's shared client, conventionally exported as `blocksClient` from `src/lib/blocks/client.ts`.
16
-
17
- ```ts
18
- import { blocksClient } from "../lib/blocks/client";
19
- const { files, dms } = blocksClient.data;
20
- ```
21
-
22
- Store a file here and keep its returned `fileId` in a schema field (see the blocks-data-gateway-crud skill) to associate it with a record.
23
-
24
- ## CLI quick reference
25
-
26
- ```bash
27
- # Cloud storage (pre-signed URL), two steps
28
- blocks data files presigned-upload-url --name invoice.pdf --access-modifier Public --json
29
- blocks data files upload-to-url --url "<uploadUrl from above>" --file ./invoice.pdf --content-type application/pdf --yes --json
30
-
31
- # Local storage, one step
32
- blocks data files upload-to-local-storage --file ./invoice.pdf --access-modifier Public --yes --json
33
-
34
- # Register the uploaded file in a DMS folder (upload alone doesn't do this)
35
- blocks data files dms-upload --file-storage-id <fileId> --artifact-name invoice.pdf --yes --json
36
-
37
- # Read it back
38
- blocks data files dms-list --parent-id "" --json
39
- blocks data files get <fileId> --json
40
-
41
- # Folders, metadata, cleanup
42
- blocks data files create-folder Invoices --yes --json
43
- blocks data files update-additional-info <fileId> --additional-properties '{"status":"reviewed"}' --yes --json
44
- blocks data files delete <fileId> --yes --json
45
- ```
46
-
47
- Same two upload paths as the SDK section below (pick based on the project's storage backend, not per-call), same `--dry-run`-before-`--yes` discipline as every other `blocks` mutation.
48
-
49
- **Shortcut:** `blocks data files upload --file ./invoice.pdf --yes --json` composes the manual sequence above into one command — presign + PUT + `dms.uploadFiles` registration for cloud storage, or add `--local-storage` for the one-step local-storage path (uploads only, no DMS registration in that case). Same relationship as `data sync` is to the manual schema/rules/reload sequence elsewhere in this skill pack: reach for the manual steps when you need to inspect or reuse an intermediate result (e.g. the presigned URL itself), reach for `upload` when you just want the file stored.
50
-
51
- ## Two upload paths — pick one per deployment
52
-
53
- A project's storage is backed by either cloud object storage (Azure Blob, S3, etc.) or local storage on the Blocks Data host. Which one applies is a property of the project's storage configuration, not something the frontend chooses per call — but the SDK exposes a distinct method for each:
54
-
55
- | Deployment | Call sequence |
56
- |---|---|
57
- | **Cloud storage** (pre-signed URL) | `files.presignedUploadUrl(...)` → `files.uploadToUrl(...)` |
58
- | **Local storage** | `files.uploadToLocalStorage(...)` (one call, no presign step) |
59
-
60
- Both are followed by the same registration step, `dms.uploadFiles(...)`, if the file needs to show up in a DMS folder.
61
-
62
- **Where that configuration lives:** `blocks storage config get/list/save/delete` (a separate top-level command group, not `data files`) reads/writes the named storage configuration itself — host, port, credentials, region/endpoint or connection string, and strategy — i.e. which provider a given `configurationName` points at, cloud or local. This skill only covers *using* that config name when uploading; managing the config's own fields is out of scope here — see the blocks-storage-configuration skill.
63
-
64
- ## Step 1a (cloud) — get a pre-signed upload URL
65
-
66
- ```ts
67
- const presign = await files.presignedUploadUrl({
68
- name: "invoice.pdf",
69
- contentType: "application/pdf",
70
- configurationName: "Default", // example only — confirm the storage config name for this project
71
- moduleName: 3, // example only — confirm the module value expected by this project
72
- parentDirectoryId: "", // required — "" for root, or a folder id; never omit/null
73
- accessModifier: "Public" // "Public" (readable without auth) or "Private"
74
- });
75
- ```
76
-
77
- It returns the pre-signed `uploadUrl` plus a `fileId` you'll need for the next steps — the method's return type is `Promise<unknown>`, so read the exact response shape at runtime rather than assuming a typed contract. Note `contentType` in this request is not forwarded to the presign call itself (the normalizer drops it); pass it again to `uploadToUrl` below so the PUT gets the right `Content-Type` header. Treat `configurationName` and `moduleName` values as project-specific unless the tenant's storage configuration says otherwise.
78
-
79
- ## Step 1b (cloud) — PUT the binary to that URL
80
-
81
- ```ts
82
- await files.uploadToUrl({
83
- url: presign.uploadUrl,
84
- body: fileBlob, // Blob | ArrayBuffer | ArrayBufferView | ReadableStream
85
- contentType: "application/pdf"
86
- });
87
- ```
88
-
89
- This is the one call in the whole skill that is **provider-direct, not a Blocks API call** — it sends **no `x-blocks-key` and no bearer token**. It PUTs straight to the storage provider's pre-signed URL. If you don't set your own `x-ms-blob-type` header, the SDK adds `x-ms-blob-type: Blockblob` for you (Azure's block-blob upload header); verify that this matches the storage provider and signed-URL policy for the project rather than assuming every provider ignores extra headers.
90
-
91
- ## Step 1 (local storage) — the one-call alternative
92
-
93
- For local-storage-backed deployments, skip the presign/PUT pair entirely and upload straight through Blocks Data:
94
-
95
- ```ts
96
- await files.uploadToLocalStorage({
97
- name: "invoice.pdf",
98
- file: fileBlob, // Blob | File
99
- configurationName: "Default",
100
- parentDirectoryId: "",
101
- accessModifier: "Public",
102
- tags: ["invoice", "2026"]
103
- });
104
- ```
105
-
106
- The SDK builds a multipart `FormData` body for you (`File`, `Name`, `ItemId`, `MetaData`, `ParentDirectoryId`, `Tags`, `AccessModifier`, `ConfigurationName`, `AdditionalProperties[key]`) and sends it as a normal authenticated Blocks API call (`x-blocks-key` + bearer, unlike the pre-signed PUT above).
107
-
108
- ## Step 2 — register the file in a DMS folder
109
-
110
- Neither upload path above makes a file appear in a document folder by itself — that's a separate registration call:
111
-
112
- ```ts
113
- await dms.uploadFiles({
114
- upload: [{
115
- fileStorageId: presign.fileId, // the fileId from presignedUploadUrl (or the equivalent id from uploadToLocalStorage's response)
116
- artifactName: "invoice.pdf",
117
- parentId: "", // "" for root, or a folder id
118
- tags: ["invoice"]
119
- }]
120
- });
121
- ```
122
-
123
- Despite the method's name suggesting a binary upload, this is the DMS *registration* call — the bytes are already stored by Step 1a/1b or Step 1. `upload` is an array, so multiple files can be registered into folders in one call.
124
-
125
- ## Step 3 — read it back
126
-
127
- ```ts
128
- const folder = await dms.list({ parentId: "", take: 20 });
129
- const meta = await files.get(presign.fileId, { configurationName: "Default" });
130
- ```
131
-
132
- `dms.list` returns the combined folder+file listing for a `parentId` (`""` = root), with `searchKey`/`skip`/`take` for search and paging. The same endpoint is also reachable as `files.listFolder(...)` — they're identical calls, `dms.list` is the more discoverable name for folder-browsing UI. `files.get` confirms a specific upload landed: a successful response with a non-null `url` (download link) and matching `name`/size means the file is stored.
133
-
134
- ## Other file operations
135
-
136
- - **`files.getMany({ fileIds, configurationName })`** — batch read instead of one `files.get` per attachment.
137
- - **`files.info({ page, pageSize, sort, filter })`** — paged file metadata/listing for storage-browser UIs; unlike `presignedUploadUrl`, the SDK does not remap these field names to PascalCase — pass exactly what your app builds.
138
- - **`files.updateAdditionalInfo({ itemId, additionalProperties })`** — attach searchable metadata to an uploaded file, e.g. a business reference or workflow status.
139
- - **`files.delete({ fileId, configurationName?, eventQueueName? })`** — delete a file.
140
- - **`dms.createFolder({ artifactName, parentId?, configurationName? })`** / **`dms.deleteFolder({ folderId, configurationName? })`** — DMS folder management.
141
-
142
- ## Gotchas
143
-
144
- - **Terminal/admin task → CLI (`data files *`); app code → SDK.** Both exist and both are fully supported; don't default to writing a throwaway script against the SDK for something the CLI already does in one command, and don't reach for `blocks` from inside a React component.
145
- - **`--module-name` / `moduleName` and `--parent-directory-id` / `parentDirectoryId` on the presigned-upload-url call** — optional in the CLI/SDK types, but the underlying endpoint may require them for a given project/storage setup. Confirm the expected module value with the project's storage configuration, and always send a `parentDirectoryId` value (`""` for root) when the endpoint requires a parent folder value — the CLI command defaults it to `""` automatically if you omit `--parent-directory-id`.
146
- - **The pre-signed PUT is the one call with no Blocks auth.** `uploadToUrl` sends no `x-blocks-key` and no bearer token by design — everything else in this skill (`presignedUploadUrl`, `uploadToLocalStorage`, `dms.*`, `files.get`/`getMany`/`info`/`delete`) is a normal authenticated Blocks API call.
147
- - **Upload ≠ visible in a folder.** `uploadToUrl`/`uploadToLocalStorage` only gets the bytes stored; call `dms.uploadFiles` afterward if the file needs to appear under a DMS folder.
148
- - **Most file/DMS methods return `Promise<unknown>`.** The SDK doesn't hand you a typed response for this surface — check the actual JSON shape at runtime (e.g. log the presign response once) rather than assuming field names beyond what's documented here.
149
- - **`accessModifier`** is `"Public"` (readable without auth) or `"Private"` — decide per file, not per project.
150
- - Don't confuse this with the data model: a file's `fileId` is just a string you store in a schema field via **blocks-data-gateway-crud**; this skill never touches schemas.
151
-
152
- ## Example trigger prompts
153
-
154
- - "Upload a PDF and get a download link." → CLI (`data files presigned-upload-url` + `upload-to-url`, or `upload-to-local-storage`) for a one-off; SDK if it's a feature in the app.
155
- - "Attach an image to this record." (upload via CLI or SDK, then store the `fileId` via blocks-data-gateway-crud)
156
- - "Let users download this file from the app." → SDK, this is in-app behavior.
157
- - "Create a folder and list its contents." → `data files create-folder` + `data files dms-list`.
158
- - "Get a presigned upload URL for a cloud storage upload." → `data files presigned-upload-url`.
159
- - "This deployment uses local storage how do I upload a file?" → `data files upload-to-local-storage`.
160
- - "Tag this uploaded file with a status so it's searchable later." → `data files update-additional-info`.
161
- - "Delete this file / delete this folder." → `data files delete` / `data files delete-folder`.
1
+ ---
2
+ name: blocks-data-storage
3
+ description: "Build file and document-management features on SELISE Blocks Data: upload/download, directory trees, cursor-paginated browsing and search, file versions, rename/move/copy, soft delete/trash/restore, sharing, access policies, and inheritance. Use for attachments, file browsers, folders, shared files, permissions, or version history. Use 'blocks data files *' for terminal/admin work and @seliseblocks/client data.files/data.directories/data.objects for app code."
4
+ ---
5
+
6
+ # Blocks Data — Storage
7
+
8
+ Treat storage as one permission-aware object tree containing **directories** and **files**. Uploading a new file now creates the file object in that tree; there is no second DMS registration step.
9
+
10
+ Use:
11
+
12
+ - **`blocks data files *`** for supported terminal/admin operations.
13
+ - **`@seliseblocks/client`** for app code. Use the shared `blocksClient` created by blocks-onboarding.
14
+ - **blocks-storage-configuration** only to manage the named provider configuration used by `configurationName`.
15
+
16
+ Select a project first with `blocks use <tenantId>`. If login/project state or the shared client setup is unknown, use blocks-onboarding before this skill.
17
+
18
+ Store the returned `fileId` in a Data record when attaching a file to domain data. Use blocks-data-gateway-crud for the record mutation.
19
+
20
+ ## Choose the surface
21
+
22
+ The current CLI and SDK follow the backend's file, directory, and object resource groups.
23
+
24
+ ```bash
25
+ blocks --version
26
+ blocks data files --help
27
+ ```
28
+
29
+ - Use `blocks data files *` for terminal/admin work and inspect `blocks --help` for exact flags.
30
+ - Use `blocksClient.data.files` for bytes, metadata, versions, and file operations.
31
+ - Use `blocksClient.data.directories` for directory create/get/update/delete/move.
32
+ - Use `blocksClient.data.objects` for browse/search/trash/shared/restore/share/access/inheritance.
33
+
34
+ Legacy `data.dms.*`, `dms-upload`, `dms-list`, `create-folder`, and `delete-folder` wrappers are retired. Uploads need no registration call.
35
+
36
+ ## Upload a new file
37
+
38
+ Choose one path from the project's storage configuration.
39
+
40
+ | Provider category | Sequence |
41
+ |---|---|
42
+ | Cloud object storage | request a pre-signed URL, then PUT the bytes to it |
43
+ | Local/SFTP storage | send one authenticated multipart upload |
44
+
45
+ Both paths create the file object and initial version directly. Do not call `dms-upload` afterward.
46
+
47
+ ### Cloud upload from the CLI
48
+
49
+ Prefer the composed command. It previews and confirms both metadata creation and the provider PUT:
50
+
51
+ ```bash
52
+ blocks data files upload --file ./invoice.pdf --parent-id <directoryId> \
53
+ --configuration-name Default --access-modifier Private --dry-run --json
54
+ blocks data files upload --file ./invoice.pdf --parent-id <directoryId> \
55
+ --configuration-name Default --access-modifier Private --yes --json
56
+ ```
57
+
58
+ Use the two explicit commands only when the intermediate upload URL is required:
59
+
60
+ ```bash
61
+ blocks data files presigned-upload-url \
62
+ --name invoice.pdf \
63
+ --parent-directory-id <directoryId> \
64
+ --configuration-name Default \
65
+ --access-modifier Private \
66
+ --dry-run --json
67
+ blocks data files presigned-upload-url \
68
+ --name invoice.pdf \
69
+ --parent-directory-id <directoryId> \
70
+ --configuration-name Default \
71
+ --access-modifier Private \
72
+ --yes --json
73
+
74
+ blocks data files upload-to-url \
75
+ --url "<uploadUrl>" \
76
+ --file ./invoice.pdf \
77
+ --content-type application/pdf \
78
+ --dry-run --json
79
+ blocks data files upload-to-url \
80
+ --url "<uploadUrl>" \
81
+ --file ./invoice.pdf \
82
+ --content-type application/pdf \
83
+ --yes --json
84
+ ```
85
+
86
+ The presign response contains `uploadUrl`, `fileId`, and `isSuccess`. The first call creates the file metadata/version; the PUT fills its object-storage key. Handle PUT failure explicitly because it can leave metadata for missing bytes.
87
+
88
+ When `parentDirectoryId` is empty, the cloud upload resolves `moduleName` to that module's default directory. The backend default is module value `8` (`Default_Construct`), but pass the intended module or a concrete directory id instead of relying on that default.
89
+
90
+ ### Cloud upload from app code
91
+
92
+ ```ts
93
+ const presign = await blocksClient.data.files.presignedUploadUrl({
94
+ name: "invoice.pdf",
95
+ parentDirectoryId: directoryId,
96
+ configurationName: "Default",
97
+ accessModifier: "Private",
98
+ tags: "invoice,2026",
99
+ });
100
+
101
+ if (!presign || typeof presign !== "object") {
102
+ throw new Error("Unexpected upload response");
103
+ }
104
+
105
+ const result = presign as {
106
+ uploadUrl: string;
107
+ fileId: string;
108
+ isSuccess: boolean;
109
+ errors?: Record<string, string>;
110
+ };
111
+
112
+ if (!result.isSuccess) throw new Error(JSON.stringify(result.errors));
113
+
114
+ await blocksClient.data.files.uploadToUrl({
115
+ url: result.uploadUrl,
116
+ body: file,
117
+ contentType: file.type || "application/octet-stream",
118
+ });
119
+ ```
120
+
121
+ `uploadToUrl` is provider-direct and sends no bearer token or `x-blocks-key`. The SDK adds Azure's `x-ms-blob-type: Blockblob` header unless overridden; ensure that header matches the signed provider policy.
122
+
123
+ ### Local-storage upload
124
+
125
+ ```bash
126
+ blocks data files upload-to-local-storage \
127
+ --file ./invoice.pdf \
128
+ --parent-directory-id <directoryId> \
129
+ --configuration-name Default \
130
+ --access-modifier Private \
131
+ --dry-run --json
132
+ blocks data files upload-to-local-storage \
133
+ --file ./invoice.pdf \
134
+ --parent-directory-id <directoryId> \
135
+ --configuration-name Default \
136
+ --access-modifier Private \
137
+ --yes --json
138
+ ```
139
+
140
+ ```ts
141
+ const uploaded = await blocksClient.data.files.uploadToLocalStorage({
142
+ name: file.name,
143
+ file,
144
+ parentDirectoryId: directoryId,
145
+ configurationName: "Default",
146
+ accessModifier: "Private",
147
+ tags: ["invoice", "2026"],
148
+ });
149
+ ```
150
+
151
+ This call creates the file object and uploads version 1 in one request. Unlike cloud presign, an empty local `parentDirectoryId` stays at the top level; it is not resolved through `moduleName`.
152
+
153
+ ## Add a file version
154
+
155
+ Supplying an existing `itemId` to either upload flow creates another version after the caller passes the file's Edit check. For cloud storage, the dedicated command/method returns another pre-signed URL:
156
+
157
+ ```bash
158
+ blocks data files versions <fileId> --limit 25 --json
159
+ blocks data files create-version <fileId> --configuration-name Default --dry-run --json
160
+ blocks data files create-version <fileId> --configuration-name Default --yes --json
161
+ ```
162
+
163
+ ```ts
164
+ const history = await blocksClient.data.files.versions({ fileId, limit: 25 });
165
+ const next = await blocksClient.data.files.createVersion({ fileId, configurationName: "Default" });
166
+ ```
167
+
168
+ Version history is newest-first and cursor-paginated. Use the returned opaque `nextCursor`; the current backend uses the last version number internally, but callers must not construct cursors. Limits are 1–100, default 25.
169
+
170
+ ## Read and download
171
+
172
+ ```bash
173
+ blocks data files get <fileId> --configuration-name Default --json
174
+ blocks data files get <fileId> --version <versionNo> --configuration-name Default --json
175
+ blocks data files get-many <fileId...> --configuration-name Default --json
176
+ ```
177
+
178
+ ```ts
179
+ const file = await blocksClient.data.files.get(fileId, {
180
+ configurationName: "Default",
181
+ version: 2,
182
+ });
183
+ ```
184
+
185
+ The response includes a download URL plus metadata. Download requires the caller's Download permission. Access-denied reads may deliberately look like missing resources so clients cannot probe hidden object ids.
186
+
187
+ ## Directory and object workflows
188
+
189
+ For browsing, directories, search, move/copy/rename, trash, sharing, and ACL behavior, read [flows/object-management.md](flows/object-management.md).
190
+
191
+ ## Update custom metadata
192
+
193
+ ```bash
194
+ blocks data files update-additional-info <fileId> \
195
+ --additional-properties '{"status":"reviewed"}' \
196
+ --dry-run --json
197
+ blocks data files update-additional-info <fileId> \
198
+ --additional-properties '{"status":"reviewed"}' \
199
+ --yes --json
200
+ ```
201
+
202
+ This updates `additionalProperties`; it does not rename, move, tag, or version the file.
203
+
204
+ ## Delete safely
205
+
206
+ Deletion now distinguishes trash from permanent removal:
207
+
208
+ - `permanent: false` archives the file or directory so it can be restored.
209
+ - `permanent: true` removes it for good.
210
+ - The backend default is **`true`** when `permanent` is omitted.
211
+
212
+ The CLI defaults to safe soft deletion. Add `--permanent` only after explicit approval.
213
+
214
+ ```bash
215
+ blocks data files delete <fileId> --dry-run --json
216
+ blocks data files delete <fileId> --yes --json
217
+ blocks data files delete <fileId> --permanent --dry-run --json
218
+ ```
219
+
220
+ In app code, send the choice explicitly: `blocksClient.data.files.delete({ fileId, permanent: false })`.
221
+
222
+ ## Permission model
223
+
224
+ Every storage request passes two checks:
225
+
226
+ 1. The endpoint permission permits that class of action.
227
+ 2. The object ACL permits the action on that specific file/directory.
228
+
229
+ Capabilities are ordered: `View`, `Download`, `Edit`, `Delete`, `Manage`, `Owner`. Higher capabilities imply lower ones. Directory children inherit ancestor access while `inheritsParentAccess` is true. New files inherit from their directory.
230
+
231
+ Do not infer permission from a visible button or endpoint grant. Render actions from each object's returned `permissions` flags and still handle 403/404 races.
232
+
233
+ ## Gotchas
234
+
235
+ - Upload now creates the visible file object; legacy DMS registration is wrong and may fail after the bytes were successfully PUT.
236
+ - Cloud presign creates metadata before the provider PUT. Treat the two steps as a recoverable workflow and surface partial failure.
237
+ - A name must contain an allowed extension. Directories may restrict extensions.
238
+ - Names are unique within a directory. File move/copy can fail on a name conflict or extension policy.
239
+ - `Private` is the safe default. Use `Public` only when unauthenticated download is intended.
240
+ - `configurationName` selects an existing provider record; it does not configure storage.
241
+ - Most legacy file SDK methods return `Promise<unknown>`; validate responses at the boundary.
242
+ - Never send Blocks auth headers to a pre-signed provider URL.
243
+ - Never use a raw API call to work around a missing CLI/SDK wrapper; update the client surface first.
244
+
245
+ ## Example triggers
246
+
247
+ - "Upload this PDF into the Contracts folder."
248
+ - "Build a file browser with folders and search."
249
+ - "Show files shared with the current user."
250
+ - "Move this file to trash and let users restore it."
251
+ - "Add version history and upload a replacement version."
252
+ - "Share this directory with a role and let its children inherit access."
253
+ - "Move, copy, or rename a file."
@@ -0,0 +1,124 @@
1
+ # Object management
2
+
3
+ Use `blocks data files *` from a terminal and `blocksClient.data.directories` / `blocksClient.data.objects` / `blocksClient.data.files` in app code. Do not fall back to legacy `data.dms.*` helpers or raw HTTP.
4
+
5
+ ## Browse a directory
6
+
7
+ Use the current `get-objects` operation with:
8
+
9
+ - `parentDirectoryId` for a concrete directory, or `moduleName` to resolve a module's default directory when no parent id is supplied.
10
+ - `cursor` from the previous response and `limit` from 1–200 (default 50).
11
+ - `type: "directory" | "file"` to narrow results.
12
+ - `search` for name filtering within that parent.
13
+
14
+ The result contains `items`, `nextCursor`, `hasMore`, and `totalChildCount`. Each item has a lowercase `type` discriminator and permission flags. Continue while `hasMore` using `nextCursor`; access filtering can produce a short page even when more results remain.
15
+
16
+ Use `search-objects` for a case-insensitive name search across descendants. Pass `query`, optional `directoryId`, optional type, cursor, and limit. Search text is treated literally rather than as a regular expression.
17
+
18
+ ```bash
19
+ blocks data files list --parent-id <directoryId> --type file --limit 50 --json
20
+ blocks data files search invoice --directory-id <directoryId> --json
21
+ ```
22
+
23
+ ```ts
24
+ const page = await blocksClient.data.objects.list({ parentDirectoryId, limit: 50 });
25
+ const matches = await blocksClient.data.objects.search({ query: "invoice", directoryId });
26
+ ```
27
+
28
+ ## Manage directories
29
+
30
+ - **Create:** provide `name`, an existing `parentDirectoryId`, optional `description`, `configurationName`, and `allowedFileExtensions`. If the parent is empty but `moduleName` is present, the backend resolves the module default. Creating a true root uses a separate owner-only capability and should not be a normal app action.
31
+ - **Read details:** get one directory by `directoryId`. The response includes path, ancestors, child counts, size, extension rules, inheritance, timestamps, and permissions.
32
+ - **Update:** provide `directoryId` and optional `name`/`description`. Default directories cannot be renamed.
33
+ - **Move:** provide `directoryId` and `targetDirectoryId`; an empty target means top level. Moving into self/descendants is rejected. Default directories cannot move.
34
+ - **Delete:** send `permanent: false` for trash. Permanent deletion requires an empty directory. Default directories cannot be deleted.
35
+
36
+ Directory names must be 1–255 characters, trimmed, not `.`/`..`, and contain no slash or backslash. Descriptions are limited to 2,000 characters.
37
+
38
+ ```bash
39
+ blocks data files directory-create Contracts --parent-id <directoryId> \
40
+ --allowed-extensions pdf,docx --dry-run --json
41
+ blocks data files directory-get <directoryId> --json
42
+ blocks data files directory-update <directoryId> --name Agreements --dry-run --json
43
+ blocks data files directory-move <directoryId> --target-directory-id <targetId> --dry-run --json
44
+ blocks data files directory-delete <directoryId> --dry-run --json
45
+ ```
46
+
47
+ Use the matching `blocksClient.data.directories.create/get/update/move/delete` methods in app code.
48
+
49
+ ## Manage files
50
+
51
+ - **Rename:** needs Edit and a unique name in the current directory.
52
+ - **Move:** needs Delete on the source file and Edit on the target directory. Stored bytes and versions stay in place.
53
+ - **Copy:** needs View on the source and Edit on the target. The new file gets a new id; version rows reference the same immutable stored bytes. It inherits from the target. Set `copyAccessPolicies` only when the user intends to duplicate direct policy entries.
54
+ - **Versions:** list newest-first with a cursor and limit 1–100. Creating a cloud version returns `versionNo` and `uploadUrl`; PUT the bytes to that URL without Blocks auth headers.
55
+
56
+ Move/copy reject a target name collision and a file extension disallowed by the target directory.
57
+
58
+ ```bash
59
+ blocks data files rename <fileId> --name final.pdf --dry-run --json
60
+ blocks data files move <fileId> --target-directory-id <targetId> --dry-run --json
61
+ blocks data files copy <fileId> --target-directory-id <targetId> --dry-run --json
62
+ ```
63
+
64
+ Use `blocksClient.data.files.rename`, `.move`, and `.copy` in app code.
65
+
66
+ ## Trash and restore
67
+
68
+ Use soft delete (`permanent: false`) to archive an item. List archived files/directories with `get-trash`, optionally filtering by type and paging with cursor/limit. Restore with `restore-from-trash`; permanently purge an archived item with `delete-from-trash` only after explicit approval.
69
+
70
+ Restore returns the item to its original parent. Reads and mutations remain ACL-filtered while the item is archived.
71
+
72
+ ```bash
73
+ blocks data files trash --type file --json
74
+ blocks data files restore <resourceId> --dry-run --json
75
+ blocks data files purge <resourceId> --dry-run --json
76
+ ```
77
+
78
+ Use `blocksClient.data.objects.trash`, `.restore`, and `.deleteFromTrash` in app code.
79
+
80
+ ## Shared objects
81
+
82
+ Use `get-shared-objects` for live items shared with the caller. It is cursor-paginated and may be filtered by `directory`/`file`. Owned objects are excluded. Direct or inherited allow entries for the current user, role, or organization qualify as shares.
83
+
84
+ Use `share-object` for the common allow-only action. Provide:
85
+
86
+ - `resourceId`
87
+ - `resourceType`: `Directory` or `File`
88
+ - `principalType`: `User`, `Role`, `Organization`, or `Everyone`
89
+ - `principalId` for every type except `Everyone`
90
+ - `permission`: `View`, `Download`, `Edit`, `Delete`, `Manage`, or `Owner`
91
+ - optional future `expiresAt`
92
+
93
+ Sharing requires Manage on the resource.
94
+
95
+ ```bash
96
+ blocks data files shared --json
97
+ blocks data files share <resourceId> --resource-type Directory \
98
+ --principal-type Role --principal-id editors --permission Edit --dry-run --json
99
+ ```
100
+
101
+ Use `blocksClient.data.objects.shared` and `.share` in app code.
102
+
103
+ ## Advanced access policies
104
+
105
+ Use the policy operations only for an access-management UI:
106
+
107
+ - `get-access-policies` lists direct entries on a resource.
108
+ - `grant-access` adds Allow or Deny with optional priority and expiry.
109
+ - `update-access-policy` replaces an entry and requires its `policyItemId`.
110
+ - `revoke-access-policy` removes an entry by `resourceId` and `policyItemId`.
111
+ - `resolve-access` returns the caller's `canView`, `canDownload`, `canEdit`, `canDelete`, `canManage`, and `canOwner` flags.
112
+ - `toggle-inheritance` changes whether ancestors participate in resolution.
113
+
114
+ Enums are JSON strings, not guessed numeric values. Priority must be non-negative and expiry must be in the future.
115
+
116
+ Policy guardrails:
117
+
118
+ - Manage is required to change access.
119
+ - A Deny aimed at the resource owner is rejected.
120
+ - Turning inheritance off is rejected until the resource has a direct Allow entry, preventing an orphaned resource.
121
+ - The request model for access-policy listing contains `includeInherited`, but the current controller does not use it; do not promise inherited entries in that response. Use `resolve-access` for effective permissions.
122
+ - Object reads may return 404 instead of 403 to avoid revealing hidden ids.
123
+
124
+ CLI access commands are `access-list`, `access-grant`, `access-update`, `access-revoke`, `access-resolve`, and `inheritance`. Their SDK equivalents are under `blocksClient.data.objects`.