@milaboratories/pl-middle-layer 1.67.5 → 1.68.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/dist/block_registry/registry-v2-provider.d.ts +1 -3
- package/dist/block_registry/registry-v2-provider.d.ts.map +1 -1
- package/dist/block_registry/registry.d.ts +5 -9
- package/dist/block_registry/registry.d.ts.map +1 -1
- package/dist/block_registry/template_provider.d.ts +3 -5
- package/dist/block_registry/template_provider.d.ts.map +1 -1
- package/dist/block_registry/watcher.d.ts +4 -6
- package/dist/block_registry/watcher.d.ts.map +1 -1
- package/dist/block_registry/well_known_registries.d.ts +3 -5
- package/dist/block_registry/well_known_registries.d.ts.map +1 -1
- package/dist/index.d.ts +6 -2
- package/dist/middle_layer/build_stamp.cjs +34 -0
- package/dist/middle_layer/build_stamp.cjs.map +1 -0
- package/dist/middle_layer/build_stamp.js +34 -0
- package/dist/middle_layer/build_stamp.js.map +1 -0
- package/dist/middle_layer/driver_kit.d.ts +3 -4
- package/dist/middle_layer/driver_kit.d.ts.map +1 -1
- package/dist/middle_layer/index.d.ts +3 -2
- package/dist/middle_layer/middle_layer.cjs +48 -0
- package/dist/middle_layer/middle_layer.cjs.map +1 -1
- package/dist/middle_layer/middle_layer.d.ts +23 -4
- package/dist/middle_layer/middle_layer.d.ts.map +1 -1
- package/dist/middle_layer/middle_layer.js +48 -0
- package/dist/middle_layer/middle_layer.js.map +1 -1
- package/dist/middle_layer/ops.cjs +8 -2
- package/dist/middle_layer/ops.cjs.map +1 -1
- package/dist/middle_layer/ops.d.ts +70 -26
- package/dist/middle_layer/ops.d.ts.map +1 -1
- package/dist/middle_layer/ops.js +8 -2
- package/dist/middle_layer/ops.js.map +1 -1
- package/dist/middle_layer/project.cjs +163 -8
- package/dist/middle_layer/project.cjs.map +1 -1
- package/dist/middle_layer/project.d.ts +56 -7
- package/dist/middle_layer/project.d.ts.map +1 -1
- package/dist/middle_layer/project.js +165 -11
- package/dist/middle_layer/project.js.map +1 -1
- package/dist/middle_layer/project_list.d.ts +5 -3
- package/dist/middle_layer/project_list.d.ts.map +1 -1
- package/dist/middle_layer/sharing_list.d.ts +3 -4
- package/dist/middle_layer/sharing_list.d.ts.map +1 -1
- package/dist/middle_layer/tree_snapshot_store.cjs +283 -0
- package/dist/middle_layer/tree_snapshot_store.cjs.map +1 -0
- package/dist/middle_layer/tree_snapshot_store.d.ts +143 -0
- package/dist/middle_layer/tree_snapshot_store.d.ts.map +1 -0
- package/dist/middle_layer/tree_snapshot_store.js +280 -0
- package/dist/middle_layer/tree_snapshot_store.js.map +1 -0
- package/dist/middle_layer/types.d.ts +1 -3
- package/dist/middle_layer/types.d.ts.map +1 -1
- package/dist/model/block_pack_spec.d.ts +13 -15
- package/dist/model/block_pack_spec.d.ts.map +1 -1
- package/dist/model/frontend.d.ts +1 -2
- package/dist/model/frontend.d.ts.map +1 -1
- package/dist/model/project_helper.d.ts +2 -4
- package/dist/model/project_helper.d.ts.map +1 -1
- package/dist/model/project_model.d.ts +12 -12
- package/dist/model/project_model.d.ts.map +1 -1
- package/dist/model/sharing_model.d.ts +24 -26
- package/dist/model/sharing_model.d.ts.map +1 -1
- package/dist/model/template_apply.d.ts +9 -8
- package/dist/model/template_apply.d.ts.map +1 -1
- package/dist/model/template_export.d.ts +2 -2
- package/dist/model/template_export.d.ts.map +1 -1
- package/dist/model/template_parser.d.ts +2 -4
- package/dist/model/template_parser.d.ts.map +1 -1
- package/dist/model/template_resolve.d.ts +10 -11
- package/dist/model/template_resolve.d.ts.map +1 -1
- package/dist/model/template_serializer.d.ts +6 -5
- package/dist/model/template_serializer.d.ts.map +1 -1
- package/dist/model/template_spec.d.ts +7 -9
- package/dist/model/template_spec.d.ts.map +1 -1
- package/dist/mutator/block-pack/block_pack.d.ts +2 -3
- package/dist/mutator/block-pack/block_pack.d.ts.map +1 -1
- package/dist/mutator/project.cjs +20 -3
- package/dist/mutator/project.cjs.map +1 -1
- package/dist/mutator/project.d.ts +4 -3
- package/dist/mutator/project.d.ts.map +1 -1
- package/dist/mutator/project.js +20 -3
- package/dist/mutator/project.js.map +1 -1
- package/dist/mutator/template/render_template.d.ts +1 -3
- package/dist/mutator/template/render_template.d.ts.map +1 -1
- package/dist/mutator/template/template_cache.d.ts +15 -16
- package/dist/mutator/template/template_cache.d.ts.map +1 -1
- package/dist/mutator/template/template_loading.d.ts +10 -12
- package/dist/mutator/template/template_loading.d.ts.map +1 -1
- package/dist/network_check/network_check.d.ts +3 -5
- package/dist/network_check/network_check.d.ts.map +1 -1
- package/dist/pool/data.d.ts +2 -4
- package/dist/pool/data.d.ts.map +1 -1
- package/dist/pool/driver.d.ts +6 -6
- package/dist/pool/driver.d.ts.map +1 -1
- package/dist/pool/index.d.ts +1 -0
- package/dist/pool/p_object_collection.d.ts +1 -3
- package/dist/pool/p_object_collection.d.ts.map +1 -1
- package/dist/pool/result_pool.d.ts +3 -1
- package/dist/worker/worker.d.ts +1 -1
- package/package.json +14 -14
- package/src/middle_layer/build_stamp.ts +36 -0
- package/src/middle_layer/index.ts +1 -0
- package/src/middle_layer/middle_layer.ts +77 -0
- package/src/middle_layer/ops.ts +46 -1
- package/src/middle_layer/project.ts +234 -14
- package/src/middle_layer/project_failsafe.test.ts +47 -0
- package/src/middle_layer/tree_snapshot_scenarios.test.ts +337 -0
- package/src/middle_layer/tree_snapshot_store.test.ts +331 -0
- package/src/middle_layer/tree_snapshot_store.ts +419 -0
- package/src/mutator/project.ts +27 -5
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import { TemporalSynchronizedTreeOps } from "./types.js";
|
|
2
2
|
import { PFrameDriverOps, ParquetCacheOps } from "../pool/driver.js";
|
|
3
|
+
import "../pool/index.js";
|
|
3
4
|
import { MiLogger } from "@milaboratories/ts-helpers";
|
|
4
5
|
import { DownloadBlobToURLDriverOps, DownloadDriverOps, DownloadUrlDriverOps, LocalStorageProjection, LogsStreamDriverOps, OpenFileDialogCallback, UploadDriverOps, VirtualLocalStorageSpec } from "@milaboratories/pl-drivers";
|
|
5
|
-
|
|
6
6
|
//#region src/middle_layer/ops.d.ts
|
|
7
7
|
/** Paths part of {@link DriverKitOps}. */
|
|
8
|
-
type DriverKitOpsPaths = {
|
|
9
|
-
/** Common root where to put downloaded blobs / downloaded blob cache */
|
|
10
|
-
readonly
|
|
8
|
+
export type DriverKitOpsPaths = {
|
|
9
|
+
/** Common root where to put downloaded blobs / downloaded blob cache */
|
|
10
|
+
readonly blobDownloadPath: string;
|
|
11
|
+
/** Common root for a cache for range queries. */
|
|
12
|
+
readonly blobDownloadRangesCachePath: string;
|
|
13
|
+
/** Common root where to put downloaded blobs with */
|
|
11
14
|
readonly downloadBlobToURLPath: string;
|
|
12
15
|
/**
|
|
13
16
|
* List of pl storages that have projections in local file system.
|
|
@@ -26,7 +29,8 @@ type DriverKitOpsPaths = {
|
|
|
26
29
|
* List of virtual storages that will allow homogeneous access to local FSs through LS API.
|
|
27
30
|
* If undefined, default list will be created.
|
|
28
31
|
* */
|
|
29
|
-
readonly virtualLocalStoragesOverride?: VirtualLocalStorageSpec[];
|
|
32
|
+
readonly virtualLocalStoragesOverride?: VirtualLocalStorageSpec[];
|
|
33
|
+
/** Path to the directory where pframes will spill temporary files and store materialized views */
|
|
30
34
|
readonly pframesSpillPath: string;
|
|
31
35
|
/**
|
|
32
36
|
* Path to the directory backing the persistent parquet blob cache. Unlike
|
|
@@ -36,7 +40,7 @@ type DriverKitOpsPaths = {
|
|
|
36
40
|
readonly parquetCachePath: string;
|
|
37
41
|
};
|
|
38
42
|
/** Options required to initialize full set of middle layer driver kit */
|
|
39
|
-
type DriverKitOpsSettings = {
|
|
43
|
+
export type DriverKitOpsSettings = {
|
|
40
44
|
readonly logger: MiLogger;
|
|
41
45
|
/**
|
|
42
46
|
* Local secret, that is used to sign and verify different pieces of information
|
|
@@ -49,21 +53,24 @@ type DriverKitOpsSettings = {
|
|
|
49
53
|
* Settings related to the download driver making operations with blobs. This driver is also used
|
|
50
54
|
* to download logs when source process terminates and log terns into a blob
|
|
51
55
|
*/
|
|
52
|
-
readonly blobDriverOps: DownloadDriverOps;
|
|
56
|
+
readonly blobDriverOps: DownloadDriverOps;
|
|
57
|
+
/** Settings related to the frontend driver that downloads frontends. */
|
|
53
58
|
readonly frontendDownloadDriverOps: DownloadUrlDriverOps;
|
|
54
59
|
readonly downloadBlobToURLDriverOps: DownloadBlobToURLDriverOps;
|
|
55
60
|
/**
|
|
56
61
|
* Settings related to the upload driver that actually performs upload and helps render upload
|
|
57
62
|
* and indexing progresses from related pl resources.
|
|
58
63
|
* */
|
|
59
|
-
readonly uploadDriverOps: UploadDriverOps;
|
|
64
|
+
readonly uploadDriverOps: UploadDriverOps;
|
|
65
|
+
/** Settings related to the streaming log driver */
|
|
60
66
|
readonly logStreamDriverOps: LogsStreamDriverOps;
|
|
61
67
|
/**
|
|
62
68
|
* Callback to access system file open dialog, must be provided by the environment,
|
|
63
69
|
* to allow for {@link showOpenSingleFileDialog} / {@link showOpenMultipleFilesDialog}
|
|
64
70
|
* calls from the UI.
|
|
65
71
|
*/
|
|
66
|
-
readonly openFileDialogCallback: OpenFileDialogCallback;
|
|
72
|
+
readonly openFileDialogCallback: OpenFileDialogCallback;
|
|
73
|
+
/** Settings related to the PFrame driver */
|
|
67
74
|
readonly pFrameDriverOps: PFrameDriverOps;
|
|
68
75
|
/**
|
|
69
76
|
* Tuning for the persistent parquet blob cache. Its directory is
|
|
@@ -72,40 +79,77 @@ type DriverKitOpsSettings = {
|
|
|
72
79
|
*/
|
|
73
80
|
readonly parquetCacheOps: ParquetCacheOps;
|
|
74
81
|
};
|
|
75
|
-
type DriverKitOps = DriverKitOpsPaths & DriverKitOpsSettings;
|
|
82
|
+
export type DriverKitOps = DriverKitOpsPaths & DriverKitOpsSettings;
|
|
76
83
|
/** Some defaults fot MiddleLayerOps. */
|
|
77
|
-
declare const DefaultDriverKitOpsSettings: Pick<DriverKitOpsSettings, "logger" | "blobDriverOps" | "frontendDownloadDriverOps" | "downloadBlobToURLDriverOps" | "uploadDriverOps" | "logStreamDriverOps" | "pFrameDriverOps" | "parquetCacheOps">;
|
|
78
|
-
declare function DefaultDriverKitOpsPaths(workDir: string): Pick<DriverKitOpsPaths, "blobDownloadPath" | "blobDownloadRangesCachePath" | "downloadBlobToURLPath" | "pframesSpillPath" | "parquetCachePath">;
|
|
84
|
+
export declare const DefaultDriverKitOpsSettings: Pick<DriverKitOpsSettings, "logger" | "blobDriverOps" | "frontendDownloadDriverOps" | "downloadBlobToURLDriverOps" | "uploadDriverOps" | "logStreamDriverOps" | "pFrameDriverOps" | "parquetCacheOps">;
|
|
85
|
+
export declare function DefaultDriverKitOpsPaths(workDir: string): Pick<DriverKitOpsPaths, "blobDownloadPath" | "blobDownloadRangesCachePath" | "downloadBlobToURLPath" | "pframesSpillPath" | "parquetCachePath">;
|
|
79
86
|
/** Fields with default values are marked as optional here. */
|
|
80
|
-
type DriverKitOpsConstructor = Omit<DriverKitOpsSettings, keyof typeof DefaultDriverKitOpsSettings> & Partial<typeof DefaultDriverKitOpsSettings> & Omit<DriverKitOpsPaths, keyof ReturnType<typeof DefaultDriverKitOpsPaths>> & Partial<ReturnType<typeof DefaultDriverKitOpsPaths>>;
|
|
81
|
-
type MiddleLayerOpsPaths = DriverKitOpsPaths & {
|
|
82
|
-
/** Common root where to put frontend code. */
|
|
87
|
+
export type DriverKitOpsConstructor = Omit<DriverKitOpsSettings, keyof typeof DefaultDriverKitOpsSettings> & Partial<typeof DefaultDriverKitOpsSettings> & Omit<DriverKitOpsPaths, keyof ReturnType<typeof DefaultDriverKitOpsPaths>> & Partial<ReturnType<typeof DefaultDriverKitOpsPaths>>;
|
|
88
|
+
export type MiddleLayerOpsPaths = DriverKitOpsPaths & {
|
|
89
|
+
/** Common root where to put frontend code. */
|
|
90
|
+
readonly frontendDownloadPath: string;
|
|
91
|
+
/**
|
|
92
|
+
* Directory holding persisted project tree mirrors, one file per project. Like
|
|
93
|
+
* {@link DriverKitOpsPaths.parquetCachePath} and unlike the spill directories, it is NOT
|
|
94
|
+
* emptied on startup: surviving a restart is the entire point. It is pruned instead, see
|
|
95
|
+
* {@link TreeSnapshotOps.maxSizeBytes}.
|
|
96
|
+
*/
|
|
97
|
+
readonly treeSnapshotPath: string;
|
|
98
|
+
};
|
|
99
|
+
/** Tuning for the persisted project tree mirrors. Their directory is
|
|
100
|
+
* {@link MiddleLayerOpsPaths.treeSnapshotPath}; this carries the behaviour knobs. */
|
|
101
|
+
export type TreeSnapshotOps = {
|
|
102
|
+
/**
|
|
103
|
+
* Whether project tree mirrors are persisted and restored at all.
|
|
104
|
+
*
|
|
105
|
+
* On by default. This is an operational kill switch, for a deployment where the cache
|
|
106
|
+
* directory turns out to be unwritable or otherwise troublesome, not a rollout gate: the
|
|
107
|
+
* floor of the feature is current behaviour, since a cache that never hits is a cold open.
|
|
108
|
+
*/
|
|
109
|
+
readonly enabled: boolean;
|
|
110
|
+
/**
|
|
111
|
+
* Minimum wall-clock gap between periodic writes for one project.
|
|
112
|
+
*
|
|
113
|
+
* Can be generous, because a stale snapshot is less complete rather than wrong: final
|
|
114
|
+
* resources never change and are never refetched, so this only bounds how much recent work
|
|
115
|
+
* comes back from the non-final frontier on restore.
|
|
116
|
+
*/
|
|
117
|
+
readonly writeInterval: number;
|
|
118
|
+
/** Total bytes the snapshot directory may occupy after startup eviction. Needed because a
|
|
119
|
+
* heavy project runs to roughly ten megabytes. */
|
|
120
|
+
readonly maxSizeBytes: number;
|
|
83
121
|
};
|
|
84
122
|
/** Debug options for middle layer. */
|
|
85
|
-
type MiddleLayerDebugOptions = {
|
|
86
|
-
/** If true, will dump initial tree state to the file with root resource id as name. */
|
|
123
|
+
export type MiddleLayerDebugOptions = {
|
|
124
|
+
/** If true, will dump initial tree state to the file with root resource id as name. */
|
|
125
|
+
dumpInitialTreeState: boolean;
|
|
87
126
|
};
|
|
88
127
|
/** Configuration controlling different aspects of middle layer behaviour. */
|
|
89
|
-
type MiddleLayerOpsSettings = DriverKitOpsSettings & {
|
|
90
|
-
/** Debug options. */
|
|
128
|
+
export type MiddleLayerOpsSettings = DriverKitOpsSettings & {
|
|
129
|
+
/** Debug options. */
|
|
130
|
+
readonly debugOps: MiddleLayerDebugOptions;
|
|
91
131
|
/** Contain temporal options controlling how often should pl trees be
|
|
92
132
|
* synchronized with the pl server. */
|
|
93
133
|
readonly defaultTreeOptions: TemporalSynchronizedTreeOps;
|
|
94
134
|
/** Defines interval in milliseconds for running periodic project maintenance job.
|
|
95
135
|
* Project maintenance includes background staging rendering and cached outputs cleanup. */
|
|
96
|
-
readonly projectRefreshInterval: number;
|
|
97
|
-
|
|
136
|
+
readonly projectRefreshInterval: number;
|
|
137
|
+
/** How often to check for dev block updates */
|
|
138
|
+
readonly devBlockUpdateRecheckInterval: number;
|
|
139
|
+
/** Prioritize this channel if update is available in this block */
|
|
98
140
|
readonly preferredUpdateChannel?: string;
|
|
99
141
|
/** Default lifetime applied to a targeted shared envelope at creation, as
|
|
100
142
|
* `sharedAt + envelopeTtlMs`. Share-with-everybody envelopes never expire
|
|
101
143
|
* (`expiresAt: null`) and ignore this. */
|
|
102
144
|
readonly envelopeTtlMs: number;
|
|
145
|
+
/** Settings for persisting project tree mirrors to disk, so reopening a project transfers
|
|
146
|
+
* what changed rather than the tree again. */
|
|
147
|
+
readonly treeSnapshotOps: TreeSnapshotOps;
|
|
103
148
|
};
|
|
104
|
-
type MiddleLayerOps = MiddleLayerOpsSettings & MiddleLayerOpsPaths;
|
|
149
|
+
export type MiddleLayerOps = MiddleLayerOpsSettings & MiddleLayerOpsPaths;
|
|
105
150
|
/** Some defaults fot MiddleLayerOps. */
|
|
106
|
-
declare const DefaultMiddleLayerOpsSettings: Pick<MiddleLayerOps, keyof typeof DefaultDriverKitOpsSettings | "defaultTreeOptions" | "projectRefreshInterval" | "devBlockUpdateRecheckInterval" | "debugOps" | "envelopeTtlMs">;
|
|
107
|
-
declare function DefaultMiddleLayerOpsPaths(workDir: string): Pick<MiddleLayerOpsPaths, keyof ReturnType<typeof DefaultDriverKitOpsPaths> | "frontendDownloadPath">;
|
|
108
|
-
type MiddleLayerOpsConstructor = Omit<MiddleLayerOpsSettings, keyof typeof DefaultMiddleLayerOpsSettings> & Partial<typeof DefaultMiddleLayerOpsSettings> & Omit<MiddleLayerOpsPaths, keyof Awaited<ReturnType<typeof DefaultMiddleLayerOpsPaths>>> & Partial<Awaited<ReturnType<typeof DefaultMiddleLayerOpsPaths>>>;
|
|
151
|
+
export declare const DefaultMiddleLayerOpsSettings: Pick<MiddleLayerOps, keyof typeof DefaultDriverKitOpsSettings | "defaultTreeOptions" | "projectRefreshInterval" | "devBlockUpdateRecheckInterval" | "debugOps" | "envelopeTtlMs" | "treeSnapshotOps">;
|
|
152
|
+
export declare function DefaultMiddleLayerOpsPaths(workDir: string): Pick<MiddleLayerOpsPaths, keyof ReturnType<typeof DefaultDriverKitOpsPaths> | "frontendDownloadPath" | "treeSnapshotPath">;
|
|
153
|
+
export type MiddleLayerOpsConstructor = Omit<MiddleLayerOpsSettings, keyof typeof DefaultMiddleLayerOpsSettings> & Partial<typeof DefaultMiddleLayerOpsSettings> & Omit<MiddleLayerOpsPaths, keyof Awaited<ReturnType<typeof DefaultMiddleLayerOpsPaths>>> & Partial<Awaited<ReturnType<typeof DefaultMiddleLayerOpsPaths>>>;
|
|
109
154
|
//#endregion
|
|
110
|
-
export { DefaultDriverKitOpsPaths, DefaultDriverKitOpsSettings, DefaultMiddleLayerOpsPaths, DefaultMiddleLayerOpsSettings, DriverKitOps, DriverKitOpsConstructor, DriverKitOpsPaths, DriverKitOpsSettings, MiddleLayerDebugOptions, MiddleLayerOps, MiddleLayerOpsConstructor, MiddleLayerOpsPaths, MiddleLayerOpsSettings };
|
|
111
155
|
//# sourceMappingURL=ops.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ops.d.ts","names":[],"sources":["../../src/middle_layer/ops.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"ops.d.ts","names":[],"sources":["../../src/middle_layer/ops.ts"],"mappings":";;;;;;;YAiBY;;WAED;;WAGA;;WAGA;;;;;;;;;;;;;WAcA,kBAAkB;;;;;WAMlB,+BAA+B;;WAG/B;;;;;;WAOA;;;YAIC;WAKD,QAAQ;;;;;;;WAYR;;;;;WAUA,eAAe;;WAOf,2BAA2B;WAM3B,4BAA4B;;;;;WAU5B,iBAAiB;;WAQjB,oBAAoB;;;;;;WAWpB,wBAAwB;;WAOxB,iBAAiB;;;;;;WAOjB,iBAAiB;;YAGhB,eAAe,oBAAoB;;qBAGlC,6BAA6B,KACxC;wBA4Cc,yBACd,kBACC,KACD;;YAiBU,0BAA0B,KACpC,mCACa,+BAEb,eAAe,+BACf,KAAK,yBAAyB,kBAAkB,6BAChD,QAAQ,kBAAkB;YAEhB,sBAAsB;;WAEvB;;;;;;;WAQA;;;;YAKC;;;;;;;;WAQD;;;;;;;;WASA;;;WAIA;;;YAIC;;EAEV;;;YAIU,yBAAyB;;WAE1B,UAAU;;;WAIV,oBAAoB;;;WAIpB;;WAGA;;WAGA;;;;WAKA;;;WAIA,iBAAiB;;YAGhB,iBAAiB,yBAAyB;;qBAGzC,+BAA+B,KAC1C,6BACe;wBA2BD,2BACd,kBACC,KACD,2BACM,kBAAkB;YASd,4BAA4B,KACtC,qCACa,iCAEb,eAAe,iCACf,KAAK,2BAA2B,QAAQ,kBAAkB,gCAC1D,QAAQ,QAAQ,kBAAkB"}
|
package/dist/middle_layer/ops.js
CHANGED
|
@@ -58,12 +58,18 @@ const DefaultMiddleLayerOpsSettings = {
|
|
|
58
58
|
debugOps: { dumpInitialTreeState: false },
|
|
59
59
|
devBlockUpdateRecheckInterval: 1e3,
|
|
60
60
|
projectRefreshInterval: 2e3,
|
|
61
|
-
envelopeTtlMs: 336 * 3600 * 1e3
|
|
61
|
+
envelopeTtlMs: 336 * 3600 * 1e3,
|
|
62
|
+
treeSnapshotOps: {
|
|
63
|
+
enabled: true,
|
|
64
|
+
writeInterval: 300 * 1e3,
|
|
65
|
+
maxSizeBytes: 256 * 1024 * 1024
|
|
66
|
+
}
|
|
62
67
|
};
|
|
63
68
|
function DefaultMiddleLayerOpsPaths(workDir) {
|
|
64
69
|
return {
|
|
65
70
|
...DefaultDriverKitOpsPaths(workDir),
|
|
66
|
-
frontendDownloadPath: path.join(workDir, "frontend")
|
|
71
|
+
frontendDownloadPath: path.join(workDir, "frontend"),
|
|
72
|
+
treeSnapshotPath: path.join(workDir, "treeSnapshots")
|
|
67
73
|
};
|
|
68
74
|
}
|
|
69
75
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ops.js","names":[],"sources":["../../src/middle_layer/ops.ts"],"sourcesContent":["import type { TemporalSynchronizedTreeOps } from \"./types\";\nimport type {\n DownloadBlobToURLDriverOps,\n DownloadDriverOps,\n DownloadUrlDriverOps,\n OpenFileDialogCallback,\n VirtualLocalStorageSpec,\n} from \"@milaboratories/pl-drivers\";\nimport type { UploadDriverOps } from \"@milaboratories/pl-drivers\";\nimport type { LogsStreamDriverOps } from \"@milaboratories/pl-drivers\";\nimport type { MiLogger } from \"@milaboratories/ts-helpers\";\nimport { ConsoleLoggerAdapter } from \"@milaboratories/ts-helpers\";\nimport type { LocalStorageProjection } from \"@milaboratories/pl-drivers\";\nimport path from \"node:path\";\nimport { PFrameDriverOpsDefaults, type PFrameDriverOps, type ParquetCacheOps } from \"../pool\";\n\n/** Paths part of {@link DriverKitOps}. */\nexport type DriverKitOpsPaths = {\n /** Common root where to put downloaded blobs / downloaded blob cache */\n readonly blobDownloadPath: string;\n\n /** Common root for a cache for range queries. */\n readonly blobDownloadRangesCachePath: string;\n\n /** Common root where to put downloaded blobs with */\n readonly downloadBlobToURLPath: string;\n\n /**\n * List of pl storages that have projections in local file system.\n *\n * This option affect two drivers:\n *\n * (1) LS driver generates \"index\" handles instead of \"upload\" for paths inside those locations\n *\n * (2) Download driver directly serves content retrieval requests for blobs from listed storages,\n * and don't apply any caching for such blobs (i.e. preventing duplication of files for Downloaded\n * type handles, making OnDemand and Downloaded handles equivalent)\n *\n * */\n readonly localProjections: LocalStorageProjection[];\n\n /**\n * List of virtual storages that will allow homogeneous access to local FSs through LS API.\n * If undefined, default list will be created.\n * */\n readonly virtualLocalStoragesOverride?: VirtualLocalStorageSpec[];\n\n /** Path to the directory where pframes will spill temporary files and store materialized views */\n readonly pframesSpillPath: string;\n\n /**\n * Path to the directory backing the persistent parquet blob cache. Unlike\n * {@link pframesSpillPath} this directory is NOT emptied on startup - the cache\n * (and its lifetime counters) is meant to survive restarts.\n */\n readonly parquetCachePath: string;\n};\n\n/** Options required to initialize full set of middle layer driver kit */\nexport type DriverKitOpsSettings = {\n //\n // Common\n //\n\n readonly logger: MiLogger;\n\n //\n // Signer\n //\n\n /**\n * Local secret, that is used to sign and verify different pieces of information\n * that can be used to access local data, like local paths for ongoing uploads.\n *\n * Use {@link MiddleLayer.generateLocalSecret} to generate sufficiently random string.\n * */\n readonly localSecret: string;\n\n //\n // Blob Driver\n //\n\n /**\n * Settings related to the download driver making operations with blobs. This driver is also used\n * to download logs when source process terminates and log terns into a blob\n */\n readonly blobDriverOps: DownloadDriverOps;\n\n //\n // Frontend Driver\n //\n\n /** Settings related to the frontend driver that downloads frontends. */\n readonly frontendDownloadDriverOps: DownloadUrlDriverOps;\n\n //\n // Blob To URL Driver\n //\n\n readonly downloadBlobToURLDriverOps: DownloadBlobToURLDriverOps;\n\n //\n // Upload Driver\n //\n\n /**\n * Settings related to the upload driver that actually performs upload and helps render upload\n * and indexing progresses from related pl resources.\n * */\n readonly uploadDriverOps: UploadDriverOps;\n\n //\n // Log streaming ops\n // (static logs are served via the blob driver)\n //\n\n /** Settings related to the streaming log driver */\n readonly logStreamDriverOps: LogsStreamDriverOps;\n\n //\n // LS Driver\n //\n\n /**\n * Callback to access system file open dialog, must be provided by the environment,\n * to allow for {@link showOpenSingleFileDialog} / {@link showOpenMultipleFilesDialog}\n * calls from the UI.\n */\n readonly openFileDialogCallback: OpenFileDialogCallback;\n\n //\n // PFrame Driver\n //\n\n /** Settings related to the PFrame driver */\n readonly pFrameDriverOps: PFrameDriverOps;\n\n /**\n * Tuning for the persistent parquet blob cache. Its directory is\n * {@link DriverKitOpsPaths.parquetCachePath}; this carries only the\n * size/eviction knobs.\n */\n readonly parquetCacheOps: ParquetCacheOps;\n};\n\nexport type DriverKitOps = DriverKitOpsPaths & DriverKitOpsSettings;\n\n/** Some defaults fot MiddleLayerOps. */\nexport const DefaultDriverKitOpsSettings: Pick<\n DriverKitOpsSettings,\n | \"logger\"\n | \"blobDriverOps\"\n | \"frontendDownloadDriverOps\"\n | \"downloadBlobToURLDriverOps\"\n | \"uploadDriverOps\"\n | \"logStreamDriverOps\"\n | \"pFrameDriverOps\"\n | \"parquetCacheOps\"\n> = {\n logger: new ConsoleLoggerAdapter(),\n blobDriverOps: {\n cacheSoftSizeBytes: 8 * 1024 * 1024 * 1024, // 8 GB\n rangesCacheMaxSizeBytes: 8 * 1024 * 1024 * 1024, // 8 GB\n nConcurrentDownloads: 10,\n },\n frontendDownloadDriverOps: {\n cacheSoftSizeBytes: 1 * 1024 * 1024 * 1024, // 1 GB\n withGunzip: true,\n nConcurrentDownloads: 50,\n },\n downloadBlobToURLDriverOps: {\n cacheSoftSizeBytes: 1 * 1024 * 1024 * 1024, // 1 GB\n nConcurrentDownloads: 10,\n },\n uploadDriverOps: {\n nConcurrentPartUploads: 10,\n nConcurrentGetProgresses: 10,\n pollingInterval: 1000,\n stopPollingDelay: 1000,\n },\n logStreamDriverOps: {\n nConcurrentGetLogs: 10,\n pollingInterval: 1000,\n stopPollingDelay: 1000,\n },\n pFrameDriverOps: PFrameDriverOpsDefaults,\n parquetCacheOps: {\n maxSizeBytes: 8 * 1024 * 1024 * 1024, // 8 GB\n admissionFraction: 0.2, // a single file may occupy at most 20% of the budget\n maxFilesTracked: 50_000, // ghost (previously-evicted) files remembered to guide readmission\n },\n};\n\nexport function DefaultDriverKitOpsPaths(\n workDir: string,\n): Pick<\n DriverKitOpsPaths,\n | \"blobDownloadPath\"\n | \"blobDownloadRangesCachePath\"\n | \"downloadBlobToURLPath\"\n | \"pframesSpillPath\"\n | \"parquetCachePath\"\n> {\n return {\n blobDownloadPath: path.join(workDir, \"download\"),\n blobDownloadRangesCachePath: path.join(workDir, \"downloadRangesCache\"),\n downloadBlobToURLPath: path.join(workDir, \"downloadToURL\"),\n pframesSpillPath: path.join(workDir, \"pframes\"),\n parquetCachePath: path.join(workDir, \"parquetCache\"),\n };\n}\n\n/** Fields with default values are marked as optional here. */\nexport type DriverKitOpsConstructor = Omit<\n DriverKitOpsSettings,\n keyof typeof DefaultDriverKitOpsSettings\n> &\n Partial<typeof DefaultDriverKitOpsSettings> &\n Omit<DriverKitOpsPaths, keyof ReturnType<typeof DefaultDriverKitOpsPaths>> &\n Partial<ReturnType<typeof DefaultDriverKitOpsPaths>>;\n\nexport type MiddleLayerOpsPaths = DriverKitOpsPaths & {\n /** Common root where to put frontend code. */\n readonly frontendDownloadPath: string;\n};\n\n/** Debug options for middle layer. */\nexport type MiddleLayerDebugOptions = {\n /** If true, will dump initial tree state to the file with root resource id as name. */\n dumpInitialTreeState: boolean;\n};\n\n/** Configuration controlling different aspects of middle layer behaviour. */\nexport type MiddleLayerOpsSettings = DriverKitOpsSettings & {\n /** Debug options. */\n readonly debugOps: MiddleLayerDebugOptions;\n\n /** Contain temporal options controlling how often should pl trees be\n * synchronized with the pl server. */\n readonly defaultTreeOptions: TemporalSynchronizedTreeOps;\n\n /** Defines interval in milliseconds for running periodic project maintenance job.\n * Project maintenance includes background staging rendering and cached outputs cleanup. */\n readonly projectRefreshInterval: number;\n\n /** How often to check for dev block updates */\n readonly devBlockUpdateRecheckInterval: number;\n\n /** Prioritize this channel if update is available in this block */\n readonly preferredUpdateChannel?: string;\n\n /** Default lifetime applied to a targeted shared envelope at creation, as\n * `sharedAt + envelopeTtlMs`. Share-with-everybody envelopes never expire\n * (`expiresAt: null`) and ignore this. */\n readonly envelopeTtlMs: number;\n};\n\nexport type MiddleLayerOps = MiddleLayerOpsSettings & MiddleLayerOpsPaths;\n\n/** Some defaults fot MiddleLayerOps. */\nexport const DefaultMiddleLayerOpsSettings: Pick<\n MiddleLayerOps,\n | keyof typeof DefaultDriverKitOpsSettings\n | \"defaultTreeOptions\"\n | \"projectRefreshInterval\"\n | \"devBlockUpdateRecheckInterval\"\n | \"debugOps\"\n | \"envelopeTtlMs\"\n> = {\n ...DefaultDriverKitOpsSettings,\n defaultTreeOptions: {\n pollingInterval: 200,\n stopPollingDelay: 2500,\n initialTreeLoadingTimeout: 100 * 60 * 60 * 1000, // disable timeout for loading project tree (100 hours)\n },\n debugOps: {\n dumpInitialTreeState: false,\n },\n devBlockUpdateRecheckInterval: 1000,\n projectRefreshInterval: 2000,\n envelopeTtlMs: 14 * 24 * 3600 * 1000, // 14 days\n};\n\nexport function DefaultMiddleLayerOpsPaths(\n workDir: string,\n): Pick<\n MiddleLayerOpsPaths,\n keyof ReturnType<typeof DefaultDriverKitOpsPaths> | \"frontendDownloadPath\"\n> {\n return {\n ...DefaultDriverKitOpsPaths(workDir),\n frontendDownloadPath: path.join(workDir, \"frontend\"),\n };\n}\n\nexport type MiddleLayerOpsConstructor = Omit<\n MiddleLayerOpsSettings,\n keyof typeof DefaultMiddleLayerOpsSettings\n> &\n Partial<typeof DefaultMiddleLayerOpsSettings> &\n Omit<MiddleLayerOpsPaths, keyof Awaited<ReturnType<typeof DefaultMiddleLayerOpsPaths>>> &\n Partial<Awaited<ReturnType<typeof DefaultMiddleLayerOpsPaths>>>;\n"],"mappings":";;;;;;AAoJA,MAAa,8BAUT;CACF,QAAQ,IAAI,qBAAqB;CACjC,eAAe;EACb,oBAAoB,IAAI,OAAO,OAAO;EACtC,yBAAyB,IAAI,OAAO,OAAO;EAC3C,sBAAsB;CACxB;CACA,2BAA2B;EACzB,oBAAoB,IAAI,OAAO,OAAO;EACtC,YAAY;EACZ,sBAAsB;CACxB;CACA,4BAA4B;EAC1B,oBAAoB,IAAI,OAAO,OAAO;EACtC,sBAAsB;CACxB;CACA,iBAAiB;EACf,wBAAwB;EACxB,0BAA0B;EAC1B,iBAAiB;EACjB,kBAAkB;CACpB;CACA,oBAAoB;EAClB,oBAAoB;EACpB,iBAAiB;EACjB,kBAAkB;CACpB;CACA,iBAAiB;CACjB,iBAAiB;EACf,cAAc,IAAI,OAAO,OAAO;EAChC,mBAAmB;EACnB,iBAAiB;CACnB;AACF;AAEA,SAAgB,yBACd,SAQA;CACA,OAAO;EACL,kBAAkB,KAAK,KAAK,SAAS,UAAU;EAC/C,6BAA6B,KAAK,KAAK,SAAS,qBAAqB;EACrE,uBAAuB,KAAK,KAAK,SAAS,eAAe;EACzD,kBAAkB,KAAK,KAAK,SAAS,SAAS;EAC9C,kBAAkB,KAAK,KAAK,SAAS,cAAc;CACrD;AACF;;AAkDA,MAAa,gCAQT;CACF,GAAG;CACH,oBAAoB;EAClB,iBAAiB;EACjB,kBAAkB;EAClB,2BAA2B,MAAW,KAAK;CAC7C;CACA,UAAU,EACR,sBAAsB,MACxB;CACA,+BAA+B;CAC/B,wBAAwB;CACxB,eAAe,MAAU,OAAO;AAClC;AAEA,SAAgB,2BACd,SAIA;CACA,OAAO;EACL,GAAG,yBAAyB,OAAO;EACnC,sBAAsB,KAAK,KAAK,SAAS,UAAU;CACrD;AACF"}
|
|
1
|
+
{"version":3,"file":"ops.js","names":[],"sources":["../../src/middle_layer/ops.ts"],"sourcesContent":["import type { TemporalSynchronizedTreeOps } from \"./types\";\nimport type {\n DownloadBlobToURLDriverOps,\n DownloadDriverOps,\n DownloadUrlDriverOps,\n OpenFileDialogCallback,\n VirtualLocalStorageSpec,\n} from \"@milaboratories/pl-drivers\";\nimport type { UploadDriverOps } from \"@milaboratories/pl-drivers\";\nimport type { LogsStreamDriverOps } from \"@milaboratories/pl-drivers\";\nimport type { MiLogger } from \"@milaboratories/ts-helpers\";\nimport { ConsoleLoggerAdapter } from \"@milaboratories/ts-helpers\";\nimport type { LocalStorageProjection } from \"@milaboratories/pl-drivers\";\nimport path from \"node:path\";\nimport { PFrameDriverOpsDefaults, type PFrameDriverOps, type ParquetCacheOps } from \"../pool\";\n\n/** Paths part of {@link DriverKitOps}. */\nexport type DriverKitOpsPaths = {\n /** Common root where to put downloaded blobs / downloaded blob cache */\n readonly blobDownloadPath: string;\n\n /** Common root for a cache for range queries. */\n readonly blobDownloadRangesCachePath: string;\n\n /** Common root where to put downloaded blobs with */\n readonly downloadBlobToURLPath: string;\n\n /**\n * List of pl storages that have projections in local file system.\n *\n * This option affect two drivers:\n *\n * (1) LS driver generates \"index\" handles instead of \"upload\" for paths inside those locations\n *\n * (2) Download driver directly serves content retrieval requests for blobs from listed storages,\n * and don't apply any caching for such blobs (i.e. preventing duplication of files for Downloaded\n * type handles, making OnDemand and Downloaded handles equivalent)\n *\n * */\n readonly localProjections: LocalStorageProjection[];\n\n /**\n * List of virtual storages that will allow homogeneous access to local FSs through LS API.\n * If undefined, default list will be created.\n * */\n readonly virtualLocalStoragesOverride?: VirtualLocalStorageSpec[];\n\n /** Path to the directory where pframes will spill temporary files and store materialized views */\n readonly pframesSpillPath: string;\n\n /**\n * Path to the directory backing the persistent parquet blob cache. Unlike\n * {@link pframesSpillPath} this directory is NOT emptied on startup - the cache\n * (and its lifetime counters) is meant to survive restarts.\n */\n readonly parquetCachePath: string;\n};\n\n/** Options required to initialize full set of middle layer driver kit */\nexport type DriverKitOpsSettings = {\n //\n // Common\n //\n\n readonly logger: MiLogger;\n\n //\n // Signer\n //\n\n /**\n * Local secret, that is used to sign and verify different pieces of information\n * that can be used to access local data, like local paths for ongoing uploads.\n *\n * Use {@link MiddleLayer.generateLocalSecret} to generate sufficiently random string.\n * */\n readonly localSecret: string;\n\n //\n // Blob Driver\n //\n\n /**\n * Settings related to the download driver making operations with blobs. This driver is also used\n * to download logs when source process terminates and log terns into a blob\n */\n readonly blobDriverOps: DownloadDriverOps;\n\n //\n // Frontend Driver\n //\n\n /** Settings related to the frontend driver that downloads frontends. */\n readonly frontendDownloadDriverOps: DownloadUrlDriverOps;\n\n //\n // Blob To URL Driver\n //\n\n readonly downloadBlobToURLDriverOps: DownloadBlobToURLDriverOps;\n\n //\n // Upload Driver\n //\n\n /**\n * Settings related to the upload driver that actually performs upload and helps render upload\n * and indexing progresses from related pl resources.\n * */\n readonly uploadDriverOps: UploadDriverOps;\n\n //\n // Log streaming ops\n // (static logs are served via the blob driver)\n //\n\n /** Settings related to the streaming log driver */\n readonly logStreamDriverOps: LogsStreamDriverOps;\n\n //\n // LS Driver\n //\n\n /**\n * Callback to access system file open dialog, must be provided by the environment,\n * to allow for {@link showOpenSingleFileDialog} / {@link showOpenMultipleFilesDialog}\n * calls from the UI.\n */\n readonly openFileDialogCallback: OpenFileDialogCallback;\n\n //\n // PFrame Driver\n //\n\n /** Settings related to the PFrame driver */\n readonly pFrameDriverOps: PFrameDriverOps;\n\n /**\n * Tuning for the persistent parquet blob cache. Its directory is\n * {@link DriverKitOpsPaths.parquetCachePath}; this carries only the\n * size/eviction knobs.\n */\n readonly parquetCacheOps: ParquetCacheOps;\n};\n\nexport type DriverKitOps = DriverKitOpsPaths & DriverKitOpsSettings;\n\n/** Some defaults fot MiddleLayerOps. */\nexport const DefaultDriverKitOpsSettings: Pick<\n DriverKitOpsSettings,\n | \"logger\"\n | \"blobDriverOps\"\n | \"frontendDownloadDriverOps\"\n | \"downloadBlobToURLDriverOps\"\n | \"uploadDriverOps\"\n | \"logStreamDriverOps\"\n | \"pFrameDriverOps\"\n | \"parquetCacheOps\"\n> = {\n logger: new ConsoleLoggerAdapter(),\n blobDriverOps: {\n cacheSoftSizeBytes: 8 * 1024 * 1024 * 1024, // 8 GB\n rangesCacheMaxSizeBytes: 8 * 1024 * 1024 * 1024, // 8 GB\n nConcurrentDownloads: 10,\n },\n frontendDownloadDriverOps: {\n cacheSoftSizeBytes: 1 * 1024 * 1024 * 1024, // 1 GB\n withGunzip: true,\n nConcurrentDownloads: 50,\n },\n downloadBlobToURLDriverOps: {\n cacheSoftSizeBytes: 1 * 1024 * 1024 * 1024, // 1 GB\n nConcurrentDownloads: 10,\n },\n uploadDriverOps: {\n nConcurrentPartUploads: 10,\n nConcurrentGetProgresses: 10,\n pollingInterval: 1000,\n stopPollingDelay: 1000,\n },\n logStreamDriverOps: {\n nConcurrentGetLogs: 10,\n pollingInterval: 1000,\n stopPollingDelay: 1000,\n },\n pFrameDriverOps: PFrameDriverOpsDefaults,\n parquetCacheOps: {\n maxSizeBytes: 8 * 1024 * 1024 * 1024, // 8 GB\n admissionFraction: 0.2, // a single file may occupy at most 20% of the budget\n maxFilesTracked: 50_000, // ghost (previously-evicted) files remembered to guide readmission\n },\n};\n\nexport function DefaultDriverKitOpsPaths(\n workDir: string,\n): Pick<\n DriverKitOpsPaths,\n | \"blobDownloadPath\"\n | \"blobDownloadRangesCachePath\"\n | \"downloadBlobToURLPath\"\n | \"pframesSpillPath\"\n | \"parquetCachePath\"\n> {\n return {\n blobDownloadPath: path.join(workDir, \"download\"),\n blobDownloadRangesCachePath: path.join(workDir, \"downloadRangesCache\"),\n downloadBlobToURLPath: path.join(workDir, \"downloadToURL\"),\n pframesSpillPath: path.join(workDir, \"pframes\"),\n parquetCachePath: path.join(workDir, \"parquetCache\"),\n };\n}\n\n/** Fields with default values are marked as optional here. */\nexport type DriverKitOpsConstructor = Omit<\n DriverKitOpsSettings,\n keyof typeof DefaultDriverKitOpsSettings\n> &\n Partial<typeof DefaultDriverKitOpsSettings> &\n Omit<DriverKitOpsPaths, keyof ReturnType<typeof DefaultDriverKitOpsPaths>> &\n Partial<ReturnType<typeof DefaultDriverKitOpsPaths>>;\n\nexport type MiddleLayerOpsPaths = DriverKitOpsPaths & {\n /** Common root where to put frontend code. */\n readonly frontendDownloadPath: string;\n\n /**\n * Directory holding persisted project tree mirrors, one file per project. Like\n * {@link DriverKitOpsPaths.parquetCachePath} and unlike the spill directories, it is NOT\n * emptied on startup: surviving a restart is the entire point. It is pruned instead, see\n * {@link TreeSnapshotOps.maxSizeBytes}.\n */\n readonly treeSnapshotPath: string;\n};\n\n/** Tuning for the persisted project tree mirrors. Their directory is\n * {@link MiddleLayerOpsPaths.treeSnapshotPath}; this carries the behaviour knobs. */\nexport type TreeSnapshotOps = {\n /**\n * Whether project tree mirrors are persisted and restored at all.\n *\n * On by default. This is an operational kill switch, for a deployment where the cache\n * directory turns out to be unwritable or otherwise troublesome, not a rollout gate: the\n * floor of the feature is current behaviour, since a cache that never hits is a cold open.\n */\n readonly enabled: boolean;\n\n /**\n * Minimum wall-clock gap between periodic writes for one project.\n *\n * Can be generous, because a stale snapshot is less complete rather than wrong: final\n * resources never change and are never refetched, so this only bounds how much recent work\n * comes back from the non-final frontier on restore.\n */\n readonly writeInterval: number;\n\n /** Total bytes the snapshot directory may occupy after startup eviction. Needed because a\n * heavy project runs to roughly ten megabytes. */\n readonly maxSizeBytes: number;\n};\n\n/** Debug options for middle layer. */\nexport type MiddleLayerDebugOptions = {\n /** If true, will dump initial tree state to the file with root resource id as name. */\n dumpInitialTreeState: boolean;\n};\n\n/** Configuration controlling different aspects of middle layer behaviour. */\nexport type MiddleLayerOpsSettings = DriverKitOpsSettings & {\n /** Debug options. */\n readonly debugOps: MiddleLayerDebugOptions;\n\n /** Contain temporal options controlling how often should pl trees be\n * synchronized with the pl server. */\n readonly defaultTreeOptions: TemporalSynchronizedTreeOps;\n\n /** Defines interval in milliseconds for running periodic project maintenance job.\n * Project maintenance includes background staging rendering and cached outputs cleanup. */\n readonly projectRefreshInterval: number;\n\n /** How often to check for dev block updates */\n readonly devBlockUpdateRecheckInterval: number;\n\n /** Prioritize this channel if update is available in this block */\n readonly preferredUpdateChannel?: string;\n\n /** Default lifetime applied to a targeted shared envelope at creation, as\n * `sharedAt + envelopeTtlMs`. Share-with-everybody envelopes never expire\n * (`expiresAt: null`) and ignore this. */\n readonly envelopeTtlMs: number;\n\n /** Settings for persisting project tree mirrors to disk, so reopening a project transfers\n * what changed rather than the tree again. */\n readonly treeSnapshotOps: TreeSnapshotOps;\n};\n\nexport type MiddleLayerOps = MiddleLayerOpsSettings & MiddleLayerOpsPaths;\n\n/** Some defaults fot MiddleLayerOps. */\nexport const DefaultMiddleLayerOpsSettings: Pick<\n MiddleLayerOps,\n | keyof typeof DefaultDriverKitOpsSettings\n | \"defaultTreeOptions\"\n | \"projectRefreshInterval\"\n | \"devBlockUpdateRecheckInterval\"\n | \"debugOps\"\n | \"envelopeTtlMs\"\n | \"treeSnapshotOps\"\n> = {\n ...DefaultDriverKitOpsSettings,\n defaultTreeOptions: {\n pollingInterval: 200,\n stopPollingDelay: 2500,\n initialTreeLoadingTimeout: 100 * 60 * 60 * 1000, // disable timeout for loading project tree (100 hours)\n },\n debugOps: {\n dumpInitialTreeState: false,\n },\n devBlockUpdateRecheckInterval: 1000,\n projectRefreshInterval: 2000,\n envelopeTtlMs: 14 * 24 * 3600 * 1000, // 14 days\n treeSnapshotOps: {\n enabled: true,\n writeInterval: 5 * 60 * 1000, // 5 minutes\n maxSizeBytes: 256 * 1024 * 1024, // 256 MB, roughly 25 heavy projects\n },\n};\n\nexport function DefaultMiddleLayerOpsPaths(\n workDir: string,\n): Pick<\n MiddleLayerOpsPaths,\n keyof ReturnType<typeof DefaultDriverKitOpsPaths> | \"frontendDownloadPath\" | \"treeSnapshotPath\"\n> {\n return {\n ...DefaultDriverKitOpsPaths(workDir),\n frontendDownloadPath: path.join(workDir, \"frontend\"),\n treeSnapshotPath: path.join(workDir, \"treeSnapshots\"),\n };\n}\n\nexport type MiddleLayerOpsConstructor = Omit<\n MiddleLayerOpsSettings,\n keyof typeof DefaultMiddleLayerOpsSettings\n> &\n Partial<typeof DefaultMiddleLayerOpsSettings> &\n Omit<MiddleLayerOpsPaths, keyof Awaited<ReturnType<typeof DefaultMiddleLayerOpsPaths>>> &\n Partial<Awaited<ReturnType<typeof DefaultMiddleLayerOpsPaths>>>;\n"],"mappings":";;;;;;AAoJA,MAAa,8BAUT;CACF,QAAQ,IAAI,qBAAqB;CACjC,eAAe;EACb,oBAAoB,IAAI,OAAO,OAAO;EACtC,yBAAyB,IAAI,OAAO,OAAO;EAC3C,sBAAsB;CACxB;CACA,2BAA2B;EACzB,oBAAoB,IAAI,OAAO,OAAO;EACtC,YAAY;EACZ,sBAAsB;CACxB;CACA,4BAA4B;EAC1B,oBAAoB,IAAI,OAAO,OAAO;EACtC,sBAAsB;CACxB;CACA,iBAAiB;EACf,wBAAwB;EACxB,0BAA0B;EAC1B,iBAAiB;EACjB,kBAAkB;CACpB;CACA,oBAAoB;EAClB,oBAAoB;EACpB,iBAAiB;EACjB,kBAAkB;CACpB;CACA,iBAAiB;CACjB,iBAAiB;EACf,cAAc,IAAI,OAAO,OAAO;EAChC,mBAAmB;EACnB,iBAAiB;CACnB;AACF;AAEA,SAAgB,yBACd,SAQA;CACA,OAAO;EACL,kBAAkB,KAAK,KAAK,SAAS,UAAU;EAC/C,6BAA6B,KAAK,KAAK,SAAS,qBAAqB;EACrE,uBAAuB,KAAK,KAAK,SAAS,eAAe;EACzD,kBAAkB,KAAK,KAAK,SAAS,SAAS;EAC9C,kBAAkB,KAAK,KAAK,SAAS,cAAc;CACrD;AACF;;AAwFA,MAAa,gCAST;CACF,GAAG;CACH,oBAAoB;EAClB,iBAAiB;EACjB,kBAAkB;EAClB,2BAA2B,MAAW,KAAK;CAC7C;CACA,UAAU,EACR,sBAAsB,MACxB;CACA,+BAA+B;CAC/B,wBAAwB;CACxB,eAAe,MAAU,OAAO;CAChC,iBAAiB;EACf,SAAS;EACT,eAAe,MAAS;EACxB,cAAc,MAAM,OAAO;CAC7B;AACF;AAEA,SAAgB,2BACd,SAIA;CACA,OAAO;EACL,GAAG,yBAAyB,OAAO;EACnC,sBAAsB,KAAK,KAAK,SAAS,UAAU;EACnD,kBAAkB,KAAK,KAAK,SAAS,eAAe;CACtD;AACF"}
|
|
@@ -44,14 +44,28 @@ var Project = class Project {
|
|
|
44
44
|
activeConfigs;
|
|
45
45
|
refreshLoopResult;
|
|
46
46
|
abortController = new AbortController();
|
|
47
|
+
/** Tree change generation as of the snapshot currently on disk, or -1 when this session has
|
|
48
|
+
* not written one. Compared against the tree's current generation to skip writing a mirror
|
|
49
|
+
* that has not moved, which is what makes a project left open and idle go quiet. */
|
|
50
|
+
snapshotGeneration;
|
|
51
|
+
/** When a snapshot was last attempted, for the periodic write's wall-clock gate.
|
|
52
|
+
*
|
|
53
|
+
* Zero, not the construction time, so the first write lands on the first maintenance pass
|
|
54
|
+
* after the tree has settled rather than a full interval later. Sessions shorter than one
|
|
55
|
+
* interval are the common case for a desktop app that is quit with a project still open, and
|
|
56
|
+
* seeding this to now would leave every one of them with nothing on disk. Set on every
|
|
57
|
+
* attempt, successful or not, so a persistently failing write retries at the interval rather
|
|
58
|
+
* than on every pass of the loop. */
|
|
59
|
+
lastSnapshotAt = 0;
|
|
47
60
|
get destroyed() {
|
|
48
61
|
return this.abortController.signal.aborted;
|
|
49
62
|
}
|
|
50
|
-
constructor(env, id, rid, projectTree) {
|
|
63
|
+
constructor(env, id, rid, projectTree, restoredFromSnapshot = false) {
|
|
51
64
|
this.env = env;
|
|
52
65
|
this.id = id;
|
|
53
66
|
this.rid = rid;
|
|
54
67
|
this.projectTree = projectTree;
|
|
68
|
+
this.snapshotGeneration = restoredFromSnapshot ? 0 : -1;
|
|
55
69
|
this.overview = require_project_overview.projectOverview(projectTree.entry(), this.navigationStates, env).withPreCalculatedValueTree();
|
|
56
70
|
this.overviewLight = require_project_overview_light.projectOverviewLight(projectTree.entry()).withPreCalculatedValueTree();
|
|
57
71
|
this.refreshLoopResult = this.refreshLoop();
|
|
@@ -63,6 +77,85 @@ var Project = class Project {
|
|
|
63
77
|
get projectLockId() {
|
|
64
78
|
return "project:" + this.id.toString();
|
|
65
79
|
}
|
|
80
|
+
/**
|
|
81
|
+
* Periodic snapshot write, carried on the maintenance loop rather than a timer of its own.
|
|
82
|
+
*
|
|
83
|
+
* Gated on the tree having changed since the last snapshot, so a project left open and idle
|
|
84
|
+
* writes once and then goes quiet, and on wall clock, so a project changing continuously
|
|
85
|
+
* writes at most once per interval.
|
|
86
|
+
*/
|
|
87
|
+
async maybeWriteSnapshot() {
|
|
88
|
+
const store = this.env.treeSnapshots;
|
|
89
|
+
if (store === void 0) return;
|
|
90
|
+
const generation = this.projectTree.changeGeneration;
|
|
91
|
+
if (generation === this.snapshotGeneration) return;
|
|
92
|
+
if (Date.now() - this.lastSnapshotAt < this.env.ops.treeSnapshotOps.writeInterval) return;
|
|
93
|
+
await this.writeSnapshot(store, generation);
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Starts the close-boundary snapshot and returns without waiting for the write.
|
|
97
|
+
*
|
|
98
|
+
* On top of the periodic write, since closing is a natural point to persist. Change-gated but
|
|
99
|
+
* not interval-gated: rewriting a mirror that has not moved is pure waste, but a mirror that
|
|
100
|
+
* has moved is worth keeping however recently the last write happened.
|
|
101
|
+
*
|
|
102
|
+
* The **capture is synchronous and happens here**, before the caller destroys the tree,
|
|
103
|
+
* because destroying it invalidates it and a later capture would be refused. Only the encode
|
|
104
|
+
* and the write are deferred: they are up to ten megabytes of work, and project switching
|
|
105
|
+
* should not wait for them. Deferring is safe only because a capture is a copy rather than a
|
|
106
|
+
* view of the tree.
|
|
107
|
+
*
|
|
108
|
+
* The returned promise never rejects. The caller is expected to keep it so it can be drained
|
|
109
|
+
* at shutdown, not to await it here.
|
|
110
|
+
*/
|
|
111
|
+
snapshotOnClose() {
|
|
112
|
+
const store = this.env.treeSnapshots;
|
|
113
|
+
if (store === void 0) return Promise.resolve();
|
|
114
|
+
const generation = this.projectTree.changeGeneration;
|
|
115
|
+
if (generation === this.snapshotGeneration) return Promise.resolve();
|
|
116
|
+
let snapshot;
|
|
117
|
+
try {
|
|
118
|
+
snapshot = this.projectTree.capture((0, _milaboratories_pl_client.parseSignedResourceId)(this.rid).signature);
|
|
119
|
+
} catch (e) {
|
|
120
|
+
this.env.logger.warn(new Error(`failed to capture tree snapshot for project ${this.id} on close`, { cause: e }));
|
|
121
|
+
return Promise.resolve();
|
|
122
|
+
}
|
|
123
|
+
this.lastSnapshotAt = Date.now();
|
|
124
|
+
const write = (this.snapshotInFlight ?? Promise.resolve()).then(async () => {
|
|
125
|
+
if (this.snapshotGeneration >= generation) return;
|
|
126
|
+
if (await store.write(this.rid, snapshot)) this.snapshotGeneration = generation;
|
|
127
|
+
});
|
|
128
|
+
this.snapshotInFlight = write.finally(() => {
|
|
129
|
+
this.snapshotInFlight = void 0;
|
|
130
|
+
});
|
|
131
|
+
return this.snapshotInFlight;
|
|
132
|
+
}
|
|
133
|
+
/** In-flight snapshot write, if any. Both triggers can fire close together (the close write
|
|
134
|
+
* lands while the loop is mid-write), and encoding ten megabytes twice for the same mirror
|
|
135
|
+
* is worth avoiding. */
|
|
136
|
+
snapshotInFlight;
|
|
137
|
+
/** Serializes writes, and skips one that the in-flight write has already made redundant. */
|
|
138
|
+
async writeSnapshot(store, generation) {
|
|
139
|
+
while (this.snapshotInFlight !== void 0) {
|
|
140
|
+
await this.snapshotInFlight;
|
|
141
|
+
if (this.snapshotGeneration >= generation) return;
|
|
142
|
+
}
|
|
143
|
+
this.snapshotInFlight = this.captureAndWrite(store, generation).finally(() => {
|
|
144
|
+
this.snapshotInFlight = void 0;
|
|
145
|
+
});
|
|
146
|
+
await this.snapshotInFlight;
|
|
147
|
+
}
|
|
148
|
+
/** Captures and writes, never throwing: a snapshot is an optimisation and must not fail
|
|
149
|
+
* whatever triggered it. */
|
|
150
|
+
async captureAndWrite(store, generation) {
|
|
151
|
+
this.lastSnapshotAt = Date.now();
|
|
152
|
+
try {
|
|
153
|
+
const snapshot = this.projectTree.capture((0, _milaboratories_pl_client.parseSignedResourceId)(this.rid).signature);
|
|
154
|
+
if (await store.write(this.rid, snapshot)) this.snapshotGeneration = generation;
|
|
155
|
+
} catch (e) {
|
|
156
|
+
this.env.logger.warn(new Error(`failed to capture tree snapshot for project ${this.id}`, { cause: e }));
|
|
157
|
+
}
|
|
158
|
+
}
|
|
66
159
|
async refreshLoop() {
|
|
67
160
|
let retryState;
|
|
68
161
|
while (!this.destroyed) try {
|
|
@@ -74,6 +167,7 @@ var Project = class Project {
|
|
|
74
167
|
});
|
|
75
168
|
await this.activeConfigs.getValue();
|
|
76
169
|
await (0, node_timers_promises.setTimeout)(this.env.ops.projectRefreshInterval, void 0, { signal: this.abortController.signal });
|
|
170
|
+
await this.maybeWriteSnapshot();
|
|
77
171
|
const overviewLight = await this.overviewLight.getValue();
|
|
78
172
|
const existingBlocks = new Set(overviewLight.listOfBlocks);
|
|
79
173
|
for (const blockId of this.blockComputables.keys()) if (!existingBlocks.has(blockId)) {
|
|
@@ -470,12 +564,7 @@ var Project = class Project {
|
|
|
470
564
|
static async init(env, id, rid) {
|
|
471
565
|
await require_migration.applyProjectMigrations(env.pl, rid);
|
|
472
566
|
await require_project.withProject(env.projectHelper, env.pl, rid, (_) => {}, { name: "init" });
|
|
473
|
-
const projectTree = await
|
|
474
|
-
...env.ops.defaultTreeOptions,
|
|
475
|
-
pruning: projectTreePruning(env.logger),
|
|
476
|
-
fieldFilter: projectTreeFieldFilter(),
|
|
477
|
-
traverseStopRules: projectTreeTraverseStopRules()
|
|
478
|
-
}, env.logger);
|
|
567
|
+
const { tree: projectTree, restored } = await loadProjectTree(env, rid);
|
|
479
568
|
if (env.ops.debugOps.dumpInitialTreeState) {
|
|
480
569
|
const state = projectTree.dumpState();
|
|
481
570
|
state.sort((a, b) => (b.data?.byteLength ?? 0) - (a.data?.byteLength ?? 0));
|
|
@@ -483,9 +572,74 @@ var Project = class Project {
|
|
|
483
572
|
await node_fs_promises.default.writeFile(`${(0, _milaboratories_pl_client.resourceIdToString)(rid)}.json`, stringifyForDump(state));
|
|
484
573
|
await node_fs_promises.default.writeFile(`${(0, _milaboratories_pl_client.resourceIdToString)(rid)}.stats.json`, stringifyForDump(stats));
|
|
485
574
|
}
|
|
486
|
-
return new Project(env, id, rid, projectTree);
|
|
575
|
+
return new Project(env, id, rid, projectTree, restored);
|
|
487
576
|
}
|
|
488
577
|
};
|
|
578
|
+
/**
|
|
579
|
+
* Opens the project tree, seeded from a persisted mirror when there is a usable one.
|
|
580
|
+
*
|
|
581
|
+
* Carries the fail-safe: if the restored tree fails its first refresh on authentication,
|
|
582
|
+
* permission or an inconsistency, the snapshot is deleted and the open is retried cold. Once,
|
|
583
|
+
* and only for that first refresh, so a genuinely dead session still surfaces as itself rather
|
|
584
|
+
* than being masked as a slow open.
|
|
585
|
+
*
|
|
586
|
+
* The fail-safe is what bounds every case the cache key does not cover: a rotated master
|
|
587
|
+
* secret, a revoked grant, a snapshot valid in itself but no longer matching what the backend
|
|
588
|
+
* will serve. Without it, an explicit-root tree propagates the refresh failure rather than
|
|
589
|
+
* healing, so the project would fail to open on every attempt until someone deleted the cache
|
|
590
|
+
* directory by hand.
|
|
591
|
+
*/
|
|
592
|
+
async function loadProjectTree(env, rid) {
|
|
593
|
+
const treeOps = {
|
|
594
|
+
...env.ops.defaultTreeOptions,
|
|
595
|
+
pruning: projectTreePruning(env.logger),
|
|
596
|
+
fieldFilter: projectTreeFieldFilter(),
|
|
597
|
+
traverseStopRules: projectTreeTraverseStopRules()
|
|
598
|
+
};
|
|
599
|
+
const cold = async () => ({
|
|
600
|
+
tree: await _milaboratories_pl_tree.SynchronizedTreeState.init(env.pl, rid, treeOps, env.logger),
|
|
601
|
+
restored: false
|
|
602
|
+
});
|
|
603
|
+
const store = env.treeSnapshots;
|
|
604
|
+
if (store === void 0) return await cold();
|
|
605
|
+
const snapshot = await store.read(rid);
|
|
606
|
+
if (!snapshot.ok) {
|
|
607
|
+
env.logger.info(`project tree opening cold, snapshot miss: ${snapshot.miss}`);
|
|
608
|
+
return await cold();
|
|
609
|
+
}
|
|
610
|
+
try {
|
|
611
|
+
const tree = await _milaboratories_pl_tree.SynchronizedTreeState.init(env.pl, rid, {
|
|
612
|
+
...treeOps,
|
|
613
|
+
restoreFrom: snapshot.tree
|
|
614
|
+
}, env.logger);
|
|
615
|
+
const restored = tree.wasRestoredFromSnapshot;
|
|
616
|
+
if (restored) store.noteRestored();
|
|
617
|
+
else env.logger.info("project tree opening cold: the snapshot was not applied");
|
|
618
|
+
return {
|
|
619
|
+
tree,
|
|
620
|
+
restored
|
|
621
|
+
};
|
|
622
|
+
} catch (e) {
|
|
623
|
+
env.logger.warn(new Error("restored project tree failed its first refresh, opening cold", { cause: e }));
|
|
624
|
+
if (isSnapshotFailsafeError(e)) await store.discard(rid);
|
|
625
|
+
return await cold();
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
/** The failures that implicate the snapshot rather than the link or the session: a rotated
|
|
629
|
+
* master secret, a revoked grant, or state the tree cannot reconcile. Only these delete the
|
|
630
|
+
* file; every other failure still falls back to a cold open, it just keeps the file.
|
|
631
|
+
*
|
|
632
|
+
* The cause chain is walked because a wrapper anywhere between the tree update and here would
|
|
633
|
+
* otherwise silently disarm the inconsistency arm. `isUnauthenticated` and `isPermissionDenied`
|
|
634
|
+
* do their own one-level unwrapping. */
|
|
635
|
+
function isSnapshotFailsafeError(e) {
|
|
636
|
+
if ((0, _milaboratories_pl_client.isUnauthenticated)(e) || (0, _milaboratories_pl_client.isPermissionDenied)(e)) return true;
|
|
637
|
+
for (let cause = e, depth = 0; cause !== void 0 && depth < 8; depth++) {
|
|
638
|
+
if (cause instanceof _milaboratories_pl_tree.TreeStateUpdateError) return true;
|
|
639
|
+
cause = cause?.cause;
|
|
640
|
+
}
|
|
641
|
+
return false;
|
|
642
|
+
}
|
|
489
643
|
function projectTreePruning(logger) {
|
|
490
644
|
return (r) => {
|
|
491
645
|
if (r.fields.length > 1e3) logger.warn(`resource with excessive field count: type=${r.type.name} id=${r.id} fields=${r.fields.length} names=[${r.fields.slice(0, 10).map((f) => f.name).join(", ")}, ...]`);
|
|
@@ -573,6 +727,7 @@ function throwIfMissingServerCapabilities(pl, requiredCapabilities) {
|
|
|
573
727
|
}
|
|
574
728
|
//#endregion
|
|
575
729
|
exports.Project = Project;
|
|
730
|
+
exports.isSnapshotFailsafeError = isSnapshotFailsafeError;
|
|
576
731
|
exports.projectTreeFieldFilter = projectTreeFieldFilter;
|
|
577
732
|
exports.projectTreePruning = projectTreePruning;
|
|
578
733
|
exports.projectTreeTraverseStopRules = projectTreeTraverseStopRules;
|