@pikku/core 0.12.128 → 0.12.129

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,3 +1,9 @@
1
+ ## 0.12.129
2
+
3
+ ### Patch Changes
4
+
5
+ - 1ab6eb4: Fix app functions reached through `rpc.invoke`/`rpc.exposed` (including the generated `/rpc/:rpcName` endpoint) running without their wire services. Since `singletonServicesOnly`, the forwarding `rpcCaller` built none and the callee inherited that, so any service from `pikkuWireServices` was `undefined`. The runner now resolves the callee's factory itself. Channels pass their per-connection set as the new `wireServices` option, which is reused and left open.
6
+
1
7
  ## 0.12.128
2
8
 
3
9
  ### Patch Changes
@@ -8,9 +8,14 @@ import type { AddonInstance } from '../wirings/addon/addon-runner.js';
8
8
  export declare const addFunction: (funcName: string, funcConfig: CorePikkuFunctionConfig<any, any, any, any, any>, packageName?: string | null) => void;
9
9
  export declare const getFunctionNames: (packageName?: string | null) => string[];
10
10
  export declare const getAllFunctionNames: () => string[];
11
- export declare const runPikkuFunc: <In = any, Out = any>(wireType: PikkuWiringTypes, wireId: string, funcName: string, { singletonServices, createWireServices, data, auth: wiringAuth, inheritedMiddleware, wireMiddleware, inheritedChannelMiddleware, wireChannelMiddleware, coerceDataFromSchema, wire, sessionService, credentialWireService, packageName, addonInstance, }: {
11
+ export declare const runPikkuFunc: <In = any, Out = any>(wireType: PikkuWiringTypes, wireId: string, funcName: string, { singletonServices, createWireServices, wireServices: providedWireServices, data, auth: wiringAuth, inheritedMiddleware, wireMiddleware, inheritedChannelMiddleware, wireChannelMiddleware, coerceDataFromSchema, wire, sessionService, credentialWireService, packageName, addonInstance, }: {
12
12
  singletonServices: CoreSingletonServices;
13
13
  createWireServices?: CreateWireServices;
14
+ /**
15
+ * Wire services the caller already built and still owns, such as a
16
+ * channel's per-connection set. Used as-is and never closed here.
17
+ */
18
+ wireServices?: Record<string, unknown>;
14
19
  data: () => Promise<In> | In;
15
20
  auth?: boolean;
16
21
  inheritedMiddleware?: MiddlewareMetadata[];
@@ -3,7 +3,7 @@ import { runMiddleware, combineMiddleware } from '../middleware-runner.js';
3
3
  import { combineChannelMiddleware, wrapChannelWithMiddleware, } from '../wirings/channel/channel-middleware-runner.js';
4
4
  import { runPermissions } from '../permissions.js';
5
5
  import { withoutSecrets } from '../services/secretless.js';
6
- import { pikkuState } from '../pikku-state.js';
6
+ import { getCreateWireServices, pikkuState } from '../pikku-state.js';
7
7
  import { applyDefaultsFromSchema, coerceTopLevelDataFromSchema, validateSchema, } from '../schema.js';
8
8
  import { parseVersionedId } from '../version.js';
9
9
  import { PikkuSessionService } from '../services/user-session-service.js';
@@ -64,7 +64,7 @@ export const getAllFunctionNames = () => {
64
64
  }
65
65
  return functions;
66
66
  };
67
- export const runPikkuFunc = async (wireType, wireId, funcName, { singletonServices, createWireServices, data, auth: wiringAuth, inheritedMiddleware, wireMiddleware, inheritedChannelMiddleware, wireChannelMiddleware, coerceDataFromSchema, wire, sessionService, credentialWireService, packageName = null, addonInstance, }) => {
67
+ export const runPikkuFunc = async (wireType, wireId, funcName, { singletonServices, createWireServices, wireServices: providedWireServices, data, auth: wiringAuth, inheritedMiddleware, wireMiddleware, inheritedChannelMiddleware, wireChannelMiddleware, coerceDataFromSchema, wire, sessionService, credentialWireService, packageName = null, addonInstance, }) => {
68
68
  wire.wireType ??= wireType;
69
69
  wire.wireId ??= wireId;
70
70
  const funcMap = pikkuState(packageName, 'function', 'functions');
@@ -118,13 +118,14 @@ export const runPikkuFunc = async (wireType, wireId, funcName, { singletonServic
118
118
  const resolvedSingletonServices = funcPackageName
119
119
  ? await getOrCreatePackageSingletonServices(funcPackageName, singletonServices, addonInstance)
120
120
  : singletonServices;
121
- let resolvedCreateWireServices = createWireServices;
122
- if (funcPackageName) {
123
- const factories = pikkuState(funcPackageName, 'package', 'factories');
124
- if (factories?.createWireServices) {
125
- resolvedCreateWireServices = factories.createWireServices;
126
- }
127
- }
121
+ // knowledge: decisions/internals/the-runner-resolves-wire-services-itself.md
122
+ const packageCreateWireServices = funcPackageName
123
+ ? pikkuState(funcPackageName, 'package', 'factories')?.createWireServices
124
+ : undefined;
125
+ const reusedWireServices = packageCreateWireServices
126
+ ? undefined
127
+ : providedWireServices;
128
+ const resolvedCreateWireServices = packageCreateWireServices ?? createWireServices ?? getCreateWireServices();
128
129
  const allChannelMiddleware = combineChannelMiddleware(wireType, wireId, {
129
130
  wireInheritedChannelMiddleware: inheritedChannelMiddleware,
130
131
  wireChannelMiddleware,
@@ -256,11 +257,14 @@ export const runPikkuFunc = async (wireType, wireId, funcName, { singletonServic
256
257
  let invocationAuditLog;
257
258
  let invocationAnalytics;
258
259
  try {
259
- wireServices = funcMeta.singletonServicesOnly
260
+ if (!funcMeta.singletonServicesOnly && !reusedWireServices) {
261
+ wireServices = (await resolvedCreateWireServices?.(resolvedSingletonServices, invocationWire));
262
+ }
263
+ const invocationWireServices = funcMeta.singletonServicesOnly
260
264
  ? undefined
261
- : (await resolvedCreateWireServices?.(resolvedSingletonServices, invocationWire));
262
- let services = wireServices && Object.keys(wireServices).length > 0
263
- ? { ...resolvedSingletonServices, ...wireServices }
265
+ : (reusedWireServices ?? wireServices);
266
+ let services = invocationWireServices && Object.keys(invocationWireServices).length > 0
267
+ ? { ...resolvedSingletonServices, ...invocationWireServices }
264
268
  : resolvedSingletonServices;
265
269
  // knowledge: decisions/internals/core-function-runner-restores-the-wire-fields-it-overwrites.md
266
270
  if (resolvedAuditConfig &&
@@ -286,7 +290,7 @@ export const runPikkuFunc = async (wireType, wireId, funcName, { singletonServic
286
290
  const callerPackageName = funcPackageName;
287
291
  Object.defineProperty(invocationWire, 'rpc', {
288
292
  get() {
289
- const rpc = rpcService.getContextRPCService(services, invocationWire, { sessionService }, 0, callerPackageName);
293
+ const rpc = rpcService.getContextRPCService(resolvedSingletonServices, invocationWire, { sessionService }, 0, callerPackageName);
290
294
  Object.defineProperty(invocationWire, 'rpc', {
291
295
  value: rpc,
292
296
  writable: true,
@@ -2,7 +2,7 @@ import type { CoreSingletonServices } from '../../types/core.types.js';
2
2
  import type { MiddlewareMetadata } from '../../middleware/middleware.types.js';
3
3
  import type { CoreChannel, ChannelMessageMeta } from './channel.types.js';
4
4
  import { type PikkuSessionService } from '../../services/user-session-service.js';
5
- export declare const runChannelLifecycleWithMiddleware: ({ channelConfig, meta, lifecycleConfig, lifecycleType, services, channel, data, channelMiddlewareMeta, userSession, }: {
5
+ export declare const runChannelLifecycleWithMiddleware: ({ channelConfig, meta, lifecycleConfig, lifecycleType, services, channel, data, channelMiddlewareMeta, userSession, wireServices, }: {
6
6
  channelConfig: CoreChannel<unknown, any>;
7
7
  meta: ChannelMessageMeta;
8
8
  lifecycleConfig: any;
@@ -16,4 +16,6 @@ export declare const runChannelLifecycleWithMiddleware: ({ channelConfig, meta,
16
16
  * just connected.
17
17
  */
18
18
  userSession?: PikkuSessionService<any>;
19
+ /** The connection's wire services, reused rather than rebuilt per call. */
20
+ wireServices?: Record<string, unknown>;
19
21
  }) => Promise<unknown>;
@@ -2,7 +2,7 @@ import { combineMiddleware, runMiddleware } from '../../middleware-runner.js';
2
2
  import { runPikkuFunc } from '../../function/function-runner.js';
3
3
  import { createMiddlewareSessionWireProps, } from '../../services/user-session-service.js';
4
4
  import { combineChannelMiddleware, wrapChannelWithMiddleware, } from './channel-middleware-runner.js';
5
- export const runChannelLifecycleWithMiddleware = async ({ channelConfig, meta, lifecycleConfig, lifecycleType, services, channel, data, channelMiddlewareMeta, userSession, }) => {
5
+ export const runChannelLifecycleWithMiddleware = async ({ channelConfig, meta, lifecycleConfig, lifecycleType, services, channel, data, channelMiddlewareMeta, userSession, wireServices, }) => {
6
6
  const lifecycleMiddleware = typeof lifecycleConfig === 'object' && 'middleware' in lifecycleConfig
7
7
  ? lifecycleConfig.middleware || []
8
8
  : [];
@@ -25,6 +25,7 @@ export const runChannelLifecycleWithMiddleware = async ({ channelConfig, meta, l
25
25
  const runLifecycle = async () => {
26
26
  return await runPikkuFunc('channel', channelConfig.name, meta.pikkuFuncId, {
27
27
  singletonServices: services,
28
+ wireServices,
28
29
  data: () => data,
29
30
  wire,
30
31
  sessionService: userSession,
@@ -1,7 +1,7 @@
1
1
  import type { CoreServices, CoreUserSession } from '../../types/core.types.js';
2
2
  import type { BinaryData, CoreChannel, PikkuChannelHandler } from './channel.types.js';
3
3
  import type { SessionService } from '../../services/user-session-service.js';
4
- export declare const processMessageHandlers: (services: CoreServices, channelConfig: CoreChannel<any, any>, channelHandler: PikkuChannelHandler, userSession?: SessionService<CoreUserSession>) => {
4
+ export declare const processMessageHandlers: (services: CoreServices, channelConfig: CoreChannel<any, any>, channelHandler: PikkuChannelHandler, userSession?: SessionService<CoreUserSession>, wireServices?: Record<string, unknown>) => {
5
5
  onMessage: (rawData: unknown) => Promise<unknown>;
6
6
  onBinaryMessage: ((data: BinaryData) => Promise<BinaryData | void>) | undefined;
7
7
  };
@@ -37,7 +37,7 @@ const validateAuth = (requiresSession, session, onMessage) => {
37
37
  }
38
38
  return true;
39
39
  };
40
- export const processMessageHandlers = (services, channelConfig, channelHandler, userSession) => {
40
+ export const processMessageHandlers = (services, channelConfig, channelHandler, userSession, wireServices) => {
41
41
  const logger = services.logger;
42
42
  const requiresSession = channelConfig.auth !== false;
43
43
  const processMessage = async (data, onMessage, routingProperty, routerValue) => {
@@ -76,6 +76,7 @@ export const processMessageHandlers = (services, channelConfig, channelHandler,
76
76
  const wireChannelMiddleware = channelConfig.channelMiddleware || [];
77
77
  return await runPikkuFunc('channel', cacheKey, pikkuFuncId, {
78
78
  singletonServices: services,
79
+ wireServices,
79
80
  data: () => data,
80
81
  inheritedMiddleware,
81
82
  wireMiddleware,
@@ -89,6 +89,7 @@ export const runLocalChannel = async ({ channelId, request, response, route, ski
89
89
  data: openingData,
90
90
  channelMiddlewareMeta: meta.channelMiddleware,
91
91
  userSession,
92
+ wireServices,
92
93
  });
93
94
  if (result !== undefined) {
94
95
  await channel.send(result);
@@ -117,6 +118,7 @@ export const runLocalChannel = async ({ channelId, request, response, route, ski
117
118
  channel,
118
119
  channelMiddlewareMeta: meta.channelMiddleware,
119
120
  userSession,
121
+ wireServices,
120
122
  });
121
123
  }
122
124
  catch (e) {
@@ -131,7 +133,7 @@ export const runLocalChannel = async ({ channelId, request, response, route, ski
131
133
  }
132
134
  await closeServices();
133
135
  });
134
- const { onMessage, onBinaryMessage } = processMessageHandlers(services, channelConfig, channelHandler, userSession);
136
+ const { onMessage, onBinaryMessage } = processMessageHandlers(services, channelConfig, channelHandler, userSession, wireServices);
135
137
  channelHandler.registerOnMessage(async (data) => {
136
138
  try {
137
139
  const result = await onMessage(data);
@@ -80,6 +80,7 @@ export const runChannelConnect = async ({ channelId, channelObject, request, res
80
80
  data: openingData,
81
81
  channelMiddlewareMeta: meta.channelMiddleware,
82
82
  userSession,
83
+ wireServices,
83
84
  });
84
85
  }
85
86
  const pikkuUserId = userSession.getPikkuUserId();
@@ -150,6 +151,7 @@ export const runChannelDisconnect = async ({ channelId, channelStore, channelHan
150
151
  channel,
151
152
  channelMiddlewareMeta: meta.channelMiddleware,
152
153
  userSession,
154
+ wireServices,
153
155
  });
154
156
  }
155
157
  catch (e) {
@@ -197,7 +199,7 @@ export const runChannelMessage = async ({ channelId, channelStore, channelHandle
197
199
  };
198
200
  let response;
199
201
  try {
200
- const { onMessage } = processMessageHandlers(services, channelConfig, channelHandler, userSession);
202
+ const { onMessage } = processMessageHandlers(services, channelConfig, channelHandler, userSession, wireServices);
201
203
  response = await onMessage(data);
202
204
  }
203
205
  catch (e) {
@@ -62,6 +62,7 @@ caller is entitled to assume.
62
62
  - [A column's at-rest form is an axis of its own](core-column-form-is-an-axis-of-its-own.md) — How a value is stored is independent of how sensitive it is, so form carries a required nominal brand on writes while classification stays optional on reads
63
63
  - [The data-classification brand is an optional property](core-data-classification-brand-is-an-optional-property.md) — Making **classification** required would break ordinary Kysely operands, so the brand only constrains values flowing out
64
64
  - [The function runner restores the wire fields it overwrites](core-function-runner-restores-the-wire-fields-it-overwrites.md) — One wire object is reused across nested calls, so functionId, audit, addonNamespace and rpc are saved and put back in a finally
65
+ - [The function runner resolves wire services itself](the-runner-resolves-wire-services-itself.md) — A function's wire services come from its own package's factory or the app's registered one, never borrowed from whoever called it
65
66
  - [Hot reload merges generated meta and never replaces it](core-hot-reload-merges-generated-meta-never-replaces-it.md) — Reloading codegen output must preserve runtime-registered meta, which no generated JSON contains
66
67
  - [Hot reload owns its module registry instead of re-importing](core-hot-reload-owns-its-module-registry.md) — Dev reload transpiles to CJS and runs modules through vm.compileFunction, because the native ESM loader map cannot be evicted
67
68
  - [Middleware order is resolution scope first, then priority](core-middleware-order-is-scope-then-priority.md) — Middleware is collected global to function, then stably sorted by priority, deduped, frozen and cached per wire
@@ -0,0 +1,31 @@
1
+ ---
2
+ type: decision
3
+ title: The function runner resolves wire services itself
4
+ description: A function's wire services come from its own package's factory or the app's registered one, never borrowed from whoever called it
5
+ tags: core
6
+ ---
7
+
8
+ # The function runner resolves wire services itself
9
+
10
+ `runPikkuFunc` picks the wire-services factory for the function it is about to
11
+ run: the function's addon package factory if it has one, else a factory the
12
+ transport passed explicitly, else the app's registered one
13
+ (`getCreateWireServices()`). A caller that passes nothing still gets the
14
+ function its wire services.
15
+
16
+ Before this, only the transports passed a factory. `rpc.invoke`, `rpc.exposed`,
17
+ gateway handlers and webhook sources passed none, so an app function reached
18
+ through them ran on whatever services its caller had already merged. That held
19
+ until `singletonServicesOnly` let a caller skip building wire services: the
20
+ generated `rpcCaller` behind `/rpc/:rpcName` reads no service, was marked, and
21
+ every app function behind it lost `events` and every other wire service. Nested
22
+ RPC therefore gets `resolvedSingletonServices`, not the caller's merged set.
23
+
24
+ Channels are the one caller that legitimately shares wire services: they build
25
+ one set per connection. They hand it over as `wireServices`, which the runner
26
+ uses as-is and does not close, because the connection owns its lifetime.
27
+
28
+ **What this rules out:** passing a caller's merged services down as
29
+ `singletonServices` to carry wire services into a nested call, and deciding in
30
+ one function's meta whether a different function gets wire services.
31
+ `singletonServicesOnly` only ever describes the function it is set on.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pikku/core",
3
- "version": "0.12.128",
3
+ "version": "0.12.129",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/pikkujs/pikku.git",