@t4h.framework/zip 0.0.0-experimental-20260907231524

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 (56) hide show
  1. package/.ai/skills/framework-zip/SKILL.md +154 -0
  2. package/CHANGELOG.md +34 -0
  3. package/README.md +208 -0
  4. package/dist/activities/ZipExtractActivity.d.ts +20 -0
  5. package/dist/activities/ZipExtractActivity.d.ts.map +1 -0
  6. package/dist/activities/ZipExtractActivity.js +40 -0
  7. package/dist/activities/ZipExtractActivity.js.map +1 -0
  8. package/dist/activities/ZipListActivity.d.ts +19 -0
  9. package/dist/activities/ZipListActivity.d.ts.map +1 -0
  10. package/dist/activities/ZipListActivity.js +27 -0
  11. package/dist/activities/ZipListActivity.js.map +1 -0
  12. package/dist/helpers/ZipChecksum.d.ts +15 -0
  13. package/dist/helpers/ZipChecksum.d.ts.map +1 -0
  14. package/dist/helpers/ZipChecksum.js +28 -0
  15. package/dist/helpers/ZipChecksum.js.map +1 -0
  16. package/dist/helpers/ZipSource.d.ts +17 -0
  17. package/dist/helpers/ZipSource.d.ts.map +1 -0
  18. package/dist/helpers/ZipSource.js +40 -0
  19. package/dist/helpers/ZipSource.js.map +1 -0
  20. package/dist/helpers/extract-entry.d.ts +10 -0
  21. package/dist/helpers/extract-entry.d.ts.map +1 -0
  22. package/dist/helpers/extract-entry.js +30 -0
  23. package/dist/helpers/extract-entry.js.map +1 -0
  24. package/dist/helpers/open-archive.d.ts +14 -0
  25. package/dist/helpers/open-archive.d.ts.map +1 -0
  26. package/dist/helpers/open-archive.js +57 -0
  27. package/dist/helpers/open-archive.js.map +1 -0
  28. package/dist/tools/assert-entry-path.d.ts +2 -0
  29. package/dist/tools/assert-entry-path.d.ts.map +1 -0
  30. package/dist/tools/assert-entry-path.js +11 -0
  31. package/dist/tools/assert-entry-path.js.map +1 -0
  32. package/dist/tools/match-path.d.ts +2 -0
  33. package/dist/tools/match-path.d.ts.map +1 -0
  34. package/dist/tools/match-path.js +31 -0
  35. package/dist/tools/match-path.js.map +1 -0
  36. package/dist/tools/select-entries.d.ts +7 -0
  37. package/dist/tools/select-entries.d.ts.map +1 -0
  38. package/dist/tools/select-entries.js +20 -0
  39. package/dist/tools/select-entries.js.map +1 -0
  40. package/dist/tools/to-entry-info.d.ts +4 -0
  41. package/dist/tools/to-entry-info.d.ts.map +1 -0
  42. package/dist/tools/to-entry-info.js +12 -0
  43. package/dist/tools/to-entry-info.js.map +1 -0
  44. package/dist/tools/zip-format.d.ts +52 -0
  45. package/dist/tools/zip-format.d.ts.map +1 -0
  46. package/dist/tools/zip-format.js +178 -0
  47. package/dist/tools/zip-format.js.map +1 -0
  48. package/dist/typings/ZipEntry.d.ts +14 -0
  49. package/dist/typings/ZipEntry.d.ts.map +1 -0
  50. package/dist/typings/ZipEntry.js +2 -0
  51. package/dist/typings/ZipEntry.js.map +1 -0
  52. package/dist/zip.d.ts +5 -0
  53. package/dist/zip.d.ts.map +1 -0
  54. package/dist/zip.js +5 -0
  55. package/dist/zip.js.map +1 -0
  56. package/package.json +48 -0
@@ -0,0 +1,154 @@
1
+ ---
2
+ name: framework-zip
3
+ description: >-
4
+ Guides correct use of @t4h.framework/zip in workflows: opening a ZIP archive
5
+ that arrives from SFTP, HTTP or storage, listing what is inside it without
6
+ downloading it, and extracting the entries that matter into `Binary` files
7
+ that `read`, `split` and `sftp.put` accept. Use whenever a workflow receives
8
+ a `.zip` (a compressed batch file, a bundle of invoices, a partner's return
9
+ package), whenever the user mentions unzipping, decompressing, inflate,
10
+ deflate, zip entries, or the symbols `unzip`, `list`, `ZipEntry`,
11
+ `ZipExtractActivity`, and above all when someone is about to hand-roll a
12
+ lazy `Binary` that inflates on `stream()` in App code. Prefer this skill
13
+ over `node:zlib` in workflow code and over any hand-written ZIP parsing.
14
+ ---
15
+
16
+ # @t4h.framework/zip
17
+
18
+ Reading ZIP archives inside a workflow. Package path: `framework/packages/zip`.
19
+ Peer dependencies: `@t4h.framework/core`, `@t4h.framework/fs`.
20
+
21
+ ## Import surface
22
+
23
+ ```typescript
24
+ import { list, unzip } from '@t4h.framework/zip'
25
+ ```
26
+
27
+ Two durable activities, nothing else to wire:
28
+
29
+ - `unzip(archive, options?)` — extracts entries into files, resolves with
30
+ `ZipEntry[]` (`{ path, binary, contentLength, compressedLength,
31
+ compressionMethod, crc32, lastModifiedAt, directory }`), directories
32
+ excluded, in central directory order.
33
+ - `list(archive, options?)` — the same metadata **without** `binary` and
34
+ without transferring content, directories included.
35
+
36
+ `archive` is any `Binary`: `sftp.get`, an HTTP response body, `write`, a slice.
37
+
38
+ ## Workflow usage
39
+
40
+ The canonical pipeline — fetch a compressed batch, extract the one file that
41
+ carries the records, fan it out:
42
+
43
+ ```typescript
44
+ import { split, Split } from '@t4h.framework/fs'
45
+ import { unzip } from '@t4h.framework/zip'
46
+
47
+ const main = new Workflow(inputSchema, async input => {
48
+ const archive = await sftp.get(input.path)
49
+
50
+ const [file] = await unzip(archive, { include: 'MOVIMENTO.TXT' })
51
+
52
+ if (!file) throw new Error(`${input.path} has no movement file`)
53
+
54
+ return await split(file.binary, processRecord, {
55
+ delimiter: Split.line(),
56
+ })
57
+ })
58
+ ```
59
+
60
+ Deciding from metadata first, when most runs do not need the content:
61
+
62
+ ```typescript
63
+ const entries = await list(archive)
64
+
65
+ const invoice = entries.find(entry => entry.path.endsWith(`/${id}.txt`))
66
+
67
+ if (!invoice) return { status: 'missing' }
68
+
69
+ const [extracted] = await unzip(archive, { include: invoice.path })
70
+ ```
71
+
72
+ ## Options
73
+
74
+ | Option | Applies to | Default | Behavior |
75
+ | ------------- | --------------- | ------- | ---------------------------------------------------------------------- |
76
+ | `include` | `unzip`, `list` | — | Glob or array of globs an entry must match. |
77
+ | `exclude` | `unzip`, `list` | — | Globs that drop entries, applied after `include`. |
78
+ | `encoding` | `unzip`, `list` | `utf8` | Decoding of entry names not flagged UTF-8 (legacy archives: `latin1`). |
79
+ | `contentType` | `unzip` | — | Content type of the extracted files; omitted, the claim infers it. |
80
+
81
+ Patterns match the whole entry path, case sensitively: `*` and `?` never cross
82
+ a `/`, `**` does (`'**.csv'` reaches any depth, `'invoices/*'` one level). An
83
+ `include` matching nothing resolves with an empty array — always check.
84
+
85
+ ## Behaviors you can rely on
86
+
87
+ - **Content never enters the workflow.** Only file references do; the bytes go
88
+ from the archive to storage, inflated in a stream with backpressure.
89
+ - **You pay for what you extract.** A ZIP's index sits at the end of the file,
90
+ so the listing is a ranged read of the tail and each entry is fetched by
91
+ offset — pulling one file out of a 6 MB archive does not download 6 MB. A
92
+ binary the claim cannot slice (SFTP, HTTP) is streamed **once** into storage
93
+ first, then read by range.
94
+ - **Corruption fails the activity.** CRC-32 and size are verified as the entry
95
+ flows to storage.
96
+ - **Hostile paths are refused** before anything is written (`../`, absolute,
97
+ drive qualified).
98
+ - **Format coverage.** Stored and deflate, ZIP64, data descriptors, archive
99
+ comments, UTF-8 and legacy names, empty entries. Encrypted entries and other
100
+ compression methods are refused with a `TypeError`.
101
+
102
+ ## Limits
103
+
104
+ - **No writing.** The package reads archives; it does not create them. A file
105
+ to send compressed has to be produced another way.
106
+ - **Deflate and stored only.** bzip2, LZMA and zstd entries are refused.
107
+ - **Extraction is one activity per call.** The result is recorded whole, so a
108
+ `unzip` with no `include` over a 3 000-entry archive puts 3 000 references in
109
+ the history and 3 000 files in storage. Filter.
110
+ - **What is bounded and what is not.** Entry content, the reads over the
111
+ archive and the history are constant whatever the archive weighs. Memory
112
+ grows with the _number_ of entries — the central directory is read whole,
113
+ 50 to 100 bytes per entry — not with their size. A zip bomb costs storage,
114
+ not memory: the read stops at the declared compressed size and the inflate is
115
+ aborted the moment the output goes past the declared uncompressed size, so a
116
+ lying entry never writes more than it declares — one that honestly declares
117
+ 40 GB still writes 40 GB, and `list` shows `contentLength` beforehand. A
118
+ non-sliceable source adds one durable copy of the archive, and whether that
119
+ copy streams is the `FileSystemClaim`'s contract, as it is for `split`.
120
+
121
+ ## Runtime requirements
122
+
123
+ - Claim provider: `FileSystemClaim` (from `@t4h.framework/fs`).
124
+ - Production allowlist: register `ZipExtractActivity` and `ZipListActivity`
125
+ via `runtime.addActivity`.
126
+
127
+ ## Anti-patterns
128
+
129
+ - **Hand-rolling a lazy `Binary` that inflates on `stream()`.** The usual shape
130
+ is a `class ZipEntryBinary extends Binary` in App code that parses the local
131
+ header on demand. It passes locally and breaks in production: a `Binary`
132
+ given to a primitive is the input of a durable activity, serialized to
133
+ storage before the activity runs, and rehydrated in a process where the
134
+ closure no longer exists. Its `contentLength` also has to lie — the real size
135
+ lives in the central directory — so every ranged `read` over it truncates.
136
+ `unzip` does the same work inside an activity; that is the supported way.
137
+ - **Inflating in workflow code.** `createInflateRaw` in a workflow re-runs on
138
+ every replay and holds the content in workflow memory. Extraction belongs to
139
+ an activity.
140
+ - **Calling `unzip` per record.** Extract once, high in the workflow, and pass
141
+ the entry `Binary` down to children. `unzip` inside a loop or inside a helper
142
+ every child calls turns one extraction into thousands of activities.
143
+ - **Extracting to read metadata.** If the run only needs a name, a size or a
144
+ date, `list` answers without transferring content.
145
+ - **Trusting `include` to find something.** It is a filter: no match is an
146
+ empty array, not an error.
147
+
148
+ ## Related skills
149
+
150
+ - `framework-fs-split` — `split`/`join`/`read`: what to do with the entry once
151
+ it is a `Binary`.
152
+ - `framework-sftp` — `sftp.get` is where most archives come from.
153
+ - `framework-transforms` — JSON inside the archive: normalize to NDJSON before
154
+ fanning out.
package/CHANGELOG.md ADDED
@@ -0,0 +1,34 @@
1
+ # @t4h.framework/zip
2
+
3
+ ## 0.0.0-experimental-20260907231524
4
+
5
+ ### Minor Changes
6
+
7
+ - [#163](https://github.com/tech4humans-brasil/framework/pull/163) [`ff14777`](https://github.com/tech4humans-brasil/framework/commit/ff147776f44b5cb6ebb4310f44fd57aecf1df79e) Thanks [@gusteycamargo](https://github.com/gusteycamargo)! - Adds `@t4h.framework/zip`: reading ZIP archives from inside a workflow.
8
+
9
+ `unzip(archive, options?)` extracts the entries into files and resolves with
10
+ `ZipEntry[]` — `{ path, binary, contentLength, compressedLength,
11
+ compressionMethod, crc32, lastModifiedAt, directory }` — and
12
+ `list(archive, options?)` returns the same metadata without transferring any
13
+ content. `include`/`exclude` globs select what is extracted or listed,
14
+ `encoding` decodes legacy entry names and `contentType` labels the extracted
15
+ files.
16
+
17
+ Both read the archive back to front through `FileSystemClaim.slice`: the tail
18
+ of the file gives the whole listing (about a kilobyte on an archive without a
19
+ comment) and each entry is fetched by byte range, inflated in a stream and
20
+ written through `FileSystemClaim.write` with backpressure — content never
21
+ enters workflow memory or the history, and extracting one file out of a large
22
+ archive does not download the archive. A binary the claim cannot slice (SFTP,
23
+ HTTP) is streamed once into a durable copy first, the same trade `split` makes.
24
+
25
+ Every entry is verified against its CRC-32 and declared size while it flows to
26
+ storage, and the inflate is aborted the moment the output goes past that size,
27
+ so an entry that lies never writes more than it declares. Entry paths that
28
+ escape the extraction directory, encrypted entries and compression methods
29
+ other than stored and deflate are refused. ZIP64, data descriptors, archive
30
+ comments, UTF-8 and legacy names are handled.
31
+
32
+ This replaces hand-rolled lazy `Binary` subclasses that inflate on `stream()`
33
+ in App code: those are serialized as activity input, cannot be rehydrated after
34
+ a replay in another process, and have to lie about `contentLength`.
package/README.md ADDED
@@ -0,0 +1,208 @@
1
+ # @t4h.framework/zip
2
+
3
+ Reads ZIP archives inside a workflow. `list` tells you what is in an archive
4
+ without transferring its content, and `unzip` unpacks the entries you ask for
5
+ into files — each one a `Binary`, the same reference every other package
6
+ consumes.
7
+
8
+ Both are durable activities: the work happens once, the result is recorded in
9
+ the workflow history, and a replay never re-reads the archive.
10
+
11
+ ## Installation
12
+
13
+ ```bash
14
+ npm install @t4h.framework/zip
15
+ # or
16
+ yarn add @t4h.framework/zip
17
+ ```
18
+
19
+ Peer dependencies: `@t4h.framework/core` and `@t4h.framework/fs` — entries are
20
+ written through the `FileSystemClaim`, so a file system provider must be
21
+ registered in the runtime.
22
+
23
+ ## Basic usage
24
+
25
+ ```typescript
26
+ import { unzip } from '@t4h.framework/zip'
27
+
28
+ const [entry] = await unzip(archive)
29
+
30
+ // entry.binary is a regular Binary: read a window, split it, upload it
31
+ const header = await read(entry.binary, { range: { start: 0, end: 240 } })
32
+ ```
33
+
34
+ `archive` is any `Binary`: an `sftp.get`, an HTTP response body, a file written
35
+ by `write`. `unzip` resolves with one `ZipEntry` per file in the archive, in
36
+ central directory order, directories excluded.
37
+
38
+ ## Extract only what you need
39
+
40
+ An archive with three thousand invoices in it does not have to become three
41
+ thousand files:
42
+
43
+ ```typescript
44
+ const [invoice] = await unzip(archive, { include: 'invoices/0001.txt' })
45
+
46
+ const csvs = await unzip(archive, {
47
+ include: '**.csv',
48
+ exclude: '**/draft-*.csv',
49
+ })
50
+ ```
51
+
52
+ `include` and `exclude` are glob patterns (or arrays of them) matched against
53
+ the whole entry path:
54
+
55
+ | Pattern | Matches |
56
+ | -------------- | --------------------------------------- |
57
+ | `notes.txt` | exactly that path |
58
+ | `*.txt` | `.txt` files at the root of the archive |
59
+ | `invoices/*` | anything directly inside `invoices/` |
60
+ | `**.csv` | `.csv` files at any depth |
61
+ | `report-?.txt` | `report-1.txt`, not `report-42.txt` |
62
+
63
+ Matching is case sensitive, `include` runs before `exclude`, and an `include`
64
+ that matches nothing resolves with an empty array rather than throwing.
65
+
66
+ ## Listing without unpacking
67
+
68
+ ```typescript
69
+ import { list } from '@t4h.framework/zip'
70
+
71
+ const entries = await list(archive)
72
+ // [{ path, contentLength, compressedLength, compressionMethod,
73
+ // crc32, lastModifiedAt, directory }, ...]
74
+ ```
75
+
76
+ `list` reads the archive's index (its central directory), which lives in the
77
+ last few kilobytes of the file — so on a sliceable binary a listing costs about
78
+ a kilobyte, whatever the archive weighs. Use it whenever the decision (does
79
+ this archive contain the file I need? how big is it?) can be made from metadata
80
+ alone, and unzip afterwards only if the content is really needed.
81
+
82
+ Unlike `unzip`, `list` reports directory entries too, flagged with
83
+ `directory: true`.
84
+
85
+ ## How the bytes move
86
+
87
+ The package never holds an archive, or an entry, in memory.
88
+
89
+ 1. **Ranged reads.** A ZIP is read back to front — the central directory sits
90
+ at the end of the file and points backwards at each entry — so every read
91
+ goes through `FileSystemClaim.slice`: the tail for the index, one local
92
+ header per entry, then the entry's compressed range.
93
+ 2. **Streaming inflate.** That range is streamed through `inflateRaw` and
94
+ straight into `FileSystemClaim.write`, with backpressure all the way.
95
+ Extracting a thousand entries costs the same memory as extracting one.
96
+ 3. **One entry at a time.** Entries are extracted sequentially, on purpose:
97
+ parallel extraction would multiply the memory and the storage pressure for
98
+ no gain on a single source stream.
99
+ 4. **Verified content.** Every entry is checked against the CRC-32 and the size
100
+ the archive declares while it flows to storage, so a truncated download or a
101
+ corrupted archive fails the activity instead of quietly producing a shorter
102
+ file.
103
+
104
+ ### Binaries that cannot be sliced
105
+
106
+ Ranged reads need a binary the file system claim owns. An `sftp.get` reference
107
+ or an HTTP body is not one: for those, the archive is streamed **once** into
108
+ storage (`zip/<activity-id>.zip`) and the ranges are read from that copy — the
109
+ same trade `split` makes. It costs one pass and one durable copy of the
110
+ archive, and everything downstream behaves identically.
111
+
112
+ Nothing has to be configured for this; it is the fallback, not a mode.
113
+
114
+ ## Memory: what scales with what
115
+
116
+ Bounded, whatever the archive weighs: entry content (source range →
117
+ `inflateRaw` → CRC-32 → `FileSystemClaim.write`, one entry at a time), the
118
+ reads over the archive (a ranged read of the tail plus one 30-byte local header
119
+ per entry) and the workflow history (references only).
120
+
121
+ What scales, and with what:
122
+
123
+ - **The central directory is read whole**, so memory grows with the _number_ of
124
+ entries — 50 to 100 bytes each. Three thousand entries is 200 KB; a million
125
+ is around 100 MB.
126
+ - **`unzip` without `include` extracts everything**: one file in storage and
127
+ one reference in the history per entry.
128
+ - **A zip bomb costs storage, not memory.** The read stops at the declared
129
+ compressed size, and the inflate is aborted the moment the output goes past
130
+ the declared uncompressed size — an entry that lies never writes more than it
131
+ declares. One that honestly declares 40 GB still writes 40 GB, so check
132
+ `contentLength` with `list` beforehand.
133
+ - **A non-sliceable source** is streamed once into a durable copy of the
134
+ archive; whether that copy streams is the `FileSystemClaim`'s contract.
135
+ - **Downstream is yours.** `buffer(await entry.binary.stream())` pulls the file
136
+ into workflow memory; `read` is capped at 1 MiB and `split` streams.
137
+
138
+ ## Never build a lazy `Binary` by hand
139
+
140
+ The temptation, when a workflow needs one entry out of a zip, is to write a
141
+ `Binary` subclass that inflates on `stream()` and pass it around. It works in
142
+ the dev server and breaks in production:
143
+
144
+ - A `Binary` handed to a primitive becomes the **input of a durable activity**,
145
+ and that input is serialized to storage before the activity runs. A class
146
+ holding a closure over another stream has nothing to serialize into.
147
+ - After a replay the activity may run in **another process**, where the object
148
+ no longer exists.
149
+ - `contentLength` has to lie (the uncompressed size is only in the central
150
+ directory), and every consumer that trusts it — a ranged `read`, a
151
+ `Content-Length` header — is then wrong.
152
+
153
+ `unzip` is the supported answer: it does the same work inside an activity, and
154
+ what travels is a file reference the runtime knows how to rehydrate.
155
+
156
+ ## Cost model
157
+
158
+ `unzip` and `list` are activities, so each call is one history entry with the
159
+ work behind it done exactly once. The reference to watch is **how many times
160
+ you call them**, not how big the archive is:
161
+
162
+ ```typescript
163
+ // once per run, however many records follow
164
+ const [entry] = await unzip(archive, { include: 'MOVIMENTO.TXT' })
165
+
166
+ const batch = await split(entry.binary, processRecord, {
167
+ delimiter: Split.line(),
168
+ })
169
+ ```
170
+
171
+ Calling `unzip` once per record instead — inside a loop, or inside a helper
172
+ called by every child — turns one extraction into thousands of activities, each
173
+ with its own history entry and its own copy of the entry in storage. Extract
174
+ high, pass the `Binary` down.
175
+
176
+ ## Limits
177
+
178
+ - **Deflate and stored only** (methods `0` and `8`) — the two an archiver
179
+ produces without being asked. Anything else (bzip2, LZMA, zstd) is refused
180
+ with a `TypeError` naming the method.
181
+ - **No encryption.** An encrypted entry is refused; the ZIP crypto formats are
182
+ either broken (ZipCrypto) or vendor-specific (AES extensions).
183
+ - **No writing.** This package reads archives; it does not create them.
184
+ - **Reading, not extracting to disk.** Entries land wherever the
185
+ `FileSystemClaim` puts them, under `zip/<activity-id>/<entry path>`.
186
+ - **Hostile paths are refused.** An entry whose path is absolute, drive
187
+ qualified or contains `..` fails the whole activity before anything is
188
+ written (the "zip slip" class of archive).
189
+
190
+ ZIP64 archives, entries written with a data descriptor (the streaming writers'
191
+ flavour, where the local header has no sizes), UTF-8 and legacy entry names,
192
+ archive comments and empty entries are all handled.
193
+
194
+ ## Errors
195
+
196
+ | Situation | Error |
197
+ | ----------------------------------------------- | ------------- |
198
+ | Not a ZIP, or truncated beyond repair | `SyntaxError` |
199
+ | CRC-32 or size mismatch on an entry | `SyntaxError` |
200
+ | Encrypted entry, unsupported compression method | `TypeError` |
201
+ | Entry path escaping the extraction directory | `TypeError` |
202
+ | Multi-disk archive | `TypeError` |
203
+
204
+ ## Runtime requirements
205
+
206
+ - Claim provider: `FileSystemClaim` (from `@t4h.framework/fs`).
207
+ - Production allowlist: register `ZipExtractActivity` and `ZipListActivity`
208
+ through `runtime.addActivity`.
@@ -0,0 +1,20 @@
1
+ import { type Binary, SyncActivity } from '@t4h.framework/core';
2
+ import type { ZipEntry } from '../typings/ZipEntry.js';
3
+ export type UnzipOptions = {
4
+ include?: string | readonly string[];
5
+ exclude?: string | readonly string[];
6
+ encoding?: BufferEncoding;
7
+ contentType?: string;
8
+ };
9
+ export type ZipExtractActivityInput = {
10
+ binary: Binary;
11
+ options: UnzipOptions;
12
+ };
13
+ export declare class ZipExtractActivity extends SyncActivity<readonly [binary: Binary, options: UnzipOptions], ZipExtractActivityInput, ZipEntry[], ZipEntry[]> {
14
+ private readonly claims;
15
+ toInput(binary: Binary, options: UnzipOptions): ZipExtractActivityInput;
16
+ run({ binary, options, }: ZipExtractActivityInput): Promise<ZipEntry[]>;
17
+ toOutput(output: ZipEntry[]): ZipEntry[];
18
+ }
19
+ export declare function unzip(binary: Binary, options?: UnzipOptions): Promise<ZipEntry[]>;
20
+ //# sourceMappingURL=ZipExtractActivity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ZipExtractActivity.d.ts","sourceRoot":"","sources":["../../src/activities/ZipExtractActivity.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,MAAM,EAAkB,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAQ/E,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAA;AAEtD,MAAM,MAAM,YAAY,GAAG;IACzB,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAAA;IACpC,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAAA;IACpC,QAAQ,CAAC,EAAE,cAAc,CAAA;IACzB,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB,CAAA;AAED,MAAM,MAAM,uBAAuB,GAAG;IACpC,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,YAAY,CAAA;CACtB,CAAA;AAED,qBAAa,kBAAmB,SAAQ,YAAY,CAClD,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,CAAC,EAChD,uBAAuB,EACvB,QAAQ,EAAE,EACV,QAAQ,EAAE,CACX;IACC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAErB;IAEK,OAAO,CACZ,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,YAAY,GACpB,uBAAuB;IAIb,GAAG,CAAC,EACf,MAAM,EACN,OAAO,GACR,EAAE,uBAAuB,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC;IA0BzC,QAAQ,CAAC,MAAM,EAAE,QAAQ,EAAE,GAAG,QAAQ,EAAE;CAGhD;AAED,wBAAsB,KAAK,CACzB,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,YAAiB,GACzB,OAAO,CAAC,QAAQ,EAAE,CAAC,CAErB"}
@@ -0,0 +1,40 @@
1
+ import { Claim, History, SyncActivity } from '@t4h.framework/core';
2
+ import { FileSystemClaim } from '@t4h.framework/fs';
3
+ import { extractEntry } from '../helpers/extract-entry.js';
4
+ import { openArchive } from '../helpers/open-archive.js';
5
+ import { assertEntryPath } from '../tools/assert-entry-path.js';
6
+ import { selectEntries } from '../tools/select-entries.js';
7
+ import { toEntryInfo } from '../tools/to-entry-info.js';
8
+ export class ZipExtractActivity extends SyncActivity {
9
+ claims = new Claim({
10
+ fs: FileSystemClaim,
11
+ });
12
+ toInput(binary, options) {
13
+ return { binary, options };
14
+ }
15
+ async run({ binary, options, }) {
16
+ const { source, entries } = await openArchive(this.claims.fs, binary, {
17
+ encoding: options.encoding ?? 'utf8',
18
+ durablePath: `zip/${this.id}.zip`,
19
+ });
20
+ const selected = selectEntries(entries, options).filter(entry => !entry.directory);
21
+ for (const entry of selected)
22
+ assertEntryPath(entry.path);
23
+ const extracted = [];
24
+ for (const entry of selected) {
25
+ const content = await extractEntry(this.claims.fs, source, entry, {
26
+ path: `zip/${this.id}/${entry.path}`,
27
+ contentType: options.contentType,
28
+ });
29
+ extracted.push({ ...toEntryInfo(entry), binary: content });
30
+ }
31
+ return extracted;
32
+ }
33
+ toOutput(output) {
34
+ return output;
35
+ }
36
+ }
37
+ export async function unzip(binary, options = {}) {
38
+ return await History.reconciler(ZipExtractActivity, binary, options);
39
+ }
40
+ //# sourceMappingURL=ZipExtractActivity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ZipExtractActivity.js","sourceRoot":"","sources":["../../src/activities/ZipExtractActivity.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAC/E,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AAEnD,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAA;AAC1D,OAAO,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAA;AACxD,OAAO,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAA;AAC/D,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAA;AAC1D,OAAO,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAA;AAevD,MAAM,OAAO,kBAAmB,SAAQ,YAKvC;IACkB,MAAM,GAAG,IAAI,KAAK,CAAC;QAClC,EAAE,EAAE,eAAe;KACpB,CAAC,CAAA;IAEK,OAAO,CACZ,MAAc,EACd,OAAqB;QAErB,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,CAAA;IAC5B,CAAC;IAEM,KAAK,CAAC,GAAG,CAAC,EACf,MAAM,EACN,OAAO,GACiB;QACxB,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,WAAW,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE;YACpE,QAAQ,EAAE,OAAO,CAAC,QAAQ,IAAI,MAAM;YACpC,WAAW,EAAE,OAAO,IAAI,CAAC,EAAE,MAAM;SAClC,CAAC,CAAA;QAEF,MAAM,QAAQ,GAAG,aAAa,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,MAAM,CACrD,KAAK,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,SAAS,CAC1B,CAAA;QAED,KAAK,MAAM,KAAK,IAAI,QAAQ;YAAE,eAAe,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;QAEzD,MAAM,SAAS,GAAe,EAAE,CAAA;QAEhC,KAAK,MAAM,KAAK,IAAI,QAAQ,EAAE,CAAC;YAC7B,MAAM,OAAO,GAAG,MAAM,YAAY,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE;gBAChE,IAAI,EAAE,OAAO,IAAI,CAAC,EAAE,IAAI,KAAK,CAAC,IAAI,EAAE;gBACpC,WAAW,EAAE,OAAO,CAAC,WAAW;aACjC,CAAC,CAAA;YAEF,SAAS,CAAC,IAAI,CAAC,EAAE,GAAG,WAAW,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,CAAA;QAC5D,CAAC;QAED,OAAO,SAAS,CAAA;IAClB,CAAC;IAEM,QAAQ,CAAC,MAAkB;QAChC,OAAO,MAAM,CAAA;IACf,CAAC;CACF;AAED,MAAM,CAAC,KAAK,UAAU,KAAK,CACzB,MAAc,EACd,UAAwB,EAAE;IAE1B,OAAO,MAAM,OAAO,CAAC,UAAU,CAAC,kBAAkB,EAAE,MAAM,EAAE,OAAO,CAAC,CAAA;AACtE,CAAC"}
@@ -0,0 +1,19 @@
1
+ import { type Binary, SyncActivity } from '@t4h.framework/core';
2
+ import type { ZipEntryInfo } from '../typings/ZipEntry.js';
3
+ export type ZipListOptions = {
4
+ include?: string | readonly string[];
5
+ exclude?: string | readonly string[];
6
+ encoding?: BufferEncoding;
7
+ };
8
+ export type ZipListActivityInput = {
9
+ binary: Binary;
10
+ options: ZipListOptions;
11
+ };
12
+ export declare class ZipListActivity extends SyncActivity<readonly [binary: Binary, options: ZipListOptions], ZipListActivityInput, ZipEntryInfo[], ZipEntryInfo[]> {
13
+ private readonly claims;
14
+ toInput(binary: Binary, options: ZipListOptions): ZipListActivityInput;
15
+ run({ binary, options, }: ZipListActivityInput): Promise<ZipEntryInfo[]>;
16
+ toOutput(output: ZipEntryInfo[]): ZipEntryInfo[];
17
+ }
18
+ export declare function list(binary: Binary, options?: ZipListOptions): Promise<ZipEntryInfo[]>;
19
+ //# sourceMappingURL=ZipListActivity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ZipListActivity.d.ts","sourceRoot":"","sources":["../../src/activities/ZipListActivity.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,MAAM,EAAkB,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAM/E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAA;AAE1D,MAAM,MAAM,cAAc,GAAG;IAC3B,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAAA;IACpC,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAAA;IACpC,QAAQ,CAAC,EAAE,cAAc,CAAA;CAC1B,CAAA;AAED,MAAM,MAAM,oBAAoB,GAAG;IACjC,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,cAAc,CAAA;CACxB,CAAA;AAED,qBAAa,eAAgB,SAAQ,YAAY,CAC/C,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,cAAc,CAAC,EAClD,oBAAoB,EACpB,YAAY,EAAE,EACd,YAAY,EAAE,CACf;IACC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAErB;IAEK,OAAO,CACZ,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,cAAc,GACtB,oBAAoB;IAIV,GAAG,CAAC,EACf,MAAM,EACN,OAAO,GACR,EAAE,oBAAoB,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;IAS1C,QAAQ,CAAC,MAAM,EAAE,YAAY,EAAE,GAAG,YAAY,EAAE;CAGxD;AAED,wBAAsB,IAAI,CACxB,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,cAAmB,GAC3B,OAAO,CAAC,YAAY,EAAE,CAAC,CAEzB"}
@@ -0,0 +1,27 @@
1
+ import { Claim, History, SyncActivity } from '@t4h.framework/core';
2
+ import { FileSystemClaim } from '@t4h.framework/fs';
3
+ import { openArchive } from '../helpers/open-archive.js';
4
+ import { selectEntries } from '../tools/select-entries.js';
5
+ import { toEntryInfo } from '../tools/to-entry-info.js';
6
+ export class ZipListActivity extends SyncActivity {
7
+ claims = new Claim({
8
+ fs: FileSystemClaim,
9
+ });
10
+ toInput(binary, options) {
11
+ return { binary, options };
12
+ }
13
+ async run({ binary, options, }) {
14
+ const { entries } = await openArchive(this.claims.fs, binary, {
15
+ encoding: options.encoding ?? 'utf8',
16
+ durablePath: `zip/${this.id}.zip`,
17
+ });
18
+ return selectEntries(entries, options).map(toEntryInfo);
19
+ }
20
+ toOutput(output) {
21
+ return output;
22
+ }
23
+ }
24
+ export async function list(binary, options = {}) {
25
+ return await History.reconciler(ZipListActivity, binary, options);
26
+ }
27
+ //# sourceMappingURL=ZipListActivity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ZipListActivity.js","sourceRoot":"","sources":["../../src/activities/ZipListActivity.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAC/E,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AAEnD,OAAO,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAA;AACxD,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAA;AAC1D,OAAO,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAA;AAcvD,MAAM,OAAO,eAAgB,SAAQ,YAKpC;IACkB,MAAM,GAAG,IAAI,KAAK,CAAC;QAClC,EAAE,EAAE,eAAe;KACpB,CAAC,CAAA;IAEK,OAAO,CACZ,MAAc,EACd,OAAuB;QAEvB,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,CAAA;IAC5B,CAAC;IAEM,KAAK,CAAC,GAAG,CAAC,EACf,MAAM,EACN,OAAO,GACc;QACrB,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,WAAW,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE;YAC5D,QAAQ,EAAE,OAAO,CAAC,QAAQ,IAAI,MAAM;YACpC,WAAW,EAAE,OAAO,IAAI,CAAC,EAAE,MAAM;SAClC,CAAC,CAAA;QAEF,OAAO,aAAa,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,GAAG,CAAC,WAAW,CAAC,CAAA;IACzD,CAAC;IAEM,QAAQ,CAAC,MAAsB;QACpC,OAAO,MAAM,CAAA;IACf,CAAC;CACF;AAED,MAAM,CAAC,KAAK,UAAU,IAAI,CACxB,MAAc,EACd,UAA0B,EAAE;IAE5B,OAAO,MAAM,OAAO,CAAC,UAAU,CAAC,eAAe,EAAE,MAAM,EAAE,OAAO,CAAC,CAAA;AACnE,CAAC"}
@@ -0,0 +1,15 @@
1
+ import { Transform, type TransformCallback } from 'node:stream';
2
+ export type ZipChecksumExpectation = {
3
+ path: string;
4
+ crc32: number;
5
+ contentLength: number;
6
+ };
7
+ export declare class ZipChecksum extends Transform {
8
+ private readonly expected;
9
+ private checksum;
10
+ private length;
11
+ constructor(expected: ZipChecksumExpectation);
12
+ _transform(chunk: Buffer, _encoding: BufferEncoding, callback: TransformCallback): void;
13
+ _flush(callback: TransformCallback): void;
14
+ }
15
+ //# sourceMappingURL=ZipChecksum.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ZipChecksum.d.ts","sourceRoot":"","sources":["../../src/helpers/ZipChecksum.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,KAAK,iBAAiB,EAAE,MAAM,aAAa,CAAA;AAG/D,MAAM,MAAM,sBAAsB,GAAG;IACnC,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;IACb,aAAa,EAAE,MAAM,CAAA;CACtB,CAAA;AAED,qBAAa,WAAY,SAAQ,SAAS;IAK5B,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAJrC,OAAO,CAAC,QAAQ,CAAI;IAEpB,OAAO,CAAC,MAAM,CAAI;gBAEW,QAAQ,EAAE,sBAAsB;IAI7C,UAAU,CACxB,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,cAAc,EACzB,QAAQ,EAAE,iBAAiB,GAC1B,IAAI;IAgBS,MAAM,CAAC,QAAQ,EAAE,iBAAiB,GAAG,IAAI;CAiB1D"}
@@ -0,0 +1,28 @@
1
+ import { Transform } from 'node:stream';
2
+ import { crc32 } from 'node:zlib';
3
+ export class ZipChecksum extends Transform {
4
+ expected;
5
+ checksum = 0;
6
+ length = 0;
7
+ constructor(expected) {
8
+ super();
9
+ this.expected = expected;
10
+ }
11
+ _transform(chunk, _encoding, callback) {
12
+ const { path, contentLength } = this.expected;
13
+ if (this.length + chunk.length > contentLength)
14
+ return callback(new SyntaxError(`ZIP entry "${path}" unpacked past the ${contentLength} bytes the archive declares`));
15
+ this.checksum = crc32(chunk, this.checksum);
16
+ this.length += chunk.length;
17
+ callback(null, chunk);
18
+ }
19
+ _flush(callback) {
20
+ const { path, crc32: expectedChecksum, contentLength } = this.expected;
21
+ if (this.length !== contentLength)
22
+ return callback(new SyntaxError(`ZIP entry "${path}" unpacked to ${this.length} bytes, the archive declares ${contentLength}`));
23
+ if (this.checksum !== expectedChecksum)
24
+ return callback(new SyntaxError(`ZIP entry "${path}" failed its CRC-32 check`));
25
+ callback();
26
+ }
27
+ }
28
+ //# sourceMappingURL=ZipChecksum.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ZipChecksum.js","sourceRoot":"","sources":["../../src/helpers/ZipChecksum.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAA0B,MAAM,aAAa,CAAA;AAC/D,OAAO,EAAE,KAAK,EAAE,MAAM,WAAW,CAAA;AAQjC,MAAM,OAAO,WAAY,SAAQ,SAAS;IAKX;IAJrB,QAAQ,GAAG,CAAC,CAAA;IAEZ,MAAM,GAAG,CAAC,CAAA;IAElB,YAA6B,QAAgC;QAC3D,KAAK,EAAE,CAAA;QADoB,aAAQ,GAAR,QAAQ,CAAwB;IAE7D,CAAC;IAEe,UAAU,CACxB,KAAa,EACb,SAAyB,EACzB,QAA2B;QAE3B,MAAM,EAAE,IAAI,EAAE,aAAa,EAAE,GAAG,IAAI,CAAC,QAAQ,CAAA;QAE7C,IAAI,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM,GAAG,aAAa;YAC5C,OAAO,QAAQ,CACb,IAAI,WAAW,CACb,cAAc,IAAI,uBAAuB,aAAa,6BAA6B,CACpF,CACF,CAAA;QAEH,IAAI,CAAC,QAAQ,GAAG,KAAK,CAAC,KAAK,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAA;QAC3C,IAAI,CAAC,MAAM,IAAI,KAAK,CAAC,MAAM,CAAA;QAE3B,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC,CAAA;IACvB,CAAC;IAEe,MAAM,CAAC,QAA2B;QAChD,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,gBAAgB,EAAE,aAAa,EAAE,GAAG,IAAI,CAAC,QAAQ,CAAA;QAEtE,IAAI,IAAI,CAAC,MAAM,KAAK,aAAa;YAC/B,OAAO,QAAQ,CACb,IAAI,WAAW,CACb,cAAc,IAAI,iBAAiB,IAAI,CAAC,MAAM,gCAAgC,aAAa,EAAE,CAC9F,CACF,CAAA;QAEH,IAAI,IAAI,CAAC,QAAQ,KAAK,gBAAgB;YACpC,OAAO,QAAQ,CACb,IAAI,WAAW,CAAC,cAAc,IAAI,2BAA2B,CAAC,CAC/D,CAAA;QAEH,QAAQ,EAAE,CAAA;IACZ,CAAC;CACF"}
@@ -0,0 +1,17 @@
1
+ import type { Readable } from 'node:stream';
2
+ import type { Binary } from '@t4h.framework/core';
3
+ import type { FileSystemClaim } from '@t4h.framework/fs';
4
+ export declare class ZipSliceUnavailableError extends Error {
5
+ readonly reason: unknown;
6
+ constructor(reason: unknown);
7
+ }
8
+ export declare class ZipSource {
9
+ private readonly fs;
10
+ readonly binary: Binary;
11
+ constructor(fs: FileSystemClaim, binary: Binary);
12
+ get contentLength(): number;
13
+ read(start: number, end: number): Promise<Buffer>;
14
+ stream(start: number, end: number): Promise<Readable>;
15
+ private slice;
16
+ }
17
+ //# sourceMappingURL=ZipSource.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ZipSource.d.ts","sourceRoot":"","sources":["../../src/helpers/ZipSource.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AAG3C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;AACjD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AAExD,qBAAa,wBAAyB,SAAQ,KAAK;aACrB,MAAM,EAAE,OAAO;gBAAf,MAAM,EAAE,OAAO;CAK5C;AAED,qBAAa,SAAS;IAElB,OAAO,CAAC,QAAQ,CAAC,EAAE;aACH,MAAM,EAAE,MAAM;gBADb,EAAE,EAAE,eAAe,EACpB,MAAM,EAAE,MAAM;IAGhC,IAAW,aAAa,IAAI,MAAM,CAEjC;IAEY,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAMjD,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC;YAWpD,KAAK;CAOpB"}
@@ -0,0 +1,40 @@
1
+ import { buffer } from 'node:stream/consumers';
2
+ export class ZipSliceUnavailableError extends Error {
3
+ reason;
4
+ constructor(reason) {
5
+ super('The file system claim could not slice the archive');
6
+ this.reason = reason;
7
+ this.name = 'ZipSliceUnavailableError';
8
+ }
9
+ }
10
+ export class ZipSource {
11
+ fs;
12
+ binary;
13
+ constructor(fs, binary) {
14
+ this.fs = fs;
15
+ this.binary = binary;
16
+ }
17
+ get contentLength() {
18
+ return this.binary.contentLength;
19
+ }
20
+ async read(start, end) {
21
+ if (end <= start)
22
+ return Buffer.alloc(0);
23
+ return await buffer(await this.stream(start, end));
24
+ }
25
+ async stream(start, end) {
26
+ if (start < 0 || end > this.contentLength)
27
+ throw new SyntaxError(`ZIP archive is truncated: bytes ${start}-${end} are outside a file of ${this.contentLength} bytes`);
28
+ const slice = await this.slice(start, end);
29
+ return await slice.stream();
30
+ }
31
+ async slice(start, end) {
32
+ try {
33
+ return await this.fs.slice(this.binary, { start, end });
34
+ }
35
+ catch (error) {
36
+ throw new ZipSliceUnavailableError(error);
37
+ }
38
+ }
39
+ }
40
+ //# sourceMappingURL=ZipSource.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ZipSource.js","sourceRoot":"","sources":["../../src/helpers/ZipSource.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAA;AAK9C,MAAM,OAAO,wBAAyB,SAAQ,KAAK;IACrB;IAA5B,YAA4B,MAAe;QACzC,KAAK,CAAC,mDAAmD,CAAC,CAAA;QADhC,WAAM,GAAN,MAAM,CAAS;QAGzC,IAAI,CAAC,IAAI,GAAG,0BAA0B,CAAA;IACxC,CAAC;CACF;AAED,MAAM,OAAO,SAAS;IAED;IACD;IAFlB,YACmB,EAAmB,EACpB,MAAc;QADb,OAAE,GAAF,EAAE,CAAiB;QACpB,WAAM,GAAN,MAAM,CAAQ;IAC7B,CAAC;IAEJ,IAAW,aAAa;QACtB,OAAO,IAAI,CAAC,MAAM,CAAC,aAAa,CAAA;IAClC,CAAC;IAEM,KAAK,CAAC,IAAI,CAAC,KAAa,EAAE,GAAW;QAC1C,IAAI,GAAG,IAAI,KAAK;YAAE,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;QAExC,OAAO,MAAM,MAAM,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,CAAA;IACpD,CAAC;IAEM,KAAK,CAAC,MAAM,CAAC,KAAa,EAAE,GAAW;QAC5C,IAAI,KAAK,GAAG,CAAC,IAAI,GAAG,GAAG,IAAI,CAAC,aAAa;YACvC,MAAM,IAAI,WAAW,CACnB,mCAAmC,KAAK,IAAI,GAAG,0BAA0B,IAAI,CAAC,aAAa,QAAQ,CACpG,CAAA;QAEH,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,GAAG,CAAC,CAAA;QAE1C,OAAO,MAAM,KAAK,CAAC,MAAM,EAAE,CAAA;IAC7B,CAAC;IAEO,KAAK,CAAC,KAAK,CAAC,KAAa,EAAE,GAAW;QAC5C,IAAI,CAAC;YACH,OAAO,MAAM,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,CAAA;QACzD,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,IAAI,wBAAwB,CAAC,KAAK,CAAC,CAAA;QAC3C,CAAC;IACH,CAAC;CACF"}