@danypops/papyrus 0.13.6 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,142 +0,0 @@
1
- import {
2
- DISCOURSE_CONTENT_MAX_BYTES,
3
- DISCOURSE_EVENT_RETENTION_DEFAULT,
4
- DISCOURSE_EVENT_RETENTION_MAX,
5
- DISCOURSE_QUERY_MAX_LIMIT,
6
- } from "../constants.ts";
7
-
8
- /** Papyrus-owned Doc subtypes reserved for the Discourse persistence adapter. */
9
- export const DISCOURSE_THREAD_SUBTYPE = "context-thread";
10
- export const DISCOURSE_MESSAGE_SUBTYPE = "context-message";
11
- export const DISCOURSE_RELATIONS = new Set(["reply_to", "discusses"]);
12
-
13
- export type JsonPrimitive = string | number | boolean | null;
14
- export type JsonValue = JsonPrimitive | JsonValue[] | { [key: string]: JsonValue };
15
- export interface ArtifactReference { kind: string; id: string }
16
- export interface ThreadAddress { forumId: string; topicId: string; threadId: string }
17
- export interface AppendPostCommand extends ThreadAddress {
18
- schemaVersion: "discourse.command.v1";
19
- operationId: string;
20
- authorId: string;
21
- content: JsonValue;
22
- correlationId?: string;
23
- causationId?: string;
24
- replyToPostId?: string;
25
- references?: ArtifactReference[];
26
- }
27
- export interface Post extends ThreadAddress {
28
- id: string;
29
- authorId: string;
30
- content: JsonValue;
31
- timestamp: number;
32
- sequence: number;
33
- operationId: string;
34
- correlationId?: string;
35
- causationId?: string;
36
- replyToPostId?: string;
37
- references: ArtifactReference[];
38
- }
39
- export type DiscourseEventType = "post-added" | "thread-changed" | "question-opened" | "question-answered" | "subscription-resync-required";
40
- export interface DiscourseEvent extends ThreadAddress {
41
- schemaVersion: "discourse.event.v1";
42
- type: DiscourseEventType;
43
- sequence: number;
44
- timestamp: number;
45
- postId?: string;
46
- operationId?: string;
47
- correlationId?: string;
48
- causationId?: string;
49
- responseId?: string;
50
- retainedFromSequence?: number;
51
- }
52
- export interface Page<T> {
53
- items: T[];
54
- truncated: boolean;
55
- nextSequence?: number;
56
- completeness: "complete" | "truncated";
57
- }
58
- export interface TopicSummary { forumId: string; topicId: string; threadCount: number; postCount: number; lastActivity: number }
59
- export interface ThreadSummary extends ThreadAddress { postCount: number; participantIds: string[]; lastActivity: number }
60
- export interface OpenQuestion { responseId: string; post: Post }
61
- export interface ProjectionRecord { sequence: number; post: Post }
62
-
63
- export function isDiscourseSubtype(subtype: string | undefined): boolean {
64
- return subtype === DISCOURSE_THREAD_SUBTYPE || subtype === DISCOURSE_MESSAGE_SUBTYPE;
65
- }
66
-
67
- function record(value: unknown, name: string): Record<string, unknown> {
68
- if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error(`${name} must be an object`);
69
- return value as Record<string, unknown>;
70
- }
71
-
72
- export function requiredString(value: unknown, name: string): string {
73
- if (typeof value !== "string" || value.length === 0) throw new Error(`${name} is required`);
74
- return value;
75
- }
76
-
77
- export function optionalString(value: unknown, name: string): string | undefined {
78
- if (value === undefined) return undefined;
79
- if (typeof value !== "string" || value.length === 0) throw new Error(`${name} must be a non-empty string`);
80
- return value;
81
- }
82
-
83
- export function nonNegativeInteger(value: unknown, name: string): number {
84
- if (!Number.isSafeInteger(value) || (value as number) < 0) throw new Error(`${name} must be a non-negative safe integer`);
85
- return value as number;
86
- }
87
-
88
- export function queryLimit(value: unknown): number {
89
- const limit = nonNegativeInteger(value, "limit");
90
- if (limit < 1 || limit > DISCOURSE_QUERY_MAX_LIMIT) throw new Error(`limit must be between 1 and ${DISCOURSE_QUERY_MAX_LIMIT}`);
91
- return limit;
92
- }
93
-
94
- export function eventRetention(value: unknown): number {
95
- if (value === undefined) return DISCOURSE_EVENT_RETENTION_DEFAULT;
96
- const retention = nonNegativeInteger(value, "event_retention");
97
- if (retention < 1 || retention > DISCOURSE_EVENT_RETENTION_MAX) {
98
- throw new Error(`event_retention must be between 1 and ${DISCOURSE_EVENT_RETENTION_MAX}`);
99
- }
100
- return retention;
101
- }
102
-
103
- function jsonValue(value: unknown, name: string): JsonValue {
104
- const encoded = JSON.stringify(value);
105
- if (encoded === undefined) throw new Error(`${name} must be JSON-serializable`);
106
- if (new TextEncoder().encode(encoded).byteLength > DISCOURSE_CONTENT_MAX_BYTES) {
107
- throw new Error(`${name} cannot exceed ${DISCOURSE_CONTENT_MAX_BYTES} bytes`);
108
- }
109
- return JSON.parse(encoded) as JsonValue;
110
- }
111
-
112
- export function appendCommand(value: unknown): AppendPostCommand {
113
- const input = record(value, "command");
114
- if (input["schemaVersion"] !== "discourse.command.v1") throw new Error("unsupported Discourse command schema");
115
- const referencesValue = input["references"] ?? [];
116
- if (!Array.isArray(referencesValue)) throw new Error("references must be an array");
117
- const references = referencesValue.map((entry, index) => {
118
- const reference = record(entry, `references[${index}]`);
119
- return { kind: requiredString(reference["kind"], `references[${index}].kind`), id: requiredString(reference["id"], `references[${index}].id`) };
120
- });
121
- return {
122
- schemaVersion: "discourse.command.v1",
123
- operationId: requiredString(input["operationId"], "operationId"),
124
- forumId: requiredString(input["forumId"], "forumId"),
125
- topicId: requiredString(input["topicId"], "topicId"),
126
- threadId: requiredString(input["threadId"], "threadId"),
127
- authorId: requiredString(input["authorId"], "authorId"),
128
- content: jsonValue(input["content"], "content"),
129
- ...(optionalString(input["correlationId"], "correlationId") ? { correlationId: input["correlationId"] as string } : {}),
130
- ...(optionalString(input["causationId"], "causationId") ? { causationId: input["causationId"] as string } : {}),
131
- ...(optionalString(input["replyToPostId"], "replyToPostId") ? { replyToPostId: input["replyToPostId"] as string } : {}),
132
- references,
133
- };
134
- }
135
-
136
- export function threadAddress(value: Record<string, unknown>): ThreadAddress {
137
- return {
138
- forumId: requiredString(value["forumId"], "forumId"),
139
- topicId: requiredString(value["topicId"], "topicId"),
140
- threadId: requiredString(value["threadId"], "threadId"),
141
- };
142
- }