@superpanel-sdk/runtime 0.2.0 → 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,7 +79,10 @@ 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,
85
+ getTags: () => message.logTags,
78
86
  });
79
87
  try {
80
88
  return await host.execute(message.name, message.args);
@@ -248,32 +256,37 @@ export async function runServe(repoRoot, options = {}) {
248
256
  logField('websocket', url);
249
257
  let timer;
250
258
  let pending = { all: false, ids: new Set() };
259
+ let flushing = false;
251
260
  const flush = () => {
252
- const job = pending;
253
- pending = { all: false, ids: new Set() };
261
+ if (flushing)
262
+ return;
263
+ flushing = true;
254
264
  void (async () => {
255
265
  try {
256
- generation += 1;
257
- const ids = job.all ? sequenceIds : [...job.ids];
258
- if (job.all) {
259
- const next = await loadSequences(repoRoot, sequenceIds, generation);
260
- 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();
261
274
  for (const [id, loaded] of next)
262
275
  sequences.set(id, loaded);
263
- }
264
- else {
265
- for (const sequenceId of ids) {
266
- 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(', ')}`);
267
279
  }
268
280
  }
269
- if (socket && socket.readyState === WebSocket.OPEN) {
270
- pushOverlays(socket, sequences);
271
- console.log(`reloaded sequences ${ids.join(', ')}`);
272
- }
273
281
  }
274
282
  catch (error) {
275
283
  console.error('reload failed:', error instanceof Error ? error.message : error);
276
284
  }
285
+ finally {
286
+ flushing = false;
287
+ if (pending.all || pending.ids.size > 0)
288
+ flush();
289
+ }
277
290
  })();
278
291
  };
279
292
  chokidar
@@ -12,8 +12,14 @@ 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;
21
+ /** Forwarded to script log RPC calls. */
22
+ getTags?: () => string[] | undefined;
17
23
  };
18
24
  export interface SourceComputeHost extends ComputeHost {
19
25
  }
@@ -62,10 +62,16 @@ 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,
74
+ getTags: options.getTags,
69
75
  });
70
76
  }
71
77
  return createFakeApiHost();
@@ -33,6 +33,8 @@ export type OverlayExecuteMessage = {
33
33
  runToken?: string;
34
34
  /** Logger request id for `ctx.api` RPC; not the overlay execute correlation id. */
35
35
  logRequestId?: string;
36
+ /** Logger tags inherited from the backend execution context. */
37
+ logTags?: string[];
36
38
  };
37
39
  export type OverlayExecuteResultMessage = {
38
40
  type: 'execute.result';
@@ -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, REQUEST_ID_HEADER, 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, REQUEST_ID_HEADER, 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 { REQUEST_ID_HEADER, 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 { REQUEST_ID_HEADER, 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
@@ -8,11 +8,13 @@ export declare const logEmitInput: z.ZodObject<{
8
8
  }>;
9
9
  message: z.ZodString;
10
10
  extra: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
11
+ tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
11
12
  }, z.core.$strip>;
12
13
  export type LogEmitHandler = (input: {
13
14
  level: 'info' | 'warn' | 'error';
14
15
  message: string;
15
16
  extra?: Record<string, unknown>;
17
+ tags?: string[];
16
18
  ctx: RpcContext;
17
19
  }) => Promise<void>;
18
20
  export declare function createLogRouter(emit: LogEmitHandler): import("@trpc/server").TRPCBuiltRouter<{
@@ -26,6 +28,7 @@ export declare function createLogRouter(emit: LogEmitHandler): import("@trpc/ser
26
28
  level: "error" | "info" | "warn";
27
29
  message: string;
28
30
  extra?: Record<string, unknown> | undefined;
31
+ tags?: string[] | undefined;
29
32
  };
30
33
  output: {
31
34
  ok: true;
@@ -4,11 +4,12 @@ export const logEmitInput = z.object({
4
4
  level: z.enum(['info', 'warn', 'error']),
5
5
  message: z.string(),
6
6
  extra: z.record(z.string(), z.unknown()).optional(),
7
+ tags: z.array(z.string()).optional(),
7
8
  });
8
9
  export function createLogRouter(emit) {
9
10
  return router({
10
11
  emit: publicProcedure.input(logEmitInput).mutation(async ({ ctx, input }) => {
11
- await emit({ level: input.level, message: input.message, extra: input.extra, ctx });
12
+ await emit({ level: input.level, message: input.message, extra: input.extra, tags: input.tags, ctx });
12
13
  return { ok: true };
13
14
  }),
14
15
  });
@@ -1,5 +1,15 @@
1
1
  export declare const REQUEST_ID_HEADER = "x-request-id";
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;
2
5
  export declare function readRequestIdHeader(headers: {
3
6
  get(name: string): string | null;
4
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;
11
+ export declare function encodeLogTagsHeader(tags: string[]): string;
12
+ export declare function readLogTagsHeader(headers: {
13
+ get(name: string): string | null;
14
+ }): string[] | undefined;
5
15
  //# sourceMappingURL=request-id.d.ts.map
@@ -1,4 +1,9 @@
1
1
  export const REQUEST_ID_HEADER = 'x-request-id';
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
+ }
2
7
  function firstString(value) {
3
8
  const raw = Array.isArray(value) ? value[0] : value;
4
9
  return typeof raw === 'string' && raw.length > 0 ? raw : undefined;
@@ -10,4 +15,28 @@ export function readRequestIdHeader(headers) {
10
15
  const record = headers;
11
16
  return firstString(record[REQUEST_ID_HEADER]);
12
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
+ }
25
+ export function encodeLogTagsHeader(tags) {
26
+ return JSON.stringify(tags);
27
+ }
28
+ export function readLogTagsHeader(headers) {
29
+ const raw = headers.get(LOG_TAGS_HEADER);
30
+ if (!raw)
31
+ return undefined;
32
+ try {
33
+ const parsed = JSON.parse(raw);
34
+ if (!Array.isArray(parsed) || !parsed.every((tag) => typeof tag === 'string'))
35
+ return undefined;
36
+ return parsed;
37
+ }
38
+ catch {
39
+ return undefined;
40
+ }
41
+ }
13
42
  //# sourceMappingURL=request-id.js.map
@@ -2,6 +2,10 @@ 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;
8
+ getTags?: () => string[] | undefined;
9
+ waitUntil?: (promise: Promise<unknown>) => void;
6
10
  }): ApiHost;
7
11
  //# sourceMappingURL=trpc-client.d.ts.map
@@ -3,10 +3,16 @@ 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';
7
- function rpcLogger(c) {
6
+ import { REQUEST_ID_HEADER, RUNTIME_VERSION_HEADER, formatRuntimeVersion, } from './request-id.js';
7
+ function rpcLogger(c, getTags, waitUntil) {
8
8
  const emit = (level, message, extra) => {
9
- void c.log.emit.mutate({ level, message, extra }).catch(() => { });
9
+ const pending = c.log.emit.mutate({ level, message, extra, tags: getTags?.() }).catch(() => { });
10
+ if (waitUntil) {
11
+ waitUntil(pending);
12
+ }
13
+ else {
14
+ void pending;
15
+ }
10
16
  };
11
17
  return {
12
18
  info: (message, extra) => emit('info', message, extra),
@@ -80,6 +86,7 @@ export function createRpcApiHost(options) {
80
86
  headers: async () => {
81
87
  const headers = {
82
88
  authorization: `Bearer ${await options.getToken()}`,
89
+ [RUNTIME_VERSION_HEADER]: formatRuntimeVersion(options.runtimeVersion, options.runtimeSource),
83
90
  };
84
91
  const requestId = options.getRequestId?.();
85
92
  if (requestId)
@@ -100,7 +107,7 @@ export function createRpcApiHost(options) {
100
107
  return {
101
108
  ...buildPlatformApis(c),
102
109
  integration: buildSurfaces(c, metas),
103
- log: rpcLogger(c),
110
+ log: rpcLogger(c, options.getTags, options.waitUntil),
104
111
  http: defaultHttpApi(),
105
112
  };
106
113
  },
@@ -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;
@@ -72,6 +72,7 @@ export declare function createAppRouter(modules: {
72
72
  level: "error" | "info" | "warn";
73
73
  message: string;
74
74
  extra?: Record<string, unknown> | undefined;
75
+ tags?: string[] | undefined;
75
76
  };
76
77
  output: {
77
78
  ok: true;
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, REQUEST_ID_HEADER, 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, REQUEST_ID_HEADER, 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.0",
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",