返回 oh-my-ppt
schema.ts
根目录 / src / main / db / schema.ts
1 import { sqliteTable, text, integer, real, index, uniqueIndex } from 'drizzle-orm/sqlite-core'
2
3 export const sessions = sqliteTable('sessions', {
4 id: text('id').primaryKey(),
5 title: text('title').notNull(),
6 topic: text('topic'),
7 styleId: text('style_id'),
8 pageCount: integer('page_count'),
9 slideSizeId: text('slide_size_id').notNull().default('wide-16-9'),
10 slideWidth: integer('slide_width').notNull().default(1600),
11 slideHeight: integer('slide_height').notNull().default(900),
12 referenceDocumentPath: text('reference_document_path'),
13 status: text('status').notNull().default('active'),
14 provider: text('provider').notNull(),
15 model: text('model').notNull(),
16 createdAt: integer('created_at').notNull(),
17 updatedAt: integer('updated_at').notNull(),
18 metadata: text('metadata'),
19 designContract: text('design_contract'),
20 currentOperationId: text('current_operation_id'),
21 currentCommit: text('current_commit'),
22 visualEnabled: integer('visual_enabled').notNull().default(0),
23 imageModelConfigId: text('image_model_config_id').references(() => imageModelConfigs.id, {
24 onDelete: 'restrict'
25 })
26 })
27
28 export const messages = sqliteTable('messages', {
29 id: text('id').primaryKey(),
30 sessionId: text('session_id').notNull(),
31 chatScope: text('chat_scope').notNull().default('main'),
32 pageId: text('page_id'),
33 selector: text('selector'),
34 imagePaths: text('image_paths'),
35 videoPaths: text('video_paths'),
36 role: text('role').notNull(),
37 content: text('content').notNull(),
38 type: text('type'),
39 toolName: text('tool_name'),
40 toolCallId: text('tool_call_id'),
41 tokenCount: integer('token_count'),
42 runModel: text('run_model'),
43 createdAt: integer('created_at').notNull()
44 })
45
46 export const modelUsageEvents = sqliteTable(
47 'model_usage_events',
48 {
49 id: text('id').primaryKey(),
50 provider: text('provider').notNull(),
51 model: text('model').notNull(),
52 modelConfigId: text('model_config_id'),
53 inputTokens: integer('input_tokens').notNull().default(0),
54 outputTokens: integer('output_tokens').notNull().default(0),
55 totalTokens: integer('total_tokens').notNull().default(0),
56 usageSource: text('usage_source').notNull().default('provider'),
57 createdAt: integer('created_at').notNull()
58 },
59 (table) => ({
60 modelUsageCreatedIdx: index('idx_model_usage_events_created').on(table.createdAt),
61 modelUsageModelIdx: index('idx_model_usage_events_model').on(
62 table.provider,
63 table.model,
64 table.createdAt
65 )
66 })
67 )
68
69 export const projects = sqliteTable('projects', {
70 id: text('id').primaryKey(),
71 sessionId: text('session_id').notNull(),
72 title: text('title').notNull(),
73 outputPath: text('output_path').notNull(),
74 rootPath: text('root_path'),
75 fileCount: integer('file_count').default(0),
76 totalSize: integer('total_size').default(0),
77 status: text('status').notNull().default('draft'),
78 createdAt: integer('created_at').notNull(),
79 updatedAt: integer('updated_at').notNull()
80 })
81
82 export const generationRuns = sqliteTable('generation_runs', {
83 id: text('id').primaryKey(),
84 sessionId: text('session_id')
85 .notNull()
86 .references(() => sessions.id, { onDelete: 'cascade' }),
87 mode: text('mode').notNull().default('generate'),
88 status: text('status').notNull().default('running'),
89 totalPages: integer('total_pages').notNull().default(0),
90 error: text('error'),
91 metadata: text('metadata'),
92 animationPreferences: text('animation_preferences'),
93 modelConfigId: text('model_config_id'),
94 createdAt: integer('created_at').notNull(),
95 updatedAt: integer('updated_at').notNull()
96 })
97
98 export const sessionJobs = sqliteTable(
99 'session_jobs',
100 {
101 id: text('id')
102 .primaryKey()
103 .references(() => generationRuns.id, { onDelete: 'cascade' }),
104 sessionId: text('session_id')
105 .notNull()
106 .references(() => sessions.id, { onDelete: 'cascade' }),
107 kind: text('kind').notNull(),
108 previousSessionStatus: text('previous_session_status').notNull(),
109 targetPageId: text('target_page_id'),
110 targetPageNumber: integer('target_page_number'),
111 selector: text('selector'),
112 totalPages: integer('total_pages'),
113 status: text('status').notNull(),
114 abortReason: text('abort_reason'),
115 createdAt: integer('created_at').notNull(),
116 activatedAt: integer('activated_at'),
117 updatedAt: integer('updated_at').notNull(),
118 finishedAt: integer('finished_at')
119 },
120 (table) => ({
121 sessionJobsSessionStatusIdx: index('idx_session_jobs_session_status').on(
122 table.sessionId,
123 table.status,
124 table.updatedAt
125 ),
126 sessionJobsStatusIdx: index('idx_session_jobs_status').on(table.status, table.updatedAt)
127 })
128 )
129
130 export const generationPages = sqliteTable('generation_pages', {
131 id: text('id').primaryKey(),
132 runId: text('run_id')
133 .notNull()
134 .references(() => generationRuns.id, { onDelete: 'cascade' }),
135 sessionId: text('session_id')
136 .notNull()
137 .references(() => sessions.id, { onDelete: 'cascade' }),
138 pageId: text('page_id').notNull(),
139 pageNumber: integer('page_number').notNull(),
140 title: text('title').notNull(),
141 contentOutline: text('content_outline'),
142 layoutIntent: text('layout_intent'),
143 layoutId: text('layout_id'),
144 layoutContractVersion: integer('layout_contract_version'),
145 htmlPath: text('html_path'),
146 status: text('status').notNull().default('pending'),
147 error: text('error'),
148 retryCount: integer('retry_count').notNull().default(0),
149 createdAt: integer('created_at').notNull(),
150 updatedAt: integer('updated_at').notNull()
151 })
152
153 export const sessionPages = sqliteTable(
154 'session_pages',
155 {
156 id: text('id').primaryKey(),
157 sessionId: text('session_id').notNull(),
158 legacyPageId: text('legacy_page_id'),
159 fileSlug: text('file_slug').notNull(),
160 pageNumber: integer('page_number').notNull(),
161 title: text('title').notNull(),
162 htmlPath: text('html_path').notNull(),
163 layoutIntent: text('layout_intent'),
164 layoutId: text('layout_id'),
165 layoutContractVersion: integer('layout_contract_version'),
166 status: text('status').notNull().default('pending'),
167 error: text('error'),
168 createdAt: integer('created_at').notNull(),
169 updatedAt: integer('updated_at').notNull(),
170 deletedAt: integer('deleted_at')
171 },
172 (table) => ({
173 sessionPageNumberIdx: index('idx_session_pages_session_number').on(
174 table.sessionId,
175 table.pageNumber
176 )
177 })
178 )
179
180 export const sourcePageSkeletons = sqliteTable(
181 'source_page_skeletons',
182 {
183 id: text('id').primaryKey(),
184 sessionId: text('session_id')
185 .notNull()
186 .references(() => sessions.id, { onDelete: 'cascade' }),
187 pageNumber: integer('page_number').notNull(),
188 title: text('title').notNull(),
189 role: text('role').notNull().default('content'),
190 sourceDocumentPath: text('source_document_path').notNull(),
191 sourceDocumentName: text('source_document_name'),
192 sourceHeading: text('source_heading').notNull(),
193 headingLevel: integer('heading_level').notNull(),
194 lineStart: integer('line_start').notNull(),
195 lineEnd: integer('line_end').notNull(),
196 agendaItemsJson: text('agenda_items_json'),
197 reason: text('reason'),
198 confidence: text('confidence').notNull().default('high'),
199 createdAt: integer('created_at').notNull(),
200 updatedAt: integer('updated_at').notNull()
201 },
202 (table) => ({
203 sourcePageSkeletonSessionIdx: index('idx_source_page_skeletons_session').on(
204 table.sessionId,
205 table.pageNumber
206 )
207 })
208 )
209
210 export const settings = sqliteTable('settings', {
211 key: text('key').primaryKey(),
212 value: text('value').notNull(),
213 updatedAt: integer('updated_at').notNull()
214 })
215
216 export const modelConfigs = sqliteTable('model_configs', {
217 id: text('id').primaryKey(),
218 name: text('name').notNull(),
219 provider: text('provider').notNull(),
220 model: text('model').notNull(),
221 apiKey: text('api_key').notNull().default(''),
222 baseUrl: text('base_url').notNull().default(''),
223 maxTokens: integer('max_tokens').notNull().default(4096),
224 disableTemperature: integer('disable_temperature').notNull().default(0),
225 thinkingParameterMode: text('thinking_parameter_mode').notNull().default('auto'),
226 active: integer('active').notNull().default(0),
227 createdAt: integer('created_at').notNull(),
228 updatedAt: integer('updated_at').notNull()
229 })
230
231 export const imageModelConfigs = sqliteTable('image_model_configs', {
232 id: text('id').primaryKey(),
233 name: text('name').notNull(),
234 provider: text('provider').notNull(),
235 modelConfig: text('model_config').notNull().default('{}'),
236 active: integer('active').notNull().default(0),
237 createdAt: integer('created_at').notNull(),
238 updatedAt: integer('updated_at').notNull()
239 })
240
241 export const imageGenerationHistories = sqliteTable(
242 'image_generation_histories',
243 {
244 id: text('id').primaryKey(),
245 sessionId: text('session_id').notNull(),
246 pageId: text('page_id').notNull(),
247 prompt: text('prompt').notNull(),
248 imagePaths: text('image_paths').notNull().default('[]'),
249 modelConfigId: text('model_config_id').notNull(),
250 provider: text('provider').notNull(),
251 model: text('model').notNull(),
252 createdAt: integer('created_at').notNull()
253 },
254 (table) => ({
255 imageGenerationHistoriesSessionIdx: index('idx_image_generation_histories_session').on(
256 table.sessionId,
257 table.createdAt
258 ),
259 imageGenerationHistoriesPageIdx: index('idx_image_generation_histories_page').on(
260 table.sessionId,
261 table.pageId,
262 table.createdAt
263 )
264 })
265 )
266
267 export const imageFulfillmentJobs = sqliteTable(
268 'image_fulfillment_jobs',
269 {
270 id: text('id').primaryKey(),
271 runId: text('run_id')
272 .notNull()
273 .references(() => generationRuns.id, { onDelete: 'cascade' }),
274 sessionId: text('session_id')
275 .notNull()
276 .references(() => sessions.id, { onDelete: 'cascade' }),
277 sessionPageId: text('session_page_id')
278 .notNull()
279 .references(() => sessionPages.id, { onDelete: 'cascade' }),
280 pageId: text('page_id').notNull(),
281 layoutId: text('layout_id'),
282 layoutContractVersion: integer('layout_contract_version'),
283 imageModelConfigId: text('image_model_config_id').references(() => imageModelConfigs.id, {
284 onDelete: 'set null'
285 }),
286 imageProvider: text('image_provider'),
287 imageModel: text('image_model'),
288 attempt: integer('attempt').notNull(),
289 retryOfJobId: text('retry_of_job_id').references((): any => imageFulfillmentJobs.id, {
290 onDelete: 'set null'
291 }),
292 idempotencyKey: text('idempotency_key'),
293 status: text('status').notNull().default('pending'),
294 error: text('error'),
295 cancelRequestedAt: integer('cancel_requested_at'),
296 leaseOwner: text('lease_owner'),
297 leaseExpiresAt: integer('lease_expires_at'),
298 finalizationManifestPath: text('finalization_manifest_path'),
299 createdAt: integer('created_at').notNull(),
300 startedAt: integer('started_at'),
301 updatedAt: integer('updated_at').notNull(),
302 finishedAt: integer('finished_at')
303 },
304 (table) => ({
305 imageFulfillmentRunPageAttemptUnique: uniqueIndex('image_fulfillment_run_page_attempt_unique').on(
306 table.runId,
307 table.sessionPageId,
308 table.attempt
309 ),
310 imageFulfillmentIdempotencyUnique: uniqueIndex('image_fulfillment_idempotency_unique').on(
311 table.sessionId,
312 table.idempotencyKey
313 ),
314 imageFulfillmentSessionPageStatusIdx: index('idx_image_fulfillment_jobs_session_page_status').on(
315 table.sessionId,
316 table.sessionPageId,
317 table.status,
318 table.updatedAt
319 )
320 })
321 )
322
323 export const imageFulfillmentIntents = sqliteTable(
324 'image_fulfillment_intents',
325 {
326 id: text('id').primaryKey(),
327 jobId: text('job_id')
328 .notNull()
329 .references(() => imageFulfillmentJobs.id, { onDelete: 'cascade' }),
330 slotId: text('slot_id').notNull(),
331 layoutSlotId: text('layout_slot_id').notNull(),
332 role: text('role').notNull(),
333 layer: text('layer').notNull(),
334 requestVersion: integer('request_version').notNull().default(1),
335 sizeHint: text('size_hint'),
336 subject: text('subject').notNull(),
337 textZone: text('text_zone'),
338 subjectZone: text('subject_zone'),
339 negativeSpace: text('negative_space'),
340 avoidJson: text('avoid_json'),
341 requestJson: text('request_json').notNull(),
342 imageHistoryId: text('image_history_id'),
343 assetPath: text('asset_path'),
344 width: integer('width'),
345 height: integer('height'),
346 mimeType: text('mime_type'),
347 attempt: integer('attempt').notNull().default(1),
348 retryOfIntentId: text('retry_of_intent_id').references((): any => imageFulfillmentIntents.id, {
349 onDelete: 'set null'
350 }),
351 status: text('status').notNull().default('pending'),
352 error: text('error'),
353 createdAt: integer('created_at').notNull(),
354 updatedAt: integer('updated_at').notNull()
355 },
356 (table) => ({
357 imageFulfillmentIntentSlotUnique: uniqueIndex('image_fulfillment_intent_slot_unique').on(
358 table.jobId,
359 table.slotId
360 ),
361 imageFulfillmentIntentStatusIdx: index('idx_image_fulfillment_intents_job_status').on(
362 table.jobId,
363 table.status,
364 table.updatedAt
365 )
366 })
367 )
368
369 export const memorySummaries = sqliteTable('memory_summaries', {
370 id: text('id').primaryKey(),
371 sessionId: text('session_id').notNull(),
372 messageRangeStart: integer('message_range_start').notNull(),
373 messageRangeEnd: integer('message_range_end').notNull(),
374 summary: text('summary').notNull(),
375 tokenCount: integer('token_count'),
376 createdAt: integer('created_at').notNull()
377 })
378
379 export const userPreferences = sqliteTable('user_preferences', {
380 key: text('key').primaryKey(),
381 value: text('value').notNull(),
382 confidence: real('confidence').default(1.0),
383 sourceSessions: text('source_sessions'),
384 createdAt: integer('created_at').notNull(),
385 updatedAt: integer('updated_at').notNull(),
386 lastUsedAt: integer('last_used_at')
387 })
388
389 export const styles = sqliteTable('styles', {
390 id: text('id').primaryKey(),
391 style: text('style').notNull().unique(),
392 styleName: text('style_name').notNull(),
393 styleNameZh: text('style_name_zh').notNull().default(''),
394 styleNameEn: text('style_name_en').notNull().default(''),
395 description: text('description').notNull().default(''),
396 category: text('category').notNull().default(''),
397 aliases: text('aliases').notNull().default('[]'),
398 source: text('source').notNull().default('custom'),
399 styleSkill: text('style_skill').notNull().default(''),
400 version: text('version').notNull().default('1.0.0'),
401 styleCase: text('style_case').notNull().default(''),
402 imageGenerationPrompt: text('image_generation_prompt').notNull().default(''),
403 packageDir: text('package_dir').notNull().default(''),
404 active: integer('active', { mode: 'boolean' }).notNull().default(true),
405 favoriteAt: integer('favorite_at'),
406 createdAt: integer('created_at').notNull(),
407 updatedAt: integer('updated_at').notNull()
408 })
409
410 export const thumbnails = sqliteTable(
411 'thumbnails',
412 {
413 key: text('key').primaryKey(),
414 resourceType: text('resource_type').notNull(),
415 resourceId: text('resource_id').notNull(),
416 variant: text('variant').notNull().default('default'),
417 sourcePath: text('source_path').notNull(),
418 sourceMtimeMs: integer('source_mtime_ms').notNull().default(0),
419 signature: text('signature').notNull().default(''),
420 thumbnailPath: text('thumbnail_path').notNull().default(''),
421 status: text('status').notNull().default('queued'),
422 error: text('error'),
423 createdAt: integer('created_at').notNull(),
424 updatedAt: integer('updated_at').notNull()
425 },
426 (table) => ({
427 resourceVariantUniqueIdx: uniqueIndex('thumbnails_resource_variant_unique').on(
428 table.resourceType,
429 table.resourceId,
430 table.variant
431 ),
432 statusIdx: index('thumbnails_status_idx').on(table.status, table.updatedAt)
433 })
434 )
435
436 export const sessionStyleSnapshots = sqliteTable(
437 'session_style_snapshots',
438 {
439 id: text('id').primaryKey(),
440 sessionId: text('session_id')
441 .notNull()
442 .references(() => sessions.id, { onDelete: 'cascade' }),
443 styleId: text('style_id').notNull(),
444 styleKey: text('style_key').notNull(),
445 styleName: text('style_name').notNull(),
446 styleNameZh: text('style_name_zh').notNull().default(''),
447 styleNameEn: text('style_name_en').notNull().default(''),
448 description: text('description').notNull().default(''),
449 category: text('category').notNull().default(''),
450 aliases: text('aliases').notNull().default('[]'),
451 source: text('source').notNull(),
452 version: text('version').notNull().default('1.0.0'),
453 styleCase: text('style_case').notNull().default(''),
454 imageGenerationPrompt: text('image_generation_prompt').notNull().default(''),
455 packageDir: text('package_dir').notNull().default(''),
456 styleSkill: text('style_skill').notNull().default(''),
457 createdAt: integer('created_at').notNull()
458 },
459 (table) => ({
460 sessionIdUniqueIdx: uniqueIndex('session_style_snapshots_session_id_unique').on(table.sessionId)
461 })
462 )
463
464 export const sessionOperations = sqliteTable('session_operations', {
465 id: text('id').primaryKey(),
466 sessionId: text('session_id')
467 .notNull()
468 .references(() => sessions.id, { onDelete: 'cascade' }),
469 type: text('type').notNull(),
470 status: text('status').notNull().default('completed'),
471 scope: text('scope'),
472 prompt: text('prompt'),
473 parentOperationId: text('parent_operation_id'),
474 beforeCommit: text('before_commit'),
475 afterCommit: text('after_commit'),
476 targetOperationId: text('target_operation_id'),
477 targetCommit: text('target_commit'),
478 changedFilesJson: text('changed_files_json').notNull().default('[]'),
479 changedPagesJson: text('changed_pages_json').notNull().default('[]'),
480 trackedFilesJson: text('tracked_files_json').notNull().default('[]'),
481 metadataJson: text('metadata_json').notNull().default('{}'),
482 createdAt: integer('created_at').notNull(),
483 completedAt: integer('completed_at')
484 })
485
486 export const sessionOperationPages = sqliteTable(
487 'session_operation_pages',
488 {
489 id: text('id').primaryKey(),
490 operationId: text('operation_id')
491 .notNull()
492 .references(() => sessionOperations.id, { onDelete: 'cascade' }),
493 sessionId: text('session_id')
494 .notNull()
495 .references(() => sessions.id, { onDelete: 'cascade' }),
496 pageId: text('page_id').notNull(),
497 legacyPageId: text('legacy_page_id'),
498 fileSlug: text('file_slug').notNull(),
499 pageNumber: integer('page_number').notNull(),
500 title: text('title').notNull(),
501 htmlPath: text('html_path').notNull(),
502 status: text('status').notNull().default('pending'),
503 error: text('error'),
504 createdAt: integer('created_at').notNull(),
505 updatedAt: integer('updated_at').notNull()
506 },
507 (table) => ({
508 sessionOperationPagesOrderIdx: index('idx_session_operation_pages_order').on(
509 table.operationId,
510 table.pageNumber
511 ),
512 sessionOperationPagesSessionIdx: index('idx_session_operation_pages_session').on(
513 table.sessionId,
514 table.operationId
515 )
516 })
517 )
518
519 export const htmlEditDocuments = sqliteTable('html_edit_documents', {
520 id: text('id').primaryKey(),
521 title: text('title').notNull().default(''),
522 sourcePath: text('source_path'),
523 htmlPath: text('html_path').notNull(),
524 designWidth: integer('design_width').notNull().default(1280),
525 createdAt: integer('created_at').notNull(),
526 updatedAt: integer('updated_at').notNull()
527 })
528
529 export const htmlEditMessages = sqliteTable(
530 'html_edit_messages',
531 {
532 id: text('id').primaryKey(),
533 docId: text('doc_id')
534 .notNull()
535 .references(() => htmlEditDocuments.id, { onDelete: 'cascade' }),
536 role: text('role').notNull(),
537 content: text('content').notNull(),
538 intent: text('intent'),
539 planJson: text('plan_json'),
540 requiresConfirmation: integer('requires_confirmation').notNull().default(0),
541 selectedSelector: text('selected_selector'),
542 selectedLabel: text('selected_label'),
543 selectedElementTag: text('selected_element_tag'),
544 selectedElementText: text('selected_element_text'),
545 createdAt: integer('created_at').notNull()
546 },
547 (table) => ({
548 htmlEditMessagesDocIdx: index('idx_html_edit_messages_doc').on(table.docId, table.createdAt)
549 })
550 )
551
552 export const htmlEditVersions = sqliteTable(
553 'html_edit_versions',
554 {
555 id: text('id').primaryKey(),
556 docId: text('doc_id')
557 .notNull()
558 .references(() => htmlEditDocuments.id, { onDelete: 'cascade' }),
559 commitSha: text('commit_sha').notNull(),
560 message: text('message').notNull().default(''),
561 createdAt: integer('created_at').notNull()
562 },
563 (table) => ({
564 htmlEditVersionsDocIdx: index('idx_html_edit_versions_doc').on(table.docId, table.createdAt)
565 })
566 )
567
568 export type Session = typeof sessions.$inferSelect
569 export type Message = typeof messages.$inferSelect
570 export type ModelUsageEvent = typeof modelUsageEvents.$inferSelect
571 export type Project = typeof projects.$inferSelect
572 export type GenerationRun = typeof generationRuns.$inferSelect
573 export type GenerationPage = typeof generationPages.$inferSelect
574 export type SessionPage = typeof sessionPages.$inferSelect
575 export type SourcePageSkeleton = typeof sourcePageSkeletons.$inferSelect
576 export type ModelConfig = typeof modelConfigs.$inferSelect
577 export type ImageGenerationHistory = typeof imageGenerationHistories.$inferSelect
578 export type ImageFulfillmentJob = typeof imageFulfillmentJobs.$inferSelect
579 export type ImageFulfillmentIntent = typeof imageFulfillmentIntents.$inferSelect
580 export type MemorySummary = typeof memorySummaries.$inferSelect
581 export type UserPreference = typeof userPreferences.$inferSelect
582 export type SessionOperation = typeof sessionOperations.$inferSelect
583 export type SessionOperationPage = typeof sessionOperationPages.$inferSelect
584 export type HtmlEditDocument = typeof htmlEditDocuments.$inferSelect
585 export type HtmlEditMessage = typeof htmlEditMessages.$inferSelect
586 export type HtmlEditVersion = typeof htmlEditVersions.$inferSelect
587
588 export type SessionStatus = 'active' | 'completed' | 'failed' | 'archived'
589 export type MessageRole = 'user' | 'assistant' | 'system' | 'tool'
590 export type MessageType = 'text' | 'tool_call' | 'tool_result' | 'stream_chunk'
591 export type ChatScope = 'main' | 'page'
592 export type GenerationRunStatus = 'running' | 'completed' | 'failed' | 'partial'
593 export type GenerationRunMode =
594 | 'generate'
595 | 'retry'
596 | 'edit'
597 | 'import'
598 | 'addPage'
599 | 'retrySinglePage'
600 export type GenerationPageStatus = 'pending' | 'running' | 'completed' | 'failed'
601 export type SessionPageStatus = 'completed' | 'failed' | 'pending'
602 export type SessionOperationType =
603 | 'generate'
604 | 'edit'
605 | 'addPage'
606 | 'retry'
607 | 'import'
608 | 'rollback'
609 | 'reorder'
610 | 'delete'
611 export type SessionOperationScope = 'session' | 'deck' | 'page' | 'selector' | 'shell'
612 export type SessionOperationStatus = 'committing' | 'completed' | 'failed' | 'noop'
613
613 lines TYPESCRIPT