@jchat/addon-sdk 1.0.0-alpha.27 → 1.0.0-alpha.28

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/dist/testing.mjs CHANGED
@@ -1,144 +1,2 @@
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
1
+ import { s as createMockAddonServerContext } from "./src.mjs";
144
2
  export { createMockAddonServerContext };
package/dist/ui.d.mts CHANGED
@@ -1 +1,42 @@
1
- export * from "@jchat/ui";
1
+ import { d as AddonRequestUser, g as AddonUser } from "./contracts2.mjs";
2
+ import React from "react";
3
+ import { Can } from "@casl/react";
4
+ export * from "@jchat/ui";
5
+ //#region src/ui.d.ts
6
+ export interface AddonCanProps {
7
+ /** The current user attempting the action */
8
+ actor?: (Partial<AddonRequestUser | AddonUser> & {
9
+ permissions?: string[];
10
+ isOwner?: boolean;
11
+ }) | null;
12
+ /** The owner ID of the target resource, if evaluating an ownership-restricted action */
13
+ ownerId?: string;
14
+ /** Permission required if actor is the resource owner (e.g. 'addon.pollwise.close.own') */
15
+ ownPermission?: string;
16
+ /** Permission required if actor is NOT the resource owner (e.g. 'addon.pollwise.close.any') */
17
+ anyPermission?: string;
18
+ /** Simple permission string when not evaluating ownership (e.g. 'addon.pollwise.create') */
19
+ permission?: string;
20
+ /** Fallback content rendered when permission is denied (defaults to null) */
21
+ fallback?: React.ReactNode;
22
+ /** Content rendered when access is granted */
23
+ children: React.ReactNode;
24
+ }
25
+ /**
26
+ * Declarative component for conditional UI rendering based on RBAC/ABAC permissions and resource
27
+ * ownership.
28
+ *
29
+ * @param actor The current user attempting the action
30
+ * @param ownerId The owner ID of the target resource, if evaluating an ownership-restricted action
31
+ * @param ownPermission Permission required if actor is the resource owner (e.g.
32
+ * 'addon.pollwise.close.own')
33
+ * @param anyPermission Permission required if actor is NOT the resource owner (e.g.
34
+ * 'addon.pollwise.close.any')
35
+ * @param permission Simple permission string when not evaluating ownership (e.g.
36
+ * 'addon.pollwise.create')
37
+ * @param fallback Fallback content rendered when permission is denied (defaults to null)
38
+ * @param children Content rendered when access is granted
39
+ */
40
+ export declare function AddonCan({ actor, ownerId, ownPermission, anyPermission, permission, fallback, children }: AddonCanProps): React.ReactElement | null;
41
+ //#endregion
42
+ export { Can };
package/dist/ui.mjs CHANGED
@@ -1,2 +1,33 @@
1
+ import { _ as hasPermission } from "./src.mjs";
2
+ import { Can } from "@casl/react";
1
3
  export * from "@jchat/ui";
2
- export {};
4
+ //#region src/ui.ts
5
+ /**
6
+ * Declarative component for conditional UI rendering based on RBAC/ABAC permissions and resource
7
+ * ownership.
8
+ *
9
+ * @param actor The current user attempting the action
10
+ * @param ownerId The owner ID of the target resource, if evaluating an ownership-restricted action
11
+ * @param ownPermission Permission required if actor is the resource owner (e.g.
12
+ * 'addon.pollwise.close.own')
13
+ * @param anyPermission Permission required if actor is NOT the resource owner (e.g.
14
+ * 'addon.pollwise.close.any')
15
+ * @param permission Simple permission string when not evaluating ownership (e.g.
16
+ * 'addon.pollwise.create')
17
+ * @param fallback Fallback content rendered when permission is denied (defaults to null)
18
+ * @param children Content rendered when access is granted
19
+ */
20
+ function AddonCan({ actor, ownerId, ownPermission, anyPermission, permission, fallback = null, children }) {
21
+ if (!actor) return fallback;
22
+ const perms = actor.permissions ?? [];
23
+ if (actor.isOwner || hasPermission(perms, "*")) return children;
24
+ if (permission && !hasPermission(perms, permission)) return fallback;
25
+ if (ownPermission || anyPermission) {
26
+ if (Boolean(actor.id && ownerId && actor.id === ownerId) && ownPermission && hasPermission(perms, ownPermission)) return children;
27
+ if (anyPermission && hasPermission(perms, anyPermission)) return children;
28
+ return fallback;
29
+ }
30
+ return children;
31
+ }
32
+ //#endregion
33
+ export { AddonCan, Can };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jchat/addon-sdk",
3
- "version": "1.0.0-alpha.27",
3
+ "version": "1.0.0-alpha.28",
4
4
  "private": false,
5
5
  "description": "JChat Addon SDK for building JChat addons.",
6
6
  "keywords": [
@@ -156,6 +156,8 @@
156
156
  "typecheck": "tsc --noEmit"
157
157
  },
158
158
  "dependencies": {
159
+ "@casl/ability": "catalog:",
160
+ "@casl/react": "catalog:",
159
161
  "lucide-react": "catalog:"
160
162
  },
161
163
  "devDependencies": {