@sanity/agent-directives 0.0.13 → 0.0.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_chunks-dts/index.d.ts.map +1 -1
- package/dist/_chunks-es/formatters.js +1 -1
- package/dist/_chunks-es/formatters.js.map +1 -1
- package/dist/lib/streaming.d.ts +18 -99
- package/dist/lib/streaming.d.ts.map +1 -1
- package/dist/lib/streaming.js +38 -93
- package/dist/lib/streaming.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../../src/_internal/index.ts"],"mappings":";
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../../src/_internal/index.ts"],"mappings":";cAKa,uBAAA,EAAuB,CAAA,CAAA,SAAA;;;;;;;;;;;;;cAMvB,sBAAA,EAAsB,CAAA,CAAA,SAAA;;;;;;;;;;cAKtB,uBAAA,EAAuB,CAAA,CAAA,SAAA;;;;;;;cAIvB,mCAAA,EAAmC,CAAA,CAAA,SAAA;;;;;;;cAInC,kBAAA,EAAkB,CAAA,CAAA,SAAA;;;;;;;cAIlB,sBAAA,EAAsB,CAAA,CAAA,SAAA;;;;;;;;;;cAKtB,gBAAA;EAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cASA,eAAA;EAAA;;;;;;;KAaD,aAAA,WAAwB,eAAA,eAA8B,eAAA;AAAA,KAEtD,sBAAA,GAAyB,CAAA,CAAE,KAAA,QAAa,uBAAA;AAAA,KACxC,qBAAA,GAAwB,CAAA,CAAE,KAAA,QAAa,sBAAA;AAAA,KACvC,sBAAA,GAAyB,CAAA,CAAE,KAAA,QAAa,uBAAA;AAAA,KACxC,iBAAA,GAAoB,CAAA,CAAE,KAAA,QAAa,kBAAA;AAAA,KACnC,qBAAA,GAAwB,CAAA,CAAE,KAAA,QAAa,sBAAA;AAAA,KACvC,kCAAA,GAAqC,CAAA,CAAE,KAAA,QAAa,mCAAA;AAAA,UAE/C,iBAAA;EACf,QAAA,EAAU,sBAAA;EACV,OAAA,EAAS,qBAAA;EACT,QAAA,EAAU,sBAAA;EACV,GAAA,EAAK,iBAAA;EACL,OAAA,EAAS,qBAAA;EACT,oBAAA,EAAsB,kCAAA;AAAA;AAAA,UAGP,sBAAA;;;;;EAKf,eAAA;;EAEA,mBAAA;AAAA;AAAA,iBA8Bc,eAAA,CACd,IAAA,EAAM,aAAA,EACN,KAAA,EAAO,MAAA,mBACP,KAAA;AAAA,iBASc,uBAAA,CAAwB,KAAA,EAAO,sBAAA,EAAwB,KAAA;AAAA,iBAIvD,sBAAA,CAAuB,KAAA,EAAO,qBAAA,EAAuB,KAAA;AAAA,iBAIrD,uBAAA,CAAwB,KAAA,EAAO,sBAAA;AAAA,iBAI/B,kBAAA,CAAmB,KAAA,EAAO,iBAAA;AAAA,iBAI1B,sBAAA,CAAuB,KAAA,EAAO,qBAAA;AAAA,iBAI9B,mCAAA,CACd,KAAA,EAAO,kCAAA"}
|
|
@@ -29,7 +29,7 @@ const DocumentDirectiveSchema = z.object({
|
|
|
29
29
|
set: "set",
|
|
30
30
|
changes: "changes",
|
|
31
31
|
requestProjectAccess: "requestProjectAccess"
|
|
32
|
-
}, DIRECTIVE_TYPES = ["textDirective", "leafDirective", "containerDirective"], BLOCK_DIRECTIVE =
|
|
32
|
+
}, DIRECTIVE_TYPES = ["textDirective", "leafDirective", "containerDirective"], LABEL = "(?:[^\\[\\]]|\\[[^\\[\\]]*\\])*", BLOCK_DIRECTIVE = new RegExp(`::([a-zA-Z]\\w*)(?:\\[(${LABEL})\\])?\\{([^}]*)\\}`), INLINE_DIRECTIVE = new RegExp(`:([a-zA-Z]\\w*)\\[(${LABEL})\\]\\{([^}]*)\\}`);
|
|
33
33
|
function parseDirectiveAttributes(attrString) {
|
|
34
34
|
const attrs = {};
|
|
35
35
|
for (const match of attrString.matchAll(/(\w+)="([^"]*)"/g))
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"formatters.js","sources":["../../src/_internal/index.ts"],"sourcesContent":["// This internal module contains all shared code that both the main index\n// and sub-exports can import without causing self-referencing issues with pkg-utils\n\nimport { z } from 'zod'\n\
|
|
1
|
+
{"version":3,"file":"formatters.js","sources":["../../src/_internal/index.ts"],"sourcesContent":["// This internal module contains all shared code that both the main index\n// and sub-exports can import without causing self-referencing issues with pkg-utils\n\nimport { z } from 'zod'\n\nexport const DocumentDirectiveSchema = z.object({\n id: z.string(),\n type: z.string().optional(),\n source: z.string().optional(),\n})\n\nexport const ReleaseDirectiveSchema = z.object({\n id: z.string(),\n source: z.string().optional(),\n})\n\nexport const ResourceDirectiveSchema = z.object({\n source: z.string(),\n})\n\nexport const RequestProjectAccessDirectiveSchema = z.object({\n source: z.string(),\n})\n\nexport const SetDirectiveSchema = z.object({\n id: z.string(),\n})\n\nexport const ChangesDirectiveSchema = z.object({\n createdCount: z.coerce.number().min(0),\n updatedCount: z.coerce.number().min(0),\n})\n\nexport const DirectiveSchemas = {\n document: DocumentDirectiveSchema,\n release: ReleaseDirectiveSchema,\n resource: ResourceDirectiveSchema,\n set: SetDirectiveSchema,\n changes: ChangesDirectiveSchema,\n requestProjectAccess: RequestProjectAccessDirectiveSchema,\n} as const\n\nexport const DIRECTIVE_NAMES = {\n document: 'document',\n release: 'release',\n resource: 'resource',\n set: 'set',\n changes: 'changes',\n requestProjectAccess: 'requestProjectAccess',\n} as const\n\nexport const DIRECTIVE_TYPES = ['textDirective', 'leafDirective', 'containerDirective'] as const\n\nexport type DirectiveType = (typeof DIRECTIVE_TYPES)[number]\n\nexport type DirectiveName = (typeof DIRECTIVE_NAMES)[keyof typeof DIRECTIVE_NAMES]\n\nexport type DocumentDirectiveProps = z.infer<typeof DocumentDirectiveSchema>\nexport type ReleaseDirectiveProps = z.infer<typeof ReleaseDirectiveSchema>\nexport type ResourceDirectiveProps = z.infer<typeof ResourceDirectiveSchema>\nexport type SetDirectiveProps = z.infer<typeof SetDirectiveSchema>\nexport type ChangesDirectiveProps = z.infer<typeof ChangesDirectiveSchema>\nexport type RequestProjectAccessDirectiveProps = z.infer<typeof RequestProjectAccessDirectiveSchema>\n\nexport interface DirectivePropsMap {\n document: DocumentDirectiveProps\n release: ReleaseDirectiveProps\n resource: ResourceDirectiveProps\n set: SetDirectiveProps\n changes: ChangesDirectiveProps\n requestProjectAccess: RequestProjectAccessDirectiveProps\n}\n\nexport interface DefineDirectiveOptions {\n /**\n * Whether this directive requires context/application to render.\n * If true (default), the directive won't render without context.\n */\n requiresContext?: boolean\n /** @deprecated Use `requiresContext` instead */\n requiresApplication?: boolean\n}\n\n// Label content pattern: matches non-bracket chars or single-level nested [...]\n// e.g. \"Guide [E-commerce edition]\" matches, but won't span across \"] other text [\"\nconst LABEL = '(?:[^\\\\[\\\\]]|\\\\[[^\\\\[\\\\]]*\\\\])*'\n\n// Matches block directives: ::name{attrs} or ::name[label]{attrs}\n// Captures: (1) name, (2) label or undefined, (3) attribute string\nexport const BLOCK_DIRECTIVE = new RegExp(`::([a-zA-Z]\\\\w*)(?:\\\\[(${LABEL})\\\\])?\\\\{([^}]*)\\\\}`)\n\n// Matches inline directives: :name[label]{attrs}\n// Captures: (1) name, (2) label, (3) attribute string\nexport const INLINE_DIRECTIVE = new RegExp(`:([a-zA-Z]\\\\w*)\\\\[(${LABEL})\\\\]\\\\{([^}]*)\\\\}`)\n\nexport function parseDirectiveAttributes(attrString: string): Record<string, string> {\n const attrs: Record<string, string> = {}\n for (const match of attrString.matchAll(/(\\w+)=\"([^\"]*)\"/g)) {\n attrs[match[1]] = match[2]\n }\n return attrs\n}\n\nfunction buildAttributes(params: Record<string, unknown>): string {\n return Object.entries(params)\n .filter(([, value]) => value !== undefined && value !== null)\n .map(([key, value]) => `${key}=\"${value}\"`)\n .join(' ')\n}\n\nexport function formatDirective(\n name: DirectiveName,\n props: Record<string, unknown>,\n label?: string,\n): string {\n const attrs = buildAttributes(props)\n if (label) {\n return attrs ? `:${name}[${label}]{${attrs}}` : `:${name}[${label}]`\n }\n return attrs ? `::${name}{${attrs}}` : `::${name}`\n}\n\nexport function createDocumentDirective(props: DocumentDirectiveProps, label?: string): string {\n return formatDirective(DIRECTIVE_NAMES.document, props, label)\n}\n\nexport function createReleaseDirective(props: ReleaseDirectiveProps, label?: string): string {\n return formatDirective(DIRECTIVE_NAMES.release, props, label)\n}\n\nexport function createResourceDirective(props: ResourceDirectiveProps): string {\n return formatDirective(DIRECTIVE_NAMES.resource, props)\n}\n\nexport function createSetDirective(props: SetDirectiveProps): string {\n return formatDirective(DIRECTIVE_NAMES.set, props)\n}\n\nexport function createChangesDirective(props: ChangesDirectiveProps): string {\n return formatDirective(DIRECTIVE_NAMES.changes, props)\n}\n\nexport function createRequestProjectAccessDirective(\n props: RequestProjectAccessDirectiveProps,\n): string {\n return formatDirective(DIRECTIVE_NAMES.requestProjectAccess, props)\n}\n"],"names":[],"mappings":";AAKO,MAAM,0BAA0B,EAAE,OAAO;AAAA,EAC9C,IAAI,EAAE,OAAA;AAAA,EACN,MAAM,EAAE,OAAA,EAAS,SAAA;AAAA,EACjB,QAAQ,EAAE,OAAA,EAAS,SAAA;AACrB,CAAC,GAEY,yBAAyB,EAAE,OAAO;AAAA,EAC7C,IAAI,EAAE,OAAA;AAAA,EACN,QAAQ,EAAE,OAAA,EAAS,SAAA;AACrB,CAAC,GAEY,0BAA0B,EAAE,OAAO;AAAA,EAC9C,QAAQ,EAAE,OAAA;AACZ,CAAC,GAEY,sCAAsC,EAAE,OAAO;AAAA,EAC1D,QAAQ,EAAE,OAAA;AACZ,CAAC,GAEY,qBAAqB,EAAE,OAAO;AAAA,EACzC,IAAI,EAAE,OAAA;AACR,CAAC,GAEY,yBAAyB,EAAE,OAAO;AAAA,EAC7C,cAAc,EAAE,OAAO,OAAA,EAAS,IAAI,CAAC;AAAA,EACrC,cAAc,EAAE,OAAO,OAAA,EAAS,IAAI,CAAC;AACvC,CAAC,GAEY,mBAAmB;AAAA,EAC9B,UAAU;AAAA,EACV,SAAS;AAAA,EACT,UAAU;AAAA,EACV,KAAK;AAAA,EACL,SAAS;AAAA,EACT,sBAAsB;AACxB,GAEa,kBAAkB;AAAA,EAC7B,UAAU;AAAA,EACV,SAAS;AAAA,EACT,UAAU;AAAA,EACV,KAAK;AAAA,EACL,SAAS;AAAA,EACT,sBAAsB;AACxB,GAEa,kBAAkB,CAAC,iBAAiB,iBAAiB,oBAAoB,GAkChF,QAAQ,mCAID,kBAAkB,IAAI,OAAO,0BAA0B,KAAK,qBAAqB,GAIjF,mBAAmB,IAAI,OAAO,sBAAsB,KAAK,mBAAmB;AAElF,SAAS,yBAAyB,YAA4C;AACnF,QAAM,QAAgC,CAAA;AACtC,aAAW,SAAS,WAAW,SAAS,kBAAkB;AACxD,UAAM,MAAM,CAAC,CAAC,IAAI,MAAM,CAAC;AAE3B,SAAO;AACT;AAEA,SAAS,gBAAgB,QAAyC;AAChE,SAAO,OAAO,QAAQ,MAAM,EACzB,OAAO,CAAC,CAAA,EAAG,KAAK,MAA6B,SAAU,IAAI,EAC3D,IAAI,CAAC,CAAC,KAAK,KAAK,MAAM,GAAG,GAAG,KAAK,KAAK,GAAG,EACzC,KAAK,GAAG;AACb;AAEO,SAAS,gBACd,MACA,OACA,OACQ;AACR,QAAM,QAAQ,gBAAgB,KAAK;AACnC,SAAI,QACK,QAAQ,IAAI,IAAI,IAAI,KAAK,KAAK,KAAK,MAAM,IAAI,IAAI,IAAI,KAAK,MAE5D,QAAQ,KAAK,IAAI,IAAI,KAAK,MAAM,KAAK,IAAI;AAClD;AAEO,SAAS,wBAAwB,OAA+B,OAAwB;AAC7F,SAAO,gBAAgB,gBAAgB,UAAU,OAAO,KAAK;AAC/D;AAEO,SAAS,uBAAuB,OAA8B,OAAwB;AAC3F,SAAO,gBAAgB,gBAAgB,SAAS,OAAO,KAAK;AAC9D;AAEO,SAAS,wBAAwB,OAAuC;AAC7E,SAAO,gBAAgB,gBAAgB,UAAU,KAAK;AACxD;AAEO,SAAS,mBAAmB,OAAkC;AACnE,SAAO,gBAAgB,gBAAgB,KAAK,KAAK;AACnD;AAEO,SAAS,uBAAuB,OAAsC;AAC3E,SAAO,gBAAgB,gBAAgB,SAAS,KAAK;AACvD;AAEO,SAAS,oCACd,OACQ;AACR,SAAO,gBAAgB,gBAAgB,sBAAsB,KAAK;AACpE;"}
|
package/dist/lib/streaming.d.ts
CHANGED
|
@@ -1,52 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Streaming utilities for handling directive syntax during real-time text streaming.
|
|
3
|
-
*
|
|
4
|
-
* When streaming text that contains directives (e.g., `::document{id="123"}`),
|
|
5
|
-
* we need to buffer potential directive content and strip complete directives
|
|
6
|
-
* from the streamed output, since they'll be rendered as blocks/components
|
|
7
|
-
* at the end of the stream.
|
|
8
|
-
*
|
|
9
|
-
* @example
|
|
10
|
-
* ```ts
|
|
11
|
-
* import { createDirectiveStreamBuffer } from '@sanity/agent-directives/streaming'
|
|
12
|
-
*
|
|
13
|
-
* const buffer = createDirectiveStreamBuffer()
|
|
14
|
-
*
|
|
15
|
-
* for await (const chunk of stream) {
|
|
16
|
-
* const textToStream = buffer.process(chunk)
|
|
17
|
-
* if (textToStream) {
|
|
18
|
-
* await sendToClient(textToStream)
|
|
19
|
-
* }
|
|
20
|
-
* }
|
|
21
|
-
*
|
|
22
|
-
* // Flush any remaining content when stream ends
|
|
23
|
-
* const remaining = buffer.flush()
|
|
24
|
-
* if (remaining) {
|
|
25
|
-
* await sendToClient(remaining)
|
|
26
|
-
* }
|
|
27
|
-
* ```
|
|
28
|
-
*/
|
|
29
|
-
interface DirectiveStreamBuffer {
|
|
30
|
-
/**
|
|
31
|
-
* Process incoming text chunk and return text safe to stream.
|
|
32
|
-
* Directives are buffered and stripped from output.
|
|
33
|
-
*
|
|
34
|
-
* @param chunk - Incoming text chunk
|
|
35
|
-
* @returns Text safe to stream (directives removed)
|
|
36
|
-
*/
|
|
37
|
-
process(chunk: string): string;
|
|
38
|
-
/**
|
|
39
|
-
* Flush any remaining buffered content.
|
|
40
|
-
* Call this when streaming is complete.
|
|
41
|
-
*
|
|
42
|
-
* @returns Any remaining text (with complete directives stripped)
|
|
43
|
-
*/
|
|
44
|
-
flush(): string;
|
|
45
|
-
/**
|
|
46
|
-
* Reset the buffer to initial state.
|
|
47
|
-
*/
|
|
48
|
-
reset(): void;
|
|
49
|
-
}
|
|
50
1
|
/**
|
|
51
2
|
* Parsed directive information passed to the processor callback.
|
|
52
3
|
*/
|
|
@@ -61,77 +12,45 @@ interface ParsedDirective {
|
|
|
61
12
|
*/
|
|
62
13
|
type DirectiveProcessor = (directive: ParsedDirective) => Promise<string>;
|
|
63
14
|
/**
|
|
64
|
-
* A streaming processor that
|
|
65
|
-
*
|
|
15
|
+
* A streaming processor that incrementally parses directive syntax from text
|
|
16
|
+
* chunks and either strips or transforms them via a callback.
|
|
66
17
|
*/
|
|
67
18
|
interface DirectiveStreamProcessor {
|
|
68
|
-
/**
|
|
69
|
-
* Process incoming text chunk and return text with directives transformed.
|
|
70
|
-
* Complete directives are passed to the processor callback.
|
|
71
|
-
*
|
|
72
|
-
* @param chunk - Incoming text chunk
|
|
73
|
-
* @returns Text with complete directives replaced by processor output
|
|
74
|
-
*/
|
|
19
|
+
/** Process a text chunk. Complete directives are passed to the callback. */
|
|
75
20
|
process(chunk: string): Promise<string>;
|
|
76
|
-
/**
|
|
77
|
-
* Flush any remaining buffered content.
|
|
78
|
-
* Call this when streaming is complete.
|
|
79
|
-
*
|
|
80
|
-
* @returns Any remaining text with directives processed
|
|
81
|
-
*/
|
|
21
|
+
/** Flush remaining buffered content. Call when the stream ends. */
|
|
82
22
|
flush(): Promise<string>;
|
|
83
|
-
/**
|
|
84
|
-
* Reset the processor to initial state.
|
|
85
|
-
*/
|
|
23
|
+
/** Reset to initial state. */
|
|
86
24
|
reset(): void;
|
|
87
25
|
}
|
|
26
|
+
/** @deprecated Use {@link DirectiveStreamProcessor} */
|
|
27
|
+
type DirectiveStreamBuffer = DirectiveStreamProcessor;
|
|
88
28
|
/**
|
|
89
|
-
* Creates a buffer
|
|
90
|
-
*
|
|
91
|
-
* Directives have the pattern: `::name{prop="value" prop2="value2"}`
|
|
92
|
-
*
|
|
93
|
-
* During streaming, the buffer:
|
|
94
|
-
* 1. Streams non-directive text immediately
|
|
95
|
-
* 2. Buffers potential directive content until it can determine if it's complete
|
|
96
|
-
* 3. Strips complete directives from output (they'll be rendered as blocks at the end)
|
|
97
|
-
*
|
|
98
|
-
* This approach is similar to the React implementation which filters incomplete
|
|
99
|
-
* directives using pattern matching, but adapted for incremental streaming where
|
|
100
|
-
* we can't "unsend" text that was already streamed.
|
|
29
|
+
* Creates a streaming buffer that strips directive syntax from output.
|
|
30
|
+
* Thin wrapper around {@link createDirectiveStreamProcessor} with a no-op callback.
|
|
101
31
|
*/
|
|
102
|
-
declare function createDirectiveStreamBuffer():
|
|
32
|
+
declare function createDirectiveStreamBuffer(): DirectiveStreamProcessor;
|
|
103
33
|
/**
|
|
104
34
|
* Creates a streaming processor that transforms directives using a callback.
|
|
105
35
|
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
36
|
+
* During streaming, the processor:
|
|
37
|
+
* 1. Streams non-directive text immediately
|
|
38
|
+
* 2. Buffers potential directive content until it can determine if it's complete
|
|
39
|
+
* 3. Passes complete directives to the callback and emits the return value
|
|
110
40
|
*
|
|
111
41
|
* @example
|
|
112
42
|
* ```ts
|
|
113
|
-
* import { createDirectiveStreamProcessor } from '@sanity/agent-directives/streaming'
|
|
114
|
-
*
|
|
115
|
-
* // Slack client example
|
|
116
43
|
* const processor = createDirectiveStreamProcessor(async ({ name, attributes }) => {
|
|
117
|
-
* if (name === 'document') {
|
|
118
|
-
*
|
|
119
|
-
* return `[${doc.title}](${doc.url})`
|
|
120
|
-
* }
|
|
121
|
-
* return `[${name}]`
|
|
44
|
+
* if (name === 'document') return `[${attributes.id}](https://example.com/${attributes.id})`
|
|
45
|
+
* return ''
|
|
122
46
|
* })
|
|
123
47
|
*
|
|
124
48
|
* for await (const chunk of llmStream) {
|
|
125
49
|
* const text = await processor.process(chunk)
|
|
126
|
-
* if (text)
|
|
127
|
-
* await streamToClient(text)
|
|
128
|
-
* }
|
|
50
|
+
* if (text) await streamToClient(text)
|
|
129
51
|
* }
|
|
130
|
-
*
|
|
131
52
|
* const remaining = await processor.flush()
|
|
132
|
-
* if (remaining)
|
|
133
|
-
* await streamToClient(remaining)
|
|
134
|
-
* }
|
|
53
|
+
* if (remaining) await streamToClient(remaining)
|
|
135
54
|
* ```
|
|
136
55
|
*/
|
|
137
56
|
declare function createDirectiveStreamProcessor(processDirective: DirectiveProcessor): DirectiveStreamProcessor;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"streaming.d.ts","names":[],"sources":["../../src/lib/streaming.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"streaming.d.ts","names":[],"sources":["../../src/lib/streaming.ts"],"mappings":"AASA;;;AAAA,UAAiB,eAAA;EACf,IAAA;EACA,UAAA,EAAY,MAAA;EACZ,QAAA;AAAA;;;AAOF;;KAAY,kBAAA,IAAsB,SAAA,EAAW,eAAA,KAAoB,OAAA;;;;;UAMhD,wBAAA;EANuD;EAQtE,OAAA,CAAQ,KAAA,WAAgB,OAAA;EAFe;EAIvC,KAAA,IAAS,OAAA;EAAO;EAEhB,KAAA;AAAA;;KAIU,qBAAA,GAAwB,wBAAA;;;;;iBAMpB,2BAAA,CAAA,GAA+B,wBAAA;;;;;AAA/C;;;;;AA2BA;;;;;;;;;;;;;;iBAAgB,8BAAA,CACd,gBAAA,EAAkB,kBAAA,GACjB,wBAAA"}
|
package/dist/lib/streaming.js
CHANGED
|
@@ -1,91 +1,7 @@
|
|
|
1
1
|
import { INLINE_DIRECTIVE, BLOCK_DIRECTIVE, parseDirectiveAttributes } from "../_chunks-es/formatters.js";
|
|
2
|
+
const ANCHORED_BLOCK = new RegExp(`^${BLOCK_DIRECTIVE.source}`), ANCHORED_INLINE = new RegExp(`^${INLINE_DIRECTIVE.source}`);
|
|
2
3
|
function createDirectiveStreamBuffer() {
|
|
3
|
-
|
|
4
|
-
function process(chunk) {
|
|
5
|
-
return buffer += chunk, extractStreamableText();
|
|
6
|
-
}
|
|
7
|
-
function flush() {
|
|
8
|
-
const remaining = buffer;
|
|
9
|
-
return buffer = "", stripCompleteDirectives(remaining);
|
|
10
|
-
}
|
|
11
|
-
function reset() {
|
|
12
|
-
buffer = "";
|
|
13
|
-
}
|
|
14
|
-
function extractStreamableText() {
|
|
15
|
-
let output = "";
|
|
16
|
-
for (; buffer.length > 0; ) {
|
|
17
|
-
const colonIndex = buffer.indexOf(":");
|
|
18
|
-
if (colonIndex === -1) {
|
|
19
|
-
output += buffer, buffer = "";
|
|
20
|
-
break;
|
|
21
|
-
}
|
|
22
|
-
if (colonIndex > 0 && (output += buffer.slice(0, colonIndex), buffer = buffer.slice(colonIndex)), buffer === ":")
|
|
23
|
-
break;
|
|
24
|
-
if (buffer.startsWith("::")) {
|
|
25
|
-
if (!looksLikeBlockDirectiveStart(buffer)) {
|
|
26
|
-
output += "::", buffer = buffer.slice(2);
|
|
27
|
-
continue;
|
|
28
|
-
}
|
|
29
|
-
const blockMatch = buffer.match(BLOCK_DIRECTIVE);
|
|
30
|
-
if (blockMatch) {
|
|
31
|
-
buffer = buffer.slice(blockMatch[0].length);
|
|
32
|
-
continue;
|
|
33
|
-
}
|
|
34
|
-
if (isDefinitelyNotBlockDirective(buffer)) {
|
|
35
|
-
output += buffer.slice(0, 2), buffer = buffer.slice(2);
|
|
36
|
-
continue;
|
|
37
|
-
}
|
|
38
|
-
break;
|
|
39
|
-
}
|
|
40
|
-
if (looksLikeInlineDirectiveStart(buffer)) {
|
|
41
|
-
const inlineMatch = buffer.match(INLINE_DIRECTIVE);
|
|
42
|
-
if (inlineMatch) {
|
|
43
|
-
buffer = buffer.slice(inlineMatch[0].length);
|
|
44
|
-
continue;
|
|
45
|
-
}
|
|
46
|
-
if (isDefinitelyNotInlineDirective(buffer)) {
|
|
47
|
-
output += ":", buffer = buffer.slice(1);
|
|
48
|
-
continue;
|
|
49
|
-
}
|
|
50
|
-
break;
|
|
51
|
-
}
|
|
52
|
-
output += ":", buffer = buffer.slice(1);
|
|
53
|
-
}
|
|
54
|
-
return output;
|
|
55
|
-
}
|
|
56
|
-
function stripCompleteDirectives(text) {
|
|
57
|
-
return text.replace(new RegExp(BLOCK_DIRECTIVE.source, "g"), "").replace(new RegExp(INLINE_DIRECTIVE.source, "g"), "");
|
|
58
|
-
}
|
|
59
|
-
return {
|
|
60
|
-
process,
|
|
61
|
-
flush,
|
|
62
|
-
reset
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
function looksLikeBlockDirectiveStart(text) {
|
|
66
|
-
return text.startsWith("::") ? text.length === 2 ? !0 : /[a-zA-Z[{]/.test(text[2]) : !1;
|
|
67
|
-
}
|
|
68
|
-
function looksLikeInlineDirectiveStart(text) {
|
|
69
|
-
return text.length === 1 ? !0 : /[a-zA-Z]/.test(text[1]);
|
|
70
|
-
}
|
|
71
|
-
function isDefinitelyNotBlockDirective(text) {
|
|
72
|
-
const nameMatch = text.match(/^::([a-zA-Z]\w*)/);
|
|
73
|
-
return nameMatch ? isDefinitelyNotDirectiveAfterName(text.slice(nameMatch[0].length), !1) : text.length > 2 && !/[a-zA-Z]/.test(text[2]);
|
|
74
|
-
}
|
|
75
|
-
function isDefinitelyNotInlineDirective(text) {
|
|
76
|
-
const nameMatch = text.match(/^:([a-zA-Z]\w*)/);
|
|
77
|
-
return nameMatch ? isDefinitelyNotDirectiveAfterName(text.slice(nameMatch[0].length), !0) : text.length > 1 && !/[a-zA-Z]/.test(text[1]);
|
|
78
|
-
}
|
|
79
|
-
function isDefinitelyNotDirectiveAfterName(afterName, requireBrackets) {
|
|
80
|
-
if (afterName.length === 0) return !1;
|
|
81
|
-
if (afterName[0] === "{") return requireBrackets;
|
|
82
|
-
if (afterName[0] === "[") {
|
|
83
|
-
const bracketEnd = afterName.indexOf("]");
|
|
84
|
-
if (bracketEnd === -1) return !1;
|
|
85
|
-
const afterBracket = afterName.slice(bracketEnd + 1);
|
|
86
|
-
return afterBracket.length === 0 ? !1 : afterBracket[0] !== "{";
|
|
87
|
-
}
|
|
88
|
-
return !0;
|
|
4
|
+
return createDirectiveStreamProcessor(async () => "");
|
|
89
5
|
}
|
|
90
6
|
function createDirectiveStreamProcessor(processDirective) {
|
|
91
7
|
let buffer = "";
|
|
@@ -104,7 +20,7 @@ function createDirectiveStreamProcessor(processDirective) {
|
|
|
104
20
|
output += "::", buffer = buffer.slice(2);
|
|
105
21
|
continue;
|
|
106
22
|
}
|
|
107
|
-
const blockMatch = buffer.match(
|
|
23
|
+
const blockMatch = buffer.match(ANCHORED_BLOCK);
|
|
108
24
|
if (blockMatch) {
|
|
109
25
|
const [fullMatch, name, children, attrString] = blockMatch, attributes = parseDirectiveAttributes(attrString);
|
|
110
26
|
output += await processDirective({ name, attributes, children: children ?? "" }), buffer = buffer.slice(fullMatch.length);
|
|
@@ -117,7 +33,7 @@ function createDirectiveStreamProcessor(processDirective) {
|
|
|
117
33
|
break;
|
|
118
34
|
}
|
|
119
35
|
if (looksLikeInlineDirectiveStart(buffer)) {
|
|
120
|
-
const inlineMatch = buffer.match(
|
|
36
|
+
const inlineMatch = buffer.match(ANCHORED_INLINE);
|
|
121
37
|
if (inlineMatch) {
|
|
122
38
|
const [fullMatch, name, children, attrString] = inlineMatch, attributes = parseDirectiveAttributes(attrString);
|
|
123
39
|
output += await processDirective({ name, attributes, children }), buffer = buffer.slice(fullMatch.length);
|
|
@@ -166,11 +82,40 @@ function createDirectiveStreamProcessor(processDirective) {
|
|
|
166
82
|
function reset() {
|
|
167
83
|
buffer = "";
|
|
168
84
|
}
|
|
169
|
-
return {
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
85
|
+
return { process, flush, reset };
|
|
86
|
+
}
|
|
87
|
+
function looksLikeBlockDirectiveStart(text) {
|
|
88
|
+
return text.startsWith("::") ? text.length === 2 ? !0 : /[a-zA-Z[{]/.test(text[2]) : !1;
|
|
89
|
+
}
|
|
90
|
+
function looksLikeInlineDirectiveStart(text) {
|
|
91
|
+
return text.length === 1 ? !0 : /[a-zA-Z]/.test(text[1]);
|
|
92
|
+
}
|
|
93
|
+
function isDefinitelyNotBlockDirective(text) {
|
|
94
|
+
const nameMatch = text.match(/^::([a-zA-Z]\w*)/);
|
|
95
|
+
return nameMatch ? isDefinitelyNotDirectiveAfterName(text.slice(nameMatch[0].length), !1) : text.length > 2 && !/[a-zA-Z]/.test(text[2]);
|
|
96
|
+
}
|
|
97
|
+
function isDefinitelyNotInlineDirective(text) {
|
|
98
|
+
const nameMatch = text.match(/^:([a-zA-Z]\w*)/);
|
|
99
|
+
return nameMatch ? isDefinitelyNotDirectiveAfterName(text.slice(nameMatch[0].length), !0) : text.length > 1 && !/[a-zA-Z]/.test(text[1]);
|
|
100
|
+
}
|
|
101
|
+
function findMatchingBracket(text) {
|
|
102
|
+
let depth = 0;
|
|
103
|
+
for (let i = 0; i < text.length; i++)
|
|
104
|
+
if (text[i] === "[") depth++;
|
|
105
|
+
else if (text[i] === "]" && (depth--, depth === 0))
|
|
106
|
+
return i;
|
|
107
|
+
return -1;
|
|
108
|
+
}
|
|
109
|
+
function isDefinitelyNotDirectiveAfterName(afterName, requireBrackets) {
|
|
110
|
+
if (afterName.length === 0) return !1;
|
|
111
|
+
if (afterName[0] === "{") return requireBrackets;
|
|
112
|
+
if (afterName[0] === "[") {
|
|
113
|
+
const bracketEnd = findMatchingBracket(afterName);
|
|
114
|
+
if (bracketEnd === -1) return !1;
|
|
115
|
+
const afterBracket = afterName.slice(bracketEnd + 1);
|
|
116
|
+
return afterBracket.length === 0 ? !1 : afterBracket[0] !== "{";
|
|
117
|
+
}
|
|
118
|
+
return !0;
|
|
174
119
|
}
|
|
175
120
|
export {
|
|
176
121
|
createDirectiveStreamBuffer,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"streaming.js","sources":["../../src/lib/streaming.ts"],"sourcesContent":["import { BLOCK_DIRECTIVE, INLINE_DIRECTIVE, parseDirectiveAttributes } from '../_internal'\n\n/**\n * Streaming utilities for handling directive syntax during real-time text streaming.\n *\n * When streaming text that contains directives (e.g., `::document{id=\"123\"}`),\n * we need to buffer potential directive content and strip complete directives\n * from the streamed output, since they'll be rendered as blocks/components\n * at the end of the stream.\n *\n * @example\n * ```ts\n * import { createDirectiveStreamBuffer } from '@sanity/agent-directives/streaming'\n *\n * const buffer = createDirectiveStreamBuffer()\n *\n * for await (const chunk of stream) {\n * const textToStream = buffer.process(chunk)\n * if (textToStream) {\n * await sendToClient(textToStream)\n * }\n * }\n *\n * // Flush any remaining content when stream ends\n * const remaining = buffer.flush()\n * if (remaining) {\n * await sendToClient(remaining)\n * }\n * ```\n */\n\nexport interface DirectiveStreamBuffer {\n /**\n * Process incoming text chunk and return text safe to stream.\n * Directives are buffered and stripped from output.\n *\n * @param chunk - Incoming text chunk\n * @returns Text safe to stream (directives removed)\n */\n process(chunk: string): string\n\n /**\n * Flush any remaining buffered content.\n * Call this when streaming is complete.\n *\n * @returns Any remaining text (with complete directives stripped)\n */\n flush(): string\n\n /**\n * Reset the buffer to initial state.\n */\n reset(): void\n}\n\n/**\n * Parsed directive information passed to the processor callback.\n */\nexport interface ParsedDirective {\n name: string\n attributes: Record<string, string>\n children: string\n}\n\n/**\n * Callback function for processing a complete directive.\n * Returns the text to insert in place of the directive syntax.\n */\nexport type DirectiveProcessor = (directive: ParsedDirective) => Promise<string>\n\n/**\n * A streaming processor that transforms directives using a callback.\n * Unlike DirectiveStreamBuffer which strips directives, this processes them.\n */\nexport interface DirectiveStreamProcessor {\n /**\n * Process incoming text chunk and return text with directives transformed.\n * Complete directives are passed to the processor callback.\n *\n * @param chunk - Incoming text chunk\n * @returns Text with complete directives replaced by processor output\n */\n process(chunk: string): Promise<string>\n\n /**\n * Flush any remaining buffered content.\n * Call this when streaming is complete.\n *\n * @returns Any remaining text with directives processed\n */\n flush(): Promise<string>\n\n /**\n * Reset the processor to initial state.\n */\n reset(): void\n}\n\n/**\n * Creates a buffer for handling directive syntax during streaming.\n *\n * Directives have the pattern: `::name{prop=\"value\" prop2=\"value2\"}`\n *\n * During streaming, the buffer:\n * 1. Streams non-directive text immediately\n * 2. Buffers potential directive content until it can determine if it's complete\n * 3. Strips complete directives from output (they'll be rendered as blocks at the end)\n *\n * This approach is similar to the React implementation which filters incomplete\n * directives using pattern matching, but adapted for incremental streaming where\n * we can't \"unsend\" text that was already streamed.\n */\nexport function createDirectiveStreamBuffer(): DirectiveStreamBuffer {\n let buffer = ''\n\n function process(chunk: string): string {\n buffer += chunk\n return extractStreamableText()\n }\n\n function flush(): string {\n const remaining = buffer\n buffer = ''\n // Strip any complete directives from remaining text\n return stripCompleteDirectives(remaining)\n }\n\n function reset(): void {\n buffer = ''\n }\n\n function extractStreamableText(): string {\n let output = ''\n\n while (buffer.length > 0) {\n // Find potential directive start (colon that might become ::)\n const colonIndex = buffer.indexOf(':')\n\n if (colonIndex === -1) {\n // No colon found - safe to output everything\n output += buffer\n buffer = ''\n break\n }\n\n // Output text before the colon\n if (colonIndex > 0) {\n output += buffer.slice(0, colonIndex)\n buffer = buffer.slice(colonIndex)\n }\n\n // Now buffer starts with ':'\n // If buffer is just a single ':', keep it buffered - might become '::' with next chunk\n if (buffer === ':') {\n break\n }\n\n // Block directive (::name{...})\n if (buffer.startsWith('::')) {\n if (!looksLikeBlockDirectiveStart(buffer)) {\n output += '::'\n buffer = buffer.slice(2)\n continue\n }\n const blockMatch = buffer.match(BLOCK_DIRECTIVE)\n if (blockMatch) {\n buffer = buffer.slice(blockMatch[0].length)\n continue\n }\n if (isDefinitelyNotBlockDirective(buffer)) {\n output += buffer.slice(0, 2)\n buffer = buffer.slice(2)\n continue\n }\n break\n }\n\n // Inline directive (:name[content]{...})\n if (looksLikeInlineDirectiveStart(buffer)) {\n const inlineMatch = buffer.match(INLINE_DIRECTIVE)\n if (inlineMatch) {\n buffer = buffer.slice(inlineMatch[0].length)\n continue\n }\n if (isDefinitelyNotInlineDirective(buffer)) {\n output += ':'\n buffer = buffer.slice(1)\n continue\n }\n break\n }\n\n // Not a directive\n output += ':'\n buffer = buffer.slice(1)\n }\n\n return output\n }\n\n function stripCompleteDirectives(text: string): string {\n return text\n .replace(new RegExp(BLOCK_DIRECTIVE.source, 'g'), '')\n .replace(new RegExp(INLINE_DIRECTIVE.source, 'g'), '')\n }\n\n return {\n process,\n flush,\n reset,\n }\n}\n\n// ============================================================================\n// Streaming detection helpers\n// ============================================================================\n\nfunction looksLikeBlockDirectiveStart(text: string): boolean {\n if (!text.startsWith('::')) return false\n if (text.length === 2) return true\n return /[a-zA-Z[{]/.test(text[2])\n}\n\nfunction looksLikeInlineDirectiveStart(text: string): boolean {\n if (text.length === 1) return true\n return /[a-zA-Z]/.test(text[1])\n}\n\nfunction isDefinitelyNotBlockDirective(text: string): boolean {\n const nameMatch = text.match(/^::([a-zA-Z]\\w*)/)\n if (!nameMatch) return text.length > 2 && !/[a-zA-Z]/.test(text[2])\n return isDefinitelyNotDirectiveAfterName(text.slice(nameMatch[0].length), false)\n}\n\nfunction isDefinitelyNotInlineDirective(text: string): boolean {\n const nameMatch = text.match(/^:([a-zA-Z]\\w*)/)\n if (!nameMatch) return text.length > 1 && !/[a-zA-Z]/.test(text[1])\n return isDefinitelyNotDirectiveAfterName(text.slice(nameMatch[0].length), true)\n}\n\n/**\n * Checks whether text after a directive name can still form a valid directive.\n * Block directives accept `{` (attrs) or `[` (label). Inline requires `[`.\n */\nfunction isDefinitelyNotDirectiveAfterName(afterName: string, requireBrackets: boolean): boolean {\n if (afterName.length === 0) return false\n if (afterName[0] === '{') return requireBrackets\n if (afterName[0] === '[') {\n const bracketEnd = afterName.indexOf(']')\n if (bracketEnd === -1) return false\n const afterBracket = afterName.slice(bracketEnd + 1)\n if (afterBracket.length === 0) return false\n return afterBracket[0] !== '{'\n }\n return true\n}\n\n/**\n * Creates a streaming processor that transforms directives using a callback.\n *\n * Unlike `createDirectiveStreamBuffer` which strips directives from output,\n * this processor passes complete directives to a callback and replaces them\n * with the callback's return value. This enables clients to render directives\n * as links, cards, or other formats during streaming.\n *\n * @example\n * ```ts\n * import { createDirectiveStreamProcessor } from '@sanity/agent-directives/streaming'\n *\n * // Slack client example\n * const processor = createDirectiveStreamProcessor(async ({ name, attributes }) => {\n * if (name === 'document') {\n * const doc = await fetchDocument(attributes.id)\n * return `[${doc.title}](${doc.url})`\n * }\n * return `[${name}]`\n * })\n *\n * for await (const chunk of llmStream) {\n * const text = await processor.process(chunk)\n * if (text) {\n * await streamToClient(text)\n * }\n * }\n *\n * const remaining = await processor.flush()\n * if (remaining) {\n * await streamToClient(remaining)\n * }\n * ```\n */\nexport function createDirectiveStreamProcessor(\n processDirective: DirectiveProcessor,\n): DirectiveStreamProcessor {\n let buffer = ''\n\n async function process(chunk: string): Promise<string> {\n buffer += chunk\n let output = ''\n\n while (buffer.length > 0) {\n const colonIndex = buffer.indexOf(':')\n\n if (colonIndex === -1) {\n output += buffer\n buffer = ''\n break\n }\n\n if (colonIndex > 0) {\n output += buffer.slice(0, colonIndex)\n buffer = buffer.slice(colonIndex)\n }\n\n if (buffer === ':') break\n\n // Block directive (::name{...})\n if (buffer.startsWith('::')) {\n if (!looksLikeBlockDirectiveStart(buffer)) {\n output += '::'\n buffer = buffer.slice(2)\n continue\n }\n const blockMatch = buffer.match(BLOCK_DIRECTIVE)\n if (blockMatch) {\n const [fullMatch, name, children, attrString] = blockMatch\n const attributes = parseDirectiveAttributes(attrString)\n output += await processDirective({ name, attributes, children: children ?? '' })\n buffer = buffer.slice(fullMatch.length)\n continue\n }\n if (isDefinitelyNotBlockDirective(buffer)) {\n output += buffer.slice(0, 2)\n buffer = buffer.slice(2)\n continue\n }\n break\n }\n\n // Inline directive (:name[content]{...})\n if (looksLikeInlineDirectiveStart(buffer)) {\n const inlineMatch = buffer.match(INLINE_DIRECTIVE)\n if (inlineMatch) {\n const [fullMatch, name, children, attrString] = inlineMatch\n const attributes = parseDirectiveAttributes(attrString)\n output += await processDirective({ name, attributes, children })\n buffer = buffer.slice(fullMatch.length)\n continue\n }\n if (isDefinitelyNotInlineDirective(buffer)) {\n output += ':'\n buffer = buffer.slice(1)\n continue\n }\n break\n }\n\n // Not a directive\n output += ':'\n buffer = buffer.slice(1)\n }\n\n return output\n }\n\n async function flush(): Promise<string> {\n const remaining = buffer\n buffer = ''\n let output = ''\n let text = remaining\n\n while (text.length > 0) {\n const inlineMatch = text.match(INLINE_DIRECTIVE)\n const blockMatch = text.match(BLOCK_DIRECTIVE)\n\n const inlineIdx = inlineMatch?.index ?? Infinity\n const blockIdx = blockMatch?.index ?? Infinity\n\n if (inlineIdx === Infinity && blockIdx === Infinity) {\n output += text\n break\n }\n\n if (inlineIdx <= blockIdx && inlineMatch) {\n output += text.slice(0, inlineMatch.index)\n const attributes = parseDirectiveAttributes(inlineMatch[3])\n output += await processDirective({\n name: inlineMatch[1],\n attributes,\n children: inlineMatch[2],\n })\n text = text.slice(inlineMatch.index! + inlineMatch[0].length)\n } else if (blockMatch) {\n output += text.slice(0, blockMatch.index)\n const attributes = parseDirectiveAttributes(blockMatch[3])\n output += await processDirective({\n name: blockMatch[1],\n attributes,\n children: blockMatch[2] ?? '',\n })\n text = text.slice(blockMatch.index! + blockMatch[0].length)\n }\n }\n\n return output\n }\n\n function reset(): void {\n buffer = ''\n }\n\n return {\n process,\n flush,\n reset,\n }\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"streaming.js","sources":["../../src/lib/streaming.ts"],"sourcesContent":["import { BLOCK_DIRECTIVE, INLINE_DIRECTIVE, parseDirectiveAttributes } from '../_internal'\n\n// Anchored variants for streaming — only match at the start of the buffer\nconst ANCHORED_BLOCK = new RegExp(`^${BLOCK_DIRECTIVE.source}`)\nconst ANCHORED_INLINE = new RegExp(`^${INLINE_DIRECTIVE.source}`)\n\n/**\n * Parsed directive information passed to the processor callback.\n */\nexport interface ParsedDirective {\n name: string\n attributes: Record<string, string>\n children: string\n}\n\n/**\n * Callback function for processing a complete directive.\n * Returns the text to insert in place of the directive syntax.\n */\nexport type DirectiveProcessor = (directive: ParsedDirective) => Promise<string>\n\n/**\n * A streaming processor that incrementally parses directive syntax from text\n * chunks and either strips or transforms them via a callback.\n */\nexport interface DirectiveStreamProcessor {\n /** Process a text chunk. Complete directives are passed to the callback. */\n process(chunk: string): Promise<string>\n /** Flush remaining buffered content. Call when the stream ends. */\n flush(): Promise<string>\n /** Reset to initial state. */\n reset(): void\n}\n\n/** @deprecated Use {@link DirectiveStreamProcessor} */\nexport type DirectiveStreamBuffer = DirectiveStreamProcessor\n\n/**\n * Creates a streaming buffer that strips directive syntax from output.\n * Thin wrapper around {@link createDirectiveStreamProcessor} with a no-op callback.\n */\nexport function createDirectiveStreamBuffer(): DirectiveStreamProcessor {\n return createDirectiveStreamProcessor(async () => '')\n}\n\n/**\n * Creates a streaming processor that transforms directives using a callback.\n *\n * During streaming, the processor:\n * 1. Streams non-directive text immediately\n * 2. Buffers potential directive content until it can determine if it's complete\n * 3. Passes complete directives to the callback and emits the return value\n *\n * @example\n * ```ts\n * const processor = createDirectiveStreamProcessor(async ({ name, attributes }) => {\n * if (name === 'document') return `[${attributes.id}](https://example.com/${attributes.id})`\n * return ''\n * })\n *\n * for await (const chunk of llmStream) {\n * const text = await processor.process(chunk)\n * if (text) await streamToClient(text)\n * }\n * const remaining = await processor.flush()\n * if (remaining) await streamToClient(remaining)\n * ```\n */\nexport function createDirectiveStreamProcessor(\n processDirective: DirectiveProcessor,\n): DirectiveStreamProcessor {\n let buffer = ''\n\n async function process(chunk: string): Promise<string> {\n buffer += chunk\n let output = ''\n\n while (buffer.length > 0) {\n const colonIndex = buffer.indexOf(':')\n\n if (colonIndex === -1) {\n output += buffer\n buffer = ''\n break\n }\n\n if (colonIndex > 0) {\n output += buffer.slice(0, colonIndex)\n buffer = buffer.slice(colonIndex)\n }\n\n if (buffer === ':') break\n\n // Block directive (::name{...} or ::name[label]{...})\n if (buffer.startsWith('::')) {\n if (!looksLikeBlockDirectiveStart(buffer)) {\n output += '::'\n buffer = buffer.slice(2)\n continue\n }\n const blockMatch = buffer.match(ANCHORED_BLOCK)\n if (blockMatch) {\n const [fullMatch, name, children, attrString] = blockMatch\n const attributes = parseDirectiveAttributes(attrString)\n output += await processDirective({ name, attributes, children: children ?? '' })\n buffer = buffer.slice(fullMatch.length)\n continue\n }\n if (isDefinitelyNotBlockDirective(buffer)) {\n output += buffer.slice(0, 2)\n buffer = buffer.slice(2)\n continue\n }\n break\n }\n\n // Inline directive (:name[content]{...})\n if (looksLikeInlineDirectiveStart(buffer)) {\n const inlineMatch = buffer.match(ANCHORED_INLINE)\n if (inlineMatch) {\n const [fullMatch, name, children, attrString] = inlineMatch\n const attributes = parseDirectiveAttributes(attrString)\n output += await processDirective({ name, attributes, children })\n buffer = buffer.slice(fullMatch.length)\n continue\n }\n if (isDefinitelyNotInlineDirective(buffer)) {\n output += ':'\n buffer = buffer.slice(1)\n continue\n }\n break\n }\n\n // Not a directive\n output += ':'\n buffer = buffer.slice(1)\n }\n\n return output\n }\n\n async function flush(): Promise<string> {\n const remaining = buffer\n buffer = ''\n let output = ''\n let text = remaining\n\n while (text.length > 0) {\n const inlineMatch = text.match(INLINE_DIRECTIVE)\n const blockMatch = text.match(BLOCK_DIRECTIVE)\n\n const inlineIdx = inlineMatch?.index ?? Infinity\n const blockIdx = blockMatch?.index ?? Infinity\n\n if (inlineIdx === Infinity && blockIdx === Infinity) {\n output += text\n break\n }\n\n if (inlineIdx <= blockIdx && inlineMatch) {\n output += text.slice(0, inlineMatch.index)\n const attributes = parseDirectiveAttributes(inlineMatch[3])\n output += await processDirective({\n name: inlineMatch[1],\n attributes,\n children: inlineMatch[2],\n })\n text = text.slice(inlineMatch.index! + inlineMatch[0].length)\n } else if (blockMatch) {\n output += text.slice(0, blockMatch.index)\n const attributes = parseDirectiveAttributes(blockMatch[3])\n output += await processDirective({\n name: blockMatch[1],\n attributes,\n children: blockMatch[2] ?? '',\n })\n text = text.slice(blockMatch.index! + blockMatch[0].length)\n }\n }\n\n return output\n }\n\n function reset(): void {\n buffer = ''\n }\n\n return { process, flush, reset }\n}\n\nfunction looksLikeBlockDirectiveStart(text: string): boolean {\n if (!text.startsWith('::')) return false\n if (text.length === 2) return true\n return /[a-zA-Z[{]/.test(text[2])\n}\n\nfunction looksLikeInlineDirectiveStart(text: string): boolean {\n if (text.length === 1) return true\n return /[a-zA-Z]/.test(text[1])\n}\n\nfunction isDefinitelyNotBlockDirective(text: string): boolean {\n const nameMatch = text.match(/^::([a-zA-Z]\\w*)/)\n if (!nameMatch) return text.length > 2 && !/[a-zA-Z]/.test(text[2])\n return isDefinitelyNotDirectiveAfterName(text.slice(nameMatch[0].length), false)\n}\n\nfunction isDefinitelyNotInlineDirective(text: string): boolean {\n const nameMatch = text.match(/^:([a-zA-Z]\\w*)/)\n if (!nameMatch) return text.length > 1 && !/[a-zA-Z]/.test(text[1])\n return isDefinitelyNotDirectiveAfterName(text.slice(nameMatch[0].length), true)\n}\n\n/**\n * Finds the index of the closing `]` that matches the opening `[` at position 0,\n * accounting for nested bracket pairs.\n */\nfunction findMatchingBracket(text: string): number {\n let depth = 0\n for (let i = 0; i < text.length; i++) {\n if (text[i] === '[') depth++\n else if (text[i] === ']') {\n depth--\n if (depth === 0) return i\n }\n }\n return -1\n}\n\n/**\n * Checks whether text after a directive name can still form a valid directive.\n * Block directives accept `{` (attrs) or `[` (label). Inline requires `[`.\n */\nfunction isDefinitelyNotDirectiveAfterName(afterName: string, requireBrackets: boolean): boolean {\n if (afterName.length === 0) return false\n if (afterName[0] === '{') return requireBrackets\n if (afterName[0] === '[') {\n const bracketEnd = findMatchingBracket(afterName)\n if (bracketEnd === -1) return false\n const afterBracket = afterName.slice(bracketEnd + 1)\n if (afterBracket.length === 0) return false\n return afterBracket[0] !== '{'\n }\n return true\n}\n"],"names":[],"mappings":"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"}
|