@jchat/addon-sdk 1.0.0-alpha.11 → 1.0.0-alpha.12
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/CHANGELOG.md +17 -0
- package/README.md +339 -61
- package/dist/cli.d.mts +6 -7
- package/dist/cli.mjs +3 -2
- package/dist/contracts.d.mts +5 -6
- package/dist/contracts.mjs +38 -0
- package/dist/diagnostics.d.mts +8 -9
- package/dist/diagnostics.mjs +1 -1
- package/dist/i18n-validator.d.mts +17 -0
- package/dist/i18n-validator.mjs +90 -0
- package/dist/i18n.mjs +191 -0
- package/dist/icon.d.mts +4 -0
- package/dist/icon.mjs +4 -0
- package/dist/index.d.mts +16 -510
- package/dist/index.mjs +6 -338
- package/dist/index2.d.mts +478 -0
- package/dist/state.d.mts +1 -0
- package/dist/state.mjs +2 -0
- package/dist/types.d.mts +12 -0
- package/dist/vite.d.mts +7 -8
- package/dist/vite.mjs +51 -11
- package/package.json +93 -16
- package/skills/create-jchat-addon/SKILL.md +140 -0
- package/skills/jchat-addon-development/SKILL.md +260 -0
|
@@ -0,0 +1,478 @@
|
|
|
1
|
+
import { i as IconPack, n as IconComponent, r as IconName } from "./types.mjs";
|
|
2
|
+
import { AddonManifest, AddonPermissionDefinition, MOUNT_POINTS, MountPoint } from "./contracts.mjs";
|
|
3
|
+
import { InfiniteData, MutationFunction, QueryFunction, QueryFunctionContext, QueryKey, UndefinedInitialDataInfiniteOptions, UndefinedInitialDataOptions, UseInfiniteQueryResult, UseMutationOptions, UseMutationResult, UseQueryResult, useQueryClient } from "@tanstack/react-query";
|
|
4
|
+
import { Elysia, t } from "elysia";
|
|
5
|
+
import { ColumnType, Generated, Insertable, JSONColumnType, Kysely, Selectable, Updateable } from "kysely";
|
|
6
|
+
//#region src/i18n.d.ts
|
|
7
|
+
export declare const HOST_LANGUAGES: readonly ["ar", "bg", "hr", "de", "el", "en", "es", "fr", "he", "hi", "kn", "ml", "nl", "pt", "ro", "ru", "ta", "te", "tr", "ur"];
|
|
8
|
+
export type HostLanguage = (typeof HOST_LANGUAGES)[number];
|
|
9
|
+
export type AddonTranslationDictionary = Record<string, unknown>;
|
|
10
|
+
export type AddonTranslationResources = Record<string, AddonTranslationDictionary>;
|
|
11
|
+
export type AddonLocaleLoader = (locale: string) => Promise<AddonTranslationDictionary> | AddonTranslationDictionary;
|
|
12
|
+
export interface LocaleContextValue {
|
|
13
|
+
locale: string;
|
|
14
|
+
dir: "ltr" | "rtl";
|
|
15
|
+
}
|
|
16
|
+
export declare const LocaleContext: import("react").Context<LocaleContextValue>;
|
|
17
|
+
export interface TranslatorOptions {
|
|
18
|
+
resources?: AddonTranslationResources;
|
|
19
|
+
defaultLocale?: string;
|
|
20
|
+
fallbackLocale?: string;
|
|
21
|
+
}
|
|
22
|
+
export type TranslateFunction<TKeys extends string = string> = (key: TKeys | (string & {}), paramsOrLang?: Record<string, unknown> | string, maybeLang?: string) => string;
|
|
23
|
+
/**
|
|
24
|
+
* Creates a standalone translation function for backend server routes (`src/api.ts`) or utilities.
|
|
25
|
+
*
|
|
26
|
+
* @param resources - Dictionary of translation resources (e.g., `{ en: enJson, ta: taJson }`).
|
|
27
|
+
* @param defaultLocale - Default locale to use if not specified per call (defaults to "en").
|
|
28
|
+
* @param fallbackLocale - Fallback locale if a key is missing in the target language (defaults to
|
|
29
|
+
* "en").
|
|
30
|
+
*/
|
|
31
|
+
export declare function createTranslator<TResources extends AddonTranslationResources = AddonTranslationResources>(resources?: TResources, defaultLocale?: string, fallbackLocale?: string): TranslateFunction<TResources extends {
|
|
32
|
+
en: infer TEn;
|
|
33
|
+
} ? TEn extends Record<string, unknown> ? Extract<keyof TEn, string> : string : string>;
|
|
34
|
+
/**
|
|
35
|
+
* Hook to access the current active host locale and text direction in an addon component.
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* ;```tsx
|
|
39
|
+
* import { useLocale } from "@jchat/addon-sdk"
|
|
40
|
+
*
|
|
41
|
+
* export default function MyAddon() {
|
|
42
|
+
* const { locale, dir, isRtl } = useLocale()
|
|
43
|
+
* return <div dir={dir}>Current language: {locale}</div>
|
|
44
|
+
* }
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
export declare function useLocale(): {
|
|
48
|
+
locale: string;
|
|
49
|
+
dir: "ltr" | "rtl";
|
|
50
|
+
isRtl: boolean;
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* Hook to access translation function and locale metadata inside addon components. Automatically
|
|
54
|
+
* synchronizes with host language switches and falls back to English. Supports static dictionary
|
|
55
|
+
* resources or dynamic async locale loaders.
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* ;```tsx
|
|
59
|
+
* import { useTranslation } from "@jchat/addon-sdk"
|
|
60
|
+
* import locales from "./locales"
|
|
61
|
+
*
|
|
62
|
+
* export default function MyAddon() {
|
|
63
|
+
* const { t, locale, dir } = useTranslation(locales)
|
|
64
|
+
* return (
|
|
65
|
+
* <div dir={dir}>
|
|
66
|
+
* <h1>{t("welcome", { name: "Guest" })}</h1>
|
|
67
|
+
* </div>
|
|
68
|
+
* )
|
|
69
|
+
* }
|
|
70
|
+
* ```
|
|
71
|
+
*
|
|
72
|
+
* @param resourcesOrLoader - Addon-scoped translation dictionaries or async chunk loader.
|
|
73
|
+
* @param fallbackLocale - Fallback language code when a string is missing (default: "en").
|
|
74
|
+
*/
|
|
75
|
+
export declare function useTranslation<TResources extends AddonTranslationResources | AddonLocaleLoader = AddonTranslationResources>(resourcesOrLoader?: TResources, fallbackLocale?: string): {
|
|
76
|
+
t: TranslateFunction<TResources extends {
|
|
77
|
+
en: infer TEn;
|
|
78
|
+
} ? TEn extends Record<string, unknown> ? Extract<keyof TEn, string> : string : string>;
|
|
79
|
+
locale: string;
|
|
80
|
+
dir: "ltr" | "rtl";
|
|
81
|
+
isRtl: boolean;
|
|
82
|
+
isReady: boolean;
|
|
83
|
+
};
|
|
84
|
+
//#endregion
|
|
85
|
+
//#region src/query.d.ts
|
|
86
|
+
export interface UseAddonQueryOptions<TData = unknown, TError = Error> extends Omit<UndefinedInitialDataOptions<TData, TError, TData, QueryKey>, "queryKey" | "queryFn"> {
|
|
87
|
+
/** The API path to fetch, automatically prefixed with `/api/v1/addons/<addonId>` */
|
|
88
|
+
path?: string;
|
|
89
|
+
/** Custom query key elements appended to the automatic path cache key */
|
|
90
|
+
queryKey?: QueryKey;
|
|
91
|
+
/** Custom query function override */
|
|
92
|
+
queryFn?: QueryFunction<TData, QueryKey>;
|
|
93
|
+
}
|
|
94
|
+
export interface UseAddonMutationOptions<TData = unknown, TVariables = unknown, TError = Error, TContext = unknown> extends Omit<UseMutationOptions<TData, TError, TVariables, TContext>, "mutationFn"> {
|
|
95
|
+
/** The target API path, or a dynamic function of mutation variables */
|
|
96
|
+
path?: string | ((variables: TVariables) => string);
|
|
97
|
+
/** HTTP method to use (defaults to 'POST') */
|
|
98
|
+
method?: "POST" | "PUT" | "DELETE";
|
|
99
|
+
/** Query keys to automatically invalidate upon successful mutation */
|
|
100
|
+
invalidateQueries?: QueryKey[];
|
|
101
|
+
/** Custom mutation function override */
|
|
102
|
+
mutationFn?: MutationFunction<TData, TVariables>;
|
|
103
|
+
}
|
|
104
|
+
export interface UseAddonInfiniteQueryOptions<TData = unknown, TError = Error, TPageParam = unknown> extends Omit<UndefinedInitialDataInfiniteOptions<TData, TError, InfiniteData<TData, TPageParam>, QueryKey, TPageParam>, "queryKey" | "queryFn"> {
|
|
105
|
+
/** The API path to fetch, or a dynamic function that receives pageParam and returns the path */
|
|
106
|
+
path?: string | ((pageParam: TPageParam) => string);
|
|
107
|
+
/** Custom query key elements appended to the automatic cache key */
|
|
108
|
+
queryKey?: QueryKey;
|
|
109
|
+
/** Custom query function override */
|
|
110
|
+
queryFn?: (context: QueryFunctionContext<QueryKey, TPageParam>) => Promise<TData>;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Perform a declarative, cached GET request using the host's shared React Query instance.
|
|
114
|
+
* Automatically injects authentication headers and prefixes addon paths.
|
|
115
|
+
*
|
|
116
|
+
* @example
|
|
117
|
+
* ;```tsx
|
|
118
|
+
* const { data, isLoading } = useAddonQuery<{ polls: Poll[] }>({
|
|
119
|
+
* path: "/polls",
|
|
120
|
+
* queryKey: [siteId]
|
|
121
|
+
* })
|
|
122
|
+
* ```
|
|
123
|
+
*/
|
|
124
|
+
export declare function useAddonQuery<TData = unknown, TError = Error>(options: UseAddonQueryOptions<TData, TError>): UseQueryResult<TData, TError>;
|
|
125
|
+
/**
|
|
126
|
+
* Perform a paginated or infinite scrolling request using the host's shared React Query instance.
|
|
127
|
+
*
|
|
128
|
+
* @example
|
|
129
|
+
* ;```tsx
|
|
130
|
+
* const { data, fetchNextPage, hasNextPage } = useAddonInfiniteQuery<GifResponse, Error, number>({
|
|
131
|
+
* path: (page) => `/trending?offset=${page * 20}`,
|
|
132
|
+
* initialPageParam: 0,
|
|
133
|
+
* getNextPageParam: (lastPage, allPages) => (lastPage.hasMore ? allPages.length : undefined)
|
|
134
|
+
* })
|
|
135
|
+
* ```
|
|
136
|
+
*/
|
|
137
|
+
export declare function useAddonInfiniteQuery<TData = unknown, TError = Error, TPageParam = unknown>(options: UseAddonInfiniteQueryOptions<TData, TError, TPageParam>): UseInfiniteQueryResult<InfiniteData<TData, TPageParam>, TError>;
|
|
138
|
+
/**
|
|
139
|
+
* Perform a declarative mutation using the host's shared React Query instance. Automatically
|
|
140
|
+
* executes the request and can invalidate specified query keys upon success.
|
|
141
|
+
*
|
|
142
|
+
* @example
|
|
143
|
+
* ;```tsx
|
|
144
|
+
* const { mutate, isPending } = useAddonMutation<Poll, CreatePollInput>({
|
|
145
|
+
* path: "/polls",
|
|
146
|
+
* method: "POST",
|
|
147
|
+
* invalidateQueries: [["/polls"]],
|
|
148
|
+
* onSuccess: () => sdk.toast("Poll created!", "success")
|
|
149
|
+
* })
|
|
150
|
+
* ```
|
|
151
|
+
*/
|
|
152
|
+
export declare function useAddonMutation<TData = unknown, TVariables = unknown, TError = Error, TContext = unknown>(options: UseAddonMutationOptions<TData, TVariables, TError, TContext>): UseMutationResult<TData, TError, TVariables, TContext>;
|
|
153
|
+
//#endregion
|
|
154
|
+
//#region src/index.d.ts
|
|
155
|
+
export type AddonJsonBody = Record<string, unknown> | unknown[] | null;
|
|
156
|
+
/**
|
|
157
|
+
* @jchat/addon-sdk
|
|
158
|
+
*
|
|
159
|
+
* Lightweight SDK for Jchat addon developers. Provides TypeScript types for addon props, manifest
|
|
160
|
+
* schema, and host API access. This package is always EXTERNALISED from addon bundles — the host
|
|
161
|
+
* app provides the runtime implementation.
|
|
162
|
+
*/
|
|
163
|
+
/** Minimal user profile accessible to addon backends. */
|
|
164
|
+
export interface AddonUser {
|
|
165
|
+
/** Unique user identifier */
|
|
166
|
+
id: string;
|
|
167
|
+
/** Display name */
|
|
168
|
+
name: string;
|
|
169
|
+
/** Full display name */
|
|
170
|
+
displayName: string;
|
|
171
|
+
/** Full URL to the user's avatar image */
|
|
172
|
+
avatar: string;
|
|
173
|
+
/** User's gender identifier */
|
|
174
|
+
gender: string;
|
|
175
|
+
}
|
|
176
|
+
export interface AddonRequestUser {
|
|
177
|
+
id: string;
|
|
178
|
+
permissions?: string[];
|
|
179
|
+
siteId?: string;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Typed, schema-scoped database facade for an addon. The host deliberately omits raw execution,
|
|
183
|
+
* schema switching, dialect/connection access, and lifecycle methods.
|
|
184
|
+
*/
|
|
185
|
+
export type AddonDatabase<TDatabase = Record<string, Record<string, unknown>>> = Pick<Kysely<TDatabase>, "deleteFrom" | "fn" | "insertInto" | "selectFrom" | "transaction" | "updateTable">;
|
|
186
|
+
/**
|
|
187
|
+
* Scoped caching and key-value client provided to addon server instances. Keys are automatically
|
|
188
|
+
* namespaced by the host as `addon:<addonId>:<key>`.
|
|
189
|
+
*/
|
|
190
|
+
export interface AddonCacheClient {
|
|
191
|
+
/** Retrieve a string or JSON-parsed value by key */
|
|
192
|
+
get: <T = string>(key: string) => Promise<T | null>;
|
|
193
|
+
/** Store a value with an optional time-to-live in seconds */
|
|
194
|
+
set: (key: string, value: unknown, ttlSeconds?: number) => Promise<void>;
|
|
195
|
+
/** Delete one or more keys */
|
|
196
|
+
del: (...keys: string[]) => Promise<number>;
|
|
197
|
+
/** Atomically increment a numeric counter and return the new value */
|
|
198
|
+
incr: (key: string) => Promise<number>;
|
|
199
|
+
/** Set a timeout on a key in seconds */
|
|
200
|
+
expire: (key: string, ttlSeconds: number) => Promise<boolean>;
|
|
201
|
+
/** Get the remaining time-to-live of a key in seconds (-2 if key does not exist, -1 if no TTL) */
|
|
202
|
+
ttl: (key: string) => Promise<number>;
|
|
203
|
+
}
|
|
204
|
+
export interface AddonServerEmitTarget {
|
|
205
|
+
/** Target a specific site (defaults to the request user's siteId) */
|
|
206
|
+
siteId?: string;
|
|
207
|
+
/** Target a specific chat room */
|
|
208
|
+
roomId?: string;
|
|
209
|
+
/** Target a specific user */
|
|
210
|
+
userId?: string;
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Context provided to an addon's server-side factory. Parameterize it with the addon's table
|
|
214
|
+
* interface: `AddonServerContext<MyAddonDatabase>`.
|
|
215
|
+
*/
|
|
216
|
+
export interface AddonServerContext<TDatabase = Record<string, Record<string, unknown>>> {
|
|
217
|
+
/**
|
|
218
|
+
* Elysia / TypeBox schema validator for defining route validation schemas (`body`, `query`,
|
|
219
|
+
* `params`, `headers`). Provided directly by the host runtime so your addon server bundle does
|
|
220
|
+
* not need to bundle Elysia.
|
|
221
|
+
*/
|
|
222
|
+
t: typeof t;
|
|
223
|
+
/**
|
|
224
|
+
* Retrieve the encrypted settings for this addon for a specific site. Internal secrets are
|
|
225
|
+
* automatically decrypted using the host's master key.
|
|
226
|
+
*/
|
|
227
|
+
getConfig: (siteId: string) => Promise<Record<string, unknown>>;
|
|
228
|
+
/** Fetch a specific user's public profile */
|
|
229
|
+
getUser: (userId: string) => Promise<AddonUser | null>;
|
|
230
|
+
/** Efficiently fetch multiple user profiles in a single query. Returns a Map keyed by user ID. */
|
|
231
|
+
getUsers: (userIds: string[]) => Promise<Map<string, AddonUser>>;
|
|
232
|
+
/**
|
|
233
|
+
* Check whether the request user holds a given permission. Provides autocomplete for core Jchat
|
|
234
|
+
* permissions while also accepting any custom addon permission string.
|
|
235
|
+
*
|
|
236
|
+
* @param user - The user object from the request context
|
|
237
|
+
* @param permission - The permission to check, e.g., 'message.send' or 'addon.stories.view'
|
|
238
|
+
*/
|
|
239
|
+
hasPermission: (user: AddonRequestUser | null | undefined, permission: string) => boolean;
|
|
240
|
+
/**
|
|
241
|
+
* A Kysely database instance scoped entirely to your addon's tables. You cannot access core Jchat
|
|
242
|
+
* tables (Users, Rooms, etc.) through this instance.
|
|
243
|
+
*/
|
|
244
|
+
db: AddonDatabase<TDatabase>;
|
|
245
|
+
/** A secure, namespaced key-value cache client scoped to your addon (`addon:<addonId>:<key>`). */
|
|
246
|
+
cache: AddonCacheClient;
|
|
247
|
+
/**
|
|
248
|
+
* Broadcast a real-time event to connected clients across the site or room. Clients listen via
|
|
249
|
+
* `sdk.events.on('addon:<addonId>:<event>', callback)` or `sdk.events.on(event, callback)`.
|
|
250
|
+
*/
|
|
251
|
+
emit: (event: string, payload: unknown, target?: AddonServerEmitTarget) => Promise<void>;
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Elysia app instance provided to addon server endpoints with Jchat macro metadata (e.g.
|
|
255
|
+
* requirePermission) typed.
|
|
256
|
+
*/
|
|
257
|
+
export type AddonApp = Elysia<"", {
|
|
258
|
+
decorator: {};
|
|
259
|
+
store: {};
|
|
260
|
+
derive: {};
|
|
261
|
+
resolve: {
|
|
262
|
+
user: AddonRequestUser;
|
|
263
|
+
};
|
|
264
|
+
}, {
|
|
265
|
+
typebox: {};
|
|
266
|
+
error: {};
|
|
267
|
+
}, {
|
|
268
|
+
schema: {};
|
|
269
|
+
standaloneSchema: {};
|
|
270
|
+
macro: {
|
|
271
|
+
requirePermission?: string | string[];
|
|
272
|
+
};
|
|
273
|
+
macroFn: {};
|
|
274
|
+
parser: {};
|
|
275
|
+
response: {};
|
|
276
|
+
}>;
|
|
277
|
+
/**
|
|
278
|
+
* The entry point for your addon's server bundle. You should default-export a function matching
|
|
279
|
+
* this signature.
|
|
280
|
+
*
|
|
281
|
+
* @example
|
|
282
|
+
* ;```ts
|
|
283
|
+
* import { type AddonServerFactory } from '@jchat/addon-sdk'
|
|
284
|
+
*
|
|
285
|
+
* const factory: AddonServerFactory = ({ db, hasPermission }, app) => {
|
|
286
|
+
* return app.get('/trending', async () => { ... })
|
|
287
|
+
* }
|
|
288
|
+
* export default factory
|
|
289
|
+
* ```
|
|
290
|
+
*/
|
|
291
|
+
export type AddonServerFactory<TDatabase = Record<string, Record<string, unknown>>> = (ctx: AddonServerContext<TDatabase>, app: AddonApp) => Elysia;
|
|
292
|
+
/** Props passed to your addon's root React component by the Jchat host. */
|
|
293
|
+
export interface AddonProps {
|
|
294
|
+
/** The mount point where this addon instance is rendered (e.g., 'chat-sidebar') */
|
|
295
|
+
mountPoint: MountPoint;
|
|
296
|
+
/** Information about the currently logged-in user (if any) */
|
|
297
|
+
user?: {
|
|
298
|
+
id: string;
|
|
299
|
+
name: string;
|
|
300
|
+
displayName?: string;
|
|
301
|
+
avatar?: string;
|
|
302
|
+
gender?: string;
|
|
303
|
+
};
|
|
304
|
+
/** The ID of the room the user is currently viewing (if applicable) */
|
|
305
|
+
roomId?: string;
|
|
306
|
+
/** The ID of the active Jchat site */
|
|
307
|
+
siteId?: string;
|
|
308
|
+
/** The active user locale (e.g. 'en', 'ta', 'ar') */
|
|
309
|
+
locale?: string;
|
|
310
|
+
/** Text direction for the current locale */
|
|
311
|
+
dir?: "ltr" | "rtl";
|
|
312
|
+
}
|
|
313
|
+
export interface AddonApi {
|
|
314
|
+
get: <TResponse = unknown>(path: string) => Promise<TResponse>;
|
|
315
|
+
post: <TResponse = unknown>(path: string, body?: AddonJsonBody) => Promise<TResponse>;
|
|
316
|
+
put: <TResponse = unknown>(path: string, body?: AddonJsonBody) => Promise<TResponse>;
|
|
317
|
+
delete: <TResponse = unknown>(path: string) => Promise<TResponse>;
|
|
318
|
+
}
|
|
319
|
+
/** First-class addon settings management interface provided by the host. */
|
|
320
|
+
export interface AddonSettingsApi {
|
|
321
|
+
/** Fetch the current site's saved configuration for this addon */
|
|
322
|
+
get: <T = Record<string, unknown>>() => Promise<T | null>;
|
|
323
|
+
/** Save updated configuration for this addon for the current site */
|
|
324
|
+
save: <T = Record<string, unknown>>(settings: T) => Promise<void>;
|
|
325
|
+
}
|
|
326
|
+
export type AddonEventListener = (data: unknown) => void;
|
|
327
|
+
/** A font option provided by the JChat host. */
|
|
328
|
+
export interface AddonFont {
|
|
329
|
+
/** Display label shown to users (e.g. 'Modern', 'Elegant') */
|
|
330
|
+
label: string;
|
|
331
|
+
/** Font identifier stored in the database (e.g. 'poppins', 'lato') */
|
|
332
|
+
value: string;
|
|
333
|
+
/** CSS font-family string ready for use in inline styles */
|
|
334
|
+
family: string;
|
|
335
|
+
/** Google Fonts name for loading the font via a <link> tag */
|
|
336
|
+
googleName: string;
|
|
337
|
+
/** Relative size multiplier — apply this to base font sizes to match the host's rendering */
|
|
338
|
+
scale: number;
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* The bridge between your addon and the Jchat host application. Use the `useAddonSDK()` hook to
|
|
342
|
+
* access this interface in your components.
|
|
343
|
+
*/
|
|
344
|
+
export interface AddonSDK {
|
|
345
|
+
/**
|
|
346
|
+
* Make authenticated API calls to your addon's backend or core Jchat endpoints. All calls
|
|
347
|
+
* automatically include the current user's sessionId and site validation headers. Relative paths
|
|
348
|
+
* are automatically prefixed with `/api/v1/addons/<addonId>`.
|
|
349
|
+
*/
|
|
350
|
+
api: AddonApi;
|
|
351
|
+
/**
|
|
352
|
+
* First-class settings API to load and save addon configuration without boilerplate path
|
|
353
|
+
* construction.
|
|
354
|
+
*/
|
|
355
|
+
settings: AddonSettingsApi;
|
|
356
|
+
/**
|
|
357
|
+
* Display a temporary notification toast in the host UI.
|
|
358
|
+
*
|
|
359
|
+
* @param message - The text content to display
|
|
360
|
+
* @param variant - Visual style: 'default' (blue), 'success' (green), or 'error' (red)
|
|
361
|
+
*/
|
|
362
|
+
toast: (message: string, variant?: "default" | "success" | "error") => void;
|
|
363
|
+
/**
|
|
364
|
+
* Navigate the host application to a different internal route.
|
|
365
|
+
*
|
|
366
|
+
* @param path - The target location, e.g., '/settings' or '/addons/store'
|
|
367
|
+
*/
|
|
368
|
+
navigate: (path: string) => void;
|
|
369
|
+
/**
|
|
370
|
+
* Interaction utilities for the active Slate message editor. Only available if your addon is
|
|
371
|
+
* mounted near the chat input.
|
|
372
|
+
*/
|
|
373
|
+
editor: {
|
|
374
|
+
/**
|
|
375
|
+
* Trigger a specialized editor command defined by the host.
|
|
376
|
+
*
|
|
377
|
+
* @example
|
|
378
|
+
* sdk.editor.dispatchCommand("INSERT_YOUTUBE_COMMAND", "dQw4w9WgXcQ")
|
|
379
|
+
*/
|
|
380
|
+
dispatchCommand: (command: string, payload?: unknown) => void;
|
|
381
|
+
/**
|
|
382
|
+
* Insert a low-level Slate node directly at the user's cursor position.
|
|
383
|
+
*
|
|
384
|
+
* @param node - A valid Slate Descendant or Fragment object
|
|
385
|
+
*/
|
|
386
|
+
insertNode: (node: unknown) => void;
|
|
387
|
+
/**
|
|
388
|
+
* Upload and attach a file to the current message without inserting it inline. Users will see
|
|
389
|
+
* the file in the attachment bar below the editor.
|
|
390
|
+
*/
|
|
391
|
+
addAttachment: (attachment: {
|
|
392
|
+
url: string;
|
|
393
|
+
name: string;
|
|
394
|
+
type: string;
|
|
395
|
+
}) => void;
|
|
396
|
+
/** Force the browser focus back to the editor input */
|
|
397
|
+
focus: () => void;
|
|
398
|
+
};
|
|
399
|
+
/** Event Bus for reacting to real-time application state changes. */
|
|
400
|
+
events: {
|
|
401
|
+
/**
|
|
402
|
+
* Register a persistent listener for a specific event.
|
|
403
|
+
*
|
|
404
|
+
* @param event - Event name, e.g., 'room.message', 'user.typing'
|
|
405
|
+
*/
|
|
406
|
+
on: (event: string, callback: AddonEventListener) => void;
|
|
407
|
+
/** Unregister a previously attached listener */
|
|
408
|
+
off: (event: string, callback: AddonEventListener) => void;
|
|
409
|
+
};
|
|
410
|
+
/**
|
|
411
|
+
* The list of fonts available in this JChat deployment, as configured by the site administrator.
|
|
412
|
+
* Use this to build a font picker or apply a matching font family in your addon UI.
|
|
413
|
+
*/
|
|
414
|
+
supportedFonts?: AddonFont[];
|
|
415
|
+
/** The active user locale (e.g. 'en', 'ta', 'ar') */
|
|
416
|
+
locale: string;
|
|
417
|
+
/** Text direction for the active locale ('ltr' or 'rtl') */
|
|
418
|
+
dir: "ltr" | "rtl";
|
|
419
|
+
}
|
|
420
|
+
/**
|
|
421
|
+
* Seed data for your addon's tables, provided inside the addon's isolated schema.
|
|
422
|
+
*
|
|
423
|
+
* These documents are inserted into the addon's dedicated schema on installation and can be used to
|
|
424
|
+
* provide default values, references, or supporting data. The host guarantees that these documents
|
|
425
|
+
* do not conflict with core schemas or other addons.
|
|
426
|
+
*/
|
|
427
|
+
export interface AddonSeedDocument {
|
|
428
|
+
/** Addon-owned table name. Core prefixes the addon's isolated PostgreSQL schema. */
|
|
429
|
+
table: string;
|
|
430
|
+
/** Behavior when a row conflicts with an existing unique or primary key. */
|
|
431
|
+
conflict?: "error" | "ignore" | "update";
|
|
432
|
+
/** Required for update behavior; identifies the unique key used by ON CONFLICT. */
|
|
433
|
+
conflictColumns?: string[];
|
|
434
|
+
/** JSON-compatible rows inserted using parameterized values. */
|
|
435
|
+
rows: Record<string, unknown>[];
|
|
436
|
+
}
|
|
437
|
+
/** Define a manifest with full validation-friendly typing while preserving literal values. */
|
|
438
|
+
export declare function defineAddonManifest<const TManifest extends AddonManifest>(manifest: TManifest): TManifest;
|
|
439
|
+
/** Define a typed server factory without widening its addon database interface. */
|
|
440
|
+
export declare function defineAddonServer<TDatabase = Record<string, Record<string, unknown>>>(factory: AddonServerFactory<TDatabase>): AddonServerFactory<TDatabase>;
|
|
441
|
+
/**
|
|
442
|
+
* Access the host application's SDK. Provides authenticated API access, toast notifications, and
|
|
443
|
+
* navigation.
|
|
444
|
+
*
|
|
445
|
+
* This function is a shell; the real implementation is provided by the Jchat host environment at
|
|
446
|
+
* runtime.
|
|
447
|
+
*
|
|
448
|
+
* @example
|
|
449
|
+
* ;```tsx
|
|
450
|
+
* import { useAddonSDK } from "@jchat/addon-sdk"
|
|
451
|
+
*
|
|
452
|
+
* export default function MyAddon({ mountPoint }: AddonProps) {
|
|
453
|
+
* const sdk = useAddonSDK()
|
|
454
|
+
*
|
|
455
|
+
* const handleClick = async () => {
|
|
456
|
+
* const data = await sdk.api.get("/trending")
|
|
457
|
+
* sdk.toast("Fetched trending posts!", "success")
|
|
458
|
+
* }
|
|
459
|
+
* }
|
|
460
|
+
* ```
|
|
461
|
+
*/
|
|
462
|
+
export declare function useAddonSDK(): AddonSDK;
|
|
463
|
+
/**
|
|
464
|
+
* Get the version of the addon SDK. This is useful for checking compatibility with the host
|
|
465
|
+
* application.
|
|
466
|
+
*
|
|
467
|
+
* @example
|
|
468
|
+
* ;```tsx
|
|
469
|
+
* import { getAddonSdkVersion } from "@jchat/addon-sdk"
|
|
470
|
+
*
|
|
471
|
+
* console.log(getAddonSdkVersion())
|
|
472
|
+
* ```
|
|
473
|
+
*
|
|
474
|
+
* @returns
|
|
475
|
+
*/
|
|
476
|
+
export declare function getAddonSdkVersion(): string;
|
|
477
|
+
//#endregion
|
|
478
|
+
export { type AddonManifest, type AddonPermissionDefinition, type ColumnType, type Generated, type IconComponent, type IconName, type IconPack, type InfiniteData, type Insertable, type JSONColumnType, MOUNT_POINTS, type MountPoint, type QueryKey, type Selectable, type Updateable, useQueryClient };
|
package/dist/state.d.mts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "jotai";
|
package/dist/state.mjs
ADDED
package/dist/types.d.mts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { ComponentType, SVGProps } from "react";
|
|
2
|
+
import { icons } from "lucide-react";
|
|
3
|
+
//#region ../icon/dist/types.d.mts
|
|
4
|
+
//#region src/types.d.ts
|
|
5
|
+
type IconComponent = ComponentType<SVGProps<SVGSVGElement>>;
|
|
6
|
+
type LucideIconName = keyof typeof icons;
|
|
7
|
+
type IconName = LucideIconName | "Youtube";
|
|
8
|
+
declare const ICON_NAMES: readonly IconName[];
|
|
9
|
+
type IconPack = Record<IconName, IconComponent>;
|
|
10
|
+
//#endregion
|
|
11
|
+
//#endregion
|
|
12
|
+
export { IconPack as i, IconComponent as n, IconName as r, ICON_NAMES as t };
|
package/dist/vite.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { AddonManifest } from "./contracts.mjs";
|
|
2
2
|
import { Plugin } from "vite";
|
|
3
3
|
//#region src/vite.d.ts
|
|
4
4
|
/**
|
|
@@ -23,9 +23,9 @@ import { Plugin } from "vite";
|
|
|
23
23
|
* @param options - Configuration options for the JChat addon Vite plugin.
|
|
24
24
|
* @returns A Vite {@link Plugin} instance configured for addon compilation.
|
|
25
25
|
*/
|
|
26
|
-
declare function jchatAddon(options: JchatAddonPluginOptions): Plugin;
|
|
26
|
+
export declare function jchatAddon(options: JchatAddonPluginOptions): Plugin;
|
|
27
27
|
/** Configuration options for the {@link jchatAddon} Vite plugin. */
|
|
28
|
-
interface JchatAddonPluginOptions {
|
|
28
|
+
export interface JchatAddonPluginOptions {
|
|
29
29
|
/** The base manifest specification for the addon. */
|
|
30
30
|
manifest: AddonManifest;
|
|
31
31
|
/** Explicitly declare development mode to permit unsigned database-bearing builds. */
|
|
@@ -55,7 +55,7 @@ interface JchatAddonPluginOptions {
|
|
|
55
55
|
* @param developmentBuild - Indicates whether to enable external sourcemaps or minification.
|
|
56
56
|
* @returns Array of command line arguments for running `bun build`.
|
|
57
57
|
*/
|
|
58
|
-
declare function createAddonServerBuildArguments(entryPath: string, outputPath: string, developmentBuild: boolean): string[];
|
|
58
|
+
export declare function createAddonServerBuildArguments(entryPath: string, outputPath: string, developmentBuild: boolean): string[];
|
|
59
59
|
/**
|
|
60
60
|
* Cryptographically signs an addon manifest using RSA-SHA256 over all archive file contents.
|
|
61
61
|
*
|
|
@@ -64,7 +64,7 @@ declare function createAddonServerBuildArguments(entryPath: string, outputPath:
|
|
|
64
64
|
* @param privateKey - RSA private key in PEM format.
|
|
65
65
|
* @returns Updated {@link AddonManifest} containing an RSA signature block.
|
|
66
66
|
*/
|
|
67
|
-
declare function signAddonManifest(manifest: AddonManifest, files: readonly {
|
|
67
|
+
export declare function signAddonManifest(manifest: AddonManifest, files: readonly {
|
|
68
68
|
path: string;
|
|
69
69
|
contents: Buffer;
|
|
70
70
|
}[], privateKey: string): AddonManifest;
|
|
@@ -76,6 +76,5 @@ declare function signAddonManifest(manifest: AddonManifest, files: readonly {
|
|
|
76
76
|
* @returns Resolved signing key string or undefined (in dev builds).
|
|
77
77
|
* @throws {@link Error} If building a database-bearing addon in production without a signing key.
|
|
78
78
|
*/
|
|
79
|
-
declare function requireAddonSigningKey(signingPrivateKey: string | undefined, developmentBuild: boolean): string | undefined;
|
|
80
|
-
//#endregion
|
|
81
|
-
export { JchatAddonPluginOptions, createAddonServerBuildArguments, jchatAddon, requireAddonSigningKey, signAddonManifest };
|
|
79
|
+
export declare function requireAddonSigningKey(signingPrivateKey: string | undefined, developmentBuild: boolean): string | undefined;
|
|
80
|
+
//#endregion
|
package/dist/vite.mjs
CHANGED
|
@@ -57,11 +57,11 @@ function jchatAddon(options) {
|
|
|
57
57
|
"react-dom",
|
|
58
58
|
"react/jsx-runtime",
|
|
59
59
|
"react/jsx-dev-runtime",
|
|
60
|
-
"jotai",
|
|
61
|
-
"react-hot-toast",
|
|
62
60
|
"@tanstack/react-query",
|
|
63
61
|
"@jchat/addon-sdk",
|
|
64
|
-
"@jchat/addon-sdk/
|
|
62
|
+
"@jchat/addon-sdk/state",
|
|
63
|
+
"@jchat/addon-sdk/ui",
|
|
64
|
+
"@jchat/addon-sdk/icon"
|
|
65
65
|
],
|
|
66
66
|
output: { format: "esm" }
|
|
67
67
|
}
|
|
@@ -254,27 +254,67 @@ function embedCssIntoBundle(buildDir, addonId) {
|
|
|
254
254
|
const js = fs.readFileSync(jsPath, "utf-8");
|
|
255
255
|
if (css.length < 10) css = "";
|
|
256
256
|
else {
|
|
257
|
+
css = stripLayer(css, "properties");
|
|
258
|
+
css = stripLayer(css, "base");
|
|
257
259
|
css = css.replace(/:root\s*\{[^}]+\}/g, "");
|
|
260
|
+
css = css.replace(/:root:not\([^)]+\)\s*\{[^}]+\}/g, "");
|
|
258
261
|
css = css.replace(/\.dark\s*\{[^}]+\}/g, "");
|
|
259
262
|
css = css.replace(/\[data-theme=[^\]]+\]\s*\{[^}]+\}/g, "");
|
|
260
263
|
css = css.replace(/\.dark\s*\[data-theme=[^\]]+\]\s*\{[^}]+\}/g, "");
|
|
261
|
-
css =
|
|
264
|
+
css = css.replace(/\.font-(?:lato|playfair|dancing|poppins|jetbrains|quicksand|roboto-serif|audiowide|special-elite|press-start|grenze|righteous|creepster|vast-shadow|syncopate|lobster)\s*\{[^}]+\}/g, "");
|
|
265
|
+
css = css.replace(/--font-(?:lato|playfair|dancing|poppins|jetbrains|quicksand|roboto-serif|audiowide|special-elite|press-start|grenze|righteous|creepster|vast-shadow|syncopate|lobster):[^;]+;/g, "");
|
|
266
|
+
css = css.replace(/[^{}]*\[data-color[^{}]*\{[^}]+\}/g, "");
|
|
267
|
+
css = css.replace(/@property\s+--tw-[^{]+\{[^}]+\}/g, "");
|
|
268
|
+
css = css.replace(/\.loader-dots\s*\{[^}]+\}/g, "");
|
|
269
|
+
css = css.replace(/\.loader-dots\s+span[^{]*\{[^}]+\}/g, "");
|
|
270
|
+
css = css.replace(/@keyframes\s+wave-dots\s*\{[\s\S]*?\n\}/g, "");
|
|
271
|
+
css = css.replace(/@keyframes\s+gradient-border\s*\{[\s\S]*?\n\}/g, "");
|
|
272
|
+
css = css.replace(/\.animate-gradient(?:-border|-text)?\s*\{[^}]+\}/g, "");
|
|
273
|
+
css = css.replace(/\.me\s*\{[^}]+\}/g, "");
|
|
274
|
+
css = css.replace(/\.user-border\s*\{[^}]+\}/g, "");
|
|
275
|
+
css = css.replace(/\.scrollbar-hide[^{]*\{[^}]+\}/g, "");
|
|
276
|
+
css = css.replace(/@layer\s+components(?:\s*\{[^}]*\}|;)/g, "");
|
|
277
|
+
css = minifyCss(`@scope (#addon-${addonId}) { ${css} }`);
|
|
262
278
|
}
|
|
263
|
-
|
|
279
|
+
if (css.trim().length > 0) {
|
|
280
|
+
const injection = `
|
|
264
281
|
(function() {
|
|
265
|
-
if (typeof document !== 'undefined'
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
document.head.appendChild(
|
|
282
|
+
if (typeof document !== 'undefined') {
|
|
283
|
+
var s = document.createElement('style');
|
|
284
|
+
s.type = 'text/css';
|
|
285
|
+
s.textContent = ${JSON.stringify(css)};
|
|
286
|
+
document.head.appendChild(s);
|
|
270
287
|
}
|
|
271
288
|
})();
|
|
272
289
|
`;
|
|
273
|
-
|
|
290
|
+
fs.writeFileSync(jsPath, injection + js);
|
|
291
|
+
}
|
|
274
292
|
try {
|
|
275
293
|
fs.unlinkSync(cssPath);
|
|
276
294
|
} catch {}
|
|
277
295
|
}
|
|
296
|
+
function stripLayer(css, layerName) {
|
|
297
|
+
const marker = `@layer ${layerName} {`;
|
|
298
|
+
let idx = css.indexOf(marker);
|
|
299
|
+
while (idx !== -1) {
|
|
300
|
+
let braceCount = 0;
|
|
301
|
+
let endIdx = idx + marker.length - 1;
|
|
302
|
+
for (let i = idx + marker.length - 1; i < css.length; i++) if (css[i] === "{") braceCount++;
|
|
303
|
+
else if (css[i] === "}") {
|
|
304
|
+
braceCount--;
|
|
305
|
+
if (braceCount === 0) {
|
|
306
|
+
endIdx = i;
|
|
307
|
+
break;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
css = css.slice(0, idx) + css.slice(endIdx + 1);
|
|
311
|
+
idx = css.indexOf(marker);
|
|
312
|
+
}
|
|
313
|
+
return css;
|
|
314
|
+
}
|
|
315
|
+
function minifyCss(css) {
|
|
316
|
+
return css.replace(/\/\*[\s\S]*?\*\//g, "").replace(/\s+/g, " ").replace(/\s*([{};:,>])\s*/g, "$1").replace(/;}/g, "}").trim();
|
|
317
|
+
}
|
|
278
318
|
function sortValue(child) {
|
|
279
319
|
if (Array.isArray(child)) return child.map(sortValue);
|
|
280
320
|
if (child && typeof child === "object") return Object.fromEntries(Object.entries(child).toSorted(([left], [right]) => left.localeCompare(right)).map(([key, nested]) => [key, sortValue(nested)]));
|