@pikku/mongodb 0.12.3 → 0.12.4

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,19 @@
1
1
  # @pikku/mongodb
2
2
 
3
+ ## 0.12.4
4
+
5
+ ### Patch Changes
6
+
7
+ - 311c0c4: Unify session persistence through SessionStore, remove session blob from ChannelStore
8
+
9
+ - PikkuSessionService now persists sessions via SessionStore on set()/clear() instead of every function call
10
+ - ChannelStore no longer stores session data — maps channelId to pikkuUserId only
11
+ - ChannelStore API: setUserSession/getChannelAndSession replaced with setPikkuUserId/getChannel
12
+ - Serverless channel runner resolves sessions from SessionStore using pikkuUserId from ChannelStore
13
+
14
+ - Updated dependencies [311c0c4]
15
+ - @pikku/core@0.12.18
16
+
3
17
  ## 0.12.3
4
18
 
5
19
  ### Patch Changes
@@ -10,3 +10,4 @@ export { MongoDBSecretService } from './mongodb-secret-service.js';
10
10
  export type { MongoDBSecretServiceConfig } from './mongodb-secret-service.js';
11
11
  export type { WorkflowRunService } from '@pikku/core/workflow';
12
12
  export type { AgentRunService, AgentRunRow } from '@pikku/core/ai-agent';
13
+ export { MongoDBSessionStore } from './mongodb-session-store.js';
package/dist/src/index.js CHANGED
@@ -7,3 +7,4 @@ export { MongoDBDeploymentService } from './mongodb-deployment-service.js';
7
7
  export { MongoDBAIStorageService } from './mongodb-ai-storage-service.js';
8
8
  export { MongoDBAgentRunService } from './mongodb-agent-run-service.js';
9
9
  export { MongoDBSecretService } from './mongodb-secret-service.js';
10
+ export { MongoDBSessionStore } from './mongodb-session-store.js';
@@ -1,4 +1,3 @@
1
- import type { CoreUserSession } from '@pikku/core';
2
1
  import type { Channel } from '@pikku/core/channel';
3
2
  import { ChannelStore } from '@pikku/core/channel';
4
3
  import type { Db } from 'mongodb';
@@ -11,9 +10,9 @@ export declare class MongoDBChannelStore extends ChannelStore {
11
10
  init(): Promise<void>;
12
11
  addChannel({ channelId, channelName, openingData, }: Channel): Promise<void>;
13
12
  removeChannels(channelIds: string[]): Promise<void>;
14
- setUserSession(channelId: string, session: CoreUserSession | null): Promise<void>;
15
- getChannelAndSession(channelId: string): Promise<Channel & {
16
- session: CoreUserSession;
13
+ setPikkuUserId(channelId: string, pikkuUserId: string | null): Promise<void>;
14
+ getChannel(channelId: string): Promise<Channel & {
15
+ pikkuUserId?: string;
17
16
  }>;
18
17
  close(): Promise<void>;
19
18
  }
@@ -22,7 +22,7 @@ export class MongoDBChannelStore extends ChannelStore {
22
22
  _id: channelId,
23
23
  channelName,
24
24
  openingData: openingData || {},
25
- userSession: null,
25
+ pikkuUserId: null,
26
26
  createdAt: new Date(),
27
27
  lastWire: new Date(),
28
28
  });
@@ -33,10 +33,10 @@ export class MongoDBChannelStore extends ChannelStore {
33
33
  await this.channels.deleteMany({ _id: { $in: channelIds } });
34
34
  await this.subscriptions.deleteMany({ channelId: { $in: channelIds } });
35
35
  }
36
- async setUserSession(channelId, session) {
37
- await this.channels.updateOne({ _id: channelId }, { $set: { userSession: session } });
36
+ async setPikkuUserId(channelId, pikkuUserId) {
37
+ await this.channels.updateOne({ _id: channelId }, { $set: { pikkuUserId } });
38
38
  }
39
- async getChannelAndSession(channelId) {
39
+ async getChannel(channelId) {
40
40
  const row = await this.channels.findOne({ _id: channelId });
41
41
  if (!row) {
42
42
  throw new Error(`Channel not found: ${channelId}`);
@@ -45,7 +45,7 @@ export class MongoDBChannelStore extends ChannelStore {
45
45
  channelId: row._id,
46
46
  channelName: row.channelName,
47
47
  openingData: row.openingData ?? {},
48
- session: (row.userSession ?? {}),
48
+ pikkuUserId: row.pikkuUserId ?? undefined,
49
49
  };
50
50
  }
51
51
  async close() { }
@@ -1,17 +1,20 @@
1
- import type { DeploymentService, DeploymentServiceConfig, DeploymentConfig, DeploymentInfo } from '@pikku/core/services';
1
+ import type { DeploymentService, DeploymentServiceConfig, DeploymentConfig } from '@pikku/core/services';
2
+ import type { JWTService, SecretService } from '@pikku/core/services';
2
3
  import type { Db } from 'mongodb';
3
4
  export declare class MongoDBDeploymentService implements DeploymentService {
4
5
  private db;
6
+ private jwt?;
7
+ private secrets?;
5
8
  private initialized;
6
9
  private heartbeatTimer?;
7
10
  private deploymentConfig?;
8
11
  private heartbeatInterval;
9
12
  private heartbeatTtl;
10
13
  private deployments;
11
- constructor(config: DeploymentServiceConfig, db: Db);
14
+ constructor(config: DeploymentServiceConfig, db: Db, jwt?: JWTService | undefined, secrets?: SecretService | undefined);
12
15
  init(): Promise<void>;
13
16
  start(config: DeploymentConfig): Promise<void>;
14
17
  stop(): Promise<void>;
15
- findFunction(name: string): Promise<DeploymentInfo[]>;
18
+ invoke(funcName: string, data: unknown, session?: unknown, traceId?: string): Promise<unknown>;
16
19
  private sendHeartbeat;
17
20
  }
@@ -1,14 +1,19 @@
1
+ import { buildRemoteHeaders } from '@pikku/core/remote';
1
2
  import { getAllFunctionNames } from '@pikku/core/function';
2
3
  export class MongoDBDeploymentService {
3
4
  db;
5
+ jwt;
6
+ secrets;
4
7
  initialized = false;
5
8
  heartbeatTimer;
6
9
  deploymentConfig;
7
10
  heartbeatInterval;
8
11
  heartbeatTtl;
9
12
  deployments;
10
- constructor(config, db) {
13
+ constructor(config, db, jwt, secrets) {
11
14
  this.db = db;
15
+ this.jwt = jwt;
16
+ this.secrets = secrets;
12
17
  this.heartbeatInterval = config.heartbeatInterval ?? 10000;
13
18
  this.heartbeatTtl = config.heartbeatTtl ?? 30000;
14
19
  }
@@ -48,19 +53,23 @@ export class MongoDBDeploymentService {
48
53
  });
49
54
  }
50
55
  }
51
- async findFunction(name) {
56
+ async invoke(funcName, data, session, traceId) {
57
+ const headers = await buildRemoteHeaders(this.jwt, this.secrets, funcName, session, traceId);
52
58
  const cutoff = new Date(Date.now() - this.heartbeatTtl);
53
- const result = await this.deployments
54
- .find({
55
- functions: name,
56
- lastHeartbeat: { $gt: cutoff },
57
- })
58
- .sort({ lastHeartbeat: -1 })
59
- .toArray();
60
- return result.map((row) => ({
61
- deploymentId: row._id,
62
- endpoint: row.endpoint,
63
- }));
59
+ const result = await this.deployments.findOne({ functions: funcName, lastHeartbeat: { $gt: cutoff } }, { sort: { lastHeartbeat: -1 } });
60
+ if (!result) {
61
+ throw new Error(`No deployment found for function '${funcName}'`);
62
+ }
63
+ const url = `${result.endpoint}/remote/rpc/${encodeURIComponent(funcName)}`;
64
+ const response = await fetch(url, {
65
+ method: 'POST',
66
+ headers: { 'Content-Type': 'application/json', ...headers },
67
+ body: JSON.stringify({ data }),
68
+ });
69
+ if (!response.ok) {
70
+ throw new Error(`Remote RPC call to '${funcName}' failed: ${response.status}`);
71
+ }
72
+ return response.json();
64
73
  }
65
74
  async sendHeartbeat() {
66
75
  if (!this.deploymentConfig)
@@ -0,0 +1,14 @@
1
+ import type { CoreUserSession } from '@pikku/core';
2
+ import type { SessionStore } from '@pikku/core/services';
3
+ import type { Db } from 'mongodb';
4
+ export declare class MongoDBSessionStore implements SessionStore {
5
+ private db;
6
+ private initialized;
7
+ private sessions;
8
+ constructor(db: Db);
9
+ init(): Promise<void>;
10
+ get(pikkuUserId: string): Promise<CoreUserSession | undefined>;
11
+ set(pikkuUserId: string, session: CoreUserSession): Promise<void>;
12
+ clear(pikkuUserId: string): Promise<void>;
13
+ close(): Promise<void>;
14
+ }
@@ -0,0 +1,32 @@
1
+ export class MongoDBSessionStore {
2
+ db;
3
+ initialized = false;
4
+ sessions;
5
+ constructor(db) {
6
+ this.db = db;
7
+ }
8
+ async init() {
9
+ if (this.initialized)
10
+ return;
11
+ this.sessions = this.db.collection('pikkuUserSessions');
12
+ this.initialized = true;
13
+ }
14
+ async get(pikkuUserId) {
15
+ const doc = await this.sessions.findOne({ _id: pikkuUserId });
16
+ if (!doc) {
17
+ return undefined;
18
+ }
19
+ return doc.session;
20
+ }
21
+ async set(pikkuUserId, session) {
22
+ const now = new Date();
23
+ await this.sessions.updateOne({ _id: pikkuUserId }, {
24
+ $set: { session, updatedAt: now },
25
+ $setOnInsert: { createdAt: now },
26
+ }, { upsert: true });
27
+ }
28
+ async clear(pikkuUserId) {
29
+ await this.sessions.deleteOne({ _id: pikkuUserId });
30
+ }
31
+ async close() { }
32
+ }
@@ -1 +1 @@
1
- {"root":["../src/index.ts","../src/mongodb-agent-run-service.ts","../src/mongodb-ai-storage-service.ts","../src/mongodb-channel-store.ts","../src/mongodb-deployment-service.ts","../src/mongodb-eventhub-store.ts","../src/mongodb-secret-service.ts","../src/mongodb-workflow-run-service.ts","../src/mongodb-workflow-service.ts","../src/pikku-mongodb.ts"],"version":"5.9.3"}
1
+ {"root":["../src/index.ts","../src/mongodb-agent-run-service.ts","../src/mongodb-ai-storage-service.ts","../src/mongodb-channel-store.ts","../src/mongodb-deployment-service.ts","../src/mongodb-eventhub-store.ts","../src/mongodb-secret-service.ts","../src/mongodb-session-store.ts","../src/mongodb-workflow-run-service.ts","../src/mongodb-workflow-service.ts","../src/pikku-mongodb.ts"],"version":"5.9.3"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pikku/mongodb",
3
- "version": "0.12.3",
3
+ "version": "0.12.4",
4
4
  "author": "yasser.fadl@gmail.com",
5
5
  "license": "MIT",
6
6
  "module": "dist/src/index.js",
@@ -17,7 +17,7 @@
17
17
  "prepublishOnly": "yarn build"
18
18
  },
19
19
  "peerDependencies": {
20
- "@pikku/core": "^0.12.9"
20
+ "@pikku/core": "^0.12.18"
21
21
  },
22
22
  "dependencies": {
23
23
  "mongodb": "^6.12.0"
package/src/index.ts CHANGED
@@ -12,3 +12,4 @@ export type { MongoDBSecretServiceConfig } from './mongodb-secret-service.js'
12
12
 
13
13
  export type { WorkflowRunService } from '@pikku/core/workflow'
14
14
  export type { AgentRunService, AgentRunRow } from '@pikku/core/ai-agent'
15
+ export { MongoDBSessionStore } from './mongodb-session-store.js'
@@ -1,4 +1,3 @@
1
- import type { CoreUserSession } from '@pikku/core'
2
1
  import type { Channel } from '@pikku/core/channel'
3
2
  import { ChannelStore } from '@pikku/core/channel'
4
3
  import type { Db, Collection } from 'mongodb'
@@ -8,7 +7,7 @@ interface ChannelDoc {
8
7
  channelName: string
9
8
  createdAt: Date
10
9
  openingData: any
11
- userSession: any | null
10
+ pikkuUserId: string | null
12
11
  lastWire: Date
13
12
  }
14
13
 
@@ -52,7 +51,7 @@ export class MongoDBChannelStore extends ChannelStore {
52
51
  _id: channelId,
53
52
  channelName,
54
53
  openingData: openingData || {},
55
- userSession: null,
54
+ pikkuUserId: null,
56
55
  createdAt: new Date(),
57
56
  lastWire: new Date(),
58
57
  })
@@ -65,19 +64,16 @@ export class MongoDBChannelStore extends ChannelStore {
65
64
  await this.subscriptions.deleteMany({ channelId: { $in: channelIds } })
66
65
  }
67
66
 
68
- public async setUserSession(
67
+ public async setPikkuUserId(
69
68
  channelId: string,
70
- session: CoreUserSession | null
69
+ pikkuUserId: string | null
71
70
  ): Promise<void> {
72
- await this.channels.updateOne(
73
- { _id: channelId },
74
- { $set: { userSession: session } }
75
- )
71
+ await this.channels.updateOne({ _id: channelId }, { $set: { pikkuUserId } })
76
72
  }
77
73
 
78
- public async getChannelAndSession(
74
+ public async getChannel(
79
75
  channelId: string
80
- ): Promise<Channel & { session: CoreUserSession }> {
76
+ ): Promise<Channel & { pikkuUserId?: string }> {
81
77
  const row = await this.channels.findOne({ _id: channelId })
82
78
 
83
79
  if (!row) {
@@ -88,7 +84,7 @@ export class MongoDBChannelStore extends ChannelStore {
88
84
  channelId: row._id,
89
85
  channelName: row.channelName,
90
86
  openingData: row.openingData ?? {},
91
- session: (row.userSession ?? {}) as CoreUserSession,
87
+ pikkuUserId: row.pikkuUserId ?? undefined,
92
88
  }
93
89
  }
94
90
 
@@ -1,9 +1,10 @@
1
+ import { buildRemoteHeaders } from '@pikku/core/remote'
1
2
  import type {
2
3
  DeploymentService,
3
4
  DeploymentServiceConfig,
4
5
  DeploymentConfig,
5
- DeploymentInfo,
6
6
  } from '@pikku/core/services'
7
+ import type { JWTService, SecretService } from '@pikku/core/services'
7
8
  import { getAllFunctionNames } from '@pikku/core/function'
8
9
  import type { Db, Collection } from 'mongodb'
9
10
 
@@ -25,7 +26,9 @@ export class MongoDBDeploymentService implements DeploymentService {
25
26
 
26
27
  constructor(
27
28
  config: DeploymentServiceConfig,
28
- private db: Db
29
+ private db: Db,
30
+ private jwt?: JWTService,
31
+ private secrets?: SecretService
29
32
  ) {
30
33
  this.heartbeatInterval = config.heartbeatInterval ?? 10000
31
34
  this.heartbeatTtl = config.heartbeatTtl ?? 30000
@@ -82,21 +85,39 @@ export class MongoDBDeploymentService implements DeploymentService {
82
85
  }
83
86
  }
84
87
 
85
- async findFunction(name: string): Promise<DeploymentInfo[]> {
88
+ async invoke(
89
+ funcName: string,
90
+ data: unknown,
91
+ session?: unknown,
92
+ traceId?: string
93
+ ): Promise<unknown> {
94
+ const headers = await buildRemoteHeaders(
95
+ this.jwt,
96
+ this.secrets,
97
+ funcName,
98
+ session,
99
+ traceId
100
+ )
86
101
  const cutoff = new Date(Date.now() - this.heartbeatTtl)
87
-
88
- const result = await this.deployments
89
- .find({
90
- functions: name,
91
- lastHeartbeat: { $gt: cutoff },
92
- })
93
- .sort({ lastHeartbeat: -1 })
94
- .toArray()
95
-
96
- return result.map((row) => ({
97
- deploymentId: row._id,
98
- endpoint: row.endpoint,
99
- }))
102
+ const result = await this.deployments.findOne(
103
+ { functions: funcName, lastHeartbeat: { $gt: cutoff } },
104
+ { sort: { lastHeartbeat: -1 } }
105
+ )
106
+ if (!result) {
107
+ throw new Error(`No deployment found for function '${funcName}'`)
108
+ }
109
+ const url = `${result.endpoint}/remote/rpc/${encodeURIComponent(funcName)}`
110
+ const response = await fetch(url, {
111
+ method: 'POST',
112
+ headers: { 'Content-Type': 'application/json', ...headers },
113
+ body: JSON.stringify({ data }),
114
+ })
115
+ if (!response.ok) {
116
+ throw new Error(
117
+ `Remote RPC call to '${funcName}' failed: ${response.status}`
118
+ )
119
+ }
120
+ return response.json()
100
121
  }
101
122
 
102
123
  private async sendHeartbeat(): Promise<void> {
@@ -12,6 +12,7 @@ import { MongoDBDeploymentService } from './mongodb-deployment-service.js'
12
12
  import { MongoDBAIStorageService } from './mongodb-ai-storage-service.js'
13
13
  import { MongoDBAgentRunService } from './mongodb-agent-run-service.js'
14
14
  import { MongoDBSecretService } from './mongodb-secret-service.js'
15
+ import { MongoDBSessionStore } from './mongodb-session-store.js'
15
16
 
16
17
  function registerTests(name: string, getDb: () => Db) {
17
18
  defineServiceTests({
@@ -52,6 +53,11 @@ function registerTests(name: string, getDb: () => Db) {
52
53
  await s.init()
53
54
  return s
54
55
  },
56
+ sessionStore: async () => {
57
+ const s = new MongoDBSessionStore(getDb())
58
+ await s.init()
59
+ return s
60
+ },
55
61
  },
56
62
  })
57
63
 
@@ -0,0 +1,50 @@
1
+ import type { CoreUserSession } from '@pikku/core'
2
+ import type { SessionStore } from '@pikku/core/services'
3
+ import type { Db, Collection } from 'mongodb'
4
+
5
+ interface SessionDoc {
6
+ _id: string
7
+ session: any
8
+ createdAt: Date
9
+ updatedAt: Date
10
+ }
11
+
12
+ export class MongoDBSessionStore implements SessionStore {
13
+ private initialized = false
14
+ private sessions!: Collection<SessionDoc>
15
+
16
+ constructor(private db: Db) {}
17
+
18
+ public async init(): Promise<void> {
19
+ if (this.initialized) return
20
+
21
+ this.sessions = this.db.collection<SessionDoc>('pikkuUserSessions')
22
+ this.initialized = true
23
+ }
24
+
25
+ async get(pikkuUserId: string): Promise<CoreUserSession | undefined> {
26
+ const doc = await this.sessions.findOne({ _id: pikkuUserId })
27
+ if (!doc) {
28
+ return undefined
29
+ }
30
+ return doc.session as CoreUserSession
31
+ }
32
+
33
+ async set(pikkuUserId: string, session: CoreUserSession): Promise<void> {
34
+ const now = new Date()
35
+ await this.sessions.updateOne(
36
+ { _id: pikkuUserId },
37
+ {
38
+ $set: { session, updatedAt: now },
39
+ $setOnInsert: { createdAt: now },
40
+ },
41
+ { upsert: true }
42
+ )
43
+ }
44
+
45
+ async clear(pikkuUserId: string): Promise<void> {
46
+ await this.sessions.deleteOne({ _id: pikkuUserId })
47
+ }
48
+
49
+ public async close(): Promise<void> {}
50
+ }