@_linked/server 2.20.0 → 2.21.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 +30 -0
- package/lib/esm/backend.js +70 -11
- package/lib/esm/backend.js.map +1 -1
- package/lib/esm/shapes/LincdAPI.js +40 -11
- package/lib/esm/shapes/LincdAPI.js.map +1 -1
- package/lib/esm/shapes/LinkedServer.d.ts +48 -4
- package/lib/esm/shapes/LinkedServer.js +228 -86
- package/lib/esm/shapes/LinkedServer.js.map +1 -1
- package/lib/esm/shapes/filestores/LocalFileStore.d.ts +16 -0
- package/lib/esm/shapes/filestores/LocalFileStore.js +77 -8
- package/lib/esm/shapes/filestores/LocalFileStore.js.map +1 -1
- package/lib/esm/utils/jsonBodyParser.js +16 -1
- package/lib/esm/utils/jsonBodyParser.js.map +1 -1
- package/lib/esm/utils/queryPlane.d.ts +58 -0
- package/lib/esm/utils/queryPlane.js +206 -0
- package/lib/esm/utils/queryPlane.js.map +1 -0
- package/lib/esm/utils/rpcExposure.d.ts +53 -0
- package/lib/esm/utils/rpcExposure.js +199 -0
- package/lib/esm/utils/rpcExposure.js.map +1 -0
- package/package.json +5 -3
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which provider methods a `/call/...` request may reach.
|
|
3
|
+
*
|
|
4
|
+
* A provider method is dispatched over HTTP when it is declared with
|
|
5
|
+
* `@callable(level)` (or `declareCallable`) from
|
|
6
|
+
* `@_linked/server-utils/utils/callable`. TypeScript `private`/`protected` do not
|
|
7
|
+
* exist at runtime, so without a declaration every method of a provider would be
|
|
8
|
+
* reachable, including everything it inherits.
|
|
9
|
+
*
|
|
10
|
+
* The class that owns the method (the first prototype on the chain that has it)
|
|
11
|
+
* decides. When that class declares it, its own level applies. When it does not
|
|
12
|
+
* (an override of an inherited method), the method keeps the strictest level
|
|
13
|
+
* declared for it by any class up the chain (`'user'` over `'public'`), so an
|
|
14
|
+
* override never loses a session requirement. Only when no class declares it is
|
|
15
|
+
* the method undeclared.
|
|
16
|
+
*
|
|
17
|
+
* - **reserved** — never dispatched, in any mode: `Object.prototype` members,
|
|
18
|
+
* every `BackendProvider`/`ShapeProvider` method (`initRequest`,
|
|
19
|
+
* `registerRoute`, lifecycle hooks, ...), even when a subclass overrides it,
|
|
20
|
+
* `dispose` (a lifecycle hook the server calls on reload, which the base
|
|
21
|
+
* class does not define), `constructor`, `__proto__`, and anything that is not
|
|
22
|
+
* a plain function (accessors, fields). Answered 501, exactly like a missing
|
|
23
|
+
* method.
|
|
24
|
+
* - **internal** — declared with `@internal()` / `declareInternal` by the
|
|
25
|
+
* provider's class or one of its super classes (an app can declare it on a
|
|
26
|
+
* class it imports). Never dispatched over HTTP, in any mode (501), even when
|
|
27
|
+
* it is also declared callable. Backend-to-backend calls still reach it.
|
|
28
|
+
* - **undeclared** — no class on the chain declares it. In `warn` mode it runs
|
|
29
|
+
* and is logged once; in `enforce` mode it is 501.
|
|
30
|
+
* - **callable** — declared (directly or inherited, see above). A `'user'`
|
|
31
|
+
* method answers 401 without a session.
|
|
32
|
+
*
|
|
33
|
+
* Backend-to-backend calls (`Server.call` on the server) skip everything except
|
|
34
|
+
* the reserved names, internal methods included.
|
|
35
|
+
*/
|
|
36
|
+
import { BackendProvider } from '@_linked/server-utils/utils/BackendProvider';
|
|
37
|
+
import { ShapeProvider } from '@_linked/server-utils/utils/ShapeProvider';
|
|
38
|
+
import { getOwnCallableLevel, isDeclaredInternal, } from '@_linked/server-utils/utils/callable';
|
|
39
|
+
function ownNames(proto) {
|
|
40
|
+
return proto ? Object.getOwnPropertyNames(proto) : [];
|
|
41
|
+
}
|
|
42
|
+
let reservedNames;
|
|
43
|
+
/**
|
|
44
|
+
* Names no RPC may dispatch to. Computed on first use (not at import), so the
|
|
45
|
+
* base classes are fully defined, and including every method they have.
|
|
46
|
+
*/
|
|
47
|
+
export function getReservedNames() {
|
|
48
|
+
if (!reservedNames) {
|
|
49
|
+
reservedNames = new Set([
|
|
50
|
+
...ownNames(Object.prototype),
|
|
51
|
+
...ownNames(BackendProvider.prototype),
|
|
52
|
+
...ownNames(ShapeProvider.prototype),
|
|
53
|
+
// called by the server when it reloads providers; no base class defines it
|
|
54
|
+
'dispose',
|
|
55
|
+
'constructor',
|
|
56
|
+
'__proto__',
|
|
57
|
+
]);
|
|
58
|
+
}
|
|
59
|
+
return reservedNames;
|
|
60
|
+
}
|
|
61
|
+
function isBaseClassPrototype(obj) {
|
|
62
|
+
return (obj === Object.prototype ||
|
|
63
|
+
obj === BackendProvider.prototype ||
|
|
64
|
+
obj === ShapeProvider.prototype);
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Decide whether `method` of `provider` may be dispatched.
|
|
68
|
+
*
|
|
69
|
+
* Walks the prototype chain with property descriptors only, so no getter ever
|
|
70
|
+
* runs. The first object that owns the name decides: if it is a class
|
|
71
|
+
* prototype, that class's own declaration; if it is the instance itself (a
|
|
72
|
+
* method assigned onto the instance), there is no declaration.
|
|
73
|
+
*/
|
|
74
|
+
export function resolveCallable(provider, method) {
|
|
75
|
+
if (typeof method !== 'string' || !method)
|
|
76
|
+
return { status: 'missing' };
|
|
77
|
+
if (provider == null || (typeof provider !== 'object' && typeof provider !== 'function')) {
|
|
78
|
+
return { status: 'missing' };
|
|
79
|
+
}
|
|
80
|
+
const reserved = getReservedNames();
|
|
81
|
+
let obj = provider;
|
|
82
|
+
let descriptor;
|
|
83
|
+
let holder;
|
|
84
|
+
while (obj) {
|
|
85
|
+
descriptor = Object.getOwnPropertyDescriptor(obj, method);
|
|
86
|
+
if (descriptor) {
|
|
87
|
+
holder = obj;
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
90
|
+
obj = Object.getPrototypeOf(obj);
|
|
91
|
+
}
|
|
92
|
+
if (!descriptor) {
|
|
93
|
+
return reserved.has(method) ? { status: 'reserved' } : { status: 'missing' };
|
|
94
|
+
}
|
|
95
|
+
if (reserved.has(method) || isBaseClassPrototype(holder)) {
|
|
96
|
+
return { status: 'reserved' };
|
|
97
|
+
}
|
|
98
|
+
if (!('value' in descriptor) || typeof descriptor.value !== 'function') {
|
|
99
|
+
return { status: 'reserved' };
|
|
100
|
+
}
|
|
101
|
+
const holderCtor = Object.prototype.hasOwnProperty.call(holder, 'constructor')
|
|
102
|
+
? holder.constructor
|
|
103
|
+
: undefined;
|
|
104
|
+
const isClassPrototype = typeof holderCtor === 'function' && holderCtor.prototype === holder;
|
|
105
|
+
// Internal on the provider's class or any super class wins over everything
|
|
106
|
+
// else, a callable declaration included.
|
|
107
|
+
const providerCtor = typeof provider === 'function' ? undefined : provider.constructor;
|
|
108
|
+
if ((typeof providerCtor === 'function' && isDeclaredInternal(providerCtor, method)) ||
|
|
109
|
+
(isClassPrototype && isDeclaredInternal(holderCtor, method))) {
|
|
110
|
+
return { status: 'internal', owner: isClassPrototype ? holderCtor : providerCtor };
|
|
111
|
+
}
|
|
112
|
+
if (isClassPrototype) {
|
|
113
|
+
const own = getOwnCallableLevel(holderCtor, method);
|
|
114
|
+
if (own)
|
|
115
|
+
return { status: 'callable', level: own, owner: holderCtor };
|
|
116
|
+
}
|
|
117
|
+
// Not declared where it is defined (an override, or a method assigned onto
|
|
118
|
+
// the instance): the strictest level declared up the class chain applies.
|
|
119
|
+
const start = isClassPrototype ? holderCtor : providerCtor;
|
|
120
|
+
const inherited = strictestDeclaredLevel(start, method);
|
|
121
|
+
if (inherited) {
|
|
122
|
+
return { status: 'callable', level: inherited, owner: start, inherited: true };
|
|
123
|
+
}
|
|
124
|
+
return { status: 'undeclared', owner: start };
|
|
125
|
+
}
|
|
126
|
+
const STRICTNESS = { public: 0, user: 1 };
|
|
127
|
+
/** The strictest level any class from `cls` up declares for `method`. */
|
|
128
|
+
function strictestDeclaredLevel(cls, method) {
|
|
129
|
+
let level;
|
|
130
|
+
let c = cls;
|
|
131
|
+
while (typeof c === 'function' && c !== Function.prototype) {
|
|
132
|
+
const own = getOwnCallableLevel(c, method);
|
|
133
|
+
if (own && (level === undefined || STRICTNESS[own] > STRICTNESS[level]))
|
|
134
|
+
level = own;
|
|
135
|
+
c = Object.getPrototypeOf(c);
|
|
136
|
+
}
|
|
137
|
+
return level;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* A client-supplied name made safe for a log line: control characters escaped
|
|
141
|
+
* (no forged lines or terminal escapes), and cut to a bounded length.
|
|
142
|
+
*/
|
|
143
|
+
export function logSafe(value, max = 120) {
|
|
144
|
+
const text = String(value);
|
|
145
|
+
const escaped = text.replace(/[\u0000-\u001f\u007f-\u009f\u2028\u2029]/g, (ch) => '\\u' + ch.charCodeAt(0).toString(16).padStart(4, '0'));
|
|
146
|
+
return escaped.length > max ? escaped.slice(0, max) + '…' : escaped;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* True when the provider's own class opts out of the generic `/call/<pkg>/...`
|
|
150
|
+
* route with `static rpc = false`. Only the provider's own class counts.
|
|
151
|
+
*/
|
|
152
|
+
export function isGenericRpcDisabled(provider) {
|
|
153
|
+
const cls = provider === null || provider === void 0 ? void 0 : provider.constructor;
|
|
154
|
+
return (typeof cls === 'function' &&
|
|
155
|
+
Object.prototype.hasOwnProperty.call(cls, 'rpc') &&
|
|
156
|
+
cls.rpc === false);
|
|
157
|
+
}
|
|
158
|
+
let configuredMode;
|
|
159
|
+
function parseMode(value) {
|
|
160
|
+
return value === 'warn' || value === 'enforce' ? value : undefined;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Set from the server's config (`server.rpcExposure`). An invalid value is
|
|
164
|
+
* ignored with a warning, leaving the environment / default in place.
|
|
165
|
+
*/
|
|
166
|
+
export function setRpcExposureMode(mode) {
|
|
167
|
+
if (mode === undefined || mode === null) {
|
|
168
|
+
configuredMode = undefined;
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
const parsed = parseMode(mode);
|
|
172
|
+
if (!parsed) {
|
|
173
|
+
console.warn(`[linked] server.rpcExposure must be 'warn' or 'enforce', got ${JSON.stringify(mode)}; ignoring it`);
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
configuredMode = parsed;
|
|
177
|
+
}
|
|
178
|
+
/** `server.rpcExposure`, else `LINKED_RPC_EXPOSURE`, else `'warn'`. */
|
|
179
|
+
export function getRpcExposureMode() {
|
|
180
|
+
var _a;
|
|
181
|
+
return (_a = configuredMode !== null && configuredMode !== void 0 ? configuredMode : parseMode(process.env.LINKED_RPC_EXPOSURE)) !== null && _a !== void 0 ? _a : 'warn';
|
|
182
|
+
}
|
|
183
|
+
const warnedUndeclared = new Set();
|
|
184
|
+
/** Log an undeclared call once per package, class and method. */
|
|
185
|
+
export function warnUndeclaredCall(pkg, owner, method, hasSession) {
|
|
186
|
+
const className = (owner === null || owner === void 0 ? void 0 : owner.name) || 'anonymous provider';
|
|
187
|
+
const key = `${pkg}\u0000${className}\u0000${method}`;
|
|
188
|
+
if (warnedUndeclared.has(key))
|
|
189
|
+
return;
|
|
190
|
+
warnedUndeclared.add(key);
|
|
191
|
+
console.warn(`[linked] undeclared RPC ${logSafe(pkg)} ${className}.${logSafe(method)} (session: ${hasSession ? 'yes' : 'no'}). ` +
|
|
192
|
+
`Declare it with @callable('public' | 'user') if the client calls it; ` +
|
|
193
|
+
`undeclared methods are refused (501) once rpcExposure is 'enforce'.`);
|
|
194
|
+
}
|
|
195
|
+
/** For tests: forget which undeclared calls were already logged. */
|
|
196
|
+
export function resetUndeclaredWarnings() {
|
|
197
|
+
warnedUndeclared.clear();
|
|
198
|
+
}
|
|
199
|
+
//# sourceMappingURL=rpcExposure.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rpcExposure.js","sourceRoot":"","sources":["../../../src/utils/rpcExposure.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Which provider methods a `/call/...` request may reach.\n *\n * A provider method is dispatched over HTTP when it is declared with\n * `@callable(level)` (or `declareCallable`) from\n * `@_linked/server-utils/utils/callable`. TypeScript `private`/`protected` do not\n * exist at runtime, so without a declaration every method of a provider would be\n * reachable, including everything it inherits.\n *\n * The class that owns the method (the first prototype on the chain that has it)\n * decides. When that class declares it, its own level applies. When it does not\n * (an override of an inherited method), the method keeps the strictest level\n * declared for it by any class up the chain (`'user'` over `'public'`), so an\n * override never loses a session requirement. Only when no class declares it is\n * the method undeclared.\n *\n * - **reserved** — never dispatched, in any mode: `Object.prototype` members,\n * every `BackendProvider`/`ShapeProvider` method (`initRequest`,\n * `registerRoute`, lifecycle hooks, ...), even when a subclass overrides it,\n * `dispose` (a lifecycle hook the server calls on reload, which the base\n * class does not define), `constructor`, `__proto__`, and anything that is not\n * a plain function (accessors, fields). Answered 501, exactly like a missing\n * method.\n * - **internal** — declared with `@internal()` / `declareInternal` by the\n * provider's class or one of its super classes (an app can declare it on a\n * class it imports). Never dispatched over HTTP, in any mode (501), even when\n * it is also declared callable. Backend-to-backend calls still reach it.\n * - **undeclared** — no class on the chain declares it. In `warn` mode it runs\n * and is logged once; in `enforce` mode it is 501.\n * - **callable** — declared (directly or inherited, see above). A `'user'`\n * method answers 401 without a session.\n *\n * Backend-to-backend calls (`Server.call` on the server) skip everything except\n * the reserved names, internal methods included.\n */\nimport { BackendProvider } from '@_linked/server-utils/utils/BackendProvider';\nimport { ShapeProvider } from '@_linked/server-utils/utils/ShapeProvider';\nimport {\n getOwnCallableLevel,\n isDeclaredInternal,\n type CallableLevel,\n} from '@_linked/server-utils/utils/callable';\n\nexport type RpcExposureMode = 'warn' | 'enforce';\n\nexport type CallableResolution =\n | { status: 'callable'; level: CallableLevel; owner: Function; inherited?: boolean }\n | { status: 'undeclared'; owner: Function | undefined }\n | { status: 'internal'; owner: Function | undefined }\n | { status: 'reserved' }\n | { status: 'missing' };\n\nfunction ownNames(proto: object | undefined): string[] {\n return proto ? Object.getOwnPropertyNames(proto) : [];\n}\n\nlet reservedNames: Set<string> | undefined;\n\n/**\n * Names no RPC may dispatch to. Computed on first use (not at import), so the\n * base classes are fully defined, and including every method they have.\n */\nexport function getReservedNames(): ReadonlySet<string> {\n if (!reservedNames) {\n reservedNames = new Set([\n ...ownNames(Object.prototype),\n ...ownNames(BackendProvider.prototype),\n ...ownNames(ShapeProvider.prototype),\n // called by the server when it reloads providers; no base class defines it\n 'dispose',\n 'constructor',\n '__proto__',\n ]);\n }\n return reservedNames;\n}\n\nfunction isBaseClassPrototype(obj: object): boolean {\n return (\n obj === Object.prototype ||\n obj === BackendProvider.prototype ||\n obj === ShapeProvider.prototype\n );\n}\n\n/**\n * Decide whether `method` of `provider` may be dispatched.\n *\n * Walks the prototype chain with property descriptors only, so no getter ever\n * runs. The first object that owns the name decides: if it is a class\n * prototype, that class's own declaration; if it is the instance itself (a\n * method assigned onto the instance), there is no declaration.\n */\nexport function resolveCallable(provider: any, method: string): CallableResolution {\n if (typeof method !== 'string' || !method) return { status: 'missing' };\n if (provider == null || (typeof provider !== 'object' && typeof provider !== 'function')) {\n return { status: 'missing' };\n }\n const reserved = getReservedNames();\n let obj: any = provider;\n let descriptor: PropertyDescriptor | undefined;\n let holder: any;\n while (obj) {\n descriptor = Object.getOwnPropertyDescriptor(obj, method);\n if (descriptor) {\n holder = obj;\n break;\n }\n obj = Object.getPrototypeOf(obj);\n }\n if (!descriptor) {\n return reserved.has(method) ? { status: 'reserved' } : { status: 'missing' };\n }\n if (reserved.has(method) || isBaseClassPrototype(holder)) {\n return { status: 'reserved' };\n }\n if (!('value' in descriptor) || typeof descriptor.value !== 'function') {\n return { status: 'reserved' };\n }\n const holderCtor = Object.prototype.hasOwnProperty.call(holder, 'constructor')\n ? holder.constructor\n : undefined;\n const isClassPrototype =\n typeof holderCtor === 'function' && holderCtor.prototype === holder;\n // Internal on the provider's class or any super class wins over everything\n // else, a callable declaration included.\n const providerCtor = typeof provider === 'function' ? undefined : provider.constructor;\n if (\n (typeof providerCtor === 'function' && isDeclaredInternal(providerCtor, method)) ||\n (isClassPrototype && isDeclaredInternal(holderCtor, method))\n ) {\n return { status: 'internal', owner: isClassPrototype ? holderCtor : providerCtor };\n }\n if (isClassPrototype) {\n const own = getOwnCallableLevel(holderCtor, method);\n if (own) return { status: 'callable', level: own, owner: holderCtor };\n }\n // Not declared where it is defined (an override, or a method assigned onto\n // the instance): the strictest level declared up the class chain applies.\n const start = isClassPrototype ? holderCtor : providerCtor;\n const inherited = strictestDeclaredLevel(start, method);\n if (inherited) {\n return { status: 'callable', level: inherited, owner: start, inherited: true };\n }\n return { status: 'undeclared', owner: start };\n}\n\nconst STRICTNESS: Record<CallableLevel, number> = { public: 0, user: 1 };\n\n/** The strictest level any class from `cls` up declares for `method`. */\nfunction strictestDeclaredLevel(cls: unknown, method: string): CallableLevel | undefined {\n let level: CallableLevel | undefined;\n let c: any = cls;\n while (typeof c === 'function' && c !== Function.prototype) {\n const own = getOwnCallableLevel(c, method);\n if (own && (level === undefined || STRICTNESS[own] > STRICTNESS[level])) level = own;\n c = Object.getPrototypeOf(c);\n }\n return level;\n}\n\n/**\n * A client-supplied name made safe for a log line: control characters escaped\n * (no forged lines or terminal escapes), and cut to a bounded length.\n */\nexport function logSafe(value: unknown, max = 120): string {\n const text = String(value);\n const escaped = text.replace(/[\\u0000-\\u001f\\u007f-\\u009f\\u2028\\u2029]/g, (ch) =>\n '\\\\u' + ch.charCodeAt(0).toString(16).padStart(4, '0')\n );\n return escaped.length > max ? escaped.slice(0, max) + '…' : escaped;\n}\n\n/**\n * True when the provider's own class opts out of the generic `/call/<pkg>/...`\n * route with `static rpc = false`. Only the provider's own class counts.\n */\nexport function isGenericRpcDisabled(provider: any): boolean {\n const cls = provider?.constructor;\n return (\n typeof cls === 'function' &&\n Object.prototype.hasOwnProperty.call(cls, 'rpc') &&\n (cls as any).rpc === false\n );\n}\n\nlet configuredMode: RpcExposureMode | undefined;\n\nfunction parseMode(value: unknown): RpcExposureMode | undefined {\n return value === 'warn' || value === 'enforce' ? value : undefined;\n}\n\n/**\n * Set from the server's config (`server.rpcExposure`). An invalid value is\n * ignored with a warning, leaving the environment / default in place.\n */\nexport function setRpcExposureMode(mode: unknown): void {\n if (mode === undefined || mode === null) {\n configuredMode = undefined;\n return;\n }\n const parsed = parseMode(mode);\n if (!parsed) {\n console.warn(\n `[linked] server.rpcExposure must be 'warn' or 'enforce', got ${JSON.stringify(mode)}; ignoring it`\n );\n return;\n }\n configuredMode = parsed;\n}\n\n/** `server.rpcExposure`, else `LINKED_RPC_EXPOSURE`, else `'warn'`. */\nexport function getRpcExposureMode(): RpcExposureMode {\n return configuredMode ?? parseMode(process.env.LINKED_RPC_EXPOSURE) ?? 'warn';\n}\n\nconst warnedUndeclared = new Set<string>();\n\n/** Log an undeclared call once per package, class and method. */\nexport function warnUndeclaredCall(\n pkg: string,\n owner: Function | undefined,\n method: string,\n hasSession: boolean\n): void {\n const className = owner?.name || 'anonymous provider';\n const key = `${pkg}\\u0000${className}\\u0000${method}`;\n if (warnedUndeclared.has(key)) return;\n warnedUndeclared.add(key);\n console.warn(\n `[linked] undeclared RPC ${logSafe(pkg)} ${className}.${logSafe(method)} (session: ${hasSession ? 'yes' : 'no'}). ` +\n `Declare it with @callable('public' | 'user') if the client calls it; ` +\n `undeclared methods are refused (501) once rpcExposure is 'enforce'.`\n );\n}\n\n/** For tests: forget which undeclared calls were already logged. */\nexport function resetUndeclaredWarnings(): void {\n warnedUndeclared.clear();\n}\n"]}
|
package/package.json
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"type": "git",
|
|
11
11
|
"url": "git+https://github.com/linked-fw/server.git"
|
|
12
12
|
},
|
|
13
|
-
"version": "2.
|
|
13
|
+
"version": "2.21.0",
|
|
14
14
|
"packageManager": "npm@11.20.0",
|
|
15
15
|
"linkedPackage": true,
|
|
16
16
|
"type": "module",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"dependencies": {
|
|
38
38
|
"@_linked/cli": "^1.2",
|
|
39
39
|
"@_linked/core": "^2.22.8",
|
|
40
|
-
"@_linked/server-utils": "^1.
|
|
40
|
+
"@_linked/server-utils": "^1.9.0",
|
|
41
41
|
"autoprefixer": "^10.4.18",
|
|
42
42
|
"compression": "^1.7.4",
|
|
43
43
|
"cors": "^2.8.5",
|
|
@@ -96,8 +96,10 @@
|
|
|
96
96
|
"./backend",
|
|
97
97
|
"./shapes/LinkedServer",
|
|
98
98
|
"./shapes/filestores/*",
|
|
99
|
+
"./utils/queryPlane",
|
|
99
100
|
"./utils/releaseManifest",
|
|
100
|
-
"./utils/routeAssets"
|
|
101
|
+
"./utils/routeAssets",
|
|
102
|
+
"./utils/rpcExposure"
|
|
101
103
|
]
|
|
102
104
|
},
|
|
103
105
|
"publishConfig": {
|