assign-gingerly 0.0.74 → 0.0.76

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/DX/installForwarding.js +1 -1
  2. package/DX/installForwarding.ts +1 -1
  3. package/README.md +2 -0
  4. package/assignFrom.js +18 -17
  5. package/assignFrom.ts +23 -23
  6. package/assignFromAsync.js +14 -13
  7. package/assignFromAsync.ts +15 -15
  8. package/assignGingerly.js +46 -42
  9. package/assignGingerly.ts +131 -124
  10. package/assignPermissions/PermissionProcessor.js +166 -0
  11. package/assignPermissions/PermissionProcessor.ts +210 -0
  12. package/assignPermissions/isAllowedImportPath.js +2 -6
  13. package/assignPermissions/isAllowedImportPath.ts +3 -5
  14. package/assignPermissions/isAllowedUrl.js +18 -0
  15. package/assignPermissions/isAllowedUrl.ts +15 -0
  16. package/assignTentatively.js +9 -11
  17. package/assignTentatively.ts +11 -13
  18. package/beVigilant.js +1 -1
  19. package/beVigilant.ts +1 -1
  20. package/buildCSSQuery.js +1 -1
  21. package/buildCSSQuery.ts +1 -1
  22. package/eachTime.js +5 -6
  23. package/eachTime.ts +12 -15
  24. package/enhanceAll.js +3 -3
  25. package/enhanceAll.ts +4 -4
  26. package/evaluatePathWithAsyncMethods.js +12 -8
  27. package/evaluatePathWithAsyncMethods.ts +129 -117
  28. package/handlers/addEventListener.js +11 -11
  29. package/handlers/addEventListener.ts +12 -13
  30. package/handlers/lazyLoad.ts +7 -7
  31. package/handlers/lazyLoadSwitch.ts +3 -3
  32. package/handlers/manageTemplateList.js +10 -10
  33. package/handlers/manageTemplateList.ts +12 -12
  34. package/handlers/rangeSelector.ts +3 -3
  35. package/index.js +2 -3
  36. package/index.ts +2 -3
  37. package/inferencer/types/assign-gingerly/types.d.ts +37 -23
  38. package/inferencer/types/el-maker/types.d.ts +33 -0
  39. package/inferencer/types/scratch-box/types.d.ts +3 -0
  40. package/inferencer/types/truth-sourcer/types.d.ts +4 -0
  41. package/package.json +25 -23
  42. package/parseWithAttrs.js +1 -1
  43. package/parseWithAttrs.ts +1 -1
  44. package/processHandlerCommands.js +5 -7
  45. package/processHandlerCommands.ts +12 -15
  46. package/{getValues.js → resolve/getValues.js} +19 -8
  47. package/{getValues.ts → resolve/getValues.ts} +331 -314
  48. package/{resolveIdRef.js → resolve/resolveIdRef.js} +1 -1
  49. package/{resolveIdRef.ts → resolve/resolveIdRef.ts} +1 -1
  50. package/{resolveValues.ts → resolve/resolveValues.ts} +1 -1
  51. package/types/assign-gingerly/types.d.ts +37 -8
  52. package/utils/findClassPrototypeInPath.js +57 -0
  53. package/utils/findClassPrototypeInPath.ts +62 -0
  54. package/utils/isAsyncSpawn.js +20 -0
  55. package/utils/isAsyncSpawn.ts +17 -0
  56. package/assignPermissions/restrictedProps.js +0 -43
  57. package/assignPermissions/restrictedProps.ts +0 -53
  58. package/resolveAndAssignFeatures.js +0 -36
  59. package/resolveAndAssignFeatures.ts +0 -43
  60. /package/{resolveTemplate.js → resolve/resolveTemplate.js} +0 -0
  61. /package/{resolveTemplate.ts → resolve/resolveTemplate.ts} +0 -0
  62. /package/{resolveValues.js → resolve/resolveValues.js} +0 -0
@@ -78,7 +78,7 @@ export function resolveIdVariable(varName, target, pin) {
78
78
  // Fire-and-forget: log correction suggestion
79
79
  const capturedConfig = config;
80
80
  const capturedVarName = varName;
81
- import('./DX/pinCorrector.js').then(module => {
81
+ import('../DX/pinCorrector.js').then(module => {
82
82
  module.logConfigCorrection(target, capturedVarName, capturedConfig);
83
83
  }).catch(() => { });
84
84
  }
@@ -91,7 +91,7 @@ export function resolveIdVariable(
91
91
  // Fire-and-forget: log correction suggestion
92
92
  const capturedConfig = config;
93
93
  const capturedVarName = varName;
94
- import('./DX/pinCorrector.js').then(module => {
94
+ import('../DX/pinCorrector.js').then(module => {
95
95
  module.logConfigCorrection(target, capturedVarName, capturedConfig);
96
96
  }).catch(() => {});
97
97
  }
@@ -8,7 +8,7 @@
8
8
  */
9
9
 
10
10
  import { getValue, getValues } from './getValues.js';
11
- import type { ResolveValuesOptions } from './types/assign-gingerly/types.js';
11
+ import type { ResolveValuesOptions } from '../types/assign-gingerly/types.js';
12
12
 
13
13
  export type { ResolveValuesOptions };
14
14
 
@@ -424,6 +424,11 @@ export interface GetValuesOptions extends IAssignGingerlyOptions {
424
424
  */
425
425
  root?: any;
426
426
 
427
+ /**
428
+ * Optional permission processor for guarding method calls during RHS resolution.
429
+ */
430
+ permissionProcessor?: PermissionProcessor;
431
+
427
432
  /**
428
433
  * Synchronous protocol handlers for resolving protocol-prefixed values.
429
434
  * Each handler receives the key portion and MUST return synchronously.
@@ -449,6 +454,11 @@ export interface ResolveValuesOptions extends IAssignGingerlyOptions {
449
454
  */
450
455
  root?: any;
451
456
 
457
+ /**
458
+ * Optional permission processor for guarding method calls during RHS resolution.
459
+ */
460
+ permissionProcessor?: PermissionProcessor;
461
+
452
462
  /**
453
463
  * Protocol handlers for resolving protocol-prefixed values (e.g., 'globalThis://key').
454
464
  * Each handler receives the key portion and returns the resolved value (sync or async).
@@ -695,7 +705,7 @@ export interface HandlerConfig {
695
705
  * Handlers are invoked when a LHS key ends with ' =>'.
696
706
  */
697
707
  export interface AssignFromHandler {
698
- assign(lhsTarget: any, resolvedParams: Record<string, any>, options: any, permissions?: AssignPermissions): Promise<void> | void;
708
+ assign(lhsTarget: any, resolvedParams: Record<string, any>, options: any, permissionProcessor?: PermissionProcessor): Promise<void> | void;
699
709
  }
700
710
 
701
711
  /**
@@ -873,7 +883,7 @@ export interface LazyLoadInstantiatedContext {
873
883
  export declare class LazyLoadHandler implements AssignFromHandler {
874
884
  config: any;
875
885
  constructor(config: any);
876
- assign(lhsTarget: any, resolvedParams: Record<string, any>, options?: any, permissions?: AssignPermissions): Promise<void>;
886
+ assign(lhsTarget: any, resolvedParams: Record<string, any>, options?: any, permissionProcessor?: PermissionProcessor): Promise<void>;
877
887
  protected onCloneInserted(nodes: Node[], lhsTarget: Element, resolvedParams: Record<string, any>): Promise<void>;
878
888
  }
879
889
 
@@ -931,11 +941,11 @@ export interface AddEventListenerConfig extends AssignDispatchVector {
931
941
  * Phase II+: object form for a restricted property setting.
932
942
  */
933
943
  export interface RestrictedPropSetting {
934
- prop: string;
935
- useMethod?: string; // Phase II: redirect to a safe method
936
- attr?: string; // Phase III: also watch setAttribute for this attr
937
- allowFromSameHost?: boolean; // Phase III
938
- allowCrossDomain?: boolean; // Phase III
944
+ props: string | string[];
945
+ useMethod?: string; // Phase II: redirect to a safe method
946
+ attr?: boolean | string | string[]; // Phase III: also watch setAttribute for these attrs
947
+ allowFromSameDomain?: boolean; // Phase III: allow same-origin URLs
948
+ allowCrossDomain?: boolean; // Phase III: allow any URL
939
949
  }
940
950
 
941
951
  /**
@@ -958,7 +968,8 @@ export interface AssignPermissions {
958
968
  * Restricted property settings.
959
969
  * Phase I: string entries are property names that cannot be assigned.
960
970
  * Phase II: an object with useMethod redirects ordinary assignment to that
961
- * method; command operations remain blocked. Phase III+ adds attr support.
971
+ * method; command operations remain blocked. Phase III+ adds props, attr,
972
+ * allowFromSameDomain, and allowCrossDomain support.
962
973
  *
963
974
  * NOTE: This is a property-assignment guard only. Method calls (setAttribute, etc.)
964
975
  * are not blocked — see Phase III+. Event listeners can still be registered, but
@@ -972,3 +983,21 @@ export interface AssignPermissions {
972
983
  /** Restricted method settings (Phase IV+) */
973
984
  restrictedMethodSettings?: Array<string | RestrictedMethodConfig>;
974
985
  }
986
+
987
+ //#region Permissions
988
+ export interface RestrictedPropSettingsMap {
989
+ props: Map<string, RestrictedPropSetting | undefined>;
990
+ attrs: Map<string, RestrictedPropSetting | undefined>;
991
+ }
992
+
993
+ export declare class PermissionProcessor {
994
+ constructor(permissions: AssignPermissions | undefined);
995
+ get crossDomainImports(): boolean;
996
+ get hasProps(): boolean;
997
+ get hasAttrs(): boolean;
998
+ checkRestrictedProp(key: string): boolean;
999
+ checkRestrictedMethod(methodName: string): boolean;
1000
+ redirectRestrictedProp(target: any, key: string, value: any): boolean;
1001
+ checkRestrictedAttributeCall(methodName: string, args: any[]): { blocked: boolean; attrName?: string };
1002
+ }
1003
+ //#endregion
@@ -0,0 +1,57 @@
1
+ import { isAllowedImportPath } from '../assignPermissions/isAllowedImportPath.js';
2
+ /**
3
+ * Thrown when a dynamic import path is not covered by the allowed-import policy.
4
+ */
5
+ export class ImportNotAllowedError extends Error {
6
+ path;
7
+ constructor(path) {
8
+ super(`Import path "${path}" is not allowed.`);
9
+ this.path = path;
10
+ this.name = 'ImportNotAllowedError';
11
+ console.error(`ImportNotAllowedError: ${path}`);
12
+ }
13
+ }
14
+ /**
15
+ * Thrown when a module does not export a class that satisfies the required criteria.
16
+ */
17
+ export class NoMatchingExportError extends Error {
18
+ path;
19
+ constructor(path) {
20
+ super(`Module "${path}" does not export a matching class with a prototype.`);
21
+ this.path = path;
22
+ this.name = 'NoMatchingExportError';
23
+ console.error(`NoMatchingExportError: ${path}`);
24
+ }
25
+ }
26
+ /**
27
+ * Base check: value must be a function with a prototype (i.e., a class constructor).
28
+ */
29
+ function isClassWithPrototype(value) {
30
+ return typeof value === 'function' && value.prototype !== undefined;
31
+ }
32
+ /**
33
+ * Dynamically import a module at the given path, validate the path against the
34
+ * allowed-import policy, and return the first exported class whose prototype passes
35
+ * the optional criteria check.
36
+ *
37
+ * The default export is checked first. If it does not satisfy the checks, all named
38
+ * exports are scanned. If no matching class is found, a `NoMatchingExportError` is thrown.
39
+ */
40
+ export async function findClassPrototypeInPath(path, criteria) {
41
+ if (!isAllowedImportPath(path)) {
42
+ throw new ImportNotAllowedError(path);
43
+ }
44
+ const module = await import(path);
45
+ const candidates = [
46
+ module.default,
47
+ ...Object.values(module).filter((exported) => exported !== module.default),
48
+ ];
49
+ for (const exported of candidates) {
50
+ if (!isClassWithPrototype(exported))
51
+ continue;
52
+ if (criteria && !criteria(exported))
53
+ continue;
54
+ return exported;
55
+ }
56
+ throw new NoMatchingExportError(path);
57
+ }
@@ -0,0 +1,62 @@
1
+ import { isAllowedImportPath } from '../assignPermissions/isAllowedImportPath.js';
2
+
3
+ /**
4
+ * Thrown when a dynamic import path is not covered by the allowed-import policy.
5
+ */
6
+ export class ImportNotAllowedError extends Error {
7
+ constructor(public readonly path: string) {
8
+ super(`Import path "${path}" is not allowed.`);
9
+ this.name = 'ImportNotAllowedError';
10
+ console.error(`ImportNotAllowedError: ${path}`);
11
+ }
12
+ }
13
+
14
+ /**
15
+ * Thrown when a module does not export a class that satisfies the required criteria.
16
+ */
17
+ export class NoMatchingExportError extends Error {
18
+ constructor(public readonly path: string) {
19
+ super(`Module "${path}" does not export a matching class with a prototype.`);
20
+ this.name = 'NoMatchingExportError';
21
+ console.error(`NoMatchingExportError: ${path}`);
22
+ }
23
+ }
24
+
25
+ /**
26
+ * Base check: value must be a function with a prototype (i.e., a class constructor).
27
+ */
28
+ function isClassWithPrototype(value: any): boolean {
29
+ return typeof value === 'function' && value.prototype !== undefined;
30
+ }
31
+
32
+ /**
33
+ * Dynamically import a module at the given path, validate the path against the
34
+ * allowed-import policy, and return the first exported class whose prototype passes
35
+ * the optional criteria check.
36
+ *
37
+ * The default export is checked first. If it does not satisfy the checks, all named
38
+ * exports are scanned. If no matching class is found, a `NoMatchingExportError` is thrown.
39
+ */
40
+ export async function findClassPrototypeInPath<T = any>(
41
+ path: string,
42
+ criteria?: (proto: any) => boolean
43
+ ): Promise<{ new(): T }> {
44
+ if (!isAllowedImportPath(path)) {
45
+ throw new ImportNotAllowedError(path);
46
+ }
47
+
48
+ const module = await import(path);
49
+
50
+ const candidates = [
51
+ module.default,
52
+ ...Object.values(module).filter((exported: any) => exported !== module.default),
53
+ ];
54
+
55
+ for (const exported of candidates) {
56
+ if (!isClassWithPrototype(exported)) continue;
57
+ if (criteria && !criteria(exported)) continue;
58
+ return exported as { new(): T };
59
+ }
60
+
61
+ throw new NoMatchingExportError(path);
62
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Determines if a function is an async spawner (returns a Promise<Constructor>)
3
+ * rather than a synchronous constructor.
4
+ *
5
+ * Heuristic:
6
+ * - AsyncFunction (async () => ...) → async spawner
7
+ * - Arrow function (no .prototype) → async spawner (assumed to return Promise<Constructor>)
8
+ * - Class or function declaration (has .prototype) → synchronous constructor
9
+ */
10
+ export function isAsyncSpawn(fn) {
11
+ if (typeof fn !== 'function')
12
+ return false;
13
+ // Explicit async function
14
+ if (fn.constructor.name === 'AsyncFunction')
15
+ return true;
16
+ // Arrow function or non-constructor function (no .prototype)
17
+ if (fn.prototype === undefined)
18
+ return true;
19
+ return false;
20
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Determines if a function is an async spawner (returns a Promise<Constructor>)
3
+ * rather than a synchronous constructor.
4
+ *
5
+ * Heuristic:
6
+ * - AsyncFunction (async () => ...) → async spawner
7
+ * - Arrow function (no .prototype) → async spawner (assumed to return Promise<Constructor>)
8
+ * - Class or function declaration (has .prototype) → synchronous constructor
9
+ */
10
+ export function isAsyncSpawn(fn: any): boolean {
11
+ if (typeof fn !== 'function') return false;
12
+ // Explicit async function
13
+ if (fn.constructor.name === 'AsyncFunction') return true;
14
+ // Arrow function or non-constructor function (no .prototype)
15
+ if (fn.prototype === undefined) return true;
16
+ return false;
17
+ }
@@ -1,43 +0,0 @@
1
- const warnedOnce = new Set();
2
- function warnRestricted(key) {
3
- if (!warnedOnce.has(key)) {
4
- warnedOnce.add(key);
5
- console.warn(`assignGingerly: property '${key}' is in restrictedPropSettings — assignment skipped.`);
6
- }
7
- }
8
- export function buildRestrictedPropSet(permissions) {
9
- const settings = permissions?.restrictedPropSettings;
10
- if (!settings || settings.length === 0)
11
- return undefined;
12
- const restrictedPropSet = new Map();
13
- for (const setting of settings) {
14
- const prop = typeof setting === 'string' ? setting : setting.prop;
15
- if (restrictedPropSet.has(prop)) {
16
- throw new Error(`assignGingerly: duplicate restrictedPropSettings entry for '${prop}'.`);
17
- }
18
- restrictedPropSet.set(prop, typeof setting === 'string' ? undefined : setting);
19
- }
20
- return restrictedPropSet;
21
- }
22
- export function checkRestrictedProp(restrictedPropSet, key) {
23
- if (!restrictedPropSet || !restrictedPropSet.has(key))
24
- return false;
25
- warnRestricted(key);
26
- return true;
27
- }
28
- export function redirectRestrictedProp(restrictedPropSet, target, key, value) {
29
- if (!restrictedPropSet || !restrictedPropSet.has(key))
30
- return false;
31
- const setting = restrictedPropSet.get(key);
32
- if (!setting?.useMethod) {
33
- warnRestricted(key);
34
- return true;
35
- }
36
- const method = target?.[setting.useMethod];
37
- if (typeof method !== 'function') {
38
- warnRestricted(key);
39
- return true;
40
- }
41
- method.call(target, value);
42
- return true;
43
- }
@@ -1,53 +0,0 @@
1
- import type { AssignPermissions, RestrictedPropSetting } from '../types/assign-gingerly/types.js';
2
-
3
- export type RestrictedPropSettingsMap = Map<string, RestrictedPropSetting | undefined>;
4
-
5
- const warnedOnce = new Set<string>();
6
-
7
- function warnRestricted(key: string): void {
8
- if (!warnedOnce.has(key)) {
9
- warnedOnce.add(key);
10
- console.warn(`assignGingerly: property '${key}' is in restrictedPropSettings — assignment skipped.`);
11
- }
12
- }
13
-
14
- export function buildRestrictedPropSet(permissions: AssignPermissions | undefined): RestrictedPropSettingsMap | undefined {
15
- const settings = permissions?.restrictedPropSettings;
16
- if (!settings || settings.length === 0) return undefined;
17
- const restrictedPropSet: RestrictedPropSettingsMap = new Map();
18
- for (const setting of settings) {
19
- const prop = typeof setting === 'string' ? setting : setting.prop;
20
- if (restrictedPropSet.has(prop)) {
21
- throw new Error(`assignGingerly: duplicate restrictedPropSettings entry for '${prop}'.`);
22
- }
23
- restrictedPropSet.set(prop, typeof setting === 'string' ? undefined : setting);
24
- }
25
- return restrictedPropSet;
26
- }
27
-
28
- export function checkRestrictedProp(restrictedPropSet: RestrictedPropSettingsMap | undefined, key: string): boolean {
29
- if (!restrictedPropSet || !restrictedPropSet.has(key)) return false;
30
- warnRestricted(key);
31
- return true;
32
- }
33
-
34
- export function redirectRestrictedProp(
35
- restrictedPropSet: RestrictedPropSettingsMap | undefined,
36
- target: any,
37
- key: string,
38
- value: any
39
- ): boolean {
40
- if (!restrictedPropSet || !restrictedPropSet.has(key)) return false;
41
- const setting = restrictedPropSet.get(key);
42
- if (!setting?.useMethod) {
43
- warnRestricted(key);
44
- return true;
45
- }
46
- const method = target?.[setting.useMethod];
47
- if (typeof method !== 'function') {
48
- warnRestricted(key);
49
- return true;
50
- }
51
- method.call(target, value);
52
- return true;
53
- }
@@ -1,36 +0,0 @@
1
- /**
2
- * resolveAndAssignFeatures - Thin wrapper around assignFeatures for backward compatibility.
3
- *
4
- * Resolves all configured spawns (including async fallback spawns and string import paths)
5
- * and installs feature getters on the class prototype, then delegates to the registry's
6
- * assignFeatures implementation.
7
- *
8
- * This is kept as a convenience entry point for callers that already await this function.
9
- * The actual resolution logic lives in assignFeatures.
10
- *
11
- * @example
12
- * import { resolveAndAssignFeatures } from 'assign-gingerly/resolveAndAssignFeatures.js';
13
- *
14
- * await resolveAndAssignFeatures(MyElement, {
15
- * roundabout: {
16
- * customData: {...},
17
- * withAttrs: {...},
18
- * callbackForwarding: ['connectedCallback']
19
- * },
20
- * faceUp: {
21
- * callbackForwarding: ['connectedCallback', 'disconnectedCallback']
22
- * }
23
- * });
24
- */
25
- import { assignFeatures } from './assignFeatures.js';
26
- /**
27
- * Resolves all configured spawns and calls assignFeatures on the registry.
28
- *
29
- * @param ElementClass - The custom element class (must have static supportedFeatures)
30
- * @param featuresConfig - Feature configurations (spawn will be resolved from fallbackSpawn if missing)
31
- * @param registry - Optional CustomElementRegistry (defaults to global customElements)
32
- */
33
- export async function resolveAndAssignFeatures(ElementClass, featuresConfig, registry) {
34
- const reg = registry || customElements;
35
- await assignFeatures(ElementClass, featuresConfig, reg.featuresRegistry);
36
- }
@@ -1,43 +0,0 @@
1
- /**
2
- * resolveAndAssignFeatures - Thin wrapper around assignFeatures for backward compatibility.
3
- *
4
- * Resolves all configured spawns (including async fallback spawns and string import paths)
5
- * and installs feature getters on the class prototype, then delegates to the registry's
6
- * assignFeatures implementation.
7
- *
8
- * This is kept as a convenience entry point for callers that already await this function.
9
- * The actual resolution logic lives in assignFeatures.
10
- *
11
- * @example
12
- * import { resolveAndAssignFeatures } from 'assign-gingerly/resolveAndAssignFeatures.js';
13
- *
14
- * await resolveAndAssignFeatures(MyElement, {
15
- * roundabout: {
16
- * customData: {...},
17
- * withAttrs: {...},
18
- * callbackForwarding: ['connectedCallback']
19
- * },
20
- * faceUp: {
21
- * callbackForwarding: ['connectedCallback', 'disconnectedCallback']
22
- * }
23
- * });
24
- */
25
-
26
- import { FeatureConfigsMap } from './types/assign-gingerly/types.js';
27
- import { assignFeatures } from './assignFeatures.js';
28
-
29
- /**
30
- * Resolves all configured spawns and calls assignFeatures on the registry.
31
- *
32
- * @param ElementClass - The custom element class (must have static supportedFeatures)
33
- * @param featuresConfig - Feature configurations (spawn will be resolved from fallbackSpawn if missing)
34
- * @param registry - Optional CustomElementRegistry (defaults to global customElements)
35
- */
36
- export async function resolveAndAssignFeatures(
37
- ElementClass: Function,
38
- featuresConfig: FeatureConfigsMap,
39
- registry?: any
40
- ): Promise<void> {
41
- const reg = registry || customElements;
42
- await assignFeatures(ElementClass, featuresConfig, reg.featuresRegistry);
43
- }
File without changes