@jchat/addon-sdk 0.0.1 → 1.0.0-alpha.10
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 +64 -0
- package/LICENSE +18 -196
- package/README.md +26 -2
- package/dist/cli.d.mts +36 -0
- package/dist/cli.mjs +135 -0
- package/dist/contracts.d.mts +16 -24
- package/dist/diagnostics.d.mts +47 -0
- package/dist/diagnostics.mjs +175 -1
- package/dist/index.d.mts +282 -8
- package/dist/index.mjs +481 -1
- package/dist/ui.d.mts +1 -0
- package/dist/ui.mjs +2 -0
- package/dist/vite.d.mts +63 -11
- package/dist/vite.mjs +335 -4
- package/package.json +45 -12
- package/tsconfig.addon.json +20 -0
package/dist/diagnostics.mjs
CHANGED
|
@@ -1 +1,175 @@
|
|
|
1
|
-
import*as
|
|
1
|
+
import * as fs$1 from "node:fs";
|
|
2
|
+
import * as path$1 from "node:path";
|
|
3
|
+
//#region src/diagnostics.ts
|
|
4
|
+
/**
|
|
5
|
+
* Constructs a semantic version compatibility range covering all minor/patch versions of the
|
|
6
|
+
* current major version.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ;```ts
|
|
10
|
+
* createCompatibleMajorRange("1.4.2") // returns ">=1.4.2 <2.0.0"
|
|
11
|
+
* ```
|
|
12
|
+
*
|
|
13
|
+
* @param version - Valid semver string (e.g. `"1.2.0"`).
|
|
14
|
+
* @returns Semver range string matching the current major series (e.g. `">=1.2.0 <2.0.0"`).
|
|
15
|
+
* @throws {@link Error} If the input is not a valid semantic version string.
|
|
16
|
+
*/
|
|
17
|
+
function createCompatibleMajorRange(version) {
|
|
18
|
+
try {
|
|
19
|
+
Bun.semver.order(version, version);
|
|
20
|
+
} catch {
|
|
21
|
+
throw new Error(`Invalid semantic version: ${version}`);
|
|
22
|
+
}
|
|
23
|
+
const majorComponent = /^(?:v|=)?(\d+)\./.exec(version)?.[1];
|
|
24
|
+
const major = majorComponent ? Number(majorComponent) : NaN;
|
|
25
|
+
if (!Number.isSafeInteger(major)) throw new Error(`Invalid semantic version: ${version}`);
|
|
26
|
+
return `>=${version} <${major + 1}.0.0`;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Performs comprehensive diagnostic validation checks on an addon package build directory.
|
|
30
|
+
*
|
|
31
|
+
* Validates version compatibility ranges, verifies required build files (frontend/server bundles,
|
|
32
|
+
* migrations, seeds), checks total package size limits, flags production source maps, and verifies
|
|
33
|
+
* publisher digital signatures.
|
|
34
|
+
*
|
|
35
|
+
* @param options - Configuration options specifying build directory, manifest, and environment
|
|
36
|
+
* versions.
|
|
37
|
+
* @returns Array of {@link AddonPackageDiagnostic} results.
|
|
38
|
+
*/
|
|
39
|
+
function diagnoseAddonPackage(options) {
|
|
40
|
+
const { buildDir, manifest } = options;
|
|
41
|
+
const diagnostics = [];
|
|
42
|
+
diagnostics.push(compatibilityDiagnostic("jchat", "JChat compatibility", options.jchatVersion, manifest.compatibility?.jchat), compatibilityDiagnostic("addon-sdk", "Addon SDK compatibility", options.addonSdkVersion, manifest.compatibility?.addonSdk));
|
|
43
|
+
const references = [
|
|
44
|
+
[
|
|
45
|
+
"frontend-bundle",
|
|
46
|
+
"Frontend bundle",
|
|
47
|
+
manifest.frontend?.bundle
|
|
48
|
+
],
|
|
49
|
+
[
|
|
50
|
+
"server-bundle",
|
|
51
|
+
"Server bundle",
|
|
52
|
+
manifest.server?.bundle
|
|
53
|
+
],
|
|
54
|
+
...(manifest.server?.migrations ?? []).map((file) => [
|
|
55
|
+
`migration:${file}`,
|
|
56
|
+
`Migration ${file}`,
|
|
57
|
+
file
|
|
58
|
+
]),
|
|
59
|
+
...(manifest.server?.seeds ?? []).map((file) => [
|
|
60
|
+
`seed:${file}`,
|
|
61
|
+
`Seed ${file}`,
|
|
62
|
+
file
|
|
63
|
+
])
|
|
64
|
+
];
|
|
65
|
+
for (const [id, label, reference] of references) {
|
|
66
|
+
if (!reference) continue;
|
|
67
|
+
const file = resolveBuildFile(buildDir, reference);
|
|
68
|
+
diagnostics.push({
|
|
69
|
+
id,
|
|
70
|
+
label,
|
|
71
|
+
status: file && fs$1.existsSync(file) && fs$1.statSync(file).isFile() ? "pass" : "error",
|
|
72
|
+
message: file && fs$1.existsSync(file) && fs$1.statSync(file).isFile() ? reference : `Missing or unsafe build file: ${reference}`
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
const files = listRegularFiles(buildDir);
|
|
76
|
+
const productionArchive = path$1.join(buildDir, `${manifest.id}-${manifest.version}.zip`);
|
|
77
|
+
const hasProductionArchive = fs$1.existsSync(productionArchive) && fs$1.statSync(productionArchive).isFile();
|
|
78
|
+
const totalBytes = options.productionPackage && hasProductionArchive ? fs$1.statSync(productionArchive).size : files.filter((file) => file !== productionArchive).reduce((total, file) => total + fs$1.statSync(file).size, 0);
|
|
79
|
+
const maxBytes = options.maxBytes ?? 25 * 1024 * 1024;
|
|
80
|
+
diagnostics.push({
|
|
81
|
+
id: "package-size",
|
|
82
|
+
label: "Package size",
|
|
83
|
+
status: totalBytes <= maxBytes ? "pass" : "error",
|
|
84
|
+
message: `${totalBytes} of ${maxBytes} bytes`
|
|
85
|
+
});
|
|
86
|
+
const addonPermPrefix = `addon.${manifest.id}.`;
|
|
87
|
+
const invalidPerms = Array.from(/* @__PURE__ */ new Set([
|
|
88
|
+
...manifest.accessPermission ? [manifest.accessPermission] : [],
|
|
89
|
+
...manifest.permissions ?? [],
|
|
90
|
+
...manifest.permissionDefinitions?.map((d) => d.name) ?? []
|
|
91
|
+
])).filter((perm) => !perm.startsWith(addonPermPrefix));
|
|
92
|
+
diagnostics.push({
|
|
93
|
+
id: "permission-namespacing",
|
|
94
|
+
label: "Permission namespacing",
|
|
95
|
+
status: invalidPerms.length === 0 ? "pass" : "error",
|
|
96
|
+
message: invalidPerms.length === 0 ? `All declared permissions start with ${addonPermPrefix}` : `Permission(s) do not follow naming convention ${addonPermPrefix}*: ${invalidPerms.join(", ")}`
|
|
97
|
+
});
|
|
98
|
+
const sourceMaps = files.filter((file) => file.endsWith(".map"));
|
|
99
|
+
diagnostics.push({
|
|
100
|
+
id: "source-maps",
|
|
101
|
+
label: "Source maps",
|
|
102
|
+
status: options.productionPackage && sourceMaps.length ? "warning" : "pass",
|
|
103
|
+
message: sourceMaps.length ? `${sourceMaps.length} source map(s) present` : "No source maps"
|
|
104
|
+
});
|
|
105
|
+
const hasDatabaseFiles = Boolean(manifest.server?.migrations?.length || manifest.server?.seeds?.length);
|
|
106
|
+
diagnostics.push({
|
|
107
|
+
id: "database-signature",
|
|
108
|
+
label: "Database file signature",
|
|
109
|
+
status: hasDatabaseFiles && options.productionPackage && !manifest.signature?.value ? "error" : hasDatabaseFiles && !manifest.signature?.value ? "warning" : "pass",
|
|
110
|
+
message: hasDatabaseFiles && !manifest.signature?.value ? options.productionPackage ? "Production database files require a publisher signature" : "Unsigned database files are allowed only in the developer sandbox" : hasDatabaseFiles ? "Publisher signature present" : "No database files declared"
|
|
111
|
+
});
|
|
112
|
+
if (options.productionPackage) diagnostics.push({
|
|
113
|
+
id: "production-archive",
|
|
114
|
+
label: "Production archive",
|
|
115
|
+
status: hasProductionArchive ? "pass" : "error",
|
|
116
|
+
message: hasProductionArchive ? path$1.basename(productionArchive) : "Production archive is missing"
|
|
117
|
+
});
|
|
118
|
+
return diagnostics;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Checks whether any diagnostic check in a diagnostic list returned a critical error.
|
|
122
|
+
*
|
|
123
|
+
* @param diagnostics - Array of diagnostic check results to evaluate.
|
|
124
|
+
* @returns `true` if at least one check has `status === "error"`, otherwise `false`.
|
|
125
|
+
*/
|
|
126
|
+
function hasAddonDiagnosticErrors(diagnostics) {
|
|
127
|
+
return diagnostics.some(({ status }) => status === "error");
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Evaluates semantic version compatibility between a target version and a semver range string.
|
|
131
|
+
*
|
|
132
|
+
* @param id - Diagnostic check identifier.
|
|
133
|
+
* @param label - Diagnostic check label.
|
|
134
|
+
* @param version - Environment component version string.
|
|
135
|
+
* @param range - Expected semver range string from manifest compatibility declarations.
|
|
136
|
+
* @returns An {@link AddonPackageDiagnostic} result object.
|
|
137
|
+
*/
|
|
138
|
+
function compatibilityDiagnostic(id, label, version, range) {
|
|
139
|
+
const compatible = Boolean(range && Bun.semver.satisfies(version, range));
|
|
140
|
+
return {
|
|
141
|
+
id,
|
|
142
|
+
label,
|
|
143
|
+
status: compatible ? "pass" : "error",
|
|
144
|
+
message: range ? `${version} ${compatible ? "satisfies" : "does not satisfy"} ${range}` : "Compatibility range is missing"
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Safely resolves a relative build file reference within a target build directory, guarding against
|
|
149
|
+
* directory traversal attacks.
|
|
150
|
+
*
|
|
151
|
+
* @param buildDir - Absolute path to the build directory.
|
|
152
|
+
* @param reference - Relative file path declared in manifest.
|
|
153
|
+
* @returns Resolved absolute file path if safe and inside `buildDir`, or `null` if unsafe.
|
|
154
|
+
*/
|
|
155
|
+
function resolveBuildFile(buildDir, reference) {
|
|
156
|
+
if (path$1.isAbsolute(reference) || reference.includes("\\") || reference.split("/").includes("..")) return null;
|
|
157
|
+
const target = path$1.resolve(buildDir, ...reference.replace(/^\.\//, "").split("/"));
|
|
158
|
+
const relative = path$1.relative(path$1.resolve(buildDir), target);
|
|
159
|
+
return relative && !relative.startsWith("..") && !path$1.isAbsolute(relative) ? target : null;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Recursively lists all regular files within a root directory.
|
|
163
|
+
*
|
|
164
|
+
* @param root - Path to search.
|
|
165
|
+
* @returns Array of absolute file paths.
|
|
166
|
+
*/
|
|
167
|
+
function listRegularFiles(root) {
|
|
168
|
+
if (!fs$1.existsSync(root)) return [];
|
|
169
|
+
return fs$1.readdirSync(root, {
|
|
170
|
+
recursive: true,
|
|
171
|
+
withFileTypes: true
|
|
172
|
+
}).filter((entry) => entry.isFile()).map((entry) => path$1.join(entry.parentPath, entry.name));
|
|
173
|
+
}
|
|
174
|
+
//#endregion
|
|
175
|
+
export { createCompatibleMajorRange, diagnoseAddonPackage, hasAddonDiagnosticErrors };
|
package/dist/index.d.mts
CHANGED
|
@@ -1,14 +1,178 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { i as MountPoint, n as AddonPermissionDefinition, r as MOUNT_POINTS, t as AddonManifest } from "./contracts.mjs";
|
|
2
2
|
import { ComponentType, SVGProps } from "react";
|
|
3
|
+
import { InfiniteData, MutationFunction, QueryFunction, QueryFunctionContext, QueryKey, UndefinedInitialDataInfiniteOptions, UndefinedInitialDataOptions, UseInfiniteQueryResult, UseMutationOptions, UseMutationResult, UseQueryResult, useQueryClient } from "@tanstack/react-query";
|
|
3
4
|
import { Elysia } from "elysia";
|
|
4
5
|
import { Kysely } from "kysely";
|
|
5
|
-
//#region ../icon/dist/
|
|
6
|
+
//#region ../icon/dist/types.d.mts
|
|
6
7
|
//#region src/types.d.ts
|
|
7
8
|
type IconComponent = ComponentType<SVGProps<SVGSVGElement>>;
|
|
8
|
-
declare const ICON_NAMES: readonly ["
|
|
9
|
+
declare const ICON_NAMES: readonly ["Activity", "ArrowLeft", "ArrowRight", "Ban", "Bell", "BookOpen", "Brush", "Calendar", "Camera", "ChartBar", "Check", "CheckCheck", "CheckCircle2", "ChevronDown", "ChevronLeft", "ChevronRight", "ChevronUp", "CircleAlert", "CircleCheck", "CircleOff", "CirclePlay", "CirclePlus", "Clock", "Coffee", "Contact", "Cpu", "Database", "Dices", "Ellipsis", "Eraser", "ExternalLink", "Eye", "EyeOff", "Flag", "Flame", "FolderPlus", "Footprints", "Gamepad2", "Gift", "Globe", "HardDrive", "Headphones", "Heart", "Image", "Info", "Key", "KeyRound", "Layers", "LayoutDashboard", "LayoutList", "LayoutTemplate", "Link2", "ListOrdered", "Loader", "LoaderCircle", "Lock", "LogOut", "Mail", "Maximize", "Megaphone", "MemoryStick", "Menu", "MessageCircle", "MessageSquare", "Mic", "Minimize", "Minus", "Monitor", "Moon", "MoreHorizontal", "Music", "Music2", "Newspaper", "Palette", "Paperclip", "PartyPopper", "Pause", "PenLine", "Play", "Plus", "Radio", "Reply", "RotateCcw", "RotateCw", "Search", "Send", "Server", "Settings", "Shield", "ShieldAlert", "ShieldCheck", "SkipForward", "SlidersHorizontal", "Smartphone", "Smile", "Sparkles", "Square", "SquarePen", "Star", "Sun", "ThumbsDown", "ThumbsUp", "Timer", "Trash", "Trash2", "Trophy", "Upload", "User", "UserCheck", "UserCog", "UserMinus", "UserPen", "UserPlus", "UserRoundPlus", "Users", "UserX", "Utensils", "Video", "VideoOff", "Volume", "Volume2", "VolumeX", "X", "Youtube", "Zap"];
|
|
9
10
|
type IconName = (typeof ICON_NAMES)[number];
|
|
10
11
|
type IconPack = Record<IconName, IconComponent>;
|
|
11
12
|
//#endregion
|
|
13
|
+
//#region src/i18n.d.ts
|
|
14
|
+
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"];
|
|
15
|
+
type HostLanguage = (typeof HOST_LANGUAGES)[number];
|
|
16
|
+
interface AddonLocaleValidationResult {
|
|
17
|
+
valid: boolean;
|
|
18
|
+
errors: string[];
|
|
19
|
+
missingFiles: string[];
|
|
20
|
+
missingKeys: Record<string, string[]>;
|
|
21
|
+
extraKeys: Record<string, string[]>;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Validates that an addon's `locales/` directory contains valid translation files for all 11 host
|
|
25
|
+
* languages with 100% key parity against `en.json`.
|
|
26
|
+
*
|
|
27
|
+
* @param dirPath - Path to the addon's locales directory.
|
|
28
|
+
* @param domainName - Optional label for error messages (defaults to "Addon").
|
|
29
|
+
*/
|
|
30
|
+
declare function validateAddonLocales(dirPath: string, domainName?: string): AddonLocaleValidationResult;
|
|
31
|
+
type AddonTranslationDictionary = Record<string, unknown>;
|
|
32
|
+
type AddonTranslationResources = Record<string, AddonTranslationDictionary>;
|
|
33
|
+
type AddonLocaleLoader = (locale: string) => Promise<AddonTranslationDictionary> | AddonTranslationDictionary;
|
|
34
|
+
interface LocaleContextValue {
|
|
35
|
+
locale: string;
|
|
36
|
+
dir: "ltr" | "rtl";
|
|
37
|
+
}
|
|
38
|
+
declare const LocaleContext: import("react").Context<LocaleContextValue>;
|
|
39
|
+
interface TranslatorOptions {
|
|
40
|
+
resources?: AddonTranslationResources;
|
|
41
|
+
defaultLocale?: string;
|
|
42
|
+
fallbackLocale?: string;
|
|
43
|
+
}
|
|
44
|
+
type TranslateFunction<TKeys extends string = string> = (key: TKeys | (string & {}), paramsOrLang?: Record<string, unknown> | string, maybeLang?: string) => string;
|
|
45
|
+
/**
|
|
46
|
+
* Creates a standalone translation function for backend server routes (`src/api.ts`) or utilities.
|
|
47
|
+
*
|
|
48
|
+
* @param resources - Dictionary of translation resources (e.g., `{ en: enJson, ta: taJson }`).
|
|
49
|
+
* @param defaultLocale - Default locale to use if not specified per call (defaults to "en").
|
|
50
|
+
* @param fallbackLocale - Fallback locale if a key is missing in the target language (defaults to
|
|
51
|
+
* "en").
|
|
52
|
+
*/
|
|
53
|
+
declare function createTranslator<TResources extends AddonTranslationResources = AddonTranslationResources>(resources?: TResources, defaultLocale?: string, fallbackLocale?: string): TranslateFunction<TResources extends {
|
|
54
|
+
en: infer TEn;
|
|
55
|
+
} ? TEn extends Record<string, unknown> ? Extract<keyof TEn, string> : string : string>;
|
|
56
|
+
/**
|
|
57
|
+
* Hook to access the current active host locale and text direction in an addon component.
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* ;```tsx
|
|
61
|
+
* import { useLocale } from "@jchat/addon-sdk"
|
|
62
|
+
*
|
|
63
|
+
* export default function MyAddon() {
|
|
64
|
+
* const { locale, dir, isRtl } = useLocale()
|
|
65
|
+
* return <div dir={dir}>Current language: {locale}</div>
|
|
66
|
+
* }
|
|
67
|
+
* ```
|
|
68
|
+
*/
|
|
69
|
+
declare function useLocale(): {
|
|
70
|
+
locale: string;
|
|
71
|
+
dir: "ltr" | "rtl";
|
|
72
|
+
isRtl: boolean;
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* Hook to access translation function and locale metadata inside addon components. Automatically
|
|
76
|
+
* synchronizes with host language switches and falls back to English. Supports static dictionary
|
|
77
|
+
* resources or dynamic async locale loaders.
|
|
78
|
+
*
|
|
79
|
+
* @example
|
|
80
|
+
* ;```tsx
|
|
81
|
+
* import { useTranslation } from "@jchat/addon-sdk"
|
|
82
|
+
* import locales from "./locales"
|
|
83
|
+
*
|
|
84
|
+
* export default function MyAddon() {
|
|
85
|
+
* const { t, locale, dir } = useTranslation(locales)
|
|
86
|
+
* return (
|
|
87
|
+
* <div dir={dir}>
|
|
88
|
+
* <h1>{t("welcome", { name: "Guest" })}</h1>
|
|
89
|
+
* </div>
|
|
90
|
+
* )
|
|
91
|
+
* }
|
|
92
|
+
* ```
|
|
93
|
+
*
|
|
94
|
+
* @param resourcesOrLoader - Addon-scoped translation dictionaries or async chunk loader.
|
|
95
|
+
* @param fallbackLocale - Fallback language code when a string is missing (default: "en").
|
|
96
|
+
*/
|
|
97
|
+
declare function useTranslation<TResources extends AddonTranslationResources | AddonLocaleLoader = AddonTranslationResources>(resourcesOrLoader?: TResources, fallbackLocale?: string): {
|
|
98
|
+
t: TranslateFunction<TResources extends {
|
|
99
|
+
en: infer TEn;
|
|
100
|
+
} ? TEn extends Record<string, unknown> ? Extract<keyof TEn, string> : string : string>;
|
|
101
|
+
locale: string;
|
|
102
|
+
dir: "ltr" | "rtl";
|
|
103
|
+
isRtl: boolean;
|
|
104
|
+
isReady: boolean;
|
|
105
|
+
};
|
|
106
|
+
//#endregion
|
|
107
|
+
//#region src/query.d.ts
|
|
108
|
+
interface UseAddonQueryOptions<TData = unknown, TError = Error> extends Omit<UndefinedInitialDataOptions<TData, TError, TData, QueryKey>, "queryKey" | "queryFn"> {
|
|
109
|
+
/** The API path to fetch, automatically prefixed with `/api/v1/addons/<addonId>` */
|
|
110
|
+
path?: string;
|
|
111
|
+
/** Custom query key elements appended to the automatic path cache key */
|
|
112
|
+
queryKey?: QueryKey;
|
|
113
|
+
/** Custom query function override */
|
|
114
|
+
queryFn?: QueryFunction<TData, QueryKey>;
|
|
115
|
+
}
|
|
116
|
+
interface UseAddonMutationOptions<TData = unknown, TVariables = unknown, TError = Error, TContext = unknown> extends Omit<UseMutationOptions<TData, TError, TVariables, TContext>, "mutationFn"> {
|
|
117
|
+
/** The target API path, or a dynamic function of mutation variables */
|
|
118
|
+
path?: string | ((variables: TVariables) => string);
|
|
119
|
+
/** HTTP method to use (defaults to 'POST') */
|
|
120
|
+
method?: "POST" | "PUT" | "DELETE";
|
|
121
|
+
/** Query keys to automatically invalidate upon successful mutation */
|
|
122
|
+
invalidateQueries?: QueryKey[];
|
|
123
|
+
/** Custom mutation function override */
|
|
124
|
+
mutationFn?: MutationFunction<TData, TVariables>;
|
|
125
|
+
}
|
|
126
|
+
interface UseAddonInfiniteQueryOptions<TData = unknown, TError = Error, TPageParam = unknown> extends Omit<UndefinedInitialDataInfiniteOptions<TData, TError, InfiniteData<TData, TPageParam>, QueryKey, TPageParam>, "queryKey" | "queryFn"> {
|
|
127
|
+
/** The API path to fetch, or a dynamic function that receives pageParam and returns the path */
|
|
128
|
+
path?: string | ((pageParam: TPageParam) => string);
|
|
129
|
+
/** Custom query key elements appended to the automatic cache key */
|
|
130
|
+
queryKey?: QueryKey;
|
|
131
|
+
/** Custom query function override */
|
|
132
|
+
queryFn?: (context: QueryFunctionContext<QueryKey, TPageParam>) => Promise<TData>;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Perform a declarative, cached GET request using the host's shared React Query instance.
|
|
136
|
+
* Automatically injects authentication headers and prefixes addon paths.
|
|
137
|
+
*
|
|
138
|
+
* @example
|
|
139
|
+
* ;```tsx
|
|
140
|
+
* const { data, isLoading } = useAddonQuery<{ polls: Poll[] }>({
|
|
141
|
+
* path: "/polls",
|
|
142
|
+
* queryKey: [siteId]
|
|
143
|
+
* })
|
|
144
|
+
* ```
|
|
145
|
+
*/
|
|
146
|
+
declare function useAddonQuery<TData = unknown, TError = Error>(options: UseAddonQueryOptions<TData, TError>): UseQueryResult<TData, TError>;
|
|
147
|
+
/**
|
|
148
|
+
* Perform a paginated or infinite scrolling request using the host's shared React Query instance.
|
|
149
|
+
*
|
|
150
|
+
* @example
|
|
151
|
+
* ;```tsx
|
|
152
|
+
* const { data, fetchNextPage, hasNextPage } = useAddonInfiniteQuery<GifResponse, Error, number>({
|
|
153
|
+
* path: (page) => `/trending?offset=${page * 20}`,
|
|
154
|
+
* initialPageParam: 0,
|
|
155
|
+
* getNextPageParam: (lastPage, allPages) => (lastPage.hasMore ? allPages.length : undefined)
|
|
156
|
+
* })
|
|
157
|
+
* ```
|
|
158
|
+
*/
|
|
159
|
+
declare function useAddonInfiniteQuery<TData = unknown, TError = Error, TPageParam = unknown>(options: UseAddonInfiniteQueryOptions<TData, TError, TPageParam>): UseInfiniteQueryResult<InfiniteData<TData, TPageParam>, TError>;
|
|
160
|
+
/**
|
|
161
|
+
* Perform a declarative mutation using the host's shared React Query instance. Automatically
|
|
162
|
+
* executes the request and can invalidate specified query keys upon success.
|
|
163
|
+
*
|
|
164
|
+
* @example
|
|
165
|
+
* ;```tsx
|
|
166
|
+
* const { mutate, isPending } = useAddonMutation<Poll, CreatePollInput>({
|
|
167
|
+
* path: "/polls",
|
|
168
|
+
* method: "POST",
|
|
169
|
+
* invalidateQueries: [["/polls"]],
|
|
170
|
+
* onSuccess: () => sdk.toast("Poll created!", "success")
|
|
171
|
+
* })
|
|
172
|
+
* ```
|
|
173
|
+
*/
|
|
174
|
+
declare function useAddonMutation<TData = unknown, TVariables = unknown, TError = Error, TContext = unknown>(options: UseAddonMutationOptions<TData, TVariables, TError, TContext>): UseMutationResult<TData, TError, TVariables, TContext>;
|
|
175
|
+
//#endregion
|
|
12
176
|
//#region src/index.d.ts
|
|
13
177
|
type AddonJsonBody = Record<string, unknown> | unknown[] | null;
|
|
14
178
|
/**
|
|
@@ -32,7 +196,7 @@ interface AddonUser {
|
|
|
32
196
|
gender: string;
|
|
33
197
|
}
|
|
34
198
|
interface AddonRequestUser {
|
|
35
|
-
id
|
|
199
|
+
id: string;
|
|
36
200
|
permissions?: string[];
|
|
37
201
|
siteId?: string;
|
|
38
202
|
}
|
|
@@ -41,6 +205,32 @@ interface AddonRequestUser {
|
|
|
41
205
|
* schema switching, dialect/connection access, and lifecycle methods.
|
|
42
206
|
*/
|
|
43
207
|
type AddonDatabase<TDatabase = Record<string, Record<string, unknown>>> = Pick<Kysely<TDatabase>, "deleteFrom" | "fn" | "insertInto" | "selectFrom" | "transaction" | "updateTable">;
|
|
208
|
+
/**
|
|
209
|
+
* Scoped caching and key-value client provided to addon server instances. Keys are automatically
|
|
210
|
+
* namespaced by the host as `addon:<addonId>:<key>`.
|
|
211
|
+
*/
|
|
212
|
+
interface AddonCacheClient {
|
|
213
|
+
/** Retrieve a string or JSON-parsed value by key */
|
|
214
|
+
get: <T = string>(key: string) => Promise<T | null>;
|
|
215
|
+
/** Store a value with an optional time-to-live in seconds */
|
|
216
|
+
set: (key: string, value: unknown, ttlSeconds?: number) => Promise<void>;
|
|
217
|
+
/** Delete one or more keys */
|
|
218
|
+
del: (...keys: string[]) => Promise<number>;
|
|
219
|
+
/** Atomically increment a numeric counter and return the new value */
|
|
220
|
+
incr: (key: string) => Promise<number>;
|
|
221
|
+
/** Set a timeout on a key in seconds */
|
|
222
|
+
expire: (key: string, ttlSeconds: number) => Promise<boolean>;
|
|
223
|
+
/** Get the remaining time-to-live of a key in seconds (-2 if key does not exist, -1 if no TTL) */
|
|
224
|
+
ttl: (key: string) => Promise<number>;
|
|
225
|
+
}
|
|
226
|
+
interface AddonServerEmitTarget {
|
|
227
|
+
/** Target a specific site (defaults to the request user's siteId) */
|
|
228
|
+
siteId?: string;
|
|
229
|
+
/** Target a specific chat room */
|
|
230
|
+
roomId?: string;
|
|
231
|
+
/** Target a specific user */
|
|
232
|
+
userId?: string;
|
|
233
|
+
}
|
|
44
234
|
/**
|
|
45
235
|
* Context provided to an addon's server-side factory. Parameterize it with the addon's table
|
|
46
236
|
* interface: `AddonServerContext<MyAddonDatabase>`.
|
|
@@ -62,13 +252,44 @@ interface AddonServerContext<TDatabase = Record<string, Record<string, unknown>>
|
|
|
62
252
|
* @param user - The user object from the request context
|
|
63
253
|
* @param permission - The permission to check, e.g., 'message.send' or 'addon.stories.view'
|
|
64
254
|
*/
|
|
65
|
-
hasPermission: (user: AddonRequestUser | null | undefined, permission:
|
|
255
|
+
hasPermission: (user: AddonRequestUser | null | undefined, permission: string) => boolean;
|
|
66
256
|
/**
|
|
67
257
|
* A Kysely database instance scoped entirely to your addon's tables. You cannot access core Jchat
|
|
68
258
|
* tables (Users, Rooms, etc.) through this instance.
|
|
69
259
|
*/
|
|
70
260
|
db: AddonDatabase<TDatabase>;
|
|
261
|
+
/** A secure, namespaced key-value cache client scoped to your addon (`addon:<addonId>:<key>`). */
|
|
262
|
+
cache: AddonCacheClient;
|
|
263
|
+
/**
|
|
264
|
+
* Broadcast a real-time event to connected clients across the site or room. Clients listen via
|
|
265
|
+
* `sdk.events.on('addon:<addonId>:<event>', callback)` or `sdk.events.on(event, callback)`.
|
|
266
|
+
*/
|
|
267
|
+
emit: (event: string, payload: unknown, target?: AddonServerEmitTarget) => Promise<void>;
|
|
71
268
|
}
|
|
269
|
+
/**
|
|
270
|
+
* Elysia app instance provided to addon server endpoints with Jchat macro metadata (e.g.
|
|
271
|
+
* requirePermission) typed.
|
|
272
|
+
*/
|
|
273
|
+
type AddonApp = Elysia<"", {
|
|
274
|
+
decorator: {};
|
|
275
|
+
store: {};
|
|
276
|
+
derive: {};
|
|
277
|
+
resolve: {
|
|
278
|
+
user: AddonRequestUser;
|
|
279
|
+
};
|
|
280
|
+
}, {
|
|
281
|
+
typebox: {};
|
|
282
|
+
error: {};
|
|
283
|
+
}, {
|
|
284
|
+
schema: {};
|
|
285
|
+
standaloneSchema: {};
|
|
286
|
+
macro: {
|
|
287
|
+
requirePermission?: string | string[];
|
|
288
|
+
};
|
|
289
|
+
macroFn: {};
|
|
290
|
+
parser: {};
|
|
291
|
+
response: {};
|
|
292
|
+
}>;
|
|
72
293
|
/**
|
|
73
294
|
* The entry point for your addon's server bundle. You should default-export a function matching
|
|
74
295
|
* this signature.
|
|
@@ -83,7 +304,7 @@ interface AddonServerContext<TDatabase = Record<string, Record<string, unknown>>
|
|
|
83
304
|
* export default factory
|
|
84
305
|
* ```
|
|
85
306
|
*/
|
|
86
|
-
type AddonServerFactory<TDatabase = Record<string, Record<string, unknown>>> = (ctx: AddonServerContext<TDatabase>, app:
|
|
307
|
+
type AddonServerFactory<TDatabase = Record<string, Record<string, unknown>>> = (ctx: AddonServerContext<TDatabase>, app: AddonApp) => Elysia;
|
|
87
308
|
/** Props passed to your addon's root React component by the Jchat host. */
|
|
88
309
|
interface AddonProps {
|
|
89
310
|
/** The mount point where this addon instance is rendered (e.g., 'chat-sidebar') */
|
|
@@ -100,6 +321,10 @@ interface AddonProps {
|
|
|
100
321
|
roomId?: string;
|
|
101
322
|
/** The ID of the active Jchat site */
|
|
102
323
|
siteId?: string;
|
|
324
|
+
/** The active user locale (e.g. 'en', 'ta', 'ar') */
|
|
325
|
+
locale?: string;
|
|
326
|
+
/** Text direction for the current locale */
|
|
327
|
+
dir?: "ltr" | "rtl";
|
|
103
328
|
}
|
|
104
329
|
interface AddonApi {
|
|
105
330
|
get: <TResponse = unknown>(path: string) => Promise<TResponse>;
|
|
@@ -107,7 +332,27 @@ interface AddonApi {
|
|
|
107
332
|
put: <TResponse = unknown>(path: string, body?: AddonJsonBody) => Promise<TResponse>;
|
|
108
333
|
delete: <TResponse = unknown>(path: string) => Promise<TResponse>;
|
|
109
334
|
}
|
|
335
|
+
/** First-class addon settings management interface provided by the host. */
|
|
336
|
+
interface AddonSettingsApi {
|
|
337
|
+
/** Fetch the current site's saved configuration for this addon */
|
|
338
|
+
get: <T = Record<string, unknown>>() => Promise<T | null>;
|
|
339
|
+
/** Save updated configuration for this addon for the current site */
|
|
340
|
+
save: <T = Record<string, unknown>>(settings: T) => Promise<void>;
|
|
341
|
+
}
|
|
110
342
|
type AddonEventListener = (data: unknown) => void;
|
|
343
|
+
/** A font option provided by the JChat host. */
|
|
344
|
+
interface AddonFont {
|
|
345
|
+
/** Display label shown to users (e.g. 'Modern', 'Elegant') */
|
|
346
|
+
label: string;
|
|
347
|
+
/** Font identifier stored in the database (e.g. 'poppins', 'lato') */
|
|
348
|
+
value: string;
|
|
349
|
+
/** CSS font-family string ready for use in inline styles */
|
|
350
|
+
family: string;
|
|
351
|
+
/** Google Fonts name for loading the font via a <link> tag */
|
|
352
|
+
googleName: string;
|
|
353
|
+
/** Relative size multiplier — apply this to base font sizes to match the host's rendering */
|
|
354
|
+
scale: number;
|
|
355
|
+
}
|
|
111
356
|
/**
|
|
112
357
|
* The bridge between your addon and the Jchat host application. Use the `useAddonSDK()` hook to
|
|
113
358
|
* access this interface in your components.
|
|
@@ -115,9 +360,15 @@ type AddonEventListener = (data: unknown) => void;
|
|
|
115
360
|
interface AddonSDK {
|
|
116
361
|
/**
|
|
117
362
|
* Make authenticated API calls to your addon's backend or core Jchat endpoints. All calls
|
|
118
|
-
* automatically include the current user's sessionId and site validation headers.
|
|
363
|
+
* automatically include the current user's sessionId and site validation headers. Relative paths
|
|
364
|
+
* are automatically prefixed with `/api/v1/addons/<addonId>`.
|
|
119
365
|
*/
|
|
120
366
|
api: AddonApi;
|
|
367
|
+
/**
|
|
368
|
+
* First-class settings API to load and save addon configuration without boilerplate path
|
|
369
|
+
* construction.
|
|
370
|
+
*/
|
|
371
|
+
settings: AddonSettingsApi;
|
|
121
372
|
/**
|
|
122
373
|
* Display a temporary notification toast in the host UI.
|
|
123
374
|
*
|
|
@@ -172,6 +423,15 @@ interface AddonSDK {
|
|
|
172
423
|
/** Unregister a previously attached listener */
|
|
173
424
|
off: (event: string, callback: AddonEventListener) => void;
|
|
174
425
|
};
|
|
426
|
+
/**
|
|
427
|
+
* The list of fonts available in this JChat deployment, as configured by the site administrator.
|
|
428
|
+
* Use this to build a font picker or apply a matching font family in your addon UI.
|
|
429
|
+
*/
|
|
430
|
+
supportedFonts?: AddonFont[];
|
|
431
|
+
/** The active user locale (e.g. 'en', 'ta', 'ar') */
|
|
432
|
+
locale: string;
|
|
433
|
+
/** Text direction for the active locale ('ltr' or 'rtl') */
|
|
434
|
+
dir: "ltr" | "rtl";
|
|
175
435
|
}
|
|
176
436
|
/**
|
|
177
437
|
* Seed data for your addon's tables, provided inside the addon's isolated schema.
|
|
@@ -236,5 +496,19 @@ declare function useAddonSDK(): AddonSDK;
|
|
|
236
496
|
declare function useIcons(): {
|
|
237
497
|
Icons: IconPack;
|
|
238
498
|
};
|
|
499
|
+
/**
|
|
500
|
+
* Get the version of the addon SDK. This is useful for checking compatibility with the host
|
|
501
|
+
* application.
|
|
502
|
+
*
|
|
503
|
+
* @example
|
|
504
|
+
* ;```tsx
|
|
505
|
+
* import { getAddonSdkVersion } from "@jchat/addon-sdk"
|
|
506
|
+
*
|
|
507
|
+
* console.log(getAddonSdkVersion())
|
|
508
|
+
* ```
|
|
509
|
+
*
|
|
510
|
+
* @returns
|
|
511
|
+
*/
|
|
512
|
+
declare function getAddonSdkVersion(): string;
|
|
239
513
|
//#endregion
|
|
240
|
-
export { AddonApi, AddonDatabase, AddonEventListener, AddonJsonBody,
|
|
514
|
+
export { AddonApi, AddonApp, AddonCacheClient, AddonDatabase, AddonEventListener, AddonFont, AddonJsonBody, AddonLocaleLoader, AddonLocaleValidationResult, type AddonManifest, type AddonPermissionDefinition, AddonProps, AddonRequestUser, AddonSDK, AddonSeedDocument, AddonServerContext, AddonServerEmitTarget, AddonServerFactory, AddonSettingsApi, AddonTranslationDictionary, AddonTranslationResources, AddonUser, HOST_LANGUAGES, HostLanguage, ICON_NAMES, type IconComponent, type IconName, type IconPack, type InfiniteData, LocaleContext, LocaleContextValue, MOUNT_POINTS, type MountPoint, type QueryKey, TranslateFunction, TranslatorOptions, UseAddonInfiniteQueryOptions, UseAddonMutationOptions, UseAddonQueryOptions, createTranslator, defineAddonManifest, defineAddonServer, getAddonSdkVersion, useAddonInfiniteQuery, useAddonMutation, useAddonQuery, useAddonSDK, useIcons, useLocale, useQueryClient, useTranslation, validateAddonLocales };
|