@web-ppt/collab 0.5.0-beta.2

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,16 @@
1
+ import type { EditDoc, Patch } from '@web-ppt/edit-core';
2
+ import type { CollaborationSession } from './state';
3
+ import type { CollabMessage } from './types';
4
+ export interface PatchAvailability {
5
+ readonly elements: Set<string>;
6
+ readonly slides: Set<string>;
7
+ }
8
+ export interface EvaluatedMessage {
9
+ readonly accepted: Patch[];
10
+ readonly recorded: Patch[];
11
+ readonly missingDependency: boolean;
12
+ readonly availability: PatchAvailability;
13
+ }
14
+ export declare function patchAvailability(patches: readonly Patch[], seed?: PatchAvailability): PatchAvailability;
15
+ /** 纯读求值;任一依赖缺失时调用方必须延迟整条消息,不能提交其中的结构子集。 */
16
+ export declare function evaluateRemoteMessage(doc: EditDoc, session: CollaborationSession, raw: CollabMessage, seed?: PatchAvailability): EvaluatedMessage;
@@ -0,0 +1,16 @@
1
+ import type { EditDoc, ElementHierarchyPatch, ElementRecord, ElementTreePatch, Patch } from '@web-ppt/edit-core';
2
+ declare const XFRM_FIELDS: readonly ["x", "y", "w", "h", "rot", "flipH", "flipV"];
3
+ type HierarchyField = 'order' | typeof XFRM_FIELDS[number];
4
+ export declare const hierarchyRecords: (patch: Patch) => Readonly<Record<string, ElementRecord | null>> | null;
5
+ /** 删除成员不应被并发组合/解组复活;其 tombstone 同步迁到层级意图的目标父链。 */
6
+ export declare function hierarchyWithoutRemovedMembers(doc: EditDoc, patch: ElementHierarchyPatch, removedIds: ReadonlySet<string>): ElementHierarchyPatch | null;
7
+ /** 重叠结构意图只裁掉已被更新 stamp 接管的成员;不相交成员仍可交换合并。 */
8
+ export declare function hierarchyWithoutNewerMembers(patch: ElementHierarchyPatch, newerIds: ReadonlySet<string>): ElementHierarchyPatch | null;
9
+ /**
10
+ * 层级 patch 是命令时快照,不能直接覆盖并发后的整棵树:成员字段从当前模型重基,
11
+ * 同父级的不相交组合按 order 合并;重叠组合中落败的新组只保留未被赢家接管的孩子。
12
+ */
13
+ export declare function rebaseElementHierarchy(doc: EditDoc, patch: ElementHierarchyPatch, applyField: (id: string, field: HierarchyField) => boolean): ElementHierarchyPatch;
14
+ /** 层级并发已拆散原树时,转换成可删除多个现存分支的自包含层级 patch。 */
15
+ export declare function rebaseElementRemoval(doc: EditDoc, patch: ElementTreePatch): ElementTreePatch | ElementHierarchyPatch | null;
16
+ export {};
@@ -0,0 +1,4 @@
1
+ /** @web-ppt/collab —— 基于 EditDoc 补丁的无服务端协同适配层。 */
2
+ export { bindCollaboration } from './binding';
3
+ export { BroadcastChannelCollabProvider } from './broadcast-channel';
4
+ export type { CollabDeferredCheckpoint, CollabLifecycleCheckpoint, CollabMessage, CollabMessageListener, CollabRegisterCheckpoint, CollabProvider, CollabSeenCheckpoint, CollabSlideMoveCheckpoint, CollabStamp, CollaborationBinding, CollaborationCheckpoint, CollaborationOptions, } from './types';
@@ -0,0 +1,6 @@
1
+ import type { Patch } from '@web-ppt/edit-core';
2
+ import type { CollabMessage, CollabStamp } from './types';
3
+ export declare function compareStamp(left: CollabStamp, right: CollabStamp): number;
4
+ export declare function assertCollabMessage(value: unknown): asserts value is CollabMessage;
5
+ export declare const pathKey: (patch: Patch) => string;
6
+ export declare const messageKey: (message: CollabMessage) => string;
package/dist/seen.d.ts ADDED
@@ -0,0 +1,15 @@
1
+ import type { CollabMessage, CollabSeenCheckpoint } from './types';
2
+ export declare const MAX_SEEN_REPLICAS = 4096;
3
+ export declare const MAX_SEEN_GAPS = 10000;
4
+ export interface SeenState {
5
+ contiguous: number;
6
+ readonly sparse: Set<number>;
7
+ }
8
+ export type SeenMap = Map<string, SeenState>;
9
+ export declare function restoreSeen(entries?: readonly CollabSeenCheckpoint[]): SeenMap;
10
+ export declare function cloneSeen(seen: SeenMap): SeenMap;
11
+ export declare function checkpointSeen(seen: SeenMap): CollabSeenCheckpoint[];
12
+ export declare function hasSeen(seen: SeenMap, message: CollabMessage): boolean;
13
+ /** 高水位只代表连续收到;高于下一序号的消息必须先等缺口,不能越过同副本因果链落模。 */
14
+ export declare function hasSequenceGap(seen: SeenMap, message: CollabMessage): boolean;
15
+ export declare function markSeen(seen: SeenMap, message: CollabMessage): void;
@@ -0,0 +1,3 @@
1
+ import type { EditDoc, SlideTreePatch } from '@web-ppt/edit-core';
2
+ /** 远端删页必须以接收端当前整页闭包落模,否则并发新增元素会变成孤儿。 */
3
+ export declare function rebaseSlideRemoval(doc: EditDoc, patch: SlideTreePatch): SlideTreePatch | null;
@@ -0,0 +1,5 @@
1
+ import type { Patch } from '@web-ppt/edit-core';
2
+ import type { SlideMove } from './state';
3
+ export declare function desiredSlideOrder(base: readonly string[], members: ReadonlySet<string>, moves: ReadonlyMap<string, SlideMove>): string[];
4
+ /** 把页序 intent 与本消息的增删页一起物化,确保一次 external recovery 就是最终顺序。 */
5
+ export declare function materializeSlideOrder(currentOrder: readonly string[], desired: readonly string[], patches: readonly Patch[]): Patch[];
@@ -0,0 +1,57 @@
1
+ import type { EditDoc, Patch } from '@web-ppt/edit-core';
2
+ import type { SeenMap } from './seen';
3
+ import type { CollabMessage, CollabStamp } from './types';
4
+ export declare const hierarchyKey: (id: string) => string;
5
+ export type Register = {
6
+ readonly stamp: CollabStamp;
7
+ readonly kind: 'field' | 'hierarchy';
8
+ };
9
+ export type Lifecycle = {
10
+ readonly stamp: CollabStamp;
11
+ readonly state: 'present' | 'removed';
12
+ };
13
+ export type SlideMove = {
14
+ readonly stamp: CollabStamp;
15
+ readonly after: string | null;
16
+ readonly ordinal: number;
17
+ };
18
+ export interface DeferredPatch {
19
+ readonly message: CollabMessage;
20
+ readonly patch: Patch;
21
+ readonly ordinal: number;
22
+ }
23
+ export interface CollaborationSession {
24
+ readonly documentId: string;
25
+ readonly replicaId: string;
26
+ readonly replicaSlot: number;
27
+ readonly registers: Map<string, Register>;
28
+ readonly elementLifecycles: Map<string, Lifecycle>;
29
+ readonly slideLifecycles: Map<string, Lifecycle>;
30
+ readonly slideMoves: Map<string, SlideMove>;
31
+ readonly seen: SeenMap;
32
+ readonly baseSlideOrder: readonly string[];
33
+ deferred: DeferredPatch[];
34
+ clock: number;
35
+ sequence: number;
36
+ active: boolean;
37
+ }
38
+ export declare const elementLifecycle: (patch: Patch) => {
39
+ id: string;
40
+ state: Lifecycle["state"];
41
+ } | null;
42
+ export declare const slideLifecycle: (patch: Patch) => {
43
+ id: string;
44
+ state: Lifecycle["state"];
45
+ } | null;
46
+ export declare const slideMove: (patch: Patch) => {
47
+ id: string;
48
+ after: string | null;
49
+ } | null;
50
+ export declare const elementHierarchy: (patch: Patch) => Readonly<Record<string, unknown>> | null;
51
+ export declare const newer: (stamp: CollabStamp, current?: {
52
+ stamp: CollabStamp;
53
+ }) => boolean;
54
+ export declare function patchNewer(session: CollaborationSession, patch: Patch, stamp: CollabStamp): boolean;
55
+ /** 同一原子消息共用 stamp;先按 ordinal 折叠末次意图,再与先前消息做严格 LWW。 */
56
+ export declare function recordPatches(session: CollaborationSession, patches: readonly Patch[], stamp: CollabStamp): void;
57
+ export declare function targetExists(doc: EditDoc, patch: Patch): boolean;
@@ -0,0 +1,83 @@
1
+ import type { EditIdentity, Editor, EditorPatchEvent, Patch } from '@web-ppt/edit-core';
2
+ export interface CollabStamp {
3
+ readonly clock: number;
4
+ readonly replicaId: string;
5
+ }
6
+ export interface CollabLifecycleCheckpoint {
7
+ readonly stamp: CollabStamp;
8
+ readonly state: 'present' | 'removed';
9
+ }
10
+ export interface CollabRegisterCheckpoint {
11
+ readonly stamp: CollabStamp;
12
+ readonly kind: 'field' | 'hierarchy';
13
+ }
14
+ export interface CollabSlideMoveCheckpoint {
15
+ readonly stamp: CollabStamp;
16
+ readonly after: string | null;
17
+ /** 同一消息内多次页序意图共用 stamp,以原 Patch 次序补足全序。 */
18
+ readonly ordinal: number;
19
+ }
20
+ export interface CollabDeferredCheckpoint {
21
+ readonly message: CollabMessage;
22
+ readonly patch: Patch;
23
+ readonly ordinal: number;
24
+ }
25
+ export interface CollabSeenCheckpoint {
26
+ readonly replicaId: string;
27
+ /** 已连续接收的最大消息序号;之前的消息无需逐条常驻。 */
28
+ readonly contiguous: number;
29
+ /** 高水位之后已收到的乱序消息;缺口闭合时会自动压缩。 */
30
+ readonly sparse: readonly number[];
31
+ }
32
+ /** 与 Editor recovery frames 同步持久化;模型快照本身无法重建 LWW 裁决元数据。 */
33
+ export interface CollaborationCheckpoint {
34
+ readonly version: 1;
35
+ readonly documentId: string;
36
+ readonly replicaId: string;
37
+ readonly replicaSlot: number;
38
+ readonly clock: number;
39
+ readonly sequence: number;
40
+ readonly baseSlideOrder: readonly string[];
41
+ readonly registers: readonly (readonly [string, CollabRegisterCheckpoint])[];
42
+ readonly elementLifecycles: readonly (readonly [string, CollabLifecycleCheckpoint])[];
43
+ readonly slideLifecycles: readonly (readonly [string, CollabLifecycleCheckpoint])[];
44
+ readonly slideMoves: readonly (readonly [string, CollabSlideMoveCheckpoint])[];
45
+ readonly seen: readonly CollabSeenCheckpoint[];
46
+ readonly deferred: readonly CollabDeferredCheckpoint[];
47
+ }
48
+ /** 一条消息对应一个已经原子落模的 Editor 补丁批次。 */
49
+ export interface CollabMessage {
50
+ readonly version: 1;
51
+ readonly documentId: string;
52
+ readonly replicaId: string;
53
+ readonly sequence: number;
54
+ readonly stamp: CollabStamp;
55
+ readonly patches: readonly Patch[];
56
+ readonly identity: EditIdentity;
57
+ readonly label: string;
58
+ readonly time: number;
59
+ }
60
+ export type CollabMessageListener = (message: CollabMessage) => void;
61
+ /** provider 只搬运可结构化克隆的消息;鉴权、持久化和拓扑不进入模型层。 */
62
+ export interface CollabProvider {
63
+ send(message: CollabMessage): void | Promise<void>;
64
+ subscribe(listener: CollabMessageListener): () => void;
65
+ }
66
+ export interface CollaborationOptions {
67
+ readonly documentId: string;
68
+ readonly replicaId: string;
69
+ /** provider 在同一文档内分配的唯一整数;隔离逻辑 id 与全部 OOXML 数值身份,范围 0–4095。 */
70
+ readonly replicaSlot: number;
71
+ readonly provider: CollabProvider;
72
+ /** 恢复过的 EditDoc 必须同时提供;新文档与同一 Editor 的 dispose/rebind 不需要。 */
73
+ readonly checkpoint?: CollaborationCheckpoint;
74
+ readonly onError?: (error: unknown) => void;
75
+ }
76
+ export interface CollaborationBinding {
77
+ readonly documentId: string;
78
+ readonly replicaId: string;
79
+ readonly editor: Editor;
80
+ checkpoint(): CollaborationCheckpoint;
81
+ dispose(): void;
82
+ }
83
+ export type LocalPatchEvent = EditorPatchEvent;
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "@web-ppt/collab",
3
+ "version": "0.5.0-beta.2",
4
+ "description": "Optional field-level LWW collaboration adapter for @web-ppt/edit-core with pluggable providers.",
5
+ "keywords": ["pptx", "powerpoint", "collaboration", "offline", "lww", "browser", "typescript"],
6
+ "license": "MIT",
7
+ "author": "unStone",
8
+ "homepage": "https://github.com/unStone/web-ppt#readme",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/unStone/web-ppt.git",
12
+ "directory": "packages/collab"
13
+ },
14
+ "type": "module",
15
+ "sideEffects": false,
16
+ "main": "./dist/collab.js",
17
+ "module": "./dist/collab.js",
18
+ "types": "./dist/index.d.ts",
19
+ "exports": {
20
+ ".": {
21
+ "types": "./dist/index.d.ts",
22
+ "import": "./dist/collab.js"
23
+ },
24
+ "./package.json": "./package.json"
25
+ },
26
+ "files": ["dist", "README.md", "CHANGELOG.md", "LICENSE"],
27
+ "scripts": {
28
+ "build": "vite build && tsc -p tsconfig.build.json",
29
+ "prepublishOnly": "npm run build",
30
+ "prepack": "node ../../tooling/sync-package-docs.mjs"
31
+ },
32
+ "peerDependencies": {
33
+ "@web-ppt/edit-core": "^0.5.0-beta.2"
34
+ },
35
+ "publishConfig": { "access": "public" }
36
+ }