@objectstack/service-realtime 4.0.1 → 4.0.3
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/.turbo/turbo-build.log +11 -11
- package/CHANGELOG.md +15 -0
- package/dist/index.cjs +2 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +8 -31
- package/dist/index.d.ts +8 -31
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
- package/src/realtime-service-plugin.ts +3 -2
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
|
|
2
|
-
> @objectstack/service-realtime@4.0.
|
|
2
|
+
> @objectstack/service-realtime@4.0.3 build /home/runner/work/framework/framework/packages/services/service-realtime
|
|
3
3
|
> tsup --config ../../../tsup.config.ts
|
|
4
4
|
|
|
5
5
|
[34mCLI[39m Building entry: src/index.ts
|
|
6
6
|
[34mCLI[39m Using tsconfig: tsconfig.json
|
|
7
7
|
[34mCLI[39m tsup v8.5.1
|
|
8
|
-
[34mCLI[39m Using tsup config: /home/runner/work/
|
|
8
|
+
[34mCLI[39m Using tsup config: /home/runner/work/framework/framework/tsup.config.ts
|
|
9
9
|
[34mCLI[39m Target: es2020
|
|
10
10
|
[34mCLI[39m Cleaning output folder
|
|
11
11
|
[34mESM[39m Build start
|
|
12
12
|
[34mCJS[39m Build start
|
|
13
|
-
[32mCJS[39m [1mdist/index.cjs [22m[32m6.
|
|
14
|
-
[32mCJS[39m [1mdist/index.cjs.map [22m[32m13.
|
|
15
|
-
[32mCJS[39m ⚡️ Build success in
|
|
16
|
-
[32mESM[39m [1mdist/index.js [22m[32m5.
|
|
17
|
-
[32mESM[39m [1mdist/index.js.map [22m[32m13.
|
|
18
|
-
[32mESM[39m ⚡️ Build success in
|
|
13
|
+
[32mCJS[39m [1mdist/index.cjs [22m[32m6.64 KB[39m
|
|
14
|
+
[32mCJS[39m [1mdist/index.cjs.map [22m[32m13.93 KB[39m
|
|
15
|
+
[32mCJS[39m ⚡️ Build success in 71ms
|
|
16
|
+
[32mESM[39m [1mdist/index.js [22m[32m5.40 KB[39m
|
|
17
|
+
[32mESM[39m [1mdist/index.js.map [22m[32m13.42 KB[39m
|
|
18
|
+
[32mESM[39m ⚡️ Build success in 75ms
|
|
19
19
|
[34mDTS[39m Build start
|
|
20
|
-
[32mDTS[39m ⚡️ Build success in
|
|
21
|
-
[32mDTS[39m [1mdist/index.d.ts [22m[
|
|
22
|
-
[32mDTS[39m [1mdist/index.d.cts [22m[
|
|
20
|
+
[32mDTS[39m ⚡️ Build success in 16178ms
|
|
21
|
+
[32mDTS[39m [1mdist/index.d.ts [22m[32m99.42 KB[39m
|
|
22
|
+
[32mDTS[39m [1mdist/index.d.cts [22m[32m99.42 KB[39m
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# @objectstack/service-realtime
|
|
2
2
|
|
|
3
|
+
## 4.0.3
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- @objectstack/spec@4.0.3
|
|
8
|
+
- @objectstack/core@4.0.3
|
|
9
|
+
|
|
10
|
+
## 4.0.2
|
|
11
|
+
|
|
12
|
+
### Patch Changes
|
|
13
|
+
|
|
14
|
+
- Updated dependencies [5f659e9]
|
|
15
|
+
- @objectstack/spec@4.0.2
|
|
16
|
+
- @objectstack/core@4.0.2
|
|
17
|
+
|
|
3
18
|
## 4.0.0
|
|
4
19
|
|
|
5
20
|
### Patch Changes
|
package/dist/index.cjs
CHANGED
|
@@ -201,12 +201,13 @@ var RealtimeServicePlugin = class {
|
|
|
201
201
|
this.name = "com.objectstack.service.realtime";
|
|
202
202
|
this.version = "1.0.0";
|
|
203
203
|
this.type = "standard";
|
|
204
|
+
this.dependencies = ["com.objectstack.engine.objectql"];
|
|
204
205
|
this.options = { adapter: "memory", ...options };
|
|
205
206
|
}
|
|
206
207
|
async init(ctx) {
|
|
207
208
|
const realtime = new InMemoryRealtimeAdapter(this.options.memory);
|
|
208
209
|
ctx.registerService("realtime", realtime);
|
|
209
|
-
ctx.
|
|
210
|
+
ctx.getService("manifest").register({
|
|
210
211
|
id: "com.objectstack.service.realtime",
|
|
211
212
|
name: "Realtime Service",
|
|
212
213
|
version: "1.0.0",
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/in-memory-realtime-adapter.ts","../src/objects/sys-presence.object.ts","../src/realtime-service-plugin.ts"],"sourcesContent":["// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nexport { RealtimeServicePlugin } from './realtime-service-plugin.js';\nexport type { RealtimeServicePluginOptions } from './realtime-service-plugin.js';\nexport { InMemoryRealtimeAdapter } from './in-memory-realtime-adapter.js';\nexport type { InMemoryRealtimeAdapterOptions } from './in-memory-realtime-adapter.js';\nexport { SysPresence } from './objects/index.js';\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type {\n IRealtimeService,\n RealtimeEventPayload,\n RealtimeEventHandler,\n RealtimeSubscriptionOptions,\n} from '@objectstack/spec/contracts';\n\n/**\n * Internal subscription entry.\n */\ninterface Subscription {\n id: string;\n channel: string;\n handler: RealtimeEventHandler;\n options?: RealtimeSubscriptionOptions;\n}\n\n/**\n * Configuration options for InMemoryRealtimeAdapter.\n */\nexport interface InMemoryRealtimeAdapterOptions {\n /** Maximum number of subscriptions allowed (0 = unlimited) */\n maxSubscriptions?: number;\n}\n\n/**\n * In-memory pub/sub adapter implementing IRealtimeService.\n *\n * Uses a Map-backed subscription store with channel-based routing.\n * Supports event type and object filtering via subscription options.\n *\n * Suitable for single-process environments, development, and testing.\n * For production multi-instance deployments, use a Redis-backed adapter.\n *\n * @example\n * ```ts\n * const realtime = new InMemoryRealtimeAdapter();\n *\n * const subId = await realtime.subscribe('records', (event) => {\n * console.log('Received:', event.type, event.payload);\n * }, { object: 'account', eventTypes: ['record.created'] });\n *\n * await realtime.publish({\n * type: 'record.created',\n * object: 'account',\n * payload: { id: 'acc-1', name: 'Acme' },\n * timestamp: new Date().toISOString(),\n * });\n *\n * await realtime.unsubscribe(subId);\n * ```\n */\nexport class InMemoryRealtimeAdapter implements IRealtimeService {\n private readonly subscriptions = new Map<string, Subscription>();\n private readonly channelIndex = new Map<string, Set<string>>();\n private counter = 0;\n private readonly maxSubscriptions: number;\n\n constructor(options: InMemoryRealtimeAdapterOptions = {}) {\n this.maxSubscriptions = options.maxSubscriptions ?? 0;\n }\n\n async publish(event: RealtimeEventPayload): Promise<void> {\n // Deliver to all channel subscriptions that match filters\n for (const sub of this.subscriptions.values()) {\n if (this.matchesSubscription(event, sub)) {\n try {\n await sub.handler(event);\n } catch {\n // Swallow handler errors to avoid breaking the publish loop\n }\n }\n }\n }\n\n async subscribe(\n channel: string,\n handler: RealtimeEventHandler,\n options?: RealtimeSubscriptionOptions,\n ): Promise<string> {\n if (this.maxSubscriptions > 0 && this.subscriptions.size >= this.maxSubscriptions) {\n throw new Error(\n `Maximum subscription limit reached (${this.maxSubscriptions}). ` +\n 'Unsubscribe from existing channels before adding new subscriptions.',\n );\n }\n\n const id = `sub-${++this.counter}`;\n const sub: Subscription = { id, channel, handler, options };\n this.subscriptions.set(id, sub);\n\n // Maintain channel index for efficient lookups\n if (!this.channelIndex.has(channel)) {\n this.channelIndex.set(channel, new Set());\n }\n this.channelIndex.get(channel)!.add(id);\n\n return id;\n }\n\n async unsubscribe(subscriptionId: string): Promise<void> {\n const sub = this.subscriptions.get(subscriptionId);\n if (!sub) return;\n\n this.subscriptions.delete(subscriptionId);\n\n // Clean up channel index\n const channelSubs = this.channelIndex.get(sub.channel);\n if (channelSubs) {\n channelSubs.delete(subscriptionId);\n if (channelSubs.size === 0) {\n this.channelIndex.delete(sub.channel);\n }\n }\n }\n\n /**\n * Get the number of active subscriptions.\n */\n getSubscriptionCount(): number {\n return this.subscriptions.size;\n }\n\n /**\n * Get all active channel names.\n */\n getChannels(): string[] {\n return Array.from(this.channelIndex.keys());\n }\n\n /**\n * Check if an event matches a subscription's filters.\n */\n private matchesSubscription(event: RealtimeEventPayload, sub: Subscription): boolean {\n const opts = sub.options;\n if (!opts) return true;\n\n // Filter by object name\n if (opts.object && event.object !== opts.object) {\n return false;\n }\n\n // Filter by event types\n if (opts.eventTypes && opts.eventTypes.length > 0) {\n if (!opts.eventTypes.includes(event.type)) {\n return false;\n }\n }\n\n return true;\n }\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { ObjectSchema, Field } from '@objectstack/spec/data';\n\n/**\n * sys_presence — System Presence Object\n *\n * Tracks real-time user presence and activity across the platform.\n * Fields align with the PresenceStateSchema protocol definition\n * from `@objectstack/spec/api` (websocket.zod.ts).\n *\n * Owned by `service-realtime` as the canonical Presence domain object.\n *\n * @namespace sys\n * @see PresenceStateSchema in packages/spec/src/api/websocket.zod.ts\n */\nexport const SysPresence = ObjectSchema.create({\n namespace: 'sys',\n name: 'presence',\n label: 'Presence',\n pluralLabel: 'Presences',\n icon: 'wifi',\n isSystem: true,\n description: 'Real-time user presence and activity tracking',\n titleFormat: '{user_id} ({status})',\n compactLayout: ['user_id', 'status', 'last_seen'],\n\n fields: {\n id: Field.text({\n label: 'Presence ID',\n required: true,\n readonly: true,\n }),\n\n created_at: Field.datetime({\n label: 'Created At',\n defaultValue: 'NOW()',\n readonly: true,\n }),\n\n updated_at: Field.datetime({\n label: 'Updated At',\n defaultValue: 'NOW()',\n readonly: true,\n }),\n\n user_id: Field.text({\n label: 'User ID',\n required: true,\n searchable: true,\n }),\n\n session_id: Field.text({\n label: 'Session ID',\n required: true,\n }),\n\n status: Field.select({\n label: 'Status',\n required: true,\n defaultValue: 'online',\n options: [\n { value: 'online', label: 'Online' },\n { value: 'away', label: 'Away' },\n { value: 'busy', label: 'Busy' },\n { value: 'offline', label: 'Offline' },\n ],\n }),\n\n last_seen: Field.datetime({\n label: 'Last Seen',\n required: true,\n defaultValue: 'NOW()',\n }),\n\n current_location: Field.text({\n label: 'Current Location',\n required: false,\n maxLength: 500,\n }),\n\n device: Field.select({\n label: 'Device',\n required: false,\n options: [\n { value: 'desktop', label: 'Desktop' },\n { value: 'mobile', label: 'Mobile' },\n { value: 'tablet', label: 'Tablet' },\n { value: 'other', label: 'Other' },\n ],\n }),\n\n custom_status: Field.text({\n label: 'Custom Status',\n required: false,\n maxLength: 255,\n }),\n\n metadata: Field.json({\n label: 'Metadata',\n required: false,\n description: 'Arbitrary JSON metadata associated with the presence state (matches PresenceStateSchema.metadata).',\n }),\n },\n\n indexes: [\n { fields: ['user_id'], unique: false },\n { fields: ['session_id'], unique: true },\n { fields: ['status'], unique: false },\n ],\n\n enable: {\n trackHistory: false,\n searchable: false,\n apiEnabled: true,\n apiMethods: ['get', 'list', 'create', 'update', 'delete'],\n trash: false,\n mru: false,\n },\n});\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { Plugin, PluginContext } from '@objectstack/core';\nimport { InMemoryRealtimeAdapter } from './in-memory-realtime-adapter.js';\nimport type { InMemoryRealtimeAdapterOptions } from './in-memory-realtime-adapter.js';\nimport { SysPresence } from './objects/index.js';\n\n/**\n * Configuration options for the RealtimeServicePlugin.\n */\nexport interface RealtimeServicePluginOptions {\n /** Realtime adapter type (default: 'memory') */\n adapter?: 'memory';\n /** Options for the in-memory adapter */\n memory?: InMemoryRealtimeAdapterOptions;\n}\n\n/**\n * RealtimeServicePlugin — Production IRealtimeService implementation.\n *\n * Registers a realtime pub/sub service with the kernel during the init phase.\n * Currently supports in-memory pub/sub for single-process environments.\n *\n * @example\n * ```ts\n * import { ObjectKernel } from '@objectstack/core';\n * import { RealtimeServicePlugin } from '@objectstack/service-realtime';\n *\n * const kernel = new ObjectKernel();\n * kernel.use(new RealtimeServicePlugin());\n * await kernel.bootstrap();\n *\n * const realtime = kernel.getService('realtime');\n * await realtime.subscribe('records', (event) => {\n * console.log(event.type, event.payload);\n * });\n * ```\n */\nexport class RealtimeServicePlugin implements Plugin {\n name = 'com.objectstack.service.realtime';\n version = '1.0.0';\n type = 'standard';\n\n private readonly options: RealtimeServicePluginOptions;\n\n constructor(options: RealtimeServicePluginOptions = {}) {\n this.options = { adapter: 'memory', ...options };\n }\n\n async init(ctx: PluginContext): Promise<void> {\n const realtime = new InMemoryRealtimeAdapter(this.options.memory);\n ctx.registerService('realtime', realtime);\n\n // Register realtime system objects so ObjectQLPlugin auto-discovers them\n ctx.registerService('app.com.objectstack.service.realtime', {\n id: 'com.objectstack.service.realtime',\n name: 'Realtime Service',\n version: '1.0.0',\n type: 'plugin',\n namespace: 'sys',\n objects: [SysPresence],\n });\n\n ctx.logger.info('RealtimeServicePlugin: registered in-memory realtime adapter');\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/in-memory-realtime-adapter.ts","../src/objects/sys-presence.object.ts","../src/realtime-service-plugin.ts"],"sourcesContent":["// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nexport { RealtimeServicePlugin } from './realtime-service-plugin.js';\nexport type { RealtimeServicePluginOptions } from './realtime-service-plugin.js';\nexport { InMemoryRealtimeAdapter } from './in-memory-realtime-adapter.js';\nexport type { InMemoryRealtimeAdapterOptions } from './in-memory-realtime-adapter.js';\nexport { SysPresence } from './objects/index.js';\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type {\n IRealtimeService,\n RealtimeEventPayload,\n RealtimeEventHandler,\n RealtimeSubscriptionOptions,\n} from '@objectstack/spec/contracts';\n\n/**\n * Internal subscription entry.\n */\ninterface Subscription {\n id: string;\n channel: string;\n handler: RealtimeEventHandler;\n options?: RealtimeSubscriptionOptions;\n}\n\n/**\n * Configuration options for InMemoryRealtimeAdapter.\n */\nexport interface InMemoryRealtimeAdapterOptions {\n /** Maximum number of subscriptions allowed (0 = unlimited) */\n maxSubscriptions?: number;\n}\n\n/**\n * In-memory pub/sub adapter implementing IRealtimeService.\n *\n * Uses a Map-backed subscription store with channel-based routing.\n * Supports event type and object filtering via subscription options.\n *\n * Suitable for single-process environments, development, and testing.\n * For production multi-instance deployments, use a Redis-backed adapter.\n *\n * @example\n * ```ts\n * const realtime = new InMemoryRealtimeAdapter();\n *\n * const subId = await realtime.subscribe('records', (event) => {\n * console.log('Received:', event.type, event.payload);\n * }, { object: 'account', eventTypes: ['record.created'] });\n *\n * await realtime.publish({\n * type: 'record.created',\n * object: 'account',\n * payload: { id: 'acc-1', name: 'Acme' },\n * timestamp: new Date().toISOString(),\n * });\n *\n * await realtime.unsubscribe(subId);\n * ```\n */\nexport class InMemoryRealtimeAdapter implements IRealtimeService {\n private readonly subscriptions = new Map<string, Subscription>();\n private readonly channelIndex = new Map<string, Set<string>>();\n private counter = 0;\n private readonly maxSubscriptions: number;\n\n constructor(options: InMemoryRealtimeAdapterOptions = {}) {\n this.maxSubscriptions = options.maxSubscriptions ?? 0;\n }\n\n async publish(event: RealtimeEventPayload): Promise<void> {\n // Deliver to all channel subscriptions that match filters\n for (const sub of this.subscriptions.values()) {\n if (this.matchesSubscription(event, sub)) {\n try {\n await sub.handler(event);\n } catch {\n // Swallow handler errors to avoid breaking the publish loop\n }\n }\n }\n }\n\n async subscribe(\n channel: string,\n handler: RealtimeEventHandler,\n options?: RealtimeSubscriptionOptions,\n ): Promise<string> {\n if (this.maxSubscriptions > 0 && this.subscriptions.size >= this.maxSubscriptions) {\n throw new Error(\n `Maximum subscription limit reached (${this.maxSubscriptions}). ` +\n 'Unsubscribe from existing channels before adding new subscriptions.',\n );\n }\n\n const id = `sub-${++this.counter}`;\n const sub: Subscription = { id, channel, handler, options };\n this.subscriptions.set(id, sub);\n\n // Maintain channel index for efficient lookups\n if (!this.channelIndex.has(channel)) {\n this.channelIndex.set(channel, new Set());\n }\n this.channelIndex.get(channel)!.add(id);\n\n return id;\n }\n\n async unsubscribe(subscriptionId: string): Promise<void> {\n const sub = this.subscriptions.get(subscriptionId);\n if (!sub) return;\n\n this.subscriptions.delete(subscriptionId);\n\n // Clean up channel index\n const channelSubs = this.channelIndex.get(sub.channel);\n if (channelSubs) {\n channelSubs.delete(subscriptionId);\n if (channelSubs.size === 0) {\n this.channelIndex.delete(sub.channel);\n }\n }\n }\n\n /**\n * Get the number of active subscriptions.\n */\n getSubscriptionCount(): number {\n return this.subscriptions.size;\n }\n\n /**\n * Get all active channel names.\n */\n getChannels(): string[] {\n return Array.from(this.channelIndex.keys());\n }\n\n /**\n * Check if an event matches a subscription's filters.\n */\n private matchesSubscription(event: RealtimeEventPayload, sub: Subscription): boolean {\n const opts = sub.options;\n if (!opts) return true;\n\n // Filter by object name\n if (opts.object && event.object !== opts.object) {\n return false;\n }\n\n // Filter by event types\n if (opts.eventTypes && opts.eventTypes.length > 0) {\n if (!opts.eventTypes.includes(event.type)) {\n return false;\n }\n }\n\n return true;\n }\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { ObjectSchema, Field } from '@objectstack/spec/data';\n\n/**\n * sys_presence — System Presence Object\n *\n * Tracks real-time user presence and activity across the platform.\n * Fields align with the PresenceStateSchema protocol definition\n * from `@objectstack/spec/api` (websocket.zod.ts).\n *\n * Owned by `service-realtime` as the canonical Presence domain object.\n *\n * @namespace sys\n * @see PresenceStateSchema in packages/spec/src/api/websocket.zod.ts\n */\nexport const SysPresence = ObjectSchema.create({\n namespace: 'sys',\n name: 'presence',\n label: 'Presence',\n pluralLabel: 'Presences',\n icon: 'wifi',\n isSystem: true,\n description: 'Real-time user presence and activity tracking',\n titleFormat: '{user_id} ({status})',\n compactLayout: ['user_id', 'status', 'last_seen'],\n\n fields: {\n id: Field.text({\n label: 'Presence ID',\n required: true,\n readonly: true,\n }),\n\n created_at: Field.datetime({\n label: 'Created At',\n defaultValue: 'NOW()',\n readonly: true,\n }),\n\n updated_at: Field.datetime({\n label: 'Updated At',\n defaultValue: 'NOW()',\n readonly: true,\n }),\n\n user_id: Field.text({\n label: 'User ID',\n required: true,\n searchable: true,\n }),\n\n session_id: Field.text({\n label: 'Session ID',\n required: true,\n }),\n\n status: Field.select({\n label: 'Status',\n required: true,\n defaultValue: 'online',\n options: [\n { value: 'online', label: 'Online' },\n { value: 'away', label: 'Away' },\n { value: 'busy', label: 'Busy' },\n { value: 'offline', label: 'Offline' },\n ],\n }),\n\n last_seen: Field.datetime({\n label: 'Last Seen',\n required: true,\n defaultValue: 'NOW()',\n }),\n\n current_location: Field.text({\n label: 'Current Location',\n required: false,\n maxLength: 500,\n }),\n\n device: Field.select({\n label: 'Device',\n required: false,\n options: [\n { value: 'desktop', label: 'Desktop' },\n { value: 'mobile', label: 'Mobile' },\n { value: 'tablet', label: 'Tablet' },\n { value: 'other', label: 'Other' },\n ],\n }),\n\n custom_status: Field.text({\n label: 'Custom Status',\n required: false,\n maxLength: 255,\n }),\n\n metadata: Field.json({\n label: 'Metadata',\n required: false,\n description: 'Arbitrary JSON metadata associated with the presence state (matches PresenceStateSchema.metadata).',\n }),\n },\n\n indexes: [\n { fields: ['user_id'], unique: false },\n { fields: ['session_id'], unique: true },\n { fields: ['status'], unique: false },\n ],\n\n enable: {\n trackHistory: false,\n searchable: false,\n apiEnabled: true,\n apiMethods: ['get', 'list', 'create', 'update', 'delete'],\n trash: false,\n mru: false,\n },\n});\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { Plugin, PluginContext } from '@objectstack/core';\nimport { InMemoryRealtimeAdapter } from './in-memory-realtime-adapter.js';\nimport type { InMemoryRealtimeAdapterOptions } from './in-memory-realtime-adapter.js';\nimport { SysPresence } from './objects/index.js';\n\n/**\n * Configuration options for the RealtimeServicePlugin.\n */\nexport interface RealtimeServicePluginOptions {\n /** Realtime adapter type (default: 'memory') */\n adapter?: 'memory';\n /** Options for the in-memory adapter */\n memory?: InMemoryRealtimeAdapterOptions;\n}\n\n/**\n * RealtimeServicePlugin — Production IRealtimeService implementation.\n *\n * Registers a realtime pub/sub service with the kernel during the init phase.\n * Currently supports in-memory pub/sub for single-process environments.\n *\n * @example\n * ```ts\n * import { ObjectKernel } from '@objectstack/core';\n * import { RealtimeServicePlugin } from '@objectstack/service-realtime';\n *\n * const kernel = new ObjectKernel();\n * kernel.use(new RealtimeServicePlugin());\n * await kernel.bootstrap();\n *\n * const realtime = kernel.getService('realtime');\n * await realtime.subscribe('records', (event) => {\n * console.log(event.type, event.payload);\n * });\n * ```\n */\nexport class RealtimeServicePlugin implements Plugin {\n name = 'com.objectstack.service.realtime';\n version = '1.0.0';\n type = 'standard';\n dependencies = ['com.objectstack.engine.objectql'];\n\n private readonly options: RealtimeServicePluginOptions;\n\n constructor(options: RealtimeServicePluginOptions = {}) {\n this.options = { adapter: 'memory', ...options };\n }\n\n async init(ctx: PluginContext): Promise<void> {\n const realtime = new InMemoryRealtimeAdapter(this.options.memory);\n ctx.registerService('realtime', realtime);\n\n // Register realtime system objects via the manifest service.\n ctx.getService<{ register(m: any): void }>('manifest').register({\n id: 'com.objectstack.service.realtime',\n name: 'Realtime Service',\n version: '1.0.0',\n type: 'plugin',\n namespace: 'sys',\n objects: [SysPresence],\n });\n\n ctx.logger.info('RealtimeServicePlugin: registered in-memory realtime adapter');\n }\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.d.cts
CHANGED
|
@@ -94,6 +94,7 @@ declare class RealtimeServicePlugin implements Plugin {
|
|
|
94
94
|
name: string;
|
|
95
95
|
version: string;
|
|
96
96
|
type: string;
|
|
97
|
+
dependencies: string[];
|
|
97
98
|
private readonly options;
|
|
98
99
|
constructor(options?: RealtimeServicePluginOptions);
|
|
99
100
|
init(ctx: PluginContext): Promise<void>;
|
|
@@ -425,12 +426,8 @@ declare const SysPresence: Omit<{
|
|
|
425
426
|
keyPrefix?: string | undefined;
|
|
426
427
|
actions?: {
|
|
427
428
|
name: string;
|
|
428
|
-
label: string
|
|
429
|
-
|
|
430
|
-
defaultValue?: string | undefined;
|
|
431
|
-
params?: Record<string, string | number | boolean> | undefined;
|
|
432
|
-
};
|
|
433
|
-
type: "url" | "script" | "modal" | "flow" | "api";
|
|
429
|
+
label: string;
|
|
430
|
+
type: "url" | "flow" | "api" | "script" | "modal";
|
|
434
431
|
refreshAfter: boolean;
|
|
435
432
|
objectName?: string | undefined;
|
|
436
433
|
icon?: string | undefined;
|
|
@@ -440,44 +437,24 @@ declare const SysPresence: Omit<{
|
|
|
440
437
|
execute?: string | undefined;
|
|
441
438
|
params?: {
|
|
442
439
|
name: string;
|
|
443
|
-
label: string
|
|
444
|
-
key: string;
|
|
445
|
-
defaultValue?: string | undefined;
|
|
446
|
-
params?: Record<string, string | number | boolean> | undefined;
|
|
447
|
-
};
|
|
440
|
+
label: string;
|
|
448
441
|
type: "number" | "boolean" | "date" | "lookup" | "file" | "url" | "json" | "text" | "textarea" | "email" | "phone" | "password" | "markdown" | "html" | "richtext" | "currency" | "percent" | "datetime" | "time" | "toggle" | "select" | "multiselect" | "radio" | "checkboxes" | "master_detail" | "tree" | "image" | "avatar" | "video" | "audio" | "formula" | "summary" | "autonumber" | "location" | "address" | "code" | "color" | "rating" | "slider" | "signature" | "qrcode" | "progress" | "tags" | "vector";
|
|
449
442
|
required: boolean;
|
|
450
443
|
options?: {
|
|
451
|
-
label: string
|
|
452
|
-
key: string;
|
|
453
|
-
defaultValue?: string | undefined;
|
|
454
|
-
params?: Record<string, string | number | boolean> | undefined;
|
|
455
|
-
};
|
|
444
|
+
label: string;
|
|
456
445
|
value: string;
|
|
457
446
|
}[] | undefined;
|
|
458
447
|
}[] | undefined;
|
|
459
448
|
variant?: "link" | "primary" | "secondary" | "danger" | "ghost" | undefined;
|
|
460
|
-
confirmText?: string |
|
|
461
|
-
|
|
462
|
-
defaultValue?: string | undefined;
|
|
463
|
-
params?: Record<string, string | number | boolean> | undefined;
|
|
464
|
-
} | undefined;
|
|
465
|
-
successMessage?: string | {
|
|
466
|
-
key: string;
|
|
467
|
-
defaultValue?: string | undefined;
|
|
468
|
-
params?: Record<string, string | number | boolean> | undefined;
|
|
469
|
-
} | undefined;
|
|
449
|
+
confirmText?: string | undefined;
|
|
450
|
+
successMessage?: string | undefined;
|
|
470
451
|
visible?: string | undefined;
|
|
471
452
|
disabled?: string | boolean | undefined;
|
|
472
453
|
shortcut?: string | undefined;
|
|
473
454
|
bulkEnabled?: boolean | undefined;
|
|
474
455
|
timeout?: number | undefined;
|
|
475
456
|
aria?: {
|
|
476
|
-
ariaLabel?: string |
|
|
477
|
-
key: string;
|
|
478
|
-
defaultValue?: string | undefined;
|
|
479
|
-
params?: Record<string, string | number | boolean> | undefined;
|
|
480
|
-
} | undefined;
|
|
457
|
+
ariaLabel?: string | undefined;
|
|
481
458
|
ariaDescribedBy?: string | undefined;
|
|
482
459
|
role?: string | undefined;
|
|
483
460
|
} | undefined;
|
package/dist/index.d.ts
CHANGED
|
@@ -94,6 +94,7 @@ declare class RealtimeServicePlugin implements Plugin {
|
|
|
94
94
|
name: string;
|
|
95
95
|
version: string;
|
|
96
96
|
type: string;
|
|
97
|
+
dependencies: string[];
|
|
97
98
|
private readonly options;
|
|
98
99
|
constructor(options?: RealtimeServicePluginOptions);
|
|
99
100
|
init(ctx: PluginContext): Promise<void>;
|
|
@@ -425,12 +426,8 @@ declare const SysPresence: Omit<{
|
|
|
425
426
|
keyPrefix?: string | undefined;
|
|
426
427
|
actions?: {
|
|
427
428
|
name: string;
|
|
428
|
-
label: string
|
|
429
|
-
|
|
430
|
-
defaultValue?: string | undefined;
|
|
431
|
-
params?: Record<string, string | number | boolean> | undefined;
|
|
432
|
-
};
|
|
433
|
-
type: "url" | "script" | "modal" | "flow" | "api";
|
|
429
|
+
label: string;
|
|
430
|
+
type: "url" | "flow" | "api" | "script" | "modal";
|
|
434
431
|
refreshAfter: boolean;
|
|
435
432
|
objectName?: string | undefined;
|
|
436
433
|
icon?: string | undefined;
|
|
@@ -440,44 +437,24 @@ declare const SysPresence: Omit<{
|
|
|
440
437
|
execute?: string | undefined;
|
|
441
438
|
params?: {
|
|
442
439
|
name: string;
|
|
443
|
-
label: string
|
|
444
|
-
key: string;
|
|
445
|
-
defaultValue?: string | undefined;
|
|
446
|
-
params?: Record<string, string | number | boolean> | undefined;
|
|
447
|
-
};
|
|
440
|
+
label: string;
|
|
448
441
|
type: "number" | "boolean" | "date" | "lookup" | "file" | "url" | "json" | "text" | "textarea" | "email" | "phone" | "password" | "markdown" | "html" | "richtext" | "currency" | "percent" | "datetime" | "time" | "toggle" | "select" | "multiselect" | "radio" | "checkboxes" | "master_detail" | "tree" | "image" | "avatar" | "video" | "audio" | "formula" | "summary" | "autonumber" | "location" | "address" | "code" | "color" | "rating" | "slider" | "signature" | "qrcode" | "progress" | "tags" | "vector";
|
|
449
442
|
required: boolean;
|
|
450
443
|
options?: {
|
|
451
|
-
label: string
|
|
452
|
-
key: string;
|
|
453
|
-
defaultValue?: string | undefined;
|
|
454
|
-
params?: Record<string, string | number | boolean> | undefined;
|
|
455
|
-
};
|
|
444
|
+
label: string;
|
|
456
445
|
value: string;
|
|
457
446
|
}[] | undefined;
|
|
458
447
|
}[] | undefined;
|
|
459
448
|
variant?: "link" | "primary" | "secondary" | "danger" | "ghost" | undefined;
|
|
460
|
-
confirmText?: string |
|
|
461
|
-
|
|
462
|
-
defaultValue?: string | undefined;
|
|
463
|
-
params?: Record<string, string | number | boolean> | undefined;
|
|
464
|
-
} | undefined;
|
|
465
|
-
successMessage?: string | {
|
|
466
|
-
key: string;
|
|
467
|
-
defaultValue?: string | undefined;
|
|
468
|
-
params?: Record<string, string | number | boolean> | undefined;
|
|
469
|
-
} | undefined;
|
|
449
|
+
confirmText?: string | undefined;
|
|
450
|
+
successMessage?: string | undefined;
|
|
470
451
|
visible?: string | undefined;
|
|
471
452
|
disabled?: string | boolean | undefined;
|
|
472
453
|
shortcut?: string | undefined;
|
|
473
454
|
bulkEnabled?: boolean | undefined;
|
|
474
455
|
timeout?: number | undefined;
|
|
475
456
|
aria?: {
|
|
476
|
-
ariaLabel?: string |
|
|
477
|
-
key: string;
|
|
478
|
-
defaultValue?: string | undefined;
|
|
479
|
-
params?: Record<string, string | number | boolean> | undefined;
|
|
480
|
-
} | undefined;
|
|
457
|
+
ariaLabel?: string | undefined;
|
|
481
458
|
ariaDescribedBy?: string | undefined;
|
|
482
459
|
role?: string | undefined;
|
|
483
460
|
} | undefined;
|
package/dist/index.js
CHANGED
|
@@ -173,12 +173,13 @@ var RealtimeServicePlugin = class {
|
|
|
173
173
|
this.name = "com.objectstack.service.realtime";
|
|
174
174
|
this.version = "1.0.0";
|
|
175
175
|
this.type = "standard";
|
|
176
|
+
this.dependencies = ["com.objectstack.engine.objectql"];
|
|
176
177
|
this.options = { adapter: "memory", ...options };
|
|
177
178
|
}
|
|
178
179
|
async init(ctx) {
|
|
179
180
|
const realtime = new InMemoryRealtimeAdapter(this.options.memory);
|
|
180
181
|
ctx.registerService("realtime", realtime);
|
|
181
|
-
ctx.
|
|
182
|
+
ctx.getService("manifest").register({
|
|
182
183
|
id: "com.objectstack.service.realtime",
|
|
183
184
|
name: "Realtime Service",
|
|
184
185
|
version: "1.0.0",
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/in-memory-realtime-adapter.ts","../src/objects/sys-presence.object.ts","../src/realtime-service-plugin.ts"],"sourcesContent":["// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type {\n IRealtimeService,\n RealtimeEventPayload,\n RealtimeEventHandler,\n RealtimeSubscriptionOptions,\n} from '@objectstack/spec/contracts';\n\n/**\n * Internal subscription entry.\n */\ninterface Subscription {\n id: string;\n channel: string;\n handler: RealtimeEventHandler;\n options?: RealtimeSubscriptionOptions;\n}\n\n/**\n * Configuration options for InMemoryRealtimeAdapter.\n */\nexport interface InMemoryRealtimeAdapterOptions {\n /** Maximum number of subscriptions allowed (0 = unlimited) */\n maxSubscriptions?: number;\n}\n\n/**\n * In-memory pub/sub adapter implementing IRealtimeService.\n *\n * Uses a Map-backed subscription store with channel-based routing.\n * Supports event type and object filtering via subscription options.\n *\n * Suitable for single-process environments, development, and testing.\n * For production multi-instance deployments, use a Redis-backed adapter.\n *\n * @example\n * ```ts\n * const realtime = new InMemoryRealtimeAdapter();\n *\n * const subId = await realtime.subscribe('records', (event) => {\n * console.log('Received:', event.type, event.payload);\n * }, { object: 'account', eventTypes: ['record.created'] });\n *\n * await realtime.publish({\n * type: 'record.created',\n * object: 'account',\n * payload: { id: 'acc-1', name: 'Acme' },\n * timestamp: new Date().toISOString(),\n * });\n *\n * await realtime.unsubscribe(subId);\n * ```\n */\nexport class InMemoryRealtimeAdapter implements IRealtimeService {\n private readonly subscriptions = new Map<string, Subscription>();\n private readonly channelIndex = new Map<string, Set<string>>();\n private counter = 0;\n private readonly maxSubscriptions: number;\n\n constructor(options: InMemoryRealtimeAdapterOptions = {}) {\n this.maxSubscriptions = options.maxSubscriptions ?? 0;\n }\n\n async publish(event: RealtimeEventPayload): Promise<void> {\n // Deliver to all channel subscriptions that match filters\n for (const sub of this.subscriptions.values()) {\n if (this.matchesSubscription(event, sub)) {\n try {\n await sub.handler(event);\n } catch {\n // Swallow handler errors to avoid breaking the publish loop\n }\n }\n }\n }\n\n async subscribe(\n channel: string,\n handler: RealtimeEventHandler,\n options?: RealtimeSubscriptionOptions,\n ): Promise<string> {\n if (this.maxSubscriptions > 0 && this.subscriptions.size >= this.maxSubscriptions) {\n throw new Error(\n `Maximum subscription limit reached (${this.maxSubscriptions}). ` +\n 'Unsubscribe from existing channels before adding new subscriptions.',\n );\n }\n\n const id = `sub-${++this.counter}`;\n const sub: Subscription = { id, channel, handler, options };\n this.subscriptions.set(id, sub);\n\n // Maintain channel index for efficient lookups\n if (!this.channelIndex.has(channel)) {\n this.channelIndex.set(channel, new Set());\n }\n this.channelIndex.get(channel)!.add(id);\n\n return id;\n }\n\n async unsubscribe(subscriptionId: string): Promise<void> {\n const sub = this.subscriptions.get(subscriptionId);\n if (!sub) return;\n\n this.subscriptions.delete(subscriptionId);\n\n // Clean up channel index\n const channelSubs = this.channelIndex.get(sub.channel);\n if (channelSubs) {\n channelSubs.delete(subscriptionId);\n if (channelSubs.size === 0) {\n this.channelIndex.delete(sub.channel);\n }\n }\n }\n\n /**\n * Get the number of active subscriptions.\n */\n getSubscriptionCount(): number {\n return this.subscriptions.size;\n }\n\n /**\n * Get all active channel names.\n */\n getChannels(): string[] {\n return Array.from(this.channelIndex.keys());\n }\n\n /**\n * Check if an event matches a subscription's filters.\n */\n private matchesSubscription(event: RealtimeEventPayload, sub: Subscription): boolean {\n const opts = sub.options;\n if (!opts) return true;\n\n // Filter by object name\n if (opts.object && event.object !== opts.object) {\n return false;\n }\n\n // Filter by event types\n if (opts.eventTypes && opts.eventTypes.length > 0) {\n if (!opts.eventTypes.includes(event.type)) {\n return false;\n }\n }\n\n return true;\n }\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { ObjectSchema, Field } from '@objectstack/spec/data';\n\n/**\n * sys_presence — System Presence Object\n *\n * Tracks real-time user presence and activity across the platform.\n * Fields align with the PresenceStateSchema protocol definition\n * from `@objectstack/spec/api` (websocket.zod.ts).\n *\n * Owned by `service-realtime` as the canonical Presence domain object.\n *\n * @namespace sys\n * @see PresenceStateSchema in packages/spec/src/api/websocket.zod.ts\n */\nexport const SysPresence = ObjectSchema.create({\n namespace: 'sys',\n name: 'presence',\n label: 'Presence',\n pluralLabel: 'Presences',\n icon: 'wifi',\n isSystem: true,\n description: 'Real-time user presence and activity tracking',\n titleFormat: '{user_id} ({status})',\n compactLayout: ['user_id', 'status', 'last_seen'],\n\n fields: {\n id: Field.text({\n label: 'Presence ID',\n required: true,\n readonly: true,\n }),\n\n created_at: Field.datetime({\n label: 'Created At',\n defaultValue: 'NOW()',\n readonly: true,\n }),\n\n updated_at: Field.datetime({\n label: 'Updated At',\n defaultValue: 'NOW()',\n readonly: true,\n }),\n\n user_id: Field.text({\n label: 'User ID',\n required: true,\n searchable: true,\n }),\n\n session_id: Field.text({\n label: 'Session ID',\n required: true,\n }),\n\n status: Field.select({\n label: 'Status',\n required: true,\n defaultValue: 'online',\n options: [\n { value: 'online', label: 'Online' },\n { value: 'away', label: 'Away' },\n { value: 'busy', label: 'Busy' },\n { value: 'offline', label: 'Offline' },\n ],\n }),\n\n last_seen: Field.datetime({\n label: 'Last Seen',\n required: true,\n defaultValue: 'NOW()',\n }),\n\n current_location: Field.text({\n label: 'Current Location',\n required: false,\n maxLength: 500,\n }),\n\n device: Field.select({\n label: 'Device',\n required: false,\n options: [\n { value: 'desktop', label: 'Desktop' },\n { value: 'mobile', label: 'Mobile' },\n { value: 'tablet', label: 'Tablet' },\n { value: 'other', label: 'Other' },\n ],\n }),\n\n custom_status: Field.text({\n label: 'Custom Status',\n required: false,\n maxLength: 255,\n }),\n\n metadata: Field.json({\n label: 'Metadata',\n required: false,\n description: 'Arbitrary JSON metadata associated with the presence state (matches PresenceStateSchema.metadata).',\n }),\n },\n\n indexes: [\n { fields: ['user_id'], unique: false },\n { fields: ['session_id'], unique: true },\n { fields: ['status'], unique: false },\n ],\n\n enable: {\n trackHistory: false,\n searchable: false,\n apiEnabled: true,\n apiMethods: ['get', 'list', 'create', 'update', 'delete'],\n trash: false,\n mru: false,\n },\n});\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { Plugin, PluginContext } from '@objectstack/core';\nimport { InMemoryRealtimeAdapter } from './in-memory-realtime-adapter.js';\nimport type { InMemoryRealtimeAdapterOptions } from './in-memory-realtime-adapter.js';\nimport { SysPresence } from './objects/index.js';\n\n/**\n * Configuration options for the RealtimeServicePlugin.\n */\nexport interface RealtimeServicePluginOptions {\n /** Realtime adapter type (default: 'memory') */\n adapter?: 'memory';\n /** Options for the in-memory adapter */\n memory?: InMemoryRealtimeAdapterOptions;\n}\n\n/**\n * RealtimeServicePlugin — Production IRealtimeService implementation.\n *\n * Registers a realtime pub/sub service with the kernel during the init phase.\n * Currently supports in-memory pub/sub for single-process environments.\n *\n * @example\n * ```ts\n * import { ObjectKernel } from '@objectstack/core';\n * import { RealtimeServicePlugin } from '@objectstack/service-realtime';\n *\n * const kernel = new ObjectKernel();\n * kernel.use(new RealtimeServicePlugin());\n * await kernel.bootstrap();\n *\n * const realtime = kernel.getService('realtime');\n * await realtime.subscribe('records', (event) => {\n * console.log(event.type, event.payload);\n * });\n * ```\n */\nexport class RealtimeServicePlugin implements Plugin {\n name = 'com.objectstack.service.realtime';\n version = '1.0.0';\n type = 'standard';\n\n private readonly options: RealtimeServicePluginOptions;\n\n constructor(options: RealtimeServicePluginOptions = {}) {\n this.options = { adapter: 'memory', ...options };\n }\n\n async init(ctx: PluginContext): Promise<void> {\n const realtime = new InMemoryRealtimeAdapter(this.options.memory);\n ctx.registerService('realtime', realtime);\n\n // Register realtime system objects so ObjectQLPlugin auto-discovers them\n ctx.registerService('app.com.objectstack.service.realtime', {\n id: 'com.objectstack.service.realtime',\n name: 'Realtime Service',\n version: '1.0.0',\n type: 'plugin',\n namespace: 'sys',\n objects: [SysPresence],\n });\n\n ctx.logger.info('RealtimeServicePlugin: registered in-memory realtime adapter');\n }\n}\n"],"mappings":";AAsDO,IAAM,0BAAN,MAA0D;AAAA,EAM/D,YAAY,UAA0C,CAAC,GAAG;AAL1D,SAAiB,gBAAgB,oBAAI,IAA0B;AAC/D,SAAiB,eAAe,oBAAI,IAAyB;AAC7D,SAAQ,UAAU;AAIhB,SAAK,mBAAmB,QAAQ,oBAAoB;AAAA,EACtD;AAAA,EAEA,MAAM,QAAQ,OAA4C;AAExD,eAAW,OAAO,KAAK,cAAc,OAAO,GAAG;AAC7C,UAAI,KAAK,oBAAoB,OAAO,GAAG,GAAG;AACxC,YAAI;AACF,gBAAM,IAAI,QAAQ,KAAK;AAAA,QACzB,QAAQ;AAAA,QAER;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,UACJ,SACA,SACA,SACiB;AACjB,QAAI,KAAK,mBAAmB,KAAK,KAAK,cAAc,QAAQ,KAAK,kBAAkB;AACjF,YAAM,IAAI;AAAA,QACR,uCAAuC,KAAK,gBAAgB;AAAA,MAE9D;AAAA,IACF;AAEA,UAAM,KAAK,OAAO,EAAE,KAAK,OAAO;AAChC,UAAM,MAAoB,EAAE,IAAI,SAAS,SAAS,QAAQ;AAC1D,SAAK,cAAc,IAAI,IAAI,GAAG;AAG9B,QAAI,CAAC,KAAK,aAAa,IAAI,OAAO,GAAG;AACnC,WAAK,aAAa,IAAI,SAAS,oBAAI,IAAI,CAAC;AAAA,IAC1C;AACA,SAAK,aAAa,IAAI,OAAO,EAAG,IAAI,EAAE;AAEtC,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,YAAY,gBAAuC;AACvD,UAAM,MAAM,KAAK,cAAc,IAAI,cAAc;AACjD,QAAI,CAAC,IAAK;AAEV,SAAK,cAAc,OAAO,cAAc;AAGxC,UAAM,cAAc,KAAK,aAAa,IAAI,IAAI,OAAO;AACrD,QAAI,aAAa;AACf,kBAAY,OAAO,cAAc;AACjC,UAAI,YAAY,SAAS,GAAG;AAC1B,aAAK,aAAa,OAAO,IAAI,OAAO;AAAA,MACtC;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,uBAA+B;AAC7B,WAAO,KAAK,cAAc;AAAA,EAC5B;AAAA;AAAA;AAAA;AAAA,EAKA,cAAwB;AACtB,WAAO,MAAM,KAAK,KAAK,aAAa,KAAK,CAAC;AAAA,EAC5C;AAAA;AAAA;AAAA;AAAA,EAKQ,oBAAoB,OAA6B,KAA4B;AACnF,UAAM,OAAO,IAAI;AACjB,QAAI,CAAC,KAAM,QAAO;AAGlB,QAAI,KAAK,UAAU,MAAM,WAAW,KAAK,QAAQ;AAC/C,aAAO;AAAA,IACT;AAGA,QAAI,KAAK,cAAc,KAAK,WAAW,SAAS,GAAG;AACjD,UAAI,CAAC,KAAK,WAAW,SAAS,MAAM,IAAI,GAAG;AACzC,eAAO;AAAA,MACT;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AACF;;;ACvJA,SAAS,cAAc,aAAa;AAc7B,IAAM,cAAc,aAAa,OAAO;AAAA,EAC7C,WAAW;AAAA,EACX,MAAM;AAAA,EACN,OAAO;AAAA,EACP,aAAa;AAAA,EACb,MAAM;AAAA,EACN,UAAU;AAAA,EACV,aAAa;AAAA,EACb,aAAa;AAAA,EACb,eAAe,CAAC,WAAW,UAAU,WAAW;AAAA,EAEhD,QAAQ;AAAA,IACN,IAAI,MAAM,KAAK;AAAA,MACb,OAAO;AAAA,MACP,UAAU;AAAA,MACV,UAAU;AAAA,IACZ,CAAC;AAAA,IAED,YAAY,MAAM,SAAS;AAAA,MACzB,OAAO;AAAA,MACP,cAAc;AAAA,MACd,UAAU;AAAA,IACZ,CAAC;AAAA,IAED,YAAY,MAAM,SAAS;AAAA,MACzB,OAAO;AAAA,MACP,cAAc;AAAA,MACd,UAAU;AAAA,IACZ,CAAC;AAAA,IAED,SAAS,MAAM,KAAK;AAAA,MAClB,OAAO;AAAA,MACP,UAAU;AAAA,MACV,YAAY;AAAA,IACd,CAAC;AAAA,IAED,YAAY,MAAM,KAAK;AAAA,MACrB,OAAO;AAAA,MACP,UAAU;AAAA,IACZ,CAAC;AAAA,IAED,QAAQ,MAAM,OAAO;AAAA,MACnB,OAAO;AAAA,MACP,UAAU;AAAA,MACV,cAAc;AAAA,MACd,SAAS;AAAA,QACP,EAAE,OAAO,UAAU,OAAO,SAAS;AAAA,QACnC,EAAE,OAAO,QAAQ,OAAO,OAAO;AAAA,QAC/B,EAAE,OAAO,QAAQ,OAAO,OAAO;AAAA,QAC/B,EAAE,OAAO,WAAW,OAAO,UAAU;AAAA,MACvC;AAAA,IACF,CAAC;AAAA,IAED,WAAW,MAAM,SAAS;AAAA,MACxB,OAAO;AAAA,MACP,UAAU;AAAA,MACV,cAAc;AAAA,IAChB,CAAC;AAAA,IAED,kBAAkB,MAAM,KAAK;AAAA,MAC3B,OAAO;AAAA,MACP,UAAU;AAAA,MACV,WAAW;AAAA,IACb,CAAC;AAAA,IAED,QAAQ,MAAM,OAAO;AAAA,MACnB,OAAO;AAAA,MACP,UAAU;AAAA,MACV,SAAS;AAAA,QACP,EAAE,OAAO,WAAW,OAAO,UAAU;AAAA,QACrC,EAAE,OAAO,UAAU,OAAO,SAAS;AAAA,QACnC,EAAE,OAAO,UAAU,OAAO,SAAS;AAAA,QACnC,EAAE,OAAO,SAAS,OAAO,QAAQ;AAAA,MACnC;AAAA,IACF,CAAC;AAAA,IAED,eAAe,MAAM,KAAK;AAAA,MACxB,OAAO;AAAA,MACP,UAAU;AAAA,MACV,WAAW;AAAA,IACb,CAAC;AAAA,IAED,UAAU,MAAM,KAAK;AAAA,MACnB,OAAO;AAAA,MACP,UAAU;AAAA,MACV,aAAa;AAAA,IACf,CAAC;AAAA,EACH;AAAA,EAEA,SAAS;AAAA,IACP,EAAE,QAAQ,CAAC,SAAS,GAAG,QAAQ,MAAM;AAAA,IACrC,EAAE,QAAQ,CAAC,YAAY,GAAG,QAAQ,KAAK;AAAA,IACvC,EAAE,QAAQ,CAAC,QAAQ,GAAG,QAAQ,MAAM;AAAA,EACtC;AAAA,EAEA,QAAQ;AAAA,IACN,cAAc;AAAA,IACd,YAAY;AAAA,IACZ,YAAY;AAAA,IACZ,YAAY,CAAC,OAAO,QAAQ,UAAU,UAAU,QAAQ;AAAA,IACxD,OAAO;AAAA,IACP,KAAK;AAAA,EACP;AACF,CAAC;;;ACjFM,IAAM,wBAAN,MAA8C;AAAA,EAOnD,YAAY,UAAwC,CAAC,GAAG;AANxD,gBAAO;AACP,mBAAU;AACV,gBAAO;AAKL,SAAK,UAAU,EAAE,SAAS,UAAU,GAAG,QAAQ;AAAA,EACjD;AAAA,EAEA,MAAM,KAAK,KAAmC;AAC5C,UAAM,WAAW,IAAI,wBAAwB,KAAK,QAAQ,MAAM;AAChE,QAAI,gBAAgB,YAAY,QAAQ;AAGxC,QAAI,gBAAgB,wCAAwC;AAAA,MAC1D,IAAI;AAAA,MACJ,MAAM;AAAA,MACN,SAAS;AAAA,MACT,MAAM;AAAA,MACN,WAAW;AAAA,MACX,SAAS,CAAC,WAAW;AAAA,IACvB,CAAC;AAED,QAAI,OAAO,KAAK,8DAA8D;AAAA,EAChF;AACF;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/in-memory-realtime-adapter.ts","../src/objects/sys-presence.object.ts","../src/realtime-service-plugin.ts"],"sourcesContent":["// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type {\n IRealtimeService,\n RealtimeEventPayload,\n RealtimeEventHandler,\n RealtimeSubscriptionOptions,\n} from '@objectstack/spec/contracts';\n\n/**\n * Internal subscription entry.\n */\ninterface Subscription {\n id: string;\n channel: string;\n handler: RealtimeEventHandler;\n options?: RealtimeSubscriptionOptions;\n}\n\n/**\n * Configuration options for InMemoryRealtimeAdapter.\n */\nexport interface InMemoryRealtimeAdapterOptions {\n /** Maximum number of subscriptions allowed (0 = unlimited) */\n maxSubscriptions?: number;\n}\n\n/**\n * In-memory pub/sub adapter implementing IRealtimeService.\n *\n * Uses a Map-backed subscription store with channel-based routing.\n * Supports event type and object filtering via subscription options.\n *\n * Suitable for single-process environments, development, and testing.\n * For production multi-instance deployments, use a Redis-backed adapter.\n *\n * @example\n * ```ts\n * const realtime = new InMemoryRealtimeAdapter();\n *\n * const subId = await realtime.subscribe('records', (event) => {\n * console.log('Received:', event.type, event.payload);\n * }, { object: 'account', eventTypes: ['record.created'] });\n *\n * await realtime.publish({\n * type: 'record.created',\n * object: 'account',\n * payload: { id: 'acc-1', name: 'Acme' },\n * timestamp: new Date().toISOString(),\n * });\n *\n * await realtime.unsubscribe(subId);\n * ```\n */\nexport class InMemoryRealtimeAdapter implements IRealtimeService {\n private readonly subscriptions = new Map<string, Subscription>();\n private readonly channelIndex = new Map<string, Set<string>>();\n private counter = 0;\n private readonly maxSubscriptions: number;\n\n constructor(options: InMemoryRealtimeAdapterOptions = {}) {\n this.maxSubscriptions = options.maxSubscriptions ?? 0;\n }\n\n async publish(event: RealtimeEventPayload): Promise<void> {\n // Deliver to all channel subscriptions that match filters\n for (const sub of this.subscriptions.values()) {\n if (this.matchesSubscription(event, sub)) {\n try {\n await sub.handler(event);\n } catch {\n // Swallow handler errors to avoid breaking the publish loop\n }\n }\n }\n }\n\n async subscribe(\n channel: string,\n handler: RealtimeEventHandler,\n options?: RealtimeSubscriptionOptions,\n ): Promise<string> {\n if (this.maxSubscriptions > 0 && this.subscriptions.size >= this.maxSubscriptions) {\n throw new Error(\n `Maximum subscription limit reached (${this.maxSubscriptions}). ` +\n 'Unsubscribe from existing channels before adding new subscriptions.',\n );\n }\n\n const id = `sub-${++this.counter}`;\n const sub: Subscription = { id, channel, handler, options };\n this.subscriptions.set(id, sub);\n\n // Maintain channel index for efficient lookups\n if (!this.channelIndex.has(channel)) {\n this.channelIndex.set(channel, new Set());\n }\n this.channelIndex.get(channel)!.add(id);\n\n return id;\n }\n\n async unsubscribe(subscriptionId: string): Promise<void> {\n const sub = this.subscriptions.get(subscriptionId);\n if (!sub) return;\n\n this.subscriptions.delete(subscriptionId);\n\n // Clean up channel index\n const channelSubs = this.channelIndex.get(sub.channel);\n if (channelSubs) {\n channelSubs.delete(subscriptionId);\n if (channelSubs.size === 0) {\n this.channelIndex.delete(sub.channel);\n }\n }\n }\n\n /**\n * Get the number of active subscriptions.\n */\n getSubscriptionCount(): number {\n return this.subscriptions.size;\n }\n\n /**\n * Get all active channel names.\n */\n getChannels(): string[] {\n return Array.from(this.channelIndex.keys());\n }\n\n /**\n * Check if an event matches a subscription's filters.\n */\n private matchesSubscription(event: RealtimeEventPayload, sub: Subscription): boolean {\n const opts = sub.options;\n if (!opts) return true;\n\n // Filter by object name\n if (opts.object && event.object !== opts.object) {\n return false;\n }\n\n // Filter by event types\n if (opts.eventTypes && opts.eventTypes.length > 0) {\n if (!opts.eventTypes.includes(event.type)) {\n return false;\n }\n }\n\n return true;\n }\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { ObjectSchema, Field } from '@objectstack/spec/data';\n\n/**\n * sys_presence — System Presence Object\n *\n * Tracks real-time user presence and activity across the platform.\n * Fields align with the PresenceStateSchema protocol definition\n * from `@objectstack/spec/api` (websocket.zod.ts).\n *\n * Owned by `service-realtime` as the canonical Presence domain object.\n *\n * @namespace sys\n * @see PresenceStateSchema in packages/spec/src/api/websocket.zod.ts\n */\nexport const SysPresence = ObjectSchema.create({\n namespace: 'sys',\n name: 'presence',\n label: 'Presence',\n pluralLabel: 'Presences',\n icon: 'wifi',\n isSystem: true,\n description: 'Real-time user presence and activity tracking',\n titleFormat: '{user_id} ({status})',\n compactLayout: ['user_id', 'status', 'last_seen'],\n\n fields: {\n id: Field.text({\n label: 'Presence ID',\n required: true,\n readonly: true,\n }),\n\n created_at: Field.datetime({\n label: 'Created At',\n defaultValue: 'NOW()',\n readonly: true,\n }),\n\n updated_at: Field.datetime({\n label: 'Updated At',\n defaultValue: 'NOW()',\n readonly: true,\n }),\n\n user_id: Field.text({\n label: 'User ID',\n required: true,\n searchable: true,\n }),\n\n session_id: Field.text({\n label: 'Session ID',\n required: true,\n }),\n\n status: Field.select({\n label: 'Status',\n required: true,\n defaultValue: 'online',\n options: [\n { value: 'online', label: 'Online' },\n { value: 'away', label: 'Away' },\n { value: 'busy', label: 'Busy' },\n { value: 'offline', label: 'Offline' },\n ],\n }),\n\n last_seen: Field.datetime({\n label: 'Last Seen',\n required: true,\n defaultValue: 'NOW()',\n }),\n\n current_location: Field.text({\n label: 'Current Location',\n required: false,\n maxLength: 500,\n }),\n\n device: Field.select({\n label: 'Device',\n required: false,\n options: [\n { value: 'desktop', label: 'Desktop' },\n { value: 'mobile', label: 'Mobile' },\n { value: 'tablet', label: 'Tablet' },\n { value: 'other', label: 'Other' },\n ],\n }),\n\n custom_status: Field.text({\n label: 'Custom Status',\n required: false,\n maxLength: 255,\n }),\n\n metadata: Field.json({\n label: 'Metadata',\n required: false,\n description: 'Arbitrary JSON metadata associated with the presence state (matches PresenceStateSchema.metadata).',\n }),\n },\n\n indexes: [\n { fields: ['user_id'], unique: false },\n { fields: ['session_id'], unique: true },\n { fields: ['status'], unique: false },\n ],\n\n enable: {\n trackHistory: false,\n searchable: false,\n apiEnabled: true,\n apiMethods: ['get', 'list', 'create', 'update', 'delete'],\n trash: false,\n mru: false,\n },\n});\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { Plugin, PluginContext } from '@objectstack/core';\nimport { InMemoryRealtimeAdapter } from './in-memory-realtime-adapter.js';\nimport type { InMemoryRealtimeAdapterOptions } from './in-memory-realtime-adapter.js';\nimport { SysPresence } from './objects/index.js';\n\n/**\n * Configuration options for the RealtimeServicePlugin.\n */\nexport interface RealtimeServicePluginOptions {\n /** Realtime adapter type (default: 'memory') */\n adapter?: 'memory';\n /** Options for the in-memory adapter */\n memory?: InMemoryRealtimeAdapterOptions;\n}\n\n/**\n * RealtimeServicePlugin — Production IRealtimeService implementation.\n *\n * Registers a realtime pub/sub service with the kernel during the init phase.\n * Currently supports in-memory pub/sub for single-process environments.\n *\n * @example\n * ```ts\n * import { ObjectKernel } from '@objectstack/core';\n * import { RealtimeServicePlugin } from '@objectstack/service-realtime';\n *\n * const kernel = new ObjectKernel();\n * kernel.use(new RealtimeServicePlugin());\n * await kernel.bootstrap();\n *\n * const realtime = kernel.getService('realtime');\n * await realtime.subscribe('records', (event) => {\n * console.log(event.type, event.payload);\n * });\n * ```\n */\nexport class RealtimeServicePlugin implements Plugin {\n name = 'com.objectstack.service.realtime';\n version = '1.0.0';\n type = 'standard';\n dependencies = ['com.objectstack.engine.objectql'];\n\n private readonly options: RealtimeServicePluginOptions;\n\n constructor(options: RealtimeServicePluginOptions = {}) {\n this.options = { adapter: 'memory', ...options };\n }\n\n async init(ctx: PluginContext): Promise<void> {\n const realtime = new InMemoryRealtimeAdapter(this.options.memory);\n ctx.registerService('realtime', realtime);\n\n // Register realtime system objects via the manifest service.\n ctx.getService<{ register(m: any): void }>('manifest').register({\n id: 'com.objectstack.service.realtime',\n name: 'Realtime Service',\n version: '1.0.0',\n type: 'plugin',\n namespace: 'sys',\n objects: [SysPresence],\n });\n\n ctx.logger.info('RealtimeServicePlugin: registered in-memory realtime adapter');\n }\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@objectstack/service-realtime",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.3",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"description": "Realtime Service for ObjectStack — implements IRealtimeService with WebSocket and in-memory pub/sub",
|
|
6
6
|
"type": "module",
|
|
@@ -14,13 +14,13 @@
|
|
|
14
14
|
}
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"@objectstack/core": "4.0.
|
|
18
|
-
"@objectstack/spec": "4.0.
|
|
17
|
+
"@objectstack/core": "4.0.3",
|
|
18
|
+
"@objectstack/spec": "4.0.3"
|
|
19
19
|
},
|
|
20
20
|
"devDependencies": {
|
|
21
|
-
"@types/node": "^25.
|
|
21
|
+
"@types/node": "^25.6.0",
|
|
22
22
|
"typescript": "^6.0.2",
|
|
23
|
-
"vitest": "^4.1.
|
|
23
|
+
"vitest": "^4.1.4"
|
|
24
24
|
},
|
|
25
25
|
"scripts": {
|
|
26
26
|
"build": "tsup --config ../../../tsup.config.ts",
|
|
@@ -40,6 +40,7 @@ export class RealtimeServicePlugin implements Plugin {
|
|
|
40
40
|
name = 'com.objectstack.service.realtime';
|
|
41
41
|
version = '1.0.0';
|
|
42
42
|
type = 'standard';
|
|
43
|
+
dependencies = ['com.objectstack.engine.objectql'];
|
|
43
44
|
|
|
44
45
|
private readonly options: RealtimeServicePluginOptions;
|
|
45
46
|
|
|
@@ -51,8 +52,8 @@ export class RealtimeServicePlugin implements Plugin {
|
|
|
51
52
|
const realtime = new InMemoryRealtimeAdapter(this.options.memory);
|
|
52
53
|
ctx.registerService('realtime', realtime);
|
|
53
54
|
|
|
54
|
-
// Register realtime system objects
|
|
55
|
-
ctx.
|
|
55
|
+
// Register realtime system objects via the manifest service.
|
|
56
|
+
ctx.getService<{ register(m: any): void }>('manifest').register({
|
|
56
57
|
id: 'com.objectstack.service.realtime',
|
|
57
58
|
name: 'Realtime Service',
|
|
58
59
|
version: '1.0.0',
|