@embedpdf/plugin-metadata 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.
@@ -0,0 +1,22 @@
1
+ //#region src/token.ts
2
+ /** The package token — created once here; `contract.ts` re-exports it and
3
+ * `host-contract.ts` widens it. */
4
+ const MetadataToken = (0, require("@embedpdf/core").createCapabilityToken)("metadata", {
5
+ hint: "add metadataPlugin() from '@embedpdf/plugin-metadata' to your plugins list",
6
+ promises: {
7
+ update: true,
8
+ refresh: true,
9
+ ensureLoaded: true,
10
+ "custom.update": true,
11
+ "custom.refresh": true
12
+ }
13
+ });
14
+ //#endregion
15
+ Object.defineProperty(exports, "MetadataToken", {
16
+ enumerable: true,
17
+ get: function() {
18
+ return MetadataToken;
19
+ }
20
+ });
21
+
22
+ //# sourceMappingURL=contract-BQQulRec.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contract-BQQulRec.cjs","names":[],"sources":["../src/token.ts"],"sourcesContent":["/** The package token — created once here; `contract.ts` re-exports it and\n * `host-contract.ts` widens it. */\nimport { createCapabilityToken } from '@embedpdf/core';\n\nimport type { MetadataCapability } from './contract';\n\nexport const MetadataToken = createCapabilityToken<MetadataCapability>('metadata', {\n hint: \"add metadataPlugin() from '@embedpdf/plugin-metadata' to your plugins list\",\n // Without a document, an adapter's stand-in rejects these with `not-ready`, as the\n // capability would; the type asks for every member that returns a promise.\n promises: {\n update: true,\n refresh: true,\n ensureLoaded: true,\n 'custom.update': true,\n 'custom.refresh': true,\n },\n});\n"],"mappings":";;;AAMA,MAAa,iBAAA,2BAAA,CAAA,CAAA,sBAAA,CAA0D,YAAY;CACjF,MAAM;CAGN,UAAU;EACR,QAAQ;EACR,SAAS;EACT,cAAc;EACd,iBAAiB;EACjB,kBAAkB;CACpB;AACF,CAAC"}
@@ -0,0 +1,101 @@
1
+ import { CustomMetadata, CustomMetadataPatch, DocumentMetadata, EventHook, EventOrigin, MetadataPatch, OperationOptions, ResourceStatus } from "@embedpdf/core";
2
+ //#region src/token.d.ts
3
+ declare const MetadataToken: import("@embedpdf/core").CapabilityToken<MetadataCapability>;
4
+ //#endregion
5
+ //#region src/contract.d.ts
6
+ /**
7
+ * A confirmed change of the Info dict's standard fields. Local edits, other
8
+ * plugins, scripts and other sessions all reach the same confirmed
9
+ * `metadata.updated` document event, which is the one path that updates the
10
+ * snapshot and fires this.
11
+ */
12
+ interface MetadataUpdatedEvent {
13
+ readonly metadata: DocumentMetadata;
14
+ readonly previous: DocumentMetadata | null;
15
+ readonly changedKeys: readonly (keyof DocumentMetadata)[];
16
+ readonly origin: EventOrigin;
17
+ }
18
+ /** The metadata was (re)loaded from the engine. */
19
+ interface MetadataResyncedEvent {
20
+ readonly metadata: DocumentMetadata;
21
+ }
22
+ /** A confirmed change of the custom keys (`metadata.customUpdated`), whoever caused it. */
23
+ interface CustomMetadataUpdatedEvent {
24
+ readonly custom: CustomMetadata;
25
+ readonly previous: CustomMetadata | null;
26
+ /** The keys that were added, changed or removed. */
27
+ readonly changedKeys: readonly string[];
28
+ readonly origin: EventOrigin;
29
+ }
30
+ /** The custom keys were (re)loaded from the engine. */
31
+ interface CustomMetadataResyncedEvent {
32
+ readonly custom: CustomMetadata;
33
+ }
34
+ /**
35
+ * The Info dict's custom keys: the same reads and verbs as the standard
36
+ * fields, on an object of their own. Changing them needs the same permission,
37
+ * so `canUpdate()` on the metadata capability answers for both.
38
+ */
39
+ interface CustomMetadataCapability {
40
+ /** The current keys, reference-stable until they change; `null` until the first read lands. */
41
+ getSnapshot(): CustomMetadata | null;
42
+ /** Load state: `idle` until `ensureLoaded()` asks, then `loading` → `ready`, or `forbidden` / `error`. */
43
+ getStatus(): ResourceStatus;
44
+ /**
45
+ * Set and remove keys: a key left out stays, `null` removes it, a string
46
+ * sets it. Resolves `{ custom }`, the keys after the change; by then the
47
+ * snapshot shows it and `onUpdated` has fired. Rejects with
48
+ * `permission-denied`, `invalid-input`, `instance-closed` or
49
+ * `operation-cancelled`.
50
+ */
51
+ update(patch: CustomMetadataPatch, options?: OperationOptions): Promise<{
52
+ readonly custom: CustomMetadata;
53
+ }>;
54
+ /** Re-read from the engine. Rarely needed: the event stream keeps the snapshot fresh. */
55
+ refresh(options?: OperationOptions): Promise<void>;
56
+ /** A confirmed change, whoever caused it. Fires after the snapshot changed. */
57
+ readonly onUpdated: EventHook<CustomMetadataUpdatedEvent>;
58
+ /** The keys were loaded or reloaded from the engine. */
59
+ readonly onResynced: EventHook<CustomMetadataResyncedEvent>;
60
+ }
61
+ interface MetadataCapability {
62
+ /**
63
+ * The current metadata, reference-stable until it changes; `null` until
64
+ * the first read lands or while reading is forbidden — see `getStatus()`.
65
+ */
66
+ getSnapshot(): DocumentMetadata | null;
67
+ /** Load state: `idle` until `ensureLoaded()` asks, then `loading` → `ready`, or `forbidden` / `error`. */
68
+ getStatus(): ResourceStatus;
69
+ /**
70
+ * Read the standard fields and the custom keys from the document, unless they are loaded or
71
+ * loading: nothing reads them when a document opens. Resolves once both have landed, when
72
+ * `onResynced` has fired; a later call resolves at once. Rejects with `permission-denied` when
73
+ * reading is refused, `operation-cancelled` or `instance-closed`; after a failure it reads again.
74
+ */
75
+ ensureLoaded(options?: OperationOptions): Promise<void>;
76
+ /**
77
+ * Whether this session may change the Info dict (`doc.metadata.modify`):
78
+ * `update()` and `custom.update()`. The engine enforces the same permission.
79
+ */
80
+ canUpdate(): boolean;
81
+ /**
82
+ * Change the standard fields: a field left out stays, `null` clears it, a
83
+ * value sets it. Resolves `{ metadata }`, the fields after the change; by
84
+ * then the snapshot shows it and `onUpdated` has fired. Rejects with
85
+ * `permission-denied`, `instance-closed` or `operation-cancelled`.
86
+ */
87
+ update(patch: MetadataPatch, options?: OperationOptions): Promise<{
88
+ readonly metadata: DocumentMetadata;
89
+ }>;
90
+ /** Re-read from the engine. Rarely needed: the event stream keeps the snapshot fresh. */
91
+ refresh(options?: OperationOptions): Promise<void>;
92
+ /** A confirmed change, whoever caused it. Fires after the snapshot changed. */
93
+ readonly onUpdated: EventHook<MetadataUpdatedEvent>;
94
+ /** The metadata was loaded or reloaded from the engine. */
95
+ readonly onResynced: EventHook<MetadataResyncedEvent>;
96
+ /** The Info dict's other keys. */
97
+ readonly custom: CustomMetadataCapability;
98
+ }
99
+ //#endregion
100
+ export { MetadataResyncedEvent as a, MetadataCapability as i, CustomMetadataResyncedEvent as n, MetadataUpdatedEvent as o, CustomMetadataUpdatedEvent as r, MetadataToken as s, CustomMetadataCapability as t };
101
+ //# sourceMappingURL=contract-Cljrf00d.d.cts.map
@@ -0,0 +1,101 @@
1
+ import { CustomMetadata, CustomMetadataPatch, DocumentMetadata, EventHook, EventOrigin, MetadataPatch, OperationOptions, ResourceStatus } from "@embedpdf/core";
2
+ //#region src/token.d.ts
3
+ declare const MetadataToken: import("@embedpdf/core").CapabilityToken<MetadataCapability>;
4
+ //#endregion
5
+ //#region src/contract.d.ts
6
+ /**
7
+ * A confirmed change of the Info dict's standard fields. Local edits, other
8
+ * plugins, scripts and other sessions all reach the same confirmed
9
+ * `metadata.updated` document event, which is the one path that updates the
10
+ * snapshot and fires this.
11
+ */
12
+ interface MetadataUpdatedEvent {
13
+ readonly metadata: DocumentMetadata;
14
+ readonly previous: DocumentMetadata | null;
15
+ readonly changedKeys: readonly (keyof DocumentMetadata)[];
16
+ readonly origin: EventOrigin;
17
+ }
18
+ /** The metadata was (re)loaded from the engine. */
19
+ interface MetadataResyncedEvent {
20
+ readonly metadata: DocumentMetadata;
21
+ }
22
+ /** A confirmed change of the custom keys (`metadata.customUpdated`), whoever caused it. */
23
+ interface CustomMetadataUpdatedEvent {
24
+ readonly custom: CustomMetadata;
25
+ readonly previous: CustomMetadata | null;
26
+ /** The keys that were added, changed or removed. */
27
+ readonly changedKeys: readonly string[];
28
+ readonly origin: EventOrigin;
29
+ }
30
+ /** The custom keys were (re)loaded from the engine. */
31
+ interface CustomMetadataResyncedEvent {
32
+ readonly custom: CustomMetadata;
33
+ }
34
+ /**
35
+ * The Info dict's custom keys: the same reads and verbs as the standard
36
+ * fields, on an object of their own. Changing them needs the same permission,
37
+ * so `canUpdate()` on the metadata capability answers for both.
38
+ */
39
+ interface CustomMetadataCapability {
40
+ /** The current keys, reference-stable until they change; `null` until the first read lands. */
41
+ getSnapshot(): CustomMetadata | null;
42
+ /** Load state: `idle` until `ensureLoaded()` asks, then `loading` → `ready`, or `forbidden` / `error`. */
43
+ getStatus(): ResourceStatus;
44
+ /**
45
+ * Set and remove keys: a key left out stays, `null` removes it, a string
46
+ * sets it. Resolves `{ custom }`, the keys after the change; by then the
47
+ * snapshot shows it and `onUpdated` has fired. Rejects with
48
+ * `permission-denied`, `invalid-input`, `instance-closed` or
49
+ * `operation-cancelled`.
50
+ */
51
+ update(patch: CustomMetadataPatch, options?: OperationOptions): Promise<{
52
+ readonly custom: CustomMetadata;
53
+ }>;
54
+ /** Re-read from the engine. Rarely needed: the event stream keeps the snapshot fresh. */
55
+ refresh(options?: OperationOptions): Promise<void>;
56
+ /** A confirmed change, whoever caused it. Fires after the snapshot changed. */
57
+ readonly onUpdated: EventHook<CustomMetadataUpdatedEvent>;
58
+ /** The keys were loaded or reloaded from the engine. */
59
+ readonly onResynced: EventHook<CustomMetadataResyncedEvent>;
60
+ }
61
+ interface MetadataCapability {
62
+ /**
63
+ * The current metadata, reference-stable until it changes; `null` until
64
+ * the first read lands or while reading is forbidden — see `getStatus()`.
65
+ */
66
+ getSnapshot(): DocumentMetadata | null;
67
+ /** Load state: `idle` until `ensureLoaded()` asks, then `loading` → `ready`, or `forbidden` / `error`. */
68
+ getStatus(): ResourceStatus;
69
+ /**
70
+ * Read the standard fields and the custom keys from the document, unless they are loaded or
71
+ * loading: nothing reads them when a document opens. Resolves once both have landed, when
72
+ * `onResynced` has fired; a later call resolves at once. Rejects with `permission-denied` when
73
+ * reading is refused, `operation-cancelled` or `instance-closed`; after a failure it reads again.
74
+ */
75
+ ensureLoaded(options?: OperationOptions): Promise<void>;
76
+ /**
77
+ * Whether this session may change the Info dict (`doc.metadata.modify`):
78
+ * `update()` and `custom.update()`. The engine enforces the same permission.
79
+ */
80
+ canUpdate(): boolean;
81
+ /**
82
+ * Change the standard fields: a field left out stays, `null` clears it, a
83
+ * value sets it. Resolves `{ metadata }`, the fields after the change; by
84
+ * then the snapshot shows it and `onUpdated` has fired. Rejects with
85
+ * `permission-denied`, `instance-closed` or `operation-cancelled`.
86
+ */
87
+ update(patch: MetadataPatch, options?: OperationOptions): Promise<{
88
+ readonly metadata: DocumentMetadata;
89
+ }>;
90
+ /** Re-read from the engine. Rarely needed: the event stream keeps the snapshot fresh. */
91
+ refresh(options?: OperationOptions): Promise<void>;
92
+ /** A confirmed change, whoever caused it. Fires after the snapshot changed. */
93
+ readonly onUpdated: EventHook<MetadataUpdatedEvent>;
94
+ /** The metadata was loaded or reloaded from the engine. */
95
+ readonly onResynced: EventHook<MetadataResyncedEvent>;
96
+ /** The Info dict's other keys. */
97
+ readonly custom: CustomMetadataCapability;
98
+ }
99
+ //#endregion
100
+ export { MetadataResyncedEvent as a, MetadataCapability as i, CustomMetadataResyncedEvent as n, MetadataUpdatedEvent as o, CustomMetadataUpdatedEvent as r, MetadataToken as s, CustomMetadataCapability as t };
101
+ //# sourceMappingURL=contract-Cljrf00d.d.ts.map
@@ -0,0 +1,18 @@
1
+ import { createCapabilityToken } from "@embedpdf/core";
2
+ //#region src/token.ts
3
+ /** The package token — created once here; `contract.ts` re-exports it and
4
+ * `host-contract.ts` widens it. */
5
+ const MetadataToken = createCapabilityToken("metadata", {
6
+ hint: "add metadataPlugin() from '@embedpdf/plugin-metadata' to your plugins list",
7
+ promises: {
8
+ update: true,
9
+ refresh: true,
10
+ ensureLoaded: true,
11
+ "custom.update": true,
12
+ "custom.refresh": true
13
+ }
14
+ });
15
+ //#endregion
16
+ export { MetadataToken as t };
17
+
18
+ //# sourceMappingURL=contract-o3j9MoH5.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contract-o3j9MoH5.js","names":[],"sources":["../src/token.ts"],"sourcesContent":["/** The package token — created once here; `contract.ts` re-exports it and\n * `host-contract.ts` widens it. */\nimport { createCapabilityToken } from '@embedpdf/core';\n\nimport type { MetadataCapability } from './contract';\n\nexport const MetadataToken = createCapabilityToken<MetadataCapability>('metadata', {\n hint: \"add metadataPlugin() from '@embedpdf/plugin-metadata' to your plugins list\",\n // Without a document, an adapter's stand-in rejects these with `not-ready`, as the\n // capability would; the type asks for every member that returns a promise.\n promises: {\n update: true,\n refresh: true,\n ensureLoaded: true,\n 'custom.update': true,\n 'custom.refresh': true,\n },\n});\n"],"mappings":";;;;AAMA,MAAa,gBAAgB,sBAA0C,YAAY;CACjF,MAAM;CAGN,UAAU;EACR,QAAQ;EACR,SAAS;EACT,cAAc;EACd,iBAAiB;EACjB,kBAAkB;CACpB;AACF,CAAC"}
package/dist/contract.cjs CHANGED
@@ -1,3 +1,3 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_contract = require("./contract-CqhcLHxo.cjs");
2
+ const require_contract = require("./contract-BQQulRec.cjs");
3
3
  exports.MetadataToken = require_contract.MetadataToken;
@@ -1,2 +1,2 @@
1
- import { i as MetadataToken, n as MetadataCapability, r as MetadataState } from "./contract-Dk5SGhEP.cjs";
2
- export { type MetadataCapability, type MetadataState, MetadataToken };
1
+ import { a as MetadataResyncedEvent, i as MetadataCapability, n as CustomMetadataResyncedEvent, o as MetadataUpdatedEvent, r as CustomMetadataUpdatedEvent, s as MetadataToken, t as CustomMetadataCapability } from "./contract-Cljrf00d.cjs";
2
+ export { CustomMetadataCapability, CustomMetadataResyncedEvent, CustomMetadataUpdatedEvent, MetadataCapability, MetadataResyncedEvent, MetadataToken, MetadataUpdatedEvent };
@@ -1,2 +1,2 @@
1
- import { i as MetadataToken, n as MetadataCapability, r as MetadataState } from "./contract-Dk5SGhEP.js";
2
- export { type MetadataCapability, type MetadataState, MetadataToken };
1
+ import { a as MetadataResyncedEvent, i as MetadataCapability, n as CustomMetadataResyncedEvent, o as MetadataUpdatedEvent, r as CustomMetadataUpdatedEvent, s as MetadataToken, t as CustomMetadataCapability } from "./contract-Cljrf00d.js";
2
+ export { CustomMetadataCapability, CustomMetadataResyncedEvent, CustomMetadataUpdatedEvent, MetadataCapability, MetadataResyncedEvent, MetadataToken, MetadataUpdatedEvent };
package/dist/contract.js CHANGED
@@ -1,2 +1,2 @@
1
- import { t as MetadataToken } from "./contract-DXGV7Wpx.js";
1
+ import { t as MetadataToken } from "./contract-o3j9MoH5.js";
2
2
  export { MetadataToken };
package/dist/index.cjs CHANGED
@@ -1,102 +1,135 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_contract = require("./contract-CqhcLHxo.cjs");
2
+ const require_contract = require("./contract-BQQulRec.cjs");
3
3
  let _embedpdf_core = require("@embedpdf/core");
4
- //#region src/capability.ts
4
+ //#region src/model.ts
5
5
  /**
6
- * Authorization for Info-dict writes. Maps to PDF bit 4 (MODIFY). The engine
7
- * ALSO enforces this — `metadata.update` throws `PermissionDenied` without it,
8
- * identically on local and cloud — so `canEdit()` is the UI mirror of a real
9
- * guard, not the guard itself. Typed as `DocCapability` so a typo is a compile
10
- * error.
6
+ * The keys whose values differ between two snapshots, removed keys included.
7
+ * Both snapshots (the standard fields, the custom keys) hold only strings,
8
+ * `null` and enums, so a shallow compare is exact.
11
9
  */
12
- const METADATA_MODIFY_CAPABILITY = "doc.metadata.modify";
10
+ function changedKeys(previous, next) {
11
+ const keys = Object.keys(next);
12
+ if (!previous) return keys;
13
+ return [.../* @__PURE__ */ new Set([...Object.keys(previous), ...keys])].filter((key) => previous[key] !== next[key]);
14
+ }
15
+ //#endregion
16
+ //#region src/controller.ts
17
+ const METADATA_MODIFY = "doc.metadata.modify";
13
18
  /**
14
- * Reads/writes the document's Info dict through the engine handle and keeps the
15
- * plugin's state in sync. The live-update wiring (own + remote events) lives in
16
- * the effect; this is the read/write surface.
19
+ * The metadata controller. Both halves of the Info dict are mirrors: each
20
+ * changes only when a load lands or its confirmed document event arrives
21
+ * (`metadata.updated`, `metadata.customUpdated`), whoever caused the edit,
22
+ * and that is also the one place its `onUpdated` and `onResynced` fire.
23
+ * Nothing needs them when a document opens, so they load on demand: the
24
+ * first `ensureLoaded()` (what `metadataState` asks for) reads them.
17
25
  */
18
- function createMetadataCapability(ctx) {
19
- const requireDoc = () => {
20
- const doc = ctx.doc;
21
- if (!doc) throw new Error("[metadata] no document bound");
22
- return doc;
23
- };
24
- return {
25
- canEdit() {
26
- return ctx.doc?.security.allows(METADATA_MODIFY_CAPABILITY) ?? false;
27
- },
28
- current: () => ctx.getState().metadata,
29
- update: async (patch) => {
30
- const result = await requireDoc().metadata.update(patch);
31
- ctx.dispatch({
32
- type: "SET",
33
- metadata: result.metadata
26
+ function createMetadataController(ctx) {
27
+ const updated = ctx.events.source();
28
+ const resynced = ctx.events.source();
29
+ const customUpdated = ctx.events.source();
30
+ const customResynced = ctx.events.source();
31
+ const metadata = ctx.mirror({
32
+ name: "metadata",
33
+ onDemand: true,
34
+ initial: () => null,
35
+ load: async (doc) => ({ value: await doc.metadata.get() }),
36
+ fold: (value, event) => event.type === "metadata.updated" ? event.metadata : value,
37
+ changed: ({ cause, event, previous, next }) => {
38
+ if (!next) return;
39
+ if (cause === "load") resynced.emit({ metadata: next });
40
+ else if (event && "origin" in event) updated.emit({
41
+ metadata: next,
42
+ previous,
43
+ changedKeys: changedKeys(previous, next),
44
+ origin: event.origin
34
45
  });
35
- },
36
- reload: async () => {
37
- const metadata = await requireDoc().metadata.read();
38
- ctx.dispatch({
39
- type: "SET",
40
- metadata
46
+ }
47
+ });
48
+ const custom = ctx.mirror({
49
+ name: "customMetadata",
50
+ onDemand: true,
51
+ initial: () => null,
52
+ load: async (doc) => ({ value: await doc.metadata.custom.get() }),
53
+ fold: (value, event) => event.type === "metadata.customUpdated" ? event.custom : value,
54
+ changed: ({ cause, event, previous, next }) => {
55
+ if (!next) return;
56
+ if (cause === "load") customResynced.emit({ custom: next });
57
+ else if (event && "origin" in event) customUpdated.emit({
58
+ custom: next,
59
+ previous,
60
+ changedKeys: changedKeys(previous, next),
61
+ origin: event.origin
41
62
  });
42
63
  }
43
- };
44
- }
45
- //#endregion
46
- //#region src/effects.ts
47
- /**
48
- * Makes metadata REACTIVE — the reason this is a plugin, not a kernel IO call.
49
- *
50
- * 1. Seed: read the Info dict once when the document opens.
51
- * 2. Live: subscribe to the document event stream. A `metadata.updated` event
52
- * carries the full re-read metadata and fires for OWN edits AND for remote
53
- * edits delivered over SSE (another session) — both just become `setState`.
54
- *
55
- * The handle's stream is synchronous fan-out, so this coexists with the kernel's
56
- * own subscription (page registry) on the same handle.
57
- */
58
- function registerMetadataEffects(ctx) {
59
- const doc = ctx.doc;
60
- if (!doc) return;
61
- doc.metadata.read().then((metadata) => ctx.dispatch({
62
- type: "SET",
63
- metadata
64
- }), () => {});
65
- const unsubscribe = doc.events.subscribe((event) => {
66
- if (event.type === "metadata.updated") ctx.dispatch({
67
- type: "SET",
68
- metadata: event.metadata
69
- });
70
64
  });
71
- ctx.cleanup(unsubscribe);
65
+ const canUpdate = () => ctx.allows(METADATA_MODIFY);
66
+ /**
67
+ * Both writes refuse the same way before reaching the engine: the verbs are
68
+ * async, so a refusal rejects, and nothing changes.
69
+ */
70
+ const refuseWrite = (verb, options) => {
71
+ ctx.assertAllowed(METADATA_MODIFY, verb);
72
+ if (options?.signal?.aborted) throw new _embedpdf_core.PluginError("operation-cancelled", "metadata", `${verb} was cancelled`);
73
+ };
74
+ const customApi = {
75
+ getSnapshot: custom.get,
76
+ getStatus: custom.getStatus,
77
+ update: async (patch, options) => {
78
+ refuseWrite("metadata.custom.update", options);
79
+ return { custom: (await ctx.cancellable(options?.signal, ctx.doc.metadata.custom.update(patch))).custom };
80
+ },
81
+ refresh: (options) => ctx.cancellable(options?.signal, custom.refresh()),
82
+ onUpdated: customUpdated.on,
83
+ onResynced: customResynced.on
84
+ };
85
+ return { api: {
86
+ getSnapshot: metadata.get,
87
+ getStatus: metadata.getStatus,
88
+ ensureLoaded: (options) => ctx.cancellable(options?.signal, Promise.all([metadata.ensureLoaded(), custom.ensureLoaded()]).then(() => void 0)),
89
+ canUpdate,
90
+ update: async (patch, options) => {
91
+ refuseWrite("metadata.update", options);
92
+ return { metadata: (await ctx.cancellable(options?.signal, ctx.doc.metadata.update(patch))).metadata };
93
+ },
94
+ refresh: (options) => ctx.cancellable(options?.signal, metadata.refresh()),
95
+ onUpdated: updated.on,
96
+ onResynced: resynced.on,
97
+ custom: customApi
98
+ } };
72
99
  }
73
100
  //#endregion
74
- //#region src/reducer.ts
75
- const initialMetadataState = () => ({ metadata: null });
76
- /** Pure. The only transition: replace the metadata snapshot (from a read or an
77
- * event). */
78
- const metadataReducer = (state, a) => a.type === "SET" ? {
79
- ...state,
80
- metadata: a.metadata
81
- } : state;
82
- //#endregion
83
101
  //#region src/metadata.plugin.ts
84
- /**
85
- * Document-scoped metadata plugin: reactive Info-dict state. The effect seeds it
86
- * from the engine and keeps it live off the document event stream (own + remote
87
- * SSE edits); the capability is the read/write surface.
88
- */
102
+ /** Document-scoped, reactive Info-dict metadata. Takes no configuration. */
89
103
  const metadataPlugin = () => (0, _embedpdf_core.definePlugin)({
90
104
  id: "metadata",
91
105
  token: require_contract.MetadataToken,
92
106
  scope: "document",
93
- initialState: initialMetadataState,
94
- reduce: metadataReducer,
95
- capability: createMetadataCapability,
96
- effects: registerMetadataEffects
107
+ create: createMetadataController
108
+ });
109
+ //#endregion
110
+ //#region src/state.ts
111
+ /**
112
+ * The metadata page's State table as code: what `useMetadataState()` returns,
113
+ * and the same fields in every other framework. `status` is the standard
114
+ * fields' load state; `custom.getStatus()` has the custom keys' own. Reading
115
+ * the state loads it (`ensure`): the first reader of a document starts the read.
116
+ */
117
+ const metadataState = (0, _embedpdf_core.defineState)(require_contract.MetadataToken, {
118
+ read: (metadata) => ({
119
+ metadata: metadata.getSnapshot(),
120
+ custom: metadata.custom.getSnapshot(),
121
+ status: metadata.getStatus()
122
+ }),
123
+ empty: {
124
+ metadata: null,
125
+ custom: null,
126
+ status: "idle"
127
+ },
128
+ ensure: (metadata) => metadata.ensureLoaded()
97
129
  });
98
130
  //#endregion
99
131
  exports.MetadataToken = require_contract.MetadataToken;
100
132
  exports.metadataPlugin = metadataPlugin;
133
+ exports.metadataState = metadataState;
101
134
 
102
135
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":["MetadataToken"],"sources":["../src/capability.ts","../src/effects.ts","../src/reducer.ts","../src/metadata.plugin.ts"],"sourcesContent":["import type { PluginContext, DocCapability } from '@embedpdf/core';\nimport type { MetadataAction, MetadataCapability, MetadataState } from './types';\n\n/**\n * Authorization for Info-dict writes. Maps to PDF bit 4 (MODIFY). The engine\n * ALSO enforces this — `metadata.update` throws `PermissionDenied` without it,\n * identically on local and cloud — so `canEdit()` is the UI mirror of a real\n * guard, not the guard itself. Typed as `DocCapability` so a typo is a compile\n * error.\n */\nconst METADATA_MODIFY_CAPABILITY: DocCapability = 'doc.metadata.modify';\n\n/**\n * Reads/writes the document's Info dict through the engine handle and keeps the\n * plugin's state in sync. The live-update wiring (own + remote events) lives in\n * the effect; this is the read/write surface.\n */\nexport function createMetadataCapability(\n ctx: PluginContext<MetadataState, MetadataAction>,\n): MetadataCapability {\n const requireDoc = () => {\n const doc = ctx.doc;\n if (!doc) throw new Error('[metadata] no document bound');\n return doc;\n };\n\n return {\n canEdit() {\n // Wildcard-aware predicate (mirrors the engine's own enforcement),\n // identical on both engines. NOT `effectiveScope.includes(...)` — that\n // enumeration drops the `*` admin grant, so it would hide the UI on a\n // default admin open even though the engine allows the edit.\n return ctx.doc?.security.allows(METADATA_MODIFY_CAPABILITY) ?? false;\n },\n\n current: () => ctx.getState().metadata,\n\n update: async (patch) => {\n // The write also emits a `metadata.updated` event the effect catches, so\n // this dispatch is just for an immediate, in-sync update (idempotent).\n const result = await requireDoc().metadata.update(patch);\n ctx.dispatch({ type: 'SET', metadata: result.metadata });\n },\n\n reload: async () => {\n const metadata = await requireDoc().metadata.read();\n ctx.dispatch({ type: 'SET', metadata });\n },\n };\n}\n","import type { DocumentEvent, EffectContext } from '@embedpdf/core';\nimport type { MetadataAction, MetadataState } from './types';\n\n/**\n * Makes metadata REACTIVE — the reason this is a plugin, not a kernel IO call.\n *\n * 1. Seed: read the Info dict once when the document opens.\n * 2. Live: subscribe to the document event stream. A `metadata.updated` event\n * carries the full re-read metadata and fires for OWN edits AND for remote\n * edits delivered over SSE (another session) — both just become `setState`.\n *\n * The handle's stream is synchronous fan-out, so this coexists with the kernel's\n * own subscription (page registry) on the same handle.\n */\nexport function registerMetadataEffects(ctx: EffectContext<MetadataState, MetadataAction>): void {\n const doc = ctx.doc;\n if (!doc) return;\n\n // 1. seed\n void doc.metadata.read().then(\n (metadata) => ctx.dispatch({ type: 'SET', metadata }),\n () => {}, // doc closed / read aborted — ignore\n );\n\n // 2. live updates (own edits + remote SSE edits)\n const unsubscribe = doc.events.subscribe((event: DocumentEvent) => {\n if (event.type === 'metadata.updated') {\n ctx.dispatch({ type: 'SET', metadata: event.metadata });\n }\n });\n ctx.cleanup(unsubscribe);\n}\n","import type { MetadataAction, MetadataState } from './types';\n\nexport const initialMetadataState = (): MetadataState => ({ metadata: null });\n\n/** Pure. The only transition: replace the metadata snapshot (from a read or an\n * event). */\nexport const metadataReducer = (state: MetadataState, a: MetadataAction): MetadataState =>\n a.type === 'SET' ? { ...state, metadata: a.metadata } : state;\n","import { definePlugin } from '@embedpdf/core';\nimport { createMetadataCapability } from './capability';\nimport { registerMetadataEffects } from './effects';\nimport { initialMetadataState, metadataReducer } from './reducer';\nimport { MetadataToken } from './types';\nimport type { MetadataAction, MetadataCapability, MetadataState } from './types';\n\n/**\n * Document-scoped metadata plugin: reactive Info-dict state. The effect seeds it\n * from the engine and keeps it live off the document event stream (own + remote\n * SSE edits); the capability is the read/write surface.\n */\nexport const metadataPlugin = () =>\n definePlugin<MetadataState, MetadataAction, MetadataCapability>({\n id: 'metadata',\n token: MetadataToken,\n scope: 'document',\n initialState: initialMetadataState,\n reduce: metadataReducer,\n capability: createMetadataCapability,\n effects: registerMetadataEffects,\n });\n"],"mappings":";;;;;;;;;;;AAUA,MAAM,6BAA4C;;;;;;AAOlD,SAAgB,yBACd,KACoB;CACpB,MAAM,mBAAmB;EACvB,MAAM,MAAM,IAAI;EAChB,IAAI,CAAC,KAAK,MAAM,IAAI,MAAM,8BAA8B;EACxD,OAAO;CACT;CAEA,OAAO;EACL,UAAU;GAKR,OAAO,IAAI,KAAK,SAAS,OAAO,0BAA0B,KAAK;EACjE;EAEA,eAAe,IAAI,SAAS,CAAC,CAAC;EAE9B,QAAQ,OAAO,UAAU;GAGvB,MAAM,SAAS,MAAM,WAAW,CAAC,CAAC,SAAS,OAAO,KAAK;GACvD,IAAI,SAAS;IAAE,MAAM;IAAO,UAAU,OAAO;GAAS,CAAC;EACzD;EAEA,QAAQ,YAAY;GAClB,MAAM,WAAW,MAAM,WAAW,CAAC,CAAC,SAAS,KAAK;GAClD,IAAI,SAAS;IAAE,MAAM;IAAO;GAAS,CAAC;EACxC;CACF;AACF;;;;;;;;;;;;;;ACnCA,SAAgB,wBAAwB,KAAyD;CAC/F,MAAM,MAAM,IAAI;CAChB,IAAI,CAAC,KAAK;CAGV,IAAS,SAAS,KAAK,CAAC,CAAC,MACtB,aAAa,IAAI,SAAS;EAAE,MAAM;EAAO;CAAS,CAAC,SAC9C,CAAC,CACT;CAGA,MAAM,cAAc,IAAI,OAAO,WAAW,UAAyB;EACjE,IAAI,MAAM,SAAS,oBACjB,IAAI,SAAS;GAAE,MAAM;GAAO,UAAU,MAAM;EAAS,CAAC;CAE1D,CAAC;CACD,IAAI,QAAQ,WAAW;AACzB;;;AC7BA,MAAa,8BAA6C,EAAE,UAAU,KAAK;;;AAI3E,MAAa,mBAAmB,OAAsB,MACpD,EAAE,SAAS,QAAQ;CAAE,GAAG;CAAO,UAAU,EAAE;AAAS,IAAI;;;;;;;;ACK1D,MAAa,wBAAA,GAAA,eAAA,aAAA,CACqD;CAC9D,IAAI;CACJ,OAAOA,iBAAAA;CACP,OAAO;CACP,cAAc;CACd,QAAQ;CACR,YAAY;CACZ,SAAS;AACX,CAAC"}
1
+ {"version":3,"file":"index.cjs","names":["PluginError","MetadataToken","MetadataToken"],"sources":["../src/model.ts","../src/controller.ts","../src/metadata.plugin.ts","../src/state.ts"],"sourcesContent":["/**\n * The keys whose values differ between two snapshots, removed keys included.\n * Both snapshots (the standard fields, the custom keys) hold only strings,\n * `null` and enums, so a shallow compare is exact.\n */\nexport function changedKeys<T extends object>(\n previous: T | null,\n next: T,\n): readonly (keyof T & string)[] {\n const keys = Object.keys(next) as (keyof T & string)[];\n if (!previous) return keys;\n const all = new Set([...(Object.keys(previous) as (keyof T & string)[]), ...keys]);\n return [...all].filter((key) => previous[key] !== next[key]);\n}\n","import {\n PluginError,\n type PluginContext,\n type CustomMetadata,\n type DocCapability,\n type DocumentMetadata,\n type OperationOptions,\n} from '@embedpdf/core';\n\nimport type {\n CustomMetadataCapability,\n CustomMetadataResyncedEvent,\n CustomMetadataUpdatedEvent,\n MetadataCapability,\n MetadataResyncedEvent,\n MetadataUpdatedEvent,\n} from './contract';\nimport { changedKeys } from './model';\n\nconst METADATA_MODIFY: DocCapability = 'doc.metadata.modify';\n\n/**\n * The metadata controller. Both halves of the Info dict are mirrors: each\n * changes only when a load lands or its confirmed document event arrives\n * (`metadata.updated`, `metadata.customUpdated`), whoever caused the edit,\n * and that is also the one place its `onUpdated` and `onResynced` fire.\n * Nothing needs them when a document opens, so they load on demand: the\n * first `ensureLoaded()` (what `metadataState` asks for) reads them.\n */\nexport function createMetadataController(ctx: PluginContext<void>) {\n const updated = ctx.events.source<MetadataUpdatedEvent>();\n const resynced = ctx.events.source<MetadataResyncedEvent>();\n const customUpdated = ctx.events.source<CustomMetadataUpdatedEvent>();\n const customResynced = ctx.events.source<CustomMetadataResyncedEvent>();\n\n const metadata = ctx.mirror<DocumentMetadata | null>({\n name: 'metadata',\n onDemand: true,\n initial: () => null,\n load: async (doc) => ({ value: await doc.metadata.get() }),\n fold: (value, event) => (event.type === 'metadata.updated' ? event.metadata : value),\n changed: ({ cause, event, previous, next }) => {\n if (!next) return;\n if (cause === 'load') {\n resynced.emit({ metadata: next });\n } else if (event && 'origin' in event) {\n updated.emit({\n metadata: next,\n previous,\n changedKeys: changedKeys(previous, next),\n origin: event.origin,\n });\n }\n },\n });\n\n const custom = ctx.mirror<CustomMetadata | null>({\n name: 'customMetadata',\n onDemand: true,\n initial: () => null,\n load: async (doc) => ({ value: await doc.metadata.custom.get() }),\n fold: (value, event) => (event.type === 'metadata.customUpdated' ? event.custom : value),\n changed: ({ cause, event, previous, next }) => {\n if (!next) return;\n if (cause === 'load') {\n customResynced.emit({ custom: next });\n } else if (event && 'origin' in event) {\n customUpdated.emit({\n custom: next,\n previous,\n changedKeys: changedKeys(previous, next),\n origin: event.origin,\n });\n }\n },\n });\n\n const canUpdate = () => ctx.allows(METADATA_MODIFY);\n\n /**\n * Both writes refuse the same way before reaching the engine: the verbs are\n * async, so a refusal rejects, and nothing changes.\n */\n const refuseWrite = (verb: string, options?: OperationOptions): void => {\n ctx.assertAllowed(METADATA_MODIFY, verb);\n if (options?.signal?.aborted) {\n throw new PluginError('operation-cancelled', 'metadata', `${verb} was cancelled`);\n }\n };\n\n const customApi: CustomMetadataCapability = {\n getSnapshot: custom.get,\n getStatus: custom.getStatus,\n update: async (patch, options) => {\n refuseWrite('metadata.custom.update', options);\n const result = await ctx.cancellable(options?.signal, ctx.doc.metadata.custom.update(patch));\n return { custom: result.custom };\n },\n refresh: (options) => ctx.cancellable(options?.signal, custom.refresh()),\n onUpdated: customUpdated.on,\n onResynced: customResynced.on,\n };\n\n const api: MetadataCapability = {\n getSnapshot: metadata.get,\n getStatus: metadata.getStatus,\n ensureLoaded: (options) =>\n ctx.cancellable(\n options?.signal,\n Promise.all([metadata.ensureLoaded(), custom.ensureLoaded()]).then(() => undefined),\n ),\n canUpdate,\n update: async (patch, options) => {\n refuseWrite('metadata.update', options);\n const result = await ctx.cancellable(options?.signal, ctx.doc.metadata.update(patch));\n return { metadata: result.metadata };\n },\n refresh: (options) => ctx.cancellable(options?.signal, metadata.refresh()),\n onUpdated: updated.on,\n onResynced: resynced.on,\n custom: customApi,\n };\n\n return { api };\n}\n","import { definePlugin } from '@embedpdf/core';\n\nimport { MetadataToken } from './contract';\nimport { createMetadataController } from './controller';\n\n/** Document-scoped, reactive Info-dict metadata. Takes no configuration. */\nexport const metadataPlugin = () =>\n definePlugin({\n id: 'metadata',\n token: MetadataToken,\n scope: 'document',\n create: createMetadataController,\n });\n","/**\n * The metadata page's State table as code: what `useMetadataState()` returns,\n * and the same fields in every other framework. `status` is the standard\n * fields' load state; `custom.getStatus()` has the custom keys' own. Reading\n * the state loads it (`ensure`): the first reader of a document starts the read.\n */\nimport { defineState } from '@embedpdf/core';\n\nimport { MetadataToken } from './contract';\n\nexport const metadataState = defineState(MetadataToken, {\n read: (metadata) => ({\n metadata: metadata.getSnapshot(),\n custom: metadata.custom.getSnapshot(),\n status: metadata.getStatus(),\n }),\n empty: {\n metadata: null,\n custom: null,\n status: 'idle',\n },\n ensure: (metadata) => metadata.ensureLoaded(),\n});\n"],"mappings":"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"}
package/dist/index.d.cts CHANGED
@@ -1,11 +1,14 @@
1
- import { i as MetadataToken, n as MetadataCapability, r as MetadataState, t as MetadataAction } from "./contract-Dk5SGhEP.cjs";
1
+ import { a as MetadataResyncedEvent, i as MetadataCapability, n as CustomMetadataResyncedEvent, o as MetadataUpdatedEvent, r as CustomMetadataUpdatedEvent, s as MetadataToken, t as CustomMetadataCapability } from "./contract-Cljrf00d.cjs";
2
2
  //#region src/metadata.plugin.d.ts
3
- /**
4
- * Document-scoped metadata plugin: reactive Info-dict state. The effect seeds it
5
- * from the engine and keeps it live off the document event stream (own + remote
6
- * SSE edits); the capability is the read/write surface.
7
- */
8
- declare const metadataPlugin: () => import("@embedpdf/core").PluginDef<MetadataState, MetadataAction, MetadataCapability>;
3
+ /** Document-scoped, reactive Info-dict metadata. Takes no configuration. */
4
+ declare const metadataPlugin: () => import("@embedpdf/core").PluginDef<void, MetadataCapability, import("@embedpdf/core").NoSettings>;
9
5
  //#endregion
10
- export { type MetadataCapability, type MetadataState, MetadataToken, metadataPlugin };
6
+ //#region src/state.d.ts
7
+ declare const metadataState: import("@embedpdf/core").StateDeclaration<MetadataCapability, {
8
+ metadata: import("@embedpdf/core").DocumentMetadata | null;
9
+ custom: import("@embedpdf/core").CustomMetadata | null;
10
+ status: import("@embedpdf/core").ResourceStatus;
11
+ }>;
12
+ //#endregion
13
+ export { CustomMetadataCapability, CustomMetadataResyncedEvent, CustomMetadataUpdatedEvent, MetadataCapability, MetadataResyncedEvent, MetadataToken, MetadataUpdatedEvent, metadataPlugin, metadataState };
11
14
  //# sourceMappingURL=index.d.cts.map
package/dist/index.d.ts CHANGED
@@ -1,11 +1,14 @@
1
- import { i as MetadataToken, n as MetadataCapability, r as MetadataState, t as MetadataAction } from "./contract-Dk5SGhEP.js";
1
+ import { a as MetadataResyncedEvent, i as MetadataCapability, n as CustomMetadataResyncedEvent, o as MetadataUpdatedEvent, r as CustomMetadataUpdatedEvent, s as MetadataToken, t as CustomMetadataCapability } from "./contract-Cljrf00d.js";
2
2
  //#region src/metadata.plugin.d.ts
3
- /**
4
- * Document-scoped metadata plugin: reactive Info-dict state. The effect seeds it
5
- * from the engine and keeps it live off the document event stream (own + remote
6
- * SSE edits); the capability is the read/write surface.
7
- */
8
- declare const metadataPlugin: () => import("@embedpdf/core").PluginDef<MetadataState, MetadataAction, MetadataCapability>;
3
+ /** Document-scoped, reactive Info-dict metadata. Takes no configuration. */
4
+ declare const metadataPlugin: () => import("@embedpdf/core").PluginDef<void, MetadataCapability, import("@embedpdf/core").NoSettings>;
9
5
  //#endregion
10
- export { type MetadataCapability, type MetadataState, MetadataToken, metadataPlugin };
6
+ //#region src/state.d.ts
7
+ declare const metadataState: import("@embedpdf/core").StateDeclaration<MetadataCapability, {
8
+ metadata: import("@embedpdf/core").DocumentMetadata | null;
9
+ custom: import("@embedpdf/core").CustomMetadata | null;
10
+ status: import("@embedpdf/core").ResourceStatus;
11
+ }>;
12
+ //#endregion
13
+ export { CustomMetadataCapability, CustomMetadataResyncedEvent, CustomMetadataUpdatedEvent, MetadataCapability, MetadataResyncedEvent, MetadataToken, MetadataUpdatedEvent, metadataPlugin, metadataState };
11
14
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -1,100 +1,132 @@
1
- import { t as MetadataToken } from "./contract-DXGV7Wpx.js";
2
- import { definePlugin } from "@embedpdf/core";
3
- //#region src/capability.ts
1
+ import { t as MetadataToken } from "./contract-o3j9MoH5.js";
2
+ import { PluginError, definePlugin, defineState } from "@embedpdf/core";
3
+ //#region src/model.ts
4
4
  /**
5
- * Authorization for Info-dict writes. Maps to PDF bit 4 (MODIFY). The engine
6
- * ALSO enforces this — `metadata.update` throws `PermissionDenied` without it,
7
- * identically on local and cloud — so `canEdit()` is the UI mirror of a real
8
- * guard, not the guard itself. Typed as `DocCapability` so a typo is a compile
9
- * error.
5
+ * The keys whose values differ between two snapshots, removed keys included.
6
+ * Both snapshots (the standard fields, the custom keys) hold only strings,
7
+ * `null` and enums, so a shallow compare is exact.
10
8
  */
11
- const METADATA_MODIFY_CAPABILITY = "doc.metadata.modify";
9
+ function changedKeys(previous, next) {
10
+ const keys = Object.keys(next);
11
+ if (!previous) return keys;
12
+ return [.../* @__PURE__ */ new Set([...Object.keys(previous), ...keys])].filter((key) => previous[key] !== next[key]);
13
+ }
14
+ //#endregion
15
+ //#region src/controller.ts
16
+ const METADATA_MODIFY = "doc.metadata.modify";
12
17
  /**
13
- * Reads/writes the document's Info dict through the engine handle and keeps the
14
- * plugin's state in sync. The live-update wiring (own + remote events) lives in
15
- * the effect; this is the read/write surface.
18
+ * The metadata controller. Both halves of the Info dict are mirrors: each
19
+ * changes only when a load lands or its confirmed document event arrives
20
+ * (`metadata.updated`, `metadata.customUpdated`), whoever caused the edit,
21
+ * and that is also the one place its `onUpdated` and `onResynced` fire.
22
+ * Nothing needs them when a document opens, so they load on demand: the
23
+ * first `ensureLoaded()` (what `metadataState` asks for) reads them.
16
24
  */
17
- function createMetadataCapability(ctx) {
18
- const requireDoc = () => {
19
- const doc = ctx.doc;
20
- if (!doc) throw new Error("[metadata] no document bound");
21
- return doc;
22
- };
23
- return {
24
- canEdit() {
25
- return ctx.doc?.security.allows(METADATA_MODIFY_CAPABILITY) ?? false;
26
- },
27
- current: () => ctx.getState().metadata,
28
- update: async (patch) => {
29
- const result = await requireDoc().metadata.update(patch);
30
- ctx.dispatch({
31
- type: "SET",
32
- metadata: result.metadata
25
+ function createMetadataController(ctx) {
26
+ const updated = ctx.events.source();
27
+ const resynced = ctx.events.source();
28
+ const customUpdated = ctx.events.source();
29
+ const customResynced = ctx.events.source();
30
+ const metadata = ctx.mirror({
31
+ name: "metadata",
32
+ onDemand: true,
33
+ initial: () => null,
34
+ load: async (doc) => ({ value: await doc.metadata.get() }),
35
+ fold: (value, event) => event.type === "metadata.updated" ? event.metadata : value,
36
+ changed: ({ cause, event, previous, next }) => {
37
+ if (!next) return;
38
+ if (cause === "load") resynced.emit({ metadata: next });
39
+ else if (event && "origin" in event) updated.emit({
40
+ metadata: next,
41
+ previous,
42
+ changedKeys: changedKeys(previous, next),
43
+ origin: event.origin
33
44
  });
34
- },
35
- reload: async () => {
36
- const metadata = await requireDoc().metadata.read();
37
- ctx.dispatch({
38
- type: "SET",
39
- metadata
45
+ }
46
+ });
47
+ const custom = ctx.mirror({
48
+ name: "customMetadata",
49
+ onDemand: true,
50
+ initial: () => null,
51
+ load: async (doc) => ({ value: await doc.metadata.custom.get() }),
52
+ fold: (value, event) => event.type === "metadata.customUpdated" ? event.custom : value,
53
+ changed: ({ cause, event, previous, next }) => {
54
+ if (!next) return;
55
+ if (cause === "load") customResynced.emit({ custom: next });
56
+ else if (event && "origin" in event) customUpdated.emit({
57
+ custom: next,
58
+ previous,
59
+ changedKeys: changedKeys(previous, next),
60
+ origin: event.origin
40
61
  });
41
62
  }
42
- };
43
- }
44
- //#endregion
45
- //#region src/effects.ts
46
- /**
47
- * Makes metadata REACTIVE — the reason this is a plugin, not a kernel IO call.
48
- *
49
- * 1. Seed: read the Info dict once when the document opens.
50
- * 2. Live: subscribe to the document event stream. A `metadata.updated` event
51
- * carries the full re-read metadata and fires for OWN edits AND for remote
52
- * edits delivered over SSE (another session) — both just become `setState`.
53
- *
54
- * The handle's stream is synchronous fan-out, so this coexists with the kernel's
55
- * own subscription (page registry) on the same handle.
56
- */
57
- function registerMetadataEffects(ctx) {
58
- const doc = ctx.doc;
59
- if (!doc) return;
60
- doc.metadata.read().then((metadata) => ctx.dispatch({
61
- type: "SET",
62
- metadata
63
- }), () => {});
64
- const unsubscribe = doc.events.subscribe((event) => {
65
- if (event.type === "metadata.updated") ctx.dispatch({
66
- type: "SET",
67
- metadata: event.metadata
68
- });
69
63
  });
70
- ctx.cleanup(unsubscribe);
64
+ const canUpdate = () => ctx.allows(METADATA_MODIFY);
65
+ /**
66
+ * Both writes refuse the same way before reaching the engine: the verbs are
67
+ * async, so a refusal rejects, and nothing changes.
68
+ */
69
+ const refuseWrite = (verb, options) => {
70
+ ctx.assertAllowed(METADATA_MODIFY, verb);
71
+ if (options?.signal?.aborted) throw new PluginError("operation-cancelled", "metadata", `${verb} was cancelled`);
72
+ };
73
+ const customApi = {
74
+ getSnapshot: custom.get,
75
+ getStatus: custom.getStatus,
76
+ update: async (patch, options) => {
77
+ refuseWrite("metadata.custom.update", options);
78
+ return { custom: (await ctx.cancellable(options?.signal, ctx.doc.metadata.custom.update(patch))).custom };
79
+ },
80
+ refresh: (options) => ctx.cancellable(options?.signal, custom.refresh()),
81
+ onUpdated: customUpdated.on,
82
+ onResynced: customResynced.on
83
+ };
84
+ return { api: {
85
+ getSnapshot: metadata.get,
86
+ getStatus: metadata.getStatus,
87
+ ensureLoaded: (options) => ctx.cancellable(options?.signal, Promise.all([metadata.ensureLoaded(), custom.ensureLoaded()]).then(() => void 0)),
88
+ canUpdate,
89
+ update: async (patch, options) => {
90
+ refuseWrite("metadata.update", options);
91
+ return { metadata: (await ctx.cancellable(options?.signal, ctx.doc.metadata.update(patch))).metadata };
92
+ },
93
+ refresh: (options) => ctx.cancellable(options?.signal, metadata.refresh()),
94
+ onUpdated: updated.on,
95
+ onResynced: resynced.on,
96
+ custom: customApi
97
+ } };
71
98
  }
72
99
  //#endregion
73
- //#region src/reducer.ts
74
- const initialMetadataState = () => ({ metadata: null });
75
- /** Pure. The only transition: replace the metadata snapshot (from a read or an
76
- * event). */
77
- const metadataReducer = (state, a) => a.type === "SET" ? {
78
- ...state,
79
- metadata: a.metadata
80
- } : state;
81
- //#endregion
82
100
  //#region src/metadata.plugin.ts
83
- /**
84
- * Document-scoped metadata plugin: reactive Info-dict state. The effect seeds it
85
- * from the engine and keeps it live off the document event stream (own + remote
86
- * SSE edits); the capability is the read/write surface.
87
- */
101
+ /** Document-scoped, reactive Info-dict metadata. Takes no configuration. */
88
102
  const metadataPlugin = () => definePlugin({
89
103
  id: "metadata",
90
104
  token: MetadataToken,
91
105
  scope: "document",
92
- initialState: initialMetadataState,
93
- reduce: metadataReducer,
94
- capability: createMetadataCapability,
95
- effects: registerMetadataEffects
106
+ create: createMetadataController
107
+ });
108
+ //#endregion
109
+ //#region src/state.ts
110
+ /**
111
+ * The metadata page's State table as code: what `useMetadataState()` returns,
112
+ * and the same fields in every other framework. `status` is the standard
113
+ * fields' load state; `custom.getStatus()` has the custom keys' own. Reading
114
+ * the state loads it (`ensure`): the first reader of a document starts the read.
115
+ */
116
+ const metadataState = defineState(MetadataToken, {
117
+ read: (metadata) => ({
118
+ metadata: metadata.getSnapshot(),
119
+ custom: metadata.custom.getSnapshot(),
120
+ status: metadata.getStatus()
121
+ }),
122
+ empty: {
123
+ metadata: null,
124
+ custom: null,
125
+ status: "idle"
126
+ },
127
+ ensure: (metadata) => metadata.ensureLoaded()
96
128
  });
97
129
  //#endregion
98
- export { MetadataToken, metadataPlugin };
130
+ export { MetadataToken, metadataPlugin, metadataState };
99
131
 
100
132
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/capability.ts","../src/effects.ts","../src/reducer.ts","../src/metadata.plugin.ts"],"sourcesContent":["import type { PluginContext, DocCapability } from '@embedpdf/core';\nimport type { MetadataAction, MetadataCapability, MetadataState } from './types';\n\n/**\n * Authorization for Info-dict writes. Maps to PDF bit 4 (MODIFY). The engine\n * ALSO enforces this — `metadata.update` throws `PermissionDenied` without it,\n * identically on local and cloud — so `canEdit()` is the UI mirror of a real\n * guard, not the guard itself. Typed as `DocCapability` so a typo is a compile\n * error.\n */\nconst METADATA_MODIFY_CAPABILITY: DocCapability = 'doc.metadata.modify';\n\n/**\n * Reads/writes the document's Info dict through the engine handle and keeps the\n * plugin's state in sync. The live-update wiring (own + remote events) lives in\n * the effect; this is the read/write surface.\n */\nexport function createMetadataCapability(\n ctx: PluginContext<MetadataState, MetadataAction>,\n): MetadataCapability {\n const requireDoc = () => {\n const doc = ctx.doc;\n if (!doc) throw new Error('[metadata] no document bound');\n return doc;\n };\n\n return {\n canEdit() {\n // Wildcard-aware predicate (mirrors the engine's own enforcement),\n // identical on both engines. NOT `effectiveScope.includes(...)` — that\n // enumeration drops the `*` admin grant, so it would hide the UI on a\n // default admin open even though the engine allows the edit.\n return ctx.doc?.security.allows(METADATA_MODIFY_CAPABILITY) ?? false;\n },\n\n current: () => ctx.getState().metadata,\n\n update: async (patch) => {\n // The write also emits a `metadata.updated` event the effect catches, so\n // this dispatch is just for an immediate, in-sync update (idempotent).\n const result = await requireDoc().metadata.update(patch);\n ctx.dispatch({ type: 'SET', metadata: result.metadata });\n },\n\n reload: async () => {\n const metadata = await requireDoc().metadata.read();\n ctx.dispatch({ type: 'SET', metadata });\n },\n };\n}\n","import type { DocumentEvent, EffectContext } from '@embedpdf/core';\nimport type { MetadataAction, MetadataState } from './types';\n\n/**\n * Makes metadata REACTIVE — the reason this is a plugin, not a kernel IO call.\n *\n * 1. Seed: read the Info dict once when the document opens.\n * 2. Live: subscribe to the document event stream. A `metadata.updated` event\n * carries the full re-read metadata and fires for OWN edits AND for remote\n * edits delivered over SSE (another session) — both just become `setState`.\n *\n * The handle's stream is synchronous fan-out, so this coexists with the kernel's\n * own subscription (page registry) on the same handle.\n */\nexport function registerMetadataEffects(ctx: EffectContext<MetadataState, MetadataAction>): void {\n const doc = ctx.doc;\n if (!doc) return;\n\n // 1. seed\n void doc.metadata.read().then(\n (metadata) => ctx.dispatch({ type: 'SET', metadata }),\n () => {}, // doc closed / read aborted — ignore\n );\n\n // 2. live updates (own edits + remote SSE edits)\n const unsubscribe = doc.events.subscribe((event: DocumentEvent) => {\n if (event.type === 'metadata.updated') {\n ctx.dispatch({ type: 'SET', metadata: event.metadata });\n }\n });\n ctx.cleanup(unsubscribe);\n}\n","import type { MetadataAction, MetadataState } from './types';\n\nexport const initialMetadataState = (): MetadataState => ({ metadata: null });\n\n/** Pure. The only transition: replace the metadata snapshot (from a read or an\n * event). */\nexport const metadataReducer = (state: MetadataState, a: MetadataAction): MetadataState =>\n a.type === 'SET' ? { ...state, metadata: a.metadata } : state;\n","import { definePlugin } from '@embedpdf/core';\nimport { createMetadataCapability } from './capability';\nimport { registerMetadataEffects } from './effects';\nimport { initialMetadataState, metadataReducer } from './reducer';\nimport { MetadataToken } from './types';\nimport type { MetadataAction, MetadataCapability, MetadataState } from './types';\n\n/**\n * Document-scoped metadata plugin: reactive Info-dict state. The effect seeds it\n * from the engine and keeps it live off the document event stream (own + remote\n * SSE edits); the capability is the read/write surface.\n */\nexport const metadataPlugin = () =>\n definePlugin<MetadataState, MetadataAction, MetadataCapability>({\n id: 'metadata',\n token: MetadataToken,\n scope: 'document',\n initialState: initialMetadataState,\n reduce: metadataReducer,\n capability: createMetadataCapability,\n effects: registerMetadataEffects,\n });\n"],"mappings":";;;;;;;;;;AAUA,MAAM,6BAA4C;;;;;;AAOlD,SAAgB,yBACd,KACoB;CACpB,MAAM,mBAAmB;EACvB,MAAM,MAAM,IAAI;EAChB,IAAI,CAAC,KAAK,MAAM,IAAI,MAAM,8BAA8B;EACxD,OAAO;CACT;CAEA,OAAO;EACL,UAAU;GAKR,OAAO,IAAI,KAAK,SAAS,OAAO,0BAA0B,KAAK;EACjE;EAEA,eAAe,IAAI,SAAS,CAAC,CAAC;EAE9B,QAAQ,OAAO,UAAU;GAGvB,MAAM,SAAS,MAAM,WAAW,CAAC,CAAC,SAAS,OAAO,KAAK;GACvD,IAAI,SAAS;IAAE,MAAM;IAAO,UAAU,OAAO;GAAS,CAAC;EACzD;EAEA,QAAQ,YAAY;GAClB,MAAM,WAAW,MAAM,WAAW,CAAC,CAAC,SAAS,KAAK;GAClD,IAAI,SAAS;IAAE,MAAM;IAAO;GAAS,CAAC;EACxC;CACF;AACF;;;;;;;;;;;;;;ACnCA,SAAgB,wBAAwB,KAAyD;CAC/F,MAAM,MAAM,IAAI;CAChB,IAAI,CAAC,KAAK;CAGV,IAAS,SAAS,KAAK,CAAC,CAAC,MACtB,aAAa,IAAI,SAAS;EAAE,MAAM;EAAO;CAAS,CAAC,SAC9C,CAAC,CACT;CAGA,MAAM,cAAc,IAAI,OAAO,WAAW,UAAyB;EACjE,IAAI,MAAM,SAAS,oBACjB,IAAI,SAAS;GAAE,MAAM;GAAO,UAAU,MAAM;EAAS,CAAC;CAE1D,CAAC;CACD,IAAI,QAAQ,WAAW;AACzB;;;AC7BA,MAAa,8BAA6C,EAAE,UAAU,KAAK;;;AAI3E,MAAa,mBAAmB,OAAsB,MACpD,EAAE,SAAS,QAAQ;CAAE,GAAG;CAAO,UAAU,EAAE;AAAS,IAAI;;;;;;;;ACK1D,MAAa,uBACX,aAAgE;CAC9D,IAAI;CACJ,OAAO;CACP,OAAO;CACP,cAAc;CACd,QAAQ;CACR,YAAY;CACZ,SAAS;AACX,CAAC"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/model.ts","../src/controller.ts","../src/metadata.plugin.ts","../src/state.ts"],"sourcesContent":["/**\n * The keys whose values differ between two snapshots, removed keys included.\n * Both snapshots (the standard fields, the custom keys) hold only strings,\n * `null` and enums, so a shallow compare is exact.\n */\nexport function changedKeys<T extends object>(\n previous: T | null,\n next: T,\n): readonly (keyof T & string)[] {\n const keys = Object.keys(next) as (keyof T & string)[];\n if (!previous) return keys;\n const all = new Set([...(Object.keys(previous) as (keyof T & string)[]), ...keys]);\n return [...all].filter((key) => previous[key] !== next[key]);\n}\n","import {\n PluginError,\n type PluginContext,\n type CustomMetadata,\n type DocCapability,\n type DocumentMetadata,\n type OperationOptions,\n} from '@embedpdf/core';\n\nimport type {\n CustomMetadataCapability,\n CustomMetadataResyncedEvent,\n CustomMetadataUpdatedEvent,\n MetadataCapability,\n MetadataResyncedEvent,\n MetadataUpdatedEvent,\n} from './contract';\nimport { changedKeys } from './model';\n\nconst METADATA_MODIFY: DocCapability = 'doc.metadata.modify';\n\n/**\n * The metadata controller. Both halves of the Info dict are mirrors: each\n * changes only when a load lands or its confirmed document event arrives\n * (`metadata.updated`, `metadata.customUpdated`), whoever caused the edit,\n * and that is also the one place its `onUpdated` and `onResynced` fire.\n * Nothing needs them when a document opens, so they load on demand: the\n * first `ensureLoaded()` (what `metadataState` asks for) reads them.\n */\nexport function createMetadataController(ctx: PluginContext<void>) {\n const updated = ctx.events.source<MetadataUpdatedEvent>();\n const resynced = ctx.events.source<MetadataResyncedEvent>();\n const customUpdated = ctx.events.source<CustomMetadataUpdatedEvent>();\n const customResynced = ctx.events.source<CustomMetadataResyncedEvent>();\n\n const metadata = ctx.mirror<DocumentMetadata | null>({\n name: 'metadata',\n onDemand: true,\n initial: () => null,\n load: async (doc) => ({ value: await doc.metadata.get() }),\n fold: (value, event) => (event.type === 'metadata.updated' ? event.metadata : value),\n changed: ({ cause, event, previous, next }) => {\n if (!next) return;\n if (cause === 'load') {\n resynced.emit({ metadata: next });\n } else if (event && 'origin' in event) {\n updated.emit({\n metadata: next,\n previous,\n changedKeys: changedKeys(previous, next),\n origin: event.origin,\n });\n }\n },\n });\n\n const custom = ctx.mirror<CustomMetadata | null>({\n name: 'customMetadata',\n onDemand: true,\n initial: () => null,\n load: async (doc) => ({ value: await doc.metadata.custom.get() }),\n fold: (value, event) => (event.type === 'metadata.customUpdated' ? event.custom : value),\n changed: ({ cause, event, previous, next }) => {\n if (!next) return;\n if (cause === 'load') {\n customResynced.emit({ custom: next });\n } else if (event && 'origin' in event) {\n customUpdated.emit({\n custom: next,\n previous,\n changedKeys: changedKeys(previous, next),\n origin: event.origin,\n });\n }\n },\n });\n\n const canUpdate = () => ctx.allows(METADATA_MODIFY);\n\n /**\n * Both writes refuse the same way before reaching the engine: the verbs are\n * async, so a refusal rejects, and nothing changes.\n */\n const refuseWrite = (verb: string, options?: OperationOptions): void => {\n ctx.assertAllowed(METADATA_MODIFY, verb);\n if (options?.signal?.aborted) {\n throw new PluginError('operation-cancelled', 'metadata', `${verb} was cancelled`);\n }\n };\n\n const customApi: CustomMetadataCapability = {\n getSnapshot: custom.get,\n getStatus: custom.getStatus,\n update: async (patch, options) => {\n refuseWrite('metadata.custom.update', options);\n const result = await ctx.cancellable(options?.signal, ctx.doc.metadata.custom.update(patch));\n return { custom: result.custom };\n },\n refresh: (options) => ctx.cancellable(options?.signal, custom.refresh()),\n onUpdated: customUpdated.on,\n onResynced: customResynced.on,\n };\n\n const api: MetadataCapability = {\n getSnapshot: metadata.get,\n getStatus: metadata.getStatus,\n ensureLoaded: (options) =>\n ctx.cancellable(\n options?.signal,\n Promise.all([metadata.ensureLoaded(), custom.ensureLoaded()]).then(() => undefined),\n ),\n canUpdate,\n update: async (patch, options) => {\n refuseWrite('metadata.update', options);\n const result = await ctx.cancellable(options?.signal, ctx.doc.metadata.update(patch));\n return { metadata: result.metadata };\n },\n refresh: (options) => ctx.cancellable(options?.signal, metadata.refresh()),\n onUpdated: updated.on,\n onResynced: resynced.on,\n custom: customApi,\n };\n\n return { api };\n}\n","import { definePlugin } from '@embedpdf/core';\n\nimport { MetadataToken } from './contract';\nimport { createMetadataController } from './controller';\n\n/** Document-scoped, reactive Info-dict metadata. Takes no configuration. */\nexport const metadataPlugin = () =>\n definePlugin({\n id: 'metadata',\n token: MetadataToken,\n scope: 'document',\n create: createMetadataController,\n });\n","/**\n * The metadata page's State table as code: what `useMetadataState()` returns,\n * and the same fields in every other framework. `status` is the standard\n * fields' load state; `custom.getStatus()` has the custom keys' own. Reading\n * the state loads it (`ensure`): the first reader of a document starts the read.\n */\nimport { defineState } from '@embedpdf/core';\n\nimport { MetadataToken } from './contract';\n\nexport const metadataState = defineState(MetadataToken, {\n read: (metadata) => ({\n metadata: metadata.getSnapshot(),\n custom: metadata.custom.getSnapshot(),\n status: metadata.getStatus(),\n }),\n empty: {\n metadata: null,\n custom: null,\n status: 'idle',\n },\n ensure: (metadata) => metadata.ensureLoaded(),\n});\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@embedpdf/plugin-metadata",
3
- "version": "3.0.0-next.14",
3
+ "version": "3.0.0-next.15",
4
4
  "private": false,
5
5
  "description": "Document-scoped metadata: reactive Info-dict state fed by the document event stream (own edits + remote SSE), with a three-state update that writes to the layer.",
6
6
  "type": "module",
@@ -22,7 +22,7 @@
22
22
  "dist"
23
23
  ],
24
24
  "dependencies": {
25
- "@embedpdf/core": "3.0.0-next.14"
25
+ "@embedpdf/core": "3.0.0-next.15"
26
26
  },
27
27
  "devDependencies": {
28
28
  "typescript": "^5.0.0",
@@ -1,11 +0,0 @@
1
- //#region src/types.ts
2
- const MetadataToken = (0, require("@embedpdf/core").createCapabilityToken)("metadata");
3
- //#endregion
4
- Object.defineProperty(exports, "MetadataToken", {
5
- enumerable: true,
6
- get: function() {
7
- return MetadataToken;
8
- }
9
- });
10
-
11
- //# sourceMappingURL=contract-CqhcLHxo.cjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"contract-CqhcLHxo.cjs","names":[],"sources":["../src/types.ts"],"sourcesContent":["import { createCapabilityToken, type DocumentMetadata, type MetadataPatch } from '@embedpdf/core';\n\n/**\n * The document's Info-dict metadata, held as REACTIVE state — not a one-shot\n * read. It changes from local edits AND from other sessions (a remote edit\n * arrives over SSE as a `metadata.updated` event), so the plugin subscribes to\n * the document event stream and keeps `current()` live. `update` writes a\n * three-state patch into the LAYER (like a page mutation), so a metadata edit\n * saves with the layer.\n */\nexport interface MetadataState {\n /** Current metadata, or null until the first read resolves. */\n metadata: DocumentMetadata | null;\n}\n\nexport type MetadataAction = { type: 'SET'; metadata: DocumentMetadata | null };\n\nexport interface MetadataCapability {\n /**\n * Whether this caller is authorized to edit the Info dict —\n * `effectiveScope` includes `doc.metadata.modify` (PDF bit 4). UIs gate their\n * edit affordances on this; the engine independently enforces the same\n * capability and throws `PermissionDenied` if a write slips through.\n */\n canEdit(): boolean;\n /** Current metadata; null until the initial read lands. Reactive — reflects\n * own edits and remote (SSE) edits via the event stream. */\n current(): DocumentMetadata | null;\n /** Patch the Info dict (undefined=leave, null=clear, value=set). Writes to the\n * layer; state refreshes from the resulting `metadata.updated` event. */\n update(patch: MetadataPatch): Promise<void>;\n /** Force a re-read from the engine (rarely needed — the event stream keeps\n * state fresh). */\n reload(): Promise<void>;\n}\n\nexport const MetadataToken = createCapabilityToken<MetadataCapability>('metadata');\n"],"mappings":";AAoCA,MAAa,iBAAA,2BAAA,CAAA,CAAA,sBAAA,CAA0D,UAAU"}
@@ -1,7 +0,0 @@
1
- import { createCapabilityToken } from "@embedpdf/core";
2
- //#region src/types.ts
3
- const MetadataToken = createCapabilityToken("metadata");
4
- //#endregion
5
- export { MetadataToken as t };
6
-
7
- //# sourceMappingURL=contract-DXGV7Wpx.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"contract-DXGV7Wpx.js","names":[],"sources":["../src/types.ts"],"sourcesContent":["import { createCapabilityToken, type DocumentMetadata, type MetadataPatch } from '@embedpdf/core';\n\n/**\n * The document's Info-dict metadata, held as REACTIVE state — not a one-shot\n * read. It changes from local edits AND from other sessions (a remote edit\n * arrives over SSE as a `metadata.updated` event), so the plugin subscribes to\n * the document event stream and keeps `current()` live. `update` writes a\n * three-state patch into the LAYER (like a page mutation), so a metadata edit\n * saves with the layer.\n */\nexport interface MetadataState {\n /** Current metadata, or null until the first read resolves. */\n metadata: DocumentMetadata | null;\n}\n\nexport type MetadataAction = { type: 'SET'; metadata: DocumentMetadata | null };\n\nexport interface MetadataCapability {\n /**\n * Whether this caller is authorized to edit the Info dict —\n * `effectiveScope` includes `doc.metadata.modify` (PDF bit 4). UIs gate their\n * edit affordances on this; the engine independently enforces the same\n * capability and throws `PermissionDenied` if a write slips through.\n */\n canEdit(): boolean;\n /** Current metadata; null until the initial read lands. Reactive — reflects\n * own edits and remote (SSE) edits via the event stream. */\n current(): DocumentMetadata | null;\n /** Patch the Info dict (undefined=leave, null=clear, value=set). Writes to the\n * layer; state refreshes from the resulting `metadata.updated` event. */\n update(patch: MetadataPatch): Promise<void>;\n /** Force a re-read from the engine (rarely needed — the event stream keeps\n * state fresh). */\n reload(): Promise<void>;\n}\n\nexport const MetadataToken = createCapabilityToken<MetadataCapability>('metadata');\n"],"mappings":";;AAoCA,MAAa,gBAAgB,sBAA0C,UAAU"}
@@ -1,40 +0,0 @@
1
- import { DocumentMetadata, MetadataPatch } from "@embedpdf/core";
2
- //#region src/types.d.ts
3
- /**
4
- * The document's Info-dict metadata, held as REACTIVE state — not a one-shot
5
- * read. It changes from local edits AND from other sessions (a remote edit
6
- * arrives over SSE as a `metadata.updated` event), so the plugin subscribes to
7
- * the document event stream and keeps `current()` live. `update` writes a
8
- * three-state patch into the LAYER (like a page mutation), so a metadata edit
9
- * saves with the layer.
10
- */
11
- interface MetadataState {
12
- /** Current metadata, or null until the first read resolves. */
13
- metadata: DocumentMetadata | null;
14
- }
15
- type MetadataAction = {
16
- type: 'SET';
17
- metadata: DocumentMetadata | null;
18
- };
19
- interface MetadataCapability {
20
- /**
21
- * Whether this caller is authorized to edit the Info dict —
22
- * `effectiveScope` includes `doc.metadata.modify` (PDF bit 4). UIs gate their
23
- * edit affordances on this; the engine independently enforces the same
24
- * capability and throws `PermissionDenied` if a write slips through.
25
- */
26
- canEdit(): boolean;
27
- /** Current metadata; null until the initial read lands. Reactive — reflects
28
- * own edits and remote (SSE) edits via the event stream. */
29
- current(): DocumentMetadata | null;
30
- /** Patch the Info dict (undefined=leave, null=clear, value=set). Writes to the
31
- * layer; state refreshes from the resulting `metadata.updated` event. */
32
- update(patch: MetadataPatch): Promise<void>;
33
- /** Force a re-read from the engine (rarely needed — the event stream keeps
34
- * state fresh). */
35
- reload(): Promise<void>;
36
- }
37
- declare const MetadataToken: import("@embedpdf/core").CapabilityToken<MetadataCapability>;
38
- //#endregion
39
- export { MetadataToken as i, MetadataCapability as n, MetadataState as r, MetadataAction as t };
40
- //# sourceMappingURL=contract-Dk5SGhEP.d.cts.map
@@ -1,40 +0,0 @@
1
- import { DocumentMetadata, MetadataPatch } from "@embedpdf/core";
2
- //#region src/types.d.ts
3
- /**
4
- * The document's Info-dict metadata, held as REACTIVE state — not a one-shot
5
- * read. It changes from local edits AND from other sessions (a remote edit
6
- * arrives over SSE as a `metadata.updated` event), so the plugin subscribes to
7
- * the document event stream and keeps `current()` live. `update` writes a
8
- * three-state patch into the LAYER (like a page mutation), so a metadata edit
9
- * saves with the layer.
10
- */
11
- interface MetadataState {
12
- /** Current metadata, or null until the first read resolves. */
13
- metadata: DocumentMetadata | null;
14
- }
15
- type MetadataAction = {
16
- type: 'SET';
17
- metadata: DocumentMetadata | null;
18
- };
19
- interface MetadataCapability {
20
- /**
21
- * Whether this caller is authorized to edit the Info dict —
22
- * `effectiveScope` includes `doc.metadata.modify` (PDF bit 4). UIs gate their
23
- * edit affordances on this; the engine independently enforces the same
24
- * capability and throws `PermissionDenied` if a write slips through.
25
- */
26
- canEdit(): boolean;
27
- /** Current metadata; null until the initial read lands. Reactive — reflects
28
- * own edits and remote (SSE) edits via the event stream. */
29
- current(): DocumentMetadata | null;
30
- /** Patch the Info dict (undefined=leave, null=clear, value=set). Writes to the
31
- * layer; state refreshes from the resulting `metadata.updated` event. */
32
- update(patch: MetadataPatch): Promise<void>;
33
- /** Force a re-read from the engine (rarely needed — the event stream keeps
34
- * state fresh). */
35
- reload(): Promise<void>;
36
- }
37
- declare const MetadataToken: import("@embedpdf/core").CapabilityToken<MetadataCapability>;
38
- //#endregion
39
- export { MetadataToken as i, MetadataCapability as n, MetadataState as r, MetadataAction as t };
40
- //# sourceMappingURL=contract-Dk5SGhEP.d.ts.map