@open-xchange/soap-client 0.1.5 → 0.2.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/CHANGELOG.md CHANGED
@@ -4,6 +4,34 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.2.0] - 2026-09-08
8
+
9
+ ### Added
10
+
11
+ - `createProvisioningClient({ url, auth, mxDomain })` (export `@open-xchange/soap-client/client`): builds a provisioning client bound to one endpoint and one set of credentials, so a single process can talk to several middlewares instead of inheriting the process-wide `PROVISIONING_URL`/`E2E_ADMIN_*` binding. Each service is also exported as a `createXService(createClient)` factory.
12
+
13
+ ### Changed
14
+
15
+ - Importing the library no longer touches process-global state. The `.env` loading, the timeout-swallowing `unhandledRejection` filter and the `DEBUG_SOAP` PerformanceObserver now attach when the env-configured default client is first used, not at import time. The classic module exports (`.../common`, `.../reseller`) behave as before; factory clients never install the rejection filter.
16
+ - `userService.changeByModuleAccessName` and `resellerUserService.changeByModuleAccessName` now reject on SOAP faults instead of swallowing them via `console.error` and resolving successfully. Callers that awaited them silently succeeding on a dead middleware will now see the real fault.
17
+ - The published tarball now carries only the runtime surface (46 files down to 16). Publishing was filtered by a `.npmignore` deny-list, which also disables `.gitignore`, so `test/`, `vitest.config.js` and `catalog-info.yaml` shipped and a local `pnpm publish` packed the whole `coverage/` report. Replaced with a `files` allow-list, which fails safe when files are added.
18
+ - `engines` now declares `node >=20.12`. This documents an existing requirement rather than adding one: `soap.js` and `bin/provision.js` call `process.loadEnvFile`, which landed in Node 20.12 / 21.7. Consumers installing with `engine-strict` on an older Node will now fail at install with a clear reason instead of at runtime with a `TypeError`.
19
+ - Dropped the `main` field. It pointed at a non-existent `index.js` and was never consulted: `exports` declares no `"."` subpath, so a bare `import '@open-xchange/soap-client'` fails with `ERR_PACKAGE_PATH_NOT_EXPORTED` as before.
20
+ - **Breaking:** the classic module exports now require `PROVISIONING_URL`, `E2E_ADMIN_USER` and `E2E_ADMIN_PW` to be set, and throw naming the missing variables on first use. Previously the master credentials could be left unset as long as every call carried its own `auth` (`context.admin`), which most user, account and reseller methods do; those setups now fail fast instead of reaching the middleware. Pass an endpoint and credentials explicitly with `createProvisioningClient({ url, auth })` if the environment binding does not fit.
21
+
22
+ ### Fixed
23
+
24
+ - `oxaasService.createSharedDomain` now calls the promise-returning `createSharedDomainAsync` binding. It was calling node-soap's callback-style `createSharedDomain`, which returns `undefined` synchronously and treats its second argument as the callback: the call resolved to `undefined` before the request had completed, bypassed the retry and `wrapSoapFault` handling entirely, and threw `TypeError: callback is not a function` from inside node-soap once the response arrived — asynchronously, outside any caller's `try`/`catch`.
25
+ - `contextService.getDefault()` now resolves the first matching context instead of always `undefined`. It indexed the promise returned by `listAsync` rather than the resolved array, so every caller dereferencing the result hit `TypeError: Cannot read properties of undefined`.
26
+ - A call whose `auth` is present but `undefined` no longer sends the request unauthenticated. Every user, account and reseller method passes `auth: context.admin`, so a context without a resolved admin put an `auth: undefined` key into the options; the proxy spread that over the endpoint's configured credentials and wiped them, and the call went out with no auth at all despite valid credentials being configured. `auth` is now pulled out before the merge, so an absent or undefined one falls back to the endpoint default while a real one still wins.
27
+ - A failed SOAP client construction is no longer cached for the lifetime of the process. The per-service client promise was memoized before it settled, so a WSDL fetch that exhausted its retry schedule or hit a non-retryable fault left a rejected promise in place and every later call re-threw that stale error without retrying. Construction is now re-attempted after a failure, which matters most for long-running embedders where one bad startup would otherwise poison the service until restart.
28
+
29
+ ## [0.1.6] - 2026-05-26
30
+
31
+ ### Fixed
32
+
33
+ - Aborting a non-retryable SOAP fault no longer corrupts the error message. p-retry v8 passes `onFailedAttempt` a context object (`{ error, attemptNumber, retriesLeft, ... }`) rather than the error itself; the abort path was passing that context object to `AbortError`, which stored it verbatim as `.message`. p-retry rejects with that `AbortError` as-is (it only unwraps `AbortError`s thrown by the retried function, not by the callback), so consumers received an error whose `.message` was an object and crashed with `error.message.includes is not a function` — masking the real fault. We now pass the inner `context.error`, keeping `.message` a string and letting `wrapSoapFault` recover the fault downstream. Both retry sites (WSDL fetch and per-method calls) share one `handleFailedAttempt` helper.
34
+
7
35
  ## [0.1.5] - 2026-05-26
8
36
 
9
37
  ### Fixed
@@ -113,7 +141,9 @@ All notable changes to this project will be documented in this file.
113
141
 
114
142
  - Initial release: extract SOAP client into its own library
115
143
 
116
- [unreleased]: https://gitlab.com/openxchange/appsuite/web-foundation/tools/-/compare/soap-client-0.1.5...main
144
+ [unreleased]: https://gitlab.com/openxchange/appsuite/web-foundation/tools/-/compare/soap-client-0.2.0...main
145
+ [0.2.0]: https://gitlab.com/openxchange/appsuite/web-foundation/tools/-/compare/soap-client-0.1.6...soap-client-0.2.0
146
+ [0.1.6]: https://gitlab.com/openxchange/appsuite/web-foundation/tools/-/compare/soap-client-0.1.5...soap-client-0.1.6
117
147
  [0.1.5]: https://gitlab.com/openxchange/appsuite/web-foundation/tools/-/compare/soap-client-0.1.4...soap-client-0.1.5
118
148
  [0.1.4]: https://gitlab.com/openxchange/appsuite/web-foundation/tools/-/compare/soap-client-0.1.3...soap-client-0.1.4
119
149
  [0.1.3]: https://gitlab.com/openxchange/appsuite/web-foundation/tools/-/compare/soap-client-0.1.2...soap-client-0.1.3
package/README.md CHANGED
@@ -106,6 +106,29 @@ const context = await contextService.create({ name: 'my-context' })
106
106
  const user = await userService.create(context, { name: 'myuser', email1: 'user@example.com' })
107
107
  ```
108
108
 
109
+ ### Embedding: one client per endpoint
110
+
111
+ The module exports above bind to `PROVISIONING_URL`/`E2E_ADMIN_USER`/`E2E_ADMIN_PW`
112
+ on first use — right for the CLI and test runs, wrong for a long-running process
113
+ that talks to many middlewares. For that, build clients explicitly:
114
+
115
+ ```js
116
+ import { createProvisioningClient } from '@open-xchange/soap-client/client'
117
+
118
+ const client = createProvisioningClient({
119
+ url: 'http://core-mw-admin.appsuite-main.svc',
120
+ auth: { login: 'oxadminmaster', password: secret },
121
+ mxDomain: 'box.ox.io' // optional, for generated context-admin addresses
122
+ })
123
+ await client.contextService.list('defaultcontext')
124
+ ```
125
+
126
+ Factory clients install no process-global handlers and each carries its own
127
+ endpoint, credentials and retry/fault handling. They read the environment in
128
+ exactly two places: `MX_DOMAIN` as the fallback when `mxDomain` is omitted, and
129
+ `DEBUG_SOAP` for timing output. Pass `mxDomain` explicitly if an ambient
130
+ `MX_DOMAIN` must not leak into generated context-admin addresses.
131
+
109
132
  ## Configuration
110
133
 
111
134
  Set up your `.env` file with the required environment variables. See `.env.default` for an example.
package/client.js ADDED
@@ -0,0 +1,72 @@
1
+ /**
2
+ * @copyright Copyright (c) Open-Xchange GmbH, Germany <info@open-xchange.com>
3
+ * @license AGPL-3.0
4
+ *
5
+ * This code is free software: you can redistribute it and/or modify
6
+ * it under the terms of the GNU Affero General Public License as published by
7
+ * the Free Software Foundation, either version 3 of the License, or
8
+ * (at your option) any later version.
9
+ *
10
+ * This program is distributed in the hope that it will be useful,
11
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ * GNU Affero General Public License for more details.
14
+ *
15
+ * You should have received a copy of the GNU Affero General Public License
16
+ * along with OX App Suite. If not, see <https://www.gnu.org/licenses/agpl-3.0.txt>.
17
+ *
18
+ * Any use of the work other than as authorized under this license or copyright law is prohibited.
19
+ */
20
+
21
+ import { createClientFactory } from './soap.js'
22
+ import { createContextService } from './services/common/context.js'
23
+ import { createUserService } from './services/common/user.js'
24
+ import { createUtilService } from './services/common/util.js'
25
+ import { createSecondaryAccountService } from './services/secondaryAccount.js'
26
+ import { createSharedAccountService } from './services/sharedAccount.js'
27
+ import { createOxaasService } from './services/reseller/oxaas.js'
28
+ import { createResellerContextService } from './services/reseller/resellerContext.js'
29
+ import { createResellerUserService } from './services/reseller/resellerUser.js'
30
+
31
+ /**
32
+ * @typedef {object} ProvisioningClientOptions
33
+ * @property {string} url Base URL of the provisioning API (the middleware
34
+ * admin endpoint), without the `/webservices` suffix.
35
+ * @property {{login: string, password: string}} auth Master admin credentials.
36
+ * @property {string} [mxDomain] Domain for generated context-admin email
37
+ * addresses; defaults to the MX_DOMAIN environment variable.
38
+ */
39
+
40
+ /**
41
+ * Create a provisioning client bound to one endpoint and one set of
42
+ * credentials. Any number of clients coexist in a single process; the only
43
+ * environment reads are the mxDomain fallback (MX_DOMAIN) and DEBUG_SOAP, and
44
+ * no process-global state is touched — unlike the classic module exports, which
45
+ * bind to PROVISIONING_URL / E2E_ADMIN_USER / E2E_ADMIN_PW on first use and
46
+ * install the timeout-rejection filter for CLI compatibility.
47
+ *
48
+ * @param {ProvisioningClientOptions} options
49
+ * @returns {{
50
+ * contextService: Object,
51
+ * userService: Object,
52
+ * utilService: Object,
53
+ * secondaryAccountService: Object,
54
+ * sharedAccountService: Object,
55
+ * oxaasService: Object,
56
+ * resellerContextService: Object,
57
+ * resellerUserService: Object
58
+ * }}
59
+ */
60
+ export function createProvisioningClient ({ url, auth, mxDomain } = {}) {
61
+ const createClient = createClientFactory({ url, auth })
62
+ return {
63
+ contextService: createContextService(createClient, { mxDomain }),
64
+ userService: createUserService(createClient),
65
+ utilService: createUtilService(createClient),
66
+ secondaryAccountService: createSecondaryAccountService(createClient),
67
+ sharedAccountService: createSharedAccountService(createClient),
68
+ oxaasService: createOxaasService(createClient),
69
+ resellerContextService: createResellerContextService(createClient),
70
+ resellerUserService: createResellerUserService(createClient)
71
+ }
72
+ }
package/package.json CHANGED
@@ -1,8 +1,7 @@
1
1
  {
2
2
  "name": "@open-xchange/soap-client",
3
- "version": "0.1.5",
3
+ "version": "0.2.0",
4
4
  "description": "SOAP client for OX App Suite",
5
- "main": "index.js",
6
5
  "type": "module",
7
6
  "repository": {
8
7
  "type": "git",
@@ -11,26 +10,38 @@
11
10
  },
12
11
  "exports": {
13
12
  "./common": "./services/common/index.js",
14
- "./reseller": "./services/reseller/index.js"
13
+ "./reseller": "./services/reseller/index.js",
14
+ "./client": "./client.js"
15
15
  },
16
+ "files": [
17
+ "bin/",
18
+ "services/",
19
+ "client.js",
20
+ "soap.js",
21
+ "README.md",
22
+ "CHANGELOG.md"
23
+ ],
16
24
  "bin": {
17
25
  "provision": "./bin/provision.js"
18
26
  },
19
27
  "keywords": [],
20
28
  "author": "",
21
29
  "license": "AGPL-3.0-or-later",
30
+ "engines": {
31
+ "node": ">=20.12"
32
+ },
22
33
  "dependencies": {
23
- "commander": "^14.0.3",
34
+ "commander": "^15.0.0",
24
35
  "p-retry": "^8.0.0",
25
36
  "soap": "^1.9.3"
26
37
  },
27
38
  "devDependencies": {
28
- "vitest": "^4.1.7",
29
- "@open-xchange/lint": "0.3.0"
39
+ "@open-xchange/lint": "^0.3.1",
40
+ "vitest": "^4.1.7"
30
41
  },
31
42
  "scripts": {
32
43
  "lint": "eslint .",
33
- "test": "vitest --run",
44
+ "test": "vitest --run --coverage",
34
45
  "provision": "node ./bin/provision.js"
35
46
  }
36
47
  }
@@ -18,147 +18,160 @@
18
18
  * Any use of the work other than as authorized under this license or copyright law is prohibited.
19
19
  */
20
20
 
21
- import { createClientAsync, logSoapError } from '../../soap.js'
22
-
23
- let OXContextService
24
- function getClient () {
25
- if (!OXContextService) OXContextService = createClientAsync('OXContextService')
26
- return OXContextService
27
- }
21
+ import { createClientAsync, memoizeClient, logSoapError } from '../../soap.js'
28
22
 
29
23
  /**
30
- * This function retrieves the default context from the OXContextService.
31
- * @returns {Promise<Object>} The first result from the listAsync method call.
24
+ * Build a context service bound to a SOAP client constructor.
25
+ * @param {(type: string) => Promise<Object>} createClient
26
+ * @param {{mxDomain?: string}} [options] mxDomain feeds the default context
27
+ * admin's email addresses; falls back to MX_DOMAIN from the environment.
28
+ * @returns {Object} The context service.
32
29
  */
33
- export async function getDefault () {
34
- return (await getClient()).listAsync({ search_pattern: 'defaultcontext' })[0]
35
- }
30
+ export function createContextService (createClient, { mxDomain } = {}) {
31
+ const getClient = memoizeClient(createClient, 'OXContextService')
32
+ const domain = () => mxDomain ?? process.env.MX_DOMAIN
36
33
 
37
- /**
38
- * This function removes the context with the specified ID.
39
- * Fire-and-forget operation - spawns a separate process to avoid blocking.
40
- * @param {number} id The ID of the context to remove.
41
- */
42
- export async function remove (id) {
43
- return (await getClient()).deleteAsync({ ctx: { id } })
44
- }
34
+ /**
35
+ * This function retrieves the default context from the OXContextService.
36
+ * @returns {Promise<Object>} The first result from the listAsync method call.
37
+ */
38
+ async function getDefault () {
39
+ return (await (await getClient()).listAsync({ search_pattern: 'defaultcontext' }))?.[0]
40
+ }
45
41
 
46
- /**
47
- * This function creates a new context.
48
- * @param {Object} ctx The context to create.
49
- * @param {Object} adminUser The admin user of the context.
50
- * Defaults to:
51
- * ```JSON
52
- * {
53
- name: 'oxadmin',
54
- password: 'secret',
55
- display_name: 'context admin',
56
- sur_name: 'admin',
57
- given_name: 'context',
58
- email1: `oxadmin@${process.env.MX_DOMAIN}`,
59
- primaryEmail: `oxadmin@${process.env.MX_DOMAIN}`
60
- }
61
- All properties are needed and will be inserted if not provided.
62
- ```
63
- * @returns {Promise<Object>} The created context.
64
- */
65
- export async function create (ctx = {}, adminUser = {}, accessCombinationName = 'all') {
66
- return (await getClient())
67
- .createAsync({
68
- ctx,
69
- admin_user: Object.assign({
70
- name: 'oxadmin',
71
- password: 'secret',
72
- display_name: 'context admin',
73
- sur_name: 'admin',
74
- given_name: 'context',
75
- email1: `oxadmin@${process.env.MX_DOMAIN}`,
76
- primaryEmail: `oxadmin@${process.env.MX_DOMAIN}`
77
- }, adminUser)
78
- })
79
- .then(async context => {
80
- await changeModuleAccessByName(context.id, accessCombinationName)
81
- return context
82
- })
83
- .catch(e => {
84
- logSoapError(e)
85
- throw e
86
- })
87
- }
42
+ /**
43
+ * This function removes the context with the specified ID.
44
+ * @param {number} id The ID of the context to remove.
45
+ */
46
+ async function remove (id) {
47
+ return (await getClient()).deleteAsync({ ctx: { id } })
48
+ }
88
49
 
89
- /**
90
- * This function changes the module access of the specified context.
91
- * @param {number} id The ID of the context.
92
- * @param {string} access_combination_name The name of the access combination to set.
93
- * @returns {Promise<void>}
94
- */
95
- // eslint-disable-next-line camelcase
96
- export async function changeModuleAccessByName (id, access_combination_name) {
50
+ /**
51
+ * This function creates a new context.
52
+ * @param {Object} ctx The context to create (name, capabilities, config,
53
+ * maxQuota, loginMappings, ... passed through to the SOAP Context object).
54
+ * @param {Object} adminUser The admin user of the context. Missing fields are
55
+ * filled with the oxadmin defaults (email under the configured mxDomain).
56
+ * @param {string} accessCombinationName Access combination applied after create.
57
+ * @returns {Promise<Object>} The created context.
58
+ */
59
+ async function create (ctx = {}, adminUser = {}, accessCombinationName = 'all') {
60
+ return (await getClient())
61
+ .createAsync({
62
+ ctx,
63
+ admin_user: Object.assign({
64
+ name: 'oxadmin',
65
+ password: 'secret',
66
+ display_name: 'context admin',
67
+ sur_name: 'admin',
68
+ given_name: 'context',
69
+ email1: `oxadmin@${domain()}`,
70
+ primaryEmail: `oxadmin@${domain()}`
71
+ }, adminUser)
72
+ })
73
+ .then(async context => {
74
+ await changeModuleAccessByName(context.id, accessCombinationName)
75
+ return context
76
+ })
77
+ .catch(e => {
78
+ logSoapError(e)
79
+ throw e
80
+ })
81
+ }
82
+
83
+ /**
84
+ * This function changes the module access of the specified context.
85
+ * @param {number} id The ID of the context.
86
+ * @param {string} access_combination_name The name of the access combination to set.
87
+ * @returns {Promise<void>}
88
+ */
97
89
  // eslint-disable-next-line camelcase
98
- return (await getClient()).changeModuleAccessByNameAsync({ ctx: { id }, access_combination_name })
99
- }
90
+ async function changeModuleAccessByName (id, access_combination_name) {
91
+ // eslint-disable-next-line camelcase
92
+ return (await getClient()).changeModuleAccessByNameAsync({ ctx: { id }, access_combination_name })
93
+ }
100
94
 
101
- /**
102
- * This function changes the capabilities of the specified context.
103
- * @param {number} id The ID of the context.
104
- * @param {string} capsToAdd The capabilities to add.
105
- * @param {string} capsToRemove The capabilities to remove.
106
- * @returns {Promise<void>}
107
- */
108
- export async function changeCapabilities (id, capsToAdd, capsToRemove) {
109
- const data = {}
110
- if (capsToAdd) data.capsToAdd = capsToAdd
111
- if (capsToRemove) data.capsToRemove = capsToRemove
112
- return (await getClient()).changeCapabilitiesAsync({ ctx: { id }, ...data })
113
- }
95
+ /**
96
+ * This function changes the capabilities of the specified context.
97
+ * @param {number} id The ID of the context.
98
+ * @param {string} capsToAdd The capabilities to add.
99
+ * @param {string} capsToRemove The capabilities to remove.
100
+ * @returns {Promise<void>}
101
+ */
102
+ async function changeCapabilities (id, capsToAdd, capsToRemove) {
103
+ const data = {}
104
+ if (capsToAdd) data.capsToAdd = capsToAdd
105
+ if (capsToRemove) data.capsToRemove = capsToRemove
106
+ return (await getClient()).changeCapabilitiesAsync({ ctx: { id }, ...data })
107
+ }
114
108
 
115
- /**
116
- * This function retrieves the module access of the specified context.
117
- * @param {number} id The ID of the context.
118
- * @returns {Promise<Object>} The module access of the specified context.
119
- */
120
- export async function getModuleAccess (id) {
121
- return (await getClient()).getModuleAccessAsync({ ctx: { id } })
122
- }
123
- /**
124
- * This function changes the module access of the specified context.
125
- * @param {number} id The ID of the context.
126
- * @param {Object} moduleAccess The module access to set.
127
- * @returns {Promise<void>}
128
- */
129
- export async function changeModuleAccess (id, moduleAccess) {
130
- const currentAccess = await getModuleAccess(id)
131
- return (await getClient()).changeModuleAccessAsync({ ctx: { id }, access: { ...currentAccess, ...moduleAccess } })
132
- }
109
+ /**
110
+ * This function retrieves the module access of the specified context.
111
+ * @param {number} id The ID of the context.
112
+ * @returns {Promise<Object>} The module access of the specified context.
113
+ */
114
+ async function getModuleAccess (id) {
115
+ return (await getClient()).getModuleAccessAsync({ ctx: { id } })
116
+ }
133
117
 
134
- /**
135
- * Search for contexts.
136
- * @param {String} searchPattern The pattern to search for
137
- * @param {Object} options Additional options
138
- * @param {Boolean} options.excludeDisabled Exclude disabled contexts from the search results (default: true)
139
- * @returns {Promise<Array<Object>>} The list of contexts that match the search pattern.
140
- */
141
- export async function list (searchPattern, { excludeDisabled } = { excludeDisabled: true }) {
142
- return (await getClient()).listAsync({ search_pattern: searchPattern, exclude_disabled: excludeDisabled }).catch(e => {
143
- logSoapError(e)
144
- return []
145
- })
146
- }
118
+ /**
119
+ * This function changes the module access of the specified context.
120
+ * @param {number} id The ID of the context.
121
+ * @param {Object} moduleAccess The module access to set.
122
+ * @returns {Promise<void>}
123
+ */
124
+ async function changeModuleAccess (id, moduleAccess) {
125
+ const currentAccess = await getModuleAccess(id)
126
+ return (await getClient()).changeModuleAccessAsync({ ctx: { id }, access: { ...currentAccess, ...moduleAccess } })
127
+ }
147
128
 
148
- /**
149
- * This function retrieves the context with the specified ID.
150
- * @param {number} id The ID of the context to retrieve.
151
- * @returns {Promise<Object>} The context with the specified ID.
152
- */
153
- export async function get (id) {
154
- return (await getClient()).getDataAsync({ ctx: { id } })
155
- }
129
+ /**
130
+ * Search for contexts.
131
+ * @param {String} searchPattern The pattern to search for
132
+ * @param {Object} options Additional options
133
+ * @param {Boolean} options.excludeDisabled Exclude disabled contexts from the search results (default: true)
134
+ * @returns {Promise<Array<Object>>} The list of contexts that match the search pattern.
135
+ */
136
+ async function list (searchPattern, { excludeDisabled } = { excludeDisabled: true }) {
137
+ return (await getClient()).listAsync({ search_pattern: searchPattern, exclude_disabled: excludeDisabled }).catch(e => {
138
+ logSoapError(e)
139
+ return []
140
+ })
141
+ }
156
142
 
157
- /**
158
- * This function changes the context with the specified ID.
159
- * @param {Object} ctx The context to change.
160
- * @returns {Promise<void>}
161
- */
162
- export async function change (ctx) {
163
- return (await getClient()).changeAsync({ ctx })
143
+ /**
144
+ * This function retrieves the context with the specified ID.
145
+ * @param {number} id The ID of the context to retrieve.
146
+ * @returns {Promise<Object>} The context with the specified ID.
147
+ */
148
+ async function get (id) {
149
+ return (await getClient()).getDataAsync({ ctx: { id } })
150
+ }
151
+
152
+ /**
153
+ * This function changes the context with the specified ID.
154
+ * @param {Object} ctx The context to change.
155
+ * @returns {Promise<void>}
156
+ */
157
+ async function change (ctx) {
158
+ return (await getClient()).changeAsync({ ctx })
159
+ }
160
+
161
+ return { getDefault, remove, create, changeModuleAccessByName, changeCapabilities, getModuleAccess, changeModuleAccess, list, get, change }
164
162
  }
163
+
164
+ // Classic module exports, bound to the env-configured default endpoint.
165
+ let defaultService
166
+ const service = () => (defaultService ??= createContextService(createClientAsync))
167
+
168
+ export const getDefault = (...args) => service().getDefault(...args)
169
+ export const remove = (...args) => service().remove(...args)
170
+ export const create = (...args) => service().create(...args)
171
+ export const changeModuleAccessByName = (...args) => service().changeModuleAccessByName(...args)
172
+ export const changeCapabilities = (...args) => service().changeCapabilities(...args)
173
+ export const getModuleAccess = (...args) => service().getModuleAccess(...args)
174
+ export const changeModuleAccess = (...args) => service().changeModuleAccess(...args)
175
+ export const list = (...args) => service().list(...args)
176
+ export const get = (...args) => service().get(...args)
177
+ export const change = (...args) => service().change(...args)