@t4h.framework/fs 0.13.0 → 0.14.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.
Files changed (80) hide show
  1. package/.ai/skills/framework-fs/SKILL.md +87 -186
  2. package/.ai/skills/framework-fs-split/SKILL.md +134 -314
  3. package/.ai/skills/framework-fs-write/SKILL.md +54 -214
  4. package/CHANGELOG.md +56 -0
  5. package/README.md +20 -351
  6. package/dist/activities/FileJoinActivity.d.ts +4 -5
  7. package/dist/activities/FileJoinActivity.d.ts.map +1 -1
  8. package/dist/activities/FileJoinActivity.js +38 -43
  9. package/dist/activities/FileJoinActivity.js.map +1 -1
  10. package/dist/activities/FileReadActivity.d.ts +1 -0
  11. package/dist/activities/FileReadActivity.d.ts.map +1 -1
  12. package/dist/activities/FileReadActivity.js +34 -26
  13. package/dist/activities/FileReadActivity.js.map +1 -1
  14. package/dist/activities/FileSplitActivity.d.ts +23 -43
  15. package/dist/activities/FileSplitActivity.d.ts.map +1 -1
  16. package/dist/activities/FileSplitActivity.js +23 -38
  17. package/dist/activities/FileSplitActivity.js.map +1 -1
  18. package/dist/constants/operations.d.ts +1 -1
  19. package/dist/constants/operations.d.ts.map +1 -1
  20. package/dist/constants/operations.js +3 -14
  21. package/dist/constants/operations.js.map +1 -1
  22. package/dist/fs.d.ts +5 -1
  23. package/dist/fs.d.ts.map +1 -1
  24. package/dist/helpers/CSV.d.ts +2 -2
  25. package/dist/helpers/CSV.d.ts.map +1 -1
  26. package/dist/helpers/CSV.js +20 -22
  27. package/dist/helpers/CSV.js.map +1 -1
  28. package/dist/helpers/group-by-key.d.ts +8 -0
  29. package/dist/helpers/group-by-key.d.ts.map +1 -0
  30. package/dist/helpers/group-by-key.js +35 -0
  31. package/dist/helpers/group-by-key.js.map +1 -0
  32. package/dist/helpers/group-records.d.ts +2 -2
  33. package/dist/helpers/group-records.d.ts.map +1 -1
  34. package/dist/helpers/group-records.js +3 -3
  35. package/dist/helpers/group-records.js.map +1 -1
  36. package/dist/helpers/in-chunks.d.ts +5 -0
  37. package/dist/helpers/in-chunks.d.ts.map +1 -0
  38. package/dist/helpers/in-chunks.js +17 -0
  39. package/dist/helpers/in-chunks.js.map +1 -0
  40. package/dist/helpers/split-records.d.ts +4 -3
  41. package/dist/helpers/split-records.d.ts.map +1 -1
  42. package/dist/helpers/split-records.js +75 -46
  43. package/dist/helpers/split-records.js.map +1 -1
  44. package/dist/helpers/where-matcher.d.ts +21 -0
  45. package/dist/helpers/where-matcher.d.ts.map +1 -0
  46. package/dist/helpers/where-matcher.js +11 -0
  47. package/dist/helpers/where-matcher.js.map +1 -0
  48. package/dist/models/BinaryRecordKey.d.ts +27 -0
  49. package/dist/models/BinaryRecordKey.d.ts.map +1 -0
  50. package/dist/models/BinaryRecordKey.js +52 -0
  51. package/dist/models/BinaryRecordKey.js.map +1 -0
  52. package/dist/models/BinarySource.d.ts +45 -0
  53. package/dist/models/BinarySource.d.ts.map +1 -0
  54. package/dist/models/BinarySource.js +58 -0
  55. package/dist/models/BinarySource.js.map +1 -0
  56. package/dist/models/BinarySplit.d.ts +45 -0
  57. package/dist/models/BinarySplit.d.ts.map +1 -0
  58. package/dist/models/BinarySplit.js +81 -0
  59. package/dist/models/BinarySplit.js.map +1 -0
  60. package/dist/models/BinarySplitInput.d.ts +21 -0
  61. package/dist/models/BinarySplitInput.d.ts.map +1 -0
  62. package/dist/models/BinarySplitInput.js +85 -0
  63. package/dist/models/BinarySplitInput.js.map +1 -0
  64. package/dist/models/Database.d.ts +15 -0
  65. package/dist/models/Database.d.ts.map +1 -0
  66. package/dist/models/Database.js +57 -0
  67. package/dist/models/Database.js.map +1 -0
  68. package/dist/models/OpenKeyGroup.d.ts +23 -0
  69. package/dist/models/OpenKeyGroup.d.ts.map +1 -0
  70. package/dist/models/OpenKeyGroup.js +42 -0
  71. package/dist/models/OpenKeyGroup.js.map +1 -0
  72. package/dist/models/SplitDatabaseWorker.d.ts +12 -0
  73. package/dist/models/SplitDatabaseWorker.d.ts.map +1 -0
  74. package/dist/models/SplitDatabaseWorker.js +65 -0
  75. package/dist/models/SplitDatabaseWorker.js.map +1 -0
  76. package/dist/workers/split-database.d.ts +10 -0
  77. package/dist/workers/split-database.d.ts.map +1 -0
  78. package/dist/workers/split-database.js +43 -0
  79. package/dist/workers/split-database.js.map +1 -0
  80. package/package.json +5 -4
@@ -1,226 +1,127 @@
1
1
  ---
2
2
  name: framework-fs
3
3
  description: >-
4
- Guides correct use of @t4h.framework/fs in T4H Framework workflows: the
5
- FileSystemClaim abstraction that the package activities (http, jsx) use to
6
- read/write files, the Binary result workflows consume, and runtime providers.
7
- A workflow never implements Binary itself — see the framework-fs-write skill.
8
- Use when a workflow orchestrates file-backed package activities, consuming
9
- Binary results, mocking FileSystemClaim in workflow tests, registering fs
10
- claim providers, or debugging missing fs provider errors.
4
+ Explains the shared rules of @t4h.framework/fs in a T4H Framework app: the
5
+ Binary reference that file primitives pass around, the FileSystemClaim that
6
+ the runtime provides, read() for a small byte window (header, trailer, one
7
+ record), asset() for files shipped with the project, and how to mock
8
+ FileSystemClaim and AssetClaim in a workflow test. Use when a workflow gets,
9
+ passes or reads a Binary (an HTTP body, sftp.get, a PDF), reads a file header
10
+ or trailer, loads a font or template from the project, hits
11
+ ClaimProviderNotFoundException for FileSystemClaim, or wants to call
12
+ readFileSync, fs/promises or binary.stream() in a workflow. For write() use
13
+ framework-fs-write; for split() and join() use framework-fs-split.
11
14
  ---
12
15
 
13
16
  # @t4h.framework/fs
14
17
 
15
- File system **claim** for reading and writing files inside workflow activities. The package defines only the abstract contract; concrete storage (local disk, object storage, etc.) is supplied by the **runtime** or **dev** claim registry.
16
-
17
- Package path: `framework/packages/fs`. Peer dependency: `@t4h.framework/core` (`Binary`).
18
-
19
- ---
20
-
21
- ## Import surface
22
-
23
- Public exports live in `src/fs.ts`:
18
+ ## The Binary
19
+
20
+ A `Binary` (from `@t4h.framework/core`) is a reference to a stored file:
21
+ `contentType`, `contentLength` and `stream()`. It is not the bytes. A workflow
22
+ gets a `Binary` from a framework primitive (an HTTP response body, `sftp.get`,
23
+ `write`, `join`, `asset`, a PDF render) and passes it to the next primitive.
24
+
25
+ Traps:
26
+
27
+ - Never write a class that extends `Binary` in app code. The
28
+ `@t4h.framework/core/no-binary-subclass` rule rejects it outside test files.
29
+ A primitive persists its input, and a `Binary` that holds bytes in memory
30
+ cannot be persisted. It can pass in one environment and fail in another. To
31
+ turn a string or a `Buffer` into a `Binary`, use `write` (skill
32
+ `framework-fs-write`).
33
+ - Never call `binary.stream()` in a workflow. The
34
+ `no-binary-stream-outside-activity` rule rejects it. Use `read` for a small
35
+ window and `split` for the records.
36
+ - Never use `node:fs`, `fs/promises` or `readFileSync` in a workflow. The
37
+ `no-io-in-workflow` rule rejects it. For a file that ships with the project,
38
+ use `asset`.
39
+ - Never construct `FileReadActivity`, `FileWriteActivity`,
40
+ `FileSplitActivity` or `FileJoinActivity`. The
41
+ `no-framework-activity-in-app` rule rejects it. Call the function.
42
+ - A workflow never calls `FileSystemClaim`. The package ships no
43
+ implementation: the runtime provides it. A missing provider gives
44
+ `ClaimProviderNotFoundException`.
45
+
46
+ ## Read a header or a trailer
24
47
 
25
48
  ```typescript
26
- import {
27
- FileSystemClaim,
28
- // file fan-out / fold / windows — see the framework-fs-split skill
29
- split, Split, FileSplitActivity, type FileSplitOptions,
30
- join, FileJoinActivity, type FileJoinOptions, type FileJoinWhere,
31
- read, FileReadActivity, type FileReadOptions,
32
- // turn workflow content into a Binary — see the framework-fs-write skill
33
- write, FileWriteActivity, type FileWriteOptions,
34
- } from '@t4h.framework/fs'
35
- ```
49
+ import { read } from '@t4h.framework/fs'
36
50
 
37
- Do not import from internal paths such as `src/FileSystemClaim.ts` in application code.
38
-
39
- ---
40
-
41
- ## FileSystemClaim contract
42
-
43
- ```typescript
44
- import type { Readable } from 'node:stream'
45
- import type { Binary } from '@t4h.framework/core'
46
-
47
- export abstract class FileSystemClaim {
48
- public abstract write(
49
- path: string,
50
- readable: Readable | Buffer,
51
- contentType?: string,
52
- ): Binary | Promise<Binary>
53
-
54
- public abstract read(path: string): Binary | Promise<Binary>
55
-
56
- public abstract slice(
57
- binary: Binary,
58
- range: { start: number; end: number },
59
- encoding?: BufferEncoding,
60
- ): Binary | Promise<Binary>
61
- }
51
+ const header = await read(file, { range: { start: 0, end: 240 } })
52
+ const trailer = await read(file, {
53
+ range: { start: file.contentLength - 240, end: file.contentLength },
54
+ })
62
55
  ```
63
56
 
64
- | Method | Purpose |
65
- |--------|---------|
66
- | `write(path, readable, contentType?)` | Persist stream or buffer at `path`; return a `Binary` handle (metadata + `stream()`). When `contentType` is omitted, infer it from the path. |
67
- | `read(path)` | Load an existing file at `path`; return a `Binary` handle. |
68
- | `slice(binary, range, encoding?)` | A view over a byte range of an existing binary, **without copying data**. When `encoding` is set, the runtime hydrates the slice as a decoded string instead of a binary — this is how `read` returns a window as a string. Implementations must **compose ranges**: slicing a slice is relative to it, and the stored range is absolute against the underlying file. |
57
+ - `range` is in bytes, `end` is exclusive, and `end` must not be more than
58
+ `contentLength`. With no `range`, `read` reads the whole file.
59
+ - The string goes into the workflow history, so `read` throws a `RangeError`
60
+ past 1 MiB. Never read a data file with `read`; use `split`.
61
+ - Do not call `read` in a loop. The `no-batch-query-in-loop` rule rejects it:
62
+ each call is one activity and one history entry. Read the records with
63
+ `split`.
64
+ - `encoding` defaults to `'utf8'`. Set it (for example `'latin1'`) when the
65
+ file is not UTF-8.
69
66
 
70
- All methods may return synchronously or as a `Promise`. Implementations choose storage layout; callers should only rely on the returned `Binary` API.
67
+ ## Read a file shipped with the project
71
68
 
72
- ### Binary (from `@t4h.framework/core`)
69
+ Declare the files in `framework.config.ts` with `build: { assets: ['assets/**'] }`,
70
+ then:
73
71
 
74
72
  ```typescript
75
- export abstract class Binary {
76
- public abstract readonly contentType: string
77
- public abstract readonly contentLength: number
78
- public abstract stream(): Readable | Promise<Readable>
79
- }
80
- ```
73
+ import { asset } from '@t4h.framework/fs'
81
74
 
82
- The package activities return this `Binary` to your workflow — e.g. an `http`
83
- response `Body` is backed by it. Consume it with `stream()` (or the higher-level
84
- `body.json()` / `text()` that packages expose):
85
-
86
- ```typescript
87
- const stream = await binary.stream()
88
- // consume stream (e.g. stream.toArray() then Buffer.concat)
75
+ const font = await asset('assets/DejaVuSans.ttf')
89
76
  ```
90
77
 
91
- ---
92
-
93
- ## How workflows use it
94
-
95
- Workflows never call `FileSystemClaim` directly. File I/O happens inside the
96
- **ready-made package activities** that already own the claim — the `http` client
97
- persists response bodies, jsx render activities write their output — and you
98
- read the resulting `Binary` (e.g. `await res.body.json()` / `text()`). Your only
99
- responsibility is that the runtime **registers a provider** (below). See
100
- **Known consumers** for which package primitives to orchestrate.
101
-
102
- This package's own workflow-facing primitives are `split`, `join`, `read`
103
- (small byte windows — headers, trailers — hydrated into the workflow as a
104
- string, capped at 1 MiB) and `write` (content produced in the workflow turned
105
- into a file). See the `framework-fs-split` and `framework-fs-write` skills.
106
-
107
- **A workflow never implements `Binary`.** Every `Binary` comes from the
108
- framework — an HTTP response body, `sftp.get`, `write`, `join`. A class holding
109
- bytes in memory cannot be persisted, and every primitive it is handed to
110
- persists its input. See the `framework-fs-write` skill.
78
+ The path is relative to the package root. `asset` is not an activity and
79
+ returns a `Binary`. It throws when the path is not in `build.assets`.
111
80
 
112
- ---
113
-
114
- ## Runtime provider
81
+ ## Test
115
82
 
116
- The runtime must register a concrete subclass:
83
+ Use `TestWorkflowEnvironment` and `mockClaim` from
84
+ `@t4h.framework/core/testing` (skill `framework-workflow-testing`). A test file
85
+ can extend `Binary` for a test double:
117
86
 
118
87
  ```typescript
119
- { provide: FileSystemClaim, value: new MyFileSystemClaimImpl() }
120
- ```
121
-
122
- `MyFileSystemClaimImpl` must extend `FileSystemClaim` and implement `write` and `read`. The README uses `LocalFileSystemClaimImpl` as a **placeholder name** for that implementation; there is no class by that name in this monorepo.
123
-
124
- ---
125
-
126
- ## Known consumers in this monorepo
127
-
128
- These packages depend on `@t4h.framework/fs` and declare `FileSystemClaim`; read their skills/README for full workflow patterns.
129
-
130
- | Package | Usage |
131
- |---------|--------|
132
- | `@t4h.framework/http` | The HTTP activity persists response bodies to `fs` and returns them wrapped in a `Body` — you read them with `res.body.json()` / `text()`. |
133
- | `@t4h.framework/jsx` | Render activities write their output to `fs` and hand back a `Binary` you consume in the workflow. |
134
- | `@t4h.framework/fs` itself | `FileSplitActivity` streams the source and hands its slices to `startMany` as strings; `FileJoinActivity` streams batch results into a new file; `FileReadActivity` slices (or streams, when the binary is not file-backed) a small window into a string. See the `framework-fs-split` skill. |
135
-
136
- Orchestrate these package primitives from your workflow; the `fs` write/read happens inside them.
137
-
138
- ---
139
-
140
- ## Testing
141
-
142
- Test the **workflow** with `TestWorkflowEnvironment` from
143
- `@t4h.framework/core/testing`, mocking `FileSystemClaim` (plus whatever other
144
- claims the triggered activities need, e.g. `HttpClientRequestClaim` for an HTTP
145
- call). See the **framework-workflow-testing** skill for the harness API.
146
-
147
- Subclassing `Binary` is legitimate **only here**, in a test double standing in
148
- for the claim — never in workflow or App code.
149
-
150
- ```typescript
151
- import { Readable } from 'node:stream'
152
- import { Binary } from '@t4h.framework/core'
153
- import { TestWorkflowEnvironment, mockClaim } from '@t4h.framework/core/testing'
154
- import { FileSystemClaim } from '@t4h.framework/fs'
155
-
156
- class MockBinary extends Binary {
157
- public readonly contentType: string
88
+ class TestBinary extends Binary {
158
89
  public readonly contentLength: number
159
90
 
160
- constructor(private readonly data: Buffer, contentType: string) {
91
+ constructor(
92
+ private readonly data: Buffer,
93
+ public readonly contentType = 'application/octet-stream',
94
+ ) {
161
95
  super()
162
- this.contentType = contentType
163
96
  this.contentLength = data.byteLength
164
97
  }
165
98
 
166
99
  public stream() {
167
- return Readable.from(this.data)
100
+ return Readable.from([this.data])
168
101
  }
169
102
  }
170
103
 
171
- // mockImplementation: replay runs the workflow twice, so return a fresh Binary each call.
172
- const mockWrite = vi.fn().mockImplementation(async (_path: string, readable: Readable) => {
173
- const chunks = await readable.toArray()
174
- return new MockBinary(Buffer.concat(chunks), 'application/json')
175
- })
176
-
177
- const env = TestWorkflowEnvironment.create({ now: 0 })
178
-
179
- await env.execute(workflow, input, {
104
+ await TestWorkflowEnvironment.create().execute(workflow, input, {
180
105
  claims: [
181
- mockClaim(FileSystemClaim, { write: mockWrite, read: vi.fn() }),
182
- // ...other claims the workflow's activities need
106
+ mockClaim(FileSystemClaim, {
107
+ write: vi.fn(async (_path, content) => new TestBinary(content)),
108
+ }),
109
+ mockClaim(AssetClaim, {
110
+ resolve: async () => new TestBinary(fontBytes, 'font/ttf'),
111
+ }),
183
112
  ],
184
113
  })
185
114
  ```
186
115
 
187
- Assert `mockWrite` was called with the expected path and body when relevant.
188
-
189
- ---
190
-
191
- ## Implementing a custom FileSystemClaim
192
-
193
- 1. **Extend** `FileSystemClaim` from `@t4h.framework/fs`.
194
- 2. **Return** a `Binary` subclass (or core-compatible `Binary`) from `write` and `read` with correct `contentType`, `contentLength`, and `stream()`.
195
- 3. **Accept** `Readable | Buffer` and the optional `contentType` in `write`
196
- (match the abstract signature), falling back to inferring the content type
197
- from the path when it is omitted.
198
- 4. **Register** `{ provide: FileSystemClaim, value: new YourImpl() }` in the runtime (tests use `mockClaim(FileSystemClaim, ...)` instead).
199
- 5. **Keep paths deterministic** when activities replay — unpredictable temp paths are fine if only `Binary` metadata is serialized and replay re-executes `write`; know your serializer/replay story if `path` is persisted.
200
-
201
- ---
202
-
203
- ## App / monorepo checklist
204
-
205
- 1. Add `"@t4h.framework/fs": "workspace:^"` (or published version) to app `dependencies` when activities use `FileSystemClaim`.
206
- 2. Ensure **`@t4h.framework/core`** is available (peer of `fs`).
207
- 3. Register **`FileSystemClaim`** in the runtime or test `claims` array.
208
- 4. Orchestrate file I/O through the package activities that own `FileSystemClaim` (http, jsx) — workflow code never declares an activity or accesses `this.claims.fs`.
209
-
210
- ---
211
-
212
- ## Anti-patterns
213
-
214
- - Importing from `@t4h.framework/fs` paths other than the package export (`FileSystemClaim` only).
215
- - Omitting `FileSystemClaim` from providers while using `@t4h.framework/http` or `@t4h.framework/jsx` activities that require `fs`.
216
- - Assuming `packages/fs` ships a concrete disk/S3 implementation — it does not; implement and register `FileSystemClaim` in your runtime or test harness.
217
-
218
- ---
116
+ The environment runs the workflow again to check the replay, so return a new
117
+ `Binary` on each call. In a `read` test, a `TestBinary` passes: when the claim
118
+ cannot slice, `read` streams the window.
219
119
 
220
- ## Related skills
120
+ ## Reference
221
121
 
222
- - `framework-fs-split` — `split`/`join`: fan a file out into a batch of workflows and fold the results back into a file.
223
- - `framework-fs-write` — `write`: turn workflow content into a `Binary`, and why an App must never implement `Binary`.
224
- - `framework-core` — `Claim`, `Binary`, and the `TestWorkflowEnvironment` testing harness.
225
- - `framework-http` — HTTP activities that persist bodies via `FileSystemClaim`.
226
- - `framework-jsx` — render activities that write/read via `fs`.
122
+ - Overview, `Binary` and `FileSystemClaim`: https://tech4humans-brasil.github.io/framework/fs/
123
+ - `read`: https://tech4humans-brasil.github.io/framework/fs/read
124
+ - `asset`: https://tech4humans-brasil.github.io/framework/fs/asset
125
+ - Types: `node_modules/@t4h.framework/fs/dist/fs.d.ts`
126
+ - Related skills: `framework-fs-write`, `framework-fs-split`,
127
+ `framework-workflow-testing`.