@classytic/arc 2.2.5 → 2.4.1
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 +187 -18
- package/bin/arc.js +11 -3
- package/dist/BaseController-CkM5dUh_.mjs +1031 -0
- package/dist/{EventTransport-BkUDYZEb.d.mts → EventTransport-wc5hSLik.d.mts} +1 -1
- package/dist/{HookSystem-BsGV-j2l.mjs → HookSystem-COkyWztM.mjs} +2 -3
- package/dist/{ResourceRegistry-7Ic20ZMw.mjs → ResourceRegistry-DeCIFlix.mjs} +8 -5
- package/dist/adapters/index.d.mts +3 -5
- package/dist/adapters/index.mjs +2 -3
- package/dist/{prisma-DJbMt3yf.mjs → adapters-DTC4Ug66.mjs} +45 -12
- package/dist/audit/index.d.mts +4 -7
- package/dist/audit/index.mjs +2 -29
- package/dist/audit/mongodb.d.mts +1 -4
- package/dist/audit/mongodb.mjs +2 -3
- package/dist/auth/index.d.mts +7 -9
- package/dist/auth/index.mjs +65 -63
- package/dist/auth/redis-session.d.mts +1 -1
- package/dist/auth/redis-session.mjs +1 -2
- package/dist/{betterAuthOpenApi-DjWDddNc.mjs → betterAuthOpenApi-lz0IRbXJ.mjs} +4 -6
- package/dist/cache/index.d.mts +23 -23
- package/dist/cache/index.mjs +4 -6
- package/dist/{caching-GSDJcA6-.mjs → caching-BSXB-Xr7.mjs} +2 -24
- package/dist/chunk-BpYLSNr0.mjs +14 -0
- package/dist/circuitBreaker-BOBOpN2w.mjs +284 -0
- package/dist/circuitBreaker-JP2GdJ4b.d.mts +206 -0
- package/dist/cli/commands/describe.mjs +24 -7
- package/dist/cli/commands/docs.mjs +6 -7
- package/dist/cli/commands/doctor.d.mts +10 -0
- package/dist/cli/commands/doctor.mjs +156 -0
- package/dist/cli/commands/generate.mjs +66 -17
- package/dist/cli/commands/init.mjs +315 -45
- package/dist/cli/commands/introspect.mjs +2 -4
- package/dist/cli/index.d.mts +1 -10
- package/dist/cli/index.mjs +4 -153
- package/dist/{constants-DdXFXQtN.mjs → constants-Cxde4rpC.mjs} +1 -2
- package/dist/core/index.d.mts +3 -5
- package/dist/core/index.mjs +5 -4
- package/dist/core-C1XCMtqM.mjs +185 -0
- package/dist/{createApp-BKHSl2nT.mjs → createApp-ByWNRsZj.mjs} +65 -36
- package/dist/{defineResource-DO9ONe_D.mjs → defineResource-D9aY5Cy6.mjs} +154 -1165
- package/dist/discovery/index.mjs +37 -5
- package/dist/docs/index.d.mts +6 -9
- package/dist/docs/index.mjs +3 -21
- package/dist/dynamic/index.d.mts +93 -0
- package/dist/dynamic/index.mjs +122 -0
- package/dist/{elevation-DSTbVvYj.mjs → elevation-BEdACOLB.mjs} +5 -36
- package/dist/{elevation-DGo5shaX.d.mts → elevation-Ca_yveIO.d.mts} +41 -7
- package/dist/{errorHandler-C3GY3_ow.mjs → errorHandler--zp54tGc.mjs} +3 -5
- package/dist/errorHandler-Do4vVQ1f.d.mts +139 -0
- package/dist/{errors-DBANPbGr.mjs → errors-rxhfP7Hf.mjs} +1 -2
- package/dist/{eventPlugin-BEOvaDqo.mjs → eventPlugin-Ba00swHF.mjs} +25 -27
- package/dist/{eventPlugin-H6wDDjGO.d.mts → eventPlugin-iGrSEmwJ.d.mts} +105 -5
- package/dist/events/index.d.mts +72 -7
- package/dist/events/index.mjs +216 -4
- package/dist/events/transports/redis-stream-entry.d.mts +1 -1
- package/dist/events/transports/redis-stream-entry.mjs +19 -7
- package/dist/events/transports/redis.d.mts +1 -1
- package/dist/events/transports/redis.mjs +3 -4
- package/dist/factory/index.d.mts +23 -9
- package/dist/factory/index.mjs +48 -3
- package/dist/{fields-Bi_AVKSo.d.mts → fields-DFwdaWCq.d.mts} +1 -1
- package/dist/{fields-CTd_CrKr.mjs → fields-ipsbIRPK.mjs} +1 -2
- package/dist/hooks/index.d.mts +1 -3
- package/dist/hooks/index.mjs +2 -3
- package/dist/idempotency/index.d.mts +5 -5
- package/dist/idempotency/index.mjs +3 -7
- package/dist/idempotency/mongodb.d.mts +1 -1
- package/dist/idempotency/mongodb.mjs +4 -5
- package/dist/idempotency/redis.d.mts +1 -1
- package/dist/idempotency/redis.mjs +2 -5
- package/dist/{fastifyAdapter-CyAA2zlB.d.mts → index-BL8CaQih.d.mts} +56 -57
- package/dist/index-Diqcm14c.d.mts +369 -0
- package/dist/{prisma-xjhMEq_S.d.mts → index-yhxyjqNb.d.mts} +4 -5
- package/dist/index.d.mts +100 -105
- package/dist/index.mjs +85 -58
- package/dist/integrations/event-gateway.d.mts +1 -1
- package/dist/integrations/event-gateway.mjs +8 -4
- package/dist/integrations/index.d.mts +4 -2
- package/dist/integrations/index.mjs +1 -1
- package/dist/integrations/jobs.d.mts +2 -2
- package/dist/integrations/jobs.mjs +63 -14
- package/dist/integrations/mcp/index.d.mts +219 -0
- package/dist/integrations/mcp/index.mjs +572 -0
- package/dist/integrations/mcp/testing.d.mts +53 -0
- package/dist/integrations/mcp/testing.mjs +104 -0
- package/dist/integrations/streamline.mjs +39 -19
- package/dist/integrations/webhooks.d.mts +56 -0
- package/dist/integrations/webhooks.mjs +139 -0
- package/dist/integrations/websocket-redis.d.mts +46 -0
- package/dist/integrations/websocket-redis.mjs +50 -0
- package/dist/integrations/websocket.d.mts +68 -2
- package/dist/integrations/websocket.mjs +96 -13
- package/dist/{interface-CSNjltAc.d.mts → interface-B4awm1RJ.d.mts} +2 -2
- package/dist/interface-DGmPxakH.d.mts +2213 -0
- package/dist/{keys-DhqDRxv3.mjs → keys-qcD-TVJl.mjs} +3 -4
- package/dist/{logger-ByrvQWZO.mjs → logger-Dz3j1ItV.mjs} +2 -4
- package/dist/{memory-B2v7KrCB.mjs → memory-Cb_7iy9e.mjs} +2 -4
- package/dist/metrics-Csh4nsvv.mjs +224 -0
- package/dist/migrations/index.mjs +3 -7
- package/dist/{mongodb-DNKEExbf.mjs → mongodb-BuQ7fNTg.mjs} +1 -4
- package/dist/{mongodb-ClykrfGo.d.mts → mongodb-CUpYfxfD.d.mts} +2 -3
- package/dist/{mongodb-Dg8O_gvd.d.mts → mongodb-bga9AbkD.d.mts} +2 -2
- package/dist/{openapi-9nB_kiuR.mjs → openapi-CBmZ6EQN.mjs} +4 -21
- package/dist/org/index.d.mts +12 -14
- package/dist/org/index.mjs +92 -119
- package/dist/org/types.d.mts +2 -2
- package/dist/org/types.mjs +1 -1
- package/dist/permissions/index.d.mts +4 -278
- package/dist/permissions/index.mjs +4 -579
- package/dist/permissions-CA5zg0yK.mjs +751 -0
- package/dist/plugins/index.d.mts +104 -107
- package/dist/plugins/index.mjs +203 -313
- package/dist/plugins/response-cache.mjs +4 -69
- package/dist/plugins/tracing-entry.d.mts +1 -1
- package/dist/plugins/tracing-entry.mjs +24 -11
- package/dist/{pluralize-CM-jZg7p.mjs → pluralize-CcT6qF0a.mjs} +12 -13
- package/dist/policies/index.d.mts +2 -2
- package/dist/policies/index.mjs +80 -83
- package/dist/presets/index.d.mts +26 -19
- package/dist/presets/index.mjs +2 -142
- package/dist/presets/multiTenant.d.mts +1 -4
- package/dist/presets/multiTenant.mjs +4 -6
- package/dist/presets-C9QXJV1u.mjs +422 -0
- package/dist/{queryCachePlugin-B6R0d4av.mjs → queryCachePlugin-ClosZdNS.mjs} +6 -27
- package/dist/{queryCachePlugin-Q6SYuHZ6.d.mts → queryCachePlugin-DcmETvcB.d.mts} +3 -3
- package/dist/queryParser-CgCtsjti.mjs +352 -0
- package/dist/{redis-UwjEp8Ea.d.mts → redis-CQ5YxMC5.d.mts} +2 -2
- package/dist/{redis-stream-CBg0upHI.d.mts → redis-stream-BW9UKLZM.d.mts} +9 -2
- package/dist/registry/index.d.mts +1 -4
- package/dist/registry/index.mjs +3 -4
- package/dist/{introspectionPlugin-B3JkrjwU.mjs → registry-I-ogLgL9.mjs} +1 -8
- package/dist/{requestContext-xi6OKBL-.mjs → requestContext-DYtmNpm5.mjs} +1 -3
- package/dist/resourceToTools-B6ZN9Ing.mjs +489 -0
- package/dist/rpc/index.d.mts +90 -0
- package/dist/rpc/index.mjs +248 -0
- package/dist/{schemaConverter-Dtg0Kt9T.mjs → schemaConverter-DjzHpFam.mjs} +1 -2
- package/dist/schemas/index.d.mts +30 -30
- package/dist/schemas/index.mjs +4 -6
- package/dist/scope/index.d.mts +13 -2
- package/dist/scope/index.mjs +18 -5
- package/dist/{sessionManager-D_iEHjQl.d.mts → sessionManager-wbkYj2HL.d.mts} +2 -2
- package/dist/{sse-DkqQ1uxb.mjs → sse-BkViJPlT.mjs} +4 -25
- package/dist/testing/index.d.mts +551 -567
- package/dist/testing/index.mjs +1744 -1799
- package/dist/{tracing-8CEbhF0w.d.mts → tracing-bz_U4EM1.d.mts} +6 -1
- package/dist/{typeGuards-DwxA1t_L.mjs → typeGuards-Cj5Rgvlg.mjs} +1 -2
- package/dist/types/index.d.mts +4 -946
- package/dist/types/index.mjs +2 -4
- package/dist/types-BJmgxNbF.d.mts +275 -0
- package/dist/{types-RLkFVgaw.d.mts → types-BNUccdcf.d.mts} +2 -2
- package/dist/{types-Beqn1Un7.mjs → types-C6TQjtdi.mjs} +30 -2
- package/dist/{types-DMSBMkaZ.d.mts → types-Dt0-AI6E.d.mts} +85 -27
- package/dist/{types-DelU6kln.mjs → types-ZUu_h0jp.mjs} +1 -2
- package/dist/utils/index.d.mts +255 -352
- package/dist/utils/index.mjs +7 -6
- package/dist/utils-Dc0WhlIl.mjs +594 -0
- package/dist/versioning-BzfeHmhj.mjs +37 -0
- package/package.json +46 -12
- package/skills/arc/SKILL.md +506 -0
- package/skills/arc/references/auth.md +250 -0
- package/skills/arc/references/events.md +272 -0
- package/skills/arc/references/integrations.md +385 -0
- package/skills/arc/references/mcp.md +386 -0
- package/skills/arc/references/production.md +610 -0
- package/skills/arc/references/testing.md +183 -0
- package/dist/audited-CGdLiSlE.mjs +0 -140
- package/dist/chunk-C7Uep-_p.mjs +0 -20
- package/dist/circuitBreaker-DYhWBW_D.mjs +0 -1096
- package/dist/errorHandler-CW3OOeYq.d.mts +0 -72
- package/dist/interface-DZYNK9bb.d.mts +0 -1112
- package/dist/presets-BTeYbw7h.d.mts +0 -57
- package/dist/presets-CeFtfDR8.mjs +0 -119
- /package/dist/{errors-DAWRdiYP.d.mts → errors-CPpvPHT0.d.mts} +0 -0
- /package/dist/{externalPaths-SyPF2tgK.d.mts → externalPaths-DpO-s7r8.d.mts} +0 -0
- /package/dist/{interface-DTbsvIWe.d.mts → interface-D_BWALyZ.d.mts} +0 -0
|
@@ -1,7 +1,20 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
2
|
import fp from "fastify-plugin";
|
|
3
|
-
|
|
4
3
|
//#region src/plugins/tracing.ts
|
|
4
|
+
/**
|
|
5
|
+
* OpenTelemetry Distributed Tracing Plugin
|
|
6
|
+
*
|
|
7
|
+
* Traces HTTP requests, repository operations, and MongoDB queries
|
|
8
|
+
* across the entire application lifecycle.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* import { tracingPlugin } from '@classytic/arc/plugins';
|
|
12
|
+
*
|
|
13
|
+
* await fastify.register(tracingPlugin, {
|
|
14
|
+
* serviceName: 'my-api',
|
|
15
|
+
* exporterUrl: 'http://localhost:4318/v1/traces', // OTLP endpoint
|
|
16
|
+
* });
|
|
17
|
+
*/
|
|
5
18
|
const require = createRequire(import.meta.url);
|
|
6
19
|
let trace;
|
|
7
20
|
let context;
|
|
@@ -24,17 +37,18 @@ try {
|
|
|
24
37
|
require("@opentelemetry/instrumentation-mongodb").MongoDBInstrumentation;
|
|
25
38
|
getNodeAutoInstrumentations = require("@opentelemetry/auto-instrumentations-node").getNodeAutoInstrumentations;
|
|
26
39
|
isAvailable = true;
|
|
27
|
-
} catch (
|
|
40
|
+
} catch (_e) {}
|
|
28
41
|
/**
|
|
29
42
|
* Create a tracer provider
|
|
30
43
|
*/
|
|
31
44
|
function createTracerProvider(options) {
|
|
32
45
|
if (!isAvailable) return null;
|
|
33
|
-
const { serviceName = "@classytic/arc", exporterUrl = "http://localhost:4318/v1/traces" } = options;
|
|
46
|
+
const { serviceName = "@classytic/arc", serviceVersion, exporterUrl = "http://localhost:4318/v1/traces" } = options;
|
|
47
|
+
const resolvedVersion = serviceVersion ?? "2.4.1";
|
|
34
48
|
const exporter = new OTLPTraceExporter({ url: exporterUrl });
|
|
35
49
|
const provider = new NodeTracerProvider({ resource: { attributes: {
|
|
36
50
|
"service.name": serviceName,
|
|
37
|
-
"service.version":
|
|
51
|
+
"service.version": resolvedVersion
|
|
38
52
|
} } });
|
|
39
53
|
provider.addSpanProcessor(new BatchSpanProcessor(exporter));
|
|
40
54
|
provider.register();
|
|
@@ -61,7 +75,7 @@ async function tracingPlugin(fastify, options = {}) {
|
|
|
61
75
|
}
|
|
62
76
|
const tracer = trace.getTracer(serviceName);
|
|
63
77
|
fastify.decorateRequest("tracer", void 0);
|
|
64
|
-
fastify.addHook("onRequest", async (request,
|
|
78
|
+
fastify.addHook("onRequest", async (request, _reply) => {
|
|
65
79
|
if (Math.random() > sampleRate) return;
|
|
66
80
|
const span = tracer.startSpan(`HTTP ${request.method} ${request.url}`, {
|
|
67
81
|
kind: 1,
|
|
@@ -94,7 +108,7 @@ async function tracingPlugin(fastify, options = {}) {
|
|
|
94
108
|
else span.setStatus({ code: SpanStatusCode.OK });
|
|
95
109
|
span.end();
|
|
96
110
|
});
|
|
97
|
-
fastify.addHook("onError", async (request,
|
|
111
|
+
fastify.addHook("onError", async (request, _reply, error) => {
|
|
98
112
|
if (!request.tracer?.currentSpan) return;
|
|
99
113
|
const span = request.tracer.currentSpan;
|
|
100
114
|
span.recordException(error);
|
|
@@ -119,7 +133,7 @@ async function tracingPlugin(fastify, options = {}) {
|
|
|
119
133
|
* }
|
|
120
134
|
*/
|
|
121
135
|
function createSpan(request, name, fn, attributes) {
|
|
122
|
-
if (!
|
|
136
|
+
if (!request.tracer) return fn(null);
|
|
123
137
|
const { tracer, currentSpan } = request.tracer;
|
|
124
138
|
const span = tracer.startSpan(name, {
|
|
125
139
|
parent: currentSpan,
|
|
@@ -154,10 +168,10 @@ function createSpan(request, name, fn, attributes) {
|
|
|
154
168
|
* }
|
|
155
169
|
*/
|
|
156
170
|
function traced(spanName) {
|
|
157
|
-
return
|
|
171
|
+
return (target, propertyKey, descriptor) => {
|
|
158
172
|
const originalMethod = descriptor.value;
|
|
159
173
|
descriptor.value = async function(...args) {
|
|
160
|
-
const request = args.find((arg) => arg
|
|
174
|
+
const request = args.find((arg) => arg?.tracer);
|
|
161
175
|
if (!request?.tracer) return originalMethod.apply(this, args);
|
|
162
176
|
return createSpan(request, spanName || `${target.constructor.name}.${propertyKey}`, async (span) => {
|
|
163
177
|
if (span) {
|
|
@@ -180,6 +194,5 @@ var tracing_default = fp(tracingPlugin, {
|
|
|
180
194
|
name: "arc-tracing",
|
|
181
195
|
fastify: "5.x"
|
|
182
196
|
});
|
|
183
|
-
|
|
184
197
|
//#endregion
|
|
185
|
-
export { createSpan, isTracingAvailable, traced, tracing_default as tracingPlugin };
|
|
198
|
+
export { createSpan, isTracingAvailable, traced, tracing_default as tracingPlugin };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//#region src/
|
|
1
|
+
//#region src/utils/pluralize.ts
|
|
2
2
|
/**
|
|
3
3
|
* Lightweight English pluralization for the Arc CLI.
|
|
4
4
|
*
|
|
@@ -58,12 +58,12 @@ function pluralize(word) {
|
|
|
58
58
|
if (UNCOUNTABLES.has(lower)) return word;
|
|
59
59
|
if (IRREGULARS[lower]) {
|
|
60
60
|
const plural = IRREGULARS[lower];
|
|
61
|
-
return word[0] === word[0]
|
|
61
|
+
return word[0] === word[0]?.toUpperCase() ? plural.charAt(0).toUpperCase() + plural.slice(1) : plural;
|
|
62
62
|
}
|
|
63
|
-
if (lower.endsWith("fe")) return word.slice(0, -2)
|
|
64
|
-
if (lower.endsWith("f") && !lower.endsWith("ff") && !lower.endsWith("roof") && !lower.endsWith("chief") && !lower.endsWith("belief")) return word.slice(0, -1)
|
|
65
|
-
if (lower.endsWith("y") && !/[aeiou]y$/i.test(lower)) return word.slice(0, -1)
|
|
66
|
-
if (lower.endsWith("is")) return word.slice(0, -2)
|
|
63
|
+
if (lower.endsWith("fe")) return `${word.slice(0, -2)}ves`;
|
|
64
|
+
if (lower.endsWith("f") && !lower.endsWith("ff") && !lower.endsWith("roof") && !lower.endsWith("chief") && !lower.endsWith("belief")) return `${word.slice(0, -1)}ves`;
|
|
65
|
+
if (lower.endsWith("y") && !/[aeiou]y$/i.test(lower)) return `${word.slice(0, -1)}ies`;
|
|
66
|
+
if (lower.endsWith("is")) return `${word.slice(0, -2)}es`;
|
|
67
67
|
if (new Set([
|
|
68
68
|
"cactus",
|
|
69
69
|
"stimulus",
|
|
@@ -75,12 +75,11 @@ function pluralize(word) {
|
|
|
75
75
|
"alumnus",
|
|
76
76
|
"terminus",
|
|
77
77
|
"bacillus"
|
|
78
|
-
]).has(lower)) return word.slice(0, -2)
|
|
79
|
-
if (lower.endsWith("z") && !lower.endsWith("zz")) return word
|
|
80
|
-
if (/(?:s|sh|ch|x|zz)$/i.test(lower)) return word
|
|
81
|
-
if (lower.endsWith("o") && !/[aeiou]o$/i.test(lower)) return word
|
|
82
|
-
return word
|
|
78
|
+
]).has(lower)) return `${word.slice(0, -2)}i`;
|
|
79
|
+
if (lower.endsWith("z") && !lower.endsWith("zz")) return `${word}zes`;
|
|
80
|
+
if (/(?:s|sh|ch|x|zz)$/i.test(lower)) return `${word}es`;
|
|
81
|
+
if (lower.endsWith("o") && !/[aeiou]o$/i.test(lower)) return `${word}es`;
|
|
82
|
+
return `${word}s`;
|
|
83
83
|
}
|
|
84
|
-
|
|
85
84
|
//#endregion
|
|
86
|
-
export { pluralize as t };
|
|
85
|
+
export { pluralize as t };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as PermissionCheck } from "../types-
|
|
1
|
+
import { t as PermissionCheck } from "../types-BNUccdcf.mjs";
|
|
2
2
|
import { FastifyReply, FastifyRequest } from "fastify";
|
|
3
3
|
|
|
4
4
|
//#region src/policies/PolicyInterface.d.ts
|
|
@@ -317,7 +317,7 @@ interface AccessControlPolicyOptions {
|
|
|
317
317
|
* ```
|
|
318
318
|
*/
|
|
319
319
|
declare function createAccessControlPolicy(options: AccessControlPolicyOptions): PermissionCheck;
|
|
320
|
-
declare module
|
|
320
|
+
declare module "fastify" {
|
|
321
321
|
interface FastifyRequest {
|
|
322
322
|
policyResult?: PolicyResult;
|
|
323
323
|
}
|
package/dist/policies/index.mjs
CHANGED
|
@@ -1,82 +1,3 @@
|
|
|
1
|
-
//#region src/policies/PolicyInterface.ts
|
|
2
|
-
/**
|
|
3
|
-
* Create a PermissionCheck from access control statements.
|
|
4
|
-
*
|
|
5
|
-
* Maps Better Auth's statement-based access control model to Arc's
|
|
6
|
-
* PermissionCheck function, which can be used directly in resource permissions.
|
|
7
|
-
*
|
|
8
|
-
* The returned PermissionCheck:
|
|
9
|
-
* 1. Looks up the resource + action in the statements list
|
|
10
|
-
* 2. If no matching statement exists, denies access
|
|
11
|
-
* 3. If a matching statement exists and `checkPermission` is provided,
|
|
12
|
-
* calls it for dynamic verification (e.g., check org role)
|
|
13
|
-
* 4. If `checkPermission` is not provided, allows access based on static statements
|
|
14
|
-
*
|
|
15
|
-
* @example Static statements only
|
|
16
|
-
* ```typescript
|
|
17
|
-
* import { createAccessControlPolicy } from '@classytic/arc/policies';
|
|
18
|
-
*
|
|
19
|
-
* const editorPermissions = createAccessControlPolicy({
|
|
20
|
-
* statements: [
|
|
21
|
-
* { resource: 'product', action: ['create', 'update'] },
|
|
22
|
-
* { resource: 'order', action: ['read'] },
|
|
23
|
-
* ],
|
|
24
|
-
* });
|
|
25
|
-
*
|
|
26
|
-
* // Use in resource config
|
|
27
|
-
* defineResource({
|
|
28
|
-
* name: 'product',
|
|
29
|
-
* permissions: {
|
|
30
|
-
* create: editorPermissions,
|
|
31
|
-
* update: editorPermissions,
|
|
32
|
-
* },
|
|
33
|
-
* });
|
|
34
|
-
* ```
|
|
35
|
-
*
|
|
36
|
-
* @example With dynamic permission check (Better Auth org roles)
|
|
37
|
-
* ```typescript
|
|
38
|
-
* const policy = createAccessControlPolicy({
|
|
39
|
-
* statements: [
|
|
40
|
-
* { resource: 'product', action: ['create', 'update'] },
|
|
41
|
-
* { resource: 'order', action: ['read'] },
|
|
42
|
-
* ],
|
|
43
|
-
* checkPermission: async (userId, resource, action) => {
|
|
44
|
-
* return hasOrgPermission(userId, resource, action);
|
|
45
|
-
* },
|
|
46
|
-
* });
|
|
47
|
-
* ```
|
|
48
|
-
*/
|
|
49
|
-
function createAccessControlPolicy(options) {
|
|
50
|
-
const statementMap = /* @__PURE__ */ new Map();
|
|
51
|
-
for (const statement of options.statements) {
|
|
52
|
-
const existing = statementMap.get(statement.resource);
|
|
53
|
-
if (existing) for (const action of statement.action) existing.add(action);
|
|
54
|
-
else statementMap.set(statement.resource, new Set(statement.action));
|
|
55
|
-
}
|
|
56
|
-
const permissionCheck = async (context) => {
|
|
57
|
-
const { user, resource, action } = context;
|
|
58
|
-
const allowedActions = statementMap.get(resource);
|
|
59
|
-
if (!allowedActions || !allowedActions.has(action)) return {
|
|
60
|
-
granted: false,
|
|
61
|
-
reason: `Action '${action}' is not permitted on resource '${resource}'`
|
|
62
|
-
};
|
|
63
|
-
if (options.checkPermission) {
|
|
64
|
-
const userId = user?.id ?? user?._id;
|
|
65
|
-
if (!userId) return {
|
|
66
|
-
granted: false,
|
|
67
|
-
reason: "Authentication required"
|
|
68
|
-
};
|
|
69
|
-
if (!await options.checkPermission(String(userId), resource, action)) return {
|
|
70
|
-
granted: false,
|
|
71
|
-
reason: `User does not have '${action}' permission on '${resource}'`
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
|
-
return { granted: true };
|
|
75
|
-
};
|
|
76
|
-
return permissionCheck;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
//#endregion
|
|
80
1
|
//#region src/policies/helpers.ts
|
|
81
2
|
/**
|
|
82
3
|
* Helper to create Fastify middleware from any PolicyEngine implementation
|
|
@@ -170,7 +91,7 @@ function combinePolicies(...policies) {
|
|
|
170
91
|
const allExcludes = /* @__PURE__ */ new Set();
|
|
171
92
|
const allIncludes = [];
|
|
172
93
|
for (const result of results) {
|
|
173
|
-
if (result.fieldMask?.exclude) result.fieldMask.exclude
|
|
94
|
+
if (result.fieldMask?.exclude) for (const field of result.fieldMask.exclude) allExcludes.add(field);
|
|
174
95
|
if (result.fieldMask?.include) allIncludes.push(new Set(result.fieldMask.include));
|
|
175
96
|
}
|
|
176
97
|
if (allExcludes.size > 0 || allIncludes.length > 0) {
|
|
@@ -306,7 +227,7 @@ function denyAll(reason = "Operation not allowed") {
|
|
|
306
227
|
};
|
|
307
228
|
},
|
|
308
229
|
toMiddleware() {
|
|
309
|
-
return async (
|
|
230
|
+
return async (_request, reply) => {
|
|
310
231
|
return reply.code(403).send({
|
|
311
232
|
success: false,
|
|
312
233
|
error: "Access denied",
|
|
@@ -316,6 +237,82 @@ function denyAll(reason = "Operation not allowed") {
|
|
|
316
237
|
}
|
|
317
238
|
};
|
|
318
239
|
}
|
|
319
|
-
|
|
320
240
|
//#endregion
|
|
321
|
-
|
|
241
|
+
//#region src/policies/PolicyInterface.ts
|
|
242
|
+
/**
|
|
243
|
+
* Create a PermissionCheck from access control statements.
|
|
244
|
+
*
|
|
245
|
+
* Maps Better Auth's statement-based access control model to Arc's
|
|
246
|
+
* PermissionCheck function, which can be used directly in resource permissions.
|
|
247
|
+
*
|
|
248
|
+
* The returned PermissionCheck:
|
|
249
|
+
* 1. Looks up the resource + action in the statements list
|
|
250
|
+
* 2. If no matching statement exists, denies access
|
|
251
|
+
* 3. If a matching statement exists and `checkPermission` is provided,
|
|
252
|
+
* calls it for dynamic verification (e.g., check org role)
|
|
253
|
+
* 4. If `checkPermission` is not provided, allows access based on static statements
|
|
254
|
+
*
|
|
255
|
+
* @example Static statements only
|
|
256
|
+
* ```typescript
|
|
257
|
+
* import { createAccessControlPolicy } from '@classytic/arc/policies';
|
|
258
|
+
*
|
|
259
|
+
* const editorPermissions = createAccessControlPolicy({
|
|
260
|
+
* statements: [
|
|
261
|
+
* { resource: 'product', action: ['create', 'update'] },
|
|
262
|
+
* { resource: 'order', action: ['read'] },
|
|
263
|
+
* ],
|
|
264
|
+
* });
|
|
265
|
+
*
|
|
266
|
+
* // Use in resource config
|
|
267
|
+
* defineResource({
|
|
268
|
+
* name: 'product',
|
|
269
|
+
* permissions: {
|
|
270
|
+
* create: editorPermissions,
|
|
271
|
+
* update: editorPermissions,
|
|
272
|
+
* },
|
|
273
|
+
* });
|
|
274
|
+
* ```
|
|
275
|
+
*
|
|
276
|
+
* @example With dynamic permission check (Better Auth org roles)
|
|
277
|
+
* ```typescript
|
|
278
|
+
* const policy = createAccessControlPolicy({
|
|
279
|
+
* statements: [
|
|
280
|
+
* { resource: 'product', action: ['create', 'update'] },
|
|
281
|
+
* { resource: 'order', action: ['read'] },
|
|
282
|
+
* ],
|
|
283
|
+
* checkPermission: async (userId, resource, action) => {
|
|
284
|
+
* return hasOrgPermission(userId, resource, action);
|
|
285
|
+
* },
|
|
286
|
+
* });
|
|
287
|
+
* ```
|
|
288
|
+
*/
|
|
289
|
+
function createAccessControlPolicy(options) {
|
|
290
|
+
const statementMap = /* @__PURE__ */ new Map();
|
|
291
|
+
for (const statement of options.statements) {
|
|
292
|
+
const existing = statementMap.get(statement.resource);
|
|
293
|
+
if (existing) for (const action of statement.action) existing.add(action);
|
|
294
|
+
else statementMap.set(statement.resource, new Set(statement.action));
|
|
295
|
+
}
|
|
296
|
+
const permissionCheck = async (context) => {
|
|
297
|
+
const { user, resource, action } = context;
|
|
298
|
+
if (!statementMap.get(resource)?.has(action)) return {
|
|
299
|
+
granted: false,
|
|
300
|
+
reason: `Action '${action}' is not permitted on resource '${resource}'`
|
|
301
|
+
};
|
|
302
|
+
if (options.checkPermission) {
|
|
303
|
+
const userId = user?.id ?? user?._id;
|
|
304
|
+
if (!userId) return {
|
|
305
|
+
granted: false,
|
|
306
|
+
reason: "Authentication required"
|
|
307
|
+
};
|
|
308
|
+
if (!await options.checkPermission(String(userId), resource, action)) return {
|
|
309
|
+
granted: false,
|
|
310
|
+
reason: `User does not have '${action}' permission on '${resource}'`
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
return { granted: true };
|
|
314
|
+
};
|
|
315
|
+
return permissionCheck;
|
|
316
|
+
}
|
|
317
|
+
//#endregion
|
|
318
|
+
export { allowAll, anyPolicy, combinePolicies, createAccessControlPolicy, createPolicyMiddleware, denyAll };
|
package/dist/presets/index.d.mts
CHANGED
|
@@ -1,29 +1,20 @@
|
|
|
1
|
-
import "../
|
|
2
|
-
import { b as IControllerResponse, k as PaginatedResult, x as IRequestContext } from "../interface-DZYNK9bb.mjs";
|
|
3
|
-
import "../types-RLkFVgaw.mjs";
|
|
4
|
-
import { AnyRecord, PresetResult, ResourceConfig } from "../types/index.mjs";
|
|
1
|
+
import { Mt as IControllerResponse, Nt as IRequestContext, Vt as PaginatedResult, Y as PresetResult, it as ResourceConfig, l as AnyRecord } from "../interface-DGmPxakH.mjs";
|
|
5
2
|
import multiTenantPreset, { MultiTenantOptions } from "./multiTenant.mjs";
|
|
6
3
|
|
|
7
|
-
//#region src/presets/softDelete.d.ts
|
|
8
|
-
declare function softDeletePreset(): PresetResult;
|
|
9
|
-
//#endregion
|
|
10
|
-
//#region src/presets/slugLookup.d.ts
|
|
11
|
-
interface SlugLookupOptions {
|
|
12
|
-
slugField?: string;
|
|
13
|
-
}
|
|
14
|
-
declare function slugLookupPreset(options?: SlugLookupOptions): PresetResult;
|
|
15
|
-
//#endregion
|
|
16
4
|
//#region src/presets/ownedByUser.d.ts
|
|
17
5
|
interface OwnedByUserOptions {
|
|
18
6
|
ownerField?: string;
|
|
19
7
|
}
|
|
20
8
|
declare function ownedByUserPreset(options?: OwnedByUserOptions): PresetResult;
|
|
21
9
|
//#endregion
|
|
22
|
-
//#region src/presets/
|
|
23
|
-
interface
|
|
24
|
-
|
|
10
|
+
//#region src/presets/slugLookup.d.ts
|
|
11
|
+
interface SlugLookupOptions {
|
|
12
|
+
slugField?: string;
|
|
25
13
|
}
|
|
26
|
-
declare function
|
|
14
|
+
declare function slugLookupPreset(options?: SlugLookupOptions): PresetResult;
|
|
15
|
+
//#endregion
|
|
16
|
+
//#region src/presets/softDelete.d.ts
|
|
17
|
+
declare function softDeletePreset(): PresetResult;
|
|
27
18
|
//#endregion
|
|
28
19
|
//#region src/presets/audited.d.ts
|
|
29
20
|
interface AuditedPresetOptions {
|
|
@@ -37,6 +28,22 @@ interface AuditedPresetOptions {
|
|
|
37
28
|
*/
|
|
38
29
|
declare function auditedPreset(options?: AuditedPresetOptions): PresetResult;
|
|
39
30
|
//#endregion
|
|
31
|
+
//#region src/presets/bulk.d.ts
|
|
32
|
+
type BulkOperation = "createMany" | "updateMany" | "deleteMany";
|
|
33
|
+
interface BulkPresetOptions {
|
|
34
|
+
/** Which bulk operations to enable (default: all three) */
|
|
35
|
+
operations?: BulkOperation[];
|
|
36
|
+
/** Max items per bulk create (default: 1000) */
|
|
37
|
+
maxCreateItems?: number;
|
|
38
|
+
}
|
|
39
|
+
declare function bulkPreset(opts?: BulkPresetOptions): PresetResult;
|
|
40
|
+
//#endregion
|
|
41
|
+
//#region src/presets/tree.d.ts
|
|
42
|
+
interface TreeOptions {
|
|
43
|
+
parentField?: string;
|
|
44
|
+
}
|
|
45
|
+
declare function treePreset(options?: TreeOptions): PresetResult;
|
|
46
|
+
//#endregion
|
|
40
47
|
//#region src/presets/types.d.ts
|
|
41
48
|
/**
|
|
42
49
|
* Soft Delete Preset Interface
|
|
@@ -228,7 +235,7 @@ type IAuditedPreset = never;
|
|
|
228
235
|
* }
|
|
229
236
|
* ```
|
|
230
237
|
*/
|
|
231
|
-
type IPresetController<TDoc = unknown, TPresets extends
|
|
238
|
+
type IPresetController<TDoc = unknown, TPresets extends "softDelete" | "slugLookup" | "tree" | never = never> = TPresets extends "softDelete" ? ISoftDeleteController<TDoc> : TPresets extends "slugLookup" ? ISlugLookupController<TDoc> : TPresets extends "tree" ? ITreeController<TDoc> : unknown;
|
|
232
239
|
//#endregion
|
|
233
240
|
//#region src/presets/index.d.ts
|
|
234
241
|
/**
|
|
@@ -264,4 +271,4 @@ type PresetInput = string | PresetResult | {
|
|
|
264
271
|
*/
|
|
265
272
|
declare function applyPresets<TDoc = AnyRecord>(config: ResourceConfig<TDoc>, presets?: PresetInput[]): ResourceConfig<TDoc>;
|
|
266
273
|
//#endregion
|
|
267
|
-
export { type AuditedPresetOptions, type IAuditedPreset, type IMultiTenantPreset, type IOwnedByUserPreset, type IPresetController, type ISlugLookupController, type ISoftDeleteController, type ITreeController, type MultiTenantOptions, type OwnedByUserOptions, type SlugLookupOptions, type TreeOptions, applyPresets, auditedPreset, flexibleMultiTenantPreset, getAvailablePresets, getPreset, multiTenantPreset, ownedByUserPreset, registerPreset, slugLookupPreset, softDeletePreset, treePreset };
|
|
274
|
+
export { type AuditedPresetOptions, type BulkOperation, type BulkPresetOptions, type IAuditedPreset, type IMultiTenantPreset, type IOwnedByUserPreset, type IPresetController, type ISlugLookupController, type ISoftDeleteController, type ITreeController, type MultiTenantOptions, type OwnedByUserOptions, type SlugLookupOptions, type TreeOptions, applyPresets, auditedPreset, bulkPreset, flexibleMultiTenantPreset, getAvailablePresets, getPreset, multiTenantPreset, ownedByUserPreset, registerPreset, slugLookupPreset, softDeletePreset, treePreset };
|
package/dist/presets/index.mjs
CHANGED
|
@@ -1,143 +1,3 @@
|
|
|
1
|
-
import { a as softDeletePreset, i as slugLookupPreset, n as treePreset, r as ownedByUserPreset, t as auditedPreset } from "../audited-CGdLiSlE.mjs";
|
|
2
1
|
import multiTenantPreset from "./multiTenant.mjs";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Convenience alias for multiTenantPreset with public list/get routes
|
|
7
|
-
* Equivalent to: multiTenantPreset({ allowPublic: ['list', 'get'] })
|
|
8
|
-
*/
|
|
9
|
-
const flexibleMultiTenantPreset = (options = {}) => multiTenantPreset({
|
|
10
|
-
...options,
|
|
11
|
-
allowPublic: ["list", "get"]
|
|
12
|
-
});
|
|
13
|
-
const presetRegistry = {
|
|
14
|
-
softDelete: softDeletePreset,
|
|
15
|
-
slugLookup: slugLookupPreset,
|
|
16
|
-
ownedByUser: ownedByUserPreset,
|
|
17
|
-
multiTenant: multiTenantPreset,
|
|
18
|
-
tree: treePreset,
|
|
19
|
-
audited: auditedPreset
|
|
20
|
-
};
|
|
21
|
-
/**
|
|
22
|
-
* Get preset by name with options
|
|
23
|
-
*/
|
|
24
|
-
function getPreset(nameOrConfig) {
|
|
25
|
-
if (typeof nameOrConfig === "object" && nameOrConfig.name) {
|
|
26
|
-
const { name, ...options } = nameOrConfig;
|
|
27
|
-
return resolvePreset(name, options);
|
|
28
|
-
}
|
|
29
|
-
return resolvePreset(nameOrConfig);
|
|
30
|
-
}
|
|
31
|
-
/**
|
|
32
|
-
* Resolve preset by name
|
|
33
|
-
*/
|
|
34
|
-
function resolvePreset(name, options = {}) {
|
|
35
|
-
const factory = presetRegistry[name];
|
|
36
|
-
if (!factory) {
|
|
37
|
-
const available = Object.keys(presetRegistry).join(", ");
|
|
38
|
-
throw new Error(`Unknown preset: '${name}'\nAvailable presets: ${available}\nDocs: https://github.com/classytic/arc#presets`);
|
|
39
|
-
}
|
|
40
|
-
return factory(options);
|
|
41
|
-
}
|
|
42
|
-
/**
|
|
43
|
-
* Register a custom preset
|
|
44
|
-
*/
|
|
45
|
-
function registerPreset(name, factory, options) {
|
|
46
|
-
if (presetRegistry[name] && !options?.override) throw new Error(`Preset '${name}' already exists. Pass { override: true } to replace.`);
|
|
47
|
-
presetRegistry[name] = factory;
|
|
48
|
-
}
|
|
49
|
-
/**
|
|
50
|
-
* Get all available preset names
|
|
51
|
-
*/
|
|
52
|
-
function getAvailablePresets() {
|
|
53
|
-
return Object.keys(presetRegistry);
|
|
54
|
-
}
|
|
55
|
-
/**
|
|
56
|
-
* Validate that preset combinations don't conflict.
|
|
57
|
-
* Detects route collisions (same method + path from different presets).
|
|
58
|
-
*/
|
|
59
|
-
function validatePresetCombination(presets) {
|
|
60
|
-
const conflicts = [];
|
|
61
|
-
const routeMap = /* @__PURE__ */ new Map();
|
|
62
|
-
for (const preset of presets) {
|
|
63
|
-
const name = preset.name ?? "unknown";
|
|
64
|
-
const routes = typeof preset.additionalRoutes === "function" ? preset.additionalRoutes({}) : preset.additionalRoutes ?? [];
|
|
65
|
-
for (const route of routes) {
|
|
66
|
-
const key = `${route.method} ${route.path}`;
|
|
67
|
-
const existing = routeMap.get(key);
|
|
68
|
-
if (existing) conflicts.push({
|
|
69
|
-
presets: [existing, name],
|
|
70
|
-
message: `Both '${existing}' and '${name}' define route ${key}`,
|
|
71
|
-
severity: "error"
|
|
72
|
-
});
|
|
73
|
-
routeMap.set(key, name);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
return conflicts;
|
|
77
|
-
}
|
|
78
|
-
/**
|
|
79
|
-
* Apply presets to resource config.
|
|
80
|
-
* Validates preset combinations for conflicts before merging.
|
|
81
|
-
*/
|
|
82
|
-
function applyPresets(config, presets = []) {
|
|
83
|
-
let result = { ...config };
|
|
84
|
-
const resolved = presets.map(resolvePresetInput);
|
|
85
|
-
const errors = validatePresetCombination(resolved).filter((c) => c.severity === "error");
|
|
86
|
-
if (errors.length > 0) throw new Error(`[Arc] Resource '${config.name}' preset conflicts:\n` + errors.map((c) => ` - ${c.message}`).join("\n"));
|
|
87
|
-
for (const preset of resolved) result = mergePreset(result, preset);
|
|
88
|
-
return result;
|
|
89
|
-
}
|
|
90
|
-
/**
|
|
91
|
-
* Resolve preset input to PresetResult
|
|
92
|
-
*/
|
|
93
|
-
function resolvePresetInput(preset) {
|
|
94
|
-
if (typeof preset === "object" && ("middlewares" in preset || "additionalRoutes" in preset)) return preset;
|
|
95
|
-
if (typeof preset === "object" && "name" in preset) {
|
|
96
|
-
const { name, ...options } = preset;
|
|
97
|
-
return resolvePreset(name, options);
|
|
98
|
-
}
|
|
99
|
-
return resolvePreset(preset);
|
|
100
|
-
}
|
|
101
|
-
/**
|
|
102
|
-
* Merge preset into config
|
|
103
|
-
*/
|
|
104
|
-
function mergePreset(config, preset) {
|
|
105
|
-
const result = { ...config };
|
|
106
|
-
if (preset.additionalRoutes) {
|
|
107
|
-
const routes = typeof preset.additionalRoutes === "function" ? preset.additionalRoutes(config.permissions ?? {}) : preset.additionalRoutes;
|
|
108
|
-
result.additionalRoutes = [...result.additionalRoutes ?? [], ...routes];
|
|
109
|
-
}
|
|
110
|
-
if (preset.middlewares) {
|
|
111
|
-
result.middlewares = result.middlewares ?? {};
|
|
112
|
-
for (const [op, mws] of Object.entries(preset.middlewares)) {
|
|
113
|
-
const key = op;
|
|
114
|
-
result.middlewares[key] = [...result.middlewares[key] ?? [], ...mws ?? []];
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
if (preset.schemaOptions) result.schemaOptions = {
|
|
118
|
-
...result.schemaOptions,
|
|
119
|
-
...preset.schemaOptions,
|
|
120
|
-
fieldRules: {
|
|
121
|
-
...result.schemaOptions?.fieldRules,
|
|
122
|
-
...preset.schemaOptions?.fieldRules
|
|
123
|
-
}
|
|
124
|
-
};
|
|
125
|
-
if (preset.controllerOptions) result._controllerOptions = {
|
|
126
|
-
...result._controllerOptions,
|
|
127
|
-
...preset.controllerOptions
|
|
128
|
-
};
|
|
129
|
-
if (preset.hooks && preset.hooks.length > 0) {
|
|
130
|
-
result._hooks = result._hooks ?? [];
|
|
131
|
-
for (const hook of preset.hooks) result._hooks.push({
|
|
132
|
-
presetName: preset.name,
|
|
133
|
-
operation: hook.operation,
|
|
134
|
-
phase: hook.phase,
|
|
135
|
-
handler: hook.handler,
|
|
136
|
-
priority: hook.priority
|
|
137
|
-
});
|
|
138
|
-
}
|
|
139
|
-
return result;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
//#endregion
|
|
143
|
-
export { applyPresets, auditedPreset, flexibleMultiTenantPreset, getAvailablePresets, getPreset, multiTenantPreset, ownedByUserPreset, registerPreset, slugLookupPreset, softDeletePreset, treePreset };
|
|
2
|
+
import { a as registerPreset, c as auditedPreset, d as ownedByUserPreset, i as getPreset, l as softDeletePreset, n as flexibleMultiTenantPreset, o as treePreset, r as getAvailablePresets, s as bulkPreset, t as applyPresets, u as slugLookupPreset } from "../presets-C9QXJV1u.mjs";
|
|
3
|
+
export { applyPresets, auditedPreset, bulkPreset, flexibleMultiTenantPreset, getAvailablePresets, getPreset, multiTenantPreset, ownedByUserPreset, registerPreset, slugLookupPreset, softDeletePreset, treePreset };
|
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
import "../
|
|
2
|
-
import "../interface-DZYNK9bb.mjs";
|
|
3
|
-
import "../types-RLkFVgaw.mjs";
|
|
4
|
-
import { CrudRouteKey, PresetResult } from "../types/index.mjs";
|
|
1
|
+
import { Y as PresetResult, b as CrudRouteKey } from "../interface-DGmPxakH.mjs";
|
|
5
2
|
|
|
6
3
|
//#region src/presets/multiTenant.d.ts
|
|
7
4
|
interface MultiTenantOptions {
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { o as DEFAULT_TENANT_FIELD } from "../constants-
|
|
2
|
-
import {
|
|
3
|
-
|
|
1
|
+
import { o as DEFAULT_TENANT_FIELD } from "../constants-Cxde4rpC.mjs";
|
|
2
|
+
import { d as isMember, n as PUBLIC_SCOPE, r as getOrgId, u as isElevated } from "../types-C6TQjtdi.mjs";
|
|
4
3
|
//#region src/presets/multiTenant.ts
|
|
5
4
|
/** Read request.scope safely */
|
|
6
5
|
function getScope(request) {
|
|
@@ -51,7 +50,7 @@ function createTenantFilter(tenantField) {
|
|
|
51
50
|
* Org context present = require auth and apply filter
|
|
52
51
|
*/
|
|
53
52
|
function createFlexibleTenantFilter(tenantField) {
|
|
54
|
-
return async (request,
|
|
53
|
+
return async (request, _reply) => {
|
|
55
54
|
const scope = getScope(request);
|
|
56
55
|
if (isElevated(scope)) {
|
|
57
56
|
const orgId = getOrgId(scope);
|
|
@@ -108,6 +107,5 @@ function multiTenantPreset(options = {}) {
|
|
|
108
107
|
}
|
|
109
108
|
};
|
|
110
109
|
}
|
|
111
|
-
|
|
112
110
|
//#endregion
|
|
113
|
-
export { multiTenantPreset as default, multiTenantPreset };
|
|
111
|
+
export { multiTenantPreset as default, multiTenantPreset };
|