The Coline App SDK: apps, tools, file types and surfaces that run inside Coline workspaces, and public websites with Coline Sites. TypeScript, with an in-memory test workspace. MIT.
| 6 | colineKairoIntegrationSchema, |
| 7 | colineAppRegistrationPayloadSchema, |
| 8 | colineFileTypeSchema, |
| 9 | createNotificationInputSchema, |
| 10 | emitAmbientEventsInputSchema, |
| 11 | type AppIndexDocument, |
| 12 | type AppIndexDocumentInput, |
| 13 | type BatchDeleteAppIndexDocuments, |
| 14 | type BatchDeleteAppIndexDocumentsInput, |
| 15 | type BatchUpsertAppIndexDocuments, |
| 16 | type BatchUpsertAppIndexDocumentsInput, |
| 17 | type ColineAppManifest, |
| 18 | type ColineAppManifestInput, |
| 19 | type ColineFileTypeDefinition, |
| 20 | type ColineFileTypeDefinitionInput, |
| 21 | type ColineKairoIntegration, |
| 22 | type ColineKairoIntegrationInput, |
| 23 | type ColineAppRegistrationPayload, |
| 24 | type EmitAmbientEventsInput, |
| 25 | type EmitAmbientEventsInputInput, |
| 26 | type CreateNotificationInput, |
| 27 | type CreateNotificationInputInput, |
| 28 | type NotificationChannelDefinition, |
| 29 | type NotificationChannelDefinitionInput, |
| 30 | notificationChannelDefinitionSchema, |
| 31 | } from "./manifest"; |
| 32 | import type { |
| 33 | FileRenderContext, |
| 34 | FileRenderResult, |
| 35 | HomeRenderContext, |
| 36 | HomeRenderResult, |
| 37 | IndexSyncContext, |
| 38 | } from "./contexts"; |
| 39 |
| 40 | export type HomeRenderHandler = ( |
| 41 | context: HomeRenderContext, |
| 42 | ) => HomeRenderResult | Promise<HomeRenderResult>; |
| 43 | export type FileRenderHandler = ( |
| 44 | context: FileRenderContext, |
| 45 | ) => FileRenderResult | Promise<FileRenderResult>; |
| 46 | export type IndexSyncHandler = (context: IndexSyncContext) => unknown | Promise<unknown>; |
| 47 |
| 48 | export interface ColineAppDefinition { |
| 49 | manifest: ColineAppManifest; |
| 50 | fileTypes: ColineFileTypeDefinition[]; |
| 51 | notificationChannels: NotificationChannelDefinition[]; |
| 52 | handlers: { |
| 53 | homeRender?: HomeRenderHandler; |
| 54 | fileRender?: FileRenderHandler; |
| 55 | indexSync?: IndexSyncHandler; |
| 56 | }; |
| 57 | } |
| 58 |
| 59 | class ColineAmbientHelper { |
| 60 | emit(input: EmitAmbientEventsInputInput): EmitAmbientEventsInput { |
| 61 | return emitAmbientEventsInputSchema.parse(input); |
| 62 | } |
| 63 | } |
| 64 |
| 65 | class ColineSearchHelper { |
| 66 | document(input: AppIndexDocumentInput): AppIndexDocument { |
| 67 | return appIndexDocumentSchema.parse(input); |
| 68 | } |
| 69 |
| 70 | upsert(input: BatchUpsertAppIndexDocumentsInput): BatchUpsertAppIndexDocuments { |
| 71 | return batchUpsertAppIndexDocumentsSchema.parse(input); |
| 72 | } |
| 73 |
| 74 | delete(input: BatchDeleteAppIndexDocumentsInput): BatchDeleteAppIndexDocuments { |
| 75 | return batchDeleteAppIndexDocumentsSchema.parse(input); |
| 76 | } |
| 77 | } |
| 78 |
| 79 | export class ColineApp { |
| 80 | readonly ambient = new ColineAmbientHelper(); |
| 81 | readonly search = new ColineSearchHelper(); |
| 82 | private readonly fileTypes = new Map<string, ColineFileTypeDefinition>(); |
| 83 | private readonly notificationChannels = new Map<string, NotificationChannelDefinition>(); |
| 84 | private readonly handlers: ColineAppDefinition["handlers"] = {}; |
| 85 | private manifestConfig: ColineAppManifest; |
| 86 |
| 87 | constructor(manifest: ColineAppManifestInput) { |
| 88 | this.manifestConfig = colineAppManifestSchema.parse(manifest); |
| 89 | } |
| 90 |
| 91 | defineFileType(fileType: ColineFileTypeDefinitionInput): this { |
| 92 | const parsed = colineFileTypeSchema.parse(fileType); |
| 93 | this.fileTypes.set(parsed.typeKey, parsed); |
| 94 | return this; |
| 95 | } |
| 96 |
| 97 | defineNotificationChannel(channel: NotificationChannelDefinitionInput): this { |
| 98 | const parsed = notificationChannelDefinitionSchema.parse(channel); |
| 99 | this.notificationChannels.set(parsed.key, parsed); |
| 100 | return this; |
| 101 | } |
| 102 |
| 103 | defineKairo(config: ColineKairoIntegrationInput): this { |
| 104 | const parsed = colineKairoIntegrationSchema.parse(config); |
| 105 | this.manifestConfig = colineAppManifestSchema.parse({ |
| 106 | ...this.manifestConfig, |
| 107 | kairo: parsed, |
| 108 | }); |
| 109 | return this; |
| 110 | } |
| 111 |
| 112 | onHomeRender(handler: HomeRenderHandler): this { |
| 113 | this.handlers.homeRender = handler; |
| 114 | return this; |
| 115 | } |
| 116 |
| 117 | onFileRender(handler: FileRenderHandler): this { |
| 118 | this.handlers.fileRender = handler; |
| 119 | return this; |
| 120 | } |
| 121 |
| 122 | onIndexSync(handler: IndexSyncHandler): this { |
| 123 | this.handlers.indexSync = handler; |
| 124 | return this; |
| 125 | } |
| 126 |
| 127 | get manifest(): ColineAppManifest { |
| 128 | return this.manifestConfig; |
| 129 | } |
| 130 |
| 131 | get kairo(): ColineKairoIntegration | undefined { |
| 132 | return this.manifestConfig.kairo; |
| 133 | } |
| 134 |
| 135 | getDefinition(): ColineAppDefinition { |
| 136 | const notificationChannels = [...this.notificationChannels.values()]; |
| 137 |
| 138 | return { |
| 139 | manifest: { |
| 140 | ...this.manifestConfig, |
| 141 | notificationChannels, |
| 142 | }, |
| 143 | fileTypes: [...this.fileTypes.values()], |
| 144 | notificationChannels, |
| 145 | handlers: { ...this.handlers }, |
| 146 | }; |
| 147 | } |
| 148 |
| 149 | getRegistrationPayload(version: string): ColineAppRegistrationPayload { |
| 150 | const notificationChannels = [...this.notificationChannels.values()]; |
| 151 |
| 152 | return colineAppRegistrationPayloadSchema.parse({ |
| 153 | version, |
| 154 | manifest: { |
| 155 | ...this.manifestConfig, |
| 156 | notificationChannels, |
| 157 | }, |
| 158 | fileTypes: [...this.fileTypes.values()], |
| 159 | }); |
| 160 | } |
| 161 |
| 162 | createNotification(input: CreateNotificationInputInput): CreateNotificationInput { |
| 163 | return createNotificationInputSchema.parse(input); |
| 164 | } |
| 165 |
| 166 | createAmbientEvents(input: EmitAmbientEventsInputInput): EmitAmbientEventsInput { |
| 167 | return this.ambient.emit(input); |
| 168 | } |
| 169 |
| 170 | /** |
| 171 | * Returns a Response for `GET /coline/manifest`. |
| 172 | * Mount this on your server so the Coline developer console can |
| 173 | * auto-import your app's manifest instead of manual JSON entry. |
| 174 | * |
| 175 | * @example |
| 176 | * ```ts |
| 177 | * // Hono |
| 178 | * app.get("/coline/manifest", (c) => c.body(myColineApp.handleManifestRequest())); |
| 179 | * |
| 180 | * // Next.js route handler |
| 181 | * export function GET() { return myColineApp.handleManifestRequest(); } |
| 182 | * |
| 183 | * // Plain fetch handler |
| 184 | * if (url.pathname === "/coline/manifest") return myColineApp.handleManifestRequest(); |
| 185 | * ``` |
| 186 | */ |
| 187 | handleManifestRequest(): Response { |
| 188 | const definition = this.getDefinition(); |
| 189 | const payload = { |
| 190 | manifest: { |
| 191 | key: definition.manifest.key, |
| 192 | name: definition.manifest.name, |
| 193 | ...(definition.manifest.description |
| 194 | ? { description: definition.manifest.description } |
| 195 | : {}), |
| 196 | ...(definition.manifest.iconUrl |
| 197 | ? { iconUrl: definition.manifest.iconUrl } |
| 198 | : {}), |
| 199 | permissions: definition.manifest.permissions, |
| 200 | notificationChannels: definition.manifest.notificationChannels, |
| 201 | ...(definition.manifest.kairo |
| 202 | ? { kairo: definition.manifest.kairo } |
| 203 | : {}), |
| 204 | hosting: definition.manifest.hosting, |
| 205 | }, |
| 206 | fileTypes: definition.fileTypes, |
| 207 | }; |
| 208 |
| 209 | return new Response(JSON.stringify(payload), { |
| 210 | status: 200, |
| 211 | headers: { |
| 212 | "Content-Type": "application/json", |
| 213 | "Access-Control-Allow-Origin": "*", |
| 214 | "Access-Control-Allow-Methods": "GET", |
| 215 | "Cache-Control": "no-store", |
| 216 | }, |
| 217 | }); |
| 218 | } |
| 219 | } |
| 220 |