@superpanel-sdk/runtime 0.2.1 → 0.2.2

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.
@@ -1,5 +1,6 @@
1
1
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
2
2
  import path from 'node:path';
3
+ import { withCompanyImportGraph } from '../company-import-graph.js';
3
4
  import { listSequenceIds, loadCompanySchema, loadSequenceSlots } from '../load.js';
4
5
  import { mergeEffectiveSchema } from '../merge.js';
5
6
  import { findTsModule, resolveTsModule } from '../paths.js';
@@ -12,6 +13,9 @@ function sequenceCaseContextGenPath(repoRoot, sequenceId) {
12
13
  return path.join(repoRoot, 'sequences', sequenceId, '__gen', 'case-context.type.ts');
13
14
  }
14
15
  export async function generateSequence(repoRoot, sequenceId, options = {}) {
16
+ return withCompanyImportGraph(repoRoot, () => generateSequenceInGraph(repoRoot, sequenceId, options));
17
+ }
18
+ async function generateSequenceInGraph(repoRoot, sequenceId, options) {
15
19
  const schemaPath = resolveTsModule(repoRoot, 'schema', `No company schema found. Expected schema.ts or schema/index.ts under ${repoRoot}`);
16
20
  const slotsPath = resolveTsModule(path.join(repoRoot, 'sequences', sequenceId), 'slots', `No sequence slots found. Expected sequences/${sequenceId}/slots.ts or sequences/${sequenceId}/slots/index.ts`);
17
21
  const company = await loadCompanySchema(schemaPath);
@@ -38,15 +42,17 @@ export async function generateSequence(repoRoot, sequenceId, options = {}) {
38
42
  };
39
43
  }
40
44
  export async function generateAll(repoRoot, options = {}) {
41
- const ids = options.sequenceIds ?? listSequenceIds(repoRoot);
42
- if (ids.length === 0) {
43
- throw new Error(`No sequences with slots found under ${path.join(repoRoot, 'sequences')} (expected slots.ts or slots/index.ts)`);
44
- }
45
- const results = [];
46
- for (const sequenceId of ids) {
47
- results.push(await generateSequence(repoRoot, sequenceId, options));
48
- }
49
- return results;
45
+ return withCompanyImportGraph(repoRoot, async () => {
46
+ const ids = options.sequenceIds ?? listSequenceIds(repoRoot);
47
+ if (ids.length === 0) {
48
+ throw new Error(`No sequences with slots found under ${path.join(repoRoot, 'sequences')} (expected slots.ts or slots/index.ts)`);
49
+ }
50
+ const results = [];
51
+ for (const sequenceId of ids) {
52
+ results.push(await generateSequence(repoRoot, sequenceId, options));
53
+ }
54
+ return results;
55
+ });
50
56
  }
51
57
  function assertFileMatches(outPath, next, repoRoot) {
52
58
  let current = '';
@@ -0,0 +1,11 @@
1
+ /** Stamp a company-repo entry URL, or return it unchanged outside a graph. */
2
+ export declare function companyImportGraphHref(fileHref: string): string;
3
+ /**
4
+ * Load company-repo modules as one ESM graph.
5
+ *
6
+ * Nested calls (same async context) reuse the graph so compile + overlay
7
+ * `loadSequenceFunctions` share object identity. Overlapping outer calls wait
8
+ * their turn, then bump generation so watch sees new source.
9
+ */
10
+ export declare function withCompanyImportGraph<T>(repoRoot: string, fn: () => Promise<T>): Promise<T>;
11
+ //# sourceMappingURL=company-import-graph.d.ts.map
@@ -0,0 +1,105 @@
1
+ import { AsyncLocalStorage } from 'node:async_hooks';
2
+ import { realpathSync } from 'node:fs';
3
+ import { registerHooks } from 'node:module';
4
+ import path from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
6
+ const COMPANY_GRAPH_GEN_QUERY = 'spgen';
7
+ const COMPANY_GRAPH_ROOT_QUERY = 'sproot';
8
+ const graph = new AsyncLocalStorage();
9
+ let generation = 0;
10
+ let registered = false;
11
+ let outerTail = Promise.resolve();
12
+ function filePathFromUrl(url) {
13
+ const parsed = new URL(url);
14
+ parsed.search = '';
15
+ parsed.hash = '';
16
+ return fileURLToPath(parsed);
17
+ }
18
+ function realPath(filePath) {
19
+ try {
20
+ return realpathSync(filePath);
21
+ }
22
+ catch {
23
+ return path.resolve(filePath);
24
+ }
25
+ }
26
+ function underRoot(filePath, root) {
27
+ const resolved = realPath(filePath);
28
+ const base = realPath(root);
29
+ return resolved === base || resolved.startsWith(`${base}${path.sep}`);
30
+ }
31
+ function inNodeModules(filePath, url) {
32
+ return (url.includes('/node_modules/') ||
33
+ url.includes('\\node_modules\\') ||
34
+ filePath.includes(`${path.sep}node_modules${path.sep}`));
35
+ }
36
+ function stampCompanyResolve(specifier, context, nextResolve) {
37
+ const result = nextResolve(specifier, context);
38
+ if (!result.url.startsWith('file:'))
39
+ return result;
40
+ const resolved = new URL(result.url);
41
+ if (resolved.searchParams.has(COMPANY_GRAPH_GEN_QUERY))
42
+ return result;
43
+ const parent = context.parentURL ? new URL(context.parentURL) : null;
44
+ const graphGeneration = parent?.searchParams.get(COMPANY_GRAPH_GEN_QUERY);
45
+ const repoRoot = parent?.searchParams.get(COMPANY_GRAPH_ROOT_QUERY);
46
+ if (!graphGeneration || !repoRoot)
47
+ return result;
48
+ let resolvedFile;
49
+ try {
50
+ resolvedFile = filePathFromUrl(result.url);
51
+ }
52
+ catch {
53
+ return result;
54
+ }
55
+ if (inNodeModules(resolvedFile, result.url))
56
+ return result;
57
+ if (!underRoot(resolvedFile, repoRoot))
58
+ return result;
59
+ resolved.searchParams.set(COMPANY_GRAPH_GEN_QUERY, graphGeneration);
60
+ resolved.searchParams.set(COMPANY_GRAPH_ROOT_QUERY, repoRoot);
61
+ return { ...result, url: resolved.href };
62
+ }
63
+ function ensureHookRegistered() {
64
+ if (registered)
65
+ return;
66
+ registered = true;
67
+ registerHooks({ resolve: stampCompanyResolve });
68
+ }
69
+ /** Stamp a company-repo entry URL, or return it unchanged outside a graph. */
70
+ export function companyImportGraphHref(fileHref) {
71
+ const store = graph.getStore();
72
+ if (!store)
73
+ return fileHref;
74
+ const url = new URL(fileHref);
75
+ url.searchParams.set(COMPANY_GRAPH_GEN_QUERY, String(store.generation));
76
+ url.searchParams.set(COMPANY_GRAPH_ROOT_QUERY, store.repoRoot);
77
+ return url.href;
78
+ }
79
+ /**
80
+ * Load company-repo modules as one ESM graph.
81
+ *
82
+ * Nested calls (same async context) reuse the graph so compile + overlay
83
+ * `loadSequenceFunctions` share object identity. Overlapping outer calls wait
84
+ * their turn, then bump generation so watch sees new source.
85
+ */
86
+ export async function withCompanyImportGraph(repoRoot, fn) {
87
+ ensureHookRegistered();
88
+ if (graph.getStore())
89
+ return fn();
90
+ const root = realpathSync(path.resolve(repoRoot));
91
+ let release;
92
+ const previous = outerTail;
93
+ outerTail = new Promise((resolve) => {
94
+ release = resolve;
95
+ });
96
+ await previous;
97
+ generation += 1;
98
+ try {
99
+ return await graph.run({ generation, repoRoot: root }, fn);
100
+ }
101
+ finally {
102
+ release();
103
+ }
104
+ }
105
+ //# sourceMappingURL=company-import-graph.js.map
@@ -1,8 +1,9 @@
1
1
  import type { FuncMap } from '../../core/functions/types.js';
2
2
  import type { CompanySchema } from '../../core/slot/schema.js';
3
- import type { SequenceBuild } from '../types.js';
3
+ import type { RuntimePackageSource, SequenceBuild } from '../types.js';
4
4
  /** SequenceBuild / SequenceConfigBuild cache key. Bump when compiled `code` shape changes. */
5
5
  export declare function runtimePackageVersion(): string;
6
+ export declare function runtimePackageSource(repoRoot: string): RuntimePackageSource;
6
7
  /** Deep-clone company schema with derived computeFn normalized to string names. */
7
8
  export declare function snapshotCompanySchema(company: CompanySchema, functions?: FuncMap): CompanySchema;
8
9
  export declare function sequenceBuildPath(repoRoot: string, sequenceId: string): string;
@@ -12,6 +13,8 @@ export type CompileSequenceOptions = {
12
13
  includeCode?: boolean;
13
14
  /** Persist on SequenceBuild.sha. Defaults to `local`. */
14
15
  sha?: string;
16
+ /** Dependency source captured before deployment rewrites package.json. */
17
+ runtimeSource?: RuntimePackageSource;
15
18
  };
16
19
  export type CompileSequenceResult = {
17
20
  sequenceId: string;
@@ -4,6 +4,7 @@ import { fileURLToPath } from 'node:url';
4
4
  import { resolveCaseContextViewFn } from '../../core/functions/define/case-context.js';
5
5
  import { resolveFuncName } from '../../core/functions/func-ref.js';
6
6
  import { compileSlotSchema } from '../../core/slot/compile-schema.js';
7
+ import { withCompanyImportGraph } from '../company-import-graph.js';
7
8
  import { listSequenceIds, loadCompanySchema, loadSequenceAgendas, loadSequenceCaseContextView, loadSequenceFunctions, loadSequenceSlots, } from '../load.js';
8
9
  import { findTsModule, resolveTsModule } from '../paths.js';
9
10
  import { compileSequenceAgendas, fatalAgendaIssues } from './compile-agenda.js';
@@ -27,6 +28,18 @@ export function runtimePackageVersion() {
27
28
  const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
28
29
  return pkg.version ?? '0.0.0';
29
30
  }
31
+ export function runtimePackageSource(repoRoot) {
32
+ try {
33
+ const pkg = JSON.parse(readFileSync(path.join(repoRoot, 'package.json'), 'utf8'));
34
+ const spec = pkg.dependencies?.['@superpanel-sdk/runtime'] ??
35
+ pkg.devDependencies?.['@superpanel-sdk/runtime'] ??
36
+ pkg.optionalDependencies?.['@superpanel-sdk/runtime'];
37
+ return spec?.startsWith('file:') || spec?.startsWith('workspace:') ? 'local-file' : 'npm';
38
+ }
39
+ catch {
40
+ return 'npm';
41
+ }
42
+ }
30
43
  /** Deep-clone company schema with derived computeFn normalized to string names. */
31
44
  export function snapshotCompanySchema(company, functions) {
32
45
  const tables = {};
@@ -51,6 +64,9 @@ export function sequenceBuildPath(repoRoot, sequenceId) {
51
64
  return path.join(repoRoot, 'sequences', sequenceId, 'build', 'sequence-build.json');
52
65
  }
53
66
  export async function compileSequence(repoRoot, sequenceId, options = {}) {
67
+ return withCompanyImportGraph(repoRoot, () => compileSequenceInGraph(repoRoot, sequenceId, options));
68
+ }
69
+ async function compileSequenceInGraph(repoRoot, sequenceId, options) {
54
70
  const schemaPath = resolveTsModule(repoRoot, 'schema', `No company schema found. Expected schema.ts or schema/index.ts under ${repoRoot}`);
55
71
  const sequenceDir = path.join(repoRoot, 'sequences', sequenceId);
56
72
  const slotsPath = resolveTsModule(sequenceDir, 'slots', `No sequence slots found. Expected sequences/${sequenceId}/slots.ts or sequences/${sequenceId}/slots/index.ts`);
@@ -92,6 +108,7 @@ export async function compileSequence(repoRoot, sequenceId, options = {}) {
92
108
  sequenceId,
93
109
  sha: options.sha ?? 'local',
94
110
  engineVersion: runtimePackageVersion(),
111
+ runtimeSource: options.runtimeSource ?? runtimePackageSource(repoRoot),
95
112
  compiledAt: new Date().toISOString(),
96
113
  companySchemaSnapshot: snapshotCompanySchema(company, functions),
97
114
  schema: compiledAgendas.schema,
@@ -108,14 +125,16 @@ export async function compileSequence(repoRoot, sequenceId, options = {}) {
108
125
  return { sequenceId, outPath, build, wrote: !options.dryRun };
109
126
  }
110
127
  export async function compileAll(repoRoot, options = {}) {
111
- const ids = options.sequenceIds ?? listSequenceIds(repoRoot);
112
- if (ids.length === 0) {
113
- throw new Error(`No sequences with slots found under ${path.join(repoRoot, 'sequences')} (expected slots.ts or slots/index.ts)`);
114
- }
115
- const results = [];
116
- for (const sequenceId of ids) {
117
- results.push(await compileSequence(repoRoot, sequenceId, options));
118
- }
119
- return results;
128
+ return withCompanyImportGraph(repoRoot, async () => {
129
+ const ids = options.sequenceIds ?? listSequenceIds(repoRoot);
130
+ if (ids.length === 0) {
131
+ throw new Error(`No sequences with slots found under ${path.join(repoRoot, 'sequences')} (expected slots.ts or slots/index.ts)`);
132
+ }
133
+ const results = [];
134
+ for (const sequenceId of ids) {
135
+ results.push(await compileSequence(repoRoot, sequenceId, options));
136
+ }
137
+ return results;
138
+ });
120
139
  }
121
140
  //# sourceMappingURL=compile-sequence.js.map
@@ -3,11 +3,11 @@ import path from 'node:path';
3
3
  import { pathToFileURL } from 'node:url';
4
4
  import { resolveCaseContextViewFn } from '../core/functions/define/case-context.js';
5
5
  import { isAgendaTree, isSequenceAgendas } from '../core/stage/flatten.js';
6
+ import { companyImportGraphHref } from './company-import-graph.js';
6
7
  import { findTsModule } from './paths.js';
7
8
  async function importModule(filePath) {
8
- const href = pathToFileURL(filePath).href;
9
- // Cache-bust so --watch reloads edited TS modules.
10
- const mod = await import(`${href}?t=${Date.now()}`);
9
+ const href = companyImportGraphHref(pathToFileURL(filePath).href);
10
+ const mod = await import(href);
11
11
  return mod;
12
12
  }
13
13
  function isFuncMap(value) {
@@ -2,6 +2,7 @@ import type { CaseContextViewRegistryEntry, FunctionRegistry } from '../core/fun
2
2
  import type { CompanySchema, EffectiveSchema } from '../core/slot/schema.js';
3
3
  import type { CompiledSlotSchema } from '../core/slot/types.js';
4
4
  import type { CompiledAgenda } from '../core/stage/types.js';
5
+ export type RuntimePackageSource = 'local-file' | 'npm';
5
6
  /**
6
7
  * Compiled sequence artifact (Manual §9 subset; grows over time).
7
8
  * Omitted for now: paramsDefaults, schedulers, caseManual.
@@ -13,6 +14,10 @@ export type SequenceBuild = {
13
14
  sequenceId: string;
14
15
  sha: string;
15
16
  engineVersion: string;
17
+ /** Installed @superpanel-sdk/runtime version resolved from the company repo. */
18
+ runtimeVersion?: string;
19
+ /** Dependency source declared by the company repo. Optional for legacy persisted builds. */
20
+ runtimeSource?: RuntimePackageSource;
16
21
  compiledAt: string;
17
22
  companySchemaSnapshot: CompanySchema;
18
23
  /** SlotEngine runtime schema (tables + derivedOrder / reverseDeps). */
@@ -1,6 +1,7 @@
1
1
  import path from 'node:path';
2
2
  import chokidar from 'chokidar';
3
3
  import { compileAll } from '../../builder/compile/compile-sequence.js';
4
+ import { withCompanyImportGraph } from '../../builder/company-import-graph.js';
4
5
  import { findTsModule } from '../../builder/paths.js';
5
6
  import { listSequenceIds, loadSequenceCaseContextView, loadSequenceFunctions } from '../../builder/load.js';
6
7
  import { createSourceComputeHost } from '../../compute-host/source-compute-host.js';
@@ -37,30 +38,34 @@ function isSchemaPath(repoRoot, file) {
37
38
  return rel === 'schema.ts' || rel.startsWith(`schema${path.sep}`) || rel.startsWith('schema/');
38
39
  }
39
40
  async function loadOne(repoRoot, sequenceId, generation) {
40
- const [compiled] = await compileAll(repoRoot, {
41
- sequenceIds: [sequenceId],
42
- dryRun: true,
43
- includeCode: false,
41
+ return withCompanyImportGraph(repoRoot, async () => {
42
+ const [compiled] = await compileAll(repoRoot, {
43
+ sequenceIds: [sequenceId],
44
+ dryRun: true,
45
+ includeCode: false,
46
+ });
47
+ if (!compiled) {
48
+ throw new Error(`Failed to compile sequence ${sequenceId}`);
49
+ }
50
+ const sequenceDir = path.join(repoRoot, 'sequences', sequenceId);
51
+ const functionsPath = findTsModule(sequenceDir, 'functions');
52
+ if (!functionsPath) {
53
+ throw new Error(`No functions module for sequence ${sequenceId}`);
54
+ }
55
+ const functions = await loadSequenceFunctions(functionsPath);
56
+ const viewPath = findTsModule(sequenceDir, 'view');
57
+ const view = viewPath ? await loadSequenceCaseContextView(viewPath) : undefined;
58
+ return { build: compiled.build, functions, view, generation };
44
59
  });
45
- if (!compiled) {
46
- throw new Error(`Failed to compile sequence ${sequenceId}`);
47
- }
48
- const sequenceDir = path.join(repoRoot, 'sequences', sequenceId);
49
- const functionsPath = findTsModule(sequenceDir, 'functions');
50
- if (!functionsPath) {
51
- throw new Error(`No functions module for sequence ${sequenceId}`);
52
- }
53
- const functions = await loadSequenceFunctions(functionsPath);
54
- const viewPath = findTsModule(sequenceDir, 'view');
55
- const view = viewPath ? await loadSequenceCaseContextView(viewPath) : undefined;
56
- return { build: compiled.build, functions, view, generation };
57
60
  }
58
61
  async function loadSequences(repoRoot, sequenceIds, generation) {
59
- const out = new Map();
60
- for (const sequenceId of sequenceIds) {
61
- out.set(sequenceId, await loadOne(repoRoot, sequenceId, generation));
62
- }
63
- return out;
62
+ return withCompanyImportGraph(repoRoot, async () => {
63
+ const out = new Map();
64
+ for (const sequenceId of sequenceIds) {
65
+ out.set(sequenceId, await loadOne(repoRoot, sequenceId, generation));
66
+ }
67
+ return out;
68
+ });
64
69
  }
65
70
  function send(ws, message) {
66
71
  ws.send(encodeOverlayMessage(message));
@@ -74,6 +79,8 @@ async function handleExecute(loaded, message) {
74
79
  caseContextView: loaded.view,
75
80
  data: message.data,
76
81
  runToken: message.runToken,
82
+ runtimeVersion: loaded.build.runtimeVersion ?? loaded.build.engineVersion,
83
+ runtimeSource: loaded.build.runtimeSource ?? 'npm',
77
84
  getRequestId: () => message.logRequestId,
78
85
  getTags: () => message.logTags,
79
86
  });
@@ -249,32 +256,37 @@ export async function runServe(repoRoot, options = {}) {
249
256
  logField('websocket', url);
250
257
  let timer;
251
258
  let pending = { all: false, ids: new Set() };
259
+ let flushing = false;
252
260
  const flush = () => {
253
- const job = pending;
254
- pending = { all: false, ids: new Set() };
261
+ if (flushing)
262
+ return;
263
+ flushing = true;
255
264
  void (async () => {
256
265
  try {
257
- generation += 1;
258
- const ids = job.all ? sequenceIds : [...job.ids];
259
- if (job.all) {
260
- const next = await loadSequences(repoRoot, sequenceIds, generation);
261
- sequences.clear();
266
+ while (pending.all || pending.ids.size > 0) {
267
+ const job = pending;
268
+ pending = { all: false, ids: new Set() };
269
+ generation += 1;
270
+ const ids = job.all ? sequenceIds : [...job.ids];
271
+ const next = await loadSequences(repoRoot, ids, generation);
272
+ if (job.all)
273
+ sequences.clear();
262
274
  for (const [id, loaded] of next)
263
275
  sequences.set(id, loaded);
264
- }
265
- else {
266
- for (const sequenceId of ids) {
267
- sequences.set(sequenceId, await loadOne(repoRoot, sequenceId, generation));
276
+ if (socket && socket.readyState === WebSocket.OPEN) {
277
+ pushOverlays(socket, sequences);
278
+ console.log(`reloaded sequences ${ids.join(', ')}`);
268
279
  }
269
280
  }
270
- if (socket && socket.readyState === WebSocket.OPEN) {
271
- pushOverlays(socket, sequences);
272
- console.log(`reloaded sequences ${ids.join(', ')}`);
273
- }
274
281
  }
275
282
  catch (error) {
276
283
  console.error('reload failed:', error instanceof Error ? error.message : error);
277
284
  }
285
+ finally {
286
+ flushing = false;
287
+ if (pending.all || pending.ids.size > 0)
288
+ flush();
289
+ }
278
290
  })();
279
291
  };
280
292
  chokidar
@@ -12,6 +12,10 @@ export type SourceComputeHostOptions = {
12
12
  data?: ActionDataSlice;
13
13
  /** When set with data.config.backendUrl, uses Http RpcApiHost. */
14
14
  runToken?: string;
15
+ /** Runtime version forwarded on every `ctx.api` RPC. Required when RpcApiHost is used. */
16
+ runtimeVersion?: string;
17
+ /** Runtime dependency source forwarded with runtimeVersion. Required when RpcApiHost is used. */
18
+ runtimeSource?: 'local-file' | 'npm';
15
19
  /** Forwarded on every `ctx.api` RPC as `x-request-id`. */
16
20
  getRequestId?: () => string | undefined;
17
21
  /** Forwarded to script log RPC calls. */
@@ -62,9 +62,14 @@ function resolveApiHost(options) {
62
62
  if (options.apiHost)
63
63
  return options.apiHost;
64
64
  if (options.runToken && options.data?.config.backendUrl) {
65
+ if (!options.runtimeVersion || !options.runtimeSource) {
66
+ throw new Error('runtimeVersion and runtimeSource are required when using RpcApiHost');
67
+ }
65
68
  return createRpcApiHost({
66
69
  baseUrl: options.data.config.backendUrl,
67
70
  getToken: () => options.runToken,
71
+ runtimeVersion: options.runtimeVersion,
72
+ runtimeSource: options.runtimeSource,
68
73
  getRequestId: options.getRequestId,
69
74
  getTags: options.getTags,
70
75
  });
@@ -3,7 +3,7 @@ export { buildActionContext, type BuildActionContextOptions } from './build-cont
3
3
  export { resolveSystemFnParams } from './system-args.js';
4
4
  export { createFakeApiHost, type FakeApiHostOptions } from './fake/index.js';
5
5
  export { defaultPlatformApis, emptyIntegration, type PlatformApiOverrides } from './fake/empty-api.js';
6
- export { createRpcApiHost, LOG_TAGS_HEADER, REQUEST_ID_HEADER, encodeLogTagsHeader, readLogTagsHeader, readRequestIdHeader, } from './rpc/index.js';
6
+ export { createRpcApiHost, LOG_TAGS_HEADER, REQUEST_ID_HEADER, RUNTIME_VERSION_HEADER, encodeLogTagsHeader, formatRuntimeVersion, readLogTagsHeader, readRequestIdHeader, readRuntimeVersionHeader, } from './rpc/index.js';
7
7
  export { bindLogMeta, noopLog } from './log.js';
8
8
  export { makeCtx, makeCtxAsync } from './make-ctx.js';
9
9
  export { defaultHttpApi } from '../core/context/http.js';
@@ -2,7 +2,7 @@ export { buildActionContext } from './build-context.js';
2
2
  export { resolveSystemFnParams } from './system-args.js';
3
3
  export { createFakeApiHost } from './fake/index.js';
4
4
  export { defaultPlatformApis, emptyIntegration } from './fake/empty-api.js';
5
- export { createRpcApiHost, LOG_TAGS_HEADER, REQUEST_ID_HEADER, encodeLogTagsHeader, readLogTagsHeader, readRequestIdHeader, } from './rpc/index.js';
5
+ export { createRpcApiHost, LOG_TAGS_HEADER, REQUEST_ID_HEADER, RUNTIME_VERSION_HEADER, encodeLogTagsHeader, formatRuntimeVersion, readLogTagsHeader, readRequestIdHeader, readRuntimeVersionHeader, } from './rpc/index.js';
6
6
  export { bindLogMeta, noopLog } from './log.js';
7
7
  export { makeCtx, makeCtxAsync } from './make-ctx.js';
8
8
  export { defaultHttpApi } from '../core/context/http.js';
@@ -6,5 +6,5 @@ export { createListBindingsProcedure } from './integration/list-bindings.js';
6
6
  export { createPlatformRouter, type PlatformInvokeHandler } from './platform.js';
7
7
  export { createLogRouter, type LogEmitHandler } from './log.js';
8
8
  export { createRpcApiHost } from './trpc-client.js';
9
- export { LOG_TAGS_HEADER, REQUEST_ID_HEADER, encodeLogTagsHeader, readLogTagsHeader, readRequestIdHeader, } from './request-id.js';
9
+ export { LOG_TAGS_HEADER, REQUEST_ID_HEADER, RUNTIME_VERSION_HEADER, encodeLogTagsHeader, formatRuntimeVersion, readLogTagsHeader, readRequestIdHeader, readRuntimeVersionHeader, } from './request-id.js';
10
10
  //# sourceMappingURL=index.d.ts.map
@@ -5,5 +5,5 @@ export { createListBindingsProcedure } from './integration/list-bindings.js';
5
5
  export { createPlatformRouter } from './platform.js';
6
6
  export { createLogRouter } from './log.js';
7
7
  export { createRpcApiHost } from './trpc-client.js';
8
- export { LOG_TAGS_HEADER, REQUEST_ID_HEADER, encodeLogTagsHeader, readLogTagsHeader, readRequestIdHeader, } from './request-id.js';
8
+ export { LOG_TAGS_HEADER, REQUEST_ID_HEADER, RUNTIME_VERSION_HEADER, encodeLogTagsHeader, formatRuntimeVersion, readLogTagsHeader, readRequestIdHeader, readRuntimeVersionHeader, } from './request-id.js';
9
9
  //# sourceMappingURL=index.js.map
@@ -1,8 +1,13 @@
1
1
  export declare const REQUEST_ID_HEADER = "x-request-id";
2
2
  export declare const LOG_TAGS_HEADER = "x-superpanel-log-tags";
3
+ export declare const RUNTIME_VERSION_HEADER = "x-superpanel-runtime-version";
4
+ export declare function formatRuntimeVersion(version: string, source: 'local-file' | 'npm'): string;
3
5
  export declare function readRequestIdHeader(headers: {
4
6
  get(name: string): string | null;
5
7
  } | Record<string, string | string[] | undefined>): string | undefined;
8
+ export declare function readRuntimeVersionHeader(headers: {
9
+ get(name: string): string | null;
10
+ } | Record<string, string | string[] | undefined>): string | undefined;
6
11
  export declare function encodeLogTagsHeader(tags: string[]): string;
7
12
  export declare function readLogTagsHeader(headers: {
8
13
  get(name: string): string | null;
@@ -1,5 +1,9 @@
1
1
  export const REQUEST_ID_HEADER = 'x-request-id';
2
2
  export const LOG_TAGS_HEADER = 'x-superpanel-log-tags';
3
+ export const RUNTIME_VERSION_HEADER = 'x-superpanel-runtime-version';
4
+ export function formatRuntimeVersion(version, source) {
5
+ return `${version}-${source}`;
6
+ }
3
7
  function firstString(value) {
4
8
  const raw = Array.isArray(value) ? value[0] : value;
5
9
  return typeof raw === 'string' && raw.length > 0 ? raw : undefined;
@@ -11,6 +15,13 @@ export function readRequestIdHeader(headers) {
11
15
  const record = headers;
12
16
  return firstString(record[REQUEST_ID_HEADER]);
13
17
  }
18
+ export function readRuntimeVersionHeader(headers) {
19
+ if (typeof headers.get === 'function') {
20
+ return firstString(headers.get(RUNTIME_VERSION_HEADER));
21
+ }
22
+ const record = headers;
23
+ return firstString(record[RUNTIME_VERSION_HEADER]);
24
+ }
14
25
  export function encodeLogTagsHeader(tags) {
15
26
  return JSON.stringify(tags);
16
27
  }
@@ -2,6 +2,8 @@ import type { ApiHost } from '../types.js';
2
2
  export declare function createRpcApiHost(options: {
3
3
  baseUrl: string;
4
4
  getToken: () => string | Promise<string>;
5
+ runtimeVersion: string;
6
+ runtimeSource: 'local-file' | 'npm';
5
7
  getRequestId?: () => string | undefined;
6
8
  getTags?: () => string[] | undefined;
7
9
  waitUntil?: (promise: Promise<unknown>) => void;
@@ -3,7 +3,7 @@ import { platformSurfaces } from '../../core/context/platform-registry.js';
3
3
  import { defaultHttpApi } from '../../core/context/http.js';
4
4
  import { integrationSurfaceKeys, } from '../../core/context/types.js';
5
5
  import { bindEnvService } from '../../core/context/env.js';
6
- import { REQUEST_ID_HEADER } from './request-id.js';
6
+ import { REQUEST_ID_HEADER, RUNTIME_VERSION_HEADER, formatRuntimeVersion, } from './request-id.js';
7
7
  function rpcLogger(c, getTags, waitUntil) {
8
8
  const emit = (level, message, extra) => {
9
9
  const pending = c.log.emit.mutate({ level, message, extra, tags: getTags?.() }).catch(() => { });
@@ -86,6 +86,7 @@ export function createRpcApiHost(options) {
86
86
  headers: async () => {
87
87
  const headers = {
88
88
  authorization: `Bearer ${await options.getToken()}`,
89
+ [RUNTIME_VERSION_HEADER]: formatRuntimeVersion(options.runtimeVersion, options.runtimeSource),
89
90
  };
90
91
  const requestId = options.getRequestId?.();
91
92
  if (requestId)
@@ -2,6 +2,7 @@ import type { RunScope } from '../types.js';
2
2
  export type RpcContext = {
3
3
  run: RunScope;
4
4
  requestId?: string;
5
+ runtimeVersion?: string;
5
6
  };
6
7
  export declare const router: import("@trpc/server").TRPCRouterBuilder<{
7
8
  ctx: RpcContext;
package/dist/index.d.ts CHANGED
@@ -19,18 +19,18 @@ export { twilioCreateCallInput, twilioCreateCallOutput, twilioIncomingNumber, tw
19
19
  export type { ApplyWritesOptions, ApplyWritesResult, CompiledSlotSchema, ExtractedField, FieldHistoryEntry, FieldValue, HistoryAppend, MultiRowProjection, MultiRowRecord, MultiRowTableData, RecomputeResult, RuntimeDerivedFieldDef, RuntimeFieldDef, RuntimeNormalFieldDef, RuntimeSlotsSnapshot, RuntimeTableDef, SingleRowProjection, SingleRowTableData, SlotCurrent, SlotFillingOutput, SlotFillingValidateResult, TableData, UpdatedBy, ValidationIssue, ValidationResult, WriteSource, } from './core/slot/types.js';
20
20
  export type { FuncRef } from './core/functions/func-ref.js';
21
21
  export { resolveFuncName } from './core/functions/func-ref.js';
22
- export type { SequenceBuild } from './builder/types.js';
22
+ export type { RuntimePackageSource, SequenceBuild } from './builder/types.js';
23
23
  export type { CaseContextViewRegistryEntry, ComputeFn, CustomFn, DefinedFunc, FuncMap, FuncOptions, FuncRegistration, FunctionCall, FunctionCtx, FunctionRegistry, FunctionRegistryEntry, GetFirstMessageFunction, GetFirstMessageParams, SystemFuncMap, WebHookFunction, WebHookFunctionParams, } from './core/functions/types.js';
24
24
  export { toFuncRegistrations } from './core/functions/types.js';
25
25
  export { toCaseContextViewRegistry, toFunctionRegistry } from './builder/compile/registry.js';
26
26
  export { compileFunctionCode } from './builder/compile/function-code.js';
27
- export { compileAll, compileSequence, runtimePackageVersion, snapshotCompanySchema, type CompileSequenceOptions, type CompileSequenceResult, } from './builder/compile/compile-sequence.js';
27
+ export { compileAll, compileSequence, runtimePackageSource, runtimePackageVersion, snapshotCompanySchema, type CompileSequenceOptions, type CompileSequenceResult, } from './builder/compile/compile-sequence.js';
28
28
  export { compileSequenceAgendas, fatalAgendaIssues } from './builder/compile/compile-agenda.js';
29
29
  export { loadSequenceCaseContextView, loadSequenceFunctions } from './builder/load.js';
30
30
  export { findTsModule } from './builder/paths.js';
31
31
  export type { OverlayClientMessage, OverlayExecuteMessage, OverlayExecuteResultMessage, OverlayAuthErrorMessage, OverlayAuthMessage, OverlayAuthOkMessage, OverlayMessage, OverlayPushMessage, OverlayReplacedMessage, OverlayServerMessage, } from './dev-overlay/protocol.js';
32
32
  export { encodeOverlayMessage, OVERLAY_REPLACED_CLOSE_CODE, parseOverlayMessage } from './dev-overlay/protocol.js';
33
- export { buildActionContext, bindLogMeta, createFakeApiHost, defaultPlatformApis, emptyIntegration, createRpcApiHost, LOG_TAGS_HEADER, REQUEST_ID_HEADER, encodeLogTagsHeader, readLogTagsHeader, readRequestIdHeader, makeCtx, makeCtxAsync, noopLog, type ActionContext, type ActionDataSlice, type ApiHost, type FakeApiHostOptions, type RunScope, } from './fn-context/index.js';
33
+ export { buildActionContext, bindLogMeta, createFakeApiHost, defaultPlatformApis, emptyIntegration, createRpcApiHost, LOG_TAGS_HEADER, REQUEST_ID_HEADER, RUNTIME_VERSION_HEADER, encodeLogTagsHeader, formatRuntimeVersion, readLogTagsHeader, readRequestIdHeader, readRuntimeVersionHeader, makeCtx, makeCtxAsync, noopLog, type ActionContext, type ActionDataSlice, type ApiHost, type FakeApiHostOptions, type RunScope, } from './fn-context/index.js';
34
34
  export { createAppRouter, createIntegrationRouter, createListBindingsProcedure, createLogRouter, createPlatformRouter, isRpcCodedError, parseScriptInput, ScriptInputError, type AppRouter, type RpcCodedError, type RpcErrorCode, type IntegrationInvokeHandler, type LogEmitHandler, type PlatformInvokeHandler, type RpcContext, } from './fn-context/rpc/index.js';
35
35
  export * from './compute-host/index.js';
36
36
  export { defineBoolComputeFunc, defineComputeFunc } from './core/functions/define/compute.js';
package/dist/index.js CHANGED
@@ -14,12 +14,12 @@ export { resolveFuncName } from './core/functions/func-ref.js';
14
14
  export { toFuncRegistrations } from './core/functions/types.js';
15
15
  export { toCaseContextViewRegistry, toFunctionRegistry } from './builder/compile/registry.js';
16
16
  export { compileFunctionCode } from './builder/compile/function-code.js';
17
- export { compileAll, compileSequence, runtimePackageVersion, snapshotCompanySchema, } from './builder/compile/compile-sequence.js';
17
+ export { compileAll, compileSequence, runtimePackageSource, runtimePackageVersion, snapshotCompanySchema, } from './builder/compile/compile-sequence.js';
18
18
  export { compileSequenceAgendas, fatalAgendaIssues } from './builder/compile/compile-agenda.js';
19
19
  export { loadSequenceCaseContextView, loadSequenceFunctions } from './builder/load.js';
20
20
  export { findTsModule } from './builder/paths.js';
21
21
  export { encodeOverlayMessage, OVERLAY_REPLACED_CLOSE_CODE, parseOverlayMessage } from './dev-overlay/protocol.js';
22
- export { buildActionContext, bindLogMeta, createFakeApiHost, defaultPlatformApis, emptyIntegration, createRpcApiHost, LOG_TAGS_HEADER, REQUEST_ID_HEADER, encodeLogTagsHeader, readLogTagsHeader, readRequestIdHeader, makeCtx, makeCtxAsync, noopLog, } from './fn-context/index.js';
22
+ export { buildActionContext, bindLogMeta, createFakeApiHost, defaultPlatformApis, emptyIntegration, createRpcApiHost, LOG_TAGS_HEADER, REQUEST_ID_HEADER, RUNTIME_VERSION_HEADER, encodeLogTagsHeader, formatRuntimeVersion, readLogTagsHeader, readRequestIdHeader, readRuntimeVersionHeader, makeCtx, makeCtxAsync, noopLog, } from './fn-context/index.js';
23
23
  export { createAppRouter, createIntegrationRouter, createListBindingsProcedure, createLogRouter, createPlatformRouter, isRpcCodedError, parseScriptInput, ScriptInputError, } from './fn-context/rpc/index.js';
24
24
  export * from './compute-host/index.js';
25
25
  export { defineBoolComputeFunc, defineComputeFunc } from './core/functions/define/compute.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@superpanel-sdk/runtime",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "description": "Slot + Stage runtime: schema, writes, derived recompute, agenda compile/walk/simulate",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",