@otorp/plugin-utils 1.0.0 → 1.0.1-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.
Files changed (3) hide show
  1. package/README.md +78 -88
  2. package/index.js +116 -150
  3. package/package.json +6 -12
package/README.md CHANGED
@@ -8,9 +8,9 @@
8
8
  <!-- omit in toc -->
9
9
  ## 🎯 Objective
10
10
 
11
- Small, init-time utilities extracted from the Otorp core so plugin authors can register modules without duplicating boilerplate or reimplementing the shared symbol registry.
11
+ Small, init-time utilities extracted from the Otorp monolith so plugin authors can register modules without duplicating boilerplate or reimplementing the shared symbol registry.
12
12
 
13
- This package does not extend prototypes or run Otorp's alias pass — it only wires your plugin into the registry that **Otorp core** consumes during initialization.
13
+ This package does not extend prototypes or run Otorp's alias pass — it only wires your plugin into otorp plugin registry that **Otorp** consumes during initialization.
14
14
 
15
15
  ---
16
16
 
@@ -20,20 +20,42 @@ This package does not extend prototypes or run Otorp's alias pass — it only wi
20
20
  - [🚀 Quickstart](#-quickstart)
21
21
  - [📚 API](#-api)
22
22
  - [`setMod(config)`](#setmodconfig)
23
- - [`newErrorHandler(modName?)`](#newerrorhandlermodname)
24
23
  - [`ref(key, value?)`](#refkey-value)
24
+ - [`newErrorHandler(modName?)`](#newerrorhandlermodname)
25
25
  - [`root`](#root)
26
26
  - [🔌 Plugin registration](#-plugin-registration)
27
27
  - [With `@otorp/plugin-utils` (recommended)](#with-otorpplugin-utils-recommended)
28
28
  - [Manual registration (equivalent)](#manual-registration-equivalent)
29
29
  - [❓ FAQ](#-faq)
30
+ - [Why a separate package?](#why-a-separate-package)
31
+ - [Do I need this package to write a plugin?](#do-i-need-this-package-to-write-a-plugin)
30
32
  - [🤝 Contributing](#-contributing)
31
33
 
32
34
  <!-- /TOC -->
33
35
 
34
36
  ## 📦 Installation
35
37
 
36
- > **Load order requirement:** this package — and your plugin script — must be registered **before** Otorp core initializes. Otorp deletes itself after its init pass; any plugin registered after that point will be silently ignored.
38
+ Load **before** Otorp core, alongside your plugin script:
39
+
40
+ ```html
41
+ <script type="module">
42
+ import { setMod, newErrorHandler } from './path/to/@otorp/plugin-utils/src/main.js';
43
+
44
+ const key = setMod({
45
+ source: 'myPlugin',
46
+ endpoints: { HTMLElement: ['myPlugin.customMethod'] },
47
+ modules(methodName) {
48
+ return function (...args) {
49
+ // `this` is the DOM instance
50
+ };
51
+ }
52
+ });
53
+
54
+ // Optional: use error helpers attached to the returned symbol key
55
+ key.warn('registered');
56
+ </script>
57
+ <script src="path/to/otorp.js"></script>
58
+ ```
37
59
 
38
60
  **npm**
39
61
 
@@ -42,51 +64,42 @@ npm install @otorp/plugin-utils
42
64
  ```
43
65
 
44
66
  ```javascript
45
- import setMod from '@otorp/plugin-utils';
46
-
67
+ import { setMod } from '@otorp/plugin-utils';
47
68
  ```
48
69
 
70
+ > **Requirement:** [Otorp core](https://gitlab.com/tdj.dev/otorp) must be loaded after your plugin. See the core README for configuration (`otorpConfig`), alias behaviour, and the init pass.
71
+
49
72
  ---
50
73
 
51
74
  ## 🚀 Quickstart
52
75
 
76
+ Minimal plugin using `setMod`:
77
+
53
78
  ```javascript
54
- import setMod from '@otorp/plugin-utils';
79
+ import { setMod } from '@otorp/plugin-utils';
55
80
 
56
81
  setMod({
57
82
  source: 'myPlugin',
58
83
  endpoints: {
59
84
  "HTMLElement": [
60
85
  'myPlugin.customMethod',
61
- 'myPlugin.meta1.meta2.customMethod'
86
+ 'myPlugin.meta.customMethod'
62
87
  ]
63
88
  },
64
- factory(methodName, ...meta) {
65
- // methodName: last segment of the query string
66
- // meta: everything between source and methodName
89
+ factory(customMethod, meta) {
67
90
  return function (...args) {
68
- // `this` is the DOM instance at call time
91
+ // Installed on HTMLElement.prototype during Otorp init
69
92
  };
70
93
  }
71
94
  });
72
95
  ```
73
96
 
74
- **Query string anatomy:**
75
-
76
- ```
77
- "myPlugin.meta1.meta2.methodName"
78
- │ │ │ └── methodName (passed as first arg to factory)
79
- │ └─────┘────────── meta (spread as remaining args to factory)
80
- └────────────────────────── source (identifies your plugin)
81
- ```
82
-
83
97
  What `setMod` does in one call:
84
98
 
85
- 1. Validates the `factory` argument.
86
- 2. Tracks registration count to warn on duplicate source names.
87
- 3. Registers `{ endpoints, aliases, key, count }` in the shared modules Map under `source`.
88
- 4. Creates a unique symbol-like key object with `.throw`, `.warn`, and `.error` helpers attached.
89
- 5. Assigns the factory to `root` under the returned key so Otorp core can invoke it once per resolved endpoint during init.
99
+ 1. Validates the `modules` factory.
100
+ 2. Registers `{ endpoints, aliases, key }` in the modules **Map** under `source`.
101
+ 3. Creates a unique symbol-like key object with `.throw`, `.warn`, and `.error` helpers.
102
+ 4. Assigns the factory to `window` under the returned key so Otorp core can invoke it once per resolved endpoint.
90
103
 
91
104
  ---
92
105
 
@@ -94,56 +107,39 @@ What `setMod` does in one call:
94
107
 
95
108
  All exports are init-time helpers. There is no runtime API after Otorp finishes its pass.
96
109
 
97
- ### `setMod(config)` (Default Export)
110
+ ### `setMod(config)`
98
111
 
99
112
  Register a plugin module for Otorp's initialization pass.
100
113
 
101
- | Property | Type | Required | Description |
102
- | --- | --- | --- | --- |
103
- | `source` | `string` | ✓ | Unique module identifier — used as the Map key and as the first segment of endpoint query strings. |
104
- | `endpoints` | `Record<string, string[]>` | | Maps a target prototype name to an array of endpoint query strings (e.g. `{ HTMLElement: ['myPlugin.customMethod'] }`). |
105
- | `factory` | `Function` | | Called by Otorp once per resolved endpoint. Receives `(methodName, ...meta)` and must return the function to install on the prototype. |
106
- | `aliases` | `Record<string, string> \| null` | — | Optional alias map. Defaults to `null`. |
114
+ | Property | Type | Description |
115
+ | :--- | :--- | :--- |
116
+ | `source` | `string` | Unique module id — used as the Map key. |
117
+ | `endpoints` | `Record<string, string[]>` | Target prototype name endpoint query strings. |
118
+ | `modules` | `Function` | Factory called per endpoint; must return the function to install on the prototype. |
119
+ | `aliases` | `Record<string, string> \| null` | Optional alias map. Default `null`. |
107
120
 
108
- **Returns:** A unique symbol-like key object (`Object(Symbol(source))`) with `.throw`, `.warn`, and `.error` error handlers attached.
121
+ **Returns:** a Symbol-like object with attached `.throw`, `.warn`, and `.error` handlers.
109
122
 
110
- ---
123
+ ### `ref(key, value?)`
124
+
125
+ Read or write an entry in Otorp namespace.
126
+
127
+ - Pass a **truthy** `value` to assign and return it.
128
+ - Omit `value` (or pass a falsy one) to read only.
111
129
 
112
130
  ### `newErrorHandler(modName?)`
113
131
 
114
- Creates a namespaced error handler. The namespace appears in `error.name` so stack traces remain readable.
132
+ Creates a namespaced error handler. The namespace is written to `error.name`.
115
133
 
116
134
  | Callable | Behaviour |
117
135
  | :--- | :--- |
118
- | `handler(msg, opts?)` | Throws an `Error`. Accepts optional `{ type, cause }`. |
119
- | `handler.log(msg, opts?)` | `console.error` with the same formatting. |
120
- | `handler.warn(msg, opts?)` | `console.warn` with the same formatting. |
121
-
122
- ```javascript
123
- import { newErrorHandler } from '@otorp/plugin-utils';
124
-
125
- const err = newErrorHandler('myPlugin');
126
- err('Missing endpoint'); // throws exeption "[myPlugin] Missing endpoint"
127
- err.log('Deprecated alias used'); // error log "[myPlugin] Deprecated alias used"
128
- err.warn('Init failed', { cause }); // warning log "[myPlugin - <cause.name>] Init failed"
129
- ```
130
-
131
- ---
132
-
133
- ### `ref(key, value?)`
134
-
135
- Read or write an entry in the Otorp namespace (`Symbol.for('otorp:' + key)` on `root`). Designed for registries, configuration objects, and shared tooling.
136
-
137
- * Pass a **truthy** `value` to assign and return it.
138
- * Omit `value` (or pass a falsy one) to read the current value.
139
-
140
- > Falsy values cannot be stored through this helper.
141
-
142
- ---
136
+ | `handler(msg, options?)` | Throws an `Error` (optional `cause` / `type`). |
137
+ | `handler.warn(...)` | `console.warn` with the same formatting. |
138
+ | `handler.log(...)` | `console.error` with the same formatting. |
143
139
 
144
140
  ### `root`
145
141
 
146
- Minifier-friendly alias for `window`. Allows bundlers to mangle the identifier across the codebase, reducing bundle size without impacting runtime behavior.
142
+ Shorthand for `window` Minifier-friendly alias used internally and exposed for advanced plugin setups.
147
143
 
148
144
  ---
149
145
 
@@ -152,46 +148,42 @@ Minifier-friendly alias for `window`. Allows bundlers to mangle the identifier a
152
148
  ### With `@otorp/plugin-utils` (recommended)
153
149
 
154
150
  ```javascript
155
- import setMod from '@otorp/plugin-utils';
151
+ import { setMod } from '@otorp/plugin-utils';
156
152
 
157
- const key = setMod({
153
+ setMod({
158
154
  source: 'myPlugin',
159
- endpoints: { "HTMLElement": ['myPlugin.customMethod'] },
160
- factory(methodName, ...meta) {
155
+ endpoints: { HTMLElement: ['myPlugin.customMethod'] },
156
+ modules(methodName) {
161
157
  return function (...args) { /* … */ };
162
158
  }
163
159
  });
164
-
165
- key.warn('registered'); // "[myPlugin] registered"
166
-
167
160
  ```
168
161
 
169
162
  ### Manual registration (equivalent)
170
163
 
171
- If you prefer not to depend on this package, the same contract applies. Core expects a **Map**, tracks registration instances via `count`, and resolves factories from `window` object:
164
+ If you prefer not to use this package, the same contract applies core expects a **Map**, not a plain object:
172
165
 
173
166
  ```javascript
174
- const source = 'myPlugin';
175
- const pluginKey = Object(Symbol(source));
176
- const modulesMap = (window[Symbol.for('otorp:modules')] ??= new Map());
167
+ const pluginKey = Object(Symbol('myPlugin'));
177
168
 
178
- // Core tracks duplicates, so you must supply the count.
179
- const count = (modulesMap.get(source)?.count || 0) + 1;
169
+ Object.defineProperties(pluginKey, {
170
+ throw: { value: myErrHandler, writable: false, configurable: false },
171
+ warn: { value: myErrHandler.warn, writable: false, configurable: false },
172
+ error: { value: myErrHandler.log, writable: false, configurable: false }
173
+ });
180
174
 
181
- modulesMap.set(source, {
182
- endpoints: { "HTMLElement": ['myPlugin.customMethod'] },
175
+ (window[Symbol.for('otorp:modules')] ??= new Map()).set('myPlugin', {
176
+ endpoints: { HTMLElement: ['myPlugin.customMethod'] },
183
177
  aliases: null,
184
- key: pluginKey,
185
- count
178
+ key: pluginKey
186
179
  });
187
180
 
188
- // The factory must be exposed on the global object using the exact symbol object.
189
- window[pluginKey] = function factory(methodName, ...meta) {
181
+ window[pluginKey] = function factory (methodName) {
190
182
  return function (...args) { /* … */ };
191
183
  };
192
184
  ```
193
185
 
194
- `setMod` exists so you do not maintain that boilerplate and the error handler logic in every plugin repo.
186
+ `setMod` exists so you do not maintain that boilerplate in every plugin repo.
195
187
 
196
188
  ---
197
189
 
@@ -199,18 +191,16 @@ window[pluginKey] = function factory(methodName, ...meta) {
199
191
 
200
192
  ### Why a separate package?
201
193
 
202
- Otorp originally bundled core, helpers, and plugin utilities together. Splitting `@otorp/plugin-utils` keeps the core lean and gives plugin authors a stable, shared registration layer without copy-pasting registry code.
194
+ Otorp originally bundled core, helpers, and plugin utilities in one repo. Splitting `@otorp/plugin-utils` keeps the core lean and gives plugin authors a stable, shared registration layer without copy-pasting registry code.
203
195
 
204
196
  ### Do I need this package to write a plugin?
205
197
 
206
- No. Any script that writes the correct shape into `otorp:modules` and `root[key]` before Otorp initializes will work. This package is the supported shortcut.
198
+ No. Any script that writes the correct shape into `otorp:modules` and `root[key]` before Otorp loads will work. This package is the supported shortcut.
207
199
 
208
200
  ---
209
201
 
210
202
  ## 🤝 Contributing
211
203
 
212
- Contributions, bug reports, and feature requests are welcome.
204
+ Contributions, bug reports, and feature requests are welcome! Please read [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
213
205
 
214
- * **Homepage & Wiki:** [gitlab.com/otorp/plugin-utils/-/wikis/home](https://gitlab.com/otorp/plugin-utils/-/wikis/home)
215
- * **Issue Tracker:** [gitlab.com/otorp/plugin-utils/-/issues](https://gitlab.com/otorp/plugin-utils/-/issues)
216
- * **Core Otorp Docs:** [gitlab.com/tdj.dev/otorp](https://gitlab.com/tdj.dev/otorp)
206
+ **Core Otorp docs:** [gitlab.com/tdj.dev/otorp](https://gitlab.com/tdj.dev/otorp)
package/index.js CHANGED
@@ -1,173 +1,139 @@
1
- // ─── Public exports ───────────────────────────────────────────────────────────
2
-
3
-
4
- /** Register an Otorp plugin module for the initialization pass.
5
- *
6
- * Stores endpoint and aliases metadata in the shared Otorp modules Map,
7
- * attaches module-scoped error helpers to the returned key, and installs
8
- * the factory on `root` so Otorp core can resolve it during init.
9
- *
10
- * > **Load order:** must be called before Otorp core initializes.
11
- * > Otorp deletes itself after its init pass — late registrations are ignored.
12
- *
13
- * @param {object} config - Plugin registration payload.
14
- * @param {string} config.source - Unique module identifier. Used as the Map
15
- * key and as the first segment of endpoint query strings.
16
- * @param {Record<string, string[]>} config.endpoints - Maps a target prototype
17
- * name to an array of endpoint query strings.
18
- * Format: `"source.meta1.meta2.methodName"` — everything between `source`
19
- * and `methodName` is forwarded to `factory` as metadata.
20
- * (e.g. `{ "HTMLElement": ['myPlugin.meta.customMethod'] }`)
21
- * @param {Function} config.factory - Called by Otorp once per resolved
22
- * endpoint. Receives `(methodName, ...meta)` and must return the function
23
- * to install on the prototype, where `this` will be the target instance.
24
- * @param {Record<string, string>|null} [config.aliases] - Optional alias
25
- * map consumed by Otorp's aliaser during the init pass.
26
- * @returns {Object} A unique symbol-like key object (`Object(Symbol(source))`)
27
- * with `.throw`, `.warn`, and `.error` error handlers attached.
28
- * Otorp resolves the factory via `root[key]` — not through `Symbol.for`.
29
- *
30
- * @example
31
- * import setMod from '@otorp/plugin-utils'
32
- * const key = setMod({
33
- * source: 'myPlugin',
34
- * endpoints: { "HTMLElement": ['myPlugin.meta.customMethod'] },
35
- * factory(methodName, ...meta) {
36
- * return function (...args) { /* `this` is the DOM instance *\/ };
37
- * }
38
- * });
39
- *
40
- * key.warn('registered'); // "[myPlugin] registered"
41
- */
42
- export default function ({ source, endpoints, factory, aliases = null }) {
43
- typeof factory !== 'function' && err("factory must be a function")
44
-
45
- // Non-global Symbol wrapped in Object() so it can carry properties (.throw,
46
- // .warn, .error) while remaining usable as a Map/window key.
47
- // Symbol.for is intentionally avoided — each registration must be isolated.
48
- const key = Object(Symbol(source)), modErr = newErrorHandler(source);
49
-
50
- // Attach scoped error helpers directly on the key so plugin authors can use
51
- // them without importing newErrorHandler separately.
52
- Object.defineProperties(key, propsDesc(modErr, modErr.log, modErr.warn));
53
-
54
- // Track registration count so Otorp core can warn on duplicate source names
55
- // (same source registered twice, or two plugins sharing the same identifier).
56
- const count = (get(source)?.count || 0) + 1
57
-
58
- set(source, { endpoints, aliases, key, count });
59
-
60
- // Install factory under the unique key on root. Otorp reads root[key] during
61
- // its init pass — it never uses Symbol.for to look up plugin factories.
62
- return (root[key] = factory), key;
63
- }
64
-
65
- /** Creates a namespaced error handler.
66
- *
67
- * Returns a callable that throws an `Error`. The namespace appears in
68
- * `error.name` so stack traces remain readable.
69
- * `.warn` and `.log` variants emit to the console instead of throwing.
70
- *
71
- * @param {string} [modName] - Namespace prefix written to `error.name`.
72
- * @returns {((msg: string, options?: { type?: string, cause?: any }) => never) & { warn: Function, log: Function }}
73
- *
74
- * @example
75
- * const err = newErrorHandler('myPlugin');
76
- * err('Missing endpoint'); // throws — name: "[myPlugin]"
77
- * err.log('Deprecated alias used'); // error log — name: "[myPlugin]"
78
- * err.warn('Init failed', { cause }); // warns — name: "[myPlugin - <cause.name>]"
79
- */
80
- export function newErrorHandler(modName) {
81
- let [prefix, separator, suffix] = modName ? [`[${modName}`, "-", "]"] : ["", "", ""];
82
-
83
- /** Builds an Error and delegates to the bound `output` sink. */
84
- function genErrHandler(msg, { type: errType, cause } = {}) {
85
- const error = new Error(msg, { cause }), actualType = errType || cause?.name;
86
- error.name = actualType
87
- ? (`${prefix} ${separator} ${actualType + suffix}`).trim()
88
- : prefix + suffix;
89
- // V8-only: removes genErrHandler from the stack trace.
90
- // Falls back to a no-op on other engines — full trace is kept.
91
- (Error.captureStackTrace || (x => x))(error, genErrHandler);
92
- this.output(error)
93
- }
94
-
95
- const myErrHandler = genErrHandler.bind({ output(err) { throw err } })
96
- myErrHandler.log = genErrHandler.bind({ output(err) { console.error(err) } })
97
- myErrHandler.warn = genErrHandler.bind({ output(err) { console.warn(err) } })
98
- return myErrHandler
99
- }
100
-
1
+ // public
101
2
  export const
102
3
 
103
- /** Minifier-friendly alias for `window`.
104
- *
105
- * Allows bundlers to mangle the identifier across the codebase,
106
- * reducing bundle size without impacting runtime behavior.
4
+ /** Uglyfiable shorthand for the window object.
107
5
  *
6
+ * Make the bundle more concise and lightweight
7
+ *
108
8
  * @type {Window}
109
9
  */
110
10
  root = window,
111
11
 
112
- /** Read or write an entry in the Otorp namespace.
12
+ /** Read or write an entry in otorp namespace.
113
13
  *
114
- * Keys are stored under `Symbol.for('otorp:' + key)` on `root`.
115
- * Designed for registries, configuration objects, and shared tooling
116
- * not for arbitrary value storage. Falsy values cannot be written.
14
+ * Keys are stored as `Symbol.for('otorp:' + key)`. Pass a truthy `value` to
15
+ * assign and return it; omit `value` or pass a falsy one to read only. Falsy
16
+ * values cannot be stored through this helper.
117
17
  *
118
- * @param {string} key - Entry name.
119
- * @param {*} [value] - Truthy value to store. Omit to read.
18
+ * @param {string} key - Registry name (without the `otorp:` prefix).
19
+ * @param {*} [value] - Value to store. Must be truthy when writing.
120
20
  * @returns {*} The stored or newly assigned value.
121
21
  *
122
22
  * @example
123
- * ref('modules', new Map()); // write
124
- * ref('modules'); // read → same Map instance
23
+ * const modules = ref('modules', new Map()); // create registry
24
+ * ref('modules'); // → same Map instance
125
25
  */
126
- ref = (key, value) => value
127
- ? (root[sym(refPrefix + key)] = value)
128
- : root[sym(refPrefix + key)];
129
-
130
- // ─── Private ──────────────────────────────────────────────────────────────────
131
-
132
-
133
- // Builds the descriptor map passed to `Object.defineProperties` on a plugin key.
134
- // Mutates `propsDescDummy` in place to avoid a per-call object allocation
135
- // safe because defineProperties consumes the descriptors synchronously before
136
- // this function can be called again.
137
- function propsDesc(_throw, _error, _warn) {
138
- propsDescDummy.throw.value = _throw
139
- propsDescDummy.error.value = _error
140
- propsDescDummy.warn.value = _warn
141
- return propsDescDummy
142
- }
26
+ ref = (key, value) => value ? (root[sym(refPrefix + key)] = value) : root[sym(refPrefix + key)],
27
+
28
+ /** Creates a namespaced error logger.
29
+ *
30
+ * Returns a callable that throws an Error with an optional `cause`. The
31
+ * returned function also has `.warn` and `.log` variants that emit warnings or
32
+ * errors to the console instead of throwing.
33
+ *
34
+ * @param {string} [modName] - Optional namespace written to `error.name` (not `message`).
35
+ * @returns {((msg:string, options?:{type?:string, cause?:any})=>never) & { warn:Function, log:Function }}
36
+ *
37
+ * @example
38
+ * const err = newErrorHandler('myPlugin');
39
+ * err('Missing endpoint'); // throws: "[myPlugin] Missing endpoint"
40
+ * err.warn('Deprecated alias used'); // console.warn: "[myPlugin] Deprecated alias used'"
41
+ * err.log('Init failed', { cause }); // console.error => "[myPlugin - <cause>] Missing endpoint"
42
+ */
43
+ newErrorHandler = (modName) => {
44
+ let [prefix, separator, suffix] = modName ? [`[${modName}`, "-", "]"] : ["", "", ""];
45
+
46
+ /** Builds an Error and delegates to the bound `output` sink. */
47
+ function genErrHandler(msg, { type: errType, cause } = {}) {
48
+ const error = new Error(msg, { cause }), actualType = errType || cause?.name;
49
+ error.name = actualType ? (`${prefix} ${separator} ${actualType + suffix}`).trim() : prefix + suffix;
50
+ // V8 trims the factory from the stack; other engines keep the full trace.
51
+ (Error.captureStackTrace || (x => x))(error, genErrHandler);
52
+ this.output(error)
53
+ }
54
+ const myErrHandler = genErrHandler.bind({ output(err) { throw err } })
55
+ myErrHandler.log = genErrHandler.bind({ output(err) { console.error(err) } })
56
+ myErrHandler.warn = genErrHandler.bind({ output(err) { console.warn(err) } })
57
+ return myErrHandler
58
+ },
59
+
60
+ /** Register an Otorp plugin module for the initialization pass.
61
+ *
62
+ * Stores endpoint and aliases metadata in otorp module Map, exposes
63
+ * module-scoped error helpers on the plugin's symbol key, and installs the
64
+ * factory on root` so Otorp can resolve it from the registered `key`.
65
+ *
66
+ * @param {object} config - Plugin registration payload.
67
+ * @param {string} config.source - Unique module identifier used as the Map key.
68
+ * @param {Record<string, string[]>} config.endpoints - Target's name to
69
+ * endpoint query strings (e.g. `{ "HTMLElement": ['myPlugin.customMethod'] }`).
70
+ * @param {Function} config.factory - Factory called during Otorp init; receives
71
+ * the resolved method name and optional metadata segments, and must return
72
+ * the function to install on the prototype.
73
+ * @param {Record<string, string>|null} [config.aliases=null] - Optional alias map.
74
+ * @returns {Object} A unique symbol object (via `Object(Symbol(source))`) with
75
+ * `.throw`, `.warn`, and `.error` handlers attached. Otorp reads the factory
76
+ * from `root[key]`, not from a global `Symbol.for` lookup.
77
+ *
78
+ * @example
79
+ * setMod({
80
+ * source: 'myPlugin',
81
+ * endpoints: { "HTMLElement": ['myPlugin.customMethod'] },
82
+ * factory(methodName) {
83
+ * return function (...args) { /* `this` is the DOM instance *\/ };
84
+ * }
85
+ * });
86
+ */
87
+ setMod = ({ source, endpoints, factory, aliases = null }) => {
88
+ typeof factory !== 'function' && err("factory must be a function")
89
+
90
+ // Unique per call — Symbol(source), not Symbol.for. Otorp resolves the
91
+ // factory through the Map entry's `key`, keeping plugins isolated.
92
+ const key = Object(Symbol(source)), modErr = newErrorHandler(source);
93
+
94
+ // Attach throw / warn / log helpers to the symbol object itself.
95
+ Object.defineProperties(key, {
96
+ 'throw': newDescritor(modErr),
97
+ 'error': newDescritor(modErr.log),
98
+ 'warn': newDescritor(modErr.warn),
99
+ });
100
+
101
+ const count = (get(source)?.count || 0) + 1
102
+
103
+ set(source, {
104
+ endpoints,
105
+ aliases,
106
+ key,
107
+ count
108
+ });
109
+
110
+ return (root[key] = factory), key;
111
+ };
143
112
 
113
+ // private
144
114
  const
145
115
  /** Error handler for this package's own validation and setup failures. */
146
116
  err = newErrorHandler('@otorp/plugin-utils'),
147
117
 
148
- /** Cached `Symbol.for` to avoid repeated property lookups on each `ref` call. */
118
+ /** Shorthand for `Symbol.for` to reduce resolution/lookup at each `ref` invocation. */
149
119
  sym = Symbol.for,
150
120
 
151
- /** Namespace prefix used by `ref` to scope all Otorp entries on `root`. */
121
+ /** Otorp namespace. Used as prefix in the ref function */
152
122
  refPrefix = 'otorp:',
153
123
 
154
- /** Bound Map#get and Map#set for the shared modules Map.
155
- *
156
- * The Map is retrieved (or created) once at module evaluation time.
157
- * Methods are bound directly to avoid prototype lookups on every registration.
158
- */
159
- [get, set] = (map => [map.get.bind(map), map.set.bind(map)])(
160
- ref('modules') || ref('modules', new Map)
161
- ),
124
+ /** Bound `Map#set` to reduce resolution/lookup during plugins registration. */
125
+ [get, set] = (map => [map.get.bind(map), map.set.bind(map)])(ref('modules') || ref('modules', new Map));
162
126
 
163
- /** Reusable descriptor map for attaching error helpers to plugin keys.
164
- *
165
- * Mutated in place by `propsDesc` before each `Object.defineProperties` call.
166
- * The defined properties are non-writable and non-configurable on the target
167
- * to prevent plugin authors from accidentally overwriting the error helpers.
168
- */
169
- propsDescDummy = {
170
- throw: { value: null, writable: false, configurable: false },
171
- error: { value: null, writable: false, configurable: false },
172
- warn: { value: null, writable: false, configurable: false },
173
- };
127
+ /**
128
+ * @type {{ value: any, writable: boolean, configurable: boolean }}
129
+ */
130
+ const descriptorDummy = Object.create(null)
131
+ descriptorDummy.writable = descriptorDummy.configurable = false
132
+
133
+ /**
134
+ *
135
+ * @param {any} val
136
+ */
137
+ export function newDescritor(val) {
138
+ return descriptorDummy.value = val, descriptorDummy
139
+ }
package/package.json CHANGED
@@ -1,27 +1,21 @@
1
1
  {
2
2
  "name": "@otorp/plugin-utils",
3
- "version": "1.0.0",
4
- "description": "Plugin registration and error-handling utilities for the Otorp ecosystem — minimal, zero side-effects.",
5
- "exports": {
6
- ".": "./index.js"
7
- },
3
+ "version": "1.0.1-0",
4
+ "description": "",
5
+ "main": "main.js",
8
6
  "type": "module",
9
7
  "files": [
10
8
  "index.js"
11
9
  ],
12
10
  "keywords": [
13
- "javascript",
14
- "otorp",
15
- "plugin",
16
- "utilities",
17
- "error-handler",
18
- "registry"
11
+ "JavaScript",
12
+ "Otorp"
19
13
  ],
20
14
  "homepage": "https://gitlab.com/otorp/plugin-utils/-/wikis/home",
21
15
  "bugs": {
22
16
  "url": "https://gitlab.com/otorp/plugin-utils/-/issues"
23
17
  },
24
- "author": "TISSOT Davy",
18
+ "author": "TDJ.dev",
25
19
  "license": "MIT",
26
20
  "repository": {
27
21
  "type": "git",