@reactionary/core 0.0.28 → 0.0.29
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/index.js +413 -8
- package/package.json +3 -2
- package/src/providers/base.provider.d.ts +1 -0
package/index.js
CHANGED
|
@@ -54,12 +54,402 @@ function buildClient(providers) {
|
|
|
54
54
|
return client;
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
+
// otel/src/sdk.ts
|
|
58
|
+
import { NodeSDK } from "@opentelemetry/sdk-node";
|
|
59
|
+
import { resourceFromAttributes, defaultResource } from "@opentelemetry/resources";
|
|
60
|
+
import { SEMRESATTRS_SERVICE_NAME, SEMRESATTRS_SERVICE_VERSION, SEMRESATTRS_DEPLOYMENT_ENVIRONMENT } from "@opentelemetry/semantic-conventions";
|
|
61
|
+
import {
|
|
62
|
+
BatchSpanProcessor
|
|
63
|
+
} from "@opentelemetry/sdk-trace-base";
|
|
64
|
+
import { HttpInstrumentation } from "@opentelemetry/instrumentation-http";
|
|
65
|
+
import { ExpressInstrumentation } from "@opentelemetry/instrumentation-express";
|
|
66
|
+
|
|
67
|
+
// otel/src/config.ts
|
|
68
|
+
import { ConsoleSpanExporter } from "@opentelemetry/sdk-trace-base";
|
|
69
|
+
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
|
|
70
|
+
import { OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-http";
|
|
71
|
+
import { PeriodicExportingMetricReader, ConsoleMetricExporter } from "@opentelemetry/sdk-metrics";
|
|
72
|
+
function isBrowser() {
|
|
73
|
+
return typeof window !== "undefined" && typeof process === "undefined";
|
|
74
|
+
}
|
|
75
|
+
function getConfigFromEnv() {
|
|
76
|
+
if (isBrowser()) {
|
|
77
|
+
return {
|
|
78
|
+
serviceName: "browser-service",
|
|
79
|
+
serviceVersion: void 0,
|
|
80
|
+
environment: "browser",
|
|
81
|
+
otlpEndpoint: void 0,
|
|
82
|
+
otlpHeaders: void 0,
|
|
83
|
+
traceEnabled: false,
|
|
84
|
+
metricsEnabled: false,
|
|
85
|
+
metricExportIntervalMillis: 6e4
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
const tracesExporter = process.env["OTEL_TRACES_EXPORTER"] || "console";
|
|
89
|
+
const metricsExporter = process.env["OTEL_METRICS_EXPORTER"] || "console";
|
|
90
|
+
return {
|
|
91
|
+
serviceName: process.env["OTEL_SERVICE_NAME"] || "unknown_service",
|
|
92
|
+
serviceVersion: process.env["OTEL_SERVICE_VERSION"],
|
|
93
|
+
environment: process.env["DEPLOYMENT_ENVIRONMENT"] || process.env["NODE_ENV"] || "development",
|
|
94
|
+
otlpEndpoint: process.env["OTEL_EXPORTER_OTLP_ENDPOINT"],
|
|
95
|
+
otlpHeaders: process.env["OTEL_EXPORTER_OTLP_HEADERS"] ? parseHeaders(process.env["OTEL_EXPORTER_OTLP_HEADERS"]) : void 0,
|
|
96
|
+
traceEnabled: tracesExporter !== "none",
|
|
97
|
+
metricsEnabled: metricsExporter !== "none",
|
|
98
|
+
metricExportIntervalMillis: process.env["OTEL_METRIC_EXPORT_INTERVAL"] ? parseInt(process.env["OTEL_METRIC_EXPORT_INTERVAL"], 10) : 6e4
|
|
99
|
+
// Default 60 seconds per OTEL spec
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
function parseHeaders(headerString) {
|
|
103
|
+
const headers = {};
|
|
104
|
+
headerString.split(",").forEach((header) => {
|
|
105
|
+
const [key, value] = header.split("=");
|
|
106
|
+
if (key && value) {
|
|
107
|
+
headers[key.trim()] = value.trim();
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
return headers;
|
|
111
|
+
}
|
|
112
|
+
function createTraceExporter(config) {
|
|
113
|
+
if (!config.traceEnabled) {
|
|
114
|
+
return void 0;
|
|
115
|
+
}
|
|
116
|
+
const tracesExporter = process.env["OTEL_TRACES_EXPORTER"] || "console";
|
|
117
|
+
switch (tracesExporter) {
|
|
118
|
+
case "otlp":
|
|
119
|
+
case "otlp/http": {
|
|
120
|
+
if (!config.otlpEndpoint) {
|
|
121
|
+
console.warn("OTEL_EXPORTER_OTLP_ENDPOINT not set, falling back to console");
|
|
122
|
+
return new ConsoleSpanExporter();
|
|
123
|
+
}
|
|
124
|
+
const tracesEndpoint = process.env["OTEL_EXPORTER_OTLP_TRACES_ENDPOINT"] || `${config.otlpEndpoint}/v1/traces`;
|
|
125
|
+
return new OTLPTraceExporter({
|
|
126
|
+
url: tracesEndpoint,
|
|
127
|
+
headers: config.otlpHeaders
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
case "console":
|
|
131
|
+
return new ConsoleSpanExporter();
|
|
132
|
+
case "none":
|
|
133
|
+
default:
|
|
134
|
+
return void 0;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
function createMetricReader(config) {
|
|
138
|
+
if (!config.metricsEnabled) {
|
|
139
|
+
return void 0;
|
|
140
|
+
}
|
|
141
|
+
const metricsExporter = process.env["OTEL_METRICS_EXPORTER"] || "console";
|
|
142
|
+
let exporter;
|
|
143
|
+
switch (metricsExporter) {
|
|
144
|
+
case "otlp":
|
|
145
|
+
case "otlp/http": {
|
|
146
|
+
if (!config.otlpEndpoint) {
|
|
147
|
+
console.warn("OTEL_EXPORTER_OTLP_ENDPOINT not set, falling back to console");
|
|
148
|
+
exporter = new ConsoleMetricExporter();
|
|
149
|
+
} else {
|
|
150
|
+
const metricsEndpoint = process.env["OTEL_EXPORTER_OTLP_METRICS_ENDPOINT"] || `${config.otlpEndpoint}/v1/metrics`;
|
|
151
|
+
exporter = new OTLPMetricExporter({
|
|
152
|
+
url: metricsEndpoint,
|
|
153
|
+
headers: config.otlpHeaders
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
break;
|
|
157
|
+
}
|
|
158
|
+
case "console":
|
|
159
|
+
exporter = new ConsoleMetricExporter();
|
|
160
|
+
break;
|
|
161
|
+
case "none":
|
|
162
|
+
default:
|
|
163
|
+
return void 0;
|
|
164
|
+
}
|
|
165
|
+
return new PeriodicExportingMetricReader({
|
|
166
|
+
exporter,
|
|
167
|
+
exportIntervalMillis: config.metricExportIntervalMillis
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// otel/src/sdk.ts
|
|
172
|
+
var sdk = null;
|
|
173
|
+
var isInitialized = false;
|
|
174
|
+
var initializationPromise = null;
|
|
175
|
+
function isBrowser2() {
|
|
176
|
+
return typeof window !== "undefined" && typeof process === "undefined";
|
|
177
|
+
}
|
|
178
|
+
function ensureInitialized() {
|
|
179
|
+
if (isInitialized || initializationPromise) {
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
if (isBrowser2()) {
|
|
183
|
+
isInitialized = true;
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
initializationPromise = Promise.resolve().then(() => {
|
|
187
|
+
const config = getConfigFromEnv();
|
|
188
|
+
if (process.env["OTEL_LOG_LEVEL"] === "debug") {
|
|
189
|
+
console.log("OpenTelemetry auto-initializing with config:", {
|
|
190
|
+
serviceName: config.serviceName,
|
|
191
|
+
environment: config.environment,
|
|
192
|
+
tracesExporter: process.env["OTEL_TRACES_EXPORTER"] || "console",
|
|
193
|
+
metricsExporter: process.env["OTEL_METRICS_EXPORTER"] || "console"
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
const attributes = {
|
|
197
|
+
[SEMRESATTRS_SERVICE_NAME]: config.serviceName
|
|
198
|
+
};
|
|
199
|
+
if (config.serviceVersion) {
|
|
200
|
+
attributes[SEMRESATTRS_SERVICE_VERSION] = config.serviceVersion;
|
|
201
|
+
}
|
|
202
|
+
if (config.environment) {
|
|
203
|
+
attributes[SEMRESATTRS_DEPLOYMENT_ENVIRONMENT] = config.environment;
|
|
204
|
+
}
|
|
205
|
+
const customResource = resourceFromAttributes(attributes);
|
|
206
|
+
const resource = defaultResource().merge(customResource);
|
|
207
|
+
const traceExporter = createTraceExporter(config);
|
|
208
|
+
const metricReader = createMetricReader(config);
|
|
209
|
+
const instrumentations = [
|
|
210
|
+
new HttpInstrumentation({
|
|
211
|
+
requestHook: (span, request) => {
|
|
212
|
+
const req = request;
|
|
213
|
+
if (req.headers) {
|
|
214
|
+
span.setAttribute("http.request.body.size", req.headers["content-length"] || 0);
|
|
215
|
+
}
|
|
216
|
+
},
|
|
217
|
+
responseHook: (span, response) => {
|
|
218
|
+
const res = response;
|
|
219
|
+
if (res.headers) {
|
|
220
|
+
span.setAttribute("http.response.body.size", res.headers["content-length"] || 0);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}),
|
|
224
|
+
new ExpressInstrumentation()
|
|
225
|
+
];
|
|
226
|
+
sdk = new NodeSDK({
|
|
227
|
+
resource,
|
|
228
|
+
spanProcessors: traceExporter ? [new BatchSpanProcessor(traceExporter)] : [],
|
|
229
|
+
metricReader,
|
|
230
|
+
instrumentations
|
|
231
|
+
});
|
|
232
|
+
sdk.start();
|
|
233
|
+
isInitialized = true;
|
|
234
|
+
process.on("SIGTERM", async () => {
|
|
235
|
+
try {
|
|
236
|
+
await shutdownOtel();
|
|
237
|
+
if (process.env["OTEL_LOG_LEVEL"] === "debug") {
|
|
238
|
+
console.log("OpenTelemetry terminated successfully");
|
|
239
|
+
}
|
|
240
|
+
} catch (error) {
|
|
241
|
+
console.error("Error terminating OpenTelemetry", error);
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
async function shutdownOtel() {
|
|
247
|
+
if (sdk) {
|
|
248
|
+
await sdk.shutdown();
|
|
249
|
+
sdk = null;
|
|
250
|
+
isInitialized = false;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
function isOtelInitialized() {
|
|
254
|
+
ensureInitialized();
|
|
255
|
+
return isInitialized;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// otel/src/tracer.ts
|
|
259
|
+
import {
|
|
260
|
+
trace,
|
|
261
|
+
SpanStatusCode,
|
|
262
|
+
context as otelContext
|
|
263
|
+
} from "@opentelemetry/api";
|
|
264
|
+
import { SpanKind, SpanStatusCode as SpanStatusCode2 } from "@opentelemetry/api";
|
|
265
|
+
var TRACER_NAME = "@reactionary/otel";
|
|
266
|
+
var TRACER_VERSION = "0.0.1";
|
|
267
|
+
var globalTracer = null;
|
|
268
|
+
function getTracer() {
|
|
269
|
+
if (!globalTracer) {
|
|
270
|
+
isOtelInitialized();
|
|
271
|
+
globalTracer = trace.getTracer(TRACER_NAME, TRACER_VERSION);
|
|
272
|
+
}
|
|
273
|
+
return globalTracer;
|
|
274
|
+
}
|
|
275
|
+
function withSpan(name, fn, options) {
|
|
276
|
+
const tracer = getTracer();
|
|
277
|
+
return tracer.startActiveSpan(name, options || {}, async (span) => {
|
|
278
|
+
try {
|
|
279
|
+
const result = await fn(span);
|
|
280
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
281
|
+
return result;
|
|
282
|
+
} catch (error) {
|
|
283
|
+
span.setStatus({
|
|
284
|
+
code: SpanStatusCode.ERROR,
|
|
285
|
+
message: error instanceof Error ? error.message : String(error)
|
|
286
|
+
});
|
|
287
|
+
if (error instanceof Error) {
|
|
288
|
+
span.recordException(error);
|
|
289
|
+
}
|
|
290
|
+
throw error;
|
|
291
|
+
} finally {
|
|
292
|
+
span.end();
|
|
293
|
+
}
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
function setSpanAttributes(span, attributes) {
|
|
297
|
+
Object.entries(attributes).forEach(([key, value]) => {
|
|
298
|
+
if (value !== void 0 && value !== null) {
|
|
299
|
+
span.setAttribute(key, value);
|
|
300
|
+
}
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// otel/src/metrics.ts
|
|
305
|
+
import { metrics } from "@opentelemetry/api";
|
|
306
|
+
var METER_NAME = "@reactionary/otel";
|
|
307
|
+
var METER_VERSION = "0.0.1";
|
|
308
|
+
var globalMeter = null;
|
|
309
|
+
function getMeter() {
|
|
310
|
+
if (!globalMeter) {
|
|
311
|
+
isOtelInitialized();
|
|
312
|
+
globalMeter = metrics.getMeter(METER_NAME, METER_VERSION);
|
|
313
|
+
}
|
|
314
|
+
return globalMeter;
|
|
315
|
+
}
|
|
316
|
+
var metricsInstance = null;
|
|
317
|
+
function initializeMetrics() {
|
|
318
|
+
if (metricsInstance) {
|
|
319
|
+
return metricsInstance;
|
|
320
|
+
}
|
|
321
|
+
const meter = getMeter();
|
|
322
|
+
metricsInstance = {
|
|
323
|
+
requestCounter: meter.createCounter("reactionary.requests", {
|
|
324
|
+
description: "Total number of requests"
|
|
325
|
+
}),
|
|
326
|
+
requestDuration: meter.createHistogram("reactionary.request.duration", {
|
|
327
|
+
description: "Request duration in milliseconds",
|
|
328
|
+
unit: "ms"
|
|
329
|
+
}),
|
|
330
|
+
activeRequests: meter.createUpDownCounter("reactionary.requests.active", {
|
|
331
|
+
description: "Number of active requests"
|
|
332
|
+
}),
|
|
333
|
+
errorCounter: meter.createCounter("reactionary.errors", {
|
|
334
|
+
description: "Total number of errors"
|
|
335
|
+
}),
|
|
336
|
+
providerCallCounter: meter.createCounter("reactionary.provider.calls", {
|
|
337
|
+
description: "Total number of provider calls"
|
|
338
|
+
}),
|
|
339
|
+
providerCallDuration: meter.createHistogram("reactionary.provider.duration", {
|
|
340
|
+
description: "Provider call duration in milliseconds",
|
|
341
|
+
unit: "ms"
|
|
342
|
+
}),
|
|
343
|
+
cacheHitCounter: meter.createCounter("reactionary.cache.hits", {
|
|
344
|
+
description: "Total number of cache hits"
|
|
345
|
+
}),
|
|
346
|
+
cacheMissCounter: meter.createCounter("reactionary.cache.misses", {
|
|
347
|
+
description: "Total number of cache misses"
|
|
348
|
+
})
|
|
349
|
+
};
|
|
350
|
+
return metricsInstance;
|
|
351
|
+
}
|
|
352
|
+
function getMetrics() {
|
|
353
|
+
if (!metricsInstance) {
|
|
354
|
+
return initializeMetrics();
|
|
355
|
+
}
|
|
356
|
+
return metricsInstance;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// otel/src/trpc-middleware.ts
|
|
360
|
+
import { TRPCError } from "@trpc/server";
|
|
361
|
+
import {
|
|
362
|
+
SpanKind as SpanKind2,
|
|
363
|
+
SpanStatusCode as SpanStatusCode3
|
|
364
|
+
} from "@opentelemetry/api";
|
|
365
|
+
|
|
366
|
+
// otel/src/provider-instrumentation.ts
|
|
367
|
+
import { SpanKind as SpanKind3 } from "@opentelemetry/api";
|
|
368
|
+
async function withProviderSpan(options, fn) {
|
|
369
|
+
const { providerName, operationType, operationName, attributes = {} } = options;
|
|
370
|
+
const metrics2 = getMetrics();
|
|
371
|
+
const spanName = `provider.${providerName}.${operationType}${operationName ? `.${operationName}` : ""}`;
|
|
372
|
+
const startTime = Date.now();
|
|
373
|
+
metrics2.providerCallCounter.add(1, {
|
|
374
|
+
"provider.name": providerName,
|
|
375
|
+
"provider.operation.type": operationType,
|
|
376
|
+
"provider.operation.name": operationName || "unknown"
|
|
377
|
+
});
|
|
378
|
+
return withSpan(
|
|
379
|
+
spanName,
|
|
380
|
+
async (span) => {
|
|
381
|
+
setSpanAttributes(span, {
|
|
382
|
+
"provider.name": providerName,
|
|
383
|
+
"provider.operation.type": operationType,
|
|
384
|
+
"provider.operation.name": operationName,
|
|
385
|
+
...attributes
|
|
386
|
+
});
|
|
387
|
+
try {
|
|
388
|
+
const result = await fn(span);
|
|
389
|
+
const duration = Date.now() - startTime;
|
|
390
|
+
metrics2.providerCallDuration.record(duration, {
|
|
391
|
+
"provider.name": providerName,
|
|
392
|
+
"provider.operation.type": operationType,
|
|
393
|
+
"provider.operation.name": operationName || "unknown",
|
|
394
|
+
"status": "success"
|
|
395
|
+
});
|
|
396
|
+
return result;
|
|
397
|
+
} catch (error) {
|
|
398
|
+
const duration = Date.now() - startTime;
|
|
399
|
+
metrics2.providerCallDuration.record(duration, {
|
|
400
|
+
"provider.name": providerName,
|
|
401
|
+
"provider.operation.type": operationType,
|
|
402
|
+
"provider.operation.name": operationName || "unknown",
|
|
403
|
+
"status": "error"
|
|
404
|
+
});
|
|
405
|
+
metrics2.errorCounter.add(1, {
|
|
406
|
+
"provider.name": providerName,
|
|
407
|
+
"provider.operation.type": operationType,
|
|
408
|
+
"provider.operation.name": operationName || "unknown"
|
|
409
|
+
});
|
|
410
|
+
throw error;
|
|
411
|
+
}
|
|
412
|
+
},
|
|
413
|
+
{ kind: SpanKind3.CLIENT }
|
|
414
|
+
);
|
|
415
|
+
}
|
|
416
|
+
function createProviderInstrumentation(providerName) {
|
|
417
|
+
return {
|
|
418
|
+
traceQuery: (operationName, fn, attributes) => {
|
|
419
|
+
return withProviderSpan(
|
|
420
|
+
{
|
|
421
|
+
providerName,
|
|
422
|
+
operationType: "query",
|
|
423
|
+
operationName,
|
|
424
|
+
attributes
|
|
425
|
+
},
|
|
426
|
+
fn
|
|
427
|
+
);
|
|
428
|
+
},
|
|
429
|
+
traceMutation: (operationName, fn, attributes) => {
|
|
430
|
+
return withProviderSpan(
|
|
431
|
+
{
|
|
432
|
+
providerName,
|
|
433
|
+
operationType: "mutation",
|
|
434
|
+
operationName,
|
|
435
|
+
attributes
|
|
436
|
+
},
|
|
437
|
+
fn
|
|
438
|
+
);
|
|
439
|
+
}
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// otel/src/index.ts
|
|
444
|
+
import { trace as trace2, context, SpanKind as SpanKind4, SpanStatusCode as SpanStatusCode4 } from "@opentelemetry/api";
|
|
445
|
+
|
|
57
446
|
// core/src/providers/base.provider.ts
|
|
58
447
|
var BaseProvider = class {
|
|
59
448
|
constructor(schema, querySchema, mutationSchema) {
|
|
60
449
|
this.schema = schema;
|
|
61
450
|
this.querySchema = querySchema;
|
|
62
451
|
this.mutationSchema = mutationSchema;
|
|
452
|
+
this.instrumentation = createProviderInstrumentation(this.constructor.name);
|
|
63
453
|
}
|
|
64
454
|
/**
|
|
65
455
|
* Validates that the final domain model constructed by the provider
|
|
@@ -81,20 +471,35 @@ var BaseProvider = class {
|
|
|
81
471
|
* of the results will match the order of the queries.
|
|
82
472
|
*/
|
|
83
473
|
async query(queries, session) {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
474
|
+
return this.instrumentation.traceQuery(
|
|
475
|
+
"query",
|
|
476
|
+
async (span) => {
|
|
477
|
+
span.setAttribute("provider.query.count", queries.length);
|
|
478
|
+
const results = await this.fetch(queries, session);
|
|
479
|
+
for (const result of results) {
|
|
480
|
+
this.assert(result);
|
|
481
|
+
}
|
|
482
|
+
span.setAttribute("provider.result.count", results.length);
|
|
483
|
+
return results;
|
|
484
|
+
},
|
|
485
|
+
{ queryCount: queries.length }
|
|
486
|
+
);
|
|
89
487
|
}
|
|
90
488
|
/**
|
|
91
489
|
* Executes the listed mutations in order and returns the final state
|
|
92
490
|
* resulting from that set of operations.
|
|
93
491
|
*/
|
|
94
492
|
async mutate(mutations, session) {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
493
|
+
return this.instrumentation.traceMutation(
|
|
494
|
+
"mutate",
|
|
495
|
+
async (span) => {
|
|
496
|
+
span.setAttribute("provider.mutation.count", mutations.length);
|
|
497
|
+
const result = await this.process(mutations, session);
|
|
498
|
+
this.assert(result);
|
|
499
|
+
return result;
|
|
500
|
+
},
|
|
501
|
+
{ mutationCount: mutations.length }
|
|
502
|
+
);
|
|
98
503
|
}
|
|
99
504
|
};
|
|
100
505
|
|
package/package.json
CHANGED
|
@@ -11,6 +11,7 @@ export declare abstract class BaseProvider<T extends BaseModel = BaseModel, Q ex
|
|
|
11
11
|
readonly schema: z.ZodType<T>;
|
|
12
12
|
readonly querySchema: z.ZodType<Q, Q>;
|
|
13
13
|
readonly mutationSchema: z.ZodType<M, M>;
|
|
14
|
+
private instrumentation;
|
|
14
15
|
constructor(schema: z.ZodType<T>, querySchema: z.ZodType<Q, Q>, mutationSchema: z.ZodType<M, M>);
|
|
15
16
|
/**
|
|
16
17
|
* Validates that the final domain model constructed by the provider
|