@embedpdf/core 3.0.0-next.14 → 3.0.0-next.15
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/index.cjs +879 -427
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +212 -292
- package/dist/index.d.ts +212 -292
- package/dist/index.js +797 -400
- package/dist/index.js.map +1 -1
- package/dist/settings-CMWFRjmQ.js +1837 -0
- package/dist/settings-CMWFRjmQ.js.map +1 -0
- package/dist/settings-maKtf237.cjs +2034 -0
- package/dist/settings-maKtf237.cjs.map +1 -0
- package/dist/testing.cjs +401 -0
- package/dist/testing.cjs.map +1 -0
- package/dist/testing.d.cts +94 -0
- package/dist/testing.d.ts +94 -0
- package/dist/testing.js +393 -0
- package/dist/testing.js.map +1 -0
- package/dist/types-t-DaU8VK.d.cts +1162 -0
- package/dist/types-t-DaU8VK.d.ts +1162 -0
- package/package.json +6 -2
package/dist/index.cjs
CHANGED
|
@@ -1,198 +1,40 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_settings = require("./settings-maKtf237.cjs");
|
|
2
3
|
let _embedpdf_engine_core_runtime = require("@embedpdf/engine-core/runtime");
|
|
3
|
-
//#region src/types.ts
|
|
4
|
-
/** Kernel-emitted document-lifecycle actions; plugins may react via `onAction`. */
|
|
5
|
-
const CORE_DOCUMENT_ADDED = "@@core/document-added";
|
|
6
|
-
const CORE_DOCUMENT_REMOVED = "@@core/document-removed";
|
|
7
|
-
const CORE_ACTIVE_CHANGED = "@@core/active-changed";
|
|
8
|
-
const CORE_ORDER_CHANGED = "@@core/order-changed";
|
|
9
|
-
/** A document's page registry was replaced by a mutation event (rotate/move/delete). */
|
|
10
|
-
const CORE_DOCUMENT_PAGES_UPDATED = "@@core/document-pages-updated";
|
|
11
|
-
/** A tab slot was reserved: `open()` was called; the document is loading. */
|
|
12
|
-
const CORE_DOCUMENT_OPENING = "@@core/document-opening";
|
|
13
|
-
/** The engine reports the document needs a password (`documents.unlock`). */
|
|
14
|
-
const CORE_DOCUMENT_LOCKED = "@@core/document-locked";
|
|
15
|
-
/** The open failed; the tab stays with `status: 'error'` until closed. */
|
|
16
|
-
const CORE_DOCUMENT_OPEN_FAILED = "@@core/document-open-failed";
|
|
17
|
-
/** Field-wise DocInfo equality — the ONE definition every adapter's reactive
|
|
18
|
-
* `docs` read keys on, so a new lifecycle field can never silently stop
|
|
19
|
-
* re-rendering one framework's tab bar. */
|
|
20
|
-
const docInfoEquals = (a, b) => a.id === b.id && a.name === b.name && a.status === b.status && a.pageCount === b.pageCount && a.passwordProvided === b.passwordProvided;
|
|
21
|
-
const docInfoListEquals = (a, b) => a === b || a.length === b.length && a.every((d, i) => docInfoEquals(d, b[i]));
|
|
22
|
-
/** Built-in token for the document registry capability (provided by the kernel). */
|
|
23
|
-
const DocumentsToken = { name: "documents" };
|
|
24
|
-
//#endregion
|
|
25
|
-
//#region src/store.ts
|
|
26
|
-
function createStore(report = console.error) {
|
|
27
|
-
let core = {
|
|
28
|
-
documents: {},
|
|
29
|
-
pending: {},
|
|
30
|
-
order: [],
|
|
31
|
-
activeId: null
|
|
32
|
-
};
|
|
33
|
-
const slices = {};
|
|
34
|
-
const reducers = {};
|
|
35
|
-
const changeListeners = /* @__PURE__ */ new Set();
|
|
36
|
-
const actionListeners = /* @__PURE__ */ new Set();
|
|
37
|
-
const guarded = (fn) => {
|
|
38
|
-
try {
|
|
39
|
-
fn();
|
|
40
|
-
} catch (error) {
|
|
41
|
-
report(error);
|
|
42
|
-
}
|
|
43
|
-
};
|
|
44
|
-
let emitting = false;
|
|
45
|
-
let pending = false;
|
|
46
|
-
const emitChange = () => {
|
|
47
|
-
if (emitting) {
|
|
48
|
-
pending = true;
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
51
|
-
emitting = true;
|
|
52
|
-
try {
|
|
53
|
-
do {
|
|
54
|
-
pending = false;
|
|
55
|
-
changeListeners.forEach((listener) => guarded(listener));
|
|
56
|
-
} while (pending);
|
|
57
|
-
} finally {
|
|
58
|
-
emitting = false;
|
|
59
|
-
}
|
|
60
|
-
};
|
|
61
|
-
const emitAction = (action) => actionListeners.forEach((listener) => guarded(() => listener(action)));
|
|
62
|
-
return {
|
|
63
|
-
registerSlice(key, reducer, initial) {
|
|
64
|
-
reducers[key] = reducer;
|
|
65
|
-
slices[key] = initial;
|
|
66
|
-
},
|
|
67
|
-
removeSlice(key) {
|
|
68
|
-
delete reducers[key];
|
|
69
|
-
delete slices[key];
|
|
70
|
-
emitChange();
|
|
71
|
-
},
|
|
72
|
-
getSlice: (key) => slices[key],
|
|
73
|
-
getCore: () => core,
|
|
74
|
-
getState: () => ({
|
|
75
|
-
core,
|
|
76
|
-
plugins: slices
|
|
77
|
-
}),
|
|
78
|
-
dispatchTo(key, action) {
|
|
79
|
-
const r = reducers[key];
|
|
80
|
-
if (!r) return;
|
|
81
|
-
const next = r(slices[key], action);
|
|
82
|
-
if (next !== slices[key]) {
|
|
83
|
-
slices[key] = next;
|
|
84
|
-
emitChange();
|
|
85
|
-
}
|
|
86
|
-
emitAction(action);
|
|
87
|
-
},
|
|
88
|
-
setCore(patch, action) {
|
|
89
|
-
core = {
|
|
90
|
-
...core,
|
|
91
|
-
...patch
|
|
92
|
-
};
|
|
93
|
-
emitChange();
|
|
94
|
-
emitAction(action);
|
|
95
|
-
},
|
|
96
|
-
subscribe(listener) {
|
|
97
|
-
changeListeners.add(listener);
|
|
98
|
-
return () => void changeListeners.delete(listener);
|
|
99
|
-
},
|
|
100
|
-
subscribeAction(listener) {
|
|
101
|
-
actionListeners.add(listener);
|
|
102
|
-
return () => void actionListeners.delete(listener);
|
|
103
|
-
},
|
|
104
|
-
destroy() {
|
|
105
|
-
core = {
|
|
106
|
-
documents: {},
|
|
107
|
-
pending: {},
|
|
108
|
-
order: [],
|
|
109
|
-
activeId: null
|
|
110
|
-
};
|
|
111
|
-
for (const key of Object.keys(slices)) delete slices[key];
|
|
112
|
-
for (const key of Object.keys(reducers)) delete reducers[key];
|
|
113
|
-
changeListeners.clear();
|
|
114
|
-
actionListeners.clear();
|
|
115
|
-
}
|
|
116
|
-
};
|
|
117
|
-
}
|
|
118
|
-
//#endregion
|
|
119
|
-
//#region src/context.ts
|
|
120
|
-
/** A plugin's slice key. Workspace plugins use their id; document-scoped plugins are per-document. */
|
|
121
|
-
const sliceKey = (pluginId, documentId) => documentId ? `${pluginId}::${documentId}` : pluginId;
|
|
122
|
-
/**
|
|
123
|
-
* Build the context a plugin sees. When `session` is given the context is bound
|
|
124
|
-
* to that document — `getState`/`dispatch` target its slice, `document()` returns
|
|
125
|
-
* it, and `get()` resolves document-scoped capabilities for it.
|
|
126
|
-
*/
|
|
127
|
-
function createPluginContext(services, plugin, session) {
|
|
128
|
-
const { engine, store } = services;
|
|
129
|
-
const documentId = session?.id;
|
|
130
|
-
const key = sliceKey(plugin.id, documentId);
|
|
131
|
-
const ownScope = session?.scope ?? services.workspaceScope;
|
|
132
|
-
return {
|
|
133
|
-
id: plugin.id,
|
|
134
|
-
engine,
|
|
135
|
-
documentId,
|
|
136
|
-
doc: session ? session.handle : services.documentHandle(void 0),
|
|
137
|
-
documentHandle: (requestedDocumentId) => requestedDocumentId ? services.documentHandle(requestedDocumentId) : session ? session.handle : services.documentHandle(void 0),
|
|
138
|
-
getState: () => store.getSlice(key),
|
|
139
|
-
dispatch: (action) => store.dispatchTo(key, action),
|
|
140
|
-
subscribe: store.subscribe,
|
|
141
|
-
core: store.getCore,
|
|
142
|
-
document: () => {
|
|
143
|
-
if (session) return store.getCore().documents[session.id] ?? session.stagedMeta;
|
|
144
|
-
const id = store.getCore().activeId;
|
|
145
|
-
return id ? store.getCore().documents[id] ?? null : null;
|
|
146
|
-
},
|
|
147
|
-
get: (token) => services.resolveCapability(token, documentId),
|
|
148
|
-
forDocument: (token, otherDocumentId) => services.resolveCapability(token, otherDocumentId),
|
|
149
|
-
tryGet: (token) => services.tryResolveCapability(token, documentId),
|
|
150
|
-
tryForDocument: (token, otherDocumentId) => services.tryResolveCapability(token, otherDocumentId),
|
|
151
|
-
cleanup: (teardown) => ownScope.defer(teardown)
|
|
152
|
-
};
|
|
153
|
-
}
|
|
154
|
-
/** A plugin context plus the side-effect primitives (watch / onAction / cleanup). */
|
|
155
|
-
function createEffectContext(services, plugin, session) {
|
|
156
|
-
const { store } = services;
|
|
157
|
-
const ownScope = session?.scope ?? services.workspaceScope;
|
|
158
|
-
return {
|
|
159
|
-
...createPluginContext(services, plugin, session),
|
|
160
|
-
watch: (select, handler, isEqual = Object.is) => {
|
|
161
|
-
let previous = select();
|
|
162
|
-
const unsubscribe = store.subscribe(() => {
|
|
163
|
-
const next = select();
|
|
164
|
-
if (!isEqual(previous, next)) {
|
|
165
|
-
const prior = previous;
|
|
166
|
-
previous = next;
|
|
167
|
-
handler(next, prior);
|
|
168
|
-
}
|
|
169
|
-
});
|
|
170
|
-
ownScope.defer(unsubscribe);
|
|
171
|
-
return unsubscribe;
|
|
172
|
-
},
|
|
173
|
-
onAction: (type, handler) => {
|
|
174
|
-
const unsubscribe = store.subscribeAction((action) => {
|
|
175
|
-
if (action.type === type) handler(action);
|
|
176
|
-
});
|
|
177
|
-
ownScope.defer(unsubscribe);
|
|
178
|
-
return unsubscribe;
|
|
179
|
-
}
|
|
180
|
-
};
|
|
181
|
-
}
|
|
182
|
-
//#endregion
|
|
183
4
|
//#region src/order.ts
|
|
184
5
|
/**
|
|
185
|
-
* Validate
|
|
186
|
-
* are initialised before dependents.
|
|
6
|
+
* Validate the composition (fail fast, before anything is constructed) and
|
|
7
|
+
* topologically sort plugins so dependencies are initialised before dependents.
|
|
8
|
+
*
|
|
9
|
+
* Rejected compositions, each with both names in the error:
|
|
10
|
+
* - the same plugin definition installed twice, or two definitions sharing an id;
|
|
11
|
+
* - two plugins providing the same capability token (no implicit last-wins —
|
|
12
|
+
* an intentional replacement is a composition-time choice, made by
|
|
13
|
+
* removing the old definition from the list);
|
|
14
|
+
* - a workspace plugin that `requires` a document-scoped token (it has no
|
|
15
|
+
* document to resolve it for; `ctx.forDocument()` is the explicit path);
|
|
16
|
+
* - a required token no plugin provides;
|
|
17
|
+
* - a dependency cycle.
|
|
187
18
|
*/
|
|
188
19
|
function planPlugins(plugins) {
|
|
20
|
+
const byId = /* @__PURE__ */ new Map();
|
|
189
21
|
const providerByToken = /* @__PURE__ */ new Map();
|
|
190
|
-
const scopeByToken = /* @__PURE__ */ new Map([[DocumentsToken, "workspace"]]);
|
|
191
|
-
for (const plugin of plugins)
|
|
192
|
-
|
|
193
|
-
|
|
22
|
+
const scopeByToken = /* @__PURE__ */ new Map([[require_settings.DocumentsToken, "workspace"]]);
|
|
23
|
+
for (const plugin of plugins) {
|
|
24
|
+
const prior = byId.get(plugin.id);
|
|
25
|
+
if (prior) throw new Error(prior === plugin ? `[kernel] plugin "${plugin.id}" is installed twice.` : `[kernel] two different plugins use the id "${plugin.id}".`);
|
|
26
|
+
byId.set(plugin.id, plugin);
|
|
27
|
+
if (plugin.token) {
|
|
28
|
+
const other = providerByToken.get(plugin.token);
|
|
29
|
+
if (other) throw new Error(`[kernel] capability "${plugin.token.name}" is provided by both "${other.id}" and "${plugin.id}".`);
|
|
30
|
+
providerByToken.set(plugin.token, plugin);
|
|
31
|
+
scopeByToken.set(plugin.token, plugin.scope ?? "workspace");
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
for (const plugin of plugins) for (const required of plugin.requires ?? []) {
|
|
35
|
+
if (required !== require_settings.DocumentsToken && !providerByToken.has(required)) throw new Error(`Plugin "${plugin.id}" requires capability "${required.name}", which no plugin provides` + (required.hint ? ` — ${required.hint}.` : "."));
|
|
36
|
+
if (plugin.scope !== "document" && providerByToken.get(required)?.scope === "document") throw new Error(`[kernel] workspace plugin "${plugin.id}" requires document-scoped capability "${required.name}"; resolve it per document with ctx.forDocument() and declare it as optional.`);
|
|
194
37
|
}
|
|
195
|
-
for (const plugin of plugins) for (const required of plugin.requires ?? []) if (required !== DocumentsToken && !providerByToken.has(required)) throw new Error(`Plugin "${plugin.id}" requires capability "${required.name}", which no plugin provides` + (required.hint ? ` — ${required.hint}.` : "."));
|
|
196
38
|
const ordered = [];
|
|
197
39
|
const status = /* @__PURE__ */ new Map();
|
|
198
40
|
const visit = (plugin) => {
|
|
@@ -215,49 +57,44 @@ function planPlugins(plugins) {
|
|
|
215
57
|
};
|
|
216
58
|
}
|
|
217
59
|
//#endregion
|
|
218
|
-
//#region src/
|
|
60
|
+
//#region src/viewer-settings.ts
|
|
61
|
+
/** The viewer's settings when the app gives none; the colors are the theming page's defaults. */
|
|
62
|
+
const VIEWER_DEFAULTS = Object.freeze({
|
|
63
|
+
identity: null,
|
|
64
|
+
scope: null,
|
|
65
|
+
accent: "#3858e9",
|
|
66
|
+
page: Object.freeze({
|
|
67
|
+
background: "#ffffff",
|
|
68
|
+
shadow: "0 6px 18px rgb(0 0 0 / 0.18)"
|
|
69
|
+
})
|
|
70
|
+
});
|
|
71
|
+
/** An identity is one person: a new one replaces the last instead of merging into it. */
|
|
72
|
+
const VIEWER_WHOLE_SETTINGS = ["identity"];
|
|
219
73
|
/**
|
|
220
|
-
*
|
|
221
|
-
*
|
|
222
|
-
*
|
|
74
|
+
* The viewer's settings as a framework's viewer component gives them: a setting left out is its
|
|
75
|
+
* default, and `page` fills in the side it leaves out. Every adapter's viewer starts the kernel
|
|
76
|
+
* with this and passes it again when its props change.
|
|
223
77
|
*/
|
|
224
|
-
|
|
225
|
-
constructor(what) {
|
|
226
|
-
super(`[kernel] cancelled: ${what}`);
|
|
227
|
-
this.name = "CancelledError";
|
|
228
|
-
}
|
|
229
|
-
};
|
|
230
|
-
const isCancelled = (error) => error instanceof CancelledError;
|
|
231
|
-
function createScope(report) {
|
|
232
|
-
const teardowns = [];
|
|
233
|
-
let disposal = null;
|
|
78
|
+
function viewerSettingsOf({ identity, scope, accent, page }) {
|
|
234
79
|
return {
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
},
|
|
242
|
-
dispose() {
|
|
243
|
-
return disposal ?? (disposal = (async () => {
|
|
244
|
-
while (teardowns.length) try {
|
|
245
|
-
await teardowns.pop()();
|
|
246
|
-
} catch (error) {
|
|
247
|
-
report(error);
|
|
248
|
-
}
|
|
249
|
-
})());
|
|
80
|
+
identity: identity ?? null,
|
|
81
|
+
scope: scope ?? null,
|
|
82
|
+
accent: accent ?? VIEWER_DEFAULTS.accent,
|
|
83
|
+
page: {
|
|
84
|
+
...VIEWER_DEFAULTS.page,
|
|
85
|
+
...page
|
|
250
86
|
}
|
|
251
87
|
};
|
|
252
88
|
}
|
|
253
89
|
//#endregion
|
|
254
90
|
//#region src/kernel.ts
|
|
91
|
+
const EMPTY_PAGES = Object.freeze([]);
|
|
255
92
|
/** The new page registry a document mutation carries, or null for events that
|
|
256
93
|
* don't change page structure (annotations, metadata). The snapshot is the
|
|
257
94
|
* same shape `pages.list()` returns, so callers swap it in directly. */
|
|
258
95
|
function layoutFromEvent(event) {
|
|
259
96
|
switch (event.type) {
|
|
260
|
-
case "pages.
|
|
97
|
+
case "pages.reordered":
|
|
261
98
|
case "pages.rotated":
|
|
262
99
|
case "pages.deleted":
|
|
263
100
|
case "pages.inserted": return event.layout;
|
|
@@ -265,24 +102,70 @@ function layoutFromEvent(event) {
|
|
|
265
102
|
}
|
|
266
103
|
}
|
|
267
104
|
const isDocumentScoped = (plugin) => plugin.scope === "document";
|
|
268
|
-
const initialStateOf = (plugin) =>
|
|
269
|
-
|
|
270
|
-
|
|
105
|
+
const initialStateOf = (plugin) => plugin.state?.();
|
|
106
|
+
/**
|
|
107
|
+
* A document's public fields, one object per registry entry: the entries are replaced, never
|
|
108
|
+
* changed, so the same entry gives the same object, and readers re-render only for their own.
|
|
109
|
+
*/
|
|
110
|
+
const infoByEntry = /* @__PURE__ */ new WeakMap();
|
|
111
|
+
function documentInfoOf(entry) {
|
|
112
|
+
let info = infoByEntry.get(entry);
|
|
113
|
+
if (!info) {
|
|
114
|
+
info = "pages" in entry ? readyInfo(entry) : pendingInfo(entry);
|
|
115
|
+
infoByEntry.set(entry, info);
|
|
116
|
+
}
|
|
117
|
+
return info;
|
|
118
|
+
}
|
|
119
|
+
const readyInfo = (meta) => Object.freeze({
|
|
271
120
|
id: meta.id,
|
|
272
121
|
name: meta.name,
|
|
273
122
|
status: "ready",
|
|
274
|
-
pageCount: meta.pageCount
|
|
123
|
+
pageCount: meta.pageCount,
|
|
124
|
+
hasUnsavedChanges: meta.hasUnsavedChanges
|
|
275
125
|
});
|
|
276
|
-
const
|
|
126
|
+
const pendingInfo = (meta) => Object.freeze({
|
|
277
127
|
id: meta.id,
|
|
278
128
|
name: meta.name,
|
|
279
129
|
status: meta.status,
|
|
280
130
|
pageCount: 0,
|
|
281
|
-
|
|
131
|
+
hasUnsavedChanges: false,
|
|
132
|
+
...meta.status === "locked" ? { passwordProvided: meta.passwordProvided ?? false } : {},
|
|
133
|
+
...meta.status === "error" && meta.error !== void 0 ? { error: require_settings.toPluginErrorInfo(require_settings.toPluginError("documents", meta.error)) } : {}
|
|
134
|
+
});
|
|
135
|
+
/** Whether an event is a change to the document, one a download would carry. */
|
|
136
|
+
const changesDocument = (event) => event.type !== "stream.desynced" && event.type !== "document.versioned" && event.type !== "signatures.prepared" && event.type !== "signatures.cancelled";
|
|
137
|
+
/** Download a URL source's file; its tab shows the failure when the server says no. */
|
|
138
|
+
async function fetchUrlSource(source, signal) {
|
|
139
|
+
const fetchFile = globalThis.fetch;
|
|
140
|
+
if (!fetchFile) throw new require_settings.PluginError("unsupported", "documents", `can't download ${source.url}: no fetch()`);
|
|
141
|
+
const response = await fetchFile(source.url, { signal });
|
|
142
|
+
if (!response.ok) throw new require_settings.PluginError(response.status === 404 ? "not-found" : "operation-failed", "documents", `couldn't download ${source.url} (HTTP ${response.status})`);
|
|
143
|
+
return {
|
|
144
|
+
kind: "bytes",
|
|
145
|
+
bytes: new Uint8Array(await response.arrayBuffer())
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
const isUrlSource = (source) => source.kind === "url";
|
|
149
|
+
const noLayer = () => new require_settings.PluginError("unsupported", "documents", "only the local engine gives a layer to download");
|
|
150
|
+
/**
|
|
151
|
+
* The documents capability as seen from inside one document's scope: every call whose document
|
|
152
|
+
* is optional uses that document instead of the active one. The rest is the registry itself.
|
|
153
|
+
*/
|
|
154
|
+
const documentsInScope = (documents, documentId) => ({
|
|
155
|
+
...documents,
|
|
156
|
+
get: (id = documentId) => documents.get(id),
|
|
157
|
+
download: (id = documentId, options) => documents.download(id, options),
|
|
158
|
+
downloadLayer: (id = documentId, options) => documents.downloadLayer(id, options),
|
|
159
|
+
canDownload: (id = documentId) => documents.canDownload(id),
|
|
160
|
+
canPrint: (id = documentId) => documents.canPrint(id),
|
|
161
|
+
listPages: (id = documentId) => documents.listPages(id),
|
|
162
|
+
getPage: (page, id = documentId) => documents.getPage(page, id),
|
|
163
|
+
getPageIndex: (ref, id = documentId) => documents.getPageIndex(ref, id),
|
|
164
|
+
getRevision: (id = documentId) => documents.getRevision(id)
|
|
282
165
|
});
|
|
283
166
|
/** The stable id an input implies, if it carries one ('bytes'/'layerBytes'/'id'). */
|
|
284
167
|
const idOfInput = (input) => "id" in input && typeof input.id === "string" ? input.id : null;
|
|
285
|
-
|
|
168
|
+
let instanceCounter = 0;
|
|
286
169
|
/** Engine rejections carry AbortError when close() aborts the live call;
|
|
287
170
|
* match structurally so test fakes with plain promises still work. */
|
|
288
171
|
const isAbortLike = (error) => error instanceof Error && error.name === "AbortError";
|
|
@@ -290,7 +173,10 @@ const isAbortLike = (error) => error instanceof Error && error.name === "AbortEr
|
|
|
290
173
|
* Assemble a kernel from an engine + plugins.
|
|
291
174
|
*
|
|
292
175
|
* planPlugins — validate dependencies, order them
|
|
293
|
-
* resolveCapability — workspace singletons
|
|
176
|
+
* resolveCapability — workspace singletons (or their view from a document's scope),
|
|
177
|
+
* or per-document instances built lazily
|
|
178
|
+
* settings — one store per plugin registration, built up front, shared
|
|
179
|
+
* by its instances and readable with no document
|
|
294
180
|
* document lifecycle — one DocumentSession per document: transactional open
|
|
295
181
|
* (publish-last), one idempotent close for every phase
|
|
296
182
|
* start / destroy — explicit status machine; destroy closes everything
|
|
@@ -298,14 +184,49 @@ const isAbortLike = (error) => error instanceof Error && error.name === "AbortEr
|
|
|
298
184
|
* The kernel closes every handle it opened; it never destroys the engine —
|
|
299
185
|
* ownership follows acquisition, and the engine was handed in by the caller.
|
|
300
186
|
*/
|
|
301
|
-
function createKernel(
|
|
302
|
-
const { engine, plugins } =
|
|
303
|
-
const report =
|
|
304
|
-
const store = createStore(report);
|
|
187
|
+
function createKernel(config) {
|
|
188
|
+
const { engine, plugins } = config;
|
|
189
|
+
const report = config.report ?? ((error) => console.error("[kernel]", error));
|
|
190
|
+
const store = require_settings.createStore(report);
|
|
305
191
|
const plan = planPlugins(plugins);
|
|
306
192
|
const documentScopedPlugins = plan.ordered.filter(isDocumentScoped);
|
|
307
193
|
const workspaceCapabilities = /* @__PURE__ */ new Map();
|
|
308
|
-
const
|
|
194
|
+
const workspaceLeases = /* @__PURE__ */ new Map();
|
|
195
|
+
const workspaceConnectors = /* @__PURE__ */ new Map();
|
|
196
|
+
const workspaceCancel = new AbortController();
|
|
197
|
+
const workspaceScope = require_settings.createScope(report);
|
|
198
|
+
/** How each workspace capability looks from inside a document's scope, by token. The
|
|
199
|
+
* documents capability is built in, so its view is declared here; plugins declare theirs. */
|
|
200
|
+
const inScopeByToken = /* @__PURE__ */ new Map([[require_settings.DocumentsToken, documentsInScope]]);
|
|
201
|
+
for (const plugin of plan.ordered) if (plugin.token && plugin.inScope && !isDocumentScoped(plugin)) inScopeByToken.set(plugin.token, plugin.inScope);
|
|
202
|
+
const opened = require_settings.createEventHook(report);
|
|
203
|
+
const openFailed = require_settings.createEventHook(report);
|
|
204
|
+
const locked = require_settings.createEventHook(report);
|
|
205
|
+
const closed = require_settings.createEventHook(report);
|
|
206
|
+
const activeChanged = require_settings.createEventHook(report);
|
|
207
|
+
const pagesChanged = require_settings.createEventHook(report);
|
|
208
|
+
const unsavedChangesChanged = require_settings.createEventHook(report);
|
|
209
|
+
workspaceScope.defer(() => {
|
|
210
|
+
for (const hook of [
|
|
211
|
+
opened,
|
|
212
|
+
openFailed,
|
|
213
|
+
locked,
|
|
214
|
+
closed,
|
|
215
|
+
activeChanged,
|
|
216
|
+
pagesChanged,
|
|
217
|
+
unsavedChangesChanged
|
|
218
|
+
]) hook.dispose();
|
|
219
|
+
});
|
|
220
|
+
/** Every core write goes through here so an active-tab change is observed exactly once. */
|
|
221
|
+
const setCore = (patch) => {
|
|
222
|
+
const before = store.getCore().activeId;
|
|
223
|
+
store.setCore(patch);
|
|
224
|
+
const after = store.getCore().activeId;
|
|
225
|
+
if (before !== after) activeChanged.emit({
|
|
226
|
+
documentId: after,
|
|
227
|
+
previousDocumentId: before
|
|
228
|
+
});
|
|
229
|
+
};
|
|
309
230
|
const sessions = /* @__PURE__ */ new Map();
|
|
310
231
|
let status = "created";
|
|
311
232
|
let startPromise = null;
|
|
@@ -314,18 +235,18 @@ function createKernel(opts) {
|
|
|
314
235
|
if (status === "failed" || status === "destroying" || status === "destroyed") throw new Error(`[kernel] ${what} on a ${status} kernel`);
|
|
315
236
|
};
|
|
316
237
|
const checkpoint = (session) => {
|
|
317
|
-
if (session.cancel.signal.aborted) throw new CancelledError(`closed while opening: ${session.id}`);
|
|
238
|
+
if (session.cancel.signal.aborted) throw new require_settings.CancelledError(`closed while opening: ${session.id}`);
|
|
318
239
|
};
|
|
319
240
|
/**
|
|
320
241
|
* Await an engine/network call under the session's cancellation:
|
|
321
242
|
* - the call is retained so close() can abort real worker-side work
|
|
322
243
|
* (`AbortablePromise`), and
|
|
323
|
-
* - the await
|
|
244
|
+
* - the await races the cancellation, so close()'s join never blocks on a
|
|
324
245
|
* call that cannot be aborted (a plain-promise engine, a stuck fetch).
|
|
325
246
|
* When cancellation wins but the call later lands anyway, `onLateResult`
|
|
326
247
|
* routes the result into the session scope — whose late-defer rule runs it
|
|
327
248
|
* immediately after disposal — so a late-arriving resource cannot leak.
|
|
328
|
-
* (Plugin inits are deliberately
|
|
249
|
+
* (Plugin inits are deliberately not raced: they are first-party code that
|
|
329
250
|
* close() joins to completion; only unbounded external waits are raced.)
|
|
330
251
|
*/
|
|
331
252
|
async function engineCall(session, call, onLateResult) {
|
|
@@ -334,14 +255,14 @@ function createKernel(opts) {
|
|
|
334
255
|
const signal = session.cancel.signal;
|
|
335
256
|
let onAbort;
|
|
336
257
|
const cancellation = new Promise((_, reject) => {
|
|
337
|
-
onAbort = () => reject(new CancelledError(`closed while opening: ${session.id}`));
|
|
258
|
+
onAbort = () => reject(new require_settings.CancelledError(`closed while opening: ${session.id}`));
|
|
338
259
|
if (signal.aborted) onAbort();
|
|
339
260
|
else signal.addEventListener("abort", onAbort, { once: true });
|
|
340
261
|
});
|
|
341
262
|
try {
|
|
342
263
|
return await Promise.race([call, cancellation]);
|
|
343
264
|
} catch (error) {
|
|
344
|
-
if (isCancelled(error)) call.then((value) => {
|
|
265
|
+
if (require_settings.isCancelled(error)) call.then((value) => {
|
|
345
266
|
if (onLateResult) session.scope.defer(() => onLateResult(value));
|
|
346
267
|
}, () => {});
|
|
347
268
|
throw error;
|
|
@@ -351,19 +272,38 @@ function createKernel(opts) {
|
|
|
351
272
|
}
|
|
352
273
|
}
|
|
353
274
|
function createSession(id, name) {
|
|
275
|
+
const cancel = new AbortController();
|
|
354
276
|
const session = {
|
|
355
277
|
id,
|
|
278
|
+
instanceId: `${id}#${++instanceCounter}`,
|
|
356
279
|
name,
|
|
280
|
+
source: null,
|
|
281
|
+
openOptions: void 0,
|
|
357
282
|
phase: "opening",
|
|
358
283
|
handle: null,
|
|
359
284
|
stagedMeta: null,
|
|
360
|
-
scope: createScope(report),
|
|
285
|
+
scope: require_settings.createScope(report),
|
|
361
286
|
operation: null,
|
|
362
287
|
engineOp: null,
|
|
363
|
-
cancel
|
|
288
|
+
cancel,
|
|
289
|
+
signal: cancel.signal,
|
|
364
290
|
capabilities: /* @__PURE__ */ new Map(),
|
|
291
|
+
building: /* @__PURE__ */ new Set(),
|
|
292
|
+
views: /* @__PURE__ */ new Map(),
|
|
293
|
+
connectors: /* @__PURE__ */ new Map(),
|
|
294
|
+
leases: /* @__PURE__ */ new Map(),
|
|
295
|
+
settleFlushes: /* @__PURE__ */ new Set(),
|
|
296
|
+
downloadWraps: [],
|
|
297
|
+
changes: require_settings.createDocumentChanges({
|
|
298
|
+
handle: () => session.handle,
|
|
299
|
+
notify: () => store.notify(),
|
|
300
|
+
report
|
|
301
|
+
}),
|
|
302
|
+
changeCount: 0,
|
|
365
303
|
close: () => closeSession(session)
|
|
366
304
|
};
|
|
305
|
+
session.settleFlushes.add(() => session.changes.settle());
|
|
306
|
+
session.scope.defer(() => session.changes.close());
|
|
367
307
|
return session;
|
|
368
308
|
}
|
|
369
309
|
/** One transition at a time per session (open, then possibly unlock). */
|
|
@@ -381,14 +321,15 @@ function createKernel(opts) {
|
|
|
381
321
|
const previousId = session.id;
|
|
382
322
|
if (previousId === nextId) return;
|
|
383
323
|
const core = store.getCore();
|
|
384
|
-
if (sessions.has(nextId) || core.documents[nextId] || core.pending[nextId]) throw new
|
|
324
|
+
if (sessions.has(nextId) || core.documents[nextId] || core.pending[nextId]) throw new require_settings.PluginError("conflict", "documents", `duplicate document id: ${nextId}`);
|
|
385
325
|
sessions.delete(previousId);
|
|
386
326
|
session.id = nextId;
|
|
387
327
|
sessions.set(nextId, session);
|
|
328
|
+
session.views.clear();
|
|
388
329
|
const slot = core.pending[previousId];
|
|
389
330
|
if (slot) {
|
|
390
331
|
const { [previousId]: _moved, ...pending } = core.pending;
|
|
391
|
-
|
|
332
|
+
setCore({
|
|
392
333
|
pending: {
|
|
393
334
|
...pending,
|
|
394
335
|
[nextId]: {
|
|
@@ -398,7 +339,7 @@ function createKernel(opts) {
|
|
|
398
339
|
},
|
|
399
340
|
order: core.order.map((id) => id === previousId ? nextId : id),
|
|
400
341
|
activeId: core.activeId === previousId ? nextId : core.activeId
|
|
401
|
-
}
|
|
342
|
+
});
|
|
402
343
|
}
|
|
403
344
|
}
|
|
404
345
|
const closingSessions = /* @__PURE__ */ new WeakMap();
|
|
@@ -408,14 +349,17 @@ function createKernel(opts) {
|
|
|
408
349
|
const closing = (async () => {
|
|
409
350
|
session.phase = "closing";
|
|
410
351
|
unpublishSlot(session.id);
|
|
411
|
-
|
|
352
|
+
for (const lease of session.leases.values()) lease.revoke();
|
|
353
|
+
const reason = new require_settings.CancelledError(`closed while opening: ${session.id}`);
|
|
412
354
|
session.cancel.abort(reason);
|
|
413
355
|
session.engineOp?.abort(reason);
|
|
414
356
|
await session.operation?.catch((error) => {
|
|
415
|
-
if (!isCancelled(error) && !isAbortLike(error)) report(error);
|
|
357
|
+
if (!require_settings.isCancelled(error) && !isAbortLike(error)) report(error);
|
|
416
358
|
});
|
|
417
359
|
await session.scope.dispose();
|
|
418
360
|
if (sessions.get(session.id) === session) sessions.delete(session.id);
|
|
361
|
+
session.views.clear();
|
|
362
|
+
closed.emit({ documentId: session.id });
|
|
419
363
|
})();
|
|
420
364
|
closingSessions.set(session, closing);
|
|
421
365
|
return closing;
|
|
@@ -428,7 +372,7 @@ function createKernel(opts) {
|
|
|
428
372
|
}
|
|
429
373
|
function publishPendingSlot(session, activate) {
|
|
430
374
|
const core = store.getCore();
|
|
431
|
-
|
|
375
|
+
setCore({
|
|
432
376
|
pending: {
|
|
433
377
|
...core.pending,
|
|
434
378
|
[session.id]: {
|
|
@@ -437,13 +381,13 @@ function createKernel(opts) {
|
|
|
437
381
|
status: "loading"
|
|
438
382
|
}
|
|
439
383
|
},
|
|
440
|
-
order: [...core.order, session.id],
|
|
384
|
+
order: core.order.includes(session.id) ? core.order : [...core.order, session.id],
|
|
441
385
|
activeId: activate || core.activeId === null ? session.id : core.activeId
|
|
442
|
-
}
|
|
386
|
+
});
|
|
443
387
|
}
|
|
444
388
|
function publishLocked(session, passwordProvided) {
|
|
445
389
|
const core = store.getCore();
|
|
446
|
-
|
|
390
|
+
setCore({ pending: {
|
|
447
391
|
...core.pending,
|
|
448
392
|
[session.id]: {
|
|
449
393
|
id: session.id,
|
|
@@ -451,11 +395,15 @@ function createKernel(opts) {
|
|
|
451
395
|
status: "locked",
|
|
452
396
|
passwordProvided
|
|
453
397
|
}
|
|
454
|
-
} }
|
|
398
|
+
} });
|
|
399
|
+
locked.emit({
|
|
400
|
+
documentId: session.id,
|
|
401
|
+
passwordProvided
|
|
402
|
+
});
|
|
455
403
|
}
|
|
456
404
|
function publishError(session, error) {
|
|
457
405
|
const core = store.getCore();
|
|
458
|
-
|
|
406
|
+
setCore({ pending: {
|
|
459
407
|
...core.pending,
|
|
460
408
|
[session.id]: {
|
|
461
409
|
id: session.id,
|
|
@@ -463,35 +411,70 @@ function createKernel(opts) {
|
|
|
463
411
|
status: "error",
|
|
464
412
|
error
|
|
465
413
|
}
|
|
466
|
-
} }
|
|
414
|
+
} });
|
|
415
|
+
openFailed.emit({
|
|
416
|
+
documentId: session.id,
|
|
417
|
+
error: require_settings.toPluginErrorInfo(require_settings.toPluginError("documents", error))
|
|
418
|
+
});
|
|
467
419
|
}
|
|
468
420
|
function unpublishSlot(id) {
|
|
469
421
|
const core = store.getCore();
|
|
470
422
|
if (!core.pending[id] && !core.documents[id]) return;
|
|
471
423
|
const { [id]: _pending, ...pending } = core.pending;
|
|
472
424
|
const { [id]: _document, ...documents } = core.documents;
|
|
473
|
-
|
|
425
|
+
setCore({
|
|
474
426
|
pending,
|
|
475
427
|
documents,
|
|
476
428
|
order: core.order.filter((other) => other !== id),
|
|
477
429
|
activeId: nextActiveDocument(core, id)
|
|
478
|
-
}
|
|
430
|
+
});
|
|
479
431
|
}
|
|
480
|
-
/** The
|
|
481
|
-
*
|
|
432
|
+
/** The one ready transition: swap the pending slot for the staged meta and
|
|
433
|
+
* announce `onOpened`. Everything before this is unpublished and rolls
|
|
482
434
|
* back by disposing the session scope; nothing after this can fail. */
|
|
483
435
|
function commitReady(session) {
|
|
484
436
|
session.phase = "ready";
|
|
485
437
|
const meta = session.stagedMeta;
|
|
486
438
|
const core = store.getCore();
|
|
487
439
|
const { [session.id]: _resolved, ...pending } = core.pending;
|
|
488
|
-
|
|
440
|
+
setCore({
|
|
489
441
|
documents: {
|
|
490
442
|
...core.documents,
|
|
491
443
|
[session.id]: meta
|
|
492
444
|
},
|
|
493
445
|
pending
|
|
494
|
-
}
|
|
446
|
+
});
|
|
447
|
+
opened.emit({
|
|
448
|
+
documentId: session.id,
|
|
449
|
+
info: documentInfoOf(meta)
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
/**
|
|
453
|
+
* The document changed, or a download read every change it had: set `hasUnsavedChanges` and
|
|
454
|
+
* announce a flip. Before the document is published, its staged meta carries the flag.
|
|
455
|
+
*/
|
|
456
|
+
function setUnsavedChanges(session, hasUnsavedChanges) {
|
|
457
|
+
const core = store.getCore();
|
|
458
|
+
const published = core.documents[session.id];
|
|
459
|
+
if (!published) {
|
|
460
|
+
if (session.stagedMeta && session.stagedMeta.hasUnsavedChanges !== hasUnsavedChanges) session.stagedMeta = {
|
|
461
|
+
...session.stagedMeta,
|
|
462
|
+
hasUnsavedChanges
|
|
463
|
+
};
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
if (published.hasUnsavedChanges === hasUnsavedChanges) return;
|
|
467
|
+
setCore({ documents: {
|
|
468
|
+
...core.documents,
|
|
469
|
+
[session.id]: {
|
|
470
|
+
...published,
|
|
471
|
+
hasUnsavedChanges
|
|
472
|
+
}
|
|
473
|
+
} });
|
|
474
|
+
unsavedChangesChanged.emit({
|
|
475
|
+
documentId: session.id,
|
|
476
|
+
hasUnsavedChanges
|
|
477
|
+
});
|
|
495
478
|
}
|
|
496
479
|
const sessionOf = (documentId) => {
|
|
497
480
|
const id = documentId ?? store.getCore().activeId;
|
|
@@ -504,18 +487,89 @@ function createKernel(opts) {
|
|
|
504
487
|
if (!session) return null;
|
|
505
488
|
return session.phase === "bringup" || session.phase === "ready" ? session.handle : null;
|
|
506
489
|
};
|
|
490
|
+
/** The session a per-document verb acts on: `documentId`, or the active document. */
|
|
491
|
+
function readySession(documentId, verb) {
|
|
492
|
+
const session = sessionOf(documentId);
|
|
493
|
+
if (!session || !documentHandle(session.id)) throw new require_settings.PluginError("not-ready", "documents", `no open document to ${verb}`);
|
|
494
|
+
return session;
|
|
495
|
+
}
|
|
496
|
+
const allowsOn = (documentId, permission) => {
|
|
497
|
+
const handle = documentHandle(documentId);
|
|
498
|
+
return handle ? require_settings.sessionAllows(handle.security, permission) : false;
|
|
499
|
+
};
|
|
500
|
+
/**
|
|
501
|
+
* Read the document's file, with everything the user sees: refuse without `doc.download`, let
|
|
502
|
+
* it settle (what its plugins held back is sent and answered, settle.ts), then read it inside
|
|
503
|
+
* every `aroundDownload` wrap (the document's own save actions). A read that saw every change
|
|
504
|
+
* the document had clears `hasUnsavedChanges`; a change during the read, or after it (a
|
|
505
|
+
* did-save script), keeps it. Closing the document, or `signal`, cancels it.
|
|
506
|
+
*/
|
|
507
|
+
async function downloadFile(documentId, signal, verb, read, refuse) {
|
|
508
|
+
const session = readySession(documentId, verb);
|
|
509
|
+
if (!allowsOn(session.id, "doc.download")) throw require_settings.permissionDenied("documents", "doc.download", `documents.${verb}`);
|
|
510
|
+
const refusal = refuse?.(session.handle);
|
|
511
|
+
if (refusal) throw refusal;
|
|
512
|
+
try {
|
|
513
|
+
await require_settings.settle(session.settleFlushes, [signal, session.signal], report);
|
|
514
|
+
const handle = documentHandle(session.id);
|
|
515
|
+
if (!handle) throw new require_settings.PluginError("not-ready", "documents", `no open document to ${verb}`);
|
|
516
|
+
let readAt = -1;
|
|
517
|
+
const readFile = () => {
|
|
518
|
+
readAt = session.changeCount;
|
|
519
|
+
return require_settings.cancellable("documents", signal, read(handle));
|
|
520
|
+
};
|
|
521
|
+
const bytes = await require_settings.readWrapped(session.downloadWraps, readFile);
|
|
522
|
+
if (readAt === session.changeCount) setUnsavedChanges(session, false);
|
|
523
|
+
return bytes;
|
|
524
|
+
} catch (error) {
|
|
525
|
+
throw require_settings.toPluginError("documents", error);
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
/** Whether the plugin's `create()` is running for this document: it has no capability yet. */
|
|
529
|
+
const isBuilding = (plugin, session) => session.building.has(plugin) && !session.capabilities.has(plugin);
|
|
507
530
|
function buildDocumentCapability(plugin, session) {
|
|
508
531
|
let capability = session.capabilities.get(plugin);
|
|
509
532
|
if (!capability) {
|
|
510
|
-
|
|
533
|
+
if (session.building.has(plugin)) throw new require_settings.PluginError("not-ready", plugin.id, `"${plugin.id}" was asked for while it is being created`);
|
|
534
|
+
const ctx = require_settings.createPluginContext(services, plugin, session, session.signal, session.scope);
|
|
535
|
+
session.building.add(plugin);
|
|
536
|
+
let created;
|
|
537
|
+
try {
|
|
538
|
+
created = plugin.create(ctx);
|
|
539
|
+
} finally {
|
|
540
|
+
session.building.delete(plugin);
|
|
541
|
+
}
|
|
542
|
+
const { api, connect } = created;
|
|
543
|
+
capability = api;
|
|
544
|
+
session.connectors.set(plugin, () => {
|
|
545
|
+
connect?.();
|
|
546
|
+
require_settings.markConnected(ctx);
|
|
547
|
+
});
|
|
511
548
|
session.capabilities.set(plugin, capability);
|
|
512
549
|
}
|
|
513
550
|
return capability;
|
|
514
551
|
}
|
|
552
|
+
/**
|
|
553
|
+
* A workspace capability as a document's scope sees it: the view its plugin declares
|
|
554
|
+
* (`inScope`), built once per document and kept on the session, so it is the same object on
|
|
555
|
+
* every call and goes when the document closes. Without a document, or for a document the
|
|
556
|
+
* kernel doesn't know, it is the capability itself.
|
|
557
|
+
*/
|
|
558
|
+
function workspaceViewOf(token, capability, documentId) {
|
|
559
|
+
const inScope = inScopeByToken.get(token);
|
|
560
|
+
const session = documentId === void 0 ? void 0 : sessions.get(documentId);
|
|
561
|
+
if (!inScope || !session) return capability;
|
|
562
|
+
let view = session.views.get(token);
|
|
563
|
+
if (view === void 0) {
|
|
564
|
+
view = inScope(capability, session.id);
|
|
565
|
+
session.views.set(token, view);
|
|
566
|
+
}
|
|
567
|
+
return view;
|
|
568
|
+
}
|
|
515
569
|
function resolveCapability(token, documentId) {
|
|
516
570
|
guardUsable(`capability("${token.name}")`);
|
|
517
571
|
const workspaceCapability = workspaceCapabilities.get(token);
|
|
518
|
-
if (workspaceCapability) return workspaceCapability;
|
|
572
|
+
if (workspaceCapability) return workspaceViewOf(token, workspaceCapability, documentId);
|
|
519
573
|
const provider = plan.providerOf(token);
|
|
520
574
|
if (!provider) throw new Error(`No capability "${token.name}".`);
|
|
521
575
|
const id = documentId ?? store.getCore().activeId;
|
|
@@ -531,55 +585,91 @@ function createKernel(opts) {
|
|
|
531
585
|
function tryResolveInternal(token, documentId) {
|
|
532
586
|
if (status === "destroying" || status === "destroyed") return null;
|
|
533
587
|
const workspaceCapability = workspaceCapabilities.get(token);
|
|
534
|
-
if (workspaceCapability) return workspaceCapability;
|
|
588
|
+
if (workspaceCapability) return workspaceViewOf(token, workspaceCapability, documentId);
|
|
535
589
|
const provider = plan.providerOf(token);
|
|
536
590
|
if (!provider) return null;
|
|
537
591
|
const session = sessionOf(documentId);
|
|
538
592
|
if (!session || session.phase !== "bringup" && session.phase !== "ready") return null;
|
|
593
|
+
if (isBuilding(provider, session)) return null;
|
|
539
594
|
return buildDocumentCapability(provider, session);
|
|
540
595
|
}
|
|
541
596
|
/** Public total resolver — see `Kernel.tryCapability`. `ready` only: the
|
|
542
|
-
* null→instance flip at commit time
|
|
597
|
+
* null→instance flip at commit time is the adapters' re-render signal. */
|
|
543
598
|
function tryResolveCapability(token, documentId) {
|
|
544
599
|
if (status === "destroying" || status === "destroyed") return null;
|
|
545
600
|
const workspaceCapability = workspaceCapabilities.get(token);
|
|
546
|
-
if (workspaceCapability) return workspaceCapability;
|
|
601
|
+
if (workspaceCapability) return workspaceViewOf(token, workspaceCapability, documentId);
|
|
547
602
|
const provider = plan.providerOf(token);
|
|
548
603
|
if (!provider) return null;
|
|
549
604
|
const session = sessionOf(documentId);
|
|
550
605
|
if (!session || session.phase !== "ready") return null;
|
|
606
|
+
if (isBuilding(provider, session)) return null;
|
|
551
607
|
return buildDocumentCapability(provider, session);
|
|
552
608
|
}
|
|
609
|
+
const viewerSettings = require_settings.createSettingsStore({
|
|
610
|
+
defaults: VIEWER_DEFAULTS,
|
|
611
|
+
registered: config.settings,
|
|
612
|
+
whole: VIEWER_WHOLE_SETTINGS
|
|
613
|
+
}, store.notify, report);
|
|
614
|
+
workspaceScope.defer(() => viewerSettings.dispose());
|
|
615
|
+
const settingsStores = /* @__PURE__ */ new Map();
|
|
616
|
+
for (const plugin of plan.ordered) if (plugin.settings) settingsStores.set(plugin, require_settings.createSettingsStore(plugin.settings, store.notify, report));
|
|
617
|
+
workspaceScope.defer(() => {
|
|
618
|
+
for (const settings of settingsStores.values()) settings.dispose();
|
|
619
|
+
});
|
|
620
|
+
function settingsOf(token) {
|
|
621
|
+
const provider = plan.providerOf(token);
|
|
622
|
+
if (!provider) throw new Error(`No capability "${token.name}".`);
|
|
623
|
+
const settings = settingsStores.get(provider);
|
|
624
|
+
if (!settings) throw new Error(`Plugin "${provider.id}" has no settings.`);
|
|
625
|
+
return settings.api;
|
|
626
|
+
}
|
|
553
627
|
const services = {
|
|
554
628
|
engine,
|
|
555
629
|
store,
|
|
556
630
|
workspaceScope,
|
|
631
|
+
workspaceSignal: workspaceCancel.signal,
|
|
632
|
+
workspaceLeases,
|
|
633
|
+
report,
|
|
634
|
+
clock: config.clock ?? require_settings.timerClock,
|
|
557
635
|
resolveCapability,
|
|
558
636
|
tryResolveCapability: tryResolveInternal,
|
|
559
|
-
documentHandle
|
|
637
|
+
documentHandle,
|
|
638
|
+
settingsStoreOf: (plugin) => settingsStores.get(plugin)
|
|
560
639
|
};
|
|
561
640
|
let ticketCounter = 0;
|
|
562
641
|
const nextTicket = () => `pending:${++ticketCounter}`;
|
|
563
|
-
/** Slices
|
|
564
|
-
*
|
|
565
|
-
*
|
|
566
|
-
*
|
|
642
|
+
/** Slices, the registry's event subscription, and every plugin's
|
|
643
|
+
* construction and connection — every step's release deferred into the
|
|
644
|
+
* session scope, every await followed by a checkpoint. Runs entirely
|
|
645
|
+
* pre-commit: a failure anywhere rolls the whole session back and the
|
|
646
|
+
* document was never `ready`. */
|
|
567
647
|
async function bringUp(session, snapshot) {
|
|
568
648
|
session.phase = "bringup";
|
|
569
649
|
let renderPolicy = _embedpdf_engine_core_runtime.CONTINUOUS_RENDER_POLICY;
|
|
570
650
|
try {
|
|
571
|
-
renderPolicy = await session.handle.render?.
|
|
651
|
+
renderPolicy = await session.handle.render?.getPolicy() ?? _embedpdf_engine_core_runtime.CONTINUOUS_RENDER_POLICY;
|
|
572
652
|
} catch {}
|
|
573
653
|
checkpoint(session);
|
|
574
654
|
session.stagedMeta = {
|
|
575
655
|
id: session.id,
|
|
656
|
+
instanceId: session.instanceId,
|
|
576
657
|
name: session.name,
|
|
577
658
|
pageCount: snapshot.pageCount,
|
|
578
659
|
pages: snapshot.pages,
|
|
579
660
|
revision: 0,
|
|
580
|
-
renderPolicy
|
|
661
|
+
renderPolicy,
|
|
662
|
+
hasUnsavedChanges: false
|
|
581
663
|
};
|
|
664
|
+
const tracksChanges = (0, _embedpdf_engine_core_runtime.isLocalDocument)(session.handle);
|
|
665
|
+
const bursts = require_settings.createBurstGate(store);
|
|
666
|
+
session.scope.defer(bursts.close);
|
|
582
667
|
const unsubscribeEvents = session.handle.events.subscribe((event) => {
|
|
668
|
+
bursts.first(event);
|
|
669
|
+
if (tracksChanges && changesDocument(event)) {
|
|
670
|
+
session.changeCount += 1;
|
|
671
|
+
setUnsavedChanges(session, true);
|
|
672
|
+
}
|
|
583
673
|
const layout = layoutFromEvent(event);
|
|
584
674
|
if (!layout) return;
|
|
585
675
|
const now = store.getCore();
|
|
@@ -591,74 +681,133 @@ function createKernel(opts) {
|
|
|
591
681
|
pages: layout.pages,
|
|
592
682
|
revision: existing.revision + 1
|
|
593
683
|
};
|
|
594
|
-
|
|
684
|
+
setCore({ documents: {
|
|
595
685
|
...now.documents,
|
|
596
686
|
[session.id]: updated
|
|
597
|
-
} }
|
|
687
|
+
} });
|
|
688
|
+
pagesChanged.emit({
|
|
689
|
+
documentId: session.id,
|
|
690
|
+
revision: updated.revision,
|
|
691
|
+
pages: updated.pages
|
|
692
|
+
});
|
|
598
693
|
});
|
|
599
694
|
session.scope.defer(unsubscribeEvents);
|
|
600
695
|
for (const plugin of documentScopedPlugins) {
|
|
601
|
-
const
|
|
602
|
-
|
|
603
|
-
session.scope.defer(() =>
|
|
696
|
+
const lease = store.lease(require_settings.sliceKey(plugin.id, session.id), initialStateOf(plugin), session.instanceId);
|
|
697
|
+
session.leases.set(plugin, lease);
|
|
698
|
+
session.scope.defer(() => lease.revoke());
|
|
604
699
|
}
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
700
|
+
const release = store.hold();
|
|
701
|
+
try {
|
|
702
|
+
for (const plugin of documentScopedPlugins) {
|
|
703
|
+
buildDocumentCapability(plugin, session);
|
|
704
|
+
session.connectors.get(plugin)?.();
|
|
705
|
+
}
|
|
706
|
+
} finally {
|
|
707
|
+
release();
|
|
608
708
|
}
|
|
609
|
-
|
|
709
|
+
session.scope.defer(session.handle.events.subscribe((event) => bursts.last(event)));
|
|
610
710
|
checkpoint(session);
|
|
611
711
|
}
|
|
612
|
-
|
|
712
|
+
/** What `open()` and `retry()` resolve: the document as its tab shows it, ready or locked. */
|
|
713
|
+
function openedResult(session) {
|
|
714
|
+
const core = store.getCore();
|
|
715
|
+
const entry = core.documents[session.id] ?? core.pending[session.id];
|
|
716
|
+
if (!entry) throw new require_settings.PluginError("instance-closed", "documents", `${session.id} was closed`);
|
|
717
|
+
return { document: documentInfoOf(entry) };
|
|
718
|
+
}
|
|
719
|
+
async function openDocument(input, options, slotId) {
|
|
720
|
+
const signal = options?.signal;
|
|
721
|
+
if (signal?.aborted) throw new require_settings.PluginError("operation-cancelled", "documents", "the open was cancelled");
|
|
722
|
+
try {
|
|
723
|
+
const { session, done } = reserveAndOpen(input, options, slotId);
|
|
724
|
+
const cancel = () => void session.close();
|
|
725
|
+
signal?.addEventListener("abort", cancel, { once: true });
|
|
726
|
+
try {
|
|
727
|
+
return openedResult(await done);
|
|
728
|
+
} finally {
|
|
729
|
+
signal?.removeEventListener("abort", cancel);
|
|
730
|
+
}
|
|
731
|
+
} catch (error) {
|
|
732
|
+
throw require_settings.toPluginError("documents", error);
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
/**
|
|
736
|
+
* The engine's open options for a document: its own, and the viewer's `scope` and
|
|
737
|
+
* `identity` where it has none. A document's own replace the viewer's, never merge.
|
|
738
|
+
*/
|
|
739
|
+
function engineOptionsOf(options) {
|
|
740
|
+
const { activate: _activate, name: _name, signal: _signal, ...own } = options ?? {};
|
|
741
|
+
const viewer = viewerSettings.api.getSettings();
|
|
742
|
+
const scope = own.scope ?? viewer.scope ?? void 0;
|
|
743
|
+
const identity = own.identity ?? viewer.identity ?? void 0;
|
|
744
|
+
return {
|
|
745
|
+
...own,
|
|
746
|
+
...scope !== void 0 ? { scope } : {},
|
|
747
|
+
...identity !== void 0 ? { identity } : {}
|
|
748
|
+
};
|
|
749
|
+
}
|
|
750
|
+
/**
|
|
751
|
+
* Reserve the tab slot synchronously and start the open; returns the slot id at once.
|
|
752
|
+
* `slotId` is a failed document's tab, which `retry()` opens again in place.
|
|
753
|
+
*/
|
|
754
|
+
function reserveAndOpen(input, options, slotId) {
|
|
613
755
|
guardUsable("documents.open()");
|
|
614
|
-
const
|
|
615
|
-
const requestedId = typeof input === "function" ? nextTicket() : idOfInput(input) ?? nextTicket();
|
|
616
|
-
if (sessions.has(requestedId)) throw new
|
|
617
|
-
const session = createSession(requestedId, name);
|
|
756
|
+
const engineOptions = engineOptionsOf(options);
|
|
757
|
+
const requestedId = slotId ?? (typeof input === "function" ? nextTicket() : idOfInput(input) ?? nextTicket());
|
|
758
|
+
if (sessions.has(requestedId)) throw new require_settings.PluginError("conflict", "documents", `document already open: ${requestedId}`);
|
|
759
|
+
const session = createSession(requestedId, options?.name);
|
|
760
|
+
session.source = input;
|
|
761
|
+
session.openOptions = options;
|
|
618
762
|
sessions.set(session.id, session);
|
|
619
|
-
publishPendingSlot(session, activate ?? true);
|
|
620
|
-
return
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
session
|
|
634
|
-
|
|
635
|
-
|
|
763
|
+
publishPendingSlot(session, options?.activate ?? true);
|
|
764
|
+
return {
|
|
765
|
+
session,
|
|
766
|
+
done: beginOperation(session, async () => {
|
|
767
|
+
try {
|
|
768
|
+
const given = typeof input === "function" ? await engineCall(session, Promise.resolve(input(session.cancel.signal))) : input;
|
|
769
|
+
checkpoint(session);
|
|
770
|
+
const source = isUrlSource(given) ? await engineCall(session, fetchUrlSource(given, session.cancel.signal)) : given;
|
|
771
|
+
checkpoint(session);
|
|
772
|
+
const sourceId = idOfInput(source);
|
|
773
|
+
if (sourceId) rekeySession(session, sourceId);
|
|
774
|
+
const handle = await engineCall(session, engine.open(source, engineOptions), (late) => late.close());
|
|
775
|
+
session.handle = handle;
|
|
776
|
+
session.scope.defer(() => handle.close());
|
|
777
|
+
checkpoint(session);
|
|
778
|
+
if (handle.id !== session.id) rekeySession(session, handle.id);
|
|
779
|
+
const prompt = handle.security?.passwordPrompt;
|
|
780
|
+
if (prompt?.state === "required") {
|
|
781
|
+
session.phase = "locked";
|
|
782
|
+
publishLocked(session, prompt.incorrect);
|
|
783
|
+
return session;
|
|
784
|
+
}
|
|
785
|
+
const snapshot = await engineCall(session, handle.pages.list());
|
|
786
|
+
checkpoint(session);
|
|
787
|
+
await bringUp(session, snapshot);
|
|
788
|
+
commitReady(session);
|
|
789
|
+
return session;
|
|
790
|
+
} catch (error) {
|
|
791
|
+
if (require_settings.isCancelled(error)) throw error;
|
|
792
|
+
if (session.phase === "closing" || isAbortLike(error)) throw new require_settings.CancelledError(`closed while opening: ${session.id}`);
|
|
793
|
+
await session.scope.dispose();
|
|
794
|
+
session.phase = "error";
|
|
795
|
+
publishError(session, error);
|
|
796
|
+
throw error;
|
|
636
797
|
}
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
await bringUp(session, snapshot);
|
|
640
|
-
commitReady(session);
|
|
641
|
-
return session.id;
|
|
642
|
-
} catch (error) {
|
|
643
|
-
if (isCancelled(error)) throw error;
|
|
644
|
-
if (session.phase === "closing" || isAbortLike(error)) throw new CancelledError(`closed while opening: ${session.id}`);
|
|
645
|
-
await session.scope.dispose();
|
|
646
|
-
session.phase = "error";
|
|
647
|
-
publishError(session, error);
|
|
648
|
-
throw error;
|
|
649
|
-
}
|
|
650
|
-
});
|
|
798
|
+
})
|
|
799
|
+
};
|
|
651
800
|
}
|
|
652
|
-
async function unlockDocument(id,
|
|
801
|
+
async function unlockDocument(id, options) {
|
|
653
802
|
guardUsable("documents.unlock()");
|
|
654
803
|
const session = sessions.get(id);
|
|
655
|
-
if (!session || session.phase !== "locked" || !session.handle) throw new
|
|
804
|
+
if (!session || session.phase !== "locked" || !session.handle) throw new require_settings.PluginError("invalid-input", "documents", `document is not locked: ${id}`);
|
|
656
805
|
const handle = session.handle;
|
|
657
806
|
return beginOperation(session, async () => {
|
|
658
807
|
try {
|
|
659
|
-
await engineCall(session, handle.security.unlock({ password:
|
|
808
|
+
await require_settings.cancellable("documents", options.signal, engineCall(session, handle.security.unlock({ password: options.password })));
|
|
660
809
|
} catch (error) {
|
|
661
|
-
if (session.phase === "closing" || isAbortLike(error)) throw new CancelledError(`closed while opening: ${session.id}`);
|
|
810
|
+
if (session.phase === "closing" || isAbortLike(error)) throw new require_settings.CancelledError(`closed while opening: ${session.id}`);
|
|
662
811
|
throw error;
|
|
663
812
|
}
|
|
664
813
|
checkpoint(session);
|
|
@@ -668,13 +817,15 @@ function createKernel(opts) {
|
|
|
668
817
|
await bringUp(session, snapshot);
|
|
669
818
|
commitReady(session);
|
|
670
819
|
} catch (error) {
|
|
671
|
-
if (isCancelled(error)) throw error;
|
|
672
|
-
if (session.phase === "closing" || isAbortLike(error)) throw new CancelledError(`closed while opening: ${session.id}`);
|
|
820
|
+
if (require_settings.isCancelled(error)) throw error;
|
|
821
|
+
if (session.phase === "closing" || isAbortLike(error)) throw new require_settings.CancelledError(`closed while opening: ${session.id}`);
|
|
673
822
|
await session.scope.dispose();
|
|
674
823
|
session.phase = "error";
|
|
675
824
|
publishError(session, error);
|
|
676
825
|
throw error;
|
|
677
826
|
}
|
|
827
|
+
}).catch((error) => {
|
|
828
|
+
throw require_settings.toPluginError("documents", error);
|
|
678
829
|
});
|
|
679
830
|
}
|
|
680
831
|
async function closeDocument(documentId) {
|
|
@@ -683,18 +834,81 @@ function createKernel(opts) {
|
|
|
683
834
|
await session.close();
|
|
684
835
|
}
|
|
685
836
|
function reorder(next) {
|
|
686
|
-
|
|
837
|
+
setCore({ order: next });
|
|
687
838
|
}
|
|
839
|
+
const metaOf = (documentId) => {
|
|
840
|
+
const core = store.getCore();
|
|
841
|
+
const id = documentId ?? core.activeId;
|
|
842
|
+
return id ? core.documents[id] ?? null : null;
|
|
843
|
+
};
|
|
844
|
+
/** The tab list: the same array until one of its documents changes, or the order does. */
|
|
845
|
+
let listMemo = null;
|
|
846
|
+
const listDocuments = () => {
|
|
847
|
+
const core = store.getCore();
|
|
848
|
+
if (listMemo?.core === core) return listMemo.value;
|
|
849
|
+
const next = core.order.map((id) => documentInfoOf(core.documents[id] ?? core.pending[id]));
|
|
850
|
+
const previous = listMemo?.value;
|
|
851
|
+
const value = previous !== void 0 && previous.length === next.length && previous.every((info, i) => info === next[i]) ? previous : Object.freeze(next);
|
|
852
|
+
listMemo = {
|
|
853
|
+
core,
|
|
854
|
+
value
|
|
855
|
+
};
|
|
856
|
+
return value;
|
|
857
|
+
};
|
|
858
|
+
/** A document's fields: `documentId`, or the active document's. */
|
|
859
|
+
const getDocument = (documentId) => {
|
|
860
|
+
const core = store.getCore();
|
|
861
|
+
const id = documentId ?? core.activeId;
|
|
862
|
+
const entry = id ? core.documents[id] ?? core.pending[id] : void 0;
|
|
863
|
+
return entry ? documentInfoOf(entry) : null;
|
|
864
|
+
};
|
|
688
865
|
const documents = {
|
|
689
866
|
open: openDocument,
|
|
690
|
-
|
|
867
|
+
canOpenFiles: () => engine.canOpenFiles,
|
|
868
|
+
retry: async (id, options) => {
|
|
869
|
+
const session = sessions.get(id);
|
|
870
|
+
if (!session || session.phase !== "error" || session.source === null) throw new require_settings.PluginError("invalid-input", "documents", `${id} didn't fail to open; nothing to retry`);
|
|
871
|
+
const { source, openOptions } = session;
|
|
872
|
+
sessions.delete(id);
|
|
873
|
+
return openDocument(source, {
|
|
874
|
+
...openOptions,
|
|
875
|
+
activate: false,
|
|
876
|
+
signal: options?.signal
|
|
877
|
+
}, id);
|
|
878
|
+
},
|
|
879
|
+
rename: (id, name) => {
|
|
880
|
+
const core = store.getCore();
|
|
881
|
+
if (core.documents[id]) setCore({ documents: {
|
|
882
|
+
...core.documents,
|
|
883
|
+
[id]: {
|
|
884
|
+
...core.documents[id],
|
|
885
|
+
name
|
|
886
|
+
}
|
|
887
|
+
} });
|
|
888
|
+
else if (core.pending[id]) setCore({ pending: {
|
|
889
|
+
...core.pending,
|
|
890
|
+
[id]: {
|
|
891
|
+
...core.pending[id],
|
|
892
|
+
name
|
|
893
|
+
}
|
|
894
|
+
} });
|
|
895
|
+
const session = sessions.get(id);
|
|
896
|
+
if (session) session.name = name;
|
|
897
|
+
},
|
|
898
|
+
openAll: (initialDocuments) => {
|
|
691
899
|
guardUsable("documents.openAll()");
|
|
692
|
-
const activeIndex = Math.max(0,
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
900
|
+
const activeIndex = Math.max(0, initialDocuments.findIndex((initialDocument) => initialDocument.active));
|
|
901
|
+
return initialDocuments.map(({ source, active: _active, ...options }, index) => {
|
|
902
|
+
try {
|
|
903
|
+
const { session, done } = reserveAndOpen(source, {
|
|
904
|
+
...options,
|
|
905
|
+
activate: index === activeIndex
|
|
906
|
+
});
|
|
907
|
+
done.catch(() => {});
|
|
908
|
+
return session.id;
|
|
909
|
+
} catch {
|
|
910
|
+
return typeof source === "function" ? "" : idOfInput(source) ?? "";
|
|
911
|
+
}
|
|
698
912
|
});
|
|
699
913
|
},
|
|
700
914
|
unlock: unlockDocument,
|
|
@@ -704,67 +918,77 @@ function createKernel(opts) {
|
|
|
704
918
|
},
|
|
705
919
|
setActive: (id) => {
|
|
706
920
|
const core = store.getCore();
|
|
707
|
-
if (core.documents[id] || core.pending[id])
|
|
708
|
-
},
|
|
709
|
-
activeId: () => store.getCore().activeId,
|
|
710
|
-
list: () => store.getCore().order.map((id) => {
|
|
711
|
-
const core = store.getCore();
|
|
712
|
-
const meta = core.documents[id];
|
|
713
|
-
return meta ? toDocInfo(meta) : pendingToDocInfo(core.pending[id]);
|
|
714
|
-
}),
|
|
715
|
-
get: (id) => {
|
|
716
|
-
const core = store.getCore();
|
|
717
|
-
const meta = core.documents[id];
|
|
718
|
-
if (meta) return toDocInfo(meta);
|
|
719
|
-
const pending = core.pending[id];
|
|
720
|
-
return pending ? pendingToDocInfo(pending) : null;
|
|
921
|
+
if (core.documents[id] || core.pending[id]) setCore({ activeId: id });
|
|
721
922
|
},
|
|
923
|
+
getActiveId: () => store.getCore().activeId,
|
|
924
|
+
getActive: () => getDocument(),
|
|
925
|
+
list: listDocuments,
|
|
926
|
+
get: getDocument,
|
|
722
927
|
has: (id) => {
|
|
723
928
|
const core = store.getCore();
|
|
724
929
|
return core.documents[id] !== void 0 || core.pending[id] !== void 0;
|
|
725
930
|
},
|
|
726
|
-
|
|
727
|
-
|
|
931
|
+
getCount: () => store.getCore().order.length,
|
|
932
|
+
getOrder: () => store.getCore().order,
|
|
933
|
+
setOrder: (ids) => {
|
|
934
|
+
const current = store.getCore().order;
|
|
935
|
+
if (!(ids.length === current.length && [...ids].sort().join("\0") === [...current].sort().join("\0"))) throw new require_settings.PluginError("invalid-input", "documents", "setOrder() needs a permutation of the current order");
|
|
936
|
+
reorder([...ids]);
|
|
937
|
+
},
|
|
728
938
|
move: (id, toIndex) => {
|
|
729
939
|
const core = store.getCore();
|
|
730
940
|
if (!core.documents[id] && !core.pending[id]) return;
|
|
731
|
-
const without = core.order.filter((
|
|
941
|
+
const without = core.order.filter((documentId) => documentId !== id);
|
|
732
942
|
const clamped = Math.max(0, Math.min(toIndex, without.length));
|
|
733
943
|
without.splice(clamped, 0, id);
|
|
734
944
|
reorder(without);
|
|
735
945
|
},
|
|
736
|
-
swap: (
|
|
946
|
+
swap: (id, otherId) => {
|
|
737
947
|
const core = store.getCore();
|
|
738
|
-
const
|
|
739
|
-
const
|
|
740
|
-
if (
|
|
948
|
+
const index = core.order.indexOf(id);
|
|
949
|
+
const otherIndex = core.order.indexOf(otherId);
|
|
950
|
+
if (index < 0 || otherIndex < 0) return;
|
|
741
951
|
const next = [...core.order];
|
|
742
|
-
next[
|
|
743
|
-
next[
|
|
952
|
+
next[index] = otherId;
|
|
953
|
+
next[otherIndex] = id;
|
|
744
954
|
reorder(next);
|
|
745
955
|
},
|
|
746
|
-
download: (id,
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
956
|
+
download: (id, options) => downloadFile(id, options?.signal, "download", (handle) => handle.download(options?.mode !== void 0 ? { mode: options.mode } : void 0)),
|
|
957
|
+
downloadLayer: (id, options) => downloadFile(id, options?.signal, "downloadLayer", (handle) => (0, _embedpdf_engine_core_runtime.isLocalDocument)(handle) ? handle.downloadLayer() : Promise.reject(noLayer()), (handle) => (0, _embedpdf_engine_core_runtime.isLocalDocument)(handle) ? null : noLayer()),
|
|
958
|
+
canDownload: (id) => allowsOn(id, "doc.download"),
|
|
959
|
+
canPrint: (id) => allowsOn(id, "doc.print"),
|
|
960
|
+
listPages: (id) => metaOf(id)?.pages ?? EMPTY_PAGES,
|
|
961
|
+
getPage: (page, id) => require_settings.findPage(metaOf(id)?.pages ?? EMPTY_PAGES, page),
|
|
962
|
+
getPageIndex: (ref, id) => metaOf(id)?.pages.findIndex((pageInfo) => (0, _embedpdf_engine_core_runtime.pageRefsEqual)(pageInfo.ref, ref)) ?? -1,
|
|
963
|
+
getRevision: (id) => metaOf(id)?.revision ?? -1,
|
|
964
|
+
onOpened: opened.on,
|
|
965
|
+
onOpenFailed: openFailed.on,
|
|
966
|
+
onLocked: locked.on,
|
|
967
|
+
onClosed: closed.on,
|
|
968
|
+
onActiveChanged: activeChanged.on,
|
|
969
|
+
onPagesChanged: pagesChanged.on,
|
|
970
|
+
onUnsavedChangesChanged: unsavedChangesChanged.on
|
|
758
971
|
};
|
|
759
|
-
workspaceCapabilities.set(DocumentsToken, documents);
|
|
760
|
-
for (const plugin of plan.ordered) if (!isDocumentScoped(plugin))
|
|
761
|
-
for (const plugin of plan.ordered)
|
|
972
|
+
workspaceCapabilities.set(require_settings.DocumentsToken, documents);
|
|
973
|
+
for (const plugin of plan.ordered) if (!isDocumentScoped(plugin)) workspaceLeases.set(plugin, store.lease(plugin.id, initialStateOf(plugin)));
|
|
974
|
+
for (const plugin of plan.ordered) {
|
|
975
|
+
if (isDocumentScoped(plugin)) continue;
|
|
976
|
+
const ctx = require_settings.createPluginContext(services, plugin, void 0, workspaceCancel.signal, workspaceScope);
|
|
977
|
+
const { api, connect } = plugin.create(ctx);
|
|
978
|
+
if (plugin.token) workspaceCapabilities.set(plugin.token, api);
|
|
979
|
+
workspaceConnectors.set(plugin, () => {
|
|
980
|
+
connect?.();
|
|
981
|
+
require_settings.markConnected(ctx);
|
|
982
|
+
});
|
|
983
|
+
}
|
|
762
984
|
return {
|
|
985
|
+
...viewerSettings.api,
|
|
763
986
|
engine,
|
|
764
987
|
documents,
|
|
765
988
|
capability: resolveCapability,
|
|
766
989
|
tryCapability: tryResolveCapability,
|
|
767
990
|
scopeOf: plan.scopeOf,
|
|
991
|
+
settingsOf,
|
|
768
992
|
subscribe: store.subscribe,
|
|
769
993
|
getState: store.getState,
|
|
770
994
|
status: () => status,
|
|
@@ -775,10 +999,8 @@ function createKernel(opts) {
|
|
|
775
999
|
try {
|
|
776
1000
|
for (const plugin of plan.ordered) {
|
|
777
1001
|
if (status !== "starting") return;
|
|
778
|
-
if (!isDocumentScoped(plugin))
|
|
1002
|
+
if (!isDocumentScoped(plugin)) workspaceConnectors.get(plugin)?.();
|
|
779
1003
|
}
|
|
780
|
-
if (status !== "starting") return;
|
|
781
|
-
for (const plugin of plan.ordered) if (!isDocumentScoped(plugin)) plugin.effects?.(createEffectContext(services, plugin));
|
|
782
1004
|
status = "started";
|
|
783
1005
|
} catch (error) {
|
|
784
1006
|
status = "failed";
|
|
@@ -792,8 +1014,9 @@ function createKernel(opts) {
|
|
|
792
1014
|
return destroyPromise ?? (destroyPromise = (async () => {
|
|
793
1015
|
const wasFailed = status === "failed";
|
|
794
1016
|
status = "destroying";
|
|
1017
|
+
workspaceCancel.abort(new require_settings.CancelledError("kernel destroyed"));
|
|
795
1018
|
await startPromise?.catch((error) => {
|
|
796
|
-
if (!isCancelled(error) && !wasFailed) report(error);
|
|
1019
|
+
if (!require_settings.isCancelled(error) && !wasFailed) report(error);
|
|
797
1020
|
});
|
|
798
1021
|
await Promise.allSettled([...sessions.values()].map((session) => session.close()));
|
|
799
1022
|
sessions.clear();
|
|
@@ -806,56 +1029,213 @@ function createKernel(opts) {
|
|
|
806
1029
|
};
|
|
807
1030
|
}
|
|
808
1031
|
//#endregion
|
|
809
|
-
//#region src/
|
|
1032
|
+
//#region src/compose.ts
|
|
1033
|
+
function composeApi(capability, slices) {
|
|
1034
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1035
|
+
const api = {};
|
|
1036
|
+
for (const slice of slices) for (const key of Object.keys(slice)) {
|
|
1037
|
+
if (seen.has(key)) throw new Error(`[${capability}] api member '${key}' is defined twice`);
|
|
1038
|
+
seen.add(key);
|
|
1039
|
+
api[key] = slice[key];
|
|
1040
|
+
}
|
|
1041
|
+
return api;
|
|
1042
|
+
}
|
|
1043
|
+
//#endregion
|
|
1044
|
+
//#region src/memo.ts
|
|
1045
|
+
const sameInputs = (left, right) => left.length === right.length && left.every((value, index) => Object.is(value, right[index]));
|
|
810
1046
|
/**
|
|
811
|
-
*
|
|
812
|
-
*
|
|
813
|
-
* Delivery contract: synchronous fan-out over a snapshot of the listener set
|
|
814
|
-
* (listeners added or removed during an emit don't affect that emit); a
|
|
815
|
-
* throwing listener is isolated — reported through `onListenerError`, never
|
|
816
|
-
* allowed to break the emitting operation or its sibling listeners. After
|
|
817
|
-
* `dispose()`, `on` returns an inert unsubscribe (teardown races never throw).
|
|
818
|
-
* No value cache, no replay, no equality — state belongs to the store.
|
|
1047
|
+
* Recompute `compute(...inputs())` only when an input changes (compared with
|
|
1048
|
+
* `Object.is`, element by element); otherwise return the previous result.
|
|
819
1049
|
*/
|
|
820
|
-
function
|
|
821
|
-
let
|
|
822
|
-
return {
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
1050
|
+
function memo(inputs, compute) {
|
|
1051
|
+
let last = null;
|
|
1052
|
+
return () => {
|
|
1053
|
+
const next = inputs();
|
|
1054
|
+
if (last && sameInputs(last.inputs, next)) return last.result;
|
|
1055
|
+
const result = compute(...next);
|
|
1056
|
+
last = {
|
|
1057
|
+
inputs: next,
|
|
1058
|
+
result
|
|
1059
|
+
};
|
|
1060
|
+
return result;
|
|
1061
|
+
};
|
|
1062
|
+
}
|
|
1063
|
+
/**
|
|
1064
|
+
* `memo` per key: each key keeps its own last inputs and result. Keys are
|
|
1065
|
+
* compared with `Map` semantics, so use primitives (a page object number, an
|
|
1066
|
+
* encoded key), not freshly built objects. `maxEntries` bounds the cache;
|
|
1067
|
+
* the oldest key is evicted first.
|
|
1068
|
+
*/
|
|
1069
|
+
function memoByKey(inputs, compute, options = {}) {
|
|
1070
|
+
const cache = /* @__PURE__ */ new Map();
|
|
1071
|
+
return (key) => {
|
|
1072
|
+
const next = inputs(key);
|
|
1073
|
+
const cached = cache.get(key);
|
|
1074
|
+
if (cached && sameInputs(cached.inputs, next)) return cached.result;
|
|
1075
|
+
const result = compute(key, ...next);
|
|
1076
|
+
if (cached) cache.delete(key);
|
|
1077
|
+
else if (options.maxEntries !== void 0 && cache.size >= options.maxEntries) {
|
|
1078
|
+
const oldest = cache.keys().next();
|
|
1079
|
+
if (!oldest.done) cache.delete(oldest.value);
|
|
838
1080
|
}
|
|
1081
|
+
cache.set(key, {
|
|
1082
|
+
inputs: next,
|
|
1083
|
+
result
|
|
1084
|
+
});
|
|
1085
|
+
return result;
|
|
839
1086
|
};
|
|
840
1087
|
}
|
|
841
1088
|
//#endregion
|
|
842
|
-
//#region src/
|
|
1089
|
+
//#region src/state.ts
|
|
1090
|
+
/**
|
|
1091
|
+
* Declare a plugin's state. The fields come from `read`, and `empty` is checked
|
|
1092
|
+
* against them (`NoInfer` keeps it out of the inference): a missing field or a
|
|
1093
|
+
* value of the wrong type is a type error. Write `empty` inline, because
|
|
1094
|
+
* TypeScript refuses an extra field only in an object literal.
|
|
1095
|
+
*/
|
|
1096
|
+
function defineState(token, declaration) {
|
|
1097
|
+
return {
|
|
1098
|
+
token,
|
|
1099
|
+
read: declaration.read,
|
|
1100
|
+
empty: Object.freeze(declaration.empty),
|
|
1101
|
+
...declaration.ensure ? { ensure: declaration.ensure } : {}
|
|
1102
|
+
};
|
|
1103
|
+
}
|
|
843
1104
|
/**
|
|
844
|
-
*
|
|
845
|
-
*
|
|
846
|
-
*
|
|
1105
|
+
* One level of identity: equal when the two values are the same, or are
|
|
1106
|
+
* objects (or arrays) holding the same values under the same keys. Declared
|
|
1107
|
+
* state compares this way, and so does a value a selector picks from it.
|
|
1108
|
+
*/
|
|
1109
|
+
function shallowEqual(left, right) {
|
|
1110
|
+
if (Object.is(left, right)) return true;
|
|
1111
|
+
if (typeof left !== "object" || typeof right !== "object" || left === null || right === null) return false;
|
|
1112
|
+
if (Array.isArray(left) !== Array.isArray(right)) return false;
|
|
1113
|
+
const leftFields = left;
|
|
1114
|
+
const rightFields = right;
|
|
1115
|
+
const keys = Object.keys(leftFields);
|
|
1116
|
+
return keys.length === Object.keys(rightFields).length && keys.every((key) => key in rightFields && Object.is(leftFields[key], rightFields[key]));
|
|
1117
|
+
}
|
|
1118
|
+
//#endregion
|
|
1119
|
+
//#region src/documents-state.ts
|
|
1120
|
+
/**
|
|
1121
|
+
* The documents' State tables as code: the document a component talks to (React's
|
|
1122
|
+
* `useDocument()`), and every open document with the active one (`useDocumentsState()`). Each
|
|
1123
|
+
* adapter turns these into its own reactive form, as it does a plugin's.
|
|
1124
|
+
*/
|
|
1125
|
+
/** No document: none is open, or none is in scope. It reads as one still on its way. */
|
|
1126
|
+
const NO_DOCUMENT = Object.freeze({
|
|
1127
|
+
id: "",
|
|
1128
|
+
name: void 0,
|
|
1129
|
+
status: "loading",
|
|
1130
|
+
pageCount: 0,
|
|
1131
|
+
hasUnsavedChanges: false,
|
|
1132
|
+
passwordProvided: void 0,
|
|
1133
|
+
error: void 0
|
|
1134
|
+
});
|
|
1135
|
+
/**
|
|
1136
|
+
* The document in scope (the capability resolved for a document, or else the active one): its
|
|
1137
|
+
* id, name, status, page count, unsaved changes, and why it's locked or failed.
|
|
1138
|
+
*/
|
|
1139
|
+
const documentState = defineState(require_settings.DocumentsToken, {
|
|
1140
|
+
read: (documents) => documents.get() ?? NO_DOCUMENT,
|
|
1141
|
+
empty: NO_DOCUMENT
|
|
1142
|
+
});
|
|
1143
|
+
const documentsState = defineState(require_settings.DocumentsToken, {
|
|
1144
|
+
read: (documents) => ({
|
|
1145
|
+
documents: documents.list(),
|
|
1146
|
+
activeId: documents.getActiveId()
|
|
1147
|
+
}),
|
|
1148
|
+
empty: {
|
|
1149
|
+
documents: [],
|
|
1150
|
+
activeId: null
|
|
1151
|
+
}
|
|
1152
|
+
});
|
|
1153
|
+
//#endregion
|
|
1154
|
+
//#region src/capability-handle.ts
|
|
1155
|
+
/**
|
|
1156
|
+
* What every framework adapter's plugin API shares (`useStage()`, `inject(EpdfSearch)`): which
|
|
1157
|
+
* members are reads, and what a document plugin's API reaches while its part of the app has no
|
|
1158
|
+
* ready document.
|
|
1159
|
+
*
|
|
1160
|
+
* Reads follow the capability vocabulary (docs/conventions/naming.md): a member named `get*`,
|
|
1161
|
+
* `list*`, `is*`, `has*` or `can*` reads state, so an adapter makes a call to it in a template or
|
|
1162
|
+
* a derived value update when its answer changes, and every other member tracks nothing.
|
|
847
1163
|
*
|
|
848
|
-
*
|
|
849
|
-
*
|
|
850
|
-
*
|
|
1164
|
+
* Without a document there is no capability to call, so the API reaches a stand-in: reading a
|
|
1165
|
+
* member never throws, so chrome renders before the first document opens, and calling one refuses
|
|
1166
|
+
* with `not-ready`. A member the token lists in `promises` returns the refusal as a rejected
|
|
1167
|
+
* promise, as the capability itself would, so a `.catch()` sees it; every other member throws.
|
|
1168
|
+
* The settings calls work: a plugin's settings belong to the plugin, not to a document.
|
|
851
1169
|
*/
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
1170
|
+
const READ = /^(?:get|list|is|has|can)(?:[A-Z]|$)/;
|
|
1171
|
+
/** Whether a capability member named `name` is a read: `get*`, `list*`, `is*`, `has*` or `can*`. */
|
|
1172
|
+
function isReadMember(name) {
|
|
1173
|
+
return READ.test(name);
|
|
1174
|
+
}
|
|
1175
|
+
/** Whether the member at `path` (`'setValue'`, `'comments.reply'`) returns a promise, as `token` declares. */
|
|
1176
|
+
function returnsPromise(token, path) {
|
|
1177
|
+
return token.promises?.[path] === true;
|
|
1178
|
+
}
|
|
1179
|
+
/** The capability members that work without a document: a plugin's settings are the plugin's own. */
|
|
1180
|
+
const SETTINGS_CALLS = /* @__PURE__ */ new Set([
|
|
1181
|
+
"getSettings",
|
|
1182
|
+
"updateSettings",
|
|
1183
|
+
"resetSettings",
|
|
1184
|
+
"onSettingsChanged"
|
|
1185
|
+
]);
|
|
1186
|
+
const standIns = /* @__PURE__ */ new WeakMap();
|
|
1187
|
+
/**
|
|
1188
|
+
* What a framework adapter's API reaches for a document plugin while its part of the app has no
|
|
1189
|
+
* ready document. One per kernel and token, so it is the same object on every read.
|
|
1190
|
+
*/
|
|
1191
|
+
function standInFor(kernel, token) {
|
|
1192
|
+
let byToken = standIns.get(kernel);
|
|
1193
|
+
if (!byToken) {
|
|
1194
|
+
byToken = /* @__PURE__ */ new WeakMap();
|
|
1195
|
+
standIns.set(kernel, byToken);
|
|
1196
|
+
}
|
|
1197
|
+
let standIn = byToken.get(token);
|
|
1198
|
+
if (!standIn) {
|
|
1199
|
+
standIn = createStandIn(token, () => kernel.settingsOf(token));
|
|
1200
|
+
byToken.set(token, standIn);
|
|
1201
|
+
}
|
|
1202
|
+
return standIn;
|
|
1203
|
+
}
|
|
1204
|
+
/**
|
|
1205
|
+
* A Proxy whose every member is a function that refuses with `not-ready`. A member is itself such
|
|
1206
|
+
* a Proxy, so namespaces work too (`annotation.comments.reply`). Members are cached by path, so
|
|
1207
|
+
* `stage.zoomIn` is the same function on every read. `then`, symbol keys and keys that start with
|
|
1208
|
+
* `__` (a framework's own flags, such as Vue's `__v_isRef`) read as undefined: the stand-in is no
|
|
1209
|
+
* thenable, and inspecting it doesn't throw. The four settings calls forward to `settings()` when
|
|
1210
|
+
* called; for a plugin without settings, they refuse too.
|
|
1211
|
+
*/
|
|
1212
|
+
function createStandIn(token, settings) {
|
|
1213
|
+
const refusal = () => new require_settings.PluginError("not-ready", token.name, "no document is open");
|
|
1214
|
+
const refuse = (path) => () => {
|
|
1215
|
+
if (returnsPromise(token, path)) return Promise.reject(refusal());
|
|
1216
|
+
throw refusal();
|
|
1217
|
+
};
|
|
1218
|
+
const forward = (name) => (...args) => {
|
|
1219
|
+
let api;
|
|
1220
|
+
try {
|
|
1221
|
+
api = settings();
|
|
1222
|
+
} catch {
|
|
1223
|
+
throw refusal();
|
|
1224
|
+
}
|
|
1225
|
+
return api[name](...args);
|
|
1226
|
+
};
|
|
1227
|
+
const members = /* @__PURE__ */ new Map();
|
|
1228
|
+
const memberOf = (parentPath, key) => {
|
|
1229
|
+
if (typeof key === "symbol" || key === "then" || key.startsWith("__")) return void 0;
|
|
1230
|
+
const path = parentPath ? `${parentPath}.${key}` : key;
|
|
1231
|
+
let member = members.get(path);
|
|
1232
|
+
if (!member) {
|
|
1233
|
+
member = !parentPath && SETTINGS_CALLS.has(key) ? forward(key) : new Proxy(refuse(path), { get: (_refuse, next) => memberOf(path, next) });
|
|
1234
|
+
members.set(path, member);
|
|
1235
|
+
}
|
|
1236
|
+
return member;
|
|
858
1237
|
};
|
|
1238
|
+
return new Proxy({}, { get: (_capability, key) => memberOf("", key) });
|
|
859
1239
|
}
|
|
860
1240
|
//#endregion
|
|
861
1241
|
//#region src/index.ts
|
|
@@ -863,16 +1243,33 @@ function createSerialQueue() {
|
|
|
863
1243
|
* Create a typed capability token. `name` is the token's identity (debugging,
|
|
864
1244
|
* error messages); `options.hint` is the authored remedy the kernel appends to
|
|
865
1245
|
* the missing-dependency error, so the error contains its own fix.
|
|
1246
|
+
* `options.promises` lists the members that return a promise, so a framework
|
|
1247
|
+
* adapter's stand-in rejects them, rather than throwing, while no document is
|
|
1248
|
+
* open; a document plugin whose capability has such members gives it.
|
|
866
1249
|
*/
|
|
867
1250
|
function createCapabilityToken(name, options) {
|
|
868
1251
|
return {
|
|
869
1252
|
name,
|
|
870
|
-
hint: options?.hint
|
|
1253
|
+
hint: options?.hint,
|
|
1254
|
+
...options?.promises ? { promises: options.promises } : {}
|
|
871
1255
|
};
|
|
872
1256
|
}
|
|
873
|
-
/**
|
|
874
|
-
|
|
875
|
-
|
|
1257
|
+
/**
|
|
1258
|
+
* The host lens over a plugin's token: the same runtime object, typed with
|
|
1259
|
+
* the wider capability sibling plugins use. The one place that cast lives.
|
|
1260
|
+
*/
|
|
1261
|
+
function createHostToken(token) {
|
|
1262
|
+
return token;
|
|
1263
|
+
}
|
|
1264
|
+
/**
|
|
1265
|
+
* Define a plugin. It returns the definition as it is, and reads the types from it, so a
|
|
1266
|
+
* plugin passes no type arguments: the capability from `token`, the state from `state`, the
|
|
1267
|
+
* settings from `settings.defaults`. `create` receives a `PluginContext<State, Settings>`
|
|
1268
|
+
* and must return the capability. A plugin without `state` is stateless (`void`); one
|
|
1269
|
+
* without `settings` has `NoSettings`, and `ctx.settings()` throws there.
|
|
1270
|
+
*/
|
|
1271
|
+
function definePlugin(definition) {
|
|
1272
|
+
return definition;
|
|
876
1273
|
}
|
|
877
1274
|
//#endregion
|
|
878
1275
|
Object.defineProperty(exports, "AbortablePromise", {
|
|
@@ -887,30 +1284,74 @@ Object.defineProperty(exports, "CONTINUOUS_RENDER_POLICY", {
|
|
|
887
1284
|
return _embedpdf_engine_core_runtime.CONTINUOUS_RENDER_POLICY;
|
|
888
1285
|
}
|
|
889
1286
|
});
|
|
890
|
-
exports.
|
|
891
|
-
exports.
|
|
892
|
-
exports.CORE_DOCUMENT_LOCKED = CORE_DOCUMENT_LOCKED;
|
|
893
|
-
exports.CORE_DOCUMENT_OPENING = CORE_DOCUMENT_OPENING;
|
|
894
|
-
exports.CORE_DOCUMENT_OPEN_FAILED = CORE_DOCUMENT_OPEN_FAILED;
|
|
895
|
-
exports.CORE_DOCUMENT_PAGES_UPDATED = CORE_DOCUMENT_PAGES_UPDATED;
|
|
896
|
-
exports.CORE_DOCUMENT_REMOVED = CORE_DOCUMENT_REMOVED;
|
|
897
|
-
exports.CORE_ORDER_CHANGED = CORE_ORDER_CHANGED;
|
|
898
|
-
exports.CancelledError = CancelledError;
|
|
899
|
-
exports.DocumentsToken = DocumentsToken;
|
|
1287
|
+
exports.CancelledError = require_settings.CancelledError;
|
|
1288
|
+
exports.DocumentsToken = require_settings.DocumentsToken;
|
|
900
1289
|
Object.defineProperty(exports, "PermissionDenied", {
|
|
901
1290
|
enumerable: true,
|
|
902
1291
|
get: function() {
|
|
903
1292
|
return _embedpdf_engine_core_runtime.PermissionDenied;
|
|
904
1293
|
}
|
|
905
1294
|
});
|
|
1295
|
+
exports.PluginError = require_settings.PluginError;
|
|
1296
|
+
exports.VIEWER_DEFAULTS = VIEWER_DEFAULTS;
|
|
1297
|
+
exports.VIEWER_WHOLE_SETTINGS = VIEWER_WHOLE_SETTINGS;
|
|
1298
|
+
Object.defineProperty(exports, "annotationKey", {
|
|
1299
|
+
enumerable: true,
|
|
1300
|
+
get: function() {
|
|
1301
|
+
return _embedpdf_engine_core_runtime.annotationKey;
|
|
1302
|
+
}
|
|
1303
|
+
});
|
|
1304
|
+
exports.composeApi = composeApi;
|
|
906
1305
|
exports.createCapabilityToken = createCapabilityToken;
|
|
907
|
-
exports.createEventHook = createEventHook;
|
|
1306
|
+
exports.createEventHook = require_settings.createEventHook;
|
|
1307
|
+
exports.createHostToken = createHostToken;
|
|
908
1308
|
exports.createKernel = createKernel;
|
|
909
|
-
exports.
|
|
1309
|
+
exports.createLatestLane = require_settings.createLatestLane;
|
|
1310
|
+
exports.createSerialQueue = require_settings.createSerialQueue;
|
|
1311
|
+
Object.defineProperty(exports, "decodePageKey", {
|
|
1312
|
+
enumerable: true,
|
|
1313
|
+
get: function() {
|
|
1314
|
+
return _embedpdf_engine_core_runtime.decodePageKey;
|
|
1315
|
+
}
|
|
1316
|
+
});
|
|
910
1317
|
exports.definePlugin = definePlugin;
|
|
911
|
-
exports.
|
|
912
|
-
exports.
|
|
913
|
-
exports.
|
|
1318
|
+
exports.defineState = defineState;
|
|
1319
|
+
exports.documentState = documentState;
|
|
1320
|
+
exports.documentsState = documentsState;
|
|
1321
|
+
Object.defineProperty(exports, "encodePageKey", {
|
|
1322
|
+
enumerable: true,
|
|
1323
|
+
get: function() {
|
|
1324
|
+
return _embedpdf_engine_core_runtime.encodePageKey;
|
|
1325
|
+
}
|
|
1326
|
+
});
|
|
1327
|
+
exports.guardHandle = require_settings.guardHandle;
|
|
1328
|
+
exports.isCancelled = require_settings.isCancelled;
|
|
1329
|
+
Object.defineProperty(exports, "isLocalEngine", {
|
|
1330
|
+
enumerable: true,
|
|
1331
|
+
get: function() {
|
|
1332
|
+
return _embedpdf_engine_core_runtime.isLocalEngine;
|
|
1333
|
+
}
|
|
1334
|
+
});
|
|
1335
|
+
exports.isPluginError = require_settings.isPluginError;
|
|
1336
|
+
exports.isReadMember = isReadMember;
|
|
1337
|
+
exports.memo = memo;
|
|
1338
|
+
exports.memoByKey = memoByKey;
|
|
1339
|
+
exports.mergeSettings = require_settings.mergeSettings;
|
|
1340
|
+
Object.defineProperty(exports, "pageRefsEqual", {
|
|
1341
|
+
enumerable: true,
|
|
1342
|
+
get: function() {
|
|
1343
|
+
return _embedpdf_engine_core_runtime.pageRefsEqual;
|
|
1344
|
+
}
|
|
1345
|
+
});
|
|
1346
|
+
exports.reload = require_settings.reload;
|
|
1347
|
+
Object.defineProperty(exports, "resolvePageLayers", {
|
|
1348
|
+
enumerable: true,
|
|
1349
|
+
get: function() {
|
|
1350
|
+
return _embedpdf_engine_core_runtime.resolvePageLayers;
|
|
1351
|
+
}
|
|
1352
|
+
});
|
|
1353
|
+
exports.returnsPromise = returnsPromise;
|
|
1354
|
+
exports.shallowEqual = shallowEqual;
|
|
914
1355
|
Object.defineProperty(exports, "snapAppearanceScale", {
|
|
915
1356
|
enumerable: true,
|
|
916
1357
|
get: function() {
|
|
@@ -929,5 +1370,16 @@ Object.defineProperty(exports, "snapTileScale", {
|
|
|
929
1370
|
return _embedpdf_engine_core_runtime.snapTileScale;
|
|
930
1371
|
}
|
|
931
1372
|
});
|
|
1373
|
+
exports.standInFor = standInFor;
|
|
1374
|
+
exports.timerClock = require_settings.timerClock;
|
|
1375
|
+
Object.defineProperty(exports, "toPageRef", {
|
|
1376
|
+
enumerable: true,
|
|
1377
|
+
get: function() {
|
|
1378
|
+
return _embedpdf_engine_core_runtime.toPageRef;
|
|
1379
|
+
}
|
|
1380
|
+
});
|
|
1381
|
+
exports.toPluginError = require_settings.toPluginError;
|
|
1382
|
+
exports.toPluginErrorInfo = require_settings.toPluginErrorInfo;
|
|
1383
|
+
exports.viewerSettingsOf = viewerSettingsOf;
|
|
932
1384
|
|
|
933
1385
|
//# sourceMappingURL=index.cjs.map
|