esoul-sdk 0.21.1 → 0.23.0

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.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,78 @@
2
2
 
3
3
  Releases before 0.20.0 are recorded in the repository history only.
4
4
 
5
+ ## 0.23.0
6
+
7
+ ### Added
8
+
9
+ - `useAppNav(nodeId, onTarget)` (esoul-sdk/react): the platform opens a place INSIDE your app —
10
+ the tasks pane lands on a conversation or a campaign, not just the app (docs/05-ui.md, "Opened
11
+ from outside").
12
+ - `origin: "server"` on an event definition: only the app's own server code (ops, routes, tasks,
13
+ webhooks) may append it; the browser's append and offline-queue doors and the PAT / SDK / MCP
14
+ dispatch routes refuse it (docs/03-events-and-state.md, "Events only the server emits").
15
+
16
+ ### Changed
17
+
18
+ - `readAppState(nodeId)` reads only for the call the platform is running: your app and its own
19
+ type in your workspace, another type only with a `workspaceTools` grant for it (refused with the
20
+ line to add), null for an app in another workspace, own type only from a webhook, and a throw
21
+ outside an op, route, task or webhook. `callWorkspaceTool` refuses a `pluginId`/`nodeId` that is
22
+ not the running call's own. The Forge box applies the same read rule.
23
+
24
+ ### Fixed
25
+
26
+ - `pluginServer.fileProducers` takes a producer with its own key type: `PluginFileProducer<MyKey>`
27
+ with `MyKey` declared as an `interface` no longer fails to typecheck against the map.
28
+
29
+ ## 0.22.0
30
+
31
+ ### Added
32
+
33
+ - **File transfers: a LIST of files into a workspace folder, as one durable platform job.**
34
+ `files.transfer({ requestKey?, title?, to: { sourceId: "workspace", path }, items, onConflict?, dedupe? })`
35
+ → `Transfer` (`snapshot()`, `wait({ timeoutMs })`, `retry({ items? })`, `cancel()`); `getTransfer(id)`,
36
+ `listTransfers()`. Items are `{ from }` (any readable mount, by ref or address), `{ producer, key }`
37
+ (the app's own bytes), or `{ staged, name }`; each takes an optional `name` and `subPath`. At most 200
38
+ items and 100 MB a file. The person sees it in the tasks pane ("Saving 37 files to Mail/Quotes —
39
+ 12/37 · 140 MB of 380 MB") with Stop and Retry. **Retry is clean**: it re-runs only failed and
40
+ stopped items, pressing it twice is one retry, and a crash at any step followed by a retry leaves
41
+ exactly what one clean run leaves — no stray bytes, no second file, no second `file_added`, no
42
+ empty folder the transfer made. Consent is checked when asked (the whole request refused, naming
43
+ the item) and again as each item runs. See `docs/09-files.md` → "Moving a list of files".
44
+ - **Producers: `fileProducers` in `plugin.json` + `pluginServer.fileProducers[key]`** (`identity(key)`,
45
+ `open(ctx, key)`, `describe?`) — the app's own bytes (a mail attachment) as a transfer source,
46
+ opened by the platform inside the item's step, as the app; the bytes never cross an op body.
47
+ Types: `PluginFileProducer`. Schema: `fileProducers: [{ key, label }]`.
48
+ - **Addresses: `"<sourceId>:/<path>"`** (`"workspace:/Mail/Quotes/a.pdf"`, `"google-drive:/Reports/q3.pdf"`).
49
+ `read`, `readMany` and the new `stat(addr)` take a ref or an address. `parseFileAddress` (pure).
50
+ - **`files.save(addr, bytes, { onConflict?, key?, contentType? })`** — one file to a path, folders
51
+ made. **`files.stage({ name, bytes })`** — bytes handed over ahead of a transfer (24 h).
52
+ - **`ctx.files` on a task context** — the same `FilesApi` as `filesForOp(ctx)` (app.tsx cannot import
53
+ `esoul-sdk/server`).
54
+ - **`useTransfer(transferId)`** in `esoul-sdk/react` — `{ transfer, loading, error, retry(), cancel() }`,
55
+ polled while it moves and on the platform's nudge; a failed press is named on the failed-request
56
+ banner with Try again. Works in a Forge preview.
57
+ - **Testing: `memoryFiles` runs transfers and saves on the platform's own protocol** (the same code):
58
+ `files.producers({...})`, `files.failItem(index, error, { times })`, `files.crashAt(point, { index })`,
59
+ `transfer.wait()` runs the items in the test, and `files.residue()` →
60
+ `{ orphanBlobs, duplicateFiles, extraEvents, emptyCreatedFolders }` — assert it is empty after a retry.
61
+ - Pure helpers and types exported from `esoul-sdk`: `TransferRequest`, `TransferSnapshot`,
62
+ `TransferStatus`, `describeTransferProgress`, `deriveTransferCounts`, `settledTransferStatus`,
63
+ `pickFreeFileName`, `normaliseTransferPath`, `validateTransferName`, `TRANSFER_*` caps.
64
+
65
+ ### Changed
66
+
67
+ - **`saveWorkspaceFile` and `importToWorkspace` are idempotent and crash-safe.** They take `key?`
68
+ and `onConflict?` (`saveWorkspaceFile`). The same key — or, without one, the same bytes and name —
69
+ into the same folder is the same file (`deduped: true`); a different file with a name already
70
+ there is saved as `"name (2).ext"` (`renamedFrom`) unless `onConflict` is `"skip"` or `"fail"`.
71
+ The call throws when its `workspace/file_added` could not be appended (it used to succeed
72
+ silently with no event), and indexing is started before it returns.
73
+ - In a Forge box, a save or stage through the files door is refused past 3 MB with a sentence
74
+ (it was an opaque 413 past ≈3.3 MB).
75
+ - Workspace folders in a path walk match case-insensitively, the way `folderPath` makes them.
76
+
5
77
  ## 0.21.1
6
78
 
7
79
  ### Fixed
package/README.md CHANGED
@@ -499,7 +499,7 @@ Platform columns on every table: `id`, `workspaceId`, `nodeId`, `ownerId`, `crea
499
499
  | `listAppRoles(ctx)` | `{ people, roles, custom, envelope }`. |
500
500
  | `defineAppRole(ctx, definition)`, `removeAppRole(ctx, name)` | The owner composes or removes a role inside `roles.custom`. |
501
501
  | `getPluginConnectionCredentials(ctx)` | The credentials of the instance's bound connection. |
502
- | `pluginFiles(ctx)`, `filesForOp(ctx)` | Workspace files and file sources from server code. |
502
+ | `pluginFiles(ctx)`, `filesForOp(ctx)` | Workspace files and file sources from server code: read by ref or address (`"workspace:/Mail/a.pdf"`), `save` to a path, and `transfer` a list of files into a folder with platform progress, Stop and a clean Retry (docs/09). |
503
503
 
504
504
  ### `esoul-sdk/react`
505
505
 
@@ -512,6 +512,7 @@ Platform columns on every table: `id`, `workspaceId`, `nodeId`, `ownerId`, `crea
512
512
  | `usePluginRealtime({ channel, workspaceId, nodeId, topics, enabled? })` | Subscribes to the instance's channel. Returns `{ data, latestData, error, state }`. |
513
513
  | `useWorkspaceTools(identity)` | Lists and calls tools of other applications from the UI, subject to `workspaceTools`. |
514
514
  | `usePluginWorkspaceFiles()`, `usePluginFileUpload()`, `useFileSources()`, `useFileSourceEntries()` | Workspace files, upload, and file sources (workspace, Google Drive, providers). |
515
+ | `useTransfer(transferId)` | A file transfer's progress, with `retry()` and `cancel()`. |
515
516
 
516
517
  ### `esoul-sdk/testing`
517
518