@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.
- package/LICENSE +21 -0
- package/README.md +83 -0
- package/dist/catalog/CatalogExtension.d.ts +37 -0
- package/dist/catalog/CatalogExtension.d.ts.map +1 -0
- package/dist/catalog/CatalogExtension.js +82 -0
- package/dist/catalog/CatalogIdentitySidecar.d.ts +52 -0
- package/dist/catalog/CatalogIdentitySidecar.d.ts.map +1 -0
- package/dist/catalog/CatalogIdentitySidecar.js +132 -0
- package/dist/catalog/CatalogProjection.d.ts +32 -0
- package/dist/catalog/CatalogProjection.d.ts.map +1 -0
- package/dist/catalog/CatalogProjection.js +109 -0
- package/dist/catalog/httpHandler.d.ts +13 -0
- package/dist/catalog/httpHandler.d.ts.map +1 -0
- package/dist/catalog/httpHandler.js +27 -0
- package/dist/catalog/index.d.ts +9 -0
- package/dist/catalog/index.d.ts.map +1 -0
- package/dist/catalog/index.js +4 -0
- package/dist/constants.d.ts +9 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +13 -0
- package/dist/createAssetBackend.d.ts +79 -0
- package/dist/createAssetBackend.d.ts.map +1 -0
- package/dist/createAssetBackend.js +146 -0
- package/dist/events/AssetEvents.d.ts +54 -0
- package/dist/events/AssetEvents.d.ts.map +1 -0
- package/dist/events/AssetEvents.js +102 -0
- package/dist/events/AssetEvents.schema.d.ts +111 -0
- package/dist/events/AssetEvents.schema.d.ts.map +1 -0
- package/dist/events/AssetEvents.schema.js +75 -0
- package/dist/events/index.d.ts +3 -0
- package/dist/events/index.d.ts.map +1 -0
- package/dist/events/index.js +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -0
- package/dist/kinds/AssetKindHandler.d.ts +38 -0
- package/dist/kinds/AssetKindHandler.d.ts.map +1 -0
- package/dist/kinds/AssetKindHandler.js +1 -0
- package/dist/kinds/AssetKindRegistry.d.ts +14 -0
- package/dist/kinds/AssetKindRegistry.d.ts.map +1 -0
- package/dist/kinds/AssetKindRegistry.js +52 -0
- package/dist/kinds/errors/UnknownAssetKindError.d.ts +5 -0
- package/dist/kinds/errors/UnknownAssetKindError.d.ts.map +1 -0
- package/dist/kinds/errors/UnknownAssetKindError.js +8 -0
- package/dist/kinds/errors/index.d.ts +2 -0
- package/dist/kinds/errors/index.d.ts.map +1 -0
- package/dist/kinds/errors/index.js +1 -0
- package/dist/kinds/handlers/binary.d.ts +10 -0
- package/dist/kinds/handlers/binary.d.ts.map +1 -0
- package/dist/kinds/handlers/binary.js +31 -0
- package/dist/kinds/handlers/index.d.ts +5 -0
- package/dist/kinds/handlers/index.d.ts.map +1 -0
- package/dist/kinds/handlers/index.js +2 -0
- package/dist/kinds/handlers/texture.d.ts +12 -0
- package/dist/kinds/handlers/texture.d.ts.map +1 -0
- package/dist/kinds/handlers/texture.js +27 -0
- package/dist/kinds/index.d.ts +5 -0
- package/dist/kinds/index.d.ts.map +1 -0
- package/dist/kinds/index.js +3 -0
- package/dist/logger.d.ts +4 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +9 -0
- package/dist/plugins/vite.d.ts +36 -0
- package/dist/plugins/vite.d.ts.map +1 -0
- package/dist/plugins/vite.js +60 -0
- package/dist/rooms/AssetRoomExtension.d.ts +29 -0
- package/dist/rooms/AssetRoomExtension.d.ts.map +1 -0
- package/dist/rooms/AssetRoomExtension.js +50 -0
- package/dist/rooms/index.d.ts +5 -0
- package/dist/rooms/index.d.ts.map +1 -0
- package/dist/rooms/index.js +2 -0
- package/dist/rooms/registerAssetRooms.d.ts +21 -0
- package/dist/rooms/registerAssetRooms.d.ts.map +1 -0
- package/dist/rooms/registerAssetRooms.js +71 -0
- package/dist/static/contentTypes.d.ts +6 -0
- package/dist/static/contentTypes.d.ts.map +1 -0
- package/dist/static/contentTypes.js +27 -0
- package/dist/static/httpHandler.d.ts +24 -0
- package/dist/static/httpHandler.d.ts.map +1 -0
- package/dist/static/httpHandler.js +105 -0
- package/dist/static/index.d.ts +4 -0
- package/dist/static/index.d.ts.map +1 -0
- package/dist/static/index.js +2 -0
- package/dist/sync/AssetProjector.d.ts +33 -0
- package/dist/sync/AssetProjector.d.ts.map +1 -0
- package/dist/sync/AssetProjector.js +186 -0
- package/dist/sync/AssetStateStore.d.ts +28 -0
- package/dist/sync/AssetStateStore.d.ts.map +1 -0
- package/dist/sync/AssetStateStore.js +86 -0
- package/dist/sync/AssetWriter.d.ts +54 -0
- package/dist/sync/AssetWriter.d.ts.map +1 -0
- package/dist/sync/AssetWriter.js +149 -0
- package/dist/sync/ProjectionState.d.ts +33 -0
- package/dist/sync/ProjectionState.d.ts.map +1 -0
- package/dist/sync/ProjectionState.js +129 -0
- package/dist/sync/Reconciler.d.ts +36 -0
- package/dist/sync/Reconciler.d.ts.map +1 -0
- package/dist/sync/Reconciler.js +182 -0
- package/dist/sync/ReconciliationWatcher.d.ts +28 -0
- package/dist/sync/ReconciliationWatcher.d.ts.map +1 -0
- package/dist/sync/ReconciliationWatcher.js +86 -0
- package/dist/sync/SnapshotScheduler.d.ts +29 -0
- package/dist/sync/SnapshotScheduler.d.ts.map +1 -0
- package/dist/sync/SnapshotScheduler.js +136 -0
- package/dist/sync/foldProjection.d.ts +12 -0
- package/dist/sync/foldProjection.d.ts.map +1 -0
- package/dist/sync/foldProjection.js +32 -0
- package/dist/sync/index.d.ts +19 -0
- package/dist/sync/index.d.ts.map +1 -0
- package/dist/sync/index.js +9 -0
- package/dist/sync/matchRenames.d.ts +45 -0
- package/dist/sync/matchRenames.d.ts.map +1 -0
- package/dist/sync/matchRenames.js +85 -0
- package/dist/utils/TaskChain.d.ts +9 -0
- package/dist/utils/TaskChain.d.ts.map +1 -0
- package/dist/utils/TaskChain.js +14 -0
- package/dist/utils/contentHash.d.ts +2 -0
- package/dist/utils/contentHash.d.ts.map +1 -0
- package/dist/utils/contentHash.js +7 -0
- package/dist/utils/index.d.ts +3 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +2 -0
- package/dist/workspace/createAssetWorkspace.d.ts +71 -0
- package/dist/workspace/createAssetWorkspace.d.ts.map +1 -0
- package/dist/workspace/createAssetWorkspace.js +72 -0
- package/dist/workspace/index.d.ts +5 -0
- package/dist/workspace/index.d.ts.map +1 -0
- package/dist/workspace/index.js +2 -0
- package/dist/workspace/seedAssetSource.d.ts +5 -0
- package/dist/workspace/seedAssetSource.d.ts.map +1 -0
- package/dist/workspace/seedAssetSource.js +20 -0
- package/docs/AssetBackend.md +73 -0
- package/docs/AssetKinds.md +204 -0
- package/docs/AssetWriter.md +62 -0
- package/docs/Catalog.md +71 -0
- package/docs/Rooms.md +70 -0
- package/docs/Sync.md +146 -0
- package/docs/Workspace.md +172 -0
- 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 @@
|
|
|
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 @@
|
|
|
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,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,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.
|