@roostorg/types 1.0.49 → 1.1.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/package.json +3 -3
- package/transpiled/index.d.ts +2 -7
- package/transpiled/index.js +2 -11
- package/transpiled/integration.d.ts +267 -0
- package/transpiled/integration.js +48 -0
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@roostorg/types",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.
|
|
5
|
-
"description": "Shared types across
|
|
4
|
+
"version": "1.1.1",
|
|
5
|
+
"description": "Shared types across Coop services",
|
|
6
6
|
"module": "transpiled/index.js",
|
|
7
7
|
"typings": "./transpiled/index.d.ts",
|
|
8
8
|
"scripts": {
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"prepublishOnly": "npm run build",
|
|
11
11
|
"test": "npm run build && node --test transpiled/test_scripts/"
|
|
12
12
|
},
|
|
13
|
-
"author": "",
|
|
13
|
+
"author": "Roostorg",
|
|
14
14
|
"files": [
|
|
15
15
|
"transpiled"
|
|
16
16
|
],
|
package/transpiled/index.d.ts
CHANGED
|
@@ -98,9 +98,6 @@ export type FieldScalarType<T extends FieldType> = T extends ScalarType ? T : Sc
|
|
|
98
98
|
* ScalarTypes.GEOHASH are both represented as strings), so, without the label,
|
|
99
99
|
* we wouldn't know unambiguously which ScalarType a value belongs to, which we
|
|
100
100
|
* need sometimes (e.g., when deciding whether we can pass it to a signal).
|
|
101
|
-
*
|
|
102
|
-
* For why this type is written this way, see
|
|
103
|
-
* https://github.com/protegoapi/protego/pull/20#discussion_r883974513
|
|
104
101
|
*/
|
|
105
102
|
type EnumScalarType = ScalarTypes['STRING'] | ScalarTypes['NUMBER'];
|
|
106
103
|
export type TaggedScalar<T extends ScalarType> = {
|
|
@@ -159,9 +156,6 @@ export declare function isMediaValue<T extends ScalarType>(it: TaggedScalar<T>):
|
|
|
159
156
|
* legal values in the enum like.
|
|
160
157
|
*/
|
|
161
158
|
export declare function makeEnumLike<T extends string>(strings: readonly T[]): { [K in T]: K; };
|
|
162
|
-
export declare const hiveProjectNames: readonly ["Visual", "Text", "Speech", "OCR", "Demographic"];
|
|
163
|
-
export type HiveProjectName = (typeof hiveProjectNames)[number];
|
|
164
|
-
export declare function isHiveProjectName(name: string): name is HiveProjectName;
|
|
165
159
|
export type SignalSubcategory = {
|
|
166
160
|
id: string;
|
|
167
161
|
label: string;
|
|
@@ -196,4 +190,5 @@ export declare const ItemTypeKind: {
|
|
|
196
190
|
USER: "USER";
|
|
197
191
|
};
|
|
198
192
|
export type ItemTypeKind = keyof typeof ItemTypeKind;
|
|
199
|
-
export {};
|
|
193
|
+
export type { CoopIntegrationConfigEntry, CoopIntegrationPlugin, CoopIntegrationsConfig, IntegrationConfigField, IntegrationId, IntegrationManifest, ModelCard, ModelCardField, ModelCardSection, ModelCardSubsection, PluginSignalContext, PluginSignalDescriptor, StoredIntegrationConfigPayload, } from './integration.js';
|
|
194
|
+
export { assertModelCardHasRequiredSections, isCoopIntegrationPlugin, REQUIRED_MODEL_CARD_SECTION_IDS, } from './integration.js';
|
package/transpiled/index.js
CHANGED
|
@@ -3,7 +3,7 @@ import { isValid, parseJSON } from 'date-fns';
|
|
|
3
3
|
// type is probably not ideal.
|
|
4
4
|
//
|
|
5
5
|
// NB: the ID type refers to ids for any type of entity, but USER_ID should be
|
|
6
|
-
// used instead for fields that hold ids of users
|
|
6
|
+
// used instead for fields that hold ids of users coop might know about. E.g.,
|
|
7
7
|
// imagine a `Message` content type. Both the `to` and `from` fields could hold
|
|
8
8
|
// ScalarTypes.USER_IDs, and then a rule could flag the message if _either_ the
|
|
9
9
|
// sender or the recipient satisfies some condition (after passing the user id
|
|
@@ -84,16 +84,6 @@ export function isMediaValue(it) {
|
|
|
84
84
|
export function makeEnumLike(strings) {
|
|
85
85
|
return Object.fromEntries(strings.map((it) => [it, it]));
|
|
86
86
|
}
|
|
87
|
-
export const hiveProjectNames = [
|
|
88
|
-
'Visual',
|
|
89
|
-
'Text',
|
|
90
|
-
'Speech',
|
|
91
|
-
'OCR',
|
|
92
|
-
'Demographic',
|
|
93
|
-
];
|
|
94
|
-
export function isHiveProjectName(name) {
|
|
95
|
-
return hiveProjectNames.includes(name);
|
|
96
|
-
}
|
|
97
87
|
export function parseDateString(it) {
|
|
98
88
|
return new Date(it);
|
|
99
89
|
}
|
|
@@ -110,3 +100,4 @@ export function makeDateString(it) {
|
|
|
110
100
|
: undefined;
|
|
111
101
|
}
|
|
112
102
|
export const ItemTypeKind = makeEnumLike(['CONTENT', 'THREAD', 'USER']);
|
|
103
|
+
export { assertModelCardHasRequiredSections, isCoopIntegrationPlugin, REQUIRED_MODEL_CARD_SECTION_IDS, } from './integration.js';
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Integration plugin types for COOP.
|
|
3
|
+
*
|
|
4
|
+
* These types define the contract that third-party integration packages
|
|
5
|
+
* implement so adopters can install and configure them without adding
|
|
6
|
+
* every integration to the main COOP repo.
|
|
7
|
+
*
|
|
8
|
+
* Integration packages export a CoopIntegrationPlugin; adopters register
|
|
9
|
+
* them via an integrations config file (see CoopIntegrationsConfig).
|
|
10
|
+
*/
|
|
11
|
+
/** Unique identifier for the integration (e.g. "GOOGLE_CONTENT_SAFETY_API"). */
|
|
12
|
+
export type IntegrationId = string;
|
|
13
|
+
/**
|
|
14
|
+
* A single key-value row in a model card (e.g. "Release Date" -> "January 2026").
|
|
15
|
+
* Values are plain strings; the UI can linkify URLs or format as needed.
|
|
16
|
+
*/
|
|
17
|
+
export type ModelCardField = Readonly<{
|
|
18
|
+
label: string;
|
|
19
|
+
value: string;
|
|
20
|
+
}>;
|
|
21
|
+
/**
|
|
22
|
+
* A named group of fields within a section (e.g. "Basic Information" with
|
|
23
|
+
* Model Name, Version, Release Date). Rendered as a bold subheading + key-value list.
|
|
24
|
+
*/
|
|
25
|
+
export type ModelCardSubsection = Readonly<{
|
|
26
|
+
title: string;
|
|
27
|
+
fields: readonly ModelCardField[];
|
|
28
|
+
}>;
|
|
29
|
+
/**
|
|
30
|
+
* One collapsible section of a model card (e.g. "Model Details", "Training Data").
|
|
31
|
+
* Either subsections (with bold sub-headings) or top-level fields, or both.
|
|
32
|
+
*/
|
|
33
|
+
export type ModelCardSection = Readonly<{
|
|
34
|
+
/** Stable id for the section (e.g. "modelDetails", "trainingData"). */
|
|
35
|
+
id: string;
|
|
36
|
+
/** Display title (e.g. "Model Details"). */
|
|
37
|
+
title: string;
|
|
38
|
+
/** Optional grouped key-value blocks with their own titles. */
|
|
39
|
+
subsections?: readonly ModelCardSubsection[];
|
|
40
|
+
/** Optional flat key-value list when there are no subsections. */
|
|
41
|
+
fields?: readonly ModelCardField[];
|
|
42
|
+
}>;
|
|
43
|
+
/**
|
|
44
|
+
* Model card: structured, JSON-backed metadata for an integration, so the UI
|
|
45
|
+
* can display it in a consistent but integration-specific way.
|
|
46
|
+
*
|
|
47
|
+
* Required: modelName and version (always shown). All sections are optional;
|
|
48
|
+
* the UI renders only those present. Sections can have subsections (e.g.
|
|
49
|
+
* "Basic Information", "Model Architecture") or flat fields.
|
|
50
|
+
*/
|
|
51
|
+
export type ModelCard = Readonly<{
|
|
52
|
+
/** Required. Display name of the model (e.g. "GPT-4"). */
|
|
53
|
+
modelName: string;
|
|
54
|
+
/** Required. Version string (e.g. "1.0.0" or "v0.0"). */
|
|
55
|
+
version: string;
|
|
56
|
+
/** Optional. Release date or similar (e.g. "January 2026"). */
|
|
57
|
+
releaseDate?: string;
|
|
58
|
+
/** Optional. Ordered list of sections; each can be collapsed/expanded in the UI. */
|
|
59
|
+
sections?: readonly ModelCardSection[];
|
|
60
|
+
}>;
|
|
61
|
+
/**
|
|
62
|
+
* Section ids that every integration's model card must include.
|
|
63
|
+
* Use assertModelCardHasRequiredSections() to validate at runtime.
|
|
64
|
+
*/
|
|
65
|
+
export declare const REQUIRED_MODEL_CARD_SECTION_IDS: readonly ["modelDetails", "technicalIntegration"];
|
|
66
|
+
/**
|
|
67
|
+
* Asserts that a model card has at least the required sections (basic information
|
|
68
|
+
* and technical integration). Call when registering integration manifests.
|
|
69
|
+
* @throws Error if any required section id is missing
|
|
70
|
+
*/
|
|
71
|
+
export declare function assertModelCardHasRequiredSections(card: ModelCard): void;
|
|
72
|
+
/**
|
|
73
|
+
* Describes a single configuration field for integrations that require
|
|
74
|
+
* user-supplied config (e.g. API keys or other settings). Used to generate or validate config forms.
|
|
75
|
+
*/
|
|
76
|
+
export type IntegrationConfigField = Readonly<{
|
|
77
|
+
/** Form field key (e.g. "apiKey", "truePercentage"). */
|
|
78
|
+
key: string;
|
|
79
|
+
/** Human-readable label for the field. */
|
|
80
|
+
label: string;
|
|
81
|
+
/** Whether the field is required. */
|
|
82
|
+
required: boolean;
|
|
83
|
+
/** Input type for the UI. */
|
|
84
|
+
inputType: 'text' | 'password' | 'json' | 'array';
|
|
85
|
+
/** Optional placeholder or hint. */
|
|
86
|
+
placeholder?: string;
|
|
87
|
+
/** Optional description for the field. */
|
|
88
|
+
description?: string;
|
|
89
|
+
}>;
|
|
90
|
+
/**
|
|
91
|
+
* Metadata and capability description for an integration.
|
|
92
|
+
* This is the stable, structured information shown to users (name, docs, logos, etc.).
|
|
93
|
+
*/
|
|
94
|
+
export type IntegrationManifest = Readonly<{
|
|
95
|
+
/** Unique integration id. Must be UPPER_SNAKE_CASE to align with GraphQL enums when used in COOP. */
|
|
96
|
+
id: IntegrationId;
|
|
97
|
+
/** Human-readable display name shown in the UI (e.g. signal modal, integration cards). Exposed as Signal.integrationTitle. */
|
|
98
|
+
name: string;
|
|
99
|
+
/** Semantic version of the integration plugin (e.g. "1.0.0"). */
|
|
100
|
+
version: string;
|
|
101
|
+
/** Short description for listings and tooltips. */
|
|
102
|
+
description?: string;
|
|
103
|
+
/** Link to documentation or product page. */
|
|
104
|
+
docsUrl?: string;
|
|
105
|
+
/** Whether this integration requires the user to supply config (e.g. API key). */
|
|
106
|
+
requiresConfig: boolean;
|
|
107
|
+
/**
|
|
108
|
+
* Schema for configuration fields when requiresConfig is true.
|
|
109
|
+
* Enables UI generation and validation without hardcoding per-integration forms.
|
|
110
|
+
*/
|
|
111
|
+
configurationFields?: readonly IntegrationConfigField[];
|
|
112
|
+
/**
|
|
113
|
+
* Optional list of signal type ids this integration provides (e.g. "ZENTROPI_LABELER").
|
|
114
|
+
* Used by the platform to associate signals with this integration for display and gating.
|
|
115
|
+
*/
|
|
116
|
+
signalTypeIds?: readonly string[];
|
|
117
|
+
/**
|
|
118
|
+
* Model card: structured metadata (model name, version, sections) for the UI.
|
|
119
|
+
* When present, the integration detail page renders it. Built-in integrations
|
|
120
|
+
* should always provide a model card with at least sections "modelDetails" and
|
|
121
|
+
* "technicalIntegration"; use assertModelCardHasRequiredSections() when
|
|
122
|
+
* registering.
|
|
123
|
+
*/
|
|
124
|
+
modelCard?: ModelCard;
|
|
125
|
+
/**
|
|
126
|
+
* ------------------------------------------------------------
|
|
127
|
+
* LOGO/IMAGE SECTION:
|
|
128
|
+
* ------------------------------------------------------------
|
|
129
|
+
* The following logo/image sections are optional. If none provided will use a fallback Coop logo.
|
|
130
|
+
*
|
|
131
|
+
* Provide either logoUrl and logoWithBackgroundUrl or logoPath and logoWithBackgroundPath.
|
|
132
|
+
*
|
|
133
|
+
* If you provide logoPath and logoWithBackgroundPath, the server will serve the files at
|
|
134
|
+
* GET /api/v1/integration-logos/:integrationId and GET /api/v1/integration-logos/:integrationId/with-background
|
|
135
|
+
* and set logoUrl and logoWithBackgroundUrl accordingly.
|
|
136
|
+
* If you provide logoUrl and logoWithBackgroundUrl, the server will use those URLs directly.
|
|
137
|
+
* Prefered size: ~180x180px for logoUrl and ~120x120px for logoWithBackgroundUrl.
|
|
138
|
+
* Prefer a square or horizontal logo that scales well.
|
|
139
|
+
*/
|
|
140
|
+
logoUrl?: string;
|
|
141
|
+
logoWithBackgroundUrl?: string;
|
|
142
|
+
logoPath?: string;
|
|
143
|
+
logoWithBackgroundPath?: string;
|
|
144
|
+
}>;
|
|
145
|
+
/** Context passed to plugin.createSignals() so the plugin can build signal instances with credential access. */
|
|
146
|
+
export type PluginSignalContext = Readonly<{
|
|
147
|
+
/** Integration id (e.g. "ACME_API") from the plugin manifest. */
|
|
148
|
+
integrationId: string;
|
|
149
|
+
/** Get stored credential/config for an org. Resolves to the JSON stored for this integration. */
|
|
150
|
+
getCredential: (orgId: string) => Promise<Record<string, unknown>>;
|
|
151
|
+
}>;
|
|
152
|
+
/** Minimal signal descriptor returned by a plugin. The platform adapts this to its internal SignalBase. */
|
|
153
|
+
export type PluginSignalDescriptor = Readonly<{
|
|
154
|
+
/** Stable signal type id (e.g. "ACME_MODERATION_SIGNAL"). Must match one of manifest.signalTypeIds. */
|
|
155
|
+
id: Readonly<{
|
|
156
|
+
type: string;
|
|
157
|
+
}>;
|
|
158
|
+
displayName: string;
|
|
159
|
+
description: string;
|
|
160
|
+
docsUrl: string | null;
|
|
161
|
+
recommendedThresholds: Readonly<{
|
|
162
|
+
highPrecisionThreshold: string | number;
|
|
163
|
+
highRecallThreshold: string | number;
|
|
164
|
+
}> | null;
|
|
165
|
+
supportedLanguages: readonly string[] | 'ALL';
|
|
166
|
+
pricingStructure: Readonly<{
|
|
167
|
+
type: 'FREE' | 'SUBSCRIPTION';
|
|
168
|
+
}>;
|
|
169
|
+
eligibleInputs: readonly string[];
|
|
170
|
+
outputType: Readonly<{
|
|
171
|
+
scalarType: string;
|
|
172
|
+
}>;
|
|
173
|
+
getCost: () => number;
|
|
174
|
+
/** Run the signal. Input shape is platform-defined; result must have outputType and score. */
|
|
175
|
+
run: (input: unknown) => Promise<unknown>;
|
|
176
|
+
getDisabledInfo: (orgId: string) => Promise<{
|
|
177
|
+
disabled: false;
|
|
178
|
+
disabledMessage?: string;
|
|
179
|
+
} | {
|
|
180
|
+
disabled: true;
|
|
181
|
+
disabledMessage: string;
|
|
182
|
+
}>;
|
|
183
|
+
needsMatchingValues: boolean;
|
|
184
|
+
eligibleSubcategories: ReadonlyArray<{
|
|
185
|
+
id: string;
|
|
186
|
+
label: string;
|
|
187
|
+
description?: string;
|
|
188
|
+
childrenIds: readonly string[];
|
|
189
|
+
}>;
|
|
190
|
+
needsActionPenalties: boolean;
|
|
191
|
+
/** Integration id (same as context.integrationId). */
|
|
192
|
+
integration: string;
|
|
193
|
+
allowedInAutomatedRules: boolean;
|
|
194
|
+
}>;
|
|
195
|
+
/**
|
|
196
|
+
* Plugin contract that third-party integration packages must implement.
|
|
197
|
+
* Export this as the default export (or a named export) from the package.
|
|
198
|
+
*
|
|
199
|
+
* Example (in an integration package):
|
|
200
|
+
*
|
|
201
|
+
* const manifest: IntegrationManifest = { id: 'ACME_API', name: 'Acme API', ... };
|
|
202
|
+
* const plugin: CoopIntegrationPlugin = { manifest };
|
|
203
|
+
* export default plugin;
|
|
204
|
+
*
|
|
205
|
+
* To power routing/enforcement rules, also implement createSignals(context) and
|
|
206
|
+
* return one descriptor per manifest.signalTypeIds entry.
|
|
207
|
+
*/
|
|
208
|
+
export type CoopIntegrationPlugin = Readonly<{
|
|
209
|
+
manifest: IntegrationManifest;
|
|
210
|
+
/**
|
|
211
|
+
* Optional static config shape for this integration.
|
|
212
|
+
* If present, adopters can pass non-secret config in the integrations config file.
|
|
213
|
+
*/
|
|
214
|
+
configSchema?: unknown;
|
|
215
|
+
/**
|
|
216
|
+
* Optional. If this integration provides signals for use in rules, implement this.
|
|
217
|
+
* Return one descriptor per signal type id listed in manifest.signalTypeIds.
|
|
218
|
+
* The platform will register these so they appear in the rule builder and can be used in conditions.
|
|
219
|
+
*/
|
|
220
|
+
createSignals?: (context: PluginSignalContext) => ReadonlyArray<Readonly<{
|
|
221
|
+
signalTypeId: string;
|
|
222
|
+
signal: PluginSignalDescriptor;
|
|
223
|
+
}>>;
|
|
224
|
+
}>;
|
|
225
|
+
/**
|
|
226
|
+
* Single entry in the adopters' integrations config file.
|
|
227
|
+
* Enables or disables a plugin and optionally passes static config.
|
|
228
|
+
*/
|
|
229
|
+
export type CoopIntegrationConfigEntry = Readonly<{
|
|
230
|
+
/** NPM package name (e.g. "@acme/coop-integration-acme") or path to a local module. */
|
|
231
|
+
package: string;
|
|
232
|
+
/** Whether this integration is enabled. Default true if omitted. */
|
|
233
|
+
enabled?: boolean;
|
|
234
|
+
/** Optional static config passed to the integration (no secrets here; use org credentials in-app). */
|
|
235
|
+
config?: Readonly<Record<string, unknown>>;
|
|
236
|
+
}>;
|
|
237
|
+
/**
|
|
238
|
+
* Root type for the integrations config file that adopters use to register
|
|
239
|
+
* plugin integrations. Can be JSON or a JS/TS module that exports this shape.
|
|
240
|
+
*
|
|
241
|
+
* Example integrations.config.json:
|
|
242
|
+
*
|
|
243
|
+
* {
|
|
244
|
+
* "integrations": [
|
|
245
|
+
* { "package": "@acme/coop-integration-acme", "enabled": true },
|
|
246
|
+
* { "package": "./local-integrations/foo", "config": { "endpoint": "https://..." } }
|
|
247
|
+
* ]
|
|
248
|
+
* }
|
|
249
|
+
*/
|
|
250
|
+
export type CoopIntegrationsConfig = Readonly<{
|
|
251
|
+
integrations: readonly CoopIntegrationConfigEntry[];
|
|
252
|
+
}>;
|
|
253
|
+
/**
|
|
254
|
+
* Shape of the config stored in the database for each integration (per org).
|
|
255
|
+
* Stored in a generic table as JSON: one row per (org_id, integration_id) with
|
|
256
|
+
* config as a JSON-serializable object. Each integration defines its own required
|
|
257
|
+
* fields via IntegrationManifest.configurationFields; the app validates and
|
|
258
|
+
* serializes/deserializes to this type.
|
|
259
|
+
*
|
|
260
|
+
* Only JSON-serializable values (no functions, symbols, or BigInt) should be
|
|
261
|
+
* included so the payload can be stored in a JSONB or TEXT column.
|
|
262
|
+
*/
|
|
263
|
+
export type StoredIntegrationConfigPayload = Readonly<Record<string, unknown>>;
|
|
264
|
+
/**
|
|
265
|
+
* Type guard for CoopIntegrationPlugin.
|
|
266
|
+
*/
|
|
267
|
+
export declare function isCoopIntegrationPlugin(value: unknown): value is CoopIntegrationPlugin;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Integration plugin types for COOP.
|
|
3
|
+
*
|
|
4
|
+
* These types define the contract that third-party integration packages
|
|
5
|
+
* implement so adopters can install and configure them without adding
|
|
6
|
+
* every integration to the main COOP repo.
|
|
7
|
+
*
|
|
8
|
+
* Integration packages export a CoopIntegrationPlugin; adopters register
|
|
9
|
+
* them via an integrations config file (see CoopIntegrationsConfig).
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* Section ids that every integration's model card must include.
|
|
13
|
+
* Use assertModelCardHasRequiredSections() to validate at runtime.
|
|
14
|
+
*/
|
|
15
|
+
export const REQUIRED_MODEL_CARD_SECTION_IDS = [
|
|
16
|
+
'modelDetails',
|
|
17
|
+
'technicalIntegration',
|
|
18
|
+
];
|
|
19
|
+
/**
|
|
20
|
+
* Asserts that a model card has at least the required sections (basic information
|
|
21
|
+
* and technical integration). Call when registering integration manifests.
|
|
22
|
+
* @throws Error if any required section id is missing
|
|
23
|
+
*/
|
|
24
|
+
export function assertModelCardHasRequiredSections(card) {
|
|
25
|
+
const sectionIds = new Set((card.sections ?? []).map((s) => s.id));
|
|
26
|
+
for (const requiredId of REQUIRED_MODEL_CARD_SECTION_IDS) {
|
|
27
|
+
if (!sectionIds.has(requiredId)) {
|
|
28
|
+
throw new Error(`Model card must include a section with id "${requiredId}" (e.g. Basic Information / Model Details and Technical Integration).`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Type guard for CoopIntegrationPlugin.
|
|
34
|
+
*/
|
|
35
|
+
export function isCoopIntegrationPlugin(value) {
|
|
36
|
+
if (value == null || typeof value !== 'object') {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
const o = value;
|
|
40
|
+
if (o.manifest == null || typeof o.manifest !== 'object') {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
const m = o.manifest;
|
|
44
|
+
return (typeof m.id === 'string' &&
|
|
45
|
+
typeof m.name === 'string' &&
|
|
46
|
+
typeof m.version === 'string' &&
|
|
47
|
+
typeof m.requiresConfig === 'boolean');
|
|
48
|
+
}
|