@memberjunction/connector-path-lms 1.0.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.
@@ -0,0 +1,394 @@
1
+ import { type UserInfo } from '@memberjunction/core';
2
+ import type { MJCompanyIntegrationEntity } from '@memberjunction/core-entities';
3
+ import { BaseRESTIntegrationConnector, type RESTAuthContext, type RESTResponse, type PaginationState, type PaginationType, type ConnectionTestResult, type ExternalObjectSchema, type ExternalFieldSchema, type FetchContext, type FetchBatchResult } from '@memberjunction/integration-engine';
4
+ /**
5
+ * Path LMS Reporting API connector (Blue Sky eLearn).
6
+ *
7
+ * TRANSPORT: GraphQL over HTTP. There is exactly ONE protocol base — {@link BaseRESTIntegrationConnector};
8
+ * GraphQL rides on top of it. Every report query POSTs a GraphQL document to the single endpoint
9
+ * `https://data-api.pathlms.com/graphql`; {@link NormalizeResponse} strips the `data.<queryName>`
10
+ * envelope and surfaces `errors[]`.
11
+ *
12
+ * AUTH (two-step, no refresh token): {@link Authenticate} exchanges `{ applicationId, applicationSecret }`
13
+ * (form-urlencoded) at `https://data-api.pathlms.com/api/v1/getToken` for a ~12h bearer JWT. The token +
14
+ * expiry are cached per credential; on expiry OR a 401, the connector re-mints. applicationId/Secret come
15
+ * from the linked Credential entity (or the CompanyIntegration.Configuration JSON) — NEVER baked in code.
16
+ *
17
+ * PAGINATION: offset/limit (`PaginationType=Offset`) on the report queries.
18
+ *
19
+ * PULL-ONLY: the SDL documents 0 mutations / 0 subscriptions, so SupportsCreate/Update/Delete stay false
20
+ * (the base defaults) and no CRUD path is wired. No 501 stubs.
21
+ *
22
+ * INCREMENTAL: every IO here is `SupportsIncrementalSync=false` — the SDL's startDate/endDate args filter
23
+ * by event date, NOT record-modification time, so there is no watermark to assert (provable-only). The
24
+ * engine relies on content-hash idempotency + {@link StableOrderingKey} (the IO's stable `id`) for
25
+ * keyset-style resume.
26
+ *
27
+ * DISCOVERY — CREDENTIAL-FREE, FROM THE PUBLIC SCHEMA (the T3-deadlock fix):
28
+ * Path LMS publishes its complete GraphQL SDL credential-free as a SpectaQL HTML reference page at
29
+ * `https://data-api.pathlms.com/`. That page is the **schema-of-record**. {@link DiscoverObjects} and
30
+ * {@link DiscoverFields} FETCH + PARSE that public page WITH NO CREDENTIAL and enumerate the full standard
31
+ * universe of GraphQL **record types** (84 = 93 SDL object types − 9 non-record types). This is what makes
32
+ * the runtime credential-free `DocStructureSelfCheck` re-yield the same standard universe every time (so
33
+ * persisted objects never read as "structure drift").
34
+ *
35
+ * A live credential is strictly **ADDITIVE**: when present, {@link DiscoverFields} also runs a standard
36
+ * GraphQL introspection against `/graphql` and appends any tenant-specific fields the public SDL lacked
37
+ * (the `Discovered` extension). It NEVER samples live data at build time and NEVER becomes the baseline —
38
+ * the standard universe always comes from the public, token-free schema. The auth-gated live introspection
39
+ * being available does NOT make the connector "case 2": the same schema is published credential-free, so
40
+ * discovery is case 1 (public schema) + an additive case-2 tenant overlay. The catalog is NOT a module-level
41
+ * constant — it is fetched + parsed from the public page at discovery time (only the small set of non-record
42
+ * SDL types to *exclude* is a documented constant, NOT the catalog itself).
43
+ */
44
+ export declare class PathLMSConnector extends BaseRESTIntegrationConnector {
45
+ /** Per-process token cache, keyed by credential identity (applicationId). Survives across fetches. */
46
+ private tokenCache;
47
+ /**
48
+ * Per-process cache of the parsed PUBLIC SpectaQL schema (type-name → record type + fields). Populated
49
+ * lazily, credential-free, from `https://data-api.pathlms.com/`. This is the standard-universe source of
50
+ * record for discovery; it is fetched + parsed at runtime, never a baked array.
51
+ */
52
+ private publicSchemaCache;
53
+ private publicSchemaPromise;
54
+ /**
55
+ * Per-CompanyIntegration introspection cache of the live SDL's object types → their scalar/object field
56
+ * shape. Lets the GraphQL selection-set builder emit a valid sub-selection for object-valued (`json`)
57
+ * report fields like `attendees: [WebinarAttendee]!`, and feeds the ADDITIVE tenant-field overlay in
58
+ * DiscoverFields. Populated lazily at runtime from the live introspection query — never a baked catalog.
59
+ */
60
+ private sdlTypeCache;
61
+ /** Verbatim ClassName / IntegrationName getter / MJ: Integrations.Name. */
62
+ get IntegrationName(): string;
63
+ /**
64
+ * KEYSET / no-watermark resume hint. None of the Path LMS report queries expose a record-modification
65
+ * watermark, so every object resumes by its stable ordering key — the object's declared primary key
66
+ * (the report's `id`). Resolved from the cached IOF PK. Returns null when no PK is declared (keyset
67
+ * resume unavailable for the PK-less roll-up/aggregate report types).
68
+ */
69
+ StableOrderingKey(objectName: string): string | null;
70
+ /**
71
+ * Conservative rate-limit policy. Path LMS publishes no explicit per-app limit; a single GraphQL
72
+ * endpoint behind Apollo tolerates modest sustained throughput. A low default keeps the connector
73
+ * polite without a documented number to push to (provable-only — see PROVENANCE BatchRequestWaitTimeGap).
74
+ */
75
+ get RateLimitPolicy(): {
76
+ TokensPerSec: number;
77
+ Burst?: number;
78
+ } | null;
79
+ /**
80
+ * Two-step token exchange. Resolves applicationId/applicationSecret from the credential, returns a
81
+ * cached non-expired token when available, else POSTs form-urlencoded credentials to /api/v1/getToken
82
+ * and caches the resulting bearer (stripping any leading "Bearer ") with a 12h expiry (minus a skew
83
+ * buffer). No refresh token exists — re-mint is just another exchange.
84
+ */
85
+ protected Authenticate(companyIntegration: MJCompanyIntegrationEntity, contextUser: UserInfo): Promise<PathLMSAuthContext>;
86
+ /**
87
+ * Builds an auth context from a directly-supplied/pre-configured bearer token WITHOUT a live token
88
+ * exchange. The transport-smoke gate (T7b) configures a dummy token and asserts the connector injects
89
+ * `Authorization: Bearer <token>` on the request — it must NOT require a successful /api/v1/getToken
90
+ * round-trip before a header is present. Whenever a credential/Configuration carries a direct bearer
91
+ * token (alias `Token`/`accessToken`/`bearerToken`/`apiKey`), {@link LoadCredentials} surfaces it as
92
+ * `PreconfiguredToken` and {@link GetOrMintToken} returns it verbatim, so {@link BuildHeaders} sets the
93
+ * header off the configured token state with no network call.
94
+ */
95
+ /** Bearer header on every GraphQL request, plus JSON content negotiation. */
96
+ protected BuildHeaders(auth: RESTAuthContext): Record<string, string>;
97
+ /** The single GraphQL endpoint. APIPath on every IO is `/graphql`, so the base URL is the host root. */
98
+ protected GetBaseURL(companyIntegration: MJCompanyIntegrationEntity, _auth: RESTAuthContext): string;
99
+ /**
100
+ * Resolves the API host root: an optional `Configuration.BaseURL`/`GraphQLEndpoint` override (a
101
+ * self-hosted/sandbox Path LMS instance, or an e2e mock origin) else the canonical {@link PATHLMS_HOST}.
102
+ * A full `…/graphql` URL is tolerated and reduced to the host root (the connector appends the paths).
103
+ */
104
+ private HostFor;
105
+ /**
106
+ * Executes an HTTP request via fetch. For GraphQL all requests are POSTs carrying a JSON
107
+ * `{ query, variables }` body. Parses the JSON body; returns the raw text on a non-JSON response.
108
+ */
109
+ protected MakeHTTPRequest(_auth: RESTAuthContext, url: string, method: string, headers: Record<string, string>, body?: unknown): Promise<RESTResponse>;
110
+ /**
111
+ * Strips the GraphQL envelope: `{ data: { <responseDataKey>: <records> } }` → records, normalizing a
112
+ * single object to a one-element array. GraphQL errors are surfaced as a thrown error so the engine
113
+ * records the failure (rather than silently treating `data: null` as zero records). When `data` is
114
+ * present alongside `errors` (partial success), the data is returned and the error is left to the
115
+ * caller's HTTP-status handling.
116
+ */
117
+ protected NormalizeResponse(rawBody: unknown, responseDataKey: string | null): Record<string, unknown>[];
118
+ /**
119
+ * Offset/limit pagination. Path LMS report queries return a flat list with no total-count or
120
+ * has-next signal, so "more pages remain" is inferred from a full page: when the page returned
121
+ * exactly `pageSize` records, there may be more (advance the offset); a short/empty page is the end.
122
+ */
123
+ protected ExtractPaginationInfo(rawBody: unknown, paginationType: PaginationType, _currentPage: number, currentOffset: number, pageSize: number): PaginationState;
124
+ /**
125
+ * Verifies credentials by minting a token then issuing the cheapest GraphQL query the SDL documents
126
+ * (`teamsList { id }`). A 401/GraphQL auth error means bad credentials; a 2xx with a `data` envelope
127
+ * confirms both the token exchange and live GraphQL access.
128
+ */
129
+ TestConnection(companyIntegration: MJCompanyIntegrationEntity, contextUser: UserInfo): Promise<ConnectionTestResult>;
130
+ /**
131
+ * Enumerates the FULL STANDARD UNIVERSE of record types CREDENTIAL-FREE by fetching + parsing the
132
+ * public SpectaQL schema page at `https://data-api.pathlms.com/` — NOT a baked array and NOT dependent
133
+ * on any token or on the seeded metadata cache. This is the T3-deadlock fix: because the standard
134
+ * universe is sourced from the public schema (which always resolves without a credential), the runtime
135
+ * credential-free `DocStructureSelfCheck` re-yields the same universe and persisted objects never read
136
+ * as structure drift.
137
+ *
138
+ * The seeded Declared metadata cache is consulted only to ATTACH the persisted IntegrationObject `ID`
139
+ * to each discovered object (a join, not the source of the object set). A live credential is NOT used
140
+ * here at all — standard objects are token-free by construction.
141
+ */
142
+ DiscoverObjects(companyIntegration: MJCompanyIntegrationEntity, _contextUser: UserInfo): Promise<ExternalObjectSchema[]>;
143
+ /**
144
+ * Returns the fields for an object as the FULL STANDARD set parsed CREDENTIAL-FREE from the public
145
+ * SpectaQL schema (the baseline), enriched by the seeded Declared metadata (PK/FK/type curation) where
146
+ * it exists, then — only when a live credential is present — ADDITIVELY augmented with any tenant-
147
+ * specific fields the live introspection exposes that the public SDL lacked (the `Discovered` overlay).
148
+ *
149
+ * The public-schema field set is the baseline that always resolves token-free. The Declared overlay
150
+ * never replaces it; the live overlay only appends. Introspection failures degrade gracefully to the
151
+ * public + Declared fields. NEVER samples live data; NEVER hardcodes the field catalog.
152
+ */
153
+ DiscoverFields(companyIntegration: MJCompanyIntegrationEntity, objectName: string, contextUser: UserInfo): Promise<ExternalFieldSchema[]>;
154
+ /**
155
+ * Appends any live-introspection fields the public schema lacked (tenant `Discovered` overlay). The
156
+ * public + Declared fields are the floor; the live introspection only ADDS. Degrades to the merged
157
+ * baseline on any introspection failure.
158
+ */
159
+ private AppendLiveFields;
160
+ /**
161
+ * Fetches records by POSTing a GraphQL document built from the IO's metadata:
162
+ * - operation name + return type + arguments from the IO's per-object Configuration JSON,
163
+ * - the selection set from the IOF field names (scalar fields selected directly; object-valued
164
+ * `json` fields given a one-level sub-selection resolved from the live SDL type map),
165
+ * - offset/limit appended for Offset-paginated queries.
166
+ *
167
+ * Full-record pass-through: every record's COMPLETE GraphQL node lands in `ExternalRecord.Fields` so
168
+ * the framework's custom-column capture sees every key. Identity is the IOF-declared PK; the base
169
+ * content-hash fallback handles PK-less / object-container objects.
170
+ */
171
+ FetchChanges(ctx: FetchContext): Promise<FetchBatchResult>;
172
+ /**
173
+ * Fetches a NESTED record type by querying its entry "door" and descending the access-path field
174
+ * chain to the leaf record collection (tables ≠ doors). One GraphQL request selects down the path;
175
+ * the response is then walked the same path to flatten out every leaf record. Each leaf carries its
176
+ * FULL node in `Fields` (custom-column pass-through). Reporting doors return the whole set (no
177
+ * offset/limit on the nested path), so HasMore is false.
178
+ */
179
+ private FetchViaAccessPath;
180
+ /** Wraps the leaf selection in the door + nested field chain: `door { seg1 { seg2 { leaf } } }`. */
181
+ private BuildNestedQuery;
182
+ /**
183
+ * Walks a GraphQL response down the access path (`data[door]` → each segment), flattening arrays and
184
+ * single objects at every hop, to yield the flat list of leaf records. Robust to a door/segment that
185
+ * resolves to either an object or an array.
186
+ */
187
+ private DescendAccessPath;
188
+ /** Returns the cached public schema, fetching + parsing it once per process (credential-free). */
189
+ private GetPublicSchema;
190
+ /**
191
+ * Fetches the public SpectaQL HTML (no credential) and parses its `definition-object` blocks into
192
+ * record types + fields. This is the standard-universe source of record. Allows a subclass / test to
193
+ * override {@link FetchPublicSchemaHTML} to supply a fixture without a network call.
194
+ */
195
+ private FetchAndParsePublicSchema;
196
+ /**
197
+ * Fetches the raw public SpectaQL schema HTML from `https://data-api.pathlms.com/` with NO auth header.
198
+ * Overridable seam (tests inject a fixture). Throws on a non-2xx response so discovery surfaces a real
199
+ * fetch failure rather than silently returning zero objects.
200
+ */
201
+ protected FetchPublicSchemaHTML(): Promise<string>;
202
+ /**
203
+ * Builds the selection set string for the query's return type. Scalar IOF fields are selected
204
+ * directly; object-valued (`json`) IOF fields receive a one-level sub-selection of their SDL type's
205
+ * SCALAR fields when the live type map resolves the field's type. When the type can't be resolved
206
+ * (no introspection / open JSON scalar), the field is selected as a leaf — correct for true scalars,
207
+ * and harmless for unresolved fields (the query simply omits an unresolvable object field rather than
208
+ * sending an invalid leaf selection on a known object type).
209
+ */
210
+ private BuildSelectionSet;
211
+ /** Builds a one-level scalar sub-selection for an object type (no nested object recursion). */
212
+ private BuildScalarSubSelection;
213
+ /**
214
+ * Assembles the full GraphQL query document and its variables. Operation arguments come from the IO's
215
+ * Configuration `OperationArguments` (e.g. `["limit:Int","offset:Int","teamIds:[Int!]"]`). Only the
216
+ * pagination args (offset/limit) are bound at fetch time when paginating; all other documented filter
217
+ * args are declared as optional variables left unset (the source returns the unfiltered report).
218
+ */
219
+ private BuildQueryDocument;
220
+ /** Returns the cached/freshly-introspected SDL type map; throws on hard introspection failure. */
221
+ private GetSDLTypeMap;
222
+ /** Best-effort SDL type map — returns an empty map (no augmentation) on any introspection failure. */
223
+ private GetSDLTypeMapSafe;
224
+ /** Parses a GraphQL `__schema.types` introspection response into a name→SDLType map (OBJECT types only). */
225
+ private ParseIntrospection;
226
+ /** True when a field's underlying named type is an OBJECT type present in the map (needs sub-selection). */
227
+ private IsObjectField;
228
+ /** Returns the field's underlying OBJECT type name when it is an object (else null). */
229
+ private ObjectFieldTypeName;
230
+ /** Maps an SDL field's underlying type into the connector's coarse DataType vocabulary. */
231
+ private SDLTypeToDataType;
232
+ private IsListOrObjectScalar;
233
+ /** POSTs a `{ query, variables }` document to the GraphQL endpoint with auth headers. */
234
+ private PostGraphQL;
235
+ /** Throws on a non-2xx HTTP status or a GraphQL error with no usable data. */
236
+ private AssertGraphQLOK;
237
+ /**
238
+ * Resolves the bearer token for these credentials. Precedence:
239
+ * 1. A directly-supplied/pre-configured bearer token (`PreconfiguredToken`) is returned verbatim with
240
+ * NO network exchange — this is the path the transport-smoke gate (T7b) exercises with a dummy token,
241
+ * and the path a broker uses when it injects a ready bearer rather than appId/secret.
242
+ * 2. Else a cached, non-expired minted token.
243
+ * 3. Else mint a fresh token via the two-step /api/v1/getToken exchange and cache it (12h, minus skew).
244
+ */
245
+ private GetOrMintToken;
246
+ /**
247
+ * Exchanges applicationId/applicationSecret for a bearer token via a form-urlencoded POST to
248
+ * /api/v1/getToken. The response carries `{ token: "Bearer <jwt>" }`; the leading "Bearer " is
249
+ * stripped so BuildHeaders applies a single prefix.
250
+ */
251
+ private MintToken;
252
+ /** Reads the token from the getToken response and strips any leading "Bearer " prefix. */
253
+ private ExtractToken;
254
+ /**
255
+ * Resolves applicationId/applicationSecret from the linked Credential entity, falling back to the
256
+ * CompanyIntegration.Configuration JSON. Credentials are issued by Blue Sky eLearn and are NEVER
257
+ * hardcoded in connector code.
258
+ */
259
+ private LoadCredentials;
260
+ /** Loads credentials from a Credential entity's Values JSON. */
261
+ private LoadFromCredentialEntity;
262
+ /**
263
+ * Parses a JSON string into credentials (tolerant of casing aliases). Accepts EITHER a pre-minted bearer
264
+ * token (`Token`/`accessToken`/`bearerToken`/`apiKey` — the exchange-free path the transport-smoke gate
265
+ * uses) OR an applicationId + applicationSecret pair for the two-step exchange. Returns null only when
266
+ * neither a usable token nor a full appId+secret pair is present.
267
+ */
268
+ private ParseCredentialJson;
269
+ /** Gets an IO from the cache without throwing (used by StableOrderingKey, which may be called early). */
270
+ private TryGetCachedObject;
271
+ /** Returns the seeded Declared IO rows for this integration keyed by lower-cased name (safe on empty cache). */
272
+ private CachedObjectsByName;
273
+ /** Returns the seeded Declared fields for an object as ExternalFieldSchema (empty when not cached). */
274
+ private DeclaredFields;
275
+ /**
276
+ * Overlays the seeded Declared field curation (PK / FK target / type / required) onto the public-schema
277
+ * baseline, by field name. Declared wins for the curated structural attributes where it has an opinion;
278
+ * the public-schema baseline supplies everything else and guarantees the full field set even when no
279
+ * Declared row exists. Never drops a public field; only enriches.
280
+ */
281
+ private MergeDeclaredOverlay;
282
+ /**
283
+ * Maps a parsed public-schema record field into the ExternalFieldSchema baseline shape — applying the
284
+ * EXACT id-PK / typed-reference-FK rules the persisted metadata was built from, so the connector's
285
+ * credential-free discovery reproduces the persisted PK/FK set and T3 `DocStructureSelfCheck` cannot
286
+ * drift. This is run with zero credential and uses ONLY the parsed public SDL universe ({@param schema}).
287
+ *
288
+ * PK: the row's own `id` field is the sole primary key. An id-less type has NO PK (the engine's
289
+ * content-hash carries identity). A `*Id` reference is NEVER a PK — it is a foreign key (below).
290
+ *
291
+ * FK (two cases, mirroring `parse-sdl-fk.mjs` + the id-less `*Id` demotion path):
292
+ * (1) TYPED REFERENCE — the field's unwrapped SDL type resolves to ANOTHER emitted record type
293
+ * (e.g. `groups: [Group]` → Group, `assessmentsReport: [Assessment]!` → Assessment).
294
+ * (2) SCALAR `<Type>Id` — a scalar field named `<emittedType>Id` (case-insensitive, e.g. userId→User,
295
+ * courseId→Course, webinarId→Webinar) where the capitalized stem matches another emitted record
296
+ * type. (No self-alias exclusion: the metadata marks even `userId`-same-as-id as an FK→User on the
297
+ * id-less report rows, so discovery must too.)
298
+ * Both cases exclude a self-reference to the field's own owning type.
299
+ */
300
+ private PublicFieldToSchema;
301
+ /**
302
+ * Resolves a field's foreign-key target to another emitted record type, or null when the field is not
303
+ * a reference. Mirrors EXACTLY the rules `scripts/parse-sdl-fk.mjs` used to author the persisted metadata,
304
+ * so the connector's credential-free FK derivation reproduces it (T3 `DocStructureSelfCheck` cannot drift):
305
+ *
306
+ * (1) TYPED REFERENCE — the SDL anchor target is itself an emitted record type (e.g. `groups: [Group]`
307
+ * → Group). Always an FK; no contradiction exclusion.
308
+ * (2) SCALAR `<Type>Id` — the field name (sans trailing `Id`, capitalized) matches an emitted record
309
+ * type and the field's underlying type is a scalar (e.g. userId→User, courseId→Course, webinarId→
310
+ * Webinar). This IS an FK, with ONE exclusion: when the owning type has its own `id` primary key
311
+ * AND the field's prose marks it a self-alias of that id ("the userId field is the same as id…",
312
+ * "alias of id", "for cross referencing"), it is a renamed view of the row's own identity, not a
313
+ * reference to another row — the persisted metadata leaves those non-FK (e.g. `Order.userId`).
314
+ * On an id-LESS report row (`CategorySale.userId`, `InPersonEventUser.userId`, …) the `<Type>Id`
315
+ * IS the only identity/reference, so the self-alias exclusion does NOT apply and the FK is kept —
316
+ * exactly as the metadata records it.
317
+ *
318
+ * Returns the CANONICAL record-type name (so the FK target matches the persisted
319
+ * `@lookup:…Name=<Type>` exactly). Never resolves to the field's own owning type.
320
+ */
321
+ private ResolveFKTarget;
322
+ /** Parses the per-IO Configuration JSON into the GraphQL query model (incl. the nested access path). */
323
+ private ParseIOConfiguration;
324
+ /** Returns the IOF PK field names (sorted by sequence), or empty when none is declared. */
325
+ private FindPKFieldNames;
326
+ /**
327
+ * Builds a stable record identity from the declared PK fields. When all PK parts are present, joins
328
+ * them with '|'; otherwise returns '' so the engine's content-hash identity fallback takes over (the
329
+ * `account`/`teams` container objects without a usable PK dedupe by content hash).
330
+ */
331
+ private BuildRecordIdentity;
332
+ /** Resolves the page size for a fetch: explicit BatchSize, else the IO default, else 50. */
333
+ private ResolvePageSize;
334
+ /** NormalizeResponse variant that never throws — used by ExtractPaginationInfo to count a page. */
335
+ private NormalizeResponseSafe;
336
+ /** Converts an IntegrationObjectField entity to the ExternalFieldSchema shape (Declared discovery). */
337
+ private DeclaredFieldToSchema;
338
+ }
339
+ /** Auth context: resolved bearer token + the credentials used to mint it (for re-mint). */
340
+ interface PathLMSAuthContext extends RESTAuthContext {
341
+ Token: string;
342
+ Credentials: PathLMSCredentials;
343
+ /** Resolved host root for this connection (override or {@link PATHLMS_HOST}). */
344
+ BaseURL?: string;
345
+ }
346
+ /**
347
+ * Resolved Path LMS credentials. The normal path carries `applicationId` + `applicationSecret` (exchanged
348
+ * at /api/v1/getToken). `PreconfiguredToken` is an alternate, exchange-free path: a ready bearer token
349
+ * supplied directly (the transport-smoke dummy-token path, or a broker injecting a bearer). When
350
+ * `PreconfiguredToken` is set, appId/secret may be absent — {@link PathLMSConnector.GetOrMintToken} returns
351
+ * the token verbatim with no network call.
352
+ */
353
+ interface PathLMSCredentials {
354
+ applicationId?: string;
355
+ applicationSecret?: string;
356
+ PreconfiguredToken?: string;
357
+ /** Optional host-root override (self-hosted/sandbox instance or e2e mock origin); absent ⇒ {@link PATHLMS_HOST}. */
358
+ BaseURL?: string;
359
+ }
360
+ /** A field parsed from the public SpectaQL schema. */
361
+ interface PublicField {
362
+ Name: string;
363
+ /** Underlying named type from the SDL link (list/non-null wrappers stripped), e.g. "Int", "Course". */
364
+ TargetType: string | null;
365
+ /** Whether the SDL type was a list (`[Foo]`). */
366
+ IsList: boolean;
367
+ /** Whether the SDL type was non-null at the top level (`!`). */
368
+ NonNull: boolean;
369
+ /** The field's documentation prose, if any. */
370
+ Description?: string;
371
+ }
372
+ /** A record type parsed from the public SpectaQL schema. */
373
+ interface PublicRecordType {
374
+ Name: string;
375
+ Description?: string;
376
+ Fields: PublicField[];
377
+ }
378
+ /** The fully-parsed public schema: the standard-universe record types + a by-name index. */
379
+ interface PublicSchema {
380
+ RecordTypes: PublicRecordType[];
381
+ RecordTypesByName: Map<string, PublicRecordType>;
382
+ }
383
+ /**
384
+ * Parses a SpectaQL HTML schema page into the standard universe of record types + fields. Each record type
385
+ * is a `<section id="definition-<Type>" class="definition definition-object">` block; the non-record SDL
386
+ * types in {@link NON_RECORD_SDL_TYPES} are removed. Each field row carries the property name + a typed
387
+ * link `<a href="#definition-<Target>"><code>[Type]!</code></a>` from which the underlying type, list-ness
388
+ * and non-null are read. This is the credential-free standard-universe enumeration (record TYPES, not the
389
+ * Query entry-point doors).
390
+ */
391
+ export declare function parseSpectaQLSchema(html: string): PublicSchema;
392
+ /** Tree-shaking prevention function — import and call from the module entry point. */
393
+ export declare function LoadPathLMSConnector(): void;
394
+ export {};