@lincyaw/kernel 1.0.0-rc.0 → 1.0.0-rc.1
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/README.md +7 -7
- package/package.json +6 -2
- package/src/access.ts +18 -0
- package/src/anchors.ts +5 -1
- package/src/authoring.ts +56 -0
- package/src/blocks.ts +0 -8
- package/src/component-code.ts +6 -0
- package/src/contracts/agent.ts +5 -82
- package/src/contracts/anchors.ts +46 -0
- package/src/contracts/authoring-actions.ts +60 -0
- package/src/contracts/authoring-clients.ts +213 -0
- package/src/contracts/authoring.ts +180 -0
- package/src/contracts/checks.ts +29 -0
- package/src/contracts/classroom.ts +49 -0
- package/src/contracts/diagnostics.ts +37 -0
- package/src/contracts/document-audio.ts +150 -0
- package/src/contracts/document-comments.ts +31 -0
- package/src/contracts/document-history.ts +63 -0
- package/src/contracts/document-image.ts +32 -0
- package/src/contracts/document-reads.ts +57 -0
- package/src/contracts/document-voice.ts +44 -0
- package/src/contracts/document.ts +240 -0
- package/src/contracts/execution.ts +4 -5
- package/src/contracts/locale.ts +3 -0
- package/src/contracts/offline.ts +2 -0
- package/src/contracts/plugin.ts +183 -0
- package/src/contracts/presenter-panels.ts +17 -0
- package/src/contracts/preview.ts +305 -310
- package/src/contracts/production-assets.ts +61 -0
- package/src/contracts/production.ts +109 -0
- package/src/contracts/projects.ts +59 -0
- package/src/contracts/server.ts +1 -51
- package/src/contracts/speech-settings.ts +16 -0
- package/src/contracts/speech.ts +82 -70
- package/src/contracts/steps.ts +15 -0
- package/src/contracts/transcription-adoption.ts +79 -0
- package/src/contracts/transcription.ts +112 -0
- package/src/contracts/voice.ts +97 -0
- package/src/contracts/voiceover-execution.ts +68 -0
- package/src/contracts/workspace.ts +458 -0
- package/src/contracts.ts +28 -278
- package/src/diagnostics.ts +7 -0
- package/src/document-authoring-actions.ts +47 -0
- package/src/document-caption-timings.ts +64 -0
- package/src/document-colors.ts +25 -0
- package/src/document-comments.ts +118 -0
- package/src/document-components.ts +251 -0
- package/src/document-operations.ts +132 -0
- package/src/document-pages.ts +215 -0
- package/src/document-reads.ts +81 -0
- package/src/document-schema.ts +85 -0
- package/src/document-voices.ts +153 -0
- package/src/document.ts +252 -0
- package/src/durations.ts +7 -0
- package/src/errors.ts +8 -0
- package/src/execution.ts +5 -1
- package/src/fingerprint.ts +1 -1
- package/src/follow.ts +26 -30
- package/src/git.ts +1 -1
- package/src/index.ts +66 -13
- package/src/ir.ts +54 -5
- package/src/jupyter-kernel.ts +9 -0
- package/src/kernel.ts +38 -25
- package/src/keyboard.ts +43 -0
- package/src/locale.ts +13 -0
- package/src/lock.ts +4 -8
- package/src/logging.ts +1 -1
- package/src/{markdown.ts → lower-myst.ts} +2 -1
- package/src/node/assets.ts +23 -0
- package/src/node/authoring-actions.ts +50 -0
- package/src/node/authoring-clients.ts +14 -0
- package/src/node/document-audio.ts +22 -0
- package/src/node/document-history.ts +11 -0
- package/src/node/document-image.ts +8 -0
- package/src/node/document-reads.ts +10 -0
- package/src/node/document-voice.ts +7 -0
- package/src/node/ffmpeg.ts +29 -0
- package/src/node/file-version.ts +6 -0
- package/src/node/fonts.ts +22 -0
- package/src/node/index.ts +29 -133
- package/src/node/installed-packages.ts +11 -0
- package/src/node/lines.ts +24 -0
- package/src/node/locale.ts +7 -0
- package/src/node/network.ts +11 -0
- package/src/node/operator-speech.ts +91 -0
- package/src/node/packages.ts +74 -0
- package/src/node/paths.ts +7 -0
- package/src/node/projects.ts +7 -0
- package/src/node/resources.ts +86 -0
- package/src/node/revision.ts +6 -0
- package/src/node/run-directory.ts +70 -0
- package/src/node/transcription-adoption.ts +9 -0
- package/src/node/transcription.ts +10 -0
- package/src/node/voiceover-routes.ts +73 -0
- package/src/node/voiceover.ts +199 -0
- package/src/page-source.ts +359 -0
- package/src/parameters.ts +81 -0
- package/src/preview-edit.ts +18 -0
- package/src/preview-events.ts +57 -0
- package/src/preview-frame.ts +1220 -0
- package/src/props.ts +17 -0
- package/src/push-stream.ts +67 -0
- package/src/reading.ts +12 -7
- package/src/recording.ts +38 -0
- package/src/renderer-components.ts +30 -0
- package/src/responses.ts +49 -0
- package/src/run-outputs.ts +15 -0
- package/src/runner.py +3 -3
- package/src/services.ts +6 -6
- package/src/slides.ts +36 -0
- package/src/speech-words.ts +76 -0
- package/src/speech.ts +13 -0
- package/src/step-keys.ts +38 -0
- package/src/steps.ts +7 -0
- package/src/transcription-adoption.ts +105 -0
- package/src/uri-decoding.ts +8 -0
- package/src/urls.ts +13 -0
- package/src/views.ts +47 -0
- package/src/voice.ts +86 -0
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Presmium's shared kernel: the IR and the plugin contracts, the service registry, anchors and checks. Plain TypeScript, used by the compiler, the renderers, the IR runtime and every plugin.
|
|
4
4
|
|
|
5
|
-
Part of [Presmium](https://github.com/Lincyaw/presmium), which
|
|
5
|
+
Part of [Presmium](https://github.com/Lincyaw/presmium), which publishes structured teaching documents as web pages, slides, Jupyter notebooks and narrated lectures.
|
|
6
6
|
|
|
7
7
|
## Install
|
|
8
8
|
|
|
@@ -14,19 +14,19 @@ Plugins declare it as a peer dependency, so they share the application's copy:
|
|
|
14
14
|
|
|
15
15
|
## Entries
|
|
16
16
|
|
|
17
|
-
- `@lincyaw/kernel`: the contracts (`PresmiumNodePlugin`, `PluginDefinition`, `DirectiveSpec`, `IRPass`, `Renderer`, `RenderContext`, `Check`, the service tokens), the IR types (`IRDocument` and its nodes), `KERNEL_VERSION`, `IR_VERSION`, and plain helpers such as `docBlocks`, `withProp`, `deckLocale`, `
|
|
18
|
-
- `@lincyaw/kernel/contracts`, `@lincyaw/kernel/ir`: the contracts or the IR types alone
|
|
19
|
-
- `@lincyaw/kernel/reading`: plain functions that read compiled
|
|
20
|
-
- `@lincyaw/kernel/node`: helpers for Node code: `copyPublished` (its other exports are internal).
|
|
17
|
+
- `@lincyaw/kernel`: the contracts (`PresmiumNodePlugin`, `PluginDefinition`, `DirectiveSpec`, `IRPass`, `Renderer`, `RenderContext`, `Check`, the service tokens), the canonical authoring types (`TeachingDocument`, `DocumentEditOperation`), `DOCUMENT_ENTRY`, `DOCUMENT_SCHEMA_VERSION`, `DOCUMENT_JSON_SCHEMA`, `validateDocument`, the derived IR types (`IRDocument` and its nodes), `KERNEL_VERSION`, `IR_VERSION`, and plain helpers such as `docBlocks`, `withProp`, `deckLocale`, `jsonResponse` and `problemResponse`.
|
|
18
|
+
- `@lincyaw/kernel/contracts`, `@lincyaw/kernel/ir`: the contracts (types, service tokens and the preview protocol's parsers) or the IR types alone; the helpers are only in `@lincyaw/kernel`.
|
|
19
|
+
- `@lincyaw/kernel/reading`: plain functions that read a compiled document, the IR (`chapters`, `blockText`, `narrationAt`, `tokenize`); they do no I/O.
|
|
20
|
+
- `@lincyaw/kernel/node`: helpers for Node code: `copyPublished` and `isInside` (its other exports are internal).
|
|
21
21
|
- `@lincyaw/kernel/runner.py`: the shared Python runner of the executors.
|
|
22
22
|
|
|
23
23
|
## Versions
|
|
24
24
|
|
|
25
|
-
`KERNEL_VERSION` is the version of the plugin contract, separate from the package version: 1.0.0 in Presmium 1.0, a minor version higher for each compatible addition to the contract, a major version (and a major version of the packages) for an incompatible change. Plugins declare `kernel: '^1.0.0'`, which any Presmium 1.x satisfies; a plugin whose range excludes it is not loaded. `
|
|
25
|
+
`KERNEL_VERSION` is the version of the plugin contract, separate from the package version: 1.0.0 in Presmium 1.0, a minor version higher for each compatible addition to the contract, a major version (and a major version of the packages) for an incompatible change. Plugins declare `kernel: '^1.0.0'`, which any Presmium 1.x satisfies; a plugin whose range excludes it is not loaded. `DOCUMENT_SCHEMA_VERSION` versions the authoritative authoring document; `IR_VERSION` separately versions derived runtime snapshots. Source CAS versions and runtime snapshot IDs have separate lifecycles.
|
|
26
26
|
|
|
27
27
|
## Documentation
|
|
28
28
|
|
|
29
|
-
- [Plugin guide](https://github.com/Lincyaw/presmium/blob/main/site/plugins/lecture.
|
|
29
|
+
- [Plugin guide](https://github.com/Lincyaw/presmium/blob/main/site/plugins/locales/en/lecture.json)
|
|
30
30
|
|
|
31
31
|
## License
|
|
32
32
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lincyaw/kernel",
|
|
3
|
-
"version": "1.0.0-rc.
|
|
3
|
+
"version": "1.0.0-rc.1",
|
|
4
4
|
"description": "Presmium's shared kernel: the IR and plugin contracts, the service registry, anchors and checks",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -33,8 +33,12 @@
|
|
|
33
33
|
"src"
|
|
34
34
|
],
|
|
35
35
|
"dependencies": {
|
|
36
|
+
"@cfworker/json-schema": "4.1.1",
|
|
36
37
|
"@types/semver": "^7.8.0",
|
|
38
|
+
"fast-json-patch": "3.1.1",
|
|
39
|
+
"json-schema-library": "11.6.16",
|
|
37
40
|
"parse5": "7.3.0",
|
|
38
|
-
"semver": "^7.8.5"
|
|
41
|
+
"semver": "^7.8.5",
|
|
42
|
+
"proper-lockfile": "4.1.2"
|
|
39
43
|
}
|
|
40
44
|
}
|
package/src/access.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { ApprovalKind, Member, MemberRole } from './contracts.ts'
|
|
2
|
+
|
|
3
|
+
const RANK: Record<MemberRole, number> = { reader: 0, editor: 1, owner: 2 }
|
|
4
|
+
|
|
5
|
+
export const APPROVAL_KINDS: readonly ApprovalKind[] = ['operate', 'spend', 'write', 'agent']
|
|
6
|
+
|
|
7
|
+
/** The person who runs presmium dev, presmium serve or presmium-mcp on their own machine. */
|
|
8
|
+
export const LOCAL_AUTHOR: Member = { id: 'local', name: 'local author', roles: ['owner'] }
|
|
9
|
+
|
|
10
|
+
/** Whether the member has the role or one above it (reader, editor, owner). */
|
|
11
|
+
export function hasRole(member: Member, role: MemberRole): boolean {
|
|
12
|
+
return member.roles.some(own => RANK[own] >= RANK[role])
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Whether a window that reports this view role shows the presentation: the presenter view or the audience's. */
|
|
16
|
+
export function isPresentingRole(role: string | undefined): boolean {
|
|
17
|
+
return role === 'presenter' || role === 'audience'
|
|
18
|
+
}
|
package/src/anchors.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AnchorResolver, AnchorResolverRegistry, Resolution } from './contracts.ts'
|
|
1
|
+
import type { AnchorResolver, AnchorResolverRegistry, Resolution } from './contracts/anchors.ts'
|
|
2
2
|
import { errorMessage } from './errors.ts'
|
|
3
3
|
|
|
4
4
|
const SCHEME = /^[a-z][a-z0-9+.-]*$/i
|
|
@@ -52,3 +52,7 @@ export function createAnchorResolverRegistry(resolvers: AnchorResolver[] = []):
|
|
|
52
52
|
for (const resolver of resolvers) registry.register(resolver)
|
|
53
53
|
return registry
|
|
54
54
|
}
|
|
55
|
+
|
|
56
|
+
export function isRenderable(resolution: Resolution): boolean {
|
|
57
|
+
return resolution.status === 'exact' || resolution.status === 'moved'
|
|
58
|
+
}
|
package/src/authoring.ts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { Validator } from '@cfworker/json-schema'
|
|
2
|
+
import type { Schema } from '@cfworker/json-schema'
|
|
3
|
+
import { DOCUMENT_JSON_SCHEMA } from './document-schema.ts'
|
|
4
|
+
import type { DocumentChange } from './contracts/workspace.ts'
|
|
5
|
+
import type { DocumentIssue } from './contracts/document.ts'
|
|
6
|
+
import { DOCUMENT_PAGE_CHANGE_APPLY_SCHEMA, DOCUMENT_PAGE_CHANGE_SCHEMA, DOCUMENT_PAGE_DUPLICATION_APPLY_SCHEMA, DOCUMENT_PAGE_DUPLICATION_SCHEMA } from './contracts/authoring.ts'
|
|
7
|
+
import type { DocumentPageChangeReference, DocumentPageChangeRequest, DocumentPageDuplicationReference, DocumentPageDuplicationRequest } from './contracts/authoring.ts'
|
|
8
|
+
|
|
9
|
+
const object = (properties: Record<string, Schema>, required = Object.keys(properties)): Schema => ({ type: 'object', properties, required, additionalProperties: false })
|
|
10
|
+
const ref = (name: string): Schema => ({ $ref: `#/$defs/${name}` })
|
|
11
|
+
const id: Schema = { type: 'string', minLength: 1, maxLength: 200 }
|
|
12
|
+
const index: Schema = { type: 'integer', minimum: 0 }
|
|
13
|
+
const operation = (type: string, fields: Record<string, Schema>, required = Object.keys(fields)) => object({ type: { const: type }, ...fields }, ['type', ...required])
|
|
14
|
+
const { $schema: _schema, $id: _id, $defs, ...documentSchema } = structuredClone(DOCUMENT_JSON_SCHEMA) as Exclude<Schema, boolean>
|
|
15
|
+
|
|
16
|
+
/** Shared strict request schema; nested values reuse the canonical document definitions. */
|
|
17
|
+
export const DOCUMENT_CHANGE_JSON_SCHEMA: Schema = {
|
|
18
|
+
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
19
|
+
$ref: '#/$defs/DocumentChange',
|
|
20
|
+
$defs: {
|
|
21
|
+
...$defs,
|
|
22
|
+
TeachingDocument: documentSchema,
|
|
23
|
+
DocumentOperation: { oneOf: [
|
|
24
|
+
operation('insertBlock', { sectionId: id, parentId: id, index, block: ref('Block') }, ['sectionId', 'index', 'block']),
|
|
25
|
+
operation('moveBlock', { blockId: id, sectionId: id, parentId: id, index }, ['blockId', 'sectionId', 'index']),
|
|
26
|
+
operation('deleteBlock', { blockId: id }),
|
|
27
|
+
operation('updateText', { blockId: id, content: { type: 'array', items: ref('RichTextNode') } }),
|
|
28
|
+
operation('updateSection', { sectionId: id, title: { type: 'string' }, layout: ref('Layout') }, ['sectionId']),
|
|
29
|
+
operation('setStepState', { stepId: id, visuals: { type: 'array', items: ref('Visual') }, inherits: id }, ['stepId', 'visuals']),
|
|
30
|
+
operation('setNarration', { narration: ref('Narration'), stepId: id, index }, ['narration']),
|
|
31
|
+
operation('setTiming', { stepId: id, sectionId: id, value: ref('StepProduction') }, ['value']),
|
|
32
|
+
operation('replaceAsset', { asset: ref('Asset') }),
|
|
33
|
+
] },
|
|
34
|
+
DocumentChange: {
|
|
35
|
+
...object({ id, baseDocumentVersion: { type: 'string', minLength: 1, maxLength: 128 }, replacement: ref('TeachingDocument'), operations: { type: 'array', minItems: 1, maxItems: 1000, items: ref('DocumentOperation') }, fileChanges: { type: 'array', maxItems: 15, items: object({ path: { type: 'string', minLength: 1 }, content: { type: 'string' }, base: { type: ['string', 'null'] } }) } }, ['id', 'baseDocumentVersion']),
|
|
36
|
+
oneOf: [{ required: ['replacement'], not: { required: ['operations'] } }, { required: ['operations'], not: { required: ['replacement'] } }],
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const validator = new Validator(structuredClone(DOCUMENT_CHANGE_JSON_SCHEMA), '2020-12', false)
|
|
42
|
+
const duplication = new Validator(structuredClone(DOCUMENT_PAGE_DUPLICATION_SCHEMA) as unknown as Schema, '2020-12', false)
|
|
43
|
+
const duplicationApply = new Validator(structuredClone(DOCUMENT_PAGE_DUPLICATION_APPLY_SCHEMA) as unknown as Schema, '2020-12', false)
|
|
44
|
+
const pageChange = new Validator(structuredClone(DOCUMENT_PAGE_CHANGE_SCHEMA) as unknown as Schema, '2020-12', false)
|
|
45
|
+
const pageChangeApply = new Validator(structuredClone(DOCUMENT_PAGE_CHANGE_APPLY_SCHEMA) as unknown as Schema, '2020-12', false)
|
|
46
|
+
|
|
47
|
+
export function validateDocumentPageDuplication(value: unknown): value is DocumentPageDuplicationRequest { return duplication.validate(value).valid }
|
|
48
|
+
export function validateDocumentPageDuplicationApply(value: unknown): value is DocumentPageDuplicationReference { return duplicationApply.validate(value).valid }
|
|
49
|
+
export function validateDocumentPageChange(value: unknown): value is DocumentPageChangeRequest { return pageChange.validate(value).valid }
|
|
50
|
+
export function validateDocumentPageChangeApply(value: unknown): value is DocumentPageChangeReference { return pageChangeApply.validate(value).valid }
|
|
51
|
+
|
|
52
|
+
export function validateDocumentChange(value: unknown): { change?: DocumentChange, issues: DocumentIssue[] } {
|
|
53
|
+
const checked = validator.validate(value)
|
|
54
|
+
const issues: DocumentIssue[] = checked.errors.slice(0, 100).map(error => ({ path: error.instanceLocation.replace(/^#/, ''), code: `schema.${error.keyword}`, message: error.error, severity: 'error' }))
|
|
55
|
+
return { ...checked.valid ? { change: value as DocumentChange } : {}, issues }
|
|
56
|
+
}
|
package/src/blocks.ts
CHANGED
|
@@ -17,11 +17,3 @@ export function nodeMdast(node: IRNode): MdastNode[] {
|
|
|
17
17
|
if (node.type === 'markdown') return [node.mdast]
|
|
18
18
|
return node.type === 'component' && node.fallback ? [node.fallback] : []
|
|
19
19
|
}
|
|
20
|
-
|
|
21
|
-
export function withProp(props: Record<string, unknown>, path: string, value: unknown): Record<string, unknown> {
|
|
22
|
-
const [key = '', ...rest] = path.split('.')
|
|
23
|
-
if (rest.length === 0) return { ...props, [key]: value }
|
|
24
|
-
const inner = props[key]
|
|
25
|
-
const object = inner !== null && typeof inner === 'object' && !Array.isArray(inner) ? inner as Record<string, unknown> : {}
|
|
26
|
-
return { ...props, [key]: withProp(object, rest.join('.'), value) }
|
|
27
|
-
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { CodeNode, ComponentNode } from './ir.ts'
|
|
2
|
+
|
|
3
|
+
/** The code block among a component's child blocks, e.g. the code of a run panel. */
|
|
4
|
+
export function componentCode(node: ComponentNode): CodeNode | undefined {
|
|
5
|
+
return node.children?.map(block => block.node).find((child): child is CodeNode => child.type === 'code')
|
|
6
|
+
}
|
package/src/contracts/agent.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Member } from './server.ts'
|
|
2
2
|
import { defineService } from './service.ts'
|
|
3
3
|
import type { ServiceToken } from './service.ts'
|
|
4
|
+
import type { Locale } from './locale.ts'
|
|
4
5
|
|
|
5
6
|
export interface SourceRange { file: string, startLine: number, endLine: number }
|
|
6
7
|
|
|
@@ -9,6 +10,7 @@ export interface Focus {
|
|
|
9
10
|
id: string
|
|
10
11
|
from: 'source' | 'web' | 'slides'
|
|
11
12
|
document: string
|
|
13
|
+
scope?: 'document' | 'workspace'
|
|
12
14
|
source: SourceRange
|
|
13
15
|
excerpt: string
|
|
14
16
|
section?: string
|
|
@@ -21,6 +23,7 @@ export interface Focus {
|
|
|
21
23
|
export interface FocusReport {
|
|
22
24
|
id: string
|
|
23
25
|
from: Focus['from']
|
|
26
|
+
scope?: Focus['scope']
|
|
24
27
|
section?: string
|
|
25
28
|
block?: string
|
|
26
29
|
step?: number
|
|
@@ -68,86 +71,6 @@ export interface AgentTool {
|
|
|
68
71
|
input: Record<string, unknown>
|
|
69
72
|
kind: AgentToolKind
|
|
70
73
|
run(input: Record<string, unknown>, ctx: AgentToolContext): Promise<unknown>
|
|
74
|
+
/** Text for the codes `run` throws as error messages, in the document's language; other messages pass on as they are. */
|
|
75
|
+
errors?: Record<Locale, Record<string, string>>
|
|
71
76
|
}
|
|
72
|
-
|
|
73
|
-
export type ReviewChangeKind = 'added' | 'modified' | 'deleted'
|
|
74
|
-
|
|
75
|
-
export interface ReviewChange {
|
|
76
|
-
path: string
|
|
77
|
-
kind: ReviewChangeKind
|
|
78
|
-
binary: boolean
|
|
79
|
-
added: number
|
|
80
|
-
removed: number
|
|
81
|
-
diff?: string
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
export interface ReviewStep {
|
|
85
|
-
id: string
|
|
86
|
-
label: string
|
|
87
|
-
tool?: string
|
|
88
|
-
at: string
|
|
89
|
-
changes: ReviewChange[]
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
export interface ChangeReview {
|
|
93
|
-
readonly root: string
|
|
94
|
-
readonly visibility: 'immediate' | 'on-commit'
|
|
95
|
-
step(label: string, tool?: string): Promise<ReviewStep>
|
|
96
|
-
steps(): readonly ReviewStep[]
|
|
97
|
-
diff(): Promise<ReviewChange[]>
|
|
98
|
-
restoreTo?(stepId: string): Promise<void>
|
|
99
|
-
accept(reject?: string[]): Promise<string[]>
|
|
100
|
-
rollback(): Promise<void>
|
|
101
|
-
close(): Promise<void>
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
export interface ChangeReviewService {
|
|
105
|
-
readonly engine: string
|
|
106
|
-
open(options: { document: string, task: string, member: Member }): Promise<ChangeReview>
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
export const CHANGE_REVIEW = defineService<ChangeReviewService>('presmium.changeReview')
|
|
110
|
-
|
|
111
|
-
export interface RevisionOrigin {
|
|
112
|
-
kind: 'agent' | 'document' | 'editor'
|
|
113
|
-
task?: string
|
|
114
|
-
member?: Member
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
export interface Revision {
|
|
118
|
-
id: string
|
|
119
|
-
message: string
|
|
120
|
-
author: string
|
|
121
|
-
date: string
|
|
122
|
-
origin: RevisionOrigin
|
|
123
|
-
files: string[]
|
|
124
|
-
revertedBy?: string
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
export type RevertResult = { ok: true, revision: Revision } | { ok: false, conflicts: string[] }
|
|
128
|
-
|
|
129
|
-
export interface RevisionService {
|
|
130
|
-
commit(change: { files: string[], message: string, origin: RevisionOrigin }): Promise<Revision | undefined>
|
|
131
|
-
history(filter?: { document?: string, task?: string, limit?: number }): Promise<Revision[]>
|
|
132
|
-
revert(id: string, member: Member): Promise<RevertResult>
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
export const REVISIONS = defineService<RevisionService>('presmium.revisions')
|
|
136
|
-
|
|
137
|
-
export interface DocumentLockService {
|
|
138
|
-
claim(document: string, holder: string): (() => void) | undefined
|
|
139
|
-
holder(document: string): string | undefined
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
export const DOCUMENT_LOCKS = defineService<DocumentLockService>('presmium.documentLocks')
|
|
143
|
-
|
|
144
|
-
export type DocumentFileChange =
|
|
145
|
-
| { path: string, content: Uint8Array, executable?: boolean, at?: number }
|
|
146
|
-
| { path: string, link: string }
|
|
147
|
-
| { path: string, remove: true }
|
|
148
|
-
|
|
149
|
-
export interface DocumentFiles {
|
|
150
|
-
write(changes: DocumentFileChange[]): Promise<void>
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
export const DOCUMENT_FILES = defineService<DocumentFiles>('presmium.documentFiles')
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { Unsubscribe } from './plugin.ts'
|
|
2
|
+
|
|
3
|
+
export type AnchorString = `${string}:${string}`
|
|
4
|
+
|
|
5
|
+
export type ResolutionStatus = 'exact' | 'moved' | 'fuzzy' | 'lost'
|
|
6
|
+
|
|
7
|
+
export interface ResolvedLocation {
|
|
8
|
+
file: string
|
|
9
|
+
startLine: number
|
|
10
|
+
endLine: number
|
|
11
|
+
text: string
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface Resolution {
|
|
15
|
+
status: ResolutionStatus
|
|
16
|
+
location?: ResolvedLocation
|
|
17
|
+
fingerprint?: string
|
|
18
|
+
candidates?: ResolvedLocation[]
|
|
19
|
+
message?: string
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface SourceReader {
|
|
23
|
+
read(file: string, ref?: string): Promise<string | undefined>
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface RecordedAnchor {
|
|
27
|
+
fingerprint: string
|
|
28
|
+
file: string
|
|
29
|
+
startLine: number
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface AnchorResolver {
|
|
33
|
+
scheme: string
|
|
34
|
+
resolve(anchor: AnchorString, src: SourceReader, recorded?: RecordedAnchor, ref?: string): Promise<Resolution>
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface AnchorResolverRegistry {
|
|
38
|
+
register(resolver: AnchorResolver): Unsubscribe
|
|
39
|
+
resolve(anchor: AnchorString, src: SourceReader, recorded?: RecordedAnchor, ref?: string): Promise<Resolution>
|
|
40
|
+
schemes(): string[]
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface AnchorLockFile {
|
|
44
|
+
version: 1
|
|
45
|
+
anchors: Record<string, RecordedAnchor>
|
|
46
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { DocumentCaptionTimingAction } from '../document-caption-timings.ts'
|
|
2
|
+
import type { DocumentCommentAction, DocumentCommentEffect } from './document-comments.ts'
|
|
3
|
+
import type { JsonValue, VoiceSource } from './document.ts'
|
|
4
|
+
import type { DocumentVoiceImpact, DocumentVoiceScope } from '../document-voices.ts'
|
|
5
|
+
import type { CanonicalSource } from '../ir.ts'
|
|
6
|
+
import type { DocumentChange, DocumentChangeAnswer, RevisionOrigin } from './workspace.ts'
|
|
7
|
+
import type { DocumentChangePlan } from './authoring.ts'
|
|
8
|
+
|
|
9
|
+
export const DOCUMENT_ACTION_MAX_REQUEST_CHARS = 4096
|
|
10
|
+
export const DOCUMENT_ACTION_MAX_RESPONSE_CHARS = 7168
|
|
11
|
+
export const DOCUMENT_AUTHORING_ACTION_TYPES = ['comment.create', 'comment.reply', 'comment.update', 'comment.react', 'comment.resolve', 'comment.reopen', 'comment.delete', 'voice.set', 'component.parameters.patch', 'component.step-parameters.patch', 'component.step-parameters.reset', 'component.definition.use', 'caption.timing.set', 'caption.timing.reset'] as const
|
|
12
|
+
export const DOCUMENT_PARAMETER_MAX_CHANGES = 32
|
|
13
|
+
export const DOCUMENT_PARAMETER_MAX_PATH_DEPTH = 16
|
|
14
|
+
/** Paths belong only to this component's parameters; set replaces an array item or appends at its length. */
|
|
15
|
+
export type DocumentParameterChange = { op: 'set', path: string[], value: JsonValue } | { op: 'remove', path: string[] }
|
|
16
|
+
export type DocumentComponentAction =
|
|
17
|
+
| { type: 'component.parameters.patch', blockId: string, changes: DocumentParameterChange[] }
|
|
18
|
+
| { type: 'component.step-parameters.patch', blockId: string, stepId: string, changes: DocumentParameterChange[] }
|
|
19
|
+
| { type: 'component.step-parameters.reset', blockId: string, stepId: string }
|
|
20
|
+
| { type: 'component.definition.use', blockId: string, definitionId: string, changes?: DocumentParameterChange[] }
|
|
21
|
+
export interface DocumentComponentEffect {
|
|
22
|
+
blockId: string
|
|
23
|
+
sectionId: string
|
|
24
|
+
layer: 'base' | 'step' | 'definition'
|
|
25
|
+
stepId?: string
|
|
26
|
+
definitionId: string
|
|
27
|
+
previousDefinitionId?: string
|
|
28
|
+
changed: boolean
|
|
29
|
+
paths: string[][]
|
|
30
|
+
affected: { blocks: string[], steps: string[], pages: string[] }
|
|
31
|
+
overrides: { preserved: string[], materialized: string[], reset: string[] }
|
|
32
|
+
references: { before: string[], after: string[] }
|
|
33
|
+
}
|
|
34
|
+
export const DOCUMENT_COMPONENT_IMPACT_COLLECTIONS = ['affected.blocks', 'affected.steps', 'affected.pages', 'overrides.preserved', 'overrides.materialized', 'overrides.reset', 'references.before', 'references.after'] as const
|
|
35
|
+
export type DocumentComponentImpactCollection = typeof DOCUMENT_COMPONENT_IMPACT_COLLECTIONS[number]
|
|
36
|
+
export interface DocumentComponentImpactQuery extends DocumentAuthoringActionRead { collection: DocumentComponentImpactCollection, offset?: number }
|
|
37
|
+
export interface DocumentComponentImpactAnswer {
|
|
38
|
+
planId: string, digest: string, source: CanonicalSource, currentSource: CanonicalSource, state: 'current' | 'applied' | 'old'
|
|
39
|
+
collection: DocumentComponentImpactCollection, offset: number, total: number, entries: string[], nextOffset?: number, complete: boolean
|
|
40
|
+
}
|
|
41
|
+
/** New recording adoption belongs to the real-media service, never a raw settings action. */
|
|
42
|
+
export type DocumentAuthoringAction = DocumentCommentAction | DocumentComponentAction | DocumentCaptionTimingAction | { type: 'voice.set', scope: DocumentVoiceScope, source: Exclude<VoiceSource, { type: 'recording' }> | null, replaceOverrides?: boolean }
|
|
43
|
+
export interface DocumentAuthoringActionRequest { id: string, source: CanonicalSource, action: DocumentAuthoringAction }
|
|
44
|
+
export type DocumentAuthoringActionEffect = { kind: 'caption', value: { stepId: string, segments: number, changed: boolean, mode: 'manual' | 'automatic' } } | { kind: 'comment', value: DocumentCommentEffect } | { kind: 'voice', scope: DocumentVoiceScope['kind'], value: DocumentVoiceImpact, recordings: { preserved: number, removed: number } } | { kind: 'component', value: DocumentComponentEffect }
|
|
45
|
+
export interface DocumentAuthoringActionPlan extends DocumentChangePlan {
|
|
46
|
+
action: DocumentAuthoringAction
|
|
47
|
+
effect: DocumentAuthoringActionEffect
|
|
48
|
+
change: DocumentChange
|
|
49
|
+
}
|
|
50
|
+
export interface DocumentAuthoringActionReference { planId: string, source: CanonicalSource, digest: string, confirmDeletion?: true }
|
|
51
|
+
export interface DocumentAuthoringActionRead { planId: string, digest: string }
|
|
52
|
+
export interface DocumentAuthoringActionInspection { plan: DocumentAuthoringActionPlan, currentSource: CanonicalSource, state: 'current' | 'applied' | 'old' }
|
|
53
|
+
export interface AppliedDocumentAuthoringAction extends Omit<DocumentChangeAnswer, 'content' | 'issues' | 'commit'> {
|
|
54
|
+
commit?: { id: string, origin: RevisionOrigin['kind'] }
|
|
55
|
+
planId: string
|
|
56
|
+
digest: string
|
|
57
|
+
action: DocumentAuthoringAction['type']
|
|
58
|
+
effect: DocumentAuthoringActionEffect
|
|
59
|
+
verification: { saved: true, source: CanonicalSource, plannedVersion: string, actionStateVerified: true, compiled: 'not-requested', runtime: 'not-requested' }
|
|
60
|
+
}
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import type { Schema } from '@cfworker/json-schema'
|
|
2
|
+
import type { CanonicalSource } from '../ir.ts'
|
|
3
|
+
import type { Member } from './server.ts'
|
|
4
|
+
import type { ProductionBinding, ProductionPlaybackState } from './production.ts'
|
|
5
|
+
import { PRODUCTION_PLAYBACK_RATES } from './production.ts'
|
|
6
|
+
import { DOCUMENT_PAGE_DUPLICATION_SCHEMA } from './authoring.ts'
|
|
7
|
+
|
|
8
|
+
export const AUTHORING_CLIENT_MAX_REQUEST_CHARS = 4096
|
|
9
|
+
export const AUTHORING_CLIENT_MAX_RESPONSE_CHARS = 7168
|
|
10
|
+
export const AUTHORING_CLIENT_MAX_CLIENTS = 16
|
|
11
|
+
export const AUTHORING_CLIENT_MAX_REQUESTS = 64
|
|
12
|
+
export const AUTHORING_CLIENT_REQUEST_TIMEOUT_MS = 30000
|
|
13
|
+
export const AUTHORING_CLIENT_ACK_MAX_BYTES = 32768
|
|
14
|
+
export const AUTHORING_CLIENT_EVENT_MAX_BYTES = 8 << 10
|
|
15
|
+
export const AUTHORING_CLIENT_ACTIONS = ['state.read', 'source.flush', 'preview.prepare', 'comment-drafts.inspect', 'comment-drafts.read', 'comment-drafts.restore', 'comment-drafts.discard', 'component-drafts.inspect', 'component-drafts.read', 'component-drafts.restore', 'component-drafts.discard', 'playback.pause', 'position.step', 'playback.play', 'playback.seek', 'playback.rate'] as const
|
|
16
|
+
export type AuthoringClientActionType = typeof AUTHORING_CLIENT_ACTIONS[number]
|
|
17
|
+
export type AuthoringClientAction =
|
|
18
|
+
| { type: 'state.read' }
|
|
19
|
+
| { type: 'source.flush' }
|
|
20
|
+
| { type: 'preview.prepare' }
|
|
21
|
+
| { type: 'comment-drafts.inspect', cursor?: { after: string, token: string }, limit?: number }
|
|
22
|
+
| { type: 'comment-drafts.read', draft: ClientCommentDraftReference, offset: number, maxChars?: number }
|
|
23
|
+
| { type: 'comment-drafts.restore', draft: ClientCommentDraftReference, composerId: string, expectedCurrent: ClientCommentDraftReference | null }
|
|
24
|
+
| { type: 'comment-drafts.discard', draft: ClientCommentDraftReference }
|
|
25
|
+
| { type: 'component-drafts.inspect', cursor?: { after: string, token: string }, limit?: number }
|
|
26
|
+
| { type: 'component-drafts.read', draft: ClientComponentDraftReference, offset: number, maxChars?: number }
|
|
27
|
+
| { type: 'component-drafts.restore', draft: ClientComponentDraftReference, expectedCurrent: ClientComponentDraftReference | null }
|
|
28
|
+
| { type: 'component-drafts.discard', draft: ClientComponentDraftReference, current: boolean }
|
|
29
|
+
| { type: 'playback.pause' }
|
|
30
|
+
| { type: 'position.step', sectionId: string, stepId: string }
|
|
31
|
+
| { type: 'playback.play', binding: ProductionBinding }
|
|
32
|
+
| { type: 'playback.seek', binding: ProductionBinding, timeMs: number }
|
|
33
|
+
| { type: 'playback.rate', binding: ProductionBinding, rate: number }
|
|
34
|
+
|
|
35
|
+
export interface ClientCommentDraftReference { id: string, revision: number, token: string }
|
|
36
|
+
export interface ClientComponentDraftReference { id: string, revision: number, token: string, blockId: string, stepId?: string }
|
|
37
|
+
/** No parameter values, baselines, workspace identity or member IDs enter this metadata. */
|
|
38
|
+
export interface ClientComponentDraftMetadata {
|
|
39
|
+
draft: ClientComponentDraftReference
|
|
40
|
+
definitionId: string
|
|
41
|
+
current: boolean
|
|
42
|
+
storage: 'durable' | 'session'
|
|
43
|
+
status: 'available' | 'pending' | 'session-only'
|
|
44
|
+
updatedAt: number
|
|
45
|
+
baseVersion: string
|
|
46
|
+
sourceCurrent: boolean
|
|
47
|
+
composing: boolean
|
|
48
|
+
reset: boolean
|
|
49
|
+
}
|
|
50
|
+
/** Explicit inspection deliberately excludes content, source signatures, comment baselines and pending values. */
|
|
51
|
+
export interface ClientCommentDraftMetadata {
|
|
52
|
+
draft: ClientCommentDraftReference
|
|
53
|
+
kind: 'create' | 'reply' | 'edit'
|
|
54
|
+
storage: 'durable' | 'session'
|
|
55
|
+
updatedAt: number
|
|
56
|
+
baseVersion: string
|
|
57
|
+
composing: boolean
|
|
58
|
+
pendingSubmission: boolean
|
|
59
|
+
ownersKnown: boolean
|
|
60
|
+
active: boolean
|
|
61
|
+
discardable: boolean
|
|
62
|
+
restore?: { composerId: string, expectedCurrent: ClientCommentDraftReference | null }
|
|
63
|
+
threadId?: string
|
|
64
|
+
commentId?: string
|
|
65
|
+
}
|
|
66
|
+
export type AuthoringClientActionResult =
|
|
67
|
+
| { type: 'comment-drafts.inspect', family: 'comment', coverage: 'stored-and-session' | 'session-only', skippedInvalid: number, token: string, drafts: ClientCommentDraftMetadata[], next?: { after: string, token: string } }
|
|
68
|
+
| { type: 'comment-drafts.read', family: 'comment', draft: ClientCommentDraftReference, storage: 'durable' | 'session', format: 'presmium-rich-text-json-v1', offset: number, endOffset: number, totalUtf16: number, totalUtf8: number, sha256: string, jsonText: string }
|
|
69
|
+
| { type: 'comment-drafts.restore', family: 'comment', draft: ClientCommentDraftReference, composerId: string, restored: ClientCommentDraftReference, preservedCurrent: ClientCommentDraftReference | null, targetState: 'current' | 'changed' }
|
|
70
|
+
| { type: 'comment-drafts.discard', family: 'comment', draft: ClientCommentDraftReference, discarded: true }
|
|
71
|
+
| { type: 'component-drafts.inspect', family: 'component', coverage: 'stored-and-session' | 'session-only', skippedInvalid: number, token: string, drafts: ClientComponentDraftMetadata[], next?: { after: string, token: string } }
|
|
72
|
+
| { type: 'component-drafts.read', family: 'component', draft: ClientComponentDraftReference, storage: 'durable' | 'session', format: 'presmium-component-parameters-json-v1', offset: number, endOffset: number, totalUtf16: number, totalUtf8: number, sha256: string, jsonText: string }
|
|
73
|
+
| { type: 'component-drafts.restore', family: 'component', draft: ClientComponentDraftReference, restored: ClientComponentDraftMetadata, preservedCurrent: ClientComponentDraftReference | null }
|
|
74
|
+
| { type: 'component-drafts.discard', family: 'component', draft: ClientComponentDraftReference, current: boolean, discarded: true }
|
|
75
|
+
|
|
76
|
+
/** Reports contain no editor text. Time is an observed sample, never a server-estimated playhead. */
|
|
77
|
+
export interface AuthoringClientSnapshot {
|
|
78
|
+
revision: number
|
|
79
|
+
source: CanonicalSource
|
|
80
|
+
view: 'document' | 'present'
|
|
81
|
+
editing: { dirty: boolean, saving: boolean, composing: boolean, conflicted: boolean, locked: boolean, loading: boolean }
|
|
82
|
+
interaction: string
|
|
83
|
+
position?: { sectionId: string, stepId?: string }
|
|
84
|
+
binding?: ProductionBinding
|
|
85
|
+
preview?: { status: 'unprepared' | 'preparing' | 'prepared' | 'unavailable', source?: CanonicalSource, irRevision?: string, projectRevision?: number }
|
|
86
|
+
playback?: ProductionPlaybackState
|
|
87
|
+
actions: AuthoringClientActionType[]
|
|
88
|
+
unsubmittedContent: 'not-inspected'
|
|
89
|
+
}
|
|
90
|
+
export interface AuthoringClientConnection { clientId: string, epoch: string, connectionId: string }
|
|
91
|
+
/** This credential belongs to the host-side transport; it is never returned by MCP client/state reads. */
|
|
92
|
+
export interface AuthoringClientLease extends AuthoringClientConnection { credential: string }
|
|
93
|
+
export interface AuthoringClientOpen { clientId: string, epoch: string, snapshot: AuthoringClientSnapshot }
|
|
94
|
+
export interface AuthoringClientDescriptor extends AuthoringClientConnection {
|
|
95
|
+
document: string
|
|
96
|
+
connected: boolean
|
|
97
|
+
observedAt: string
|
|
98
|
+
verification: 'reported-state'
|
|
99
|
+
snapshot: AuthoringClientSnapshot
|
|
100
|
+
}
|
|
101
|
+
export interface AuthoringClientList { document: string, clients: AuthoringClientDescriptor[], nextAfter?: string, unavailable: readonly string[] }
|
|
102
|
+
export interface AuthoringClientRequest {
|
|
103
|
+
clientId: string
|
|
104
|
+
epoch: string
|
|
105
|
+
requestId: string
|
|
106
|
+
source: CanonicalSource
|
|
107
|
+
expectedClientState: { connectionId: string, revision: number }
|
|
108
|
+
action: AuthoringClientAction
|
|
109
|
+
}
|
|
110
|
+
export type AuthoringClientRequestState = 'sent' | 'executing' | 'done' | 'refused' | 'failed' | 'expired'
|
|
111
|
+
export interface AuthoringClientRequestResult extends AuthoringClientConnection {
|
|
112
|
+
document: string
|
|
113
|
+
requestId: string
|
|
114
|
+
action: AuthoringClientActionType
|
|
115
|
+
source: CanonicalSource
|
|
116
|
+
state: AuthoringClientRequestState
|
|
117
|
+
admittedAt: string
|
|
118
|
+
expiresAt: string
|
|
119
|
+
observedAt?: string
|
|
120
|
+
snapshot?: AuthoringClientSnapshot
|
|
121
|
+
result?: AuthoringClientActionResult
|
|
122
|
+
error?: { code: string }
|
|
123
|
+
verification: 'not-acknowledged' | 'browser-acknowledged'
|
|
124
|
+
current: boolean
|
|
125
|
+
}
|
|
126
|
+
export type AuthoringClientEvent =
|
|
127
|
+
| { type: 'connected', connectionId: string }
|
|
128
|
+
| { type: 'request', request: AuthoringClientRequest, expiresAt: string }
|
|
129
|
+
| { type: 'cancel', requestId: string, connectionId: string }
|
|
130
|
+
export interface AuthoringClientReport { sequence: number, snapshot: AuthoringClientSnapshot }
|
|
131
|
+
export interface AuthoringClientAcknowledgement extends AuthoringClientReport {
|
|
132
|
+
requestId: string
|
|
133
|
+
state: 'executing' | 'done' | 'refused' | 'failed'
|
|
134
|
+
error?: { code: string }
|
|
135
|
+
result?: AuthoringClientActionResult
|
|
136
|
+
}
|
|
137
|
+
export interface AuthoringClients {
|
|
138
|
+
list(document: string, query: { clientId?: string, after?: string }, member: Member): Promise<AuthoringClientList>
|
|
139
|
+
request(document: string, request: AuthoringClientRequest, member: Member): Promise<AuthoringClientRequestResult>
|
|
140
|
+
get(document: string, query: { clientId: string, epoch: string, requestId: string }, member: Member): Promise<AuthoringClientRequestResult>
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** Reserve the complete receipt and the host-only lease, rather than budgeting only the text fragment. */
|
|
144
|
+
export function authoringClientResultFits(context: { request: AuthoringClientRequest, connection: AuthoringClientConnection, document: string, snapshot: AuthoringClientSnapshot }, result: AuthoringClientActionResult): boolean {
|
|
145
|
+
const { request, document, snapshot } = context, time = '2000-01-01T00:00:00.000Z'
|
|
146
|
+
const connection = { clientId: context.connection.clientId, epoch: context.connection.epoch, connectionId: context.connection.connectionId }
|
|
147
|
+
const receipt: AuthoringClientRequestResult = { ...connection, document, requestId: request.requestId, action: request.action.type, source: request.source,
|
|
148
|
+
state: 'done', admittedAt: time, expiresAt: time, observedAt: time, snapshot, result, verification: 'browser-acknowledged', current: false }
|
|
149
|
+
const body = { connection: { ...connection, credential: '0'.repeat(100) }, report: { sequence: Number.MAX_SAFE_INTEGER, snapshot, result, requestId: request.requestId, state: 'done' } }
|
|
150
|
+
const wire = JSON.stringify(body)
|
|
151
|
+
const frame = JSON.stringify({ type: 'authoring-client-publish', id: '0'.repeat(80), connectionId: connection.connectionId, sequence: Number.MAX_SAFE_INTEGER, kind: 'ack', value: body.report })
|
|
152
|
+
// Clock samples can change without changing the client revision; recheck the actual snapshot before sending.
|
|
153
|
+
return JSON.stringify(receipt, null, 2).length <= AUTHORING_CLIENT_MAX_RESPONSE_CHARS - 128
|
|
154
|
+
&& wire.length <= AUTHORING_CLIENT_MAX_RESPONSE_CHARS - 128 && new TextEncoder().encode(wire).byteLength <= AUTHORING_CLIENT_ACK_MAX_BYTES
|
|
155
|
+
&& new TextEncoder().encode(frame).byteLength <= AUTHORING_CLIENT_EVENT_MAX_BYTES - 128
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const id: Schema = { type: 'string', minLength: 1, maxLength: 80, pattern: '^[a-zA-Z0-9.-]+$' }
|
|
159
|
+
const entity: Schema = { type: 'string', minLength: 1, maxLength: 200 }
|
|
160
|
+
const integer: Schema = { type: 'integer', minimum: 0, maximum: Number.MAX_SAFE_INTEGER }
|
|
161
|
+
const number: Schema = { type: 'number', minimum: 0 }
|
|
162
|
+
const bool: Schema = { type: 'boolean' }
|
|
163
|
+
const object = (properties: Record<string, Schema>, required = Object.keys(properties)): Schema => ({ type: 'object', additionalProperties: false, properties, required })
|
|
164
|
+
const digest: Schema = { type: 'string', pattern: '^[a-f0-9]{64}$' }
|
|
165
|
+
const draftReference = object({ id: entity, revision: { type: 'integer', minimum: 1, maximum: Number.MAX_SAFE_INTEGER }, token: digest })
|
|
166
|
+
const componentDraftReference = object({ id: entity, revision: { type: 'integer', minimum: 1, maximum: Number.MAX_SAFE_INTEGER }, token: digest, blockId: entity, stepId: entity }, ['id', 'revision', 'token', 'blockId'])
|
|
167
|
+
const componentMetadata = object({ draft: componentDraftReference, definitionId: entity, current: bool, storage: { enum: ['durable', 'session'] }, status: { enum: ['available', 'pending', 'session-only'] }, updatedAt: integer, baseVersion: entity, sourceCurrent: bool, composing: bool, reset: bool })
|
|
168
|
+
const nullableComponentReference: Schema = { oneOf: [componentDraftReference, { type: 'null' }] }
|
|
169
|
+
const nullableCommentReference: Schema = { oneOf: [draftReference, { type: 'null' }] }
|
|
170
|
+
const commentRestore = object({ composerId: id, expectedCurrent: nullableCommentReference })
|
|
171
|
+
const cursor = object({ after: entity, token: digest })
|
|
172
|
+
export const AUTHORING_CLIENT_ACTION_RESULT_SCHEMA: Schema = { oneOf: [
|
|
173
|
+
object({ type: { const: 'comment-drafts.inspect' }, family: { const: 'comment' }, coverage: { enum: ['stored-and-session', 'session-only'] }, skippedInvalid: integer,
|
|
174
|
+
token: digest, drafts: { type: 'array', maxItems: 10, items: object({ draft: draftReference, kind: { enum: ['create', 'reply', 'edit'] }, storage: { enum: ['durable', 'session'] }, updatedAt: integer, baseVersion: entity, composing: bool, pendingSubmission: bool, ownersKnown: bool, active: bool, discardable: bool, restore: commentRestore, threadId: entity, commentId: entity }, ['draft', 'kind', 'storage', 'updatedAt', 'baseVersion', 'composing', 'pendingSubmission', 'ownersKnown', 'active', 'discardable']) }, next: cursor }, ['type', 'family', 'coverage', 'skippedInvalid', 'token', 'drafts']),
|
|
175
|
+
object({ type: { const: 'comment-drafts.read' }, family: { const: 'comment' }, draft: draftReference, storage: { enum: ['durable', 'session'] }, format: { const: 'presmium-rich-text-json-v1' }, offset: integer, endOffset: integer, totalUtf16: integer, totalUtf8: integer, sha256: digest, jsonText: { type: 'string', maxLength: 2048 } }),
|
|
176
|
+
object({ type: { const: 'comment-drafts.restore' }, family: { const: 'comment' }, draft: draftReference, composerId: id, restored: draftReference, preservedCurrent: nullableCommentReference, targetState: { enum: ['current', 'changed'] } }),
|
|
177
|
+
object({ type: { const: 'comment-drafts.discard' }, family: { const: 'comment' }, draft: draftReference, discarded: { const: true } }),
|
|
178
|
+
object({ type: { const: 'component-drafts.inspect' }, family: { const: 'component' }, coverage: { enum: ['stored-and-session', 'session-only'] }, skippedInvalid: integer,
|
|
179
|
+
token: digest, drafts: { type: 'array', maxItems: 10, items: componentMetadata }, next: cursor }, ['type', 'family', 'coverage', 'skippedInvalid', 'token', 'drafts']),
|
|
180
|
+
object({ type: { const: 'component-drafts.read' }, family: { const: 'component' }, draft: componentDraftReference, storage: { enum: ['durable', 'session'] }, format: { const: 'presmium-component-parameters-json-v1' }, offset: integer, endOffset: integer, totalUtf16: integer, totalUtf8: integer, sha256: digest, jsonText: { type: 'string', maxLength: 2048 } }),
|
|
181
|
+
object({ type: { const: 'component-drafts.restore' }, family: { const: 'component' }, draft: componentDraftReference, restored: componentMetadata, preservedCurrent: nullableComponentReference }),
|
|
182
|
+
object({ type: { const: 'component-drafts.discard' }, family: { const: 'component' }, draft: componentDraftReference, current: bool, discarded: { const: true } }),
|
|
183
|
+
] }
|
|
184
|
+
const source = DOCUMENT_PAGE_DUPLICATION_SCHEMA.properties.source as unknown as Schema
|
|
185
|
+
const binding = object({ source, irRevision: entity, planId: entity })
|
|
186
|
+
const position = object({ timeMs: number, stepTimeMs: number, sectionId: entity, stepId: entity, durationMs: number, playing: bool, rate: { type: 'number', exclusiveMinimum: 0, maximum: 16 }, trying: bool, waitingFor: entity }, ['timeMs', 'durationMs', 'playing', 'rate', 'trying'])
|
|
187
|
+
const playback = object({ binding, status: { enum: ['loading', 'ready', 'estimated', 'unavailable', 'failed'] }, position, error: { type: 'string', maxLength: 300 } }, ['binding', 'status'])
|
|
188
|
+
export const AUTHORING_CLIENT_SNAPSHOT_SCHEMA: Schema = object({
|
|
189
|
+
revision: integer, source, view: { enum: ['document', 'present'] }, editing: object({ dirty: bool, saving: bool, composing: bool, conflicted: bool, locked: bool, loading: bool }),
|
|
190
|
+
interaction: { type: 'string', maxLength: 200 }, position: object({ sectionId: entity, stepId: entity }, ['sectionId']), binding, playback,
|
|
191
|
+
preview: { oneOf: [object({ status: { enum: ['unprepared', 'preparing', 'unavailable'] } }), object({ status: { const: 'prepared' }, source, irRevision: entity, projectRevision: integer })] },
|
|
192
|
+
actions: { type: 'array', items: { enum: [...AUTHORING_CLIENT_ACTIONS] }, maxItems: AUTHORING_CLIENT_ACTIONS.length, uniqueItems: true }, unsubmittedContent: { const: 'not-inspected' },
|
|
193
|
+
}, ['revision', 'source', 'view', 'editing', 'interaction', 'actions', 'unsubmittedContent'])
|
|
194
|
+
const action = (type: string, properties: Record<string, Schema> = {}): Schema => object({ type: { const: type }, ...properties })
|
|
195
|
+
export const AUTHORING_CLIENT_ACTION_SCHEMA: Schema = { oneOf: [
|
|
196
|
+
action('state.read'), action('source.flush'), action('preview.prepare'), action('playback.pause'), action('position.step', { sectionId: entity, stepId: entity }),
|
|
197
|
+
object({ type: { const: 'comment-drafts.inspect' }, cursor, limit: { type: 'integer', minimum: 1, maximum: 10 } }, ['type']),
|
|
198
|
+
object({ type: { const: 'comment-drafts.read' }, draft: draftReference, offset: integer, maxChars: { type: 'integer', minimum: 2, maximum: 2048 } }, ['type', 'draft', 'offset']),
|
|
199
|
+
action('comment-drafts.restore', { draft: draftReference, composerId: id, expectedCurrent: nullableCommentReference }),
|
|
200
|
+
action('comment-drafts.discard', { draft: draftReference }),
|
|
201
|
+
object({ type: { const: 'component-drafts.inspect' }, cursor, limit: { type: 'integer', minimum: 1, maximum: 10 } }, ['type']),
|
|
202
|
+
object({ type: { const: 'component-drafts.read' }, draft: componentDraftReference, offset: integer, maxChars: { type: 'integer', minimum: 2, maximum: 2048 } }, ['type', 'draft', 'offset']),
|
|
203
|
+
action('component-drafts.restore', { draft: componentDraftReference, expectedCurrent: nullableComponentReference }),
|
|
204
|
+
action('component-drafts.discard', { draft: componentDraftReference, current: bool }),
|
|
205
|
+
action('playback.play', { binding }), action('playback.seek', { binding, timeMs: number }), action('playback.rate', { binding, rate: { enum: [...PRODUCTION_PLAYBACK_RATES] } }),
|
|
206
|
+
] }
|
|
207
|
+
export const AUTHORING_CLIENT_REQUEST_SCHEMA: Schema = object({ clientId: id, epoch: id, requestId: id, source, expectedClientState: object({ connectionId: id, revision: integer }), action: AUTHORING_CLIENT_ACTION_SCHEMA })
|
|
208
|
+
export const AUTHORING_CLIENT_QUERY_SCHEMA: Schema = object({ clientId: id, after: id }, [])
|
|
209
|
+
export const AUTHORING_CLIENT_REQUEST_QUERY_SCHEMA: Schema = object({ clientId: id, epoch: id, requestId: id })
|
|
210
|
+
export const AUTHORING_CLIENT_OPEN_SCHEMA: Schema = object({ clientId: id, epoch: id, snapshot: AUTHORING_CLIENT_SNAPSHOT_SCHEMA })
|
|
211
|
+
export const AUTHORING_CLIENT_REPORT_SCHEMA: Schema = object({ sequence: integer, snapshot: AUTHORING_CLIENT_SNAPSHOT_SCHEMA })
|
|
212
|
+
export const AUTHORING_CLIENT_ACK_SCHEMA: Schema = object({ sequence: integer, snapshot: AUTHORING_CLIENT_SNAPSHOT_SCHEMA, requestId: id, state: { enum: ['executing', 'done', 'refused', 'failed'] }, error: object({ code: { type: 'string', minLength: 1, maxLength: 160 } }), result: AUTHORING_CLIENT_ACTION_RESULT_SCHEMA }, ['sequence', 'snapshot', 'requestId', 'state'])
|
|
213
|
+
export const AUTHORING_CLIENT_CONNECTION_SCHEMA: Schema = object({ clientId: id, epoch: id, connectionId: id, credential: { type: 'string', minLength: 32, maxLength: 100 } })
|