@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,12 @@
1
+ import { type AssetEvent, type AssetInlineContent } from "../events/AssetEvents.ts";
2
+ /**
3
+ * Physical state one asset stream asks for. `null` means "absent".
4
+ */
5
+ export interface AssetProjection {
6
+ readonly path: string;
7
+ readonly kind: string;
8
+ readonly hash: string;
9
+ readonly content: AssetInlineContent;
10
+ }
11
+ export declare function applyProjection(projection: AssetProjection | null, event: AssetEvent): AssetProjection | null;
12
+ //# sourceMappingURL=foldProjection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"foldProjection.d.ts","sourceRoot":"","sources":["../../src/sync/foldProjection.ts"],"names":[],"mappings":"AACA,OAAO,EAKL,KAAK,UAAU,EACf,KAAK,kBAAkB,EACxB,MAAM,0BAA0B,CAAC;AAElC;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,kBAAkB,CAAC;CACtC;AAED,wBAAgB,eAAe,CAC7B,UAAU,EAAE,eAAe,GAAG,IAAI,EAClC,KAAK,EAAE,UAAU,GAChB,eAAe,GAAG,IAAI,CA4BxB"}
@@ -0,0 +1,32 @@
1
+ // Import Internal Dependencies
2
+ import { ASSET_CREATED, ASSET_DELETED, ASSET_RENAMED, ASSET_UPDATED } from "../events/AssetEvents.js";
3
+ export function applyProjection(projection, event) {
4
+ switch (event.eventType) {
5
+ case ASSET_CREATED:
6
+ case ASSET_UPDATED: {
7
+ const { eventData } = event;
8
+ return {
9
+ path: eventData.path,
10
+ kind: eventData.kind,
11
+ hash: eventData.hash,
12
+ content: eventData.content
13
+ };
14
+ }
15
+ case ASSET_RENAMED: {
16
+ if (projection === null) {
17
+ return null;
18
+ }
19
+ return {
20
+ ...projection,
21
+ path: event.eventData.to
22
+ };
23
+ }
24
+ case ASSET_DELETED:
25
+ return null;
26
+ default:
27
+ return unhandled(event);
28
+ }
29
+ }
30
+ function unhandled(event) {
31
+ throw new TypeError(`Unhandled asset event type: ${JSON.stringify(event)}`);
32
+ }
@@ -0,0 +1,19 @@
1
+ export { AssetProjector } from "./AssetProjector.ts";
2
+ export type { AssetProjectorOptions } from "./AssetProjector.ts";
3
+ export { AssetStateStore } from "./AssetStateStore.ts";
4
+ export type { AssetStateEntry, AssetStateStoreOptions } from "./AssetStateStore.ts";
5
+ export { applyProjection } from "./foldProjection.ts";
6
+ export type { AssetProjection } from "./foldProjection.ts";
7
+ export { ProjectionState } from "./ProjectionState.ts";
8
+ export type { ProjectionFailure, ProjectionStateData } from "./ProjectionState.ts";
9
+ export { SnapshotScheduler } from "./SnapshotScheduler.ts";
10
+ export type { SnapshotSchedulerOptions } from "./SnapshotScheduler.ts";
11
+ export { AssetWriter } from "./AssetWriter.ts";
12
+ export type { AssetWriterOptions, CreateAssetInput, DeleteAssetInput, RenameAssetInput, UpdateAssetInput } from "./AssetWriter.ts";
13
+ export { matchRenames } from "./matchRenames.ts";
14
+ export type { CreatedAsset, DeletedAsset, ObservedEntry, ProjectedEntry, RenameMatch, RenamedAsset, UpdatedAsset } from "./matchRenames.ts";
15
+ export { Reconciler } from "./Reconciler.ts";
16
+ export type { ReconcileReport, ReconcilerOptions } from "./Reconciler.ts";
17
+ export { ReconciliationWatcher } from "./ReconciliationWatcher.ts";
18
+ export type { ReconciliationWatcherOptions } from "./ReconciliationWatcher.ts";
19
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/sync/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,YAAY,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AACjE,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,YAAY,EACV,eAAe,EACf,sBAAsB,EACvB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACtD,YAAY,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,YAAY,EACV,iBAAiB,EACjB,mBAAmB,EACpB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,YAAY,EAAE,wBAAwB,EAAE,MAAM,wBAAwB,CAAC;AACvE,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,YAAY,EACV,kBAAkB,EAClB,gBAAgB,EAChB,gBAAgB,EAChB,gBAAgB,EAChB,gBAAgB,EACjB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,YAAY,EACV,YAAY,EACZ,YAAY,EACZ,aAAa,EACb,cAAc,EACd,WAAW,EACX,YAAY,EACZ,YAAY,EACb,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,YAAY,EACV,eAAe,EACf,iBAAiB,EAClB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AACnE,YAAY,EACV,4BAA4B,EAC7B,MAAM,4BAA4B,CAAC"}
@@ -0,0 +1,9 @@
1
+ export { AssetProjector } from "./AssetProjector.js";
2
+ export { AssetStateStore } from "./AssetStateStore.js";
3
+ export { applyProjection } from "./foldProjection.js";
4
+ export { ProjectionState } from "./ProjectionState.js";
5
+ export { SnapshotScheduler } from "./SnapshotScheduler.js";
6
+ export { AssetWriter } from "./AssetWriter.js";
7
+ export { matchRenames } from "./matchRenames.js";
8
+ export { Reconciler } from "./Reconciler.js";
9
+ export { ReconciliationWatcher } from "./ReconciliationWatcher.js";
@@ -0,0 +1,45 @@
1
+ export interface ProjectedEntry {
2
+ readonly id: string;
3
+ readonly path: string;
4
+ readonly kind: string;
5
+ readonly hash: string;
6
+ }
7
+ export interface ObservedEntry {
8
+ readonly path: string;
9
+ readonly hash: string;
10
+ }
11
+ export interface RenamedAsset {
12
+ readonly id: string;
13
+ readonly kind: string;
14
+ readonly from: string;
15
+ readonly to: string;
16
+ readonly hash: string;
17
+ }
18
+ export interface UpdatedAsset {
19
+ readonly id: string;
20
+ readonly kind: string;
21
+ readonly path: string;
22
+ readonly hash: string;
23
+ }
24
+ export interface CreatedAsset {
25
+ readonly path: string;
26
+ readonly hash: string;
27
+ }
28
+ export interface DeletedAsset {
29
+ readonly id: string;
30
+ readonly kind: string;
31
+ readonly path: string;
32
+ }
33
+ export interface RenameMatch {
34
+ readonly renamed: RenamedAsset[];
35
+ readonly updated: UpdatedAsset[];
36
+ readonly created: CreatedAsset[];
37
+ readonly deleted: DeletedAsset[];
38
+ }
39
+ /**
40
+ * Matches a rename only when one removed and one added path share a hash.
41
+ *
42
+ * Ambiguous hashes remain deletes and creates.
43
+ */
44
+ export declare function matchRenames(projected: Iterable<ProjectedEntry>, observed: Iterable<ObservedEntry>): RenameMatch;
45
+ //# sourceMappingURL=matchRenames.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"matchRenames.d.ts","sourceRoot":"","sources":["../../src/sync/matchRenames.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,OAAO,EAAE,YAAY,EAAE,CAAC;IACjC,QAAQ,CAAC,OAAO,EAAE,YAAY,EAAE,CAAC;IACjC,QAAQ,CAAC,OAAO,EAAE,YAAY,EAAE,CAAC;IACjC,QAAQ,CAAC,OAAO,EAAE,YAAY,EAAE,CAAC;CAClC;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAC1B,SAAS,EAAE,QAAQ,CAAC,cAAc,CAAC,EACnC,QAAQ,EAAE,QAAQ,CAAC,aAAa,CAAC,GAChC,WAAW,CAmFb"}
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Matches a rename only when one removed and one added path share a hash.
3
+ *
4
+ * Ambiguous hashes remain deletes and creates.
5
+ */
6
+ export function matchRenames(projected, observed) {
7
+ const previous = new Map();
8
+ for (const entry of projected) {
9
+ previous.set(entry.path, entry);
10
+ }
11
+ const current = new Map();
12
+ for (const entry of observed) {
13
+ current.set(entry.path, entry);
14
+ }
15
+ const updated = [];
16
+ const appeared = [];
17
+ const disappeared = [];
18
+ for (const [path, entry] of current) {
19
+ const before = previous.get(path);
20
+ if (before === undefined) {
21
+ appeared.push(entry);
22
+ }
23
+ else if (before.hash !== entry.hash) {
24
+ updated.push({
25
+ id: before.id,
26
+ kind: before.kind,
27
+ path,
28
+ hash: entry.hash
29
+ });
30
+ }
31
+ }
32
+ for (const [path, entry] of previous) {
33
+ if (!current.has(path)) {
34
+ disappeared.push(entry);
35
+ }
36
+ }
37
+ const appearedByHash = Map.groupBy(appeared, (entry) => entry.hash);
38
+ const disappearedByHash = Map.groupBy(disappeared, (entry) => entry.hash);
39
+ const renamed = [];
40
+ const created = [];
41
+ const deleted = [];
42
+ for (const [hash, candidates] of appearedByHash) {
43
+ const sources = disappearedByHash.get(hash) ?? [];
44
+ const source = singleton(sources);
45
+ const target = singleton(candidates);
46
+ if (source !== undefined && target !== undefined) {
47
+ renamed.push({
48
+ id: source.id,
49
+ kind: source.kind,
50
+ from: source.path,
51
+ to: target.path,
52
+ hash
53
+ });
54
+ disappearedByHash.delete(hash);
55
+ continue;
56
+ }
57
+ for (const candidate of candidates) {
58
+ created.push({
59
+ path: candidate.path,
60
+ hash: candidate.hash
61
+ });
62
+ }
63
+ }
64
+ for (const sources of disappearedByHash.values()) {
65
+ for (const source of sources) {
66
+ deleted.push({
67
+ id: source.id,
68
+ kind: source.kind,
69
+ path: source.path
70
+ });
71
+ }
72
+ }
73
+ return {
74
+ renamed: sortBy(renamed, (entry) => entry.to),
75
+ updated: sortBy(updated, (entry) => entry.path),
76
+ created: sortBy(created, (entry) => entry.path),
77
+ deleted: sortBy(deleted, (entry) => entry.path)
78
+ };
79
+ }
80
+ function singleton(values) {
81
+ return values.length === 1 ? values.at(0) : undefined;
82
+ }
83
+ function sortBy(values, key) {
84
+ return values.sort((a, b) => key(a).localeCompare(key(b)));
85
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Serializes async work without letting a rejection break the chain.
3
+ */
4
+ export declare class TaskChain {
5
+ #private;
6
+ run<TResult>(task: () => Promise<TResult>): Promise<TResult>;
7
+ settled(): Promise<void>;
8
+ }
9
+ //# sourceMappingURL=TaskChain.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TaskChain.d.ts","sourceRoot":"","sources":["../../src/utils/TaskChain.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,qBAAa,SAAS;;IAGpB,GAAG,CAAC,OAAO,EACT,IAAI,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,GAC3B,OAAO,CAAC,OAAO,CAAC;IAYb,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;CAG/B"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Serializes async work without letting a rejection break the chain.
3
+ */
4
+ export class TaskChain {
5
+ #tail = Promise.resolve();
6
+ run(task) {
7
+ const next = this.#tail.then(task, task);
8
+ this.#tail = next.catch(() => void 0);
9
+ return next;
10
+ }
11
+ async settled() {
12
+ await this.#tail;
13
+ }
14
+ }
@@ -0,0 +1,2 @@
1
+ export declare function contentHash(data: Uint8Array): string;
2
+ //# sourceMappingURL=contentHash.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contentHash.d.ts","sourceRoot":"","sources":["../../src/utils/contentHash.ts"],"names":[],"mappings":"AAGA,wBAAgB,WAAW,CACzB,IAAI,EAAE,UAAU,GACf,MAAM,CAIR"}
@@ -0,0 +1,7 @@
1
+ // Import Node.js Dependencies
2
+ import { createHash } from "node:crypto";
3
+ export function contentHash(data) {
4
+ return createHash("sha256")
5
+ .update(data)
6
+ .digest("hex");
7
+ }
@@ -0,0 +1,3 @@
1
+ export { contentHash } from "./contentHash.ts";
2
+ export { TaskChain } from "./TaskChain.ts";
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC"}
@@ -0,0 +1,2 @@
1
+ export { contentHash } from "./contentHash.js";
2
+ export { TaskChain } from "./TaskChain.js";
@@ -0,0 +1,71 @@
1
+ import { type AssetSource } from "@jolly-pixel/asset-source";
2
+ import * as EventStore from "@jolly-pixel/event-store";
3
+ import { Server, type Extension, type RightsMap } from "@jolly-pixel/network";
4
+ import type { AssetKindHandler } from "../kinds/AssetKindHandler.ts";
5
+ import { type AssetEventDataMap } from "../events/AssetEvents.ts";
6
+ import { type AssetBackend, type AssetBackendOptions } from "../createAssetBackend.ts";
7
+ import { type Logger } from "../logger.ts";
8
+ import { type AssetSeedMap } from "./seedAssetSource.ts";
9
+ export type AssetBackendTuning = Omit<AssetBackendOptions, "source" | "eventStore" | "handlers" | "logger">;
10
+ export interface AssetWorkspaceOptions {
11
+ /**
12
+ * Filesystem root of the workspace, behind the default source and the
13
+ * default event log.
14
+ */
15
+ root: string;
16
+ handlers?: AssetKindHandler[];
17
+ /**
18
+ * Documents written when the workspace holds no file at their path.
19
+ */
20
+ seed?: AssetSeedMap;
21
+ /**
22
+ * @default FilesystemAssetSource on `root`
23
+ */
24
+ source?: AssetSource;
25
+ /**
26
+ * @default sqlite log inside the workspace state directory
27
+ */
28
+ eventStore?: EventStore.TypedEventStore<AssetEventDataMap>;
29
+ /**
30
+ * Network server hosting the catalog and asset rooms. Pass one already
31
+ * carrying extensions instead of letting the workspace build it.
32
+ */
33
+ server?: Server;
34
+ /**
35
+ * Extensions registered before the asset rooms are attached.
36
+ */
37
+ extensions?: Extension[];
38
+ rights?: RightsMap;
39
+ logger?: Logger;
40
+ /**
41
+ * Grace period, in milliseconds, before an empty asset room is evicted.
42
+ */
43
+ roomGraceMs?: number;
44
+ /**
45
+ * Drops the events superseded by each asset's newest lifecycle
46
+ * checkpoint before the back-end loads its projections.
47
+ *
48
+ * Destructive and irreversible: it trades the editing history nothing
49
+ * reads for a log that stops growing without bound.
50
+ * @default true
51
+ */
52
+ compactOnOpen?: boolean;
53
+ backend?: AssetBackendTuning;
54
+ }
55
+ export interface AssetWorkspace extends AsyncDisposable {
56
+ readonly source: AssetSource;
57
+ readonly eventStore: EventStore.TypedEventStore<AssetEventDataMap>;
58
+ readonly backend: AssetBackend;
59
+ readonly server: Server;
60
+ close(): Promise<void>;
61
+ }
62
+ /**
63
+ * Assembles the pieces a host needs to edit an asset workspace live: a
64
+ * source, an event log, the back-end and the network server its rooms are
65
+ * attached to.
66
+ *
67
+ * Owns only what it creates. A source, event store or server passed in is
68
+ * left open by `close()`.
69
+ */
70
+ export declare function createAssetWorkspace(options: AssetWorkspaceOptions): Promise<AssetWorkspace>;
71
+ //# sourceMappingURL=createAssetWorkspace.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createAssetWorkspace.d.ts","sourceRoot":"","sources":["../../src/workspace/createAssetWorkspace.ts"],"names":[],"mappings":"AAIA,OAAO,EAEL,KAAK,WAAW,EACjB,MAAM,2BAA2B,CAAC;AACnC,OAAO,KAAK,UAAU,MAAM,0BAA0B,CAAC;AACvD,OAAO,EACL,MAAM,EACN,KAAK,SAAS,EACd,KAAK,SAAS,EACf,MAAM,sBAAsB,CAAC;AAI9B,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AACrE,OAAO,EAEL,KAAK,iBAAiB,EACvB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAEL,KAAK,YAAY,EACjB,KAAK,mBAAmB,EACzB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAEL,KAAK,MAAM,EACZ,MAAM,cAAc,CAAC;AACtB,OAAO,EAEL,KAAK,YAAY,EAClB,MAAM,sBAAsB,CAAC;AAE9B,MAAM,MAAM,kBAAkB,GAAG,IAAI,CACnC,mBAAmB,EACnB,QAAQ,GAAG,YAAY,GAAG,UAAU,GAAG,QAAQ,CAChD,CAAC;AAEF,MAAM,WAAW,qBAAqB;IACpC;;;OAGG;IACH,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,gBAAgB,EAAE,CAAC;IAC9B;;OAEG;IACH,IAAI,CAAC,EAAE,YAAY,CAAC;IACpB;;OAEG;IACH,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB;;OAEG;IACH,UAAU,CAAC,EAAE,UAAU,CAAC,eAAe,CAAC,iBAAiB,CAAC,CAAC;IAC3D;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;OAEG;IACH,UAAU,CAAC,EAAE,SAAS,EAAE,CAAC;IACzB,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;;;OAOG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,OAAO,CAAC,EAAE,kBAAkB,CAAC;CAC9B;AAED,MAAM,WAAW,cAAe,SAAQ,eAAe;IACrD,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;IAC7B,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC,eAAe,CAAC,iBAAiB,CAAC,CAAC;IACnE,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAC;IAC/B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAExB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED;;;;;;;GAOG;AACH,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,qBAAqB,GAC7B,OAAO,CAAC,cAAc,CAAC,CAwEzB"}
@@ -0,0 +1,72 @@
1
+ // Import Node.js Dependencies
2
+ import path from "node:path";
3
+ // Import Third-party Dependencies
4
+ import { FilesystemAssetSource } from "@jolly-pixel/asset-source";
5
+ import * as EventStore from "@jolly-pixel/event-store";
6
+ import { Server } from "@jolly-pixel/network";
7
+ // Import Internal Dependencies
8
+ import { EVENTS_DB_PATH } from "../constants.js";
9
+ import { ASSET_CHECKPOINT_EVENT_TYPES } from "../events/AssetEvents.js";
10
+ import { createAssetBackend } from "../createAssetBackend.js";
11
+ import { silentLogger } from "../logger.js";
12
+ import { seedAssetSource } from "./seedAssetSource.js";
13
+ /**
14
+ * Assembles the pieces a host needs to edit an asset workspace live: a
15
+ * source, an event log, the back-end and the network server its rooms are
16
+ * attached to.
17
+ *
18
+ * Owns only what it creates. A source, event store or server passed in is
19
+ * left open by `close()`.
20
+ */
21
+ export async function createAssetWorkspace(options) {
22
+ const { root, handlers = [], seed, extensions = [], rights, logger = silentLogger(), roomGraceMs, compactOnOpen = true, backend: tuning = {} } = options;
23
+ const source = options.source ?? new FilesystemAssetSource(root);
24
+ if (seed) {
25
+ await seedAssetSource(source, seed);
26
+ }
27
+ const ownsEventStore = options.eventStore === undefined;
28
+ const eventStore = options.eventStore ??
29
+ await EventStore.persistence.sqlite(path.join(root, EVENTS_DB_PATH));
30
+ if (compactOnOpen) {
31
+ const report = eventStore.compact({
32
+ checkpointEventTypes: ASSET_CHECKPOINT_EVENT_TYPES
33
+ });
34
+ logger
35
+ .withMetadata(report)
36
+ .debug("event log compacted");
37
+ }
38
+ const backend = await createAssetBackend({
39
+ ...tuning,
40
+ source,
41
+ eventStore,
42
+ handlers,
43
+ logger
44
+ });
45
+ const server = options.server ?? new Server({
46
+ eventStore,
47
+ rights,
48
+ roomGraceMs,
49
+ logger: options.logger
50
+ });
51
+ for (const extension of extensions) {
52
+ server.register(extension);
53
+ }
54
+ const detach = backend.attach(server);
55
+ const workspace = {
56
+ source,
57
+ eventStore,
58
+ backend,
59
+ server,
60
+ async close() {
61
+ detach();
62
+ await backend.close();
63
+ if (ownsEventStore) {
64
+ eventStore.close();
65
+ }
66
+ },
67
+ async [Symbol.asyncDispose]() {
68
+ await workspace.close();
69
+ }
70
+ };
71
+ return workspace;
72
+ }
@@ -0,0 +1,5 @@
1
+ export { createAssetWorkspace } from "./createAssetWorkspace.ts";
2
+ export type { AssetBackendTuning, AssetWorkspace, AssetWorkspaceOptions } from "./createAssetWorkspace.ts";
3
+ export { seedAssetSource } from "./seedAssetSource.ts";
4
+ export type { AssetSeedFactory, AssetSeedMap } from "./seedAssetSource.ts";
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/workspace/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AACjE,YAAY,EACV,kBAAkB,EAClB,cAAc,EACd,qBAAqB,EACtB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,YAAY,EACV,gBAAgB,EAChB,YAAY,EACb,MAAM,sBAAsB,CAAC"}
@@ -0,0 +1,2 @@
1
+ export { createAssetWorkspace } from "./createAssetWorkspace.js";
2
+ export { seedAssetSource } from "./seedAssetSource.js";
@@ -0,0 +1,5 @@
1
+ import type { AssetSource } from "@jolly-pixel/asset-source";
2
+ export type AssetSeedFactory = () => Uint8Array | Promise<Uint8Array>;
3
+ export type AssetSeedMap = Record<string, AssetSeedFactory>;
4
+ export declare function seedAssetSource(source: AssetSource, seed: AssetSeedMap): Promise<string[]>;
5
+ //# sourceMappingURL=seedAssetSource.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"seedAssetSource.d.ts","sourceRoot":"","sources":["../../src/workspace/seedAssetSource.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAE7D,MAAM,MAAM,gBAAgB,GAAG,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;AACtE,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;AAE5D,wBAAsB,eAAe,CACnC,MAAM,EAAE,WAAW,EACnB,IAAI,EAAE,YAAY,GACjB,OAAO,CAAC,MAAM,EAAE,CAAC,CAgBnB"}
@@ -0,0 +1,20 @@
1
+ export async function seedAssetSource(source, seed) {
2
+ const written = [];
3
+ for (const [assetPath, build] of Object.entries(seed)) {
4
+ if (await exists(source, assetPath)) {
5
+ continue;
6
+ }
7
+ await source.write(assetPath, await build());
8
+ written.push(assetPath);
9
+ }
10
+ return written;
11
+ }
12
+ async function exists(source, assetPath) {
13
+ try {
14
+ await source.read(assetPath);
15
+ return true;
16
+ }
17
+ catch {
18
+ return false;
19
+ }
20
+ }
@@ -0,0 +1,73 @@
1
+ # AssetBackend
2
+
3
+ `createAssetBackend` connects an `AssetSource` to an event store and starts
4
+ catalog projection, filesystem reconciliation and snapshot scheduling.
5
+
6
+ ```ts
7
+ createAssetBackend(options: AssetBackendOptions): Promise<AssetBackend>
8
+ ```
9
+
10
+ ## Options
11
+
12
+ | Option | Default | Description |
13
+ |---|---|---|
14
+ | `source` | required | Physical asset storage. |
15
+ | `eventStore` | required | Event store used for asset and domain events. |
16
+ | `handlers` | `[]` | Asset kind handlers. Unmatched paths use `binary`. |
17
+ | `snapshot` | `2_000` / `30_000` ms | Default quiet and maximum snapshot delays. |
18
+ | `reconcileOnStart` | `true` | Scan the source when the backend starts. |
19
+ | `watch` | `true` | Watch sources that implement `watch()`. |
20
+ | `reconcileDebounce` | `200` ms | Quiet period before external changes are scanned. |
21
+ | `logger` | silent | A `loglayer` logger. |
22
+
23
+ A handler may override either default snapshot delay. See
24
+ [Asset kinds](./AssetKinds.md#snapshot-policy).
25
+
26
+ ## Returned backend
27
+
28
+ ```ts
29
+ interface AssetBackend extends AsyncDisposable {
30
+ readonly source: AssetSource;
31
+ readonly eventStore: EventStore;
32
+ readonly kinds: AssetKindRegistry;
33
+ readonly writer: AssetWriter;
34
+ readonly catalog: CatalogProjection;
35
+
36
+ flush(assetId?: string): Promise<void>;
37
+ attach(server: Server, options?: { graceMs?: number }): () => void;
38
+ close(): Promise<void>;
39
+ }
40
+ ```
41
+
42
+ Use [`writer`](./AssetWriter.md) for asset mutations. `flush(assetId?)` waits
43
+ for pending snapshots and source writes for one asset, or for every pending
44
+ asset when the ID is omitted.
45
+
46
+ `attach(server)` registers the `asset-catalog` room and installs the dynamic
47
+ asset-room resolver. Its callback clears the resolver. Existing rooms and the
48
+ catalog room remain owned by the `Server` until `server.close()`.
49
+
50
+ Close the server before the backend so active asset rooms can flush while the
51
+ backend is still running. `close()` stops watching, flushes pending work and
52
+ releases backend subscriptions. It does not close the injected server or event
53
+ store. `[Symbol.asyncDispose]` calls `close()`.
54
+
55
+ The returned object also exposes `internals` for tests and hosts that need to
56
+ drive an individual stage. Normal application code should use `writer`,
57
+ `catalog`, `flush()` and `attach()`.
58
+
59
+ ## Workspace files
60
+
61
+ `createAssetBackend` manages these files under the source root:
62
+
63
+ ```text
64
+ .jollypixel/
65
+ assets.json
66
+ state.json
67
+ .gitignore
68
+ ```
69
+
70
+ Commit `assets.json` so paths keep the same asset IDs when a checkout has no
71
+ local event log. `state.json` stores machine-local projection positions and is
72
+ ignored by the generated `.gitignore`. The host chooses where the event store
73
+ is persisted.