@dxos/plugin-file 0.10.0 → 0.11.1

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 (174) hide show
  1. package/PLUGIN.mdl +86 -77
  2. package/dist/lib/FilePlugin.mjs +37 -0
  3. package/dist/lib/FilePlugin.mjs.map +1 -0
  4. package/dist/lib/FilePlugin.node.mjs +12 -0
  5. package/dist/lib/FilePlugin.node.mjs.map +1 -0
  6. package/dist/lib/capabilities/node.mjs +8 -0
  7. package/dist/lib/capabilities/node.mjs.map +1 -0
  8. package/dist/lib/capabilities.mjs +15 -0
  9. package/dist/lib/capabilities.mjs.map +1 -0
  10. package/dist/lib/chunk-FileArticle.mjs +62 -0
  11. package/dist/lib/chunk-FileArticle.mjs.map +1 -0
  12. package/dist/lib/chunk-create-object.mjs +28 -0
  13. package/dist/lib/chunk-create-object.mjs.map +1 -0
  14. package/dist/lib/chunk-create.mjs +78 -0
  15. package/dist/lib/chunk-create.mjs.map +1 -0
  16. package/dist/lib/chunk-edge-backend.mjs +18 -0
  17. package/dist/lib/chunk-edge-backend.mjs.map +1 -0
  18. package/dist/lib/chunk-file-uploader.mjs +37 -0
  19. package/dist/lib/chunk-file-uploader.mjs.map +1 -0
  20. package/dist/lib/chunk-inline-backend.mjs +19 -0
  21. package/dist/lib/chunk-inline-backend.mjs.map +1 -0
  22. package/dist/lib/chunk-markdown-extension.mjs +182 -0
  23. package/dist/lib/chunk-markdown-extension.mjs.map +1 -0
  24. package/dist/lib/chunk-meta.mjs +44 -0
  25. package/dist/lib/chunk-meta.mjs.map +1 -0
  26. package/dist/lib/chunk-operation-handler.mjs +11 -0
  27. package/dist/lib/chunk-operation-handler.mjs.map +1 -0
  28. package/dist/lib/chunk-react-surface.mjs +55 -0
  29. package/dist/lib/chunk-react-surface.mjs.map +1 -0
  30. package/dist/lib/chunk-read.mjs +32 -0
  31. package/dist/lib/chunk-read.mjs.map +1 -0
  32. package/dist/lib/chunk-settings.mjs +23 -0
  33. package/dist/lib/chunk-settings.mjs.map +1 -0
  34. package/dist/lib/chunk-skill-definition.mjs +10 -0
  35. package/dist/lib/chunk-skill-definition.mjs.map +1 -0
  36. package/dist/lib/chunk-skills.mjs +28 -0
  37. package/dist/lib/chunk-skills.mjs.map +1 -0
  38. package/dist/lib/chunk-types.mjs +98 -0
  39. package/dist/lib/chunk-types.mjs.map +1 -0
  40. package/dist/lib/components.mjs +57 -0
  41. package/dist/lib/components.mjs.map +1 -0
  42. package/dist/lib/containers.mjs +56 -0
  43. package/dist/lib/containers.mjs.map +1 -0
  44. package/dist/lib/index.mjs +4 -0
  45. package/dist/lib/meta.mjs +2 -0
  46. package/dist/lib/operations.mjs +7 -0
  47. package/dist/lib/operations.mjs.map +1 -0
  48. package/dist/lib/plugin.mjs +9 -0
  49. package/dist/lib/plugin.mjs.map +1 -0
  50. package/dist/lib/skills.mjs +2 -0
  51. package/dist/lib/translations.mjs +30 -0
  52. package/dist/lib/translations.mjs.map +1 -0
  53. package/dist/lib/types.mjs +2 -0
  54. package/dist/types/src/FilePlugin.d.ts.map +1 -1
  55. package/dist/types/src/FilePlugin.node.d.ts.map +1 -1
  56. package/dist/types/src/capabilities/FileUploadField.d.ts +12 -0
  57. package/dist/types/src/capabilities/FileUploadField.d.ts.map +1 -0
  58. package/dist/types/src/capabilities/create-object.d.ts +1 -1
  59. package/dist/types/src/capabilities/edge-backend.d.ts +5 -0
  60. package/dist/types/src/capabilities/edge-backend.d.ts.map +1 -0
  61. package/dist/types/src/capabilities/file-uploader.d.ts +1 -1
  62. package/dist/types/src/capabilities/file-uploader.d.ts.map +1 -1
  63. package/dist/types/src/capabilities/index.d.ts +1 -0
  64. package/dist/types/src/capabilities/index.d.ts.map +1 -1
  65. package/dist/types/src/capabilities/inline-backend.d.ts +2 -2
  66. package/dist/types/src/capabilities/inline-backend.d.ts.map +1 -1
  67. package/dist/types/src/capabilities/markdown-extension.d.ts +1 -1
  68. package/dist/types/src/capabilities/node.d.ts +5 -0
  69. package/dist/types/src/capabilities/node.d.ts.map +1 -0
  70. package/dist/types/src/capabilities/operation-handler.d.ts +1 -1
  71. package/dist/types/src/capabilities/react-surface.d.ts +1 -1
  72. package/dist/types/src/capabilities/react-surface.d.ts.map +1 -1
  73. package/dist/types/src/capabilities/settings.d.ts +1 -1
  74. package/dist/types/src/components/FileInput/FileInput.d.ts.map +1 -1
  75. package/dist/types/src/components/FilePreview/FilePreview.d.ts +1 -1
  76. package/dist/types/src/components/FilePreview/FilePreview.d.ts.map +1 -1
  77. package/dist/types/src/containers/FileArticle/FileArticle.d.ts +4 -1
  78. package/dist/types/src/containers/FileArticle/FileArticle.d.ts.map +1 -1
  79. package/dist/types/src/containers/FileSettings/FileSettings.d.ts +5 -3
  80. package/dist/types/src/containers/FileSettings/FileSettings.d.ts.map +1 -1
  81. package/dist/types/src/extensions/image.d.ts +0 -2
  82. package/dist/types/src/extensions/image.d.ts.map +1 -1
  83. package/dist/types/src/operations/create.d.ts +14 -5
  84. package/dist/types/src/operations/create.d.ts.map +1 -1
  85. package/dist/types/src/operations/read.d.ts.map +1 -1
  86. package/dist/types/src/types/FileCapabilities.d.ts +10 -28
  87. package/dist/types/src/types/FileCapabilities.d.ts.map +1 -1
  88. package/dist/types/src/types/Settings.d.ts +1 -1
  89. package/dist/types/src/types/Settings.d.ts.map +1 -1
  90. package/dist/types/src/types/limits.d.ts +0 -1
  91. package/dist/types/src/types/limits.d.ts.map +1 -1
  92. package/dist/types/tsconfig.tsbuildinfo +1 -1
  93. package/dx.config.ts +10 -6
  94. package/package.json +45 -41
  95. package/src/FilePlugin.node.ts +2 -1
  96. package/src/FilePlugin.tsx +8 -1
  97. package/src/capabilities/FileUploadField.tsx +37 -0
  98. package/src/capabilities/edge-backend.ts +27 -0
  99. package/src/capabilities/file-uploader.ts +6 -4
  100. package/src/capabilities/index.ts +1 -0
  101. package/src/capabilities/inline-backend.ts +4 -21
  102. package/src/capabilities/markdown-extension.ts +3 -8
  103. package/src/capabilities/node.ts +16 -0
  104. package/src/capabilities/react-surface.ts +46 -0
  105. package/src/components/FileInput/FileInput.tsx +3 -2
  106. package/src/containers/FileArticle/FileArticle.tsx +49 -42
  107. package/src/containers/FileSettings/FileSettings.tsx +19 -14
  108. package/src/extensions/image.tsx +49 -28
  109. package/src/operations/create.test.ts +122 -32
  110. package/src/operations/create.ts +44 -24
  111. package/src/operations/read.ts +24 -6
  112. package/src/types/FileCapabilities.ts +10 -31
  113. package/src/types/Settings.ts +1 -1
  114. package/src/types/limits.ts +0 -2
  115. package/dist/lib/neutral/FileArticle-YU6NN3KN.mjs +0 -80
  116. package/dist/lib/neutral/FileArticle-YU6NN3KN.mjs.map +0 -7
  117. package/dist/lib/neutral/FilePlugin.mjs +0 -58
  118. package/dist/lib/neutral/FilePlugin.mjs.map +0 -7
  119. package/dist/lib/neutral/FilePlugin.node.mjs +0 -23
  120. package/dist/lib/neutral/FilePlugin.node.mjs.map +0 -7
  121. package/dist/lib/neutral/capabilities/index.mjs +0 -23
  122. package/dist/lib/neutral/capabilities/index.mjs.map +0 -7
  123. package/dist/lib/neutral/chunk-DKGTSUUH.mjs +0 -8
  124. package/dist/lib/neutral/chunk-DKGTSUUH.mjs.map +0 -7
  125. package/dist/lib/neutral/chunk-GMYSEHV4.mjs +0 -40
  126. package/dist/lib/neutral/chunk-GMYSEHV4.mjs.map +0 -7
  127. package/dist/lib/neutral/chunk-J5LGTIGS.mjs +0 -10
  128. package/dist/lib/neutral/chunk-J5LGTIGS.mjs.map +0 -7
  129. package/dist/lib/neutral/chunk-LBZ4MCLI.mjs +0 -45
  130. package/dist/lib/neutral/chunk-LBZ4MCLI.mjs.map +0 -7
  131. package/dist/lib/neutral/chunk-YODPX62X.mjs +0 -136
  132. package/dist/lib/neutral/chunk-YODPX62X.mjs.map +0 -7
  133. package/dist/lib/neutral/chunk-ZUXUTRAJ.mjs +0 -80
  134. package/dist/lib/neutral/chunk-ZUXUTRAJ.mjs.map +0 -7
  135. package/dist/lib/neutral/components/index.mjs +0 -74
  136. package/dist/lib/neutral/components/index.mjs.map +0 -7
  137. package/dist/lib/neutral/containers/index.mjs +0 -59
  138. package/dist/lib/neutral/containers/index.mjs.map +0 -7
  139. package/dist/lib/neutral/create-IUUG7RI6.mjs +0 -17
  140. package/dist/lib/neutral/create-IUUG7RI6.mjs.map +0 -7
  141. package/dist/lib/neutral/create-object-V667G4ZL.mjs +0 -30
  142. package/dist/lib/neutral/create-object-V667G4ZL.mjs.map +0 -7
  143. package/dist/lib/neutral/file-uploader-BCJH6TUL.mjs +0 -41
  144. package/dist/lib/neutral/file-uploader-BCJH6TUL.mjs.map +0 -7
  145. package/dist/lib/neutral/index.mjs +0 -32
  146. package/dist/lib/neutral/index.mjs.map +0 -7
  147. package/dist/lib/neutral/inline-backend-FGGGDYRB.mjs +0 -37
  148. package/dist/lib/neutral/inline-backend-FGGGDYRB.mjs.map +0 -7
  149. package/dist/lib/neutral/markdown-extension-RA5YNMTW.mjs +0 -230
  150. package/dist/lib/neutral/markdown-extension-RA5YNMTW.mjs.map +0 -7
  151. package/dist/lib/neutral/meta.json +0 -1
  152. package/dist/lib/neutral/meta.mjs +0 -8
  153. package/dist/lib/neutral/meta.mjs.map +0 -7
  154. package/dist/lib/neutral/operation-handler-CPDANWGH.mjs +0 -13
  155. package/dist/lib/neutral/operation-handler-CPDANWGH.mjs.map +0 -7
  156. package/dist/lib/neutral/operations/index.mjs +0 -8
  157. package/dist/lib/neutral/operations/index.mjs.map +0 -7
  158. package/dist/lib/neutral/plugin.mjs +0 -16
  159. package/dist/lib/neutral/plugin.mjs.map +0 -7
  160. package/dist/lib/neutral/react-surface-5BXKSHD2.mjs +0 -60
  161. package/dist/lib/neutral/react-surface-5BXKSHD2.mjs.map +0 -7
  162. package/dist/lib/neutral/read-HG6CSWKR.mjs +0 -28
  163. package/dist/lib/neutral/read-HG6CSWKR.mjs.map +0 -7
  164. package/dist/lib/neutral/settings-RR3RESL2.mjs +0 -28
  165. package/dist/lib/neutral/settings-RR3RESL2.mjs.map +0 -7
  166. package/dist/lib/neutral/skill-definition-YHBGWBUS.mjs +0 -15
  167. package/dist/lib/neutral/skill-definition-YHBGWBUS.mjs.map +0 -7
  168. package/dist/lib/neutral/skills/index.mjs +0 -11
  169. package/dist/lib/neutral/skills/index.mjs.map +0 -7
  170. package/dist/lib/neutral/translations.mjs +0 -36
  171. package/dist/lib/neutral/translations.mjs.map +0 -7
  172. package/dist/lib/neutral/types/index.mjs +0 -22
  173. package/dist/lib/neutral/types/index.mjs.map +0 -7
  174. package/src/capabilities/react-surface.tsx +0 -61
package/PLUGIN.mdl CHANGED
@@ -1,14 +1,16 @@
1
1
  ---
2
2
  id: org.dxos.plugin.file
3
3
  name: FilePlugin
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  ---
6
6
 
7
7
  FilePlugin enables DXOS Composer to store and preview binary files — images, videos, and PDFs — as first-class ECHO objects.
8
- Uploaded bytes are routed through a pluggable backend system: the built-in `inline` backend serialises the file directly
9
- onto the ECHO document, while external backends (e.g. WNFS) can be contributed by other plugins.
10
- A URL-resolver capability lets external-storage backends convert opaque storage URIs into renderable blob, data, or HTTP URLs
11
- at view time, keeping the ECHO object schema backend-agnostic.
8
+ A File object's `data` field is a reference to a `Blob` ECHO object (defined in `@dxos/echo`), which abstracts where the
9
+ bytes actually live and how they're read. Uploads are routed through a pluggable storage system: the built-in `inline`
10
+ storage embeds bytes on the Blob object itself, while external storage (e.g. WNFS, edge) can be contributed by other
11
+ plugins by registering a `BlobBackend` on the ECHO Hypergraph. `Blob.url`/`Blob.read` resolve a renderable URL or raw
12
+ bytes uniformly regardless of backend, so the File schema and preview surfaces stay backend-agnostic without a
13
+ dedicated URL-resolver capability.
12
14
 
13
15
  ## Extensions
14
16
 
@@ -25,55 +27,33 @@ Each extension is defined in the Appendix or resolved via its URI.
25
27
 
26
28
  ## Types
27
29
 
28
- ```mdl
29
- type FileDataInline
30
- desc: File bytes stored directly on the ECHO object.
31
- fields:
32
- _tag: 'inline'
33
- bytes: Uint8Array
34
- ```
35
-
36
- ```mdl
37
- type FileDataExternal
38
- desc: File bytes stored outside ECHO; referenced by URL.
39
- fields:
40
- _tag: 'external'
41
- url: string
42
- cid?: string # optional content-addressed identifier
43
- ```
44
-
45
- ```mdl
46
- type FileData
47
- desc: Discriminated union of inline and external storage.
48
- literals: FileDataInline | FileDataExternal
49
- ```
50
-
51
30
  ```mdl
52
31
  type File
53
- desc: ECHO object (typename dxos.org.type.file) representing an uploaded binary asset.
32
+ desc: ECHO object (typename org.dxos.type.file, version 0.2.0) representing an uploaded binary asset.
54
33
  fields:
55
34
  name?: string
56
35
  type: string # MIME type
57
36
  size: number # byte count
58
- data: FileData
37
+ data: Ref<Blob> # reference to a Blob object (org.dxos.type.blob, defined in @dxos/echo)
59
38
  timestamp?: string # ISO 8601 upload time
60
39
  ```
61
40
 
62
41
  ```mdl
63
- type UploadResult
64
- desc: Normalised return value from a Backend.upload call.
42
+ type BackendDescriptor
43
+ desc: |
44
+ UI-facing descriptor for a registered storage option. Carries no upload/read behavior itself —
45
+ that lives in the matching `BlobBackend` registered on the ECHO Hypergraph under `storage`.
65
46
  fields:
66
- name: string
67
- type: string
68
- size: number
69
- data: FileData
47
+ name: string # display label shown in settings
48
+ description?: string # description shown next to the backend in settings
49
+ storage: string # Blob.fromBytes's `storage` option; also the settings key (e.g. 'inline', 'wnfs', 'edge')
70
50
  ```
71
51
 
72
52
  ```mdl
73
53
  type Settings
74
54
  desc: Per-user plugin settings stored in the ECHO space.
75
55
  fields:
76
- backend?: string # active backend id; defaults to 'inline'
56
+ backend?: string # active backend's storage name; defaults to 'inline'
77
57
  ```
78
58
 
79
59
  ## Components
@@ -86,7 +66,7 @@ component FilePreview
86
66
  all other types fall back to a download link.
87
67
  props:
88
68
  type: string # MIME type
89
- url: string # renderable blob:, data:, or http(s): URL
69
+ url: string # renderable data:, blob:, or http(s): URL
90
70
  layout: |
91
71
  ┌──────────────────────────────┐
92
72
  │ <img> / <video> / <iframe> │
@@ -118,7 +98,7 @@ component FileSettings
118
98
  ┌──────────────────────────────┐
119
99
  │ Storage backend: [select ▼] │
120
100
  │ (one row per registered │
121
- │ backend with description) │
101
+ │ BackendDescriptor) │
122
102
  └──────────────────────────────┘
123
103
  ```
124
104
 
@@ -126,9 +106,11 @@ component FileSettings
126
106
  component FileArticle
127
107
  desc: |
128
108
  Full-viewport article container rendered for article, section, and slide
129
- roles. Resolves a renderable URL from the File object via inline blob
130
- conversion or registered UrlResolver capabilities, then delegates to
131
- FilePreview. Shows an empty state while the URL is resolving.
109
+ roles. Loads the File's referenced Blob and resolves a renderable URL via
110
+ Blob.url; if the backend has no getUrl, falls back to Blob.read and
111
+ constructs a blob: object URL (revoked on unmount). Delegates to
112
+ FilePreview. Shows an empty state while the URL is resolving or if the
113
+ File is not attached to a database.
132
114
  props:
133
115
  role: AppSurface.Role
134
116
  subject: File
@@ -147,17 +129,20 @@ component FileArticle
147
129
  op Create
148
130
  key: org.dxos.plugin.file.operation.create
149
131
  desc: |
150
- Validates a browser File object (MIME type and size), routes the upload to the
151
- active Backend capability, and constructs a File ECHO object from the result.
152
- Does not add the object to the space — callers do that separately.
132
+ Validates a browser File object's MIME type, resolves the active
133
+ BackendDescriptor, and composes File.fromBytes (which hashes/uploads the
134
+ bytes via the Blob API and adds the resulting Blob to the database) to
135
+ construct a File ECHO object. Does not add the File object itself to the
136
+ space — callers do that separately.
153
137
  input:
154
138
  file: BrowserFile # browser File (globalThis.File)
155
139
  db: Database
156
140
  output: { object: File }
157
141
  errors:
158
142
  UnsupportedFileTypeError: MIME type is not image/*, video/*, or application/pdf
159
- FileTooLargeError: file exceeds the 4 MB inline limit
160
- NoBackendError: no Backend capability is registered
143
+ FileTooLargeError: file exceeds the 4 MB inline limit (only possible when the
144
+ resolved backend's storage is 'inline'; other backends scale beyond it)
145
+ NoBackendError: no BackendDescriptor capability is registered
161
146
  ```
162
147
 
163
148
  ## Features
@@ -168,47 +153,57 @@ feat F-1: File upload
168
153
  req F-1.1: Accepted MIME types MUST be image/* (jpg, jpeg, png, gif, webp, svg),
169
154
  video/* (mp4, webm, mov), and application/pdf only.
170
155
 
171
- req F-1.2: Maximum upload size MUST be 4 MB for the inline backend.
156
+ req F-1.2: Maximum upload size MUST be 4 MB when the resolved backend's storage
157
+ is 'inline'. Other backends (e.g. edge, wnfs) are not subject to this cap.
172
158
 
173
159
  req F-1.3:
174
160
  when: user submits the Create Object form with a valid file
175
161
  then:
176
162
  - op:Create is invoked with the selected file and active database
177
163
  - the resulting File object is added to the space via SpaceOperation.AddObject
178
- - FileUploader capability returns metadata to the caller (name, type, url/cid)
164
+ - FileUploader capability returns metadata to the caller (name, type, and a
165
+ renderable url resolved via Blob.url, if one is available)
179
166
 
180
167
  req F-1.4:
181
168
  when: file MIME type is not accepted
182
169
  then: UnsupportedFileTypeError returned; no ECHO write occurs
183
170
 
184
171
  req F-1.5:
185
- when: file size exceeds the limit
172
+ when: file size exceeds the limit and the resolved backend's storage is 'inline'
186
173
  then: FileTooLargeError returned; no ECHO write occurs
187
174
  ```
188
175
 
189
176
  ```mdl
190
177
  feat F-2: Backend capability system
191
178
 
192
- req F-2.1: Any plugin may contribute a Backend capability. Multiple backends
193
- may be registered simultaneously.
179
+ req F-2.1: Any plugin may contribute a BackendDescriptor capability. Multiple
180
+ descriptors may be registered simultaneously.
194
181
 
195
- req F-2.2: The inline backend MUST always be registered by FilePlugin itself.
196
- It serialises bytes directly onto the ECHO object.
182
+ req F-2.2: The inline BackendDescriptor MUST always be registered by FilePlugin
183
+ itself, mapping to Blob's built-in 'inline' storage (bytes embedded on the
184
+ Blob object; no separate BlobBackend registration needed).
197
185
 
198
186
  req F-2.3: The active backend is determined by Settings.backend. If the
199
187
  configured id is absent or unrecognised, the inline backend is used.
200
188
 
201
- req F-2.4: A UrlResolver capability allows external backends to convert an
202
- opaque storage URI (e.g. wnfs://…) into a renderable URL at view time.
203
- FileArticle iterates registered resolvers and uses the first that matches.
189
+ req F-2.4: A BackendDescriptor's `storage` names the Blob storage used by
190
+ File.fromBytes; the actual upload/read/getUrl implementation lives in a
191
+ separate `BlobBackend` registered on the ECHO Hypergraph (via
192
+ `client.graph.registerBlobBackend`), typically by the same plugin that
193
+ contributes the descriptor (e.g. plugin-wnfs, or `@dxos/client` itself for
194
+ 'edge'). FilePlugin's descriptor and the Hypergraph's BlobBackend registry
195
+ are independent — a descriptor with no matching registered backend fails at
196
+ upload time with a `backend-not-registered` error, not at settings-selection time.
204
197
  ```
205
198
 
206
199
  ```mdl
207
200
  feat F-3: File preview
208
201
 
209
- req F-3.1: FileArticle MUST resolve a renderable URL before rendering
210
- FilePreview. For inline files a blob: URL is created from the stored bytes
211
- and revoked on unmount.
202
+ req F-3.1: FileArticle MUST resolve a renderable URL via Blob.url before
203
+ rendering FilePreview. For inline files this is always a data: URL
204
+ (constructed synchronously from the stored bytes, no Blob.read needed). For
205
+ external files with no registered getUrl, FileArticle falls back to
206
+ Blob.read and constructs a blob: URL, revoked on unmount.
212
207
 
213
208
  req F-3.2: FilePreview MUST render the appropriate element for each MIME class:
214
209
  image/* → <img>, video/* → <video controls>, application/pdf → <iframe>,
@@ -222,14 +217,16 @@ feat F-3: File preview
222
217
  feat F-4: Markdown integration
223
218
 
224
219
  req F-4.1: A Markdown extension contributed at MarkdownEvents.SetupExtensions
225
- MUST allow files to be embedded in Markdown documents as inline attachments.
220
+ MUST allow files to be embedded in Markdown documents as inline attachments,
221
+ resolving each embedded File's Blob URL the same way as FileArticle (Blob.url,
222
+ falling back to Blob.read + blob: URL).
226
223
  ```
227
224
 
228
225
  ```mdl
229
226
  feat F-5: Settings
230
227
 
231
- req F-5.1: The Settings panel MUST list all registered backends with their
232
- id, display name, and optional description.
228
+ req F-5.1: The Settings panel MUST list all registered BackendDescriptor
229
+ capabilities with their id, display name, and optional description.
233
230
 
234
231
  req F-5.2: Selecting a backend MUST persist the choice to Settings.backend
235
232
  via the SettingsAtom capability.
@@ -239,17 +236,17 @@ feat F-5: Settings
239
236
 
240
237
  ```mdl
241
238
  test T-1: Upload image inline
242
- given: only the inline backend is registered
239
+ given: only the inline BackendDescriptor is registered
243
240
  when: user uploads a 1 MB PNG via the Create Object form
244
241
  then:
245
- - op:Create returns a File with data._tag === 'inline'
242
+ - op:Create returns a File whose data Ref resolves to a Blob with data._tag === 'inline'
246
243
  - File.type === 'image/png', File.size === byte count
247
244
  - File is added to the space
248
245
  ```
249
246
 
250
247
  ```mdl
251
248
  test T-2: Reject oversized file
252
- given: inline backend registered, 4 MB limit
249
+ given: inline backend selected, 4 MB limit
253
250
  when: user uploads a 5 MB JPEG
254
251
  then:
255
252
  - FileTooLargeError is returned
@@ -268,41 +265,53 @@ test T-3: Reject unsupported MIME type
268
265
 
269
266
  ```mdl
270
267
  test T-4: Preview inline image
271
- given: a File with data._tag === 'inline' and type === 'image/png'
268
+ given: a File whose data Ref resolves to a Blob with data._tag === 'inline' and type === 'image/png'
272
269
  when: FileArticle is rendered
273
270
  then:
274
- - a blob: URL is created from File.data.bytes
271
+ - Blob.url resolves a data: URL synchronously from the Blob's bytes (no Blob.read call)
275
272
  - FilePreview renders an <img> element with that URL
276
- - the blob: URL is revoked on unmount
277
273
  ```
278
274
 
279
275
  ```mdl
280
276
  test T-5: Preview PDF
281
- given: a File with type === 'application/pdf' and a resolved HTTP URL
277
+ given: a File with type === 'application/pdf' and a Blob resolved to an HTTP or data URL
282
278
  when: FilePreview is rendered
283
279
  then: an <iframe> element is rendered with the file URL as src
284
280
  ```
285
281
 
286
282
  ```mdl
287
283
  test T-6: Backend fallback to inline
288
- given: Settings.backend is 'wnfs' but no WNFS backend is registered
284
+ given: Settings.backend is 'wnfs' but no BackendDescriptor with storage 'wnfs' is registered
289
285
  when: op:Create is invoked
290
286
  then: the inline backend is used and the file is stored inline
291
287
  ```
292
288
 
293
289
  ```mdl
294
- test T-7: External URL resolved by UrlResolver
295
- given: a File with data._tag === 'external' and data.url === 'wnfs://cid/file.jpg'
296
- and: a UrlResolver capability registered that handles 'wnfs://' URIs
290
+ test T-7: External blob resolved via a registered BlobBackend's getUrl
291
+ given: a File whose data Ref resolves to a Blob with data._tag === 'external' and a
292
+ BlobBackend registered on the ECHO Hypergraph that resolves the Blob's URI scheme
293
+ and implements getUrl
297
294
  when: FileArticle is rendered
298
295
  then:
299
- - UrlResolver.resolve is called with the wnfs:// URL
296
+ - Blob.url calls the registered backend's getUrl and resolves Some(url)
300
297
  - FilePreview receives the resolved renderable URL
298
+ - Blob.read is never called
299
+ ```
300
+
301
+ ```mdl
302
+ test T-8: External blob without getUrl falls back to reading bytes
303
+ given: a File whose data Ref resolves to an external Blob whose registered
304
+ BlobBackend implements get but not getUrl
305
+ when: FileArticle is rendered
306
+ then:
307
+ - Blob.url resolves None
308
+ - Blob.read is called to fetch the bytes
309
+ - a blob: object URL is created from the bytes and revoked on unmount
301
310
  ```
302
311
 
303
312
  ```mdl
304
- test T-8: No backend error
305
- given: no Backend capability is registered
313
+ test T-9: No backend error
314
+ given: no BackendDescriptor capability is registered
306
315
  when: op:Create is invoked
307
316
  then: NoBackendError is returned
308
317
  ```
@@ -0,0 +1,37 @@
1
+ import { meta } from "#meta";
2
+ import { Plugin } from "@dxos/app-framework";
3
+ import { Blob } from "@dxos/echo";
4
+ import { AppActivationEvents, AppPlugin } from "@dxos/app-toolkit";
5
+ import { ClientEvents } from "@dxos/plugin-client";
6
+ import { MarkdownEvents } from "@dxos/plugin-markdown";
7
+ import { CreateObject, EdgeBackend, FileUploader, InlineBackend, Markdown, OperationHandler, ReactSurface, Settings, SkillDefinition } from "#capabilities";
8
+ import { translations } from "#translations";
9
+ import { File } from "#types";
10
+ //#endregion
11
+ //#region src/FilePlugin.tsx
12
+ var FilePlugin = Plugin.define(meta).pipe(AppPlugin.addSkillDefinitionModule({ activate: SkillDefinition }), AppPlugin.addCreateObjectModule({ activate: CreateObject }), AppPlugin.addOperationHandlerModule({ activate: OperationHandler }), AppPlugin.addSchemaModule({ schema: [File.File, Blob.Blob] }), AppPlugin.addSettingsModule({ activate: Settings }), AppPlugin.addSurfaceModule({ activate: ReactSurface }), AppPlugin.addTranslationsModule({ translations }), Plugin.addModule({
13
+ id: "inline-backend",
14
+ activatesOn: AppActivationEvents.SetupSchema,
15
+ activate: InlineBackend
16
+ }), Plugin.addModule({
17
+ id: "file-uploader",
18
+ activatesOn: ClientEvents.ClientReady,
19
+ activate: FileUploader
20
+ }), Plugin.addModule({
21
+ id: "edge-backend",
22
+ activatesOn: ClientEvents.ClientReady,
23
+ activate: EdgeBackend
24
+ }), Plugin.addModule({
25
+ id: "markdown",
26
+ activatesOn: MarkdownEvents.SetupExtensions,
27
+ activate: Markdown
28
+ }), AppPlugin.addPluginAssetModule({ asset: {
29
+ pluginId: meta.profile.key,
30
+ path: "PLUGIN.mdl",
31
+ content: "---\nid: org.dxos.plugin.file\nname: FilePlugin\nversion: 0.2.0\n---\n\nFilePlugin enables DXOS Composer to store and preview binary files — images, videos, and PDFs — as first-class ECHO objects.\nA File object's `data` field is a reference to a `Blob` ECHO object (defined in `@dxos/echo`), which abstracts where the\nbytes actually live and how they're read. Uploads are routed through a pluggable storage system: the built-in `inline`\nstorage embeds bytes on the Blob object itself, while external storage (e.g. WNFS, edge) can be contributed by other\nplugins by registering a `BlobBackend` on the ECHO Hypergraph. `Blob.url`/`Blob.read` resolve a renderable URL or raw\nbytes uniformly regardless of backend, so the File schema and preview surfaces stay backend-agnostic without a\ndedicated URL-resolver capability.\n\n## Extensions\n\nThe following extension dialects are used in this document.\nEach extension is defined in the Appendix or resolved via its URI.\n\n| Term | URI |\n|-------------|--------------------------------|\n| `type` | `org.dxos.mdl.type@1.0` |\n| `feat` | `org.dxos.mdl.feat@1.0` |\n| `test` | `org.dxos.mdl.test@1.0` |\n| `component` | `org.dxos.mdl.component@1.0` |\n| `op` | `org.dxos.mdl.op@1.0` |\n\n## Types\n\n```mdl\ntype File\n desc: ECHO object (typename org.dxos.type.file, version 0.2.0) representing an uploaded binary asset.\n fields:\n name?: string\n type: string # MIME type\n size: number # byte count\n data: Ref<Blob> # reference to a Blob object (org.dxos.type.blob, defined in @dxos/echo)\n timestamp?: string # ISO 8601 upload time\n```\n\n```mdl\ntype BackendDescriptor\n desc: |\n UI-facing descriptor for a registered storage option. Carries no upload/read behavior itself —\n that lives in the matching `BlobBackend` registered on the ECHO Hypergraph under `storage`.\n fields:\n name: string # display label shown in settings\n description?: string # description shown next to the backend in settings\n storage: string # Blob.fromBytes's `storage` option; also the settings key (e.g. 'inline', 'wnfs', 'edge')\n```\n\n```mdl\ntype Settings\n desc: Per-user plugin settings stored in the ECHO space.\n fields:\n backend?: string # active backend's storage name; defaults to 'inline'\n```\n\n## Components\n\n```mdl\ncomponent FilePreview\n desc: |\n Renders a File object using the appropriate HTML element for its MIME type.\n Images use <img>, videos use <video controls>, PDFs use <iframe>, and\n all other types fall back to a download link.\n props:\n type: string # MIME type\n url: string # renderable data:, blob:, or http(s): URL\n layout: |\n ┌──────────────────────────────┐\n │ <img> / <video> / <iframe> │\n │ or [Download file] link │\n └──────────────────────────────┘\n```\n\n```mdl\ncomponent FileInput\n desc: Drag-and-drop / browse file picker used inside the Create Object form.\n props:\n schema: Schema # effect/Schema with UploadAnnotation on the file field\n emits:\n onChange(file: File)\n layout: |\n ┌──────────────────────────────┐\n │ [ Drop file or click here ] │\n └──────────────────────────────┘\n```\n\n```mdl\ncomponent FileSettings\n desc: Plugin settings panel rendered in the Composer settings surface.\n props:\n settings: Settings\n emits:\n onSettingsChange(settings: Settings)\n layout: |\n ┌──────────────────────────────┐\n │ Storage backend: [select ▼] │\n │ (one row per registered │\n │ BackendDescriptor) │\n └──────────────────────────────┘\n```\n\n```mdl\ncomponent FileArticle\n desc: |\n Full-viewport article container rendered for article, section, and slide\n roles. Loads the File's referenced Blob and resolves a renderable URL via\n Blob.url; if the backend has no getUrl, falls back to Blob.read and\n constructs a blob: object URL (revoked on unmount). Delegates to\n FilePreview. Shows an empty state while the URL is resolving or if the\n File is not attached to a database.\n props:\n role: AppSurface.Role\n subject: File\n layout: |\n ┌──────────────────────────────┐\n │ │\n │ FilePreview │\n │ (fills container) │\n │ │\n └──────────────────────────────┘\n```\n\n## Operations\n\n```mdl\nop Create\n key: org.dxos.plugin.file.operation.create\n desc: |\n Validates a browser File object's MIME type, resolves the active\n BackendDescriptor, and composes File.fromBytes (which hashes/uploads the\n bytes via the Blob API and adds the resulting Blob to the database) to\n construct a File ECHO object. Does not add the File object itself to the\n space — callers do that separately.\n input:\n file: BrowserFile # browser File (globalThis.File)\n db: Database\n output: { object: File }\n errors:\n UnsupportedFileTypeError: MIME type is not image/*, video/*, or application/pdf\n FileTooLargeError: file exceeds the 4 MB inline limit (only possible when the\n resolved backend's storage is 'inline'; other backends scale beyond it)\n NoBackendError: no BackendDescriptor capability is registered\n```\n\n## Features\n\n```mdl\nfeat F-1: File upload\n\n req F-1.1: Accepted MIME types MUST be image/* (jpg, jpeg, png, gif, webp, svg),\n video/* (mp4, webm, mov), and application/pdf only.\n\n req F-1.2: Maximum upload size MUST be 4 MB when the resolved backend's storage\n is 'inline'. Other backends (e.g. edge, wnfs) are not subject to this cap.\n\n req F-1.3:\n when: user submits the Create Object form with a valid file\n then:\n - op:Create is invoked with the selected file and active database\n - the resulting File object is added to the space via SpaceOperation.AddObject\n - FileUploader capability returns metadata to the caller (name, type, and a\n renderable url resolved via Blob.url, if one is available)\n\n req F-1.4:\n when: file MIME type is not accepted\n then: UnsupportedFileTypeError returned; no ECHO write occurs\n\n req F-1.5:\n when: file size exceeds the limit and the resolved backend's storage is 'inline'\n then: FileTooLargeError returned; no ECHO write occurs\n```\n\n```mdl\nfeat F-2: Backend capability system\n\n req F-2.1: Any plugin may contribute a BackendDescriptor capability. Multiple\n descriptors may be registered simultaneously.\n\n req F-2.2: The inline BackendDescriptor MUST always be registered by FilePlugin\n itself, mapping to Blob's built-in 'inline' storage (bytes embedded on the\n Blob object; no separate BlobBackend registration needed).\n\n req F-2.3: The active backend is determined by Settings.backend. If the\n configured id is absent or unrecognised, the inline backend is used.\n\n req F-2.4: A BackendDescriptor's `storage` names the Blob storage used by\n File.fromBytes; the actual upload/read/getUrl implementation lives in a\n separate `BlobBackend` registered on the ECHO Hypergraph (via\n `client.graph.registerBlobBackend`), typically by the same plugin that\n contributes the descriptor (e.g. plugin-wnfs, or `@dxos/client` itself for\n 'edge'). FilePlugin's descriptor and the Hypergraph's BlobBackend registry\n are independent — a descriptor with no matching registered backend fails at\n upload time with a `backend-not-registered` error, not at settings-selection time.\n```\n\n```mdl\nfeat F-3: File preview\n\n req F-3.1: FileArticle MUST resolve a renderable URL via Blob.url before\n rendering FilePreview. For inline files this is always a data: URL\n (constructed synchronously from the stored bytes, no Blob.read needed). For\n external files with no registered getUrl, FileArticle falls back to\n Blob.read and constructs a blob: URL, revoked on unmount.\n\n req F-3.2: FilePreview MUST render the appropriate element for each MIME class:\n image/* → <img>, video/* → <video controls>, application/pdf → <iframe>,\n other → <a download>.\n\n req F-3.3: FileArticle surfaces are registered for article, section,\n and slide roles so files embed correctly in Markdown and Presenter layouts.\n```\n\n```mdl\nfeat F-4: Markdown integration\n\n req F-4.1: A Markdown extension contributed at MarkdownEvents.SetupExtensions\n MUST allow files to be embedded in Markdown documents as inline attachments,\n resolving each embedded File's Blob URL the same way as FileArticle (Blob.url,\n falling back to Blob.read + blob: URL).\n```\n\n```mdl\nfeat F-5: Settings\n\n req F-5.1: The Settings panel MUST list all registered BackendDescriptor\n capabilities with their id, display name, and optional description.\n\n req F-5.2: Selecting a backend MUST persist the choice to Settings.backend\n via the SettingsAtom capability.\n```\n\n## Acceptance\n\n```mdl\ntest T-1: Upload image inline\n given: only the inline BackendDescriptor is registered\n when: user uploads a 1 MB PNG via the Create Object form\n then:\n - op:Create returns a File whose data Ref resolves to a Blob with data._tag === 'inline'\n - File.type === 'image/png', File.size === byte count\n - File is added to the space\n```\n\n```mdl\ntest T-2: Reject oversized file\n given: inline backend selected, 4 MB limit\n when: user uploads a 5 MB JPEG\n then:\n - FileTooLargeError is returned\n - no File object is created\n - no ECHO write occurs\n```\n\n```mdl\ntest T-3: Reject unsupported MIME type\n given: inline backend registered\n when: user uploads a .zip file\n then:\n - UnsupportedFileTypeError is returned\n - no File object is created\n```\n\n```mdl\ntest T-4: Preview inline image\n given: a File whose data Ref resolves to a Blob with data._tag === 'inline' and type === 'image/png'\n when: FileArticle is rendered\n then:\n - Blob.url resolves a data: URL synchronously from the Blob's bytes (no Blob.read call)\n - FilePreview renders an <img> element with that URL\n```\n\n```mdl\ntest T-5: Preview PDF\n given: a File with type === 'application/pdf' and a Blob resolved to an HTTP or data URL\n when: FilePreview is rendered\n then: an <iframe> element is rendered with the file URL as src\n```\n\n```mdl\ntest T-6: Backend fallback to inline\n given: Settings.backend is 'wnfs' but no BackendDescriptor with storage 'wnfs' is registered\n when: op:Create is invoked\n then: the inline backend is used and the file is stored inline\n```\n\n```mdl\ntest T-7: External blob resolved via a registered BlobBackend's getUrl\n given: a File whose data Ref resolves to a Blob with data._tag === 'external' and a\n BlobBackend registered on the ECHO Hypergraph that resolves the Blob's URI scheme\n and implements getUrl\n when: FileArticle is rendered\n then:\n - Blob.url calls the registered backend's getUrl and resolves Some(url)\n - FilePreview receives the resolved renderable URL\n - Blob.read is never called\n```\n\n```mdl\ntest T-8: External blob without getUrl falls back to reading bytes\n given: a File whose data Ref resolves to an external Blob whose registered\n BlobBackend implements get but not getUrl\n when: FileArticle is rendered\n then:\n - Blob.url resolves None\n - Blob.read is called to fetch the bytes\n - a blob: object URL is created from the bytes and revoked on unmount\n```\n\n```mdl\ntest T-9: No backend error\n given: no BackendDescriptor capability is registered\n when: op:Create is invoked\n then: NoBackendError is returned\n```\n\n---\n\n## Appendix: Extension Definitions\n\n```mdl\next type\n uri: org.dxos.mdl.type@1.0\n desc: A named data structure with typed fields and optional literals.\n fields:\n desc?: Prose\n fields?: FieldMap\n literals?: UnionList\n extends?: TypeRef[]\n```\n\n```mdl\next feat\n uri: org.dxos.mdl.feat@1.0\n desc: A named feature grouping one or more requirements.\n fields:\n desc?: Prose\n req: RequirementList\n nesting: self\n```\n\n```mdl\next test\n uri: org.dxos.mdl.test@1.0\n desc: An acceptance scenario expressed as given / when / then steps.\n fields:\n given?: Step | Step[]\n when?: Step | Step[]\n then: Step | Step[]\n tags?: TagList\n```\n\n```mdl\next component\n uri: org.dxos.mdl.component@1.0\n desc: A UI component with props, internal state, slots, actions, and events.\n fields:\n desc?: Prose\n props?: FieldMap\n state?: FieldMap\n slots?: FieldMap\n actions?: ActionMap\n emits?: EventMap\n layout?: CodeBlock\n```\n\n```mdl\next op\n uri: org.dxos.mdl.op@1.0\n desc: A named operation with typed inputs, outputs, and declared errors.\n fields:\n desc?: Prose\n input?: FieldMap\n output?: TypeExpr\n errors?: ErrorMap\n effects?: EffectList\n requires?: ServiceList\n note?: Prose\n```\n",
32
+ mimeType: "application/x-mdl"
33
+ } }), Plugin.make);
34
+ //#endregion
35
+ export { FilePlugin, FilePlugin as default };
36
+
37
+ //# sourceMappingURL=FilePlugin.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FilePlugin.mjs","names":[],"sources":["../../src/FilePlugin.tsx"],"sourcesContent":["//\n// Copyright 2026 DXOS.org\n//\n\nimport { Plugin } from '@dxos/app-framework';\nimport { AppActivationEvents, AppPlugin } from '@dxos/app-toolkit';\nimport { Blob } from '@dxos/echo';\nimport { ClientEvents } from '@dxos/plugin-client';\nimport { MarkdownEvents } from '@dxos/plugin-markdown';\n\nimport {\n CreateObject,\n EdgeBackend,\n FileUploader,\n InlineBackend,\n Markdown,\n OperationHandler,\n ReactSurface,\n Settings,\n SkillDefinition,\n} from '#capabilities';\nimport { meta } from '#meta';\nimport { translations } from '#translations';\nimport { File } from '#types';\n\n// eslint-disable-next-line import/no-relative-packages\nimport pluginSpec from '../PLUGIN.mdl?raw';\n\nexport const FilePlugin = Plugin.define(meta).pipe(\n AppPlugin.addSkillDefinitionModule({ activate: SkillDefinition }),\n AppPlugin.addCreateObjectModule({ activate: CreateObject }),\n AppPlugin.addOperationHandlerModule({ activate: OperationHandler }),\n AppPlugin.addSchemaModule({ schema: [File.File, Blob.Blob] }),\n AppPlugin.addSettingsModule({ activate: Settings }),\n AppPlugin.addSurfaceModule({ activate: ReactSurface }),\n AppPlugin.addTranslationsModule({ translations }),\n Plugin.addModule({\n id: 'inline-backend',\n activatesOn: AppActivationEvents.SetupSchema,\n activate: InlineBackend,\n }),\n Plugin.addModule({\n id: 'file-uploader',\n activatesOn: ClientEvents.ClientReady,\n activate: FileUploader,\n }),\n Plugin.addModule({\n id: 'edge-backend',\n activatesOn: ClientEvents.ClientReady,\n activate: EdgeBackend,\n }),\n Plugin.addModule({\n id: 'markdown',\n activatesOn: MarkdownEvents.SetupExtensions,\n activate: Markdown,\n }),\n AppPlugin.addPluginAssetModule({\n asset: { pluginId: meta.profile.key, path: 'PLUGIN.mdl', content: pluginSpec, mimeType: 'application/x-mdl' },\n }),\n Plugin.make,\n);\n\nexport default FilePlugin;\n"],"mappings":";;;;;;;;;;;AA4BA,IAAa,aAAa,OAAO,OAAO,IAAI,CAAC,CAAC,KAC5C,UAAU,yBAAyB,EAAE,UAAU,gBAAgB,CAAC,GAChE,UAAU,sBAAsB,EAAE,UAAU,aAAa,CAAC,GAC1D,UAAU,0BAA0B,EAAE,UAAU,iBAAiB,CAAC,GAClE,UAAU,gBAAgB,EAAE,QAAQ,CAAC,KAAK,MAAM,KAAK,IAAI,EAAE,CAAC,GAC5D,UAAU,kBAAkB,EAAE,UAAU,SAAS,CAAC,GAClD,UAAU,iBAAiB,EAAE,UAAU,aAAa,CAAC,GACrD,UAAU,sBAAsB,EAAE,aAAa,CAAC,GAChD,OAAO,UAAU;CACf,IAAI;CACJ,aAAa,oBAAoB;CACjC,UAAU;AACZ,CAAC,GACD,OAAO,UAAU;CACf,IAAI;CACJ,aAAa,aAAa;CAC1B,UAAU;AACZ,CAAC,GACD,OAAO,UAAU;CACf,IAAI;CACJ,aAAa,aAAa;CAC1B,UAAU;AACZ,CAAC,GACD,OAAO,UAAU;CACf,IAAI;CACJ,aAAa,eAAe;CAC5B,UAAU;AACZ,CAAC,GACD,UAAU,qBAAqB,EAC7B,OAAO;CAAE,UAAU,KAAK,QAAQ;CAAK,MAAM;CAAc,SAAS;CAAY,UAAU;AAAoB,EAC9G,CAAC,GACD,OAAO,IACT"}
@@ -0,0 +1,12 @@
1
+ import { meta } from "#meta";
2
+ import { Plugin } from "@dxos/app-framework";
3
+ import { Blob } from "@dxos/echo";
4
+ import { AppPlugin } from "@dxos/app-toolkit";
5
+ import { CreateObject, OperationHandler } from "#capabilities";
6
+ import { File } from "#types";
7
+ //#region src/FilePlugin.node.ts
8
+ var FilePlugin = Plugin.define(meta).pipe(AppPlugin.addCreateObjectModule({ activate: CreateObject }), AppPlugin.addOperationHandlerModule({ activate: OperationHandler }), AppPlugin.addSchemaModule({ schema: [File.File, Blob.Blob] }), Plugin.make);
9
+ //#endregion
10
+ export { FilePlugin, FilePlugin as default };
11
+
12
+ //# sourceMappingURL=FilePlugin.node.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FilePlugin.node.mjs","names":[],"sources":["../../src/FilePlugin.node.ts"],"sourcesContent":["//\n// Copyright 2026 DXOS.org\n//\n\nimport { Plugin } from '@dxos/app-framework';\nimport { AppPlugin } from '@dxos/app-toolkit';\nimport { Blob } from '@dxos/echo';\n\nimport { CreateObject, OperationHandler } from '#capabilities';\nimport { meta } from '#meta';\nimport { File } from '#types';\n\nexport const FilePlugin = Plugin.define(meta).pipe(\n AppPlugin.addCreateObjectModule({ activate: CreateObject }),\n AppPlugin.addOperationHandlerModule({ activate: OperationHandler }),\n AppPlugin.addSchemaModule({ schema: [File.File, Blob.Blob] }),\n Plugin.make,\n);\n\nexport default FilePlugin;\n"],"mappings":";;;;;;;AAYA,IAAa,aAAa,OAAO,OAAO,IAAI,CAAC,CAAC,KAC5C,UAAU,sBAAsB,EAAE,UAAU,aAAa,CAAC,GAC1D,UAAU,0BAA0B,EAAE,UAAU,iBAAiB,CAAC,GAClE,UAAU,gBAAgB,EAAE,QAAQ,CAAC,KAAK,MAAM,KAAK,IAAI,EAAE,CAAC,GAC5D,OAAO,IACT"}
@@ -0,0 +1,8 @@
1
+ import { Capability } from "@dxos/app-framework";
2
+ //#region src/capabilities/node.ts
3
+ var CreateObject = Capability.lazy("CreateObject", () => import("../chunk-create-object.mjs"));
4
+ var OperationHandler = Capability.lazy("OperationHandler", () => import("../chunk-operation-handler.mjs"));
5
+ //#endregion
6
+ export { CreateObject, OperationHandler };
7
+
8
+ //# sourceMappingURL=node.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"node.mjs","names":[],"sources":["../../../src/capabilities/node.ts"],"sourcesContent":["//\n// Copyright 2026 DXOS.org\n//\n\nimport { Capability } from '@dxos/app-framework';\nimport { OperationHandlerSet } from '@dxos/compute';\n\n// The capabilities `FilePlugin.node` activates, and only those. `Capability.lazy` defers the\n// import at runtime but a bundler still walks it, so listing the React surfaces here would pull\n// the plugin's components into every node and bun build.\n\nexport const CreateObject = Capability.lazy('CreateObject', () => import('./create-object'));\nexport const OperationHandler = Capability.lazy<OperationHandlerSet.OperationHandlerSet>(\n 'OperationHandler',\n () => import('./operation-handler'),\n);\n"],"mappings":";;AAWA,IAAa,eAAe,WAAW,KAAK,sBAAsB,OAAO,6BAAkB;AAC3F,IAAa,mBAAmB,WAAW,KACzC,0BACM,OAAO,iCACf"}
@@ -0,0 +1,15 @@
1
+ import { Capability } from "@dxos/app-framework";
2
+ //#region src/capabilities/index.ts
3
+ var SkillDefinition = Capability.lazy("SkillDefinition", () => import("./chunk-skill-definition.mjs"));
4
+ var CreateObject = Capability.lazy("CreateObject", () => import("./chunk-create-object.mjs"));
5
+ var EdgeBackend = Capability.lazy("EdgeBackend", () => import("./chunk-edge-backend.mjs"));
6
+ var FileUploader = Capability.lazy("FileUploader", () => import("./chunk-file-uploader.mjs"));
7
+ var InlineBackend = Capability.lazy("InlineBackend", () => import("./chunk-inline-backend.mjs"));
8
+ var Markdown = Capability.lazy("MarkdownExtension", () => import("./chunk-markdown-extension.mjs"));
9
+ var OperationHandler = Capability.lazy("OperationHandler", () => import("./chunk-operation-handler.mjs"));
10
+ var ReactSurface = Capability.lazy("ReactSurface", () => import("./chunk-react-surface.mjs"));
11
+ var Settings = Capability.lazy("Settings", () => import("./chunk-settings.mjs"));
12
+ //#endregion
13
+ export { CreateObject, EdgeBackend, FileUploader, InlineBackend, Markdown, OperationHandler, ReactSurface, Settings, SkillDefinition };
14
+
15
+ //# sourceMappingURL=capabilities.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"capabilities.mjs","names":[],"sources":["../../src/capabilities/index.ts"],"sourcesContent":["//\n// Copyright 2026 DXOS.org\n//\n\nimport { Capability } from '@dxos/app-framework';\nimport { OperationHandlerSet } from '@dxos/compute';\n\nexport const SkillDefinition = Capability.lazy('SkillDefinition', () => import('./skill-definition'));\nexport const CreateObject = Capability.lazy('CreateObject', () => import('./create-object'));\nexport const EdgeBackend = Capability.lazy('EdgeBackend', () => import('./edge-backend'));\nexport const FileUploader = Capability.lazy('FileUploader', () => import('./file-uploader'));\nexport const InlineBackend = Capability.lazy('InlineBackend', () => import('./inline-backend'));\nexport const Markdown = Capability.lazy('MarkdownExtension', () => import('./markdown-extension'));\nexport const OperationHandler = Capability.lazy<OperationHandlerSet.OperationHandlerSet>(\n 'OperationHandler',\n () => import('./operation-handler'),\n);\nexport const ReactSurface = Capability.lazy('ReactSurface', () => import('./react-surface'));\nexport const Settings = Capability.lazy('Settings', () => import('./settings'));\n"],"mappings":";;AAOA,IAAa,kBAAkB,WAAW,KAAK,yBAAyB,OAAO,+BAAqB;AACpG,IAAa,eAAe,WAAW,KAAK,sBAAsB,OAAO,4BAAkB;AAC3F,IAAa,cAAc,WAAW,KAAK,qBAAqB,OAAO,2BAAiB;AACxF,IAAa,eAAe,WAAW,KAAK,sBAAsB,OAAO,4BAAkB;AAC3F,IAAa,gBAAgB,WAAW,KAAK,uBAAuB,OAAO,6BAAmB;AAC9F,IAAa,WAAW,WAAW,KAAK,2BAA2B,OAAO,iCAAuB;AACjG,IAAa,mBAAmB,WAAW,KACzC,0BACM,OAAO,gCACf;AACA,IAAa,eAAe,WAAW,KAAK,sBAAsB,OAAO,4BAAkB;AAC3F,IAAa,WAAW,WAAW,KAAK,kBAAkB,OAAO,uBAAa"}
@@ -0,0 +1,62 @@
1
+ import { Blob, Database, Obj } from "@dxos/echo";
2
+ import { useEffect, useState } from "react";
3
+ import { EffectEx } from "@dxos/effect";
4
+ import { Panel } from "@dxos/react-ui";
5
+ import { jsx } from "react/jsx-runtime";
6
+ import * as Effect from "effect/Effect";
7
+ import * as Option from "effect/Option";
8
+ import { FilePreview } from "#components";
9
+ //#region src/containers/FileArticle/FileArticle.tsx
10
+ var FileArticle = ({ role, subject: file }) => {
11
+ const [rendered, setRendered] = useState(void 0);
12
+ useEffect(() => {
13
+ setRendered(void 0);
14
+ const db = Obj.getDatabase(file);
15
+ if (!db) return;
16
+ let cancelled = false;
17
+ let createdBlobUrl;
18
+ const program = Effect.gen(function* () {
19
+ const blob = yield* Database.load(file.data);
20
+ const type = blob.type ?? "application/octet-stream";
21
+ const urlOption = yield* Blob.url(blob);
22
+ if (Option.isSome(urlOption)) return {
23
+ url: urlOption.value,
24
+ type
25
+ };
26
+ const bytes = yield* Blob.read(blob);
27
+ return {
28
+ url: URL.createObjectURL(new globalThis.Blob([bytes], { type })),
29
+ type
30
+ };
31
+ }).pipe(Effect.provide(Database.layer(db)), Effect.catchAll(() => Effect.succeed(void 0)));
32
+ EffectEx.runPromise(program).then((result) => {
33
+ if (cancelled) {
34
+ if (result?.url.startsWith("blob:")) URL.revokeObjectURL(result.url);
35
+ return;
36
+ }
37
+ if (result?.url.startsWith("blob:")) createdBlobUrl = result.url;
38
+ setRendered(result);
39
+ });
40
+ return () => {
41
+ cancelled = true;
42
+ if (createdBlobUrl) URL.revokeObjectURL(createdBlobUrl);
43
+ };
44
+ }, [file.id]);
45
+ if (!rendered) return null;
46
+ return /* @__PURE__ */ jsx(Panel.Root, {
47
+ role,
48
+ classNames: "dx-document",
49
+ children: /* @__PURE__ */ jsx(Panel.Content, {
50
+ asChild: true,
51
+ children: /* @__PURE__ */ jsx(FilePreview, {
52
+ type: rendered.type,
53
+ url: rendered.url
54
+ })
55
+ })
56
+ });
57
+ };
58
+ FileArticle.displayName = "FileArticle";
59
+ //#endregion
60
+ export { FileArticle as default };
61
+
62
+ //# sourceMappingURL=chunk-FileArticle.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chunk-FileArticle.mjs","names":[],"sources":["../../src/containers/FileArticle/FileArticle.tsx"],"sourcesContent":["//\n// Copyright 2026 DXOS.org\n//\n\nimport * as Effect from 'effect/Effect';\nimport * as Option from 'effect/Option';\nimport React, { useEffect, useState } from 'react';\n\nimport { type AppSurface } from '@dxos/app-toolkit/ui';\nimport { Blob, Database, Obj } from '@dxos/echo';\nimport { EffectEx } from '@dxos/effect';\nimport { Panel } from '@dxos/react-ui';\n\nimport { FilePreview } from '#components';\nimport { File } from '#types';\n\nexport type FileArticleProps = AppSurface.ObjectArticleProps<File.File>;\n\nexport const FileArticle = ({ role, subject: file }: FileArticleProps) => {\n const [rendered, setRendered] = useState<{ url: string; type: string } | undefined>(undefined);\n\n useEffect(() => {\n setRendered(undefined);\n\n const db = Obj.getDatabase(file);\n if (!db) {\n return;\n }\n\n let cancelled = false;\n let createdBlobUrl: string | undefined;\n\n const program = Effect.gen(function* () {\n const blob = yield* Database.load(file.data);\n const type = blob.type ?? 'application/octet-stream';\n const urlOption = yield* Blob.url(blob);\n if (Option.isSome(urlOption)) {\n return { url: urlOption.value, type };\n }\n const bytes = yield* Blob.read(blob);\n // `Uint8Array` is generic over `ArrayBufferLike` (incl. `SharedArrayBuffer`) while DOM's\n // `BlobPart` only covers `ArrayBuffer`-backed views — a gap between the DOM lib types and\n // the TS standard lib, not fixable by typing `bytes` differently.\n const url = URL.createObjectURL(new globalThis.Blob([bytes as BlobPart], { type }));\n return { url, type };\n }).pipe(\n Effect.provide(Database.layer(db)),\n Effect.catchAll(() => Effect.succeed(undefined)),\n );\n\n void EffectEx.runPromise(program).then((result) => {\n if (cancelled) {\n if (result?.url.startsWith('blob:')) {\n URL.revokeObjectURL(result.url);\n }\n return;\n }\n if (result?.url.startsWith('blob:')) {\n createdBlobUrl = result.url;\n }\n setRendered(result);\n });\n\n return () => {\n cancelled = true;\n if (createdBlobUrl) {\n URL.revokeObjectURL(createdBlobUrl);\n }\n };\n // Keyed on `file.id` rather than `file`/`file.data` directly: ECHO's reactive proxy returns a\n // fresh `Ref` wrapper for `.data` on every access, so including it (or the proxy object itself)\n // here would rerun this effect on every render — clearing the render state and re-resolving it\n // each time, which flickers the image while the object settles.\n }, [file.id]);\n\n if (!rendered) {\n return null;\n }\n\n return (\n <Panel.Root role={role} classNames='dx-document'>\n <Panel.Content asChild>\n <FilePreview type={rendered.type} url={rendered.url} />\n </Panel.Content>\n </Panel.Root>\n );\n};\n\nexport default FileArticle;\n\nFileArticle.displayName = 'FileArticle';\n"],"mappings":";;;;;;;;;AAkBA,IAAa,eAAe,EAAE,MAAM,SAAS,WAA6B;CACxE,MAAM,CAAC,UAAU,eAAe,SAAoD,KAAA,CAAS;CAE7F,gBAAgB;EACd,YAAY,KAAA,CAAS;EAErB,MAAM,KAAK,IAAI,YAAY,IAAI;EAC/B,IAAI,CAAC,IACH;EAGF,IAAI,YAAY;EAChB,IAAI;EAEJ,MAAM,UAAU,OAAO,IAAI,aAAa;GACtC,MAAM,OAAO,OAAO,SAAS,KAAK,KAAK,IAAI;GAC3C,MAAM,OAAO,KAAK,QAAQ;GAC1B,MAAM,YAAY,OAAO,KAAK,IAAI,IAAI;GACtC,IAAI,OAAO,OAAO,SAAS,GACzB,OAAO;IAAE,KAAK,UAAU;IAAO;GAAK;GAEtC,MAAM,QAAQ,OAAO,KAAK,KAAK,IAAI;GAKnC,OAAO;IAAE,KADG,IAAI,gBAAgB,IAAI,WAAW,KAAK,CAAC,KAAiB,GAAG,EAAE,KAAK,CAAC,CACxE;IAAK;GAAK;EACrB,CAAC,CAAC,CAAC,KACD,OAAO,QAAQ,SAAS,MAAM,EAAE,CAAC,GACjC,OAAO,eAAe,OAAO,QAAQ,KAAA,CAAS,CAAC,CACjD;EAEA,SAAc,WAAW,OAAO,CAAC,CAAC,MAAM,WAAW;GACjD,IAAI,WAAW;IACb,IAAI,QAAQ,IAAI,WAAW,OAAO,GAChC,IAAI,gBAAgB,OAAO,GAAG;IAEhC;GACF;GACA,IAAI,QAAQ,IAAI,WAAW,OAAO,GAChC,iBAAiB,OAAO;GAE1B,YAAY,MAAM;EACpB,CAAC;EAED,aAAa;GACX,YAAY;GACZ,IAAI,gBACF,IAAI,gBAAgB,cAAc;EAEtC;CAKF,GAAG,CAAC,KAAK,EAAE,CAAC;CAEZ,IAAI,CAAC,UACH,OAAO;CAGT,OACE,oBAAC,MAAM,MAAP;EAAkB;EAAM,YAAW;YACjC,oBAAC,MAAM,SAAP;GAAe,SAAA;aACb,oBAAC,aAAD;IAAa,MAAM,SAAS;IAAM,KAAK,SAAS;GAAM,CAAA;EACzC,CAAA;CACL,CAAA;AAEhB;AAIA,YAAY,cAAc"}
@@ -0,0 +1,28 @@
1
+ import { Operation } from "@dxos/compute";
2
+ import { Capability } from "@dxos/app-framework";
3
+ import { Type } from "@dxos/echo";
4
+ import { File, FileAction, FileOperation } from "#types";
5
+ import * as Effect from "effect/Effect";
6
+ import { SpaceCapabilities, SpaceOperation } from "@dxos/plugin-space";
7
+ //#region src/capabilities/create-object.ts
8
+ var create_object_default = Capability.makeModule(Effect.fnUntraced(function* () {
9
+ return Capability.contributes(SpaceCapabilities.CreateObjectEntry, {
10
+ id: Type.getTypename(File.File),
11
+ inputSchema: FileAction.CreateFileSchema,
12
+ createObject: (props, options) => Effect.gen(function* () {
13
+ const { object } = yield* Operation.invoke(FileOperation.Create, {
14
+ ...props,
15
+ db: options.db
16
+ });
17
+ return yield* Operation.invoke(SpaceOperation.AddObject, {
18
+ object,
19
+ target: options.target,
20
+ targetNodeId: options.targetNodeId
21
+ });
22
+ })
23
+ });
24
+ }));
25
+ //#endregion
26
+ export { create_object_default as default };
27
+
28
+ //# sourceMappingURL=chunk-create-object.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chunk-create-object.mjs","names":[],"sources":["../../src/capabilities/create-object.ts"],"sourcesContent":["//\n// Copyright 2026 DXOS.org\n//\n\nimport * as Effect from 'effect/Effect';\n\nimport { Capability } from '@dxos/app-framework';\nimport { Operation } from '@dxos/compute';\nimport { Type } from '@dxos/echo';\nimport { SpaceCapabilities, SpaceOperation } from '@dxos/plugin-space';\n\nimport { File, FileAction, FileOperation } from '#types';\n\nexport default Capability.makeModule(\n Effect.fnUntraced(function* () {\n return Capability.contributes(SpaceCapabilities.CreateObjectEntry, {\n id: Type.getTypename(File.File),\n inputSchema: FileAction.CreateFileSchema,\n createObject: (props, options) =>\n Effect.gen(function* () {\n const { object } = yield* Operation.invoke(FileOperation.Create, { ...props, db: options.db });\n return yield* Operation.invoke(SpaceOperation.AddObject, {\n object,\n target: options.target,\n targetNodeId: options.targetNodeId,\n });\n }),\n });\n }),\n);\n"],"mappings":";;;;;;;AAaA,IAAA,wBAAe,WAAW,WACxB,OAAO,WAAW,aAAa;CAC7B,OAAO,WAAW,YAAY,kBAAkB,mBAAmB;EACjE,IAAI,KAAK,YAAY,KAAK,IAAI;EAC9B,aAAa,WAAW;EACxB,eAAe,OAAO,YACpB,OAAO,IAAI,aAAa;GACtB,MAAM,EAAE,WAAW,OAAO,UAAU,OAAO,cAAc,QAAQ;IAAE,GAAG;IAAO,IAAI,QAAQ;GAAG,CAAC;GAC7F,OAAO,OAAO,UAAU,OAAO,eAAe,WAAW;IACvD;IACA,QAAQ,QAAQ;IAChB,cAAc,QAAQ;GACxB,CAAC;EACH,CAAC;CACL,CAAC;AACH,CAAC,CACH"}