@jchat/addon-sdk 1.0.0-alpha.23 → 1.0.0-alpha.24

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 CHANGED
@@ -1,5 +1,31 @@
1
1
  # @jchat/addon-sdk
2
2
 
3
+ ## 1.0.0-alpha.24
4
+
5
+ ### Minor Changes
6
+
7
+ - Add required logo property to AddonManifest contract
8
+
9
+ - Add set operations, atomic rate limiter, and mock testing utilities
10
+
11
+ - Add distribution property to AddonManifest for commercial and free classification
12
+
13
+ - Export typed chat message, attachment, runtime log, and signing contracts
14
+
15
+ ### Patch Changes
16
+
17
+ - Export HOST_LOCALES in contracts module
18
+
19
+ - Update default service and documentation domains to jchatcloud.com
20
+
21
+ - Update package repository URLs to jarvistack organization
22
+
23
+ - Improve code formatting and style consistency
24
+
25
+ - Bumped `@jchat/*` dependencies:
26
+ - [`icon@1.0.0-alpha.22`](https://github.com/jchatcloud/jchat/releases/tag/icon@v1.0.0-alpha.22)
27
+ - [`ui@1.0.0-alpha.24`](https://github.com/jchatcloud/jchat/releases/tag/ui@v1.0.0-alpha.24)
28
+
3
29
  ## 1.0.0-alpha.23
4
30
 
5
31
  ### Patch Changes
@@ -7,14 +33,14 @@
7
33
  - Support newline normalization for addon signing private keys
8
34
 
9
35
  - Bumped `@jchat/*` dependencies:
10
- - [`ui@1.0.0-alpha.23`](https://github.com/mjjabarullah/jchat/releases/tag/ui@v1.0.0-alpha.23)
36
+ - [`ui@1.0.0-alpha.23`](https://github.com/jchatcloud/jchat/releases/tag/ui@v1.0.0-alpha.23)
11
37
 
12
38
  ## 1.0.0-alpha.22
13
39
 
14
40
  ### Minor Changes
15
41
 
16
42
  - Introduce composer preview slot, active addon coordination, and lifecycle optimizations
17
-
43
+
18
44
  - Add CHAT_COMPOSER_PREVIEW mount point for live composer rich card previews
19
45
  - Optimize message lifecycle pipeline with active addon caching and AbortSignal cancellation
20
46
  - Allow passing verifyEntitlement function directly to verifyAddonEntitlement
@@ -24,7 +50,7 @@
24
50
  ### Patch Changes
25
51
 
26
52
  - Bumped `@jchat/*` dependencies:
27
- - [`ui@1.0.0-alpha.22`](https://github.com/mjjabarullah/jchat/releases/tag/ui@v1.0.0-alpha.22)
53
+ - [`ui@1.0.0-alpha.22`](https://github.com/jchatcloud/jchat/releases/tag/ui@v1.0.0-alpha.22)
28
54
 
29
55
  ## 1.0.0-alpha.21
30
56
 
@@ -43,24 +69,24 @@
43
69
  - Enhance add-on CSS compilation to ensure consistent styling across dialogs and portals
44
70
 
45
71
  - Bumped `@jchat/*` dependencies:
46
- - [`icon@1.0.0-alpha.21`](https://github.com/mjjabarullah/jchat/releases/tag/icon@v1.0.0-alpha.21)
47
- - [`ui@1.0.0-alpha.21`](https://github.com/mjjabarullah/jchat/releases/tag/ui@v1.0.0-alpha.21)
72
+ - [`icon@1.0.0-alpha.21`](https://github.com/jchatcloud/jchat/releases/tag/icon@v1.0.0-alpha.21)
73
+ - [`ui@1.0.0-alpha.21`](https://github.com/jchatcloud/jchat/releases/tag/ui@v1.0.0-alpha.21)
48
74
 
49
75
  ## 1.0.0-alpha.20
50
76
 
51
77
  ### Patch Changes
52
78
 
53
79
  - Bumped `@jchat/*` dependencies:
54
- - [`icon@1.0.0-alpha.20`](https://github.com/mjjabarullah/jchat/releases/tag/icon@v1.0.0-alpha.20)
55
- - [`ui@1.0.0-alpha.20`](https://github.com/mjjabarullah/jchat/releases/tag/ui@v1.0.0-alpha.20)
80
+ - [`icon@1.0.0-alpha.20`](https://github.com/jchatcloud/jchat/releases/tag/icon@v1.0.0-alpha.20)
81
+ - [`ui@1.0.0-alpha.20`](https://github.com/jchatcloud/jchat/releases/tag/ui@v1.0.0-alpha.20)
56
82
 
57
83
  ## 1.0.0-alpha.19
58
84
 
59
85
  ### Patch Changes
60
86
 
61
87
  - Bumped `@jchat/*` dependencies:
62
- - [`icon@1.0.0-alpha.19`](https://github.com/mjjabarullah/jchat/releases/tag/icon@v1.0.0-alpha.19)
63
- - [`ui@1.0.0-alpha.19`](https://github.com/mjjabarullah/jchat/releases/tag/ui@v1.0.0-alpha.19)
88
+ - [`icon@1.0.0-alpha.19`](https://github.com/jchatcloud/jchat/releases/tag/icon@v1.0.0-alpha.19)
89
+ - [`ui@1.0.0-alpha.19`](https://github.com/jchatcloud/jchat/releases/tag/ui@v1.0.0-alpha.19)
64
90
 
65
91
  ## 1.0.0-alpha.17
66
92
 
package/README.md CHANGED
@@ -1,11 +1,11 @@
1
1
  # @jchat/addon-sdk
2
2
 
3
- Official TypeScript SDK for developing extensions, UI widgets, and server plugins for the [JChat](https://jchatplatform.com) communication platform.
3
+ Official TypeScript SDK for developing extensions, UI widgets, and server plugins for the [JChat](https://jchatcloud.com) communication platform.
4
4
 
5
5
  Provides typed interfaces, manifest contracts, Vite bundler plugins, and CLI development tooling for building sandboxed JChat addons.
6
6
 
7
7
  [![npm version](https://img.shields.io/npm/v/@jchat/addon-sdk.svg)](https://www.npmjs.com/package/@jchat/addon-sdk)
8
- [![License](https://img.shields.io/badge/license-Proprietary-blue.svg)](https://jchatplatform.com/terms)
8
+ [![License](https://img.shields.io/badge/license-Proprietary-blue.svg)](https://jchatcloud.com/terms)
9
9
 
10
10
  ---
11
11
 
@@ -401,10 +401,10 @@ bun run build
401
401
  1. Build your addon package: `bun run build`.
402
402
  2. Ensure all 20 translation files pass validation: `bun test`.
403
403
  3. Package your addon distribution archive.
404
- 4. Sign in to the **[JChat Store Developer Portal](https://jchatplatform.com/addons)** and submit your addon package for certified distribution.
404
+ 4. Sign in to the **[JChat Store Developer Portal](https://jchatcloud.com/addons)** and submit your addon package for certified distribution.
405
405
 
406
406
  ---
407
407
 
408
408
  ## License
409
409
 
410
- Proprietary — All rights reserved. See the [Commercial EULA & Licensing Terms](https://jchatplatform.com/terms) for full details.
410
+ Proprietary — All rights reserved. See the [Commercial EULA & Licensing Terms](https://jchatcloud.com/terms) for full details.
package/dist/cli.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { AddonManifest } from "./contracts.mjs";
1
+ import { l as AddonManifest } from "./contracts2.mjs";
2
2
  import { InlineConfig } from "vite";
3
3
  //#region src/cli.d.ts
4
4
  /** Options for resolving addon build configuration. */
package/dist/cli.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import { jchatAddon } from "./vite.mjs";
2
- import fs from "node:fs";
3
2
  import path from "node:path";
3
+ import fs from "node:fs";
4
4
  import tailwindcss from "@tailwindcss/vite";
5
5
  import { pathToFileURL } from "node:url";
6
6
  import { build, loadConfigFromFile, mergeConfig } from "vite";
@@ -1,144 +1,2 @@
1
- //#region src/contracts.d.ts
2
- /** Definition of a custom permission introduced by an addon. */
3
- export interface AddonPermissionDefinition {
4
- /** Permission identifier (recommended: `addon.<id>.<action>`) */
5
- name: string;
6
- /** Short, human-readable name for the permission */
7
- label: string;
8
- /** Detailed description of what the permission grants (shown in Rank editor) */
9
- description: string;
10
- }
11
- /** Valid locations where an addon can be rendered within the Jchat layout. */
12
- export declare const MOUNT_POINTS: {
13
- /** Top of the main chat column */
14
- readonly CHAT_HEADER: "chat-header";
15
- /** Below the main chat header, above messages */
16
- readonly CHAT_TOOLBAR: "chat-toolbar";
17
- /** The persistent right-side sidebar */
18
- readonly CHAT_SIDEBAR: "chat-sidebar";
19
- /** Top of the left sidebar (room list) */
20
- readonly SIDEBAR_TOP: "sidebar-top";
21
- /** Bottom of the left sidebar */
22
- readonly SIDEBAR_BOTTOM: "sidebar-bottom";
23
- /** Action list shown when clicking a user's avatar */
24
- readonly USER_POPOVER_ACTIONS: "user-popover-actions";
25
- /** Menu shown when right-clicking a message */
26
- readonly MESSAGE_CONTEXT_MENU: "message-context-menu";
27
- /** Floating overlay rendered on top of the message stream */
28
- readonly MESSAGE_FEED_OVERLAY: "message-feed-overlay";
29
- /** Main admin dashboard area */
30
- readonly ADMIN_PANEL: "admin-panel";
31
- /** User or site settings dashboard area */
32
- readonly SETTINGS_PANEL: "settings-panel";
33
- /** The 'plus' (+) menu in the message composer */
34
- readonly CHAT_PLUS_MENU: "chat-plus-menu";
35
- /** Custom tab in the emoji/gif selection panel */
36
- readonly EMOJI_PICKER_GIF_TAB: "emoji-picker-gif-tab";
37
- /** Custom tab in the emoji/sticker selection panel */
38
- readonly EMOJI_PICKER_STICKER_TAB: "emoji-picker-sticker-tab";
39
- /** Dedicated settings panel rendered in the Owner Dashboard */
40
- readonly OWNER_SETTINGS: "owner-settings";
41
- /** Dedicated settings panel rendered in the Site Admin Dashboard */
42
- readonly ADDON_SETTINGS: "addon-settings";
43
- /** Custom interactive card rendered inside an enriched chat message */
44
- readonly CHAT_MESSAGE_CARD: "chat-message-card";
45
- /** Custom badge, audio equalizer, or particle aura rendered on a chat bubble */
46
- readonly CHAT_MESSAGE_FLAIR: "chat-message-flair";
47
- /** Custom interactive preview card rendered in the message composer */
48
- readonly CHAT_COMPOSER_PREVIEW: "chat-composer-preview";
49
- };
50
- /** Union type of all valid addon mount points. */
51
- export type MountPoint = (typeof MOUNT_POINTS)[keyof typeof MOUNT_POINTS];
52
- /** Parameter specification for an addon slash command. */
53
- export interface AddonCommandParameter {
54
- name: string;
55
- description?: string;
56
- required?: boolean;
57
- }
58
- /** Declaration of an addon slash command available in the chat composer. */
59
- export interface AddonCommandDefinition {
60
- /** The command keyword without leading slash (e.g. 'yt', 'roll', 'poll') */
61
- name: string;
62
- /** Short summary of what the command does, displayed in autocomplete */
63
- description: string;
64
- /** Usage syntax displayed as a hint (e.g. '<query | url>') */
65
- syntax?: string;
66
- /** Parameters accepted by this command */
67
- parameters?: AddonCommandParameter[];
68
- }
69
- /**
70
- * The `manifest.json` schema required for every Jchat addon. This file describes how the addon is
71
- * identified and mounted.
72
- */
73
- export interface AddonManifest {
74
- /** Unique kebab-case addon identifier (must match the folder name) */
75
- id: string;
76
- /** Human-readable addon name */
77
- name: string;
78
- /** Semver version string (e.g. `1.2.0`) */
79
- version: string;
80
- /** Brief summary of what the addon does */
81
- description?: string;
82
- /** Author name, organization, or GitHub handle */
83
- author?: string;
84
- /** Support or documentation URL */
85
- homepage?: string;
86
- /** SPDX license identifier (e.g. `MIT`) */
87
- license?: string;
88
- /**
89
- * Supported language codes (e.g. ["ar", "de", "en", "es", "fr", "hi", "kn", "ml", "ta", "te",
90
- * "ur"])
91
- */
92
- locales?: string[];
93
- /** Default fallback language code (e.g. "en") */
94
- defaultLocale?: string;
95
- /** Host and SDK versions this directly executed plugin was built and tested against */
96
- compatibility: {
97
- /** Supported JChat versions as a semantic-version range, for example `^1.0.0` */
98
- jchat: string;
99
- /** Supported `@jchat/addon-sdk` versions as a semantic-version range */
100
- addonSdk: string;
101
- };
102
- /** Identity of the trusted publisher that signed this package. Required when migrations exist */
103
- publisher?: {
104
- id: string;
105
- };
106
- /** Signature over the canonical archive digest. Required when migrations exist */
107
- signature?: {
108
- algorithm: "rsa-sha256";
109
- digest: string;
110
- value: string;
111
- };
112
- /** Configuration for the React-based frontend bundle */
113
- frontend?: {
114
- /** Relative path to the pre-compiled ESM bundle (created by Vite) */
115
- bundle: string;
116
- /** List of UI locations where this addon should be active */
117
- mountPoints: MountPoint[];
118
- /** Optional path to a specific UI for the settings panel */
119
- settings?: string;
120
- /** Additional shared packages this addon imports from the host */
121
- externals?: string[];
122
- };
123
- /** Configuration for the server-side API bundle */
124
- server?: {
125
- /** Relative path to the pre-compiled server bundle */
126
- bundle?: string;
127
- /** List of SQL files to run (in order) to set up the addon's database tables */
128
- migrations?: string[];
129
- /** List of declarative JSON master-data files to apply once, in order */
130
- seeds?: string[];
131
- };
132
- /**
133
- * The minimum permission required to see or use this addon. If provided, the host gates
134
- * visibility in the UI and access to all server routes.
135
- */
136
- accessPermission?: string;
137
- /** Full list of all permissions used by this addon for informational purposes */
138
- permissions?: string[];
139
- /** Custom permissions that should be registered with the host application on installation */
140
- permissionDefinitions?: AddonPermissionDefinition[];
141
- /** Interactive slash commands exposed in the chat composer */
142
- commands?: AddonCommandDefinition[];
143
- }
144
- //#endregion
1
+ import { _ as HOST_LOCALES, a as AddonChatMessage, b as MountPoint, c as AddonDistribution, d as AddonRequestUser, f as AddonRuntimeLog, g as AddonUser, h as AddonRuntimeLogSource, i as AddonAttachment, l as AddonManifest, m as AddonRuntimeLogLevel, n as ActiveAddonFrontend, o as AddonCommandDefinition, p as AddonRuntimeLogFilter, r as AddonArchiveFile, s as AddonCommandParameter, t as ActiveAddon, u as AddonPermissionDefinition, v as HostLocale, y as MOUNT_POINTS } from "./contracts2.mjs";
2
+ export { ActiveAddon, ActiveAddonFrontend, AddonArchiveFile, AddonAttachment, AddonChatMessage, AddonCommandDefinition, AddonCommandParameter, AddonDistribution, AddonManifest, AddonPermissionDefinition, AddonRequestUser, AddonRuntimeLog, AddonRuntimeLogFilter, AddonRuntimeLogLevel, AddonRuntimeLogSource, AddonUser, HOST_LOCALES, HostLocale, MOUNT_POINTS, MountPoint };
@@ -38,5 +38,27 @@ const MOUNT_POINTS = {
38
38
  /** Custom interactive preview card rendered in the message composer */
39
39
  CHAT_COMPOSER_PREVIEW: "chat-composer-preview"
40
40
  };
41
+ const HOST_LOCALES = [
42
+ "ar",
43
+ "bg",
44
+ "hr",
45
+ "de",
46
+ "el",
47
+ "en",
48
+ "es",
49
+ "fr",
50
+ "he",
51
+ "hi",
52
+ "kn",
53
+ "ml",
54
+ "nl",
55
+ "pt",
56
+ "ro",
57
+ "ru",
58
+ "ta",
59
+ "te",
60
+ "tr",
61
+ "ur"
62
+ ];
41
63
  //#endregion
42
- export { MOUNT_POINTS };
64
+ export { HOST_LOCALES, MOUNT_POINTS };
@@ -0,0 +1,270 @@
1
+ //#region src/contracts.d.ts
2
+ /** Definition of a custom permission introduced by an addon. */
3
+ interface AddonPermissionDefinition {
4
+ /** Permission identifier (recommended: `addon.<id>.<action>`) */
5
+ name: string;
6
+ /** Short, human-readable name for the permission */
7
+ label: string;
8
+ /** Detailed description of what the permission grants (shown in Rank editor) */
9
+ description: string;
10
+ }
11
+ /** Valid locations where an addon can be rendered within the Jchat layout. */
12
+ declare const MOUNT_POINTS: {
13
+ /** Top of the main chat column */
14
+ readonly CHAT_HEADER: "chat-header";
15
+ /** Below the main chat header, above messages */
16
+ readonly CHAT_TOOLBAR: "chat-toolbar";
17
+ /** The persistent right-side sidebar */
18
+ readonly CHAT_SIDEBAR: "chat-sidebar";
19
+ /** Top of the left sidebar (room list) */
20
+ readonly SIDEBAR_TOP: "sidebar-top";
21
+ /** Bottom of the left sidebar */
22
+ readonly SIDEBAR_BOTTOM: "sidebar-bottom";
23
+ /** Action list shown when clicking a user's avatar */
24
+ readonly USER_POPOVER_ACTIONS: "user-popover-actions";
25
+ /** Menu shown when right-clicking a message */
26
+ readonly MESSAGE_CONTEXT_MENU: "message-context-menu";
27
+ /** Floating overlay rendered on top of the message stream */
28
+ readonly MESSAGE_FEED_OVERLAY: "message-feed-overlay";
29
+ /** Main admin dashboard area */
30
+ readonly ADMIN_PANEL: "admin-panel";
31
+ /** User or site settings dashboard area */
32
+ readonly SETTINGS_PANEL: "settings-panel";
33
+ /** The 'plus' (+) menu in the message composer */
34
+ readonly CHAT_PLUS_MENU: "chat-plus-menu";
35
+ /** Custom tab in the emoji/gif selection panel */
36
+ readonly EMOJI_PICKER_GIF_TAB: "emoji-picker-gif-tab";
37
+ /** Custom tab in the emoji/sticker selection panel */
38
+ readonly EMOJI_PICKER_STICKER_TAB: "emoji-picker-sticker-tab";
39
+ /** Dedicated settings panel rendered in the Owner Dashboard */
40
+ readonly OWNER_SETTINGS: "owner-settings";
41
+ /** Dedicated settings panel rendered in the Site Admin Dashboard */
42
+ readonly ADDON_SETTINGS: "addon-settings";
43
+ /** Custom interactive card rendered inside an enriched chat message */
44
+ readonly CHAT_MESSAGE_CARD: "chat-message-card";
45
+ /** Custom badge, audio equalizer, or particle aura rendered on a chat bubble */
46
+ readonly CHAT_MESSAGE_FLAIR: "chat-message-flair";
47
+ /** Custom interactive preview card rendered in the message composer */
48
+ readonly CHAT_COMPOSER_PREVIEW: "chat-composer-preview";
49
+ };
50
+ /** Union type of all valid addon mount points. */
51
+ type MountPoint = (typeof MOUNT_POINTS)[keyof typeof MOUNT_POINTS];
52
+ /** Parameter specification for an addon slash command. */
53
+ interface AddonCommandParameter {
54
+ name: string;
55
+ description?: string;
56
+ required?: boolean;
57
+ }
58
+ /** Declaration of an addon slash command available in the chat composer. */
59
+ interface AddonCommandDefinition {
60
+ /** The command keyword without leading slash (e.g. 'yt', 'roll', 'poll') */
61
+ name: string;
62
+ /** Short summary of what the command does, displayed in autocomplete */
63
+ description: string;
64
+ /** Usage syntax displayed as a hint (e.g. '<query | url>') */
65
+ syntax?: string;
66
+ /** Parameters accepted by this command */
67
+ parameters?: AddonCommandParameter[];
68
+ }
69
+ /** Distribution model for an addon package. Defaults to `'free'` if omitted. */
70
+ type AddonDistribution = "free" | "commercial";
71
+ /**
72
+ * The `manifest.json` schema required for every Jchat addon. This file describes how the addon is
73
+ * identified and mounted.
74
+ */
75
+ interface AddonManifest {
76
+ /** Unique kebab-case addon identifier (must match the folder name) */
77
+ id: string;
78
+ /** Human-readable addon name */
79
+ name: string;
80
+ /** Semver version string (e.g. `1.2.0`) */
81
+ version: string;
82
+ /**
83
+ * Relative path to the addon's logo image asset (e.g. `./assets/logo.svg`, `./assets/logo.png`,
84
+ * or `./assets/logo.webp`). Recommended square 1:1 aspect ratio, max 512 KB.
85
+ */
86
+ logo: string;
87
+ /** Brief summary of what the addon does */
88
+ description?: string;
89
+ /** Author name, organization, or GitHub handle */
90
+ author?: string;
91
+ /** Support or documentation URL */
92
+ homepage?: string;
93
+ /** SPDX license identifier (e.g. `MIT`) */
94
+ license?: string;
95
+ /**
96
+ * Distribution model: `'free'` (default) for open/free addons, or `'commercial'` for paid addons
97
+ * requiring purchase entitlement verification.
98
+ */
99
+ distribution?: AddonDistribution;
100
+ /**
101
+ * Supported language codes (e.g. ["ar", "de", "en", "es", "fr", "hi", "kn", "ml", "ta", "te",
102
+ * "ur"])
103
+ */
104
+ locales?: string[];
105
+ /** Default fallback language code (e.g. "en") */
106
+ defaultLocale?: string;
107
+ /** Host and SDK versions this directly executed plugin was built and tested against */
108
+ compatibility: {
109
+ /** Supported JChat versions as a semantic-version range, for example `^1.0.0` */
110
+ jchat: string;
111
+ /** Supported `@jchat/addon-sdk` versions as a semantic-version range */
112
+ addonSdk: string;
113
+ };
114
+ /** Identity of the trusted publisher that signed this package. Required when migrations exist */
115
+ publisher?: {
116
+ id: string;
117
+ };
118
+ /** Signature over the canonical archive digest. Required when migrations exist */
119
+ signature?: {
120
+ algorithm: "rsa-sha256";
121
+ digest: string;
122
+ value: string;
123
+ };
124
+ /** Configuration for the React-based frontend bundle */
125
+ frontend?: {
126
+ /** Relative path to the pre-compiled ESM bundle (created by Vite) */
127
+ bundle: string;
128
+ /** List of UI locations where this addon should be active */
129
+ mountPoints: MountPoint[];
130
+ /** Optional path to a specific UI for the settings panel */
131
+ settings?: string;
132
+ /** Additional shared packages this addon imports from the host */
133
+ externals?: string[];
134
+ };
135
+ /** Configuration for the server-side API bundle */
136
+ server?: {
137
+ /** Relative path to the pre-compiled server bundle */
138
+ bundle?: string;
139
+ /** List of SQL files to run (in order) to set up the addon's database tables */
140
+ migrations?: string[];
141
+ /** List of declarative JSON master-data files to apply once, in order */
142
+ seeds?: string[];
143
+ };
144
+ /**
145
+ * The minimum permission required to see or use this addon. If provided, the host gates
146
+ * visibility in the UI and access to all server routes.
147
+ */
148
+ accessPermission?: string;
149
+ /** Full list of all permissions used by this addon for informational purposes */
150
+ permissions?: string[];
151
+ /** Custom permissions that should be registered with the host application on installation */
152
+ permissionDefinitions?: AddonPermissionDefinition[];
153
+ /** Interactive slash commands exposed in the chat composer */
154
+ commands?: AddonCommandDefinition[];
155
+ }
156
+ declare const HOST_LOCALES: readonly ["ar", "bg", "hr", "de", "el", "en", "es", "fr", "he", "hi", "kn", "ml", "nl", "pt", "ro", "ru", "ta", "te", "tr", "ur"];
157
+ type HostLocale = (typeof HOST_LOCALES)[number];
158
+ /** Minimal user profile accessible to addon backends and UI slots. */
159
+ interface AddonUser {
160
+ /** Unique user identifier */
161
+ id: string;
162
+ /** Display name */
163
+ name: string;
164
+ /** Full display name */
165
+ displayName: string;
166
+ /** Full URL to the user's avatar image */
167
+ avatar: string;
168
+ /** User's gender identifier */
169
+ gender: string;
170
+ }
171
+ /** Authenticated request user context passed to addon server routes and message pipeline hooks. */
172
+ interface AddonRequestUser {
173
+ id: string;
174
+ permissions?: string[];
175
+ siteId?: string;
176
+ }
177
+ /** Standard attachment contract for chat messages and addon file attachments. */
178
+ interface AddonAttachment {
179
+ url: string;
180
+ name: string;
181
+ type: string;
182
+ id?: string;
183
+ isUploading?: boolean;
184
+ }
185
+ /** Domain-agnostic message contract passed to addon slots (CHAT_MESSAGE_CARD, CHAT_MESSAGE_FLAIR). */
186
+ interface AddonChatMessage {
187
+ id: string;
188
+ roomId?: string;
189
+ senderId?: string;
190
+ sender?: Partial<AddonUser> & {
191
+ id: string;
192
+ name: string;
193
+ isOwner?: boolean;
194
+ isBot?: boolean;
195
+ rankName?: string;
196
+ };
197
+ content?: unknown;
198
+ attachments?: AddonAttachment[] | null;
199
+ metadata?: Record<string, unknown> | null;
200
+ createdAt?: string | Date;
201
+ }
202
+ /** Severity level classification for runtime addon logging. */
203
+ type AddonRuntimeLogLevel = "error" | "info" | "warn";
204
+ /** Operational lifecycle origin of a runtime log entry. */
205
+ type AddonRuntimeLogSource = "request" | "startup";
206
+ /** A diagnostic runtime log entry recorded during addon server startup or HTTP request execution. */
207
+ interface AddonRuntimeLog {
208
+ /** Unique auto-incrementing log entry sequence identifier */
209
+ id: number;
210
+ /** ISO-8601 UTC timestamp when the event was recorded */
211
+ timestamp: string;
212
+ /** Canonical identifier of the emitting addon */
213
+ addonId: string;
214
+ /** Log severity classification */
215
+ level: AddonRuntimeLogLevel;
216
+ /** Lifecycle stage where the log originated */
217
+ source: AddonRuntimeLogSource;
218
+ /** Descriptive log message or error description */
219
+ message: string;
220
+ /** HTTP method if the log was generated during request execution */
221
+ method?: string;
222
+ /** Request pathname if the log was generated during request execution */
223
+ path?: string;
224
+ /** HTTP response status code if the log was generated during request execution */
225
+ status?: number;
226
+ }
227
+ /** Query parameters for filtering and bounding addon developer runtime logs. */
228
+ interface AddonRuntimeLogFilter {
229
+ /** Filter logs by a specific addon identifier */
230
+ addonId?: string;
231
+ /** Filter logs by severity level */
232
+ level?: AddonRuntimeLogLevel;
233
+ /** Maximum number of recent log records to return */
234
+ limit?: number;
235
+ }
236
+ /** Frontend mounting and bundle distribution metadata for an active addon. */
237
+ interface ActiveAddonFrontend extends Pick<NonNullable<AddonManifest["frontend"]>, "mountPoints"> {
238
+ /** Relative web-accessible path to the compiled frontend bundle */
239
+ bundlePath?: string;
240
+ }
241
+ /** Public host runtime representation of an installed, active addon. */
242
+ interface ActiveAddon extends Pick<AddonManifest, "id" | "name" | "version" | "description" | "author" | "homepage" | "license" | "accessPermission" | "permissions" | "commands"> {
243
+ /** Whether the addon is globally activated on the host platform */
244
+ enabled: boolean;
245
+ /** Relative path to the compiled frontend bundle file */
246
+ bundlePath?: string;
247
+ /** Relative path or URL to the addon logo asset */
248
+ logoPath?: string | null;
249
+ /** Frontend mounting points and distribution information */
250
+ frontend?: ActiveAddonFrontend;
251
+ /** Timestamp when the addon was installed */
252
+ createdAt?: string | Date;
253
+ /** Timestamp when the addon configuration or files were last modified */
254
+ updatedAt?: string | Date;
255
+ }
256
+ /**
257
+ * In-memory representation of an archive file entry used in canonical digest creation and package
258
+ * verification.
259
+ */
260
+ interface AddonArchiveFile {
261
+ /**
262
+ * Canonical relative path inside the addon archive (e.g., 'manifest.json',
263
+ * 'migrations/001_init.sql')
264
+ */
265
+ path: string;
266
+ /** Raw binary contents of the archive file */
267
+ contents: Buffer;
268
+ }
269
+ //#endregion
270
+ export { HOST_LOCALES as _, AddonChatMessage as a, MountPoint as b, AddonDistribution as c, AddonRequestUser as d, AddonRuntimeLog as f, AddonUser as g, AddonRuntimeLogSource as h, AddonAttachment as i, AddonManifest as l, AddonRuntimeLogLevel as m, ActiveAddonFrontend as n, AddonCommandDefinition as o, AddonRuntimeLogFilter as p, AddonArchiveFile as r, AddonCommandParameter as s, ActiveAddon as t, AddonPermissionDefinition as u, HostLocale as v, MOUNT_POINTS as y };
@@ -1,4 +1,4 @@
1
- import { AddonManifest } from "./contracts.mjs";
1
+ import { l as AddonManifest } from "./contracts2.mjs";
2
2
  //#region src/diagnostics.d.ts
3
3
  /**
4
4
  * Diagnostic result status indicator for addon validation checks. - `"pass"`: The diagnostic check
@@ -1,5 +1,5 @@
1
- import * as fs$1 from "node:fs";
2
1
  import * as path$1 from "node:path";
2
+ import * as fs$1 from "node:fs";
3
3
  //#region src/diagnostics.ts
4
4
  /**
5
5
  * Constructs a semantic version compatibility range covering all minor/patch versions of the
@@ -1,6 +1,7 @@
1
- import { t as HOST_LANGUAGES } from "./i18n.mjs";
2
- import * as fs$1 from "node:fs";
1
+ import { HOST_LOCALES } from "./contracts.mjs";
2
+ import "./i18n.mjs";
3
3
  import * as path$1 from "node:path";
4
+ import * as fs$1 from "node:fs";
4
5
  //#region src/i18n-validator.ts
5
6
  function extractNestedKeys(obj, prefix = "") {
6
7
  if (!obj || typeof obj !== "object") return [];
@@ -52,7 +53,7 @@ function validateAddonLocales(dirPath, domainName = "Addon") {
52
53
  };
53
54
  }
54
55
  const enKeys = new Set(extractNestedKeys(enContent));
55
- for (const lang of HOST_LANGUAGES) {
56
+ for (const lang of HOST_LOCALES) {
56
57
  if (lang === "en") continue;
57
58
  const langFileName = `${lang}.json`;
58
59
  const langPath = path$1.join(dirPath, langFileName);
package/dist/i18n.mjs CHANGED
@@ -15,28 +15,6 @@ function interpolate(text, params) {
15
15
  return key in params ? String(params[key]) : match;
16
16
  });
17
17
  }
18
- const HOST_LANGUAGES = [
19
- "ar",
20
- "bg",
21
- "hr",
22
- "de",
23
- "el",
24
- "en",
25
- "es",
26
- "fr",
27
- "he",
28
- "hi",
29
- "kn",
30
- "ml",
31
- "nl",
32
- "pt",
33
- "ro",
34
- "ru",
35
- "ta",
36
- "te",
37
- "tr",
38
- "ur"
39
- ];
40
18
  const LocaleContext = createContext({
41
19
  locale: "en",
42
20
  dir: "ltr"
@@ -188,4 +166,4 @@ function useTranslation(resourcesOrLoader = {}, fallbackLocale = "en") {
188
166
  };
189
167
  }
190
168
  //#endregion
191
- export { useTranslation as a, useLocale as i, LocaleContext as n, createTranslator as r, HOST_LANGUAGES as t };
169
+ export { useTranslation as i, createTranslator as n, useLocale as r, LocaleContext as t };
package/dist/index.mjs CHANGED
@@ -1,8 +1,10 @@
1
- import { MOUNT_POINTS } from "./contracts.mjs";
2
- import { a as useTranslation, i as useLocale, n as LocaleContext, r as createTranslator, t as HOST_LANGUAGES } from "./i18n.mjs";
1
+ import { HOST_LOCALES, MOUNT_POINTS } from "./contracts.mjs";
2
+ import { i as useTranslation, n as createTranslator, r as useLocale, t as LocaleContext } from "./i18n.mjs";
3
+ import { ADDON_ID_PATTERN, COMPATIBILITY_RANGE_PATTERN, MAX_ADDON_ARCHIVE_BYTES, assertSafeRelativePath, canonicalManifest, createCanonicalAddonDigest, isCompatibilityRange, sortValue, validateAddonManifest } from "./signing.mjs";
4
+ import { createMockAddonServerContext } from "./testing.mjs";
3
5
  import { useInfiniteQuery, useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
4
6
  //#region package.json
5
- var version = "1.0.0-alpha.22";
7
+ var version = "1.0.0-alpha.23";
6
8
  //#endregion
7
9
  //#region src/query.ts
8
10
  /**
@@ -95,6 +97,24 @@ function useAddonMutation(options) {
95
97
  }
96
98
  //#endregion
97
99
  //#region src/index.ts
100
+ /**
101
+ * @jchat/addon-sdk
102
+ *
103
+ * Lightweight SDK for Jchat addon developers. Provides TypeScript types for addon props, manifest
104
+ * schema, and host API access. This package is always EXTERNALISED from addon bundles — the host
105
+ * app provides the runtime implementation.
106
+ */
107
+ /** Standard error thrown or handled when an addon message pipeline hook aborts sending. */
108
+ var AddonPipelineAbortError = class extends Error {
109
+ code;
110
+ reason;
111
+ constructor(error) {
112
+ super(error.reason || error.code);
113
+ this.name = "AddonPipelineAbortError";
114
+ this.code = error.code;
115
+ this.reason = error.reason;
116
+ }
117
+ };
98
118
  /** Define a manifest with full validation-friendly typing while preserving literal values. */
99
119
  function defineAddonManifest(manifest) {
100
120
  return manifest;
@@ -173,4 +193,4 @@ function verifyAddonEntitlement(addonId, target) {
173
193
  return true;
174
194
  }
175
195
  //#endregion
176
- export { HOST_LANGUAGES, LocaleContext, MOUNT_POINTS, createTranslator, defineAddonManifest, defineAddonServer, getAddonSdkVersion, useAddonInfiniteQuery, useAddonMutation, useAddonQuery, useAddonSDK, useLocale, useQueryClient, useTranslation, verifyAddonEntitlement };
196
+ export { ADDON_ID_PATTERN, AddonPipelineAbortError, COMPATIBILITY_RANGE_PATTERN, HOST_LOCALES, LocaleContext, MAX_ADDON_ARCHIVE_BYTES, MOUNT_POINTS, assertSafeRelativePath, canonicalManifest, createCanonicalAddonDigest, createMockAddonServerContext, createTranslator, defineAddonManifest, defineAddonServer, getAddonSdkVersion, isCompatibilityRange, sortValue, useAddonInfiniteQuery, useAddonMutation, useAddonQuery, useAddonSDK, useLocale, useQueryClient, useTranslation, validateAddonManifest, verifyAddonEntitlement };
package/dist/index2.d.mts CHANGED
@@ -1,11 +1,11 @@
1
1
  import { i as IconPack, n as IconComponent, r as IconName } from "./types.mjs";
2
- import { AddonCommandDefinition, AddonCommandParameter, AddonManifest, AddonPermissionDefinition, MOUNT_POINTS, MountPoint } from "./contracts.mjs";
2
+ import { _ as HOST_LOCALES, a as AddonChatMessage, b as MountPoint, c as AddonDistribution, d as AddonRequestUser, f as AddonRuntimeLog, g as AddonUser, h as AddonRuntimeLogSource, i as AddonAttachment, l as AddonManifest, m as AddonRuntimeLogLevel, n as ActiveAddonFrontend, o as AddonCommandDefinition, p as AddonRuntimeLogFilter, r as AddonArchiveFile, s as AddonCommandParameter, t as ActiveAddon, u as AddonPermissionDefinition, v as HostLocale, y as MOUNT_POINTS } from "./contracts2.mjs";
3
+ import { a as canonicalManifest, c as sortValue, i as assertSafeRelativePath, l as validateAddonManifest, n as COMPATIBILITY_RANGE_PATTERN, o as createCanonicalAddonDigest, r as MAX_ADDON_ARCHIVE_BYTES, s as isCompatibilityRange, t as ADDON_ID_PATTERN } from "./signing2.mjs";
3
4
  import { InfiniteData, MutationFunction, QueryFunction, QueryFunctionContext, QueryKey, UndefinedInitialDataInfiniteOptions, UndefinedInitialDataOptions, UseInfiniteQueryResult, UseMutationOptions, UseMutationResult, UseQueryResult, useQueryClient } from "@tanstack/react-query";
4
5
  import { Elysia, t } from "elysia";
5
6
  import { ColumnType, Generated, Insertable, JSONColumnType, Kysely, Selectable, Updateable } from "kysely";
6
7
  //#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];
8
+ export type HostLanguage = HostLocale;
9
9
  export type AddonTranslationDictionary = Record<string, unknown>;
10
10
  export type AddonTranslationResources = Record<string, AddonTranslationDictionary>;
11
11
  export type AddonLocaleLoader = (locale: string) => Promise<AddonTranslationDictionary> | AddonTranslationDictionary;
@@ -151,6 +151,30 @@ export declare function useAddonInfiniteQuery<TData = unknown, TError = Error, T
151
151
  */
152
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
153
  //#endregion
154
+ //#region src/testing.d.ts
155
+ export interface MockAddonServerContextOptions {
156
+ siteId?: string;
157
+ config?: Record<string, unknown>;
158
+ user?: Partial<AddonUser>;
159
+ isEntitled?: boolean;
160
+ }
161
+ export interface MockAddonContextResult {
162
+ context: AddonServerContext;
163
+ emittedEvents: Array<{
164
+ event: string;
165
+ payload: unknown;
166
+ target?: AddonServerEmitTarget;
167
+ }>;
168
+ registeredHooks: AddonPipelineHooks[];
169
+ cacheEntries: Map<string, unknown>;
170
+ cacheSets: Map<string, Set<string>>;
171
+ }
172
+ /**
173
+ * Creates an in-memory, zero-dependency AddonServerContext mock for testing addon server factories
174
+ * and pipeline hooks offline in CI without requiring live databases or Redis.
175
+ */
176
+ export declare function createMockAddonServerContext(addonId: string, options?: MockAddonServerContextOptions): MockAddonContextResult;
177
+ //#endregion
154
178
  //#region src/index.d.ts
155
179
  export type AddonJsonBody = Record<string, unknown> | unknown[] | null;
156
180
  /**
@@ -160,23 +184,14 @@ export type AddonJsonBody = Record<string, unknown> | unknown[] | null;
160
184
  * schema, and host API access. This package is always EXTERNALISED from addon bundles — the host
161
185
  * app provides the runtime implementation.
162
186
  */
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;
187
+ /** Standard error thrown or handled when an addon message pipeline hook aborts sending. */
188
+ export declare class AddonPipelineAbortError extends Error {
189
+ readonly code: string;
190
+ readonly reason?: string;
191
+ constructor(error: {
192
+ code: string;
193
+ reason?: string;
194
+ });
180
195
  }
181
196
  /**
182
197
  * Typed, schema-scoped database facade for an addon. The host deliberately omits raw execution,
@@ -200,6 +215,32 @@ export interface AddonCacheClient {
200
215
  expire: (key: string, ttlSeconds: number) => Promise<boolean>;
201
216
  /** Get the remaining time-to-live of a key in seconds (-2 if key does not exist, -1 if no TTL) */
202
217
  ttl: (key: string) => Promise<number>;
218
+ /** Add a member to a set stored at key */
219
+ setAdd: (key: string, member: string) => Promise<number>;
220
+ /** Remove a member from a set stored at key */
221
+ setRemove: (key: string, member: string) => Promise<number>;
222
+ /** Retrieve all members of a set stored at key */
223
+ setMembers: (key: string) => Promise<string[]>;
224
+ }
225
+ export interface AddonRateLimitOptions {
226
+ /** Maximum number of allowed requests within the sliding window */
227
+ limit: number;
228
+ /** Time window duration in milliseconds */
229
+ windowMs: number;
230
+ }
231
+ /**
232
+ * High-performance sliding-window rate limiter scoped to this addon. Keys are automatically
233
+ * prefixed by the host as `addon:<addonId>:ratelimit:<key>`.
234
+ */
235
+ export interface AddonRateLimiter {
236
+ /**
237
+ * Consume a rate limit token using an atomic sliding window.
238
+ *
239
+ * @param key - Identifier within the addon (e.g. `user:${userId}:audio`, `command:${cmd}`)
240
+ * @param options - Limit and window duration in milliseconds
241
+ * @returns `true` if within the rate limit, `false` if limit exceeded
242
+ */
243
+ consume: (key: string, options: AddonRateLimitOptions) => Promise<boolean>;
203
244
  }
204
245
  export interface AddonServerEmitTarget {
205
246
  /** Target a specific site (defaults to the request user's siteId) */
@@ -213,7 +254,7 @@ export interface AddonMessageMutableData {
213
254
  /** Message content (can be Slate JSON structure or string) */
214
255
  content: unknown;
215
256
  /** Optional message attachments */
216
- attachments?: unknown[] | null;
257
+ attachments?: AddonAttachment[] | unknown[] | null;
217
258
  /**
218
259
  * Namespaced metadata bucket for this addon. Keyed as `metadata["addon:<addonId>"]`. Storing data
219
260
  * here automatically attaches it to the message broadcast and database record.
@@ -252,7 +293,7 @@ export interface AddonMessageAfterSaveContext {
252
293
  message: {
253
294
  id: string;
254
295
  content: unknown;
255
- attachments?: unknown[] | null;
296
+ attachments?: AddonAttachment[] | unknown[] | null;
256
297
  metadata?: Record<string, unknown> | null;
257
298
  type: string;
258
299
  createdAt: Date | string;
@@ -320,6 +361,8 @@ export interface AddonServerContext<TDatabase = Record<string, Record<string, un
320
361
  pipeline?: {
321
362
  register: (hooks: AddonPipelineHooks) => void;
322
363
  };
364
+ /** An atomic sliding-window rate limiter scoped to your addon (`addon:<addonId>:ratelimit:<key>`). */
365
+ rateLimiter?: AddonRateLimiter;
323
366
  /**
324
367
  * Cryptographically verify that this addon instance holds a valid host license entitlement.
325
368
  * Commercial paid addons should invoke this or call `verifyAddonEntitlement(addonId, context)`.
@@ -389,7 +432,7 @@ export interface AddonProps {
389
432
  /** Text direction for the current locale */
390
433
  dir?: "ltr" | "rtl";
391
434
  /** The message object when rendered inside 'chat-message-card' or 'chat-message-flair' */
392
- message?: unknown;
435
+ message?: AddonChatMessage | unknown;
393
436
  /** The addon's specific payload extracted from message.metadata */
394
437
  addonData?: unknown;
395
438
  /** List of active addon IDs enabled on the current site */
@@ -467,7 +510,7 @@ export interface AddonEditorApi {
467
510
  * Upload and attach a file to the current message without inserting it inline. Users will see the
468
511
  * file in the attachment bar below the editor.
469
512
  */
470
- addAttachment: (attachment: {
513
+ addAttachment: (attachment: AddonAttachment | {
471
514
  url: string;
472
515
  name: string;
473
516
  type: string;
@@ -610,4 +653,4 @@ export declare function verifyAddonEntitlement(addonId: string, target?: ((expec
610
653
  verifyEntitlement?: (expectedAddonId?: string) => boolean;
611
654
  }): boolean;
612
655
  //#endregion
613
- export { type AddonCommandDefinition, type AddonCommandParameter, 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 };
656
+ export { ADDON_ID_PATTERN, type ActiveAddon, type ActiveAddonFrontend, type AddonArchiveFile, type AddonAttachment, type AddonChatMessage, type AddonCommandDefinition, type AddonCommandParameter, type AddonDistribution, type AddonManifest, type AddonPermissionDefinition, type AddonRequestUser, type AddonRuntimeLog, type AddonRuntimeLogFilter, type AddonRuntimeLogLevel, type AddonRuntimeLogSource, type AddonUser, COMPATIBILITY_RANGE_PATTERN, type ColumnType, type Generated, HOST_LOCALES, type IconComponent, type IconName, type IconPack, type InfiniteData, type Insertable, type JSONColumnType, MAX_ADDON_ARCHIVE_BYTES, MOUNT_POINTS, type MountPoint, type QueryKey, type Selectable, type Updateable, assertSafeRelativePath, canonicalManifest, createCanonicalAddonDigest, isCompatibilityRange, sortValue, useQueryClient, validateAddonManifest };
@@ -0,0 +1,2 @@
1
+ import { a as canonicalManifest, c as sortValue, i as assertSafeRelativePath, l as validateAddonManifest, n as COMPATIBILITY_RANGE_PATTERN, o as createCanonicalAddonDigest, r as MAX_ADDON_ARCHIVE_BYTES, s as isCompatibilityRange, t as ADDON_ID_PATTERN } from "./signing2.mjs";
2
+ export { ADDON_ID_PATTERN, COMPATIBILITY_RANGE_PATTERN, MAX_ADDON_ARCHIVE_BYTES, assertSafeRelativePath, canonicalManifest, createCanonicalAddonDigest, isCompatibilityRange, sortValue, validateAddonManifest };
@@ -0,0 +1,73 @@
1
+ import crypto from "node:crypto";
2
+ import * as path$1 from "node:path";
3
+ //#region src/signing.ts
4
+ const MAX_ADDON_ARCHIVE_BYTES = 26214400;
5
+ const ADDON_ID_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
6
+ const COMPATIBILITY_RANGE_PATTERN = /^[0-9A-Za-z*^~<>=|.\-\s]+$/;
7
+ function assertSafeRelativePath(value, label) {
8
+ if (!value || path$1.isAbsolute(value) || value.includes("\\") || value.split("/").includes("..")) throw new Error(`${label} must be a safe relative path`);
9
+ }
10
+ function isCompatibilityRange(value) {
11
+ return typeof value === "string" && value.length > 0 && value.length <= 64 && COMPATIBILITY_RANGE_PATTERN.test(value);
12
+ }
13
+ function sortValue(value) {
14
+ if (Array.isArray(value)) return value.map(sortValue);
15
+ if (value && typeof value === "object") return Object.fromEntries(Object.entries(value).toSorted(([left], [right]) => left.localeCompare(right)).map(([key, child]) => [key, sortValue(child)]));
16
+ return value;
17
+ }
18
+ function canonicalManifest(manifest) {
19
+ const copy = structuredClone(manifest);
20
+ if (copy.signature) {
21
+ copy.signature.digest = "";
22
+ copy.signature.value = "";
23
+ }
24
+ return JSON.stringify(sortValue(copy));
25
+ }
26
+ function createCanonicalAddonDigest(manifest, files) {
27
+ const hash = crypto.createHash("sha256");
28
+ const sortedFiles = [...files].toSorted((left, right) => left.path.localeCompare(right.path));
29
+ for (const file of sortedFiles) {
30
+ hash.update(file.path);
31
+ hash.update("\0");
32
+ hash.update(file.path === "manifest.json" ? canonicalManifest(manifest) : file.contents);
33
+ hash.update("\0");
34
+ }
35
+ return hash.digest("hex");
36
+ }
37
+ function validateAddonManifest(manifest) {
38
+ if (!manifest.id || manifest.id.length > 40 || !ADDON_ID_PATTERN.test(manifest.id)) throw new Error("Manifest id must contain only lowercase letters, numbers, and hyphens");
39
+ if (!manifest.name || manifest.name.length > 120) throw new Error("Manifest name is invalid");
40
+ if (!manifest.version || String(manifest.version).length > 64) throw new Error("Manifest version is invalid");
41
+ if (!isCompatibilityRange(manifest.compatibility?.jchat) || !isCompatibilityRange(manifest.compatibility?.addonSdk)) throw new Error("Manifest must declare valid JChat and addon SDK compatibility ranges");
42
+ if (manifest.distribution !== void 0 && manifest.distribution !== "free" && manifest.distribution !== "commercial") throw new Error("Manifest distribution must be either 'free' or 'commercial'");
43
+ if (!manifest.logo || typeof manifest.logo !== "string" || !manifest.logo.trim()) throw new Error("Manifest logo is required");
44
+ const cleanLogo = manifest.logo.replace(/^\.\//, "");
45
+ assertSafeRelativePath(cleanLogo, "Logo path");
46
+ const logoExt = path$1.extname(cleanLogo).toLowerCase();
47
+ if (![
48
+ ".svg",
49
+ ".png",
50
+ ".webp"
51
+ ].includes(logoExt)) throw new Error("Manifest logo must be an SVG, PNG, or WebP image file");
52
+ const references = [manifest.frontend?.bundle, manifest.server?.bundle].filter((value) => Boolean(value));
53
+ for (const reference of references) assertSafeRelativePath(reference, "Bundle path");
54
+ for (const migration of manifest.server?.migrations ?? []) {
55
+ assertSafeRelativePath(migration, "Migration path");
56
+ if (!migration.startsWith("migrations/") || !migration.endsWith(".sql")) throw new Error("Migration paths must point to migrations/*.sql");
57
+ }
58
+ for (const seed of manifest.server?.seeds ?? []) {
59
+ assertSafeRelativePath(seed, "Seed path");
60
+ if (!seed.startsWith("seeds/") || !seed.endsWith(".json")) throw new Error("Seed paths must point to seeds/*.json");
61
+ }
62
+ const databaseFiles = [...manifest.server?.migrations ?? [], ...manifest.server?.seeds ?? []];
63
+ if (new Set(databaseFiles).size !== databaseFiles.length) throw new Error("Manifest database file paths must be unique");
64
+ const addonPermPrefix = `addon.${manifest.id}.`;
65
+ const declaredPerms = [
66
+ ...manifest.accessPermission ? [manifest.accessPermission] : [],
67
+ ...manifest.permissions ?? [],
68
+ ...manifest.permissionDefinitions?.map((d) => d.name) ?? []
69
+ ];
70
+ for (const perm of declaredPerms) if (!perm.startsWith(addonPermPrefix)) throw new Error(`Addon permission '${perm}' must start with '${addonPermPrefix}' prefix to prevent permission collisions`);
71
+ }
72
+ //#endregion
73
+ export { ADDON_ID_PATTERN, COMPATIBILITY_RANGE_PATTERN, MAX_ADDON_ARCHIVE_BYTES, assertSafeRelativePath, canonicalManifest, createCanonicalAddonDigest, isCompatibilityRange, sortValue, validateAddonManifest };
@@ -0,0 +1,13 @@
1
+ import { l as AddonManifest, r as AddonArchiveFile } from "./contracts2.mjs";
2
+ //#region src/signing.d.ts
3
+ declare const MAX_ADDON_ARCHIVE_BYTES: number;
4
+ declare const ADDON_ID_PATTERN: RegExp;
5
+ declare const COMPATIBILITY_RANGE_PATTERN: RegExp;
6
+ declare function assertSafeRelativePath(value: string, label: string): void;
7
+ declare function isCompatibilityRange(value: unknown): value is string;
8
+ declare function sortValue(value: unknown): unknown;
9
+ declare function canonicalManifest(manifest: AddonManifest): string;
10
+ declare function createCanonicalAddonDigest(manifest: AddonManifest, files: readonly AddonArchiveFile[]): string;
11
+ declare function validateAddonManifest(manifest: AddonManifest): void;
12
+ //#endregion
13
+ export { canonicalManifest as a, sortValue as c, assertSafeRelativePath as i, validateAddonManifest as l, COMPATIBILITY_RANGE_PATTERN as n, createCanonicalAddonDigest as o, MAX_ADDON_ARCHIVE_BYTES as r, isCompatibilityRange as s, ADDON_ID_PATTERN as t };
@@ -0,0 +1,2 @@
1
+ import { MockAddonContextResult, MockAddonServerContextOptions, createMockAddonServerContext } from "./index2.mjs";
2
+ export { MockAddonContextResult, MockAddonServerContextOptions, createMockAddonServerContext };
@@ -0,0 +1,144 @@
1
+ //#region src/testing.ts
2
+ /**
3
+ * Creates an in-memory, zero-dependency AddonServerContext mock for testing addon server factories
4
+ * and pipeline hooks offline in CI without requiring live databases or Redis.
5
+ */
6
+ function createMockAddonServerContext(addonId, options = {}) {
7
+ const { config = {}, user = {}, isEntitled = true } = options;
8
+ const emittedEvents = [];
9
+ const registeredHooks = [];
10
+ const cacheEntries = /* @__PURE__ */ new Map();
11
+ const cacheSets = /* @__PURE__ */ new Map();
12
+ const cacheTtld = /* @__PURE__ */ new Map();
13
+ const rateLimitTimestamps = /* @__PURE__ */ new Map();
14
+ const prefix = `addon:${addonId}:`;
15
+ const cache = {
16
+ async get(key) {
17
+ const fullKey = prefix + key;
18
+ const exp = cacheTtld.get(fullKey);
19
+ if (exp && Date.now() > exp) {
20
+ cacheEntries.delete(fullKey);
21
+ cacheTtld.delete(fullKey);
22
+ return null;
23
+ }
24
+ const val = cacheEntries.get(fullKey);
25
+ return val !== void 0 ? val : null;
26
+ },
27
+ async set(key, value, ttlSeconds) {
28
+ const fullKey = prefix + key;
29
+ cacheEntries.set(fullKey, value);
30
+ if (ttlSeconds && ttlSeconds > 0) cacheTtld.set(fullKey, Date.now() + ttlSeconds * 1e3);
31
+ else cacheTtld.delete(fullKey);
32
+ },
33
+ async del(...keys) {
34
+ let count = 0;
35
+ for (const k of keys) {
36
+ const fullKey = prefix + k;
37
+ if (cacheEntries.delete(fullKey)) count++;
38
+ cacheTtld.delete(fullKey);
39
+ cacheSets.delete(fullKey);
40
+ }
41
+ return count;
42
+ },
43
+ async incr(key) {
44
+ const fullKey = prefix + key;
45
+ const next = (Number(cacheEntries.get(fullKey)) || 0) + 1;
46
+ cacheEntries.set(fullKey, next);
47
+ return next;
48
+ },
49
+ async expire(key, ttlSeconds) {
50
+ const fullKey = prefix + key;
51
+ if (!cacheEntries.has(fullKey) && !cacheSets.has(fullKey)) return false;
52
+ cacheTtld.set(fullKey, Date.now() + ttlSeconds * 1e3);
53
+ return true;
54
+ },
55
+ async ttl(key) {
56
+ const fullKey = prefix + key;
57
+ if (!cacheEntries.has(fullKey) && !cacheSets.has(fullKey)) return -2;
58
+ const exp = cacheTtld.get(fullKey);
59
+ if (!exp) return -1;
60
+ const remaining = Math.ceil((exp - Date.now()) / 1e3);
61
+ return remaining > 0 ? remaining : -2;
62
+ },
63
+ async setAdd(key, member) {
64
+ const fullKey = prefix + key;
65
+ let set = cacheSets.get(fullKey);
66
+ if (!set) {
67
+ set = /* @__PURE__ */ new Set();
68
+ cacheSets.set(fullKey, set);
69
+ }
70
+ const initialSize = set.size;
71
+ set.add(member);
72
+ return set.size > initialSize ? 1 : 0;
73
+ },
74
+ async setRemove(key, member) {
75
+ const fullKey = prefix + key;
76
+ const set = cacheSets.get(fullKey);
77
+ if (!set) return 0;
78
+ return set.delete(member) ? 1 : 0;
79
+ },
80
+ async setMembers(key) {
81
+ const fullKey = prefix + key;
82
+ const set = cacheSets.get(fullKey);
83
+ return set ? Array.from(set) : [];
84
+ }
85
+ };
86
+ const rateLimiter = { async consume(key, opts) {
87
+ const fullKey = `addon:${addonId}:ratelimit:${key}`;
88
+ const now = Date.now();
89
+ const windowStart = now - opts.windowMs;
90
+ const valid = (rateLimitTimestamps.get(fullKey) ?? []).filter((t) => t > windowStart);
91
+ if (valid.length >= opts.limit) {
92
+ rateLimitTimestamps.set(fullKey, valid);
93
+ return false;
94
+ }
95
+ valid.push(now);
96
+ rateLimitTimestamps.set(fullKey, valid);
97
+ return true;
98
+ } };
99
+ const defaultUser = {
100
+ id: user.id ?? "mock-user-1",
101
+ name: user.name ?? "Mock User",
102
+ displayName: user.displayName ?? "Mock User",
103
+ avatar: user.avatar ?? "https://example.com/avatar.png",
104
+ gender: user.gender ?? "neutral"
105
+ };
106
+ return {
107
+ context: {
108
+ t: {},
109
+ db: {},
110
+ cache,
111
+ rateLimiter,
112
+ emit: async (event, payload, target) => {
113
+ emittedEvents.push({
114
+ event: `addon:${addonId}:${event}`,
115
+ payload,
116
+ target
117
+ });
118
+ },
119
+ pipeline: { register: (hooks) => {
120
+ registeredHooks.push(hooks);
121
+ } },
122
+ getConfig: async (_targetSiteId) => config,
123
+ getUser: async (userId) => userId === defaultUser.id ? defaultUser : null,
124
+ getUsers: async (userIds) => {
125
+ const map = /* @__PURE__ */ new Map();
126
+ for (const id of userIds) if (id === defaultUser.id) map.set(id, defaultUser);
127
+ return map;
128
+ },
129
+ hasPermission: (reqUser, permission) => {
130
+ return (reqUser?.permissions ?? []).includes(permission);
131
+ },
132
+ verifyEntitlement: (expectedAddonId) => {
133
+ if (expectedAddonId && expectedAddonId !== addonId) return false;
134
+ return isEntitled;
135
+ }
136
+ },
137
+ emittedEvents,
138
+ registeredHooks,
139
+ cacheEntries,
140
+ cacheSets
141
+ };
142
+ }
143
+ //#endregion
144
+ export { createMockAddonServerContext };
package/dist/vite.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { AddonManifest } from "./contracts.mjs";
1
+ import { l as AddonManifest } from "./contracts2.mjs";
2
2
  import { Plugin } from "vite";
3
3
  //#region src/vite.d.ts
4
4
  /**
package/dist/vite.mjs CHANGED
@@ -1,6 +1,6 @@
1
- import fs from "node:fs";
2
- import path from "node:path";
3
1
  import crypto from "node:crypto";
2
+ import path from "node:path";
3
+ import fs from "node:fs";
4
4
  //#region src/vite.ts
5
5
  /**
6
6
  * Vite plugin for building JChat sandboxed addon extensions.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jchat/addon-sdk",
3
- "version": "1.0.0-alpha.23",
3
+ "version": "1.0.0-alpha.24",
4
4
  "private": false,
5
5
  "description": "JChat Addon SDK for building JChat addons.",
6
6
  "keywords": [
@@ -15,7 +15,7 @@
15
15
  "license": "SEE LICENSE IN LICENSE",
16
16
  "repository": {
17
17
  "type": "git",
18
- "url": "git+https://github.com/mjjabarullah/jchat.git",
18
+ "url": "git+https://github.com/jchatcloud/jchat.git",
19
19
  "directory": "packages/addon-sdk"
20
20
  },
21
21
  "bin": {
@@ -43,6 +43,11 @@
43
43
  "import": "./src/contracts.ts",
44
44
  "default": "./src/contracts.ts"
45
45
  },
46
+ "./signing": {
47
+ "types": "./src/signing.ts",
48
+ "import": "./src/signing.ts",
49
+ "default": "./src/signing.ts"
50
+ },
46
51
  "./cli": {
47
52
  "types": "./src/cli.ts",
48
53
  "import": "./src/cli.ts",
@@ -53,6 +58,11 @@
53
58
  "import": "./src/diagnostics.ts",
54
59
  "default": "./src/diagnostics.ts"
55
60
  },
61
+ "./testing": {
62
+ "types": "./src/testing.ts",
63
+ "import": "./src/testing.ts",
64
+ "default": "./src/testing.ts"
65
+ },
56
66
  "./vite": {
57
67
  "types": "./src/vite.ts",
58
68
  "import": "./src/vite.ts",
@@ -97,6 +107,10 @@
97
107
  "types": "./dist/contracts.d.mts",
98
108
  "import": "./dist/contracts.mjs"
99
109
  },
110
+ "./signing": {
111
+ "types": "./dist/signing.d.mts",
112
+ "import": "./dist/signing.mjs"
113
+ },
100
114
  "./cli": {
101
115
  "types": "./dist/cli.d.mts",
102
116
  "import": "./dist/cli.mjs"
@@ -105,6 +119,10 @@
105
119
  "types": "./dist/diagnostics.d.mts",
106
120
  "import": "./dist/diagnostics.mjs"
107
121
  },
122
+ "./testing": {
123
+ "types": "./dist/testing.d.mts",
124
+ "import": "./dist/testing.mjs"
125
+ },
108
126
  "./vite": {
109
127
  "types": "./dist/vite.d.mts",
110
128
  "import": "./dist/vite.mjs"