@jolly-pixel/asset-server 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (139) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +83 -0
  3. package/dist/catalog/CatalogExtension.d.ts +37 -0
  4. package/dist/catalog/CatalogExtension.d.ts.map +1 -0
  5. package/dist/catalog/CatalogExtension.js +82 -0
  6. package/dist/catalog/CatalogIdentitySidecar.d.ts +52 -0
  7. package/dist/catalog/CatalogIdentitySidecar.d.ts.map +1 -0
  8. package/dist/catalog/CatalogIdentitySidecar.js +132 -0
  9. package/dist/catalog/CatalogProjection.d.ts +32 -0
  10. package/dist/catalog/CatalogProjection.d.ts.map +1 -0
  11. package/dist/catalog/CatalogProjection.js +109 -0
  12. package/dist/catalog/httpHandler.d.ts +13 -0
  13. package/dist/catalog/httpHandler.d.ts.map +1 -0
  14. package/dist/catalog/httpHandler.js +27 -0
  15. package/dist/catalog/index.d.ts +9 -0
  16. package/dist/catalog/index.d.ts.map +1 -0
  17. package/dist/catalog/index.js +4 -0
  18. package/dist/constants.d.ts +9 -0
  19. package/dist/constants.d.ts.map +1 -0
  20. package/dist/constants.js +13 -0
  21. package/dist/createAssetBackend.d.ts +79 -0
  22. package/dist/createAssetBackend.d.ts.map +1 -0
  23. package/dist/createAssetBackend.js +146 -0
  24. package/dist/events/AssetEvents.d.ts +54 -0
  25. package/dist/events/AssetEvents.d.ts.map +1 -0
  26. package/dist/events/AssetEvents.js +102 -0
  27. package/dist/events/AssetEvents.schema.d.ts +111 -0
  28. package/dist/events/AssetEvents.schema.d.ts.map +1 -0
  29. package/dist/events/AssetEvents.schema.js +75 -0
  30. package/dist/events/index.d.ts +3 -0
  31. package/dist/events/index.d.ts.map +1 -0
  32. package/dist/events/index.js +1 -0
  33. package/dist/index.d.ts +13 -0
  34. package/dist/index.d.ts.map +1 -0
  35. package/dist/index.js +11 -0
  36. package/dist/kinds/AssetKindHandler.d.ts +38 -0
  37. package/dist/kinds/AssetKindHandler.d.ts.map +1 -0
  38. package/dist/kinds/AssetKindHandler.js +1 -0
  39. package/dist/kinds/AssetKindRegistry.d.ts +14 -0
  40. package/dist/kinds/AssetKindRegistry.d.ts.map +1 -0
  41. package/dist/kinds/AssetKindRegistry.js +52 -0
  42. package/dist/kinds/errors/UnknownAssetKindError.d.ts +5 -0
  43. package/dist/kinds/errors/UnknownAssetKindError.d.ts.map +1 -0
  44. package/dist/kinds/errors/UnknownAssetKindError.js +8 -0
  45. package/dist/kinds/errors/index.d.ts +2 -0
  46. package/dist/kinds/errors/index.d.ts.map +1 -0
  47. package/dist/kinds/errors/index.js +1 -0
  48. package/dist/kinds/handlers/binary.d.ts +10 -0
  49. package/dist/kinds/handlers/binary.d.ts.map +1 -0
  50. package/dist/kinds/handlers/binary.js +31 -0
  51. package/dist/kinds/handlers/index.d.ts +5 -0
  52. package/dist/kinds/handlers/index.d.ts.map +1 -0
  53. package/dist/kinds/handlers/index.js +2 -0
  54. package/dist/kinds/handlers/texture.d.ts +12 -0
  55. package/dist/kinds/handlers/texture.d.ts.map +1 -0
  56. package/dist/kinds/handlers/texture.js +27 -0
  57. package/dist/kinds/index.d.ts +5 -0
  58. package/dist/kinds/index.d.ts.map +1 -0
  59. package/dist/kinds/index.js +3 -0
  60. package/dist/logger.d.ts +4 -0
  61. package/dist/logger.d.ts.map +1 -0
  62. package/dist/logger.js +9 -0
  63. package/dist/plugins/vite.d.ts +36 -0
  64. package/dist/plugins/vite.d.ts.map +1 -0
  65. package/dist/plugins/vite.js +60 -0
  66. package/dist/rooms/AssetRoomExtension.d.ts +29 -0
  67. package/dist/rooms/AssetRoomExtension.d.ts.map +1 -0
  68. package/dist/rooms/AssetRoomExtension.js +50 -0
  69. package/dist/rooms/index.d.ts +5 -0
  70. package/dist/rooms/index.d.ts.map +1 -0
  71. package/dist/rooms/index.js +2 -0
  72. package/dist/rooms/registerAssetRooms.d.ts +21 -0
  73. package/dist/rooms/registerAssetRooms.d.ts.map +1 -0
  74. package/dist/rooms/registerAssetRooms.js +71 -0
  75. package/dist/static/contentTypes.d.ts +6 -0
  76. package/dist/static/contentTypes.d.ts.map +1 -0
  77. package/dist/static/contentTypes.js +27 -0
  78. package/dist/static/httpHandler.d.ts +24 -0
  79. package/dist/static/httpHandler.d.ts.map +1 -0
  80. package/dist/static/httpHandler.js +105 -0
  81. package/dist/static/index.d.ts +4 -0
  82. package/dist/static/index.d.ts.map +1 -0
  83. package/dist/static/index.js +2 -0
  84. package/dist/sync/AssetProjector.d.ts +33 -0
  85. package/dist/sync/AssetProjector.d.ts.map +1 -0
  86. package/dist/sync/AssetProjector.js +186 -0
  87. package/dist/sync/AssetStateStore.d.ts +28 -0
  88. package/dist/sync/AssetStateStore.d.ts.map +1 -0
  89. package/dist/sync/AssetStateStore.js +86 -0
  90. package/dist/sync/AssetWriter.d.ts +54 -0
  91. package/dist/sync/AssetWriter.d.ts.map +1 -0
  92. package/dist/sync/AssetWriter.js +149 -0
  93. package/dist/sync/ProjectionState.d.ts +33 -0
  94. package/dist/sync/ProjectionState.d.ts.map +1 -0
  95. package/dist/sync/ProjectionState.js +129 -0
  96. package/dist/sync/Reconciler.d.ts +36 -0
  97. package/dist/sync/Reconciler.d.ts.map +1 -0
  98. package/dist/sync/Reconciler.js +182 -0
  99. package/dist/sync/ReconciliationWatcher.d.ts +28 -0
  100. package/dist/sync/ReconciliationWatcher.d.ts.map +1 -0
  101. package/dist/sync/ReconciliationWatcher.js +86 -0
  102. package/dist/sync/SnapshotScheduler.d.ts +29 -0
  103. package/dist/sync/SnapshotScheduler.d.ts.map +1 -0
  104. package/dist/sync/SnapshotScheduler.js +136 -0
  105. package/dist/sync/foldProjection.d.ts +12 -0
  106. package/dist/sync/foldProjection.d.ts.map +1 -0
  107. package/dist/sync/foldProjection.js +32 -0
  108. package/dist/sync/index.d.ts +19 -0
  109. package/dist/sync/index.d.ts.map +1 -0
  110. package/dist/sync/index.js +9 -0
  111. package/dist/sync/matchRenames.d.ts +45 -0
  112. package/dist/sync/matchRenames.d.ts.map +1 -0
  113. package/dist/sync/matchRenames.js +85 -0
  114. package/dist/utils/TaskChain.d.ts +9 -0
  115. package/dist/utils/TaskChain.d.ts.map +1 -0
  116. package/dist/utils/TaskChain.js +14 -0
  117. package/dist/utils/contentHash.d.ts +2 -0
  118. package/dist/utils/contentHash.d.ts.map +1 -0
  119. package/dist/utils/contentHash.js +7 -0
  120. package/dist/utils/index.d.ts +3 -0
  121. package/dist/utils/index.d.ts.map +1 -0
  122. package/dist/utils/index.js +2 -0
  123. package/dist/workspace/createAssetWorkspace.d.ts +71 -0
  124. package/dist/workspace/createAssetWorkspace.d.ts.map +1 -0
  125. package/dist/workspace/createAssetWorkspace.js +72 -0
  126. package/dist/workspace/index.d.ts +5 -0
  127. package/dist/workspace/index.d.ts.map +1 -0
  128. package/dist/workspace/index.js +2 -0
  129. package/dist/workspace/seedAssetSource.d.ts +5 -0
  130. package/dist/workspace/seedAssetSource.d.ts.map +1 -0
  131. package/dist/workspace/seedAssetSource.js +20 -0
  132. package/docs/AssetBackend.md +73 -0
  133. package/docs/AssetKinds.md +204 -0
  134. package/docs/AssetWriter.md +62 -0
  135. package/docs/Catalog.md +71 -0
  136. package/docs/Rooms.md +70 -0
  137. package/docs/Sync.md +146 -0
  138. package/docs/Workspace.md +172 -0
  139. package/package.json +83 -0
@@ -0,0 +1,172 @@
1
+ # Workspace
2
+
3
+ `createAssetWorkspace` assembles what a host needs to edit an asset
4
+ workspace live: a source, an event log, the back-end, and the network server
5
+ its rooms are attached to.
6
+
7
+ ```ts
8
+ createAssetWorkspace(options: AssetWorkspaceOptions): Promise<AssetWorkspace>
9
+ ```
10
+
11
+ ```ts
12
+ await using workspace = await createAssetWorkspace({
13
+ root: "./assets",
14
+ handlers: [textureAssetHandler()],
15
+ seed: {
16
+ "textures/block.png": () => defaultTextureBytes()
17
+ }
18
+ });
19
+ ```
20
+
21
+ ## Options
22
+
23
+ | Option | Default | Description |
24
+ |---|---|---|
25
+ | `root` | required | Filesystem root, behind the default source and event log. |
26
+ | `handlers` | `[]` | Asset kind handlers. Unmatched paths use `binary`. |
27
+ | `seed` | none | Starter documents, written only where the workspace holds no file. |
28
+ | `source` | `FilesystemAssetSource(root)` | Physical storage. |
29
+ | `eventStore` | sqlite in the state directory | Event log. |
30
+ | `server` | a new `Server` | Network server hosting the rooms. |
31
+ | `extensions` | `[]` | Extensions registered before the asset rooms attach. |
32
+ | `rights` | none | Rights map for the server it builds. |
33
+ | `roomGraceMs` | server default | Grace period before an empty room is evicted. |
34
+ | `compactOnOpen` | `true` | Drop the events superseded by each asset's newest checkpoint. |
35
+ | `logger` | silent | A `loglayer` logger. Left unset, the server keeps its own. |
36
+ | `backend` | `{}` | Extra [`AssetBackend`](./AssetBackend.md) options. |
37
+
38
+ The workspace owns only what it creates: a source, event store or server
39
+ passed in is left open by `close()`.
40
+
41
+ Seeding runs before the back-end starts, so the first reconciliation catalogs
42
+ the starter documents.
43
+
44
+ ## Compaction
45
+
46
+ Every `asset.created` and `asset.updated` event carries the whole document
47
+ inline, so a log that keeps them all grows with each edit and startup slows
48
+ with it. Opening a workspace therefore compacts the log first, dropping the
49
+ events stored before each asset's newest `asset.created`, `asset.updated` or
50
+ `asset.deleted`. Nothing reads below that point: both projections load from
51
+ it, and so does state replay.
52
+
53
+ > [!WARNING]
54
+ > Compaction is destructive and irreversible. It discards the editing history
55
+ > in exchange for a log that stops growing without bound. Pass
56
+ > `compactOnOpen: false` to keep it.
57
+
58
+ It runs before the back-end, so the projections never read the superseded
59
+ events and the reclaimed file is the one the back-end opens. See
60
+ [`EventStore compaction`](../../event-store/docs/EventStore.md#compaction).
61
+
62
+ ## Event log
63
+
64
+ Without an `eventStore` option the workspace opens a sqlite log at
65
+ `.jollypixel/events.db` under the root, and the backend creates the directories
66
+ it needs. Pass `eventStore` to bring your own, in which case `close()` leaves
67
+ it open.
68
+
69
+ ## Seeding
70
+
71
+ ```ts
72
+ seedAssetSource(source: AssetSource, seed: AssetSeedMap): Promise<string[]>
73
+ ```
74
+
75
+ Writes each starter document whose path the source does not hold, and returns
76
+ the paths written. An existing file is never overwritten and its factory is
77
+ never called: once the workspace exists it is the source of truth.
78
+
79
+ ## Serving the workspace
80
+
81
+ The catalog hands the browser workspace-relative `source` paths, which have to
82
+ resolve to something.
83
+
84
+ ```ts
85
+ import { createAssetStaticHandler } from "@jolly-pixel/asset-server/static";
86
+
87
+ const handler = createAssetStaticHandler({
88
+ source: workspace.source,
89
+ kinds: workspace.backend.kinds
90
+ });
91
+ ```
92
+
93
+ Requests outside the prefix are passed to `next()`. `GET` and `HEAD` answer
94
+ `200` from the source, other methods `405`. Reads go through the
95
+ `AssetSource`, so an in-memory workspace is servable too.
96
+
97
+ The request target is stripped of its query and fragment, decoded once, then
98
+ validated by
99
+ [`safeAssetPath`](../../asset-source/docs/AssetSource.md#paths), so the source
100
+ only ever sees a root-relative POSIX path. The rejection decides the status:
101
+
102
+ | Case | Status |
103
+ |---|---|
104
+ | Absolute, drive-qualified or `..` path, source refusing the path | `403` |
105
+ | Malformed escape sequence, control character in the path | `400` |
106
+ | Missing file, directory target, state directory, path the source ignores | `404` |
107
+
108
+ The state directory is matched case-insensitively, because a
109
+ case-insensitive filesystem answers `.JOLLYPIXEL/state.json` from
110
+ `.jollypixel/`. Paths a source hides through `isIgnored` (`.git/`,
111
+ `node_modules/`, `dist/` by default) answer `404` as well, so the handler
112
+ never serves what listing and reconciliation deliberately skip.
113
+
114
+ | Option | Default | Description |
115
+ |---|---|---|
116
+ | `source` | required | Workspace the bytes are read from. |
117
+ | `prefix` | `/assets/` | URL prefix, with a trailing slash added when missing. |
118
+ | `kinds` | none | Registry contributing content types per claimed extension. |
119
+ | `contentTypes` | none | Extension-to-content-type entries, winning over the kinds. |
120
+
121
+ Content types come from [what each kind declares](./AssetKinds.md#content-types)
122
+ merged over a small default table; anything unmatched is served as
123
+ `application/octet-stream`.
124
+
125
+ `@jolly-pixel/asset` exports `ASSET_URL_PREFIX`, `CATALOG_URL_PATH` and
126
+ `assetSourceUrl(source)` so the browser builds the same URLs without repeating
127
+ the routes.
128
+
129
+ ## Vite plugin
130
+
131
+ ```ts
132
+ import {
133
+ createAssetWorkspacePlugin
134
+ } from "@jolly-pixel/asset-server/plugins/vite.ts";
135
+
136
+ export default defineConfig({
137
+ plugins: [
138
+ createAssetWorkspacePlugin({
139
+ root: path.join(import.meta.dirname, "assets"),
140
+ handlers: [textureAssetHandler()]
141
+ })
142
+ ]
143
+ });
144
+ ```
145
+
146
+ One plugin mounts the whole workspace on the dev server: the catalog route,
147
+ static delivery and the WebSocket the asset rooms are edited through. It
148
+ accepts every `createAssetWorkspace` option plus:
149
+
150
+ | Option | Default | Description |
151
+ |---|---|---|
152
+ | `catalogPath` | `/__jollypixel/catalog` | Catalog route. |
153
+ | `prefix` | `/assets/` | URL prefix the workspace is served under. |
154
+ | `socketPath` | `/ws-sync` | WebSocket upgrade path, kept apart from Vite HMR. |
155
+ | `onReady` | none | Receives the workspace once the back-end is up. |
156
+
157
+ Everything is built inside `configureServer`, so a production build never
158
+ opens the event log. `closeBundle` closes the workspace.
159
+
160
+ Use `onReady`, or pass your own `server`, when the dev server also hosts rooms
161
+ of its own:
162
+
163
+ ```ts
164
+ createAssetWorkspacePlugin({
165
+ root,
166
+ extensions: [new MyDemoRoom()],
167
+ onReady: ({ backend }) => reportCatalogSize(backend.catalog.size)
168
+ });
169
+ ```
170
+
171
+ `createAssetCatalogPlugin` and `createAssetStaticPlugin` remain available for
172
+ a host wiring the pieces itself.
package/package.json ADDED
@@ -0,0 +1,83 @@
1
+ {
2
+ "name": "@jolly-pixel/asset-server",
3
+ "description": "Back-end asset orchestration: event-sourced projection, reconciliation and catalog delivery",
4
+ "version": "1.0.0",
5
+ "type": "module",
6
+ "imports": {
7
+ "#src/*": "./src/*"
8
+ },
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "default": "./dist/index.js"
13
+ },
14
+ "./sync": {
15
+ "types": "./dist/sync/index.d.ts",
16
+ "default": "./dist/sync/index.js"
17
+ },
18
+ "./catalog": {
19
+ "types": "./dist/catalog/index.d.ts",
20
+ "default": "./dist/catalog/index.js"
21
+ },
22
+ "./rooms": {
23
+ "types": "./dist/rooms/index.d.ts",
24
+ "default": "./dist/rooms/index.js"
25
+ },
26
+ "./plugins/*.ts": "./dist/plugins/*.js",
27
+ "./static": {
28
+ "types": "./dist/static/index.d.ts",
29
+ "default": "./dist/static/index.js"
30
+ },
31
+ "./workspace": {
32
+ "types": "./dist/workspace/index.d.ts",
33
+ "default": "./dist/workspace/index.js"
34
+ }
35
+ },
36
+ "scripts": {
37
+ "prepublish": "rimraf ./dist && tsc -b",
38
+ "build": "tsc",
39
+ "typecheck": "tsc --noEmit -p test/tsconfig.json",
40
+ "test-only": "node --test \"test/**/*.spec.ts\"",
41
+ "test-types": "tstyche --tsconfig ./test/tsconfig.json",
42
+ "test": "npm run typecheck && c8 -r html npm run test-only && npm run test-types",
43
+ "lint": "eslint src test"
44
+ },
45
+ "publishConfig": {
46
+ "registry": "https://registry.npmjs.org",
47
+ "access": "public"
48
+ },
49
+ "repository": {
50
+ "type": "git",
51
+ "url": "git+https://github.com/JollyPixel/editor.git",
52
+ "directory": "packages/asset-server"
53
+ },
54
+ "keywords": [],
55
+ "files": [
56
+ "dist",
57
+ "docs"
58
+ ],
59
+ "directories": {
60
+ "doc": "docs",
61
+ "test": "test"
62
+ },
63
+ "author": "GENTILHOMME Thomas <gentilhomme.thomas@gmail.com>",
64
+ "license": "MIT",
65
+ "dependencies": {
66
+ "@jolly-pixel/asset": "1.0.0",
67
+ "@jolly-pixel/asset-source": "1.0.0",
68
+ "@jolly-pixel/event-store": "2.0.0",
69
+ "@jolly-pixel/network": "1.1.0",
70
+ "@openally/emitt": "1.0.1",
71
+ "@openally/result": "3.1.0",
72
+ "ata-validator": "1.13.2",
73
+ "loglayer": "9.4.0"
74
+ },
75
+ "peerDependencies": {
76
+ "vite": "^8.0.0"
77
+ },
78
+ "peerDependenciesMeta": {
79
+ "vite": {
80
+ "optional": true
81
+ }
82
+ }
83
+ }