@modainteract/moda-interact-shared 0.1.0 → 0.3.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/README.md +709 -58
- package/dist/chunk-T6DNWLWW.js +303 -0
- package/dist/chunk-T6DNWLWW.js.map +1 -0
- package/dist/chunk-XU4XX7SD.js +27 -0
- package/dist/chunk-XU4XX7SD.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +43 -7
- package/dist/logging/index.d.ts +45 -0
- package/dist/logging/index.js +241 -0
- package/dist/logging/index.js.map +1 -0
- package/dist/logging/node.d.ts +103 -0
- package/dist/logging/node.js +373 -0
- package/dist/logging/node.js.map +1 -0
- package/dist/shopify/index.d.ts +266 -10
- package/dist/shopify/index.js +43 -7
- package/dist/shopify/node.d.ts +6 -3
- package/dist/shopify/node.js +22 -7
- package/dist/shopify/node.js.map +1 -1
- package/package.json +17 -2
- package/dist/chunk-NE5RMNXU.js +0 -142
- package/dist/chunk-NE5RMNXU.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,67 +1,718 @@
|
|
|
1
1
|
# @modainteract/moda-interact-shared
|
|
2
2
|
|
|
3
|
-
Shared
|
|
4
|
-
Interact
|
|
5
|
-
|
|
3
|
+
Shared TypeScript contracts and reusable platform primitives used by multiple
|
|
4
|
+
Moda Interact services.
|
|
5
|
+
|
|
6
|
+
This package is a **library**, not a deployable service. It does not own
|
|
7
|
+
transport, persistence, background workers, provider SDK integration, or
|
|
8
|
+
application business workflows.
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npm install @modainteract/moda-interact-shared
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Production services should use the exact package version selected by the
|
|
17
|
+
corresponding Moda Interact architecture/release task rather than relying on a
|
|
18
|
+
floating version.
|
|
6
19
|
|
|
7
20
|
## What this package owns
|
|
8
21
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
22
|
+
The package contains code that genuinely belongs at a cross-service boundary,
|
|
23
|
+
including:
|
|
24
|
+
|
|
25
|
+
- runtime-validated cross-service event contracts;
|
|
26
|
+
- TypeScript types shared by producers and consumers;
|
|
27
|
+
- schema and event-version constants;
|
|
28
|
+
- canonical queue/job-name constants where multiple repositories must agree;
|
|
29
|
+
- deterministic correlation/job identifier helpers;
|
|
30
|
+
- small pure utilities used by more than one service;
|
|
31
|
+
- reusable structured application logging primitives.
|
|
32
|
+
|
|
33
|
+
The package deliberately keeps framework and runtime dependencies small.
|
|
34
|
+
|
|
35
|
+
## What this package does not own
|
|
36
|
+
|
|
37
|
+
This package does **not** own:
|
|
38
|
+
|
|
39
|
+
- Prisma models or database migrations;
|
|
40
|
+
- PostgreSQL persistence;
|
|
41
|
+
- BullMQ/Redis clients or worker processes;
|
|
42
|
+
- Shopify or Meta webhook HTTP handlers;
|
|
43
|
+
- Shopify or Meta SDK integration;
|
|
44
|
+
- application recovery/business logic;
|
|
45
|
+
- OpenTelemetry spans or application-specific metrics;
|
|
46
|
+
- OpenTelemetry tracing/metrics SDK bootstraps;
|
|
47
|
+
- deployment OTLP endpoint/credential wiring or observability backends;
|
|
48
|
+
- Render deployment configuration.
|
|
49
|
+
|
|
50
|
+
Those capabilities remain in the repository that owns the corresponding
|
|
51
|
+
runtime concern.
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
# Shopify contracts
|
|
56
|
+
|
|
57
|
+
Moda Interact uses the shared Shopify package boundary so webhook producers and
|
|
58
|
+
background consumers do not independently invent compatible-looking event
|
|
59
|
+
types.
|
|
60
|
+
|
|
61
|
+
Import Shopify contracts from:
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
import {
|
|
65
|
+
// schemas, parsers, constants and types
|
|
66
|
+
} from "@modainteract/moda-interact-shared/shopify";
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Node-only Shopify helpers are isolated under:
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
import {
|
|
73
|
+
createShopifyWebhookJobId,
|
|
74
|
+
} from "@modainteract/moda-interact-shared/shopify/node";
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
The Node-only subpath prevents `node:` built-ins from being accidentally pulled
|
|
78
|
+
into consumers that only need the browser-safe Shopify contract entry point.
|
|
79
|
+
|
|
80
|
+
## Recovery-focused events
|
|
81
|
+
|
|
82
|
+
The current recovery architecture defines distinct event meanings for:
|
|
83
|
+
|
|
84
|
+
```text
|
|
85
|
+
checkout.created
|
|
86
|
+
checkout.updated
|
|
87
|
+
order.completed
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Pre-recovery checkout events deliberately avoid transporting customer identity,
|
|
91
|
+
line-item, pricing and address data merely because those values existed in the
|
|
92
|
+
provider webhook.
|
|
93
|
+
|
|
94
|
+
Consumers must parse/validate cross-service data with the shared runtime
|
|
95
|
+
contracts before acting on it. TypeScript types alone are not a trust boundary.
|
|
96
|
+
|
|
97
|
+
## Contract versioning
|
|
98
|
+
|
|
99
|
+
Serialized event schema versions and the npm package version are separate
|
|
100
|
+
concepts.
|
|
101
|
+
|
|
102
|
+
A breaking change to an existing serialized event contract requires an
|
|
103
|
+
appropriate event-schema version change and coordinated producer/consumer
|
|
104
|
+
rollout.
|
|
105
|
+
|
|
106
|
+
An additive npm package capability, such as a new independent package export,
|
|
107
|
+
does not by itself require changing an unrelated serialized event schema.
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
# Structured logging
|
|
112
|
+
|
|
113
|
+
Moda Interact runtime services use one reusable structured logging primitive:
|
|
114
|
+
|
|
115
|
+
```ts
|
|
116
|
+
import {
|
|
117
|
+
createLogger,
|
|
118
|
+
type StructuredLogger,
|
|
119
|
+
type LogFields,
|
|
120
|
+
} from "@modainteract/moda-interact-shared/logging";
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Create a logger
|
|
124
|
+
|
|
125
|
+
Each deployable service/process supplies its own identity and environment:
|
|
126
|
+
|
|
127
|
+
```ts
|
|
128
|
+
const logger = createLogger({
|
|
129
|
+
serviceName: "moda-interact",
|
|
130
|
+
environment: "test",
|
|
131
|
+
});
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
The default namespace is:
|
|
135
|
+
|
|
136
|
+
```text
|
|
137
|
+
moda-interact
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Logs contain the canonical identity:
|
|
141
|
+
|
|
142
|
+
```text
|
|
143
|
+
service.namespace=moda-interact
|
|
144
|
+
service.name=<service/process name>
|
|
145
|
+
deployment.environment.name=<environment>
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
For deployed Moda Interact environments, the expected environment values are:
|
|
149
|
+
|
|
150
|
+
```text
|
|
151
|
+
test
|
|
152
|
+
production
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
The shared library does not read a service's environment variables for it.
|
|
156
|
+
The owning service resolves its configuration and passes the resulting
|
|
157
|
+
environment to `createLogger`.
|
|
158
|
+
|
|
159
|
+
## Write logs
|
|
160
|
+
|
|
161
|
+
```ts
|
|
162
|
+
logger.info("shopify.webhook.outcome", {
|
|
163
|
+
topic: "checkouts/create",
|
|
164
|
+
outcome: "ENQUEUED",
|
|
165
|
+
ackMs: 18,
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
logger.warn("queue.job.retry", {
|
|
169
|
+
queue: "recovery",
|
|
170
|
+
attempt: 2,
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
logger.error("recovery.failed", {
|
|
174
|
+
recoveryId,
|
|
175
|
+
error,
|
|
176
|
+
});
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
The default sink emits one structured JSON record per console call.
|
|
180
|
+
|
|
181
|
+
Example:
|
|
182
|
+
|
|
183
|
+
```json
|
|
184
|
+
{
|
|
185
|
+
"timestamp": "2026-08-30T12:00:00.000Z",
|
|
186
|
+
"level": "info",
|
|
187
|
+
"event": "shopify.webhook.outcome",
|
|
188
|
+
"service.namespace": "moda-interact",
|
|
189
|
+
"service.name": "moda-interact",
|
|
190
|
+
"deployment.environment.name": "test",
|
|
191
|
+
"data": {
|
|
192
|
+
"topic": "checkouts/create",
|
|
193
|
+
"outcome": "ENQUEUED",
|
|
194
|
+
"ackMs": 18
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
## Log levels
|
|
200
|
+
|
|
201
|
+
Available levels are:
|
|
202
|
+
|
|
203
|
+
```text
|
|
204
|
+
debug
|
|
205
|
+
info
|
|
206
|
+
warn
|
|
207
|
+
error
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
Use stable, machine-readable event names such as:
|
|
211
|
+
|
|
212
|
+
```text
|
|
213
|
+
shopify.webhook.outcome
|
|
214
|
+
meta.webhook.outcome
|
|
215
|
+
queue.job.started
|
|
216
|
+
queue.job.completed
|
|
217
|
+
queue.job.failed
|
|
218
|
+
recovery.materialized
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
## Child loggers
|
|
222
|
+
|
|
223
|
+
`child()` is optional.
|
|
224
|
+
|
|
225
|
+
It creates another logger with some repeated fields already attached. It does
|
|
226
|
+
**not** create a worker, queue, process, connection, or logging service.
|
|
227
|
+
|
|
228
|
+
Instead of repeatedly writing:
|
|
229
|
+
|
|
230
|
+
```ts
|
|
231
|
+
logger.info("queue.job.started", {
|
|
232
|
+
queue: "recovery",
|
|
233
|
+
jobId,
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
logger.info("queue.job.completed", {
|
|
237
|
+
queue: "recovery",
|
|
238
|
+
jobId,
|
|
239
|
+
});
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
you may write:
|
|
243
|
+
|
|
244
|
+
```ts
|
|
245
|
+
const recoveryLogger = logger.child({
|
|
246
|
+
queue: "recovery",
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
recoveryLogger.info("queue.job.started", {
|
|
250
|
+
jobId,
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
recoveryLogger.info("queue.job.completed", {
|
|
254
|
+
jobId,
|
|
255
|
+
});
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
Both records automatically contain:
|
|
259
|
+
|
|
260
|
+
```json
|
|
261
|
+
{
|
|
262
|
+
"queue": "recovery"
|
|
263
|
+
}
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
Use child context only for stable, safe operational fields.
|
|
267
|
+
|
|
268
|
+
## Sensitive data
|
|
269
|
+
|
|
270
|
+
The logger provides defense-in-depth redaction and bounded serialization, but
|
|
271
|
+
callers remain responsible for constructing safe, explicit log fields.
|
|
272
|
+
|
|
273
|
+
Do not intentionally log:
|
|
274
|
+
|
|
275
|
+
- access or refresh tokens;
|
|
276
|
+
- authorization headers;
|
|
277
|
+
- cookies;
|
|
278
|
+
- OAuth codes;
|
|
279
|
+
- passwords or API keys;
|
|
280
|
+
- private keys;
|
|
281
|
+
- webhook verification secrets;
|
|
282
|
+
- complete webhook/request/response payloads;
|
|
283
|
+
- customer names, email addresses, phone numbers or postal addresses;
|
|
284
|
+
- payment/card data.
|
|
285
|
+
|
|
286
|
+
Do not pass a complete provider/customer object to the logger and rely on
|
|
287
|
+
redaction to make it safe.
|
|
288
|
+
|
|
289
|
+
## Errors
|
|
290
|
+
|
|
291
|
+
Errors can be logged as values:
|
|
292
|
+
|
|
293
|
+
```ts
|
|
294
|
+
logger.error("queue.job.failed", {
|
|
295
|
+
jobId,
|
|
296
|
+
error,
|
|
297
|
+
});
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
The shared logger safely serializes `Error` objects without emitting stack
|
|
301
|
+
traces by default.
|
|
302
|
+
|
|
303
|
+
Logging is best-effort: a logger sink or serialization failure must not alter
|
|
304
|
+
the success/failure semantics of the business operation being logged.
|
|
305
|
+
|
|
306
|
+
## Logging destinations
|
|
307
|
+
|
|
308
|
+
Every call to the shared logger:
|
|
309
|
+
|
|
310
|
+
```ts
|
|
311
|
+
logger.debug(...)
|
|
312
|
+
logger.info(...)
|
|
313
|
+
logger.warn(...)
|
|
314
|
+
logger.error(...)
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
emits the same already-sanitized canonical record to up to three independent,
|
|
318
|
+
best-effort destinations:
|
|
319
|
+
|
|
320
|
+
1. canonical structured JSON to the configured sink (default: console);
|
|
321
|
+
2. the OpenTelemetry Logs API via the global `LoggerProvider`, if one has been
|
|
322
|
+
installed;
|
|
323
|
+
3. a direct Grafana Loki transport, if the Node process bootstrap has installed
|
|
324
|
+
the Loki emitter.
|
|
325
|
+
|
|
326
|
+
Each destination is failure-isolated: a failure in one must not suppress
|
|
327
|
+
another and must never affect application/business correctness.
|
|
328
|
+
|
|
329
|
+
The OpenTelemetry API is a no-op when no provider is installed, so stdout
|
|
330
|
+
logging continues to work in local/test environments without any backend.
|
|
331
|
+
|
|
332
|
+
The heavy Node SDK/exporter and the Winston/winston-loki transport are isolated
|
|
333
|
+
in:
|
|
334
|
+
|
|
335
|
+
```text
|
|
336
|
+
@modainteract/moda-interact-shared/logging/node
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
and must be imported and initialized only from the Node process bootstrap —
|
|
340
|
+
never through Vite/application/browser bundles.
|
|
341
|
+
|
|
342
|
+
```text
|
|
343
|
+
application module
|
|
344
|
+
|
|
|
345
|
+
v
|
|
346
|
+
@modainteract/moda-interact-shared/logging
|
|
347
|
+
|
|
|
348
|
+
+--> canonical JSON stdout
|
|
349
|
+
|
|
|
350
|
+
+--> @opentelemetry/api-logs
|
|
351
|
+
| |
|
|
352
|
+
| | global LoggerProvider
|
|
353
|
+
| v
|
|
354
|
+
| ./logging/node
|
|
355
|
+
| |
|
|
356
|
+
| +--> LoggerProvider
|
|
357
|
+
| +--> BatchLogRecordProcessor
|
|
358
|
+
| +--> OTLPLogExporter
|
|
359
|
+
| |
|
|
360
|
+
| v
|
|
361
|
+
| /v1/logs
|
|
362
|
+
|
|
|
363
|
+
+--> lightweight Loki bridge (globalThis emitter)
|
|
364
|
+
|
|
|
365
|
+
v
|
|
366
|
+
./logging/node
|
|
367
|
+
|
|
|
368
|
+
+--> Winston + winston-loki
|
|
369
|
+
|
|
|
370
|
+
v
|
|
371
|
+
Grafana Loki
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
Winston and winston-loki exist **only** in the Node-only package graph. The
|
|
375
|
+
normal `./logging` entry contains no Winston, winston-loki or Node networking
|
|
376
|
+
imports.
|
|
377
|
+
|
|
378
|
+
### Node bootstrap
|
|
379
|
+
|
|
380
|
+
Process bootstrap code installs the optional destinations once:
|
|
381
|
+
|
|
382
|
+
```ts
|
|
383
|
+
import {
|
|
384
|
+
initNodeLokiLogging,
|
|
385
|
+
initNodeOpenTelemetryLogging,
|
|
386
|
+
} from "@modainteract/moda-interact-shared/logging/node";
|
|
387
|
+
|
|
388
|
+
initNodeLokiLogging({
|
|
389
|
+
serviceName: "moda-interact",
|
|
390
|
+
environment: "production",
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
initNodeOpenTelemetryLogging({
|
|
394
|
+
serviceName: "moda-interact",
|
|
395
|
+
environment: "production",
|
|
396
|
+
});
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
Each destination is independently configurable, so a deployment may use:
|
|
400
|
+
|
|
401
|
+
```text
|
|
402
|
+
console only
|
|
403
|
+
|
|
404
|
+
console + Loki
|
|
405
|
+
|
|
406
|
+
console + OTel Logs
|
|
407
|
+
|
|
408
|
+
console + Loki + OTel Logs
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
Either Node destination may be omitted entirely; without an installed
|
|
412
|
+
provider/emitter the logger remains a safe stdout-only logger.
|
|
413
|
+
|
|
414
|
+
The bootstrap supports the standard OpenTelemetry environment variables:
|
|
415
|
+
|
|
416
|
+
```text
|
|
417
|
+
OTEL_SDK_DISABLED
|
|
418
|
+
|
|
419
|
+
OTEL_LOGS_EXPORTER
|
|
420
|
+
|
|
421
|
+
OTEL_EXPORTER_OTLP_LOGS_ENDPOINT
|
|
422
|
+
OTEL_EXPORTER_OTLP_ENDPOINT
|
|
423
|
+
|
|
424
|
+
OTEL_EXPORTER_OTLP_LOGS_HEADERS
|
|
425
|
+
OTEL_EXPORTER_OTLP_HEADERS
|
|
426
|
+
|
|
427
|
+
OTEL_BLRP_MAX_QUEUE_SIZE
|
|
428
|
+
OTEL_BLRP_MAX_EXPORT_BATCH_SIZE
|
|
429
|
+
OTEL_BLRP_SCHEDULE_DELAY
|
|
430
|
+
OTEL_BLRP_EXPORT_TIMEOUT
|
|
431
|
+
|
|
432
|
+
OTEL_LOG_EXPORT_CONCURRENCY_LIMIT
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
Endpoint rules:
|
|
436
|
+
|
|
437
|
+
```text
|
|
438
|
+
OTEL_EXPORTER_OTLP_LOGS_ENDPOINT
|
|
439
|
+
exact signal endpoint; do not append /v1/logs
|
|
440
|
+
|
|
441
|
+
OTEL_EXPORTER_OTLP_ENDPOINT
|
|
442
|
+
generic endpoint; append /v1/logs
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
Moda deliberately does **not** export to the OpenTelemetry default localhost
|
|
446
|
+
endpoint. Without an explicitly configured endpoint the logger remains
|
|
447
|
+
stdout-only.
|
|
448
|
+
|
|
449
|
+
### Direct Loki transport
|
|
450
|
+
|
|
451
|
+
The direct Loki transport is a lightweight bridge: the normal `./logging` entry
|
|
452
|
+
stores an emitter on `globalThis` and the Node bootstrap installs it. Records
|
|
453
|
+
still pass through the same shared redaction/sanitization used for stdout and
|
|
454
|
+
OpenTelemetry.
|
|
455
|
+
|
|
456
|
+
The Loki environment contract is:
|
|
457
|
+
|
|
458
|
+
```text
|
|
459
|
+
LOKI_ENABLED
|
|
460
|
+
|
|
461
|
+
LOKI_URL
|
|
462
|
+
|
|
463
|
+
LOKI_USERNAME
|
|
464
|
+
LOKI_PASSWORD
|
|
465
|
+
|
|
466
|
+
LOKI_BATCHING
|
|
467
|
+
LOKI_BATCH_INTERVAL_SECONDS
|
|
468
|
+
LOKI_TIMEOUT_MS
|
|
469
|
+
LOKI_CLEAR_ON_ERROR
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
Rules:
|
|
473
|
+
|
|
474
|
+
```text
|
|
475
|
+
no LOKI_URL
|
|
476
|
+
-> direct Loki disabled
|
|
477
|
+
|
|
478
|
+
LOKI_ENABLED=false
|
|
479
|
+
-> direct Loki disabled
|
|
480
|
+
|
|
481
|
+
LOKI_URL present
|
|
482
|
+
-> direct Loki enabled by default
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
Credentials are Node-bootstrap configuration only. Never log `LOKI_URL`
|
|
486
|
+
credentials, `LOKI_USERNAME`, `LOKI_PASSWORD`, Basic Auth values or connection
|
|
487
|
+
headers.
|
|
488
|
+
|
|
489
|
+
Default transport behavior:
|
|
490
|
+
|
|
491
|
+
```text
|
|
492
|
+
batching=true
|
|
493
|
+
bounded timeout (LOKI_TIMEOUT_MS)
|
|
494
|
+
clearOnError=true
|
|
495
|
+
replaceTimestamp=true
|
|
496
|
+
gracefulShutdown=false
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
The owning process bootstrap is responsible for explicit flush/shutdown via the
|
|
500
|
+
returned runtime (`forceFlush`/`shutdown`). If Loki is unavailable, log records
|
|
501
|
+
may be dropped but application processing must continue. Connection errors are
|
|
502
|
+
not recursively written back through the shared logger.
|
|
503
|
+
|
|
504
|
+
### Loki labels and cardinality
|
|
505
|
+
|
|
506
|
+
The only default Loki labels are the canonical low-cardinality identity:
|
|
507
|
+
|
|
508
|
+
```text
|
|
509
|
+
service_namespace
|
|
510
|
+
service_name
|
|
511
|
+
environment
|
|
512
|
+
```
|
|
513
|
+
|
|
514
|
+
Dynamic/high-cardinality identifiers must **not** be promoted to Loki stream
|
|
515
|
+
labels. The implementation explicitly retains
|
|
516
|
+
`useWinstonMetaAsLabels: false`. In particular, do not label by:
|
|
517
|
+
|
|
518
|
+
```text
|
|
519
|
+
traceId
|
|
520
|
+
spanId
|
|
521
|
+
requestId
|
|
522
|
+
jobId
|
|
523
|
+
deliveryId
|
|
524
|
+
eventId
|
|
525
|
+
recoveryId
|
|
526
|
+
checkoutToken
|
|
527
|
+
cartToken
|
|
528
|
+
shop
|
|
529
|
+
shopDomain
|
|
530
|
+
customerId
|
|
531
|
+
phone
|
|
532
|
+
email
|
|
533
|
+
```
|
|
534
|
+
|
|
535
|
+
Safe high-cardinality operational identifiers may remain in the sanitized JSON
|
|
536
|
+
log body where justified, but they must never become indexed stream labels.
|
|
537
|
+
|
|
538
|
+
### Duplicate-delivery warning
|
|
539
|
+
|
|
540
|
+
Do not route the OpenTelemetry Logs destination back into the same Loki
|
|
541
|
+
instance while direct Loki is also enabled, unless duplicate storage is
|
|
542
|
+
intentional:
|
|
543
|
+
|
|
544
|
+
```text
|
|
545
|
+
direct Loki ON
|
|
546
|
+
+
|
|
547
|
+
OTel Logs ultimately routed to same Loki
|
|
548
|
+
=
|
|
549
|
+
duplicate log storage
|
|
550
|
+
```
|
|
551
|
+
|
|
552
|
+
The library does not guess backend topology; deployment configuration owns this
|
|
553
|
+
choice.
|
|
554
|
+
|
|
555
|
+
### Log-signal data safety and correlation
|
|
556
|
+
|
|
557
|
+
- The OpenTelemetry log body is `JSON.stringify` of the same canonical
|
|
558
|
+
sanitized `LogRecord` written to stdout, so the same redaction boundary
|
|
559
|
+
applies to both destinations.
|
|
560
|
+
- Queryable OTel attributes are limited to:
|
|
561
|
+
|
|
562
|
+
```text
|
|
563
|
+
event.name
|
|
564
|
+
log.level
|
|
565
|
+
service.namespace
|
|
566
|
+
service.name
|
|
567
|
+
deployment.environment.name
|
|
568
|
+
```
|
|
569
|
+
|
|
570
|
+
- The shared logger never manufactures trace IDs. If an active
|
|
571
|
+
trace/span context exists, the OpenTelemetry Logs SDK associates the emitted
|
|
572
|
+
record with that active context automatically.
|
|
573
|
+
- stdout and OpenTelemetry emission are isolated and best-effort: a failure in
|
|
574
|
+
either must not change application/business correctness.
|
|
575
|
+
|
|
576
|
+
### Tracing/metrics remain service-specific
|
|
577
|
+
|
|
578
|
+
The shared logger does **not** create spans, counters or histograms.
|
|
579
|
+
|
|
580
|
+
Service-specific OpenTelemetry tracing and metrics remain in the owning runtime
|
|
581
|
+
repository, for example:
|
|
582
|
+
|
|
583
|
+
```text
|
|
584
|
+
Shopify operation
|
|
585
|
+
|
|
|
586
|
+
+--> shared structured logger (stdout + OTel Logs)
|
|
587
|
+
|
|
|
588
|
+
+--> Shopify-specific OpenTelemetry (spans/metrics)
|
|
589
|
+
```
|
|
590
|
+
|
|
591
|
+
This separation is intentional. Fields that are useful in diagnostic logs can
|
|
592
|
+
have very different cardinality constraints from metric attributes.
|
|
593
|
+
|
|
594
|
+
---
|
|
595
|
+
|
|
596
|
+
# Package exports
|
|
597
|
+
|
|
598
|
+
The public package exposes independent entry points.
|
|
599
|
+
|
|
600
|
+
```text
|
|
601
|
+
@modainteract/moda-interact-shared
|
|
602
|
+
existing root exports
|
|
603
|
+
|
|
604
|
+
@modainteract/moda-interact-shared/shopify
|
|
605
|
+
Shopify cross-service contracts, schemas, constants and pure helpers
|
|
606
|
+
|
|
607
|
+
@modainteract/moda-interact-shared/shopify/node
|
|
608
|
+
Node-only Shopify helpers
|
|
609
|
+
|
|
610
|
+
@modainteract/moda-interact-shared/logging
|
|
611
|
+
reusable structured logging API
|
|
612
|
+
(stdout + OTel Logs + direct Loki fan-out)
|
|
613
|
+
|
|
614
|
+
@modainteract/moda-interact-shared/logging/node
|
|
615
|
+
Node-only logging bootstrap
|
|
616
|
+
(OpenTelemetry LoggerProvider, batching, OTLP HTTP exporter;
|
|
617
|
+
Winston + winston-loki direct transport)
|
|
618
|
+
```
|
|
619
|
+
|
|
620
|
+
Consumers should prefer the narrowest appropriate subpath.
|
|
621
|
+
|
|
622
|
+
---
|
|
623
|
+
|
|
624
|
+
# Ownership boundary
|
|
625
|
+
|
|
626
|
+
Examples of repository ownership:
|
|
627
|
+
|
|
628
|
+
**moda-interact**
|
|
629
|
+
|
|
630
|
+
- authenticates Shopify ingress;
|
|
631
|
+
- normalizes/validates Shopify events;
|
|
632
|
+
- publishes the appropriate queue jobs;
|
|
633
|
+
- uses the shared structured logger for generic log mechanics;
|
|
634
|
+
- owns Shopify-specific observability semantics and OpenTelemetry.
|
|
635
|
+
|
|
636
|
+
**moda-interact-background**
|
|
637
|
+
|
|
638
|
+
- parses shared cross-service contracts before acting;
|
|
639
|
+
- runs BullMQ workers and recovery workflows;
|
|
640
|
+
- uses the shared logger for generic structured logging;
|
|
641
|
+
- owns worker/recovery OpenTelemetry.
|
|
642
|
+
|
|
643
|
+
**moda-interact-messaging**
|
|
644
|
+
|
|
645
|
+
- owns Meta/WhatsApp ingress;
|
|
646
|
+
- uses the shared logger for generic structured logging;
|
|
647
|
+
- owns Meta/WhatsApp-specific observability and OpenTelemetry.
|
|
648
|
+
|
|
649
|
+
**moda-interact-admin**
|
|
650
|
+
|
|
651
|
+
- owns the internal admin application;
|
|
652
|
+
- may use the same shared logger where server-side operational logging is
|
|
653
|
+
required;
|
|
654
|
+
- owns admin-specific application telemetry.
|
|
655
|
+
|
|
656
|
+
**moda-interact-database**
|
|
657
|
+
|
|
658
|
+
- owns Prisma schema/migrations and database artifacts;
|
|
659
|
+
- does not duplicate shared TypeScript transport contracts.
|
|
660
|
+
|
|
661
|
+
---
|
|
662
|
+
|
|
663
|
+
# Development
|
|
664
|
+
|
|
665
|
+
## Typecheck
|
|
666
|
+
|
|
667
|
+
```bash
|
|
64
668
|
npm run typecheck
|
|
669
|
+
```
|
|
670
|
+
|
|
671
|
+
## Build
|
|
672
|
+
|
|
673
|
+
```bash
|
|
65
674
|
npm run build
|
|
675
|
+
```
|
|
676
|
+
|
|
677
|
+
## Test
|
|
678
|
+
|
|
679
|
+
```bash
|
|
66
680
|
npm test
|
|
67
681
|
```
|
|
682
|
+
|
|
683
|
+
## Inspect the publish artifact
|
|
684
|
+
|
|
685
|
+
Before publishing:
|
|
686
|
+
|
|
687
|
+
```bash
|
|
688
|
+
npm pack --dry-run
|
|
689
|
+
```
|
|
690
|
+
|
|
691
|
+
Verify that the intended JavaScript and type declarations for every public
|
|
692
|
+
subpath are included.
|
|
693
|
+
|
|
694
|
+
---
|
|
695
|
+
|
|
696
|
+
# Release policy
|
|
697
|
+
|
|
698
|
+
The npm package version follows semantic versioning.
|
|
699
|
+
|
|
700
|
+
Examples:
|
|
701
|
+
|
|
702
|
+
```text
|
|
703
|
+
patch
|
|
704
|
+
backwards-compatible fix to an existing package capability
|
|
705
|
+
|
|
706
|
+
minor
|
|
707
|
+
backwards-compatible new public capability/export
|
|
708
|
+
|
|
709
|
+
major
|
|
710
|
+
breaking public API/package compatibility change
|
|
711
|
+
```
|
|
712
|
+
|
|
713
|
+
The addition of the independent `./logging` public API is an additive package
|
|
714
|
+
capability and should therefore be released as a **minor** version increment.
|
|
715
|
+
|
|
716
|
+
Publishing the package and changing consuming services are coordinated tasks;
|
|
717
|
+
a consuming service must not assume an unpublished local shared export exists
|
|
718
|
+
in the npm artifact it installs.
|