@pikku/core 0.12.17 → 0.12.18

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.
Files changed (41) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/dist/function/function-runner.js +22 -1
  3. package/dist/services/in-memory-session-store.d.ts +8 -0
  4. package/dist/services/in-memory-session-store.js +12 -0
  5. package/dist/services/index.d.ts +2 -0
  6. package/dist/services/index.js +1 -0
  7. package/dist/services/session-store.d.ts +6 -0
  8. package/dist/services/session-store.js +1 -0
  9. package/dist/services/user-session-service.d.ts +12 -10
  10. package/dist/services/user-session-service.js +18 -21
  11. package/dist/testing/service-tests.d.ts +2 -0
  12. package/dist/testing/service-tests.js +50 -11
  13. package/dist/types/core.types.d.ts +3 -0
  14. package/dist/wirings/channel/channel-store.d.ts +5 -6
  15. package/dist/wirings/channel/local/local-channel-runner.js +7 -4
  16. package/dist/wirings/channel/serverless/serverless-channel-runner.js +25 -8
  17. package/dist/wirings/cli/cli-runner.js +1 -1
  18. package/dist/wirings/http/http-runner.js +1 -1
  19. package/dist/wirings/mcp/mcp-runner.js +1 -1
  20. package/dist/wirings/scheduler/scheduler-runner.js +1 -1
  21. package/dist/wirings/workflow/pikku-workflow-service.js +0 -1
  22. package/package.json +1 -1
  23. package/src/function/function-runner.ts +36 -1
  24. package/src/services/in-memory-session-store.test.ts +58 -0
  25. package/src/services/in-memory-session-store.ts +21 -0
  26. package/src/services/index.ts +2 -0
  27. package/src/services/session-store.ts +9 -0
  28. package/src/services/user-session-service.test.ts +53 -19
  29. package/src/services/user-session-service.ts +21 -22
  30. package/src/testing/service-tests.ts +64 -11
  31. package/src/types/core.types.ts +3 -0
  32. package/src/wirings/channel/channel-store.ts +5 -8
  33. package/src/wirings/channel/local/local-channel-runner.ts +10 -4
  34. package/src/wirings/channel/local/local-eventhub-service.test.ts +0 -5
  35. package/src/wirings/channel/serverless/serverless-channel-runner.ts +30 -9
  36. package/src/wirings/cli/cli-runner.ts +3 -1
  37. package/src/wirings/http/http-runner.ts +3 -1
  38. package/src/wirings/mcp/mcp-runner.ts +3 -1
  39. package/src/wirings/scheduler/scheduler-runner.ts +1 -1
  40. package/src/wirings/workflow/pikku-workflow-service.ts +0 -1
  41. package/tsconfig.tsbuildinfo +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  ## 0.12.4
2
2
 
3
+ ## 0.12.18
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
+
3
14
  ## 0.12.17
4
15
 
5
16
  ### Patch Changes
@@ -4,11 +4,31 @@ import { runPermissions } from '../permissions.js';
4
4
  import { pikkuState } from '../pikku-state.js';
5
5
  import { coerceTopLevelDataFromSchema, validateSchema } from '../schema.js';
6
6
  import { parseVersionedId } from '../version.js';
7
- import { createFunctionSessionWireProps } from '../services/user-session-service.js';
7
+ import { PikkuSessionService, createFunctionSessionWireProps, } from '../services/user-session-service.js';
8
8
  import { ForbiddenError, ReadonlySessionError } from '../errors/errors.js';
9
9
  import { PikkuCredentialWireService, createWireServicesCredentialWireProps, } from '../services/credential-wire-service.js';
10
+ import { defaultPikkuUserIdResolver } from '../services/pikku-user-id.js';
10
11
  import { rpcService } from '../wirings/rpc/rpc-runner.js';
11
12
  import { closeWireServices } from '../utils.js';
13
+ async function resolveSession(wire, singletonServices, sessionService) {
14
+ const pikkuUserId = defaultPikkuUserIdResolver(wire);
15
+ if (pikkuUserId) {
16
+ wire.pikkuUserId = pikkuUserId;
17
+ if (sessionService instanceof PikkuSessionService) {
18
+ sessionService.setPikkuUserId(pikkuUserId);
19
+ }
20
+ }
21
+ const { sessionStore } = singletonServices;
22
+ if (!sessionStore || !pikkuUserId)
23
+ return;
24
+ if (!wire.session) {
25
+ const stored = await sessionStore.get(pikkuUserId);
26
+ if (stored) {
27
+ wire.session = stored;
28
+ sessionService?.setInitial(stored);
29
+ }
30
+ }
31
+ }
12
32
  /**
13
33
  * Get or create singleton services for an addon package.
14
34
  * Services are cached in pikkuState to avoid recreation on each call.
@@ -134,6 +154,7 @@ export const runPikkuFunc = async (wireType, wireId, funcName, { singletonServic
134
154
  }
135
155
  // Helper function to run permissions and execute the function
136
156
  const executeFunction = async () => {
157
+ await resolveSession(resolvedWire, resolvedSingletonServices, sessionService);
137
158
  if (sessionService) {
138
159
  resolvedWire.session = sessionService.freezeInitial();
139
160
  resolvedWire.setSession = (s) => sessionService.set(s);
@@ -0,0 +1,8 @@
1
+ import type { CoreUserSession } from '../types/core.types.js';
2
+ import type { SessionStore } from './session-store.js';
3
+ export declare class InMemorySessionStore<UserSession extends CoreUserSession = CoreUserSession> implements SessionStore<UserSession> {
4
+ private sessions;
5
+ get(pikkuUserId: string): Promise<UserSession | undefined>;
6
+ set(pikkuUserId: string, session: UserSession): Promise<void>;
7
+ clear(pikkuUserId: string): Promise<void>;
8
+ }
@@ -0,0 +1,12 @@
1
+ export class InMemorySessionStore {
2
+ sessions = new Map();
3
+ async get(pikkuUserId) {
4
+ return this.sessions.get(pikkuUserId);
5
+ }
6
+ async set(pikkuUserId, session) {
7
+ this.sessions.set(pikkuUserId, session);
8
+ }
9
+ async clear(pikkuUserId) {
10
+ this.sessions.delete(pikkuUserId);
11
+ }
12
+ }
@@ -36,4 +36,6 @@ export { TypedCredentialService } from './typed-credential-service.js';
36
36
  export type { CredentialStatusInfo, CredentialMetaInfo, } from './typed-credential-service.js';
37
37
  export type { VariableStatus, VariableMeta } from './typed-variables-service.js';
38
38
  export type { MetaService } from './meta-service.js';
39
+ export type { SessionStore } from './session-store.js';
40
+ export { InMemorySessionStore } from './in-memory-session-store.js';
39
41
  export type { MCPMeta, RPCMetaRecord, ServiceMeta, ServicesMetaRecord, MiddlewareDefinitionMeta, MiddlewareInstanceMeta, GroupMeta, MiddlewareGroupsMeta, PermissionDefinitionMeta, PermissionsGroupsMeta, FunctionsMeta, FunctionMeta, MiddlewareMeta, PermissionMeta, AgentsMeta, AgentMeta, } from './meta-service.js';
@@ -17,3 +17,4 @@ export { InMemoryTriggerService } from './in-memory-trigger-service.js';
17
17
  export { InMemoryAIRunStateService } from './in-memory-ai-run-state-service.js';
18
18
  export { LocalGatewayService } from './local-gateway-service.js';
19
19
  export { TypedCredentialService } from './typed-credential-service.js';
20
+ export { InMemorySessionStore } from './in-memory-session-store.js';
@@ -0,0 +1,6 @@
1
+ import type { CoreUserSession } from '../types/core.types.js';
2
+ export interface SessionStore<UserSession extends CoreUserSession = CoreUserSession> {
3
+ get(pikkuUserId: string): Promise<UserSession | undefined>;
4
+ set(pikkuUserId: string, session: UserSession): Promise<void>;
5
+ clear(pikkuUserId: string): Promise<void>;
6
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -1,5 +1,5 @@
1
- import type { ChannelStore } from '../wirings/channel/channel-store.js';
2
1
  import type { CoreUserSession } from '../types/core.types.js';
2
+ import type { SessionStore } from './session-store.js';
3
3
  export interface SessionService<UserSession extends CoreUserSession> {
4
4
  sessionChanged: boolean;
5
5
  initial: UserSession | undefined;
@@ -7,31 +7,33 @@ export interface SessionService<UserSession extends CoreUserSession> {
7
7
  freezeInitial(): UserSession | undefined;
8
8
  set(session: UserSession): Promise<void> | void;
9
9
  clear(): Promise<void> | void;
10
- get(): Promise<UserSession> | UserSession | undefined;
10
+ get(): UserSession | undefined;
11
11
  }
12
12
  export declare class PikkuSessionService<UserSession extends CoreUserSession> implements SessionService<UserSession> {
13
- private channelStore?;
14
- private channelId?;
13
+ private sessionStore?;
15
14
  sessionChanged: boolean;
16
15
  initial: UserSession | undefined;
17
16
  private session;
18
- constructor(channelStore?: ChannelStore<unknown, unknown, UserSession> | undefined, channelId?: string | undefined);
17
+ private pikkuUserId?;
18
+ constructor(sessionStore?: SessionStore<UserSession> | undefined);
19
+ setPikkuUserId(id: string): void;
20
+ getPikkuUserId(): string | undefined;
19
21
  setInitial(session: UserSession): void;
20
22
  freezeInitial(): UserSession | undefined;
21
- set(session: UserSession): void | Promise<void> | undefined;
22
- clear(): void | Promise<void> | undefined;
23
- get(): Promise<UserSession> | UserSession | undefined;
23
+ set(session: UserSession): Promise<void>;
24
+ clear(): Promise<void>;
25
+ get(): UserSession | undefined;
24
26
  }
25
27
  export declare function createMiddlewareSessionWireProps<UserSession extends CoreUserSession>(session: SessionService<UserSession>): {
26
28
  session: UserSession | undefined;
27
29
  setSession: (s: UserSession) => void;
28
- getSession: () => UserSession | Promise<UserSession> | undefined;
30
+ getSession: () => UserSession | undefined;
29
31
  hasSessionChanged: () => boolean;
30
32
  };
31
33
  export declare function createFunctionSessionWireProps<UserSession extends CoreUserSession>(session: SessionService<UserSession>): {
32
34
  session: UserSession | undefined;
33
35
  setSession: (s: UserSession) => void | Promise<void>;
34
36
  clearSession: () => void | Promise<void>;
35
- getSession: () => UserSession | Promise<UserSession> | undefined;
37
+ getSession: () => UserSession | undefined;
36
38
  hasSessionChanged: () => boolean;
37
39
  };
@@ -1,15 +1,17 @@
1
1
  export class PikkuSessionService {
2
- channelStore;
3
- channelId;
2
+ sessionStore;
4
3
  sessionChanged = false;
5
4
  initial;
6
5
  session;
7
- constructor(channelStore, channelId) {
8
- this.channelStore = channelStore;
9
- this.channelId = channelId;
10
- if (channelStore && !channelId) {
11
- throw new Error('Channel ID is required when using channel store');
12
- }
6
+ pikkuUserId;
7
+ constructor(sessionStore) {
8
+ this.sessionStore = sessionStore;
9
+ }
10
+ setPikkuUserId(id) {
11
+ this.pikkuUserId = id;
12
+ }
13
+ getPikkuUserId() {
14
+ return this.pikkuUserId;
13
15
  }
14
16
  setInitial(session) {
15
17
  this.session = session;
@@ -20,26 +22,21 @@ export class PikkuSessionService {
20
22
  }
21
23
  return this.initial;
22
24
  }
23
- set(session) {
25
+ async set(session) {
24
26
  this.sessionChanged = true;
25
27
  this.session = session;
26
- return this.channelStore?.setUserSession(this.channelId, session);
28
+ if (this.sessionStore && this.pikkuUserId) {
29
+ await this.sessionStore.set(this.pikkuUserId, session);
30
+ }
27
31
  }
28
- clear() {
32
+ async clear() {
29
33
  this.sessionChanged = true;
30
34
  this.session = undefined;
31
- return this.channelStore?.setUserSession(this.channelId, null);
35
+ if (this.sessionStore && this.pikkuUserId) {
36
+ await this.sessionStore.clear(this.pikkuUserId);
37
+ }
32
38
  }
33
39
  get() {
34
- if (this.channelStore) {
35
- const channel = this.channelStore.getChannelAndSession(this.channelId);
36
- if (channel instanceof Promise) {
37
- return channel.then(({ session }) => session);
38
- }
39
- else {
40
- return channel.session;
41
- }
42
- }
43
40
  return this.session;
44
41
  }
45
42
  }
@@ -8,6 +8,7 @@ import type { AIRunStateService } from '../services/ai-run-state-service.js';
8
8
  import type { SecretService } from '../services/secret-service.js';
9
9
  import type { CredentialService } from '../services/credential-service.js';
10
10
  import type { AgentRunService } from '../wirings/ai-agent/ai-agent.types.js';
11
+ import type { SessionStore } from '../services/session-store.js';
11
12
  export interface ServiceTestConfig {
12
13
  name: string;
13
14
  services: {
@@ -34,6 +35,7 @@ export interface ServiceTestConfig {
34
35
  }) => Promise<CredentialService & {
35
36
  rotateKEK?(): Promise<number>;
36
37
  }>;
38
+ sessionStore?: () => Promise<SessionStore>;
37
39
  };
38
40
  }
39
41
  export declare function defineServiceTests(config: ServiceTestConfig): void;
@@ -9,27 +9,31 @@ export function defineServiceTests(config) {
9
9
  before(async () => {
10
10
  store = await factory();
11
11
  });
12
- test('addChannel and getChannelAndSession', async () => {
12
+ test('addChannel and getChannel', async () => {
13
13
  await store.addChannel({
14
14
  channelId: 'ch-1',
15
15
  channelName: 'test-channel',
16
16
  openingData: { foo: 'bar' },
17
17
  });
18
- const result = await store.getChannelAndSession('ch-1');
18
+ const result = await store.getChannel('ch-1');
19
19
  assert.equal(result.channelId, 'ch-1');
20
20
  assert.equal(result.channelName, 'test-channel');
21
21
  assert.deepEqual(result.openingData, { foo: 'bar' });
22
- assert.deepEqual(result.session, {});
22
+ assert.equal(result.pikkuUserId, undefined);
23
23
  });
24
- test('setUserSession', async () => {
25
- const session = { userId: 'user-1' };
26
- await store.setUserSession('ch-1', session);
27
- const result = await store.getChannelAndSession('ch-1');
28
- assert.deepEqual(result.session, session);
24
+ test('setPikkuUserId', async () => {
25
+ await store.setPikkuUserId('ch-1', 'user-1');
26
+ const result = await store.getChannel('ch-1');
27
+ assert.equal(result.pikkuUserId, 'user-1');
29
28
  });
30
- test('getChannelAndSession throws for missing channel', async () => {
29
+ test('setPikkuUserId to null', async () => {
30
+ await store.setPikkuUserId('ch-1', null);
31
+ const result = await store.getChannel('ch-1');
32
+ assert.equal(result.pikkuUserId, undefined);
33
+ });
34
+ test('getChannel throws for missing channel', async () => {
31
35
  await assert.rejects(async () => {
32
- await store.getChannelAndSession('missing');
36
+ await store.getChannel('missing');
33
37
  }, { message: 'Channel not found: missing' });
34
38
  });
35
39
  test('removeChannels', async () => {
@@ -39,7 +43,7 @@ export function defineServiceTests(config) {
39
43
  });
40
44
  await store.removeChannels(['ch-2']);
41
45
  await assert.rejects(async () => {
42
- await store.getChannelAndSession('ch-2');
46
+ await store.getChannel('ch-2');
43
47
  });
44
48
  });
45
49
  test('removeChannels with empty array is no-op', async () => {
@@ -693,4 +697,39 @@ export function defineServiceTests(config) {
693
697
  });
694
698
  });
695
699
  }
700
+ if (services.sessionStore) {
701
+ const factory = services.sessionStore;
702
+ describe(`SessionStore [${name}]`, () => {
703
+ let store;
704
+ before(async () => {
705
+ store = await factory();
706
+ });
707
+ test('get returns undefined for unknown user', async () => {
708
+ const result = await store.get('unknown-user');
709
+ assert.equal(result, undefined);
710
+ });
711
+ test('set and get round-trip', async () => {
712
+ const session = { userId: 'user-1', organizationId: 'org-1' };
713
+ await store.set('user-1', session);
714
+ const result = await store.get('user-1');
715
+ assert.deepEqual(result, session);
716
+ });
717
+ test('set overwrites previous session', async () => {
718
+ await store.set('user-2', { userId: 'user-2', role: 'admin' });
719
+ await store.set('user-2', { userId: 'user-2', role: 'member' });
720
+ const result = await store.get('user-2');
721
+ assert.deepEqual(result, { userId: 'user-2', role: 'member' });
722
+ });
723
+ test('clear removes session', async () => {
724
+ await store.set('user-3', { userId: 'user-3' });
725
+ assert.ok(await store.get('user-3'));
726
+ await store.clear('user-3');
727
+ const result = await store.get('user-3');
728
+ assert.equal(result, undefined);
729
+ });
730
+ test('clear is no-op for unknown user', async () => {
731
+ await store.clear('nonexistent');
732
+ });
733
+ });
734
+ }
696
735
  }
@@ -25,6 +25,7 @@ import type { PikkuAIMiddlewareHooks } from '../wirings/ai-agent/ai-agent.types.
25
25
  import type { WorkflowRunService } from '../wirings/workflow/workflow.types.js';
26
26
  import type { CredentialService } from '../services/credential-service.js';
27
27
  import type { MetaService } from '../services/meta-service.js';
28
+ import type { SessionStore } from '../services/session-store.js';
28
29
  export type PikkuWiringTypes = 'http' | 'scheduler' | 'trigger' | 'channel' | 'rpc' | 'queue' | 'mcp' | 'cli' | 'workflow' | 'agent' | 'gateway';
29
30
  export interface FunctionServicesMeta {
30
31
  optimized: boolean;
@@ -181,6 +182,8 @@ export interface CoreSingletonServices<Config extends CoreConfig = CoreConfig> {
181
182
  credentialService?: CredentialService;
182
183
  /** Meta service for reading .pikku metadata files (filesystem on Node, R2/KV on CF) */
183
184
  metaService?: MetaService;
185
+ /** Session store for persisting user sessions keyed by pikkuUserId */
186
+ sessionStore?: SessionStore;
184
187
  }
185
188
  /**
186
189
  * Represents different forms of wire within Pikku and the outside world.
@@ -1,17 +1,16 @@
1
- import type { CoreUserSession } from '../../types/core.types.js';
2
1
  export type Channel<ChannelType = unknown, OpeningData = unknown> = {
3
2
  channelId: string;
4
3
  channelName: string;
5
4
  channelObject?: ChannelType;
6
5
  openingData?: OpeningData;
7
6
  };
8
- export declare abstract class ChannelStore<ChannelType = unknown, OpeningData = unknown, UserSession extends CoreUserSession = CoreUserSession, TypedChannel = Channel<ChannelType, OpeningData>> {
7
+ export declare abstract class ChannelStore<ChannelType = unknown, OpeningData = unknown, TypedChannel = Channel<ChannelType, OpeningData>> {
9
8
  abstract addChannel(channel: Channel<ChannelType, OpeningData>): Promise<void> | void;
10
9
  abstract removeChannels(channelId: string[]): Promise<void> | void;
11
- abstract setUserSession(channelId: string, userSession: UserSession | null): Promise<void> | void;
12
- abstract getChannelAndSession(channelId: string): Promise<TypedChannel & {
13
- session: UserSession;
10
+ abstract setPikkuUserId(channelId: string, pikkuUserId: string | null): Promise<void> | void;
11
+ abstract getChannel(channelId: string): Promise<TypedChannel & {
12
+ pikkuUserId?: string;
14
13
  }> | (TypedChannel & {
15
- session: UserSession;
14
+ pikkuUserId?: string;
16
15
  });
17
16
  }
@@ -15,7 +15,7 @@ export const runLocalChannel = async ({ channelId, request, response, route, ski
15
15
  let wireServices;
16
16
  let closedWireServices = false;
17
17
  let channelHandler;
18
- const userSession = new PikkuSessionService();
18
+ const userSession = new PikkuSessionService(singletonServices.sessionStore);
19
19
  let http;
20
20
  if (request) {
21
21
  http = createHTTPWire(request, response);
@@ -79,7 +79,8 @@ export const runLocalChannel = async ({ channelId, request, response, route, ski
79
79
  singletonServices.logger.error(`Error handling onConnect: ${e}`);
80
80
  const errorResponse = getErrorResponse(e);
81
81
  channel.send({
82
- error: errorResponse?.message ?? (isProduction() ? 'Internal server error' : e.message),
82
+ error: errorResponse?.message ??
83
+ (isProduction() ? 'Internal server error' : e.message),
83
84
  ...(!isProduction() && { errorName: e.constructor?.name }),
84
85
  });
85
86
  }
@@ -102,7 +103,8 @@ export const runLocalChannel = async ({ channelId, request, response, route, ski
102
103
  singletonServices.logger.error(`Error handling onDisconnect: ${e}`);
103
104
  const errorResponse = getErrorResponse(e);
104
105
  channel.send({
105
- error: errorResponse?.message ?? (isProduction() ? 'Internal server error' : e.message),
106
+ error: errorResponse?.message ??
107
+ (isProduction() ? 'Internal server error' : e.message),
106
108
  ...(!isProduction() && { errorName: e.constructor?.name }),
107
109
  });
108
110
  }
@@ -119,7 +121,8 @@ export const runLocalChannel = async ({ channelId, request, response, route, ski
119
121
  singletonServices.logger.error(e);
120
122
  const errorResponse = getErrorResponse(e);
121
123
  channel.send({
122
- error: errorResponse?.message ?? (isProduction() ? 'Internal server error' : e.message),
124
+ error: errorResponse?.message ??
125
+ (isProduction() ? 'Internal server error' : e.message),
123
126
  ...(!isProduction() && { errorName: e.constructor?.name }),
124
127
  });
125
128
  setTimeout(() => channel.close(), 200);
@@ -34,7 +34,7 @@ export const runChannelConnect = async ({ channelId, channelObject, request, res
34
34
  if (request instanceof Request) {
35
35
  http = createHTTPWire(new PikkuFetchHTTPRequest(request), response);
36
36
  }
37
- const userSession = new PikkuSessionService(channelStore, channelId);
37
+ const userSession = new PikkuSessionService(singletonServices.sessionStore);
38
38
  const { channelConfig, openingData, meta } = await openChannel({
39
39
  channelId,
40
40
  request,
@@ -77,6 +77,11 @@ export const runChannelConnect = async ({ channelId, channelObject, request, res
77
77
  channelMiddlewareMeta: meta.channelMiddleware,
78
78
  });
79
79
  }
80
+ // Store the pikkuUserId mapping after auth middleware has run
81
+ const pikkuUserId = userSession.getPikkuUserId();
82
+ if (pikkuUserId) {
83
+ await channelStore.setPikkuUserId(channelId, pikkuUserId);
84
+ }
80
85
  http?.response?.status(101);
81
86
  }
82
87
  catch (e) {
@@ -98,21 +103,27 @@ export const runChannelDisconnect = async ({ channelId, channelStore, channelHan
98
103
  // there's nothing to disconnect, so we can return early.
99
104
  let channelData;
100
105
  try {
101
- channelData = await channelStore.getChannelAndSession(channelId);
106
+ channelData = await channelStore.getChannel(channelId);
102
107
  }
103
108
  catch {
104
109
  singletonServices.logger.info(`Channel ${channelId} not found during disconnect - already cleaned up`);
105
110
  return;
106
111
  }
107
- const { openingData, channelName, session } = channelData;
112
+ const { openingData, channelName, pikkuUserId } = channelData;
108
113
  const { channel, channelConfig, meta } = getVariablesForChannel({
109
114
  channelId,
110
115
  channelHandlerFactory,
111
116
  openingData,
112
117
  channelName,
113
118
  });
114
- const userSession = new PikkuSessionService(channelStore, channelId);
115
- userSession.setInitial(session);
119
+ const userSession = new PikkuSessionService(singletonServices.sessionStore);
120
+ if (pikkuUserId) {
121
+ userSession.setPikkuUserId(pikkuUserId);
122
+ const session = await singletonServices.sessionStore?.get(pikkuUserId);
123
+ if (session) {
124
+ userSession.setInitial(session);
125
+ }
126
+ }
116
127
  const wire = {
117
128
  channel,
118
129
  ...createMiddlewareSessionWireProps(userSession),
@@ -149,15 +160,21 @@ export const runChannelMessage = async ({ channelId, channelStore, channelHandle
149
160
  const singletonServices = getSingletonServices();
150
161
  const createWireServices = getCreateWireServices();
151
162
  let wireServices;
152
- const { openingData, channelName, session } = await channelStore.getChannelAndSession(channelId);
163
+ const { openingData, channelName, pikkuUserId } = await channelStore.getChannel(channelId);
153
164
  const { channel, channelHandler, channelConfig } = getVariablesForChannel({
154
165
  channelId,
155
166
  channelHandlerFactory,
156
167
  openingData,
157
168
  channelName,
158
169
  });
159
- const userSession = new PikkuSessionService(channelStore, channelId);
160
- userSession.setInitial(session);
170
+ const userSession = new PikkuSessionService(singletonServices.sessionStore);
171
+ if (pikkuUserId) {
172
+ userSession.setPikkuUserId(pikkuUserId);
173
+ const session = await singletonServices.sessionStore?.get(pikkuUserId);
174
+ if (session) {
175
+ userSession.setInitial(session);
176
+ }
177
+ }
161
178
  const wire = {
162
179
  channel,
163
180
  ...createMiddlewareSessionWireProps(userSession),
@@ -224,7 +224,7 @@ export async function runCLICommand({ program, commandPath, data, singletonServi
224
224
  },
225
225
  state: 'open',
226
226
  };
227
- const userSession = new PikkuSessionService();
227
+ const userSession = new PikkuSessionService(singletonServices.sessionStore);
228
228
  const wire = {
229
229
  cli: {
230
230
  program,
@@ -201,9 +201,9 @@ export const createHTTPWire = (request, response) => {
201
201
  * @throws Throws errors like MissingSessionError or ForbiddenError on validation failures.
202
202
  */
203
203
  const executeRoute = async (services, matchedRoute, http, options) => {
204
- const userSession = new PikkuSessionService();
205
204
  const { params, route, meta } = matchedRoute;
206
205
  const { singletonServices, createWireServices, skipUserSession, requestId } = services;
206
+ const userSession = new PikkuSessionService(singletonServices.sessionStore);
207
207
  // Attach URL parameters to the request object
208
208
  http?.request?.setParams(params);
209
209
  // Validate request headers if schema is defined
@@ -103,7 +103,7 @@ async function runMCPPikkuFunc(request, type, name, mcp, pikkuFuncId, { mcp: mcp
103
103
  throw new NotFoundError(`MCP '${type}' PikkuFunction Mapping not found for '${name}'`);
104
104
  }
105
105
  singletonServices.logger.debug(`Running MCP ${type}: ${name}`);
106
- const mcpSessionService = new PikkuSessionService();
106
+ const mcpSessionService = new PikkuSessionService(singletonServices.sessionStore);
107
107
  const wire = {
108
108
  mcp: mcpWire,
109
109
  ...createMiddlewareSessionWireProps(mcpSessionService),
@@ -41,7 +41,7 @@ export async function runScheduledTask({ name, session, traceId, }) {
41
41
  singletonServices.logger;
42
42
  const task = pikkuState(null, 'scheduler', 'tasks').get(name);
43
43
  const meta = pikkuState(null, 'scheduler', 'meta')[name];
44
- const userSession = new PikkuSessionService();
44
+ const userSession = new PikkuSessionService(singletonServices.sessionStore);
45
45
  if (session) {
46
46
  userSession.set(session);
47
47
  }
@@ -425,7 +425,6 @@ export class PikkuWorkflowService {
425
425
  const wire = {
426
426
  workflow: workflowWire,
427
427
  pikkuUserId: run.wire?.pikkuUserId,
428
- session: rpcService.wire?.session,
429
428
  rpc: rpcService.wire?.rpc,
430
429
  };
431
430
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pikku/core",
3
- "version": "0.12.17",
3
+ "version": "0.12.18",
4
4
  "author": "yasser.fadl@gmail.com",
5
5
  "license": "MIT",
6
6
  "module": "dist/index.js",
@@ -26,15 +26,44 @@ import type {
26
26
  } from './functions.types.js'
27
27
  import { parseVersionedId } from '../version.js'
28
28
  import type { SessionService } from '../services/user-session-service.js'
29
- import { createFunctionSessionWireProps } from '../services/user-session-service.js'
29
+ import {
30
+ PikkuSessionService,
31
+ createFunctionSessionWireProps,
32
+ } from '../services/user-session-service.js'
30
33
  import { ForbiddenError, ReadonlySessionError } from '../errors/errors.js'
31
34
  import {
32
35
  PikkuCredentialWireService,
33
36
  createWireServicesCredentialWireProps,
34
37
  } from '../services/credential-wire-service.js'
38
+ import { defaultPikkuUserIdResolver } from '../services/pikku-user-id.js'
35
39
  import { rpcService } from '../wirings/rpc/rpc-runner.js'
36
40
  import { closeWireServices } from '../utils.js'
37
41
 
42
+ async function resolveSession(
43
+ wire: PikkuWire,
44
+ singletonServices: CoreSingletonServices,
45
+ sessionService?: SessionService<CoreUserSession>
46
+ ): Promise<void> {
47
+ const pikkuUserId = defaultPikkuUserIdResolver(wire)
48
+ if (pikkuUserId) {
49
+ wire.pikkuUserId = pikkuUserId
50
+ if (sessionService instanceof PikkuSessionService) {
51
+ sessionService.setPikkuUserId(pikkuUserId)
52
+ }
53
+ }
54
+
55
+ const { sessionStore } = singletonServices
56
+ if (!sessionStore || !pikkuUserId) return
57
+
58
+ if (!wire.session) {
59
+ const stored = await sessionStore.get(pikkuUserId)
60
+ if (stored) {
61
+ wire.session = stored
62
+ sessionService?.setInitial(stored)
63
+ }
64
+ }
65
+ }
66
+
38
67
  /**
39
68
  * Get or create singleton services for an addon package.
40
69
  * Services are cached in pikkuState to avoid recreation on each call.
@@ -256,6 +285,12 @@ export const runPikkuFunc = async <In = any, Out = any>(
256
285
 
257
286
  // Helper function to run permissions and execute the function
258
287
  const executeFunction = async () => {
288
+ await resolveSession(
289
+ resolvedWire,
290
+ resolvedSingletonServices,
291
+ sessionService
292
+ )
293
+
259
294
  if (sessionService) {
260
295
  resolvedWire.session = sessionService.freezeInitial()
261
296
  resolvedWire.setSession = (s: any) => sessionService.set(s)