@portel/photon-core 2.14.0 → 2.16.0
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/README.md +189 -3
- package/dist/asset-discovery.d.ts.map +1 -1
- package/dist/asset-discovery.js +22 -1
- package/dist/asset-discovery.js.map +1 -1
- package/dist/base.d.ts +198 -0
- package/dist/base.d.ts.map +1 -1
- package/dist/base.js +228 -5
- package/dist/base.js.map +1 -1
- package/dist/generator.d.ts +16 -1
- package/dist/generator.d.ts.map +1 -1
- package/dist/generator.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/mixins.d.ts.map +1 -1
- package/dist/mixins.js +26 -1
- package/dist/mixins.js.map +1 -1
- package/dist/path-resolver.d.ts +36 -3
- package/dist/path-resolver.d.ts.map +1 -1
- package/dist/path-resolver.js +122 -17
- package/dist/path-resolver.js.map +1 -1
- package/dist/photon-loader-lite.js +1 -1
- package/dist/photon-loader-lite.js.map +1 -1
- package/dist/schema-extractor.d.ts +8 -1
- package/dist/schema-extractor.d.ts.map +1 -1
- package/dist/schema-extractor.js +56 -4
- package/dist/schema-extractor.js.map +1 -1
- package/dist/types.d.ts +3 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +1 -1
- package/src/asset-discovery.ts +21 -1
- package/src/base.ts +281 -8
- package/src/generator.ts +20 -1
- package/src/index.ts +6 -0
- package/src/mixins.ts +28 -2
- package/src/path-resolver.ts +141 -18
- package/src/photon-loader-lite.ts +1 -1
- package/src/schema-extractor.ts +66 -5
- package/src/types.ts +3 -1
|
@@ -368,7 +368,7 @@ function wrapStatefulMethods(
|
|
|
368
368
|
|
|
369
369
|
// Skip framework-injected methods from withPhotonCapabilities
|
|
370
370
|
const frameworkMethods = new Set([
|
|
371
|
-
'emit', 'call', 'mcp', 'setMCPFactory', 'onInitialize', 'onShutdown',
|
|
371
|
+
'emit', 'render', 'call', 'mcp', 'setMCPFactory', 'onInitialize', 'onShutdown',
|
|
372
372
|
]);
|
|
373
373
|
|
|
374
374
|
// Walk the prototype chain to find all public methods
|
package/src/schema-extractor.ts
CHANGED
|
@@ -24,6 +24,13 @@ export interface ExtractedMetadata {
|
|
|
24
24
|
configSchema?: ConfigSchema;
|
|
25
25
|
/** Notification subscription from @notify-on tag */
|
|
26
26
|
notificationSubscriptions?: NotificationSubscription;
|
|
27
|
+
/**
|
|
28
|
+
* MCP OAuth auth requirement (from @auth tag)
|
|
29
|
+
* - 'required': all methods require authenticated caller
|
|
30
|
+
* - 'optional': caller populated if token present, anonymous allowed
|
|
31
|
+
* - string URL: OIDC provider URL (implies required)
|
|
32
|
+
*/
|
|
33
|
+
auth?: 'required' | 'optional' | string;
|
|
27
34
|
}
|
|
28
35
|
|
|
29
36
|
/**
|
|
@@ -64,6 +71,9 @@ export class SchemaExtractor {
|
|
|
64
71
|
// Notification subscriptions tracking (from @notify-on tag)
|
|
65
72
|
let notificationSubscriptions: NotificationSubscription | undefined;
|
|
66
73
|
|
|
74
|
+
// MCP OAuth auth requirement (from @auth tag)
|
|
75
|
+
let auth: 'required' | 'optional' | string | undefined;
|
|
76
|
+
|
|
67
77
|
try {
|
|
68
78
|
// If source doesn't contain a class declaration, wrap it in one
|
|
69
79
|
let sourceToParse = source;
|
|
@@ -448,6 +458,12 @@ export class SchemaExtractor {
|
|
|
448
458
|
const classJsdoc = this.getJSDocComment(node as any, sourceFile);
|
|
449
459
|
const isStatefulClass = /@stateful\b/i.test(classJsdoc);
|
|
450
460
|
|
|
461
|
+
// Extract @auth tag for MCP OAuth requirement
|
|
462
|
+
const authMatch = classJsdoc.match(/@auth(?:\s+(\S+))?/i);
|
|
463
|
+
if (authMatch) {
|
|
464
|
+
auth = authMatch[1]?.trim() || 'required';
|
|
465
|
+
}
|
|
466
|
+
|
|
451
467
|
// Extract notification subscriptions from @notify-on tag
|
|
452
468
|
notificationSubscriptions = this.extractNotifyOn(classJsdoc);
|
|
453
469
|
|
|
@@ -495,6 +511,11 @@ export class SchemaExtractor {
|
|
|
495
511
|
result.notificationSubscriptions = notificationSubscriptions;
|
|
496
512
|
}
|
|
497
513
|
|
|
514
|
+
// Include auth requirement if detected
|
|
515
|
+
if (auth) {
|
|
516
|
+
result.auth = auth;
|
|
517
|
+
}
|
|
518
|
+
|
|
498
519
|
return result;
|
|
499
520
|
}
|
|
500
521
|
|
|
@@ -1220,7 +1241,7 @@ export class SchemaExtractor {
|
|
|
1220
1241
|
private extractDescription(jsdocContent: string): string {
|
|
1221
1242
|
// Split by @tags that appear at start of a JSDoc line (after optional * prefix)
|
|
1222
1243
|
// This avoids matching @tag references inline in description text
|
|
1223
|
-
const beforeTags = jsdocContent.split(/(?:^|\n)\s*\*?\s*@(?:param|example|returns?|throws?|see|since|deprecated|version|author|license|ui|icon|format|stateful|autorun|async|webhook|cron|scheduled|locked|fallback|logged|circuitBreaker|cached|timeout|retryable|throttled|debounced|queued|validate|use|Template|Static|mcp|photon|cli|tags|dependencies|csp|visibility)\b/)[0];
|
|
1244
|
+
const beforeTags = jsdocContent.split(/(?:^|\n)\s*\*?\s*@(?:param|example|returns?|throws?|see|since|deprecated|version|author|license|ui|icon|format|stateful|autorun|async|webhook|cron|scheduled|locked|fallback|logged|circuitBreaker|cached|timeout|retryable|throttled|debounced|queued|validate|use|Template|Static|mcp|photon|cli|tags|dependencies|csp|visibility|auth)\b/)[0];
|
|
1224
1245
|
|
|
1225
1246
|
// Remove leading * from each line and trim
|
|
1226
1247
|
const lines = beforeTags
|
|
@@ -1329,6 +1350,7 @@ export class SchemaExtractor {
|
|
|
1329
1350
|
.replace(/\{@pattern\s+[^}]+\}/g, '')
|
|
1330
1351
|
.replace(/\{@format\s+[^}]+\}/g, '')
|
|
1331
1352
|
.replace(/\{@choice\s+[^}]+\}/g, '')
|
|
1353
|
+
.replace(/\{@choice-from\s+[^}]+\}/g, '')
|
|
1332
1354
|
.replace(/\{@field\s+[^}]+\}/g, '')
|
|
1333
1355
|
.replace(/\{@default\s+[^}]+\}/g, '')
|
|
1334
1356
|
.replace(/\{@unique(?:Items)?\s*\}/g, '')
|
|
@@ -1404,6 +1426,12 @@ export class SchemaExtractor {
|
|
|
1404
1426
|
paramConstraints.enum = choices;
|
|
1405
1427
|
}
|
|
1406
1428
|
|
|
1429
|
+
// Extract {@choice-from toolName} or {@choice-from toolName.field}
|
|
1430
|
+
const choiceFromMatch = description.match(/\{@choice-from\s+([^}]+)\}/);
|
|
1431
|
+
if (choiceFromMatch) {
|
|
1432
|
+
paramConstraints.choiceFrom = choiceFromMatch[1].trim();
|
|
1433
|
+
}
|
|
1434
|
+
|
|
1407
1435
|
// Extract {@field type} - hints for UI form rendering
|
|
1408
1436
|
const fieldMatch = description.match(/\{@field\s+([a-z]+)\}/);
|
|
1409
1437
|
if (fieldMatch) {
|
|
@@ -1526,7 +1554,7 @@ export class SchemaExtractor {
|
|
|
1526
1554
|
}
|
|
1527
1555
|
|
|
1528
1556
|
// Validate no unknown {@...} tags (typos in constraint names)
|
|
1529
|
-
const allKnownTags = ['min', 'max', 'pattern', 'format', 'choice', 'field', 'default', 'unique', 'uniqueItems',
|
|
1557
|
+
const allKnownTags = ['min', 'max', 'pattern', 'format', 'choice', 'choice-from', 'field', 'default', 'unique', 'uniqueItems',
|
|
1530
1558
|
'example', 'multipleOf', 'deprecated', 'readOnly', 'writeOnly', 'label', 'placeholder',
|
|
1531
1559
|
'hint', 'hidden', 'accept', 'minItems', 'maxItems'];
|
|
1532
1560
|
const unknownTagRegex = /\{@([\w-]+)\s*(?:\s+[^}]*)?\}/g;
|
|
@@ -1715,6 +1743,10 @@ export class SchemaExtractor {
|
|
|
1715
1743
|
s.enum = constraints.enum;
|
|
1716
1744
|
}
|
|
1717
1745
|
}
|
|
1746
|
+
// Apply dynamic choice provider (x-choiceFrom extension)
|
|
1747
|
+
if (constraints.choiceFrom !== undefined) {
|
|
1748
|
+
s['x-choiceFrom'] = constraints.choiceFrom;
|
|
1749
|
+
}
|
|
1718
1750
|
// Apply field hint for UI rendering
|
|
1719
1751
|
if (constraints.field !== undefined) {
|
|
1720
1752
|
s.field = constraints.field;
|
|
@@ -2652,7 +2684,7 @@ export class SchemaExtractor {
|
|
|
2652
2684
|
};
|
|
2653
2685
|
}
|
|
2654
2686
|
|
|
2655
|
-
// Check if matches an @photon declaration
|
|
2687
|
+
// Check if matches an @photon declaration (exact match)
|
|
2656
2688
|
if (photonMap.has(param.name)) {
|
|
2657
2689
|
return {
|
|
2658
2690
|
param,
|
|
@@ -2661,6 +2693,25 @@ export class SchemaExtractor {
|
|
|
2661
2693
|
};
|
|
2662
2694
|
}
|
|
2663
2695
|
|
|
2696
|
+
// Instance-aware DI: if paramName ends with a photon dep name (case-insensitive),
|
|
2697
|
+
// the prefix becomes the instance name.
|
|
2698
|
+
// e.g., personalWhatsapp + @photon whatsapp → instance "personal" of whatsapp
|
|
2699
|
+
// workWhatsapp + @photon whatsapp → instance "work" of whatsapp
|
|
2700
|
+
for (const [depName, dep] of photonMap) {
|
|
2701
|
+
const lowerParam = param.name.toLowerCase();
|
|
2702
|
+
const lowerDep = depName.toLowerCase();
|
|
2703
|
+
if (lowerParam.endsWith(lowerDep) && lowerParam.length > lowerDep.length) {
|
|
2704
|
+
const prefix = param.name.slice(0, param.name.length - depName.length);
|
|
2705
|
+
// Ensure the prefix is a valid instance name (lowercase the first char)
|
|
2706
|
+
const instanceName = prefix.charAt(0).toLowerCase() + prefix.slice(1);
|
|
2707
|
+
return {
|
|
2708
|
+
param,
|
|
2709
|
+
injectionType: 'photon' as const,
|
|
2710
|
+
photonDependency: { ...dep, instanceName: instanceName || undefined },
|
|
2711
|
+
};
|
|
2712
|
+
}
|
|
2713
|
+
}
|
|
2714
|
+
|
|
2664
2715
|
// Non-primitive with default on @stateful class → persisted state
|
|
2665
2716
|
if (isStateful && param.hasDefault) {
|
|
2666
2717
|
return {
|
|
@@ -2826,7 +2877,15 @@ export class SchemaExtractor {
|
|
|
2826
2877
|
// Link UI asset to this method
|
|
2827
2878
|
const asset = uiAssets.find(a => a.id === uiId);
|
|
2828
2879
|
if (asset) {
|
|
2829
|
-
|
|
2880
|
+
// First method wins as primary (used for app detection)
|
|
2881
|
+
if (!asset.linkedTool) {
|
|
2882
|
+
asset.linkedTool = methodName;
|
|
2883
|
+
}
|
|
2884
|
+
// Track all methods that reference this UI
|
|
2885
|
+
if (!asset.linkedTools) asset.linkedTools = [];
|
|
2886
|
+
if (!asset.linkedTools.includes(methodName)) {
|
|
2887
|
+
asset.linkedTools.push(methodName);
|
|
2888
|
+
}
|
|
2830
2889
|
}
|
|
2831
2890
|
}
|
|
2832
2891
|
}
|
|
@@ -2882,7 +2941,7 @@ export class SchemaExtractor {
|
|
|
2882
2941
|
/**
|
|
2883
2942
|
* Capability types that can be auto-detected from source code
|
|
2884
2943
|
*/
|
|
2885
|
-
export type PhotonCapability = 'emit' | 'memory' | 'call' | 'mcp' | 'lock' | 'instanceMeta' | 'allInstances';
|
|
2944
|
+
export type PhotonCapability = 'emit' | 'memory' | 'call' | 'mcp' | 'lock' | 'instanceMeta' | 'allInstances' | 'caller';
|
|
2886
2945
|
|
|
2887
2946
|
/**
|
|
2888
2947
|
* Detect capabilities used by a Photon from its source code.
|
|
@@ -2896,11 +2955,13 @@ export type PhotonCapability = 'emit' | 'memory' | 'call' | 'mcp' | 'lock' | 'in
|
|
|
2896
2955
|
export function detectCapabilities(source: string): Set<PhotonCapability> {
|
|
2897
2956
|
const caps = new Set<PhotonCapability>();
|
|
2898
2957
|
if (/this\.emit\s*\(/.test(source)) caps.add('emit');
|
|
2958
|
+
if (/this\.render\s*\(/.test(source)) caps.add('emit'); // render() needs emit injection
|
|
2899
2959
|
if (/this\.memory\b/.test(source)) caps.add('memory');
|
|
2900
2960
|
if (/this\.call\s*\(/.test(source)) caps.add('call');
|
|
2901
2961
|
if (/this\.mcp\s*\(/.test(source)) caps.add('mcp');
|
|
2902
2962
|
if (/this\.withLock\s*\(/.test(source)) caps.add('lock');
|
|
2903
2963
|
if (/this\.instanceMeta\b/.test(source)) caps.add('instanceMeta');
|
|
2904
2964
|
if (/this\.allInstances\s*\(/.test(source)) caps.add('allInstances');
|
|
2965
|
+
if (/this\.caller\b/.test(source)) caps.add('caller');
|
|
2905
2966
|
return caps;
|
|
2906
2967
|
}
|
package/src/types.ts
CHANGED
|
@@ -432,8 +432,10 @@ export interface UIAsset {
|
|
|
432
432
|
resolvedPath?: string;
|
|
433
433
|
/** MIME type (detected from extension) */
|
|
434
434
|
mimeType?: string;
|
|
435
|
-
/**
|
|
435
|
+
/** Primary tool this UI is linked to (first method with @ui annotation — used for app detection) */
|
|
436
436
|
linkedTool?: string;
|
|
437
|
+
/** All tools that reference this UI asset (multiple methods can share one template) */
|
|
438
|
+
linkedTools?: string[];
|
|
437
439
|
/** MCP resource URI (set by loader, e.g., 'ui://photon-name/main-ui') */
|
|
438
440
|
uri?: string;
|
|
439
441
|
}
|