@svadmin/ably 0.0.0-stage → 0.2.0

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/package.json CHANGED
@@ -1,6 +1,49 @@
1
1
  {
2
2
  "name": "@svadmin/ably",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.2.0",
4
+ "description": "Ably Pub/Sub LiveProvider adapter for svadmin",
5
+ "type": "module",
6
+ "sideEffects": false,
7
+ "files": [
8
+ "src/**/*.ts",
9
+ "!src/**/*.test.*",
10
+ "!src/**/*.spec.*"
11
+ ],
12
+ "main": "src/index.ts",
13
+ "types": "src/index.ts",
14
+ "exports": {
15
+ ".": {
16
+ "types": "./src/index.ts",
17
+ "default": "./src/index.ts"
18
+ }
19
+ },
20
+ "peerDependencies": {
21
+ "@svadmin/core": "^0.79.0"
22
+ },
23
+ "keywords": [
24
+ "svadmin",
25
+ "ably",
26
+ "live-provider",
27
+ "realtime",
28
+ "pubsub"
29
+ ],
30
+ "license": "MIT",
31
+ "author": "zuohuadong",
32
+ "repository": {
33
+ "type": "git",
34
+ "url": "git+https://github.com/vibeunion/svadmin.git",
35
+ "directory": "packages/ably"
36
+ },
37
+ "homepage": "https://github.com/vibeunion/svadmin#readme",
38
+ "bugs": {
39
+ "url": "https://github.com/vibeunion/svadmin/issues"
40
+ },
41
+ "svadmin": {
42
+ "capabilities": [
43
+ "live"
44
+ ],
45
+ "core": "^0.55.0",
46
+ "stability": "stable",
47
+ "migration": null
48
+ }
49
+ }
@@ -0,0 +1,155 @@
1
+ import type { LiveProvider, LiveEvent } from '@svadmin/core';
2
+
3
+ export interface AblyMessageLike {
4
+ name?: string;
5
+ data: unknown;
6
+ id?: string;
7
+ clientId?: string;
8
+ timestamp?: number;
9
+ }
10
+
11
+ export type AblyMessageListener = (message: AblyMessageLike) => void;
12
+
13
+ export interface AblyChannelLike {
14
+ subscribe(listener: AblyMessageListener): Promise<void> | void;
15
+ subscribe(name: string, listener: AblyMessageListener): Promise<void> | void;
16
+ unsubscribe(listener?: AblyMessageListener): Promise<void> | void;
17
+ unsubscribe(name?: string, listener?: AblyMessageListener): Promise<void> | void;
18
+ publish(name: string, data: unknown): Promise<void> | void;
19
+ }
20
+
21
+ export interface AblyRealtimeClientLike {
22
+ channels: {
23
+ get(name: string): AblyChannelLike;
24
+ release?(name: string): void;
25
+ };
26
+ connection?: {
27
+ state?: string;
28
+ on?(event: string, listener: () => void): void;
29
+ off?(event: string, listener: () => void): void;
30
+ };
31
+ }
32
+
33
+ export interface AblyLiveProviderOptions {
34
+ /**
35
+ * Channel prefix prepended to resource names.
36
+ * Defaults to 'resources'.
37
+ * Resulting channel name: `${channelPrefix}:${resource}`.
38
+ */
39
+ channelPrefix?: string;
40
+
41
+ /**
42
+ * Custom function to resolve channel name from resource and liveParams.
43
+ */
44
+ channelName?: (resource: string, liveParams?: Record<string, unknown>) => string;
45
+ }
46
+
47
+ type LiveActionType = LiveEvent['type'];
48
+
49
+ const actionTypeMap: Record<string, LiveActionType> = {
50
+ create: 'INSERT',
51
+ created: 'INSERT',
52
+ insert: 'INSERT',
53
+ 'create-record': 'INSERT',
54
+ update: 'UPDATE',
55
+ updated: 'UPDATE',
56
+ patch: 'UPDATE',
57
+ delete: 'DELETE',
58
+ deleted: 'DELETE',
59
+ remove: 'DELETE',
60
+ };
61
+
62
+ function normalizeEventPayload(data: unknown): Record<string, unknown> {
63
+ if (data !== null && typeof data === 'object' && !Array.isArray(data)) {
64
+ const envelope = data as Record<string, unknown>;
65
+ if ('payload' in envelope && ('type' in envelope || 'event' in envelope || 'name' in envelope)) {
66
+ return normalizeEventPayload(envelope['payload']);
67
+ }
68
+ return envelope;
69
+ }
70
+ return { value: data };
71
+ }
72
+
73
+ function mapEventType(value: unknown): LiveActionType | undefined {
74
+ if (typeof value !== 'string') return undefined;
75
+ return actionTypeMap[value.toLowerCase()];
76
+ }
77
+
78
+ function resolveEventType(name: string | undefined, data: unknown): LiveActionType {
79
+ const mappedName = mapEventType(name);
80
+ if (mappedName) return mappedName;
81
+
82
+ if (data !== null && typeof data === 'object' && !Array.isArray(data)) {
83
+ const envelope = data as Record<string, unknown>;
84
+ for (const key of ['type', 'event', 'name']) {
85
+ const mapped = mapEventType(envelope[key]);
86
+ if (mapped) return mapped;
87
+ }
88
+ }
89
+ return 'UPDATE';
90
+ }
91
+
92
+ /**
93
+ * Creates an Ably Pub/Sub LiveProvider adapter for svadmin.
94
+ * Connects resource subscriptions directly to Ably channels.
95
+ */
96
+ export function createAblyLiveProvider(
97
+ client: AblyRealtimeClientLike,
98
+ options: AblyLiveProviderOptions = {},
99
+ ): LiveProvider {
100
+ const channelPrefix = options.channelPrefix ?? 'resources';
101
+ const listenersByChannel = new Map<string, Set<AblyMessageListener>>();
102
+
103
+ function getChannelName(resource: string, liveParams?: Record<string, unknown>): string {
104
+ if (options.channelName) {
105
+ return options.channelName(resource, liveParams);
106
+ }
107
+ return `${channelPrefix}:${resource}`;
108
+ }
109
+
110
+ return {
111
+ subscribe({ resource, liveParams, callback }): () => void {
112
+ const channelName = getChannelName(resource, liveParams);
113
+ const channel = client.channels.get(channelName);
114
+
115
+ const listener: AblyMessageListener = (message: AblyMessageLike) => {
116
+ const type = resolveEventType(message.name, message.data);
117
+ const payload = normalizeEventPayload(message.data);
118
+
119
+ const liveEvent: LiveEvent = {
120
+ type,
121
+ resource,
122
+ payload,
123
+ };
124
+
125
+ callback(liveEvent);
126
+ };
127
+
128
+ void channel.subscribe(listener);
129
+ const listeners = listenersByChannel.get(channelName) ?? new Set<AblyMessageListener>();
130
+ listeners.add(listener);
131
+ listenersByChannel.set(channelName, listeners);
132
+
133
+ return () => {
134
+ void channel.unsubscribe(listener);
135
+ const current = listenersByChannel.get(channelName);
136
+ current?.delete(listener);
137
+ if (current && current.size === 0) listenersByChannel.delete(channelName);
138
+ };
139
+ },
140
+
141
+ unsubscribe({ resource, liveParams }): void {
142
+ const channelName = getChannelName(resource, liveParams);
143
+ const channel = client.channels.get(channelName);
144
+ const listeners = listenersByChannel.get(channelName);
145
+ for (const listener of listeners ?? []) void channel.unsubscribe(listener);
146
+ listenersByChannel.delete(channelName);
147
+ },
148
+
149
+ publish(event: LiveEvent): void {
150
+ const channelName = getChannelName(event.resource);
151
+ const channel = client.channels.get(channelName);
152
+ void channel.publish(event.type, event.payload);
153
+ },
154
+ };
155
+ }
package/src/index.ts ADDED
@@ -0,0 +1,8 @@
1
+ export {
2
+ createAblyLiveProvider,
3
+ type AblyLiveProviderOptions,
4
+ type AblyRealtimeClientLike,
5
+ type AblyChannelLike,
6
+ type AblyMessageLike,
7
+ type AblyMessageListener,
8
+ } from './ably-live-provider.js';
package/README.md DELETED
@@ -1,3 +0,0 @@
1
- # Temporary Holding Version
2
-
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.