@birtalanrobert/commerce 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.
- package/CHANGELOG.md +601 -0
- package/LICENSE +661 -0
- package/NOTICE +45 -0
- package/README.md +79 -0
- package/dist/deposits.d.ts +52 -0
- package/dist/deposits.d.ts.map +1 -0
- package/dist/deposits.js +71 -0
- package/dist/deposits.js.map +1 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +30 -0
- package/dist/index.js.map +1 -0
- package/dist/migrations/1789800000000-CreateCommerce.d.ts +38 -0
- package/dist/migrations/1789800000000-CreateCommerce.d.ts.map +1 -0
- package/dist/migrations/1789800000000-CreateCommerce.js +152 -0
- package/dist/migrations/1789800000000-CreateCommerce.js.map +1 -0
- package/dist/nestjs/commerce.service.d.ts +122 -0
- package/dist/nestjs/commerce.service.d.ts.map +1 -0
- package/dist/nestjs/commerce.service.js +337 -0
- package/dist/nestjs/commerce.service.js.map +1 -0
- package/dist/nestjs/index.d.ts +18 -0
- package/dist/nestjs/index.d.ts.map +1 -0
- package/dist/nestjs/index.js +27 -0
- package/dist/nestjs/index.js.map +1 -0
- package/dist/nestjs/payment.entity.d.ts +94 -0
- package/dist/nestjs/payment.entity.d.ts.map +1 -0
- package/dist/nestjs/payment.entity.js +181 -0
- package/dist/nestjs/payment.entity.js.map +1 -0
- package/dist/nestjs/payout-account.entity.d.ts +41 -0
- package/dist/nestjs/payout-account.entity.d.ts.map +1 -0
- package/dist/nestjs/payout-account.entity.js +83 -0
- package/dist/nestjs/payout-account.entity.js.map +1 -0
- package/dist/providers/port.d.ts +97 -0
- package/dist/providers/port.d.ts.map +1 -0
- package/dist/providers/port.js +16 -0
- package/dist/providers/port.js.map +1 -0
- package/dist/providers/stripe.d.ts +39 -0
- package/dist/providers/stripe.d.ts.map +1 -0
- package/dist/providers/stripe.js +221 -0
- package/dist/providers/stripe.js.map +1 -0
- package/nestjs/package.json +5 -0
- package/package.json +49 -0
- package/src/deposits.ts +96 -0
- package/src/index.ts +40 -0
- package/src/migrations/1789800000000-CreateCommerce.ts +156 -0
- package/src/nestjs/commerce.service.ts +476 -0
- package/src/nestjs/index.ts +24 -0
- package/src/nestjs/payment.entity.ts +150 -0
- package/src/nestjs/payout-account.entity.ts +56 -0
- package/src/providers/port.ts +108 -0
- package/src/providers/stripe.ts +274 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,601 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
Each package carries its own version. A release publishes only the packages
|
|
4
|
+
whose version is not yet on the registry; `pnpm release` asks npm and skips the
|
|
5
|
+
rest.
|
|
6
|
+
|
|
7
|
+
## commerce 1.0.0
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- Taking money on a business's behalf, for projects 01, 02 and 11: payout
|
|
12
|
+
onboarding with a hard gate, card payments through Stripe Connect as
|
|
13
|
+
destination charges, holds that are captured only by a human decision,
|
|
14
|
+
manually recorded cash, terminal, voucher and transfer takings, partial
|
|
15
|
+
refunds with a required reason, and webhook verification.
|
|
16
|
+
- `depositFor` and `canTakeMoney` at the pure root entry point, because a
|
|
17
|
+
console shows both while somebody drags a slider.
|
|
18
|
+
- **We never hold anybody's funds** — the customer pays the business directly
|
|
19
|
+
and our cut is an application fee. Everything in the package follows from it.
|
|
20
|
+
|
|
21
|
+
## phone 1.0.0
|
|
22
|
+
|
|
23
|
+
### Added
|
|
24
|
+
|
|
25
|
+
- Telephone numbers as the durable identity of a customer: `normalisePhone`,
|
|
26
|
+
`formatPhone`, `dialable`, `isSearchablePhone`, for Romania and Hungary.
|
|
27
|
+
Extracted from project 12 when project 02 needed the same matching, which is
|
|
28
|
+
the second consumer the policy asks for.
|
|
29
|
+
- **Three forms, and they are not interchangeable**: as typed, normalised (a
|
|
30
|
+
search key) and dialable (E.164). Project 12 shipped a pumping check that
|
|
31
|
+
refused a perfectly good normalised number for having no plus, which is what
|
|
32
|
+
the distinction exists to prevent.
|
|
33
|
+
- No dependencies and nothing framework-shaped, so a browser bundle can decide
|
|
34
|
+
whether a lookup is worth making before the keystroke lands.
|
|
35
|
+
|
|
36
|
+
## comms 1.3.0
|
|
37
|
+
|
|
38
|
+
### Added
|
|
39
|
+
|
|
40
|
+
- `SmtpMessagePort`: email over SMTP, built on nodemailer. Every project's
|
|
41
|
+
Compose file runs Mailpit and nothing could reach it, so an invitation, a
|
|
42
|
+
receipt or a password reset could not be followed end to end on a developer's
|
|
43
|
+
machine without a vendor account. It is a production transport too — a
|
|
44
|
+
customer's own mail server, a relay offered instead of an API, a deployment
|
|
45
|
+
where mail may not leave the building.
|
|
46
|
+
- A recipient the server refuses after accepting the conversation is a failure
|
|
47
|
+
rather than a success. `sendMail` resolves in that case, and recording it as
|
|
48
|
+
sent writes "delivered" against a message the server explicitly refused.
|
|
49
|
+
- Certificates are verified by default. `allowSelfSignedCertificate` is for a
|
|
50
|
+
local catcher or a private relay, and named so nobody enables it casually.
|
|
51
|
+
- Its tests run a real SMTP server in-process rather than mocking the client:
|
|
52
|
+
neither the refused recipient nor the untrusted certificate can be asserted
|
|
53
|
+
against a mock.
|
|
54
|
+
|
|
55
|
+
## messaging 1.0.1
|
|
56
|
+
|
|
57
|
+
### Fixed
|
|
58
|
+
|
|
59
|
+
- **The entity pointed at the wrong table.** `MessageCreditEntry` was mapped to
|
|
60
|
+
`message_credits` while the migration creates `mortar_message_credits`, so
|
|
61
|
+
every read through the service failed with `relation "public.message_credits"
|
|
62
|
+
does not exist`. The migration and the raw SQL in the README were right; only
|
|
63
|
+
the decorator was wrong, which is why the package's own build and typecheck
|
|
64
|
+
had nothing to say about it.
|
|
65
|
+
|
|
66
|
+
## messaging 1.0.0
|
|
67
|
+
|
|
68
|
+
Extracted from project 13 at its second consumer (project 12), which is the
|
|
69
|
+
rule: written once, moved when a second product needs it.
|
|
70
|
+
|
|
71
|
+
### Added
|
|
72
|
+
|
|
73
|
+
- **`countSegments`** — what a message actually costs, counted the way a
|
|
74
|
+
provider counts rather than the way a person counts characters. A single
|
|
75
|
+
character outside GSM 03.38 changes the encoding for the whole message and
|
|
76
|
+
cuts capacity from 160 to 70, so `offenders` names the characters responsible:
|
|
77
|
+
"your ș and ț are doubling the cost" is something a person can act on.
|
|
78
|
+
- **Quiet hours** — `isQuiet`, `nextAllowed`, `localTime`, in the _business's_
|
|
79
|
+
zone rather than the recipient's. A phone number says nothing about where
|
|
80
|
+
somebody is sitting.
|
|
81
|
+
- **`assessSmsRisk`** — pumping detection. Fraud that costs money rather than
|
|
82
|
+
data, and visible only in the shape of recent traffic rather than in any one
|
|
83
|
+
message.
|
|
84
|
+
- **`MessageCreditsService`** and `mortar_message_credits` (`/nestjs`) — credit
|
|
85
|
+
as a ledger, append-only, with the balance summed from the entries rather than
|
|
86
|
+
kept in a column that can disagree with them. No foreign key to whatever the
|
|
87
|
+
segments were spent on, which is what lets two products share it.
|
|
88
|
+
|
|
89
|
+
The root entry point is pure — no database, no framework, no Node built-ins —
|
|
90
|
+
because a console counts segments on every keystroke and that has to run in a
|
|
91
|
+
browser. Everything needing TypeORM is behind `/nestjs`.
|
|
92
|
+
|
|
93
|
+
## csv 1.0.0
|
|
94
|
+
|
|
95
|
+
Extracted at the second consumer: project 12 reads a shop's shelf out of a
|
|
96
|
+
spreadsheet, project 13 writes an access log a regulator will open. Both had
|
|
97
|
+
hand-written code, and both had a bug the other did not.
|
|
98
|
+
|
|
99
|
+
### Added
|
|
100
|
+
|
|
101
|
+
- **`parseCsv`** — delimiter detected from the file. Every locale that uses a
|
|
102
|
+
comma as the decimal separator gets semicolon-separated files out of Excel,
|
|
103
|
+
still called CSV, and the obvious shortcut of honouring both at once splits a
|
|
104
|
+
field reading `screen cracked; battery dead` in two and shifts every column
|
|
105
|
+
after it, silently. Blank lines dropped; the mark Excel writes stripped, since
|
|
106
|
+
left in place it hides in the first header.
|
|
107
|
+
- **`toCsv` and `toCsvFrom`** — quoting that is not optional, empty cells rather
|
|
108
|
+
than the strings `null` and `undefined`, and a byte-order mark by default,
|
|
109
|
+
because Excel guesses a file's encoding by looking at it and reads an unmarked
|
|
110
|
+
UTF-8 file as the system code page — so `Ioană` opens as `Ioană` for exactly
|
|
111
|
+
the people whose names have diacritics. `toCsvFrom` takes the column order
|
|
112
|
+
rather than reading it off the first object's keys.
|
|
113
|
+
|
|
114
|
+
Pure: no database, no framework, no Node built-ins, so a console can preview an
|
|
115
|
+
upload before it happens. Deliberately not part of `files`, which carries S3,
|
|
116
|
+
virus scanning and PDF assembly.
|
|
117
|
+
|
|
118
|
+
## jobs 1.1.1
|
|
119
|
+
|
|
120
|
+
### Fixed
|
|
121
|
+
|
|
122
|
+
- **`JobsModule` now closes the Redis connection it opened.** It creates a
|
|
123
|
+
dedicated connection and hands it to BullMQ, and BullMQ closes connections it
|
|
124
|
+
created while leaving alone the ones it was given — correctly, since it does
|
|
125
|
+
not own them. Nothing closed this one. An application that finished its work
|
|
126
|
+
and called `app.close()` sat there with an open socket for ever: a seed script
|
|
127
|
+
that never returned, and a deployment step that hung waiting for it. The
|
|
128
|
+
connection is now provided under `MORTAR_QUEUE_CONNECTION` and closed in
|
|
129
|
+
`onApplicationShutdown`, after the workers and queues that use it.
|
|
130
|
+
|
|
131
|
+
## redis 1.0.1
|
|
132
|
+
|
|
133
|
+
### Fixed
|
|
134
|
+
|
|
135
|
+
- **A queue connection no longer carries a command timeout.** `createQueueConnection`
|
|
136
|
+
already cleared `maxRetriesPerRequest` for BullMQ, but left the five-second
|
|
137
|
+
`commandTimeout` in place — and a queue consumer waits for work with blocking
|
|
138
|
+
reads that are _designed_ to sit there for longer than any sensible deadline.
|
|
139
|
+
The result was an idle worker logging `Command timed out` every few seconds,
|
|
140
|
+
on every queue, for ever. Jobs still ran, which is what made it easy to read
|
|
141
|
+
as a sick Redis rather than a misconfigured client. `commandTimeoutMs` now
|
|
142
|
+
accepts `null` to mean "no deadline", and queue connections pass it.
|
|
143
|
+
|
|
144
|
+
## comms 1.2.0
|
|
145
|
+
|
|
146
|
+
The vendors, behind the ports that were waiting for them (dossier D-10).
|
|
147
|
+
|
|
148
|
+
### Added
|
|
149
|
+
|
|
150
|
+
- **`ResendMessagePort`** — email, on Resend's own SDK. A message may carry its
|
|
151
|
+
own `from` and `replyTo`, which is how it is branded as a customer without
|
|
152
|
+
their domain being one the provider can sign for: their name in the display
|
|
153
|
+
part, their address to reply to, so a client who replies reaches their
|
|
154
|
+
accountant rather than a mailbox nobody reads.
|
|
155
|
+
- **`TwilioMessagePort`** — SMS, on Twilio's SDK, preferring a messaging service
|
|
156
|
+
over a single number. The sender identity is a per-market question — an
|
|
157
|
+
alphanumeric sender ID is permitted in some countries, requires registration
|
|
158
|
+
in others, and cannot be replied to anywhere — and a messaging service is what
|
|
159
|
+
lets it change without a deployment. It refuses to be constructed with no
|
|
160
|
+
sender at all, because the alternative is finding out twelve days into a
|
|
161
|
+
reminder cadence.
|
|
162
|
+
- **The segment count comes back from the provider**, not from our estimate.
|
|
163
|
+
`countSegments` decides whether a message is worth sending; the ledger is
|
|
164
|
+
debited by what was actually charged, and the two differing is the case a
|
|
165
|
+
ledger exists to catch — one accented character downgrades a message to UCS-2
|
|
166
|
+
and doubles its cost without changing a word.
|
|
167
|
+
- **`ResendInbound`** — verifying the provider's webhook and fetching the
|
|
168
|
+
message it names. The webhook carries metadata and no body, so the original is
|
|
169
|
+
fetched and returned as **raw MIME** for `parseMime` to read: the parser stays
|
|
170
|
+
ours, and the day the provider changes nothing above it moves. Verification is
|
|
171
|
+
the vendor's own (Standard Webhooks) and takes the **raw** request body — a
|
|
172
|
+
parsed object re-serialised has different bytes and fails.
|
|
173
|
+
|
|
174
|
+
### Notes
|
|
175
|
+
|
|
176
|
+
- **The vendors' SDKs rather than their REST APIs**, which is the arrangement
|
|
177
|
+
`files` already has with `@aws-sdk/client-s3`. Both were first written against
|
|
178
|
+
the published REST documentation, and the SDK types caught a field this got
|
|
179
|
+
wrong — a received message's download URL. Fewer lines, and the shapes are
|
|
180
|
+
right by construction.
|
|
181
|
+
- Both ports **throw** on refusal rather than returning a failure, carrying the
|
|
182
|
+
provider's own sentence. `CommsService` records it in the message log, which
|
|
183
|
+
is what support reads — a port that swallowed the reason would leave "it did
|
|
184
|
+
not send" and nothing else.
|
|
185
|
+
- `ResendMessagePort` imposes its own **timeout**: the SDK sets none, and
|
|
186
|
+
something is usually waiting on a message — a professional who has just
|
|
187
|
+
pressed send should not hold a response open until a socket gives up.
|
|
188
|
+
- Every port takes an optional `client`, so a deployment can share one and a
|
|
189
|
+
test can fake the vendor at its own surface rather than stubbing `fetch`.
|
|
190
|
+
|
|
191
|
+
## context 1.1.0
|
|
192
|
+
|
|
193
|
+
An actor can be an operator.
|
|
194
|
+
|
|
195
|
+
### Added
|
|
196
|
+
|
|
197
|
+
- **`Actor.type` accepts `'operator'`** — one of _us_, working inside a
|
|
198
|
+
customer's account with their consent. Separate from `user` because the audit
|
|
199
|
+
trail has to be able to say which it was: support access recorded as the
|
|
200
|
+
customer's own action is worse than no record, being a confident answer to
|
|
201
|
+
"who opened this?" that names the wrong person. Thirteen of the seventeen
|
|
202
|
+
specifications describe back-office impersonation, so the type belongs here
|
|
203
|
+
rather than in each of them.
|
|
204
|
+
- `impersonatedBy` is now documented as the _other_ shape — an operator acting
|
|
205
|
+
as a named user — with a note that acting as oneself inside the customer's
|
|
206
|
+
account is the safer one, because nothing is disguised.
|
|
207
|
+
|
|
208
|
+
## comms 1.1.0
|
|
209
|
+
|
|
210
|
+
Attachments, so a completed set of documents can be delivered by email (dossier
|
|
211
|
+
F-174).
|
|
212
|
+
|
|
213
|
+
### Added
|
|
214
|
+
|
|
215
|
+
- **`OutboundMessage.attachments`**, and `MAX_ATTACHMENT_BYTES` at 10 MB.
|
|
216
|
+
Providers differ — many refuse at 10, most at 25 — and base64 inflates an
|
|
217
|
+
attachment by a third, so the useful limit sits well under the smallest of
|
|
218
|
+
them.
|
|
219
|
+
- **Refused before the provider sees it.** A receiving server bounces an
|
|
220
|
+
oversized attachment silently and late, which becomes "they never got it and
|
|
221
|
+
nobody knows why". The log records a failure with a sentence instead, and
|
|
222
|
+
nothing is handed to the port.
|
|
223
|
+
- The message log records **how many files and how many bytes**, never their
|
|
224
|
+
names: the log is read by support, and a client's filenames are not theirs to
|
|
225
|
+
read.
|
|
226
|
+
|
|
227
|
+
### Fixed
|
|
228
|
+
|
|
229
|
+
- **`NoopMessagePort` ids are now unique across processes.** They counted from
|
|
230
|
+
one, and the message log has a unique index on
|
|
231
|
+
`(direction, provider_message_id)` — so the second test run against the same
|
|
232
|
+
database collided, and `CommsService` reported it as a message the provider
|
|
233
|
+
refused. The failure surfaced in whatever was being tested rather than in the
|
|
234
|
+
double, and only on the second run.
|
|
235
|
+
|
|
236
|
+
## files 1.2.0
|
|
237
|
+
|
|
238
|
+
ZIP archives and provider-enforced retention (dossier F-170, F-178): a completed
|
|
239
|
+
request leaves as one file whose folders and names the receiving firm can file
|
|
240
|
+
without opening it. A ZIP of `IMG_4471.jpg` is worthless; one of
|
|
241
|
+
`Ion_Popescu/03_Bank_statement.pdf` is already filed.
|
|
242
|
+
|
|
243
|
+
### Added
|
|
244
|
+
|
|
245
|
+
- **`createZip`.** Hand-written over `node:zlib` rather than taken from a
|
|
246
|
+
dependency — the essential format is two hundred lines and has not changed
|
|
247
|
+
since 1993, and every library that writes it brings a stream stack and a
|
|
248
|
+
supply chain with it.
|
|
249
|
+
- Deterministic when given a `modified` date, so a delivery retry produces the
|
|
250
|
+
file the destination already has rather than a second copy.
|
|
251
|
+
- Zip-slip paths (`/etc/passwd`, `../../secrets`) are stripped rather than
|
|
252
|
+
trusted to the extractor; duplicate paths are refused rather than left for the
|
|
253
|
+
extractor to resolve; names are flagged UTF-8 so a Romanian filename survives.
|
|
254
|
+
- Entries are deflated, and stored instead when deflate would make them bigger —
|
|
255
|
+
which is every photograph and most PDFs.
|
|
256
|
+
- Verified against `unzip` in the tests, not only against its own reader: an
|
|
257
|
+
archive only this package can read is not an archive.
|
|
258
|
+
- **`S3Storage.applyLifecycle` / `describeLifecycle`.** Provider-enforced expiry
|
|
259
|
+
as a backstop under the application's own retention. The failure it covers is
|
|
260
|
+
the one the application cannot: a sweep broken for a month leaves documents in
|
|
261
|
+
a bucket and nothing in the application says so. An empty rule list removes
|
|
262
|
+
the configuration, because S3 refuses one with zero rules.
|
|
263
|
+
- **`S3Storage` now has integration tests**, against MinIO rather than a mocked
|
|
264
|
+
SDK — whether a presigned URL is actually accepted, what a missing object
|
|
265
|
+
answers, and whether a lifecycle configuration is written in a shape a
|
|
266
|
+
provider takes are all things a mock cannot speak to. Mortar's development
|
|
267
|
+
stack gained a MinIO service on 3052/3053 for it.
|
|
268
|
+
- **`MemoryStorage` gained `has`, `clear`, `failOn` and `stopFailing`.** A suite
|
|
269
|
+
shares one instance across a file, so without `clear` every object from every
|
|
270
|
+
earlier test is still there and an assertion about what a cleanup removed
|
|
271
|
+
silently starts passing for the wrong reason. `failOn` exists because real
|
|
272
|
+
buckets fail one object at a time, and what matters is what the caller does
|
|
273
|
+
about it: a retention sweep must not abandon thirty-nine other firms because
|
|
274
|
+
one object would not delete.
|
|
275
|
+
|
|
276
|
+
## files 1.1.0
|
|
277
|
+
|
|
278
|
+
Single-PDF assembly (dossier F-090): several photographed pages become one
|
|
279
|
+
document, which is what a professional actually wants — three separate JPEGs of
|
|
280
|
+
a statement means three files to open in an order only knowable from filenames
|
|
281
|
+
the client did not choose.
|
|
282
|
+
|
|
283
|
+
### Added
|
|
284
|
+
|
|
285
|
+
- **`assemblePdf`.** JPEG and PNG are embedded natively, `DCTDecode` and
|
|
286
|
+
`FlateDecode`, so a photograph reaches the professional as the bytes the
|
|
287
|
+
camera produced rather than a generational copy. Pages are sized to their
|
|
288
|
+
image rather than floated on a fixed A4, scaled down but never up.
|
|
289
|
+
- HEIC is refused. A phone produces it, no PDF reader opens it, and converting
|
|
290
|
+
it needs a decoder this package is not going to carry.
|
|
291
|
+
- No producer or creation date is written: these are a client's bank statements,
|
|
292
|
+
and the defaults name the software that touched them. It also makes the output
|
|
293
|
+
deterministic, which a test asserts.
|
|
294
|
+
|
|
295
|
+
### A dependency, and why this one
|
|
296
|
+
|
|
297
|
+
`pdf-lib` is a real dependency in a package that has argued against them —
|
|
298
|
+
`@birtalanrobert/comms` writes its own MIME parser, and the ClamAV adapter
|
|
299
|
+
speaks the protocol directly. The distinction is where a failure shows up. A
|
|
300
|
+
MIME parser that gets something wrong loses an attachment, visibly, immediately.
|
|
301
|
+
**A malformed PDF is invisible until a professional cannot open it**, days
|
|
302
|
+
later, with a client who has already put the paper away — and PDF is a format
|
|
303
|
+
with enough subtlety that hand-rolling a writer is a wager on being right about
|
|
304
|
+
all of it.
|
|
305
|
+
|
|
306
|
+
### A bug found while writing the tests
|
|
307
|
+
|
|
308
|
+
`pdf-lib` reads an image's **whole backing `ArrayBuffer` and ignores the view's
|
|
309
|
+
`byteOffset`**. Node allocates every Buffer under 4 KB from a shared 8 KB pool,
|
|
310
|
+
so a small page — a compressed scan, or anything fetched from storage — arrives
|
|
311
|
+
at a non-zero offset, and the embedder parses whatever sits at the pool's start.
|
|
312
|
+
|
|
313
|
+
It is a nasty shape of bug: whether it fires depends on what else the process
|
|
314
|
+
has allocated, so the first several runs passed by reading a stale copy of the
|
|
315
|
+
same image left at position 0. An offset-aware view does not fix it, because it
|
|
316
|
+
shares the ArrayBuffer. `assemblePdf` copies the bytes, and a test builds a
|
|
317
|
+
pooled buffer deliberately.
|
|
318
|
+
|
|
319
|
+
## http 2.0.0 — and a minor for everything that depends on it
|
|
320
|
+
|
|
321
|
+
`@birtalanrobert/http` root entry point is now framework-free.
|
|
322
|
+
|
|
323
|
+
### Why a major
|
|
324
|
+
|
|
325
|
+
The root exported the exception filter, the context middleware, the validation
|
|
326
|
+
pipe, the health controller, `HttpModule` and `@PublicRoute()` — so importing
|
|
327
|
+
`NotFoundError` imported NestJS. Every package that raises a mortar error
|
|
328
|
+
inherited that, which is a framework in an edge bundle for the sake of a type
|
|
329
|
+
guard.
|
|
330
|
+
|
|
331
|
+
Those six now live at `@birtalanrobert/http/nestjs`. **The error classes,
|
|
332
|
+
problem serialisation, header names, locale negotiation and the health registry
|
|
333
|
+
have not moved**, so most files need no change; an application module and a
|
|
334
|
+
bootstrap file need one line each.
|
|
335
|
+
|
|
336
|
+
### Also changed
|
|
337
|
+
|
|
338
|
+
- **`toProblemDetails` recognises a Nest `HttpException` by shape rather than
|
|
339
|
+
by `instanceof`.** That removes the last runtime import, and it is the more
|
|
340
|
+
correct check: two copies of `@nestjs/common` in one install — routine in a
|
|
341
|
+
monorepo — make `instanceof` false for the framework's own exceptions, so its
|
|
342
|
+
validation errors would silently fall through to the generic 500 branch. The
|
|
343
|
+
function is documented as total; recognising the contract is what makes that
|
|
344
|
+
true.
|
|
345
|
+
- **`REQUEST_ID_HEADER`, `CORRELATION_ID_HEADER` and `negotiateLocale` moved to
|
|
346
|
+
their own module** so a Next.js middleware can read the same header names
|
|
347
|
+
without the middleware class that uses them.
|
|
348
|
+
|
|
349
|
+
### auth 1.1.0, idempotency 1.1.0, tenancy 1.1.0, workflow 1.1.0
|
|
350
|
+
|
|
351
|
+
No API change. Each depends on `http`, and each is republished so its dependency
|
|
352
|
+
range moves to `^2.0.0` — otherwise an application installing `http@2` would end
|
|
353
|
+
up with a second copy at `1.x` underneath these, and `isMortarError` is an
|
|
354
|
+
`instanceof` check that two copies quietly break.
|
|
355
|
+
|
|
356
|
+
`workflow` also gains the `mortar.entries` field it was missing, so its
|
|
357
|
+
`nestjs/` subpath stub is regenerated by the build instead of surviving only
|
|
358
|
+
because nothing had deleted it.
|
|
359
|
+
|
|
360
|
+
### Every package README now documents its wiring
|
|
361
|
+
|
|
362
|
+
What to import, whether it is `forRoot` or `forRootAsync`, where it goes in the
|
|
363
|
+
imports array and what breaks if it goes elsewhere, which entities and
|
|
364
|
+
migrations to register, and what needs no module at all — `context`, `money` and
|
|
365
|
+
the root half of `http` are imported directly.
|
|
366
|
+
|
|
367
|
+
Two scripts check the result rather than trusting it: one resolves every
|
|
368
|
+
documented import against the built `.d.ts` files, the other checks every
|
|
369
|
+
`Module.forRoot…()` shown actually exists. Both found real errors — a
|
|
370
|
+
`RedisService.remember` that does not exist (it is `redis.cache.getOrSet`), a
|
|
371
|
+
`workers.handle` that is `workers.register`, an `envBool` that is `envBoolean`,
|
|
372
|
+
and column helpers documented in the wrong package.
|
|
373
|
+
|
|
374
|
+
## files 1.0.0, comms 1.0.0
|
|
375
|
+
|
|
376
|
+
The two Tier 2 packages dossier's Phase 2 needs: somewhere for an uploaded
|
|
377
|
+
document to go, and a way for a client to forward one they already have.
|
|
378
|
+
|
|
379
|
+
Built now rather than up front because this is the phase that first needs them —
|
|
380
|
+
and built partially, on purpose. `files` has no PDF assembly, thumbnailing or
|
|
381
|
+
ZIP packaging; `comms` has no templates, quiet hours or credit ledger. Those
|
|
382
|
+
belong to the phases that need them, and writing them now would be guessing at
|
|
383
|
+
requirements three projects away.
|
|
384
|
+
|
|
385
|
+
### `files`
|
|
386
|
+
|
|
387
|
+
- **Pre-signed direct upload.** The browser uploads to storage without touching
|
|
388
|
+
the API. Proxying the bytes costs a request-sized chunk of memory per
|
|
389
|
+
concurrent upload and puts the API's timeout between a client on a train and
|
|
390
|
+
finishing. The cost is real rows in `pending`, which `sweepAbandoned` clears.
|
|
391
|
+
- **The type is read from the bytes, never from the header.** A `Content-Type`
|
|
392
|
+
and a filename extension are claims made by whoever uploaded the file.
|
|
393
|
+
- **One bucket, tenant id as the first path segment**, so a bucket policy can
|
|
394
|
+
name it. `assertTenantOwns` before every read, delete and signature: nothing
|
|
395
|
+
governs a bucket except the key handed to it.
|
|
396
|
+
- **Envelope encryption for erasure, not confidentiality.** The provider already
|
|
397
|
+
encrypts at rest. Destroying one wrapped key is the difference between an
|
|
398
|
+
erasure request honoured in seconds and one that cannot honestly be honoured,
|
|
399
|
+
because backups exist. The object key is bound in as AAD, so a ciphertext
|
|
400
|
+
moved under another tenant's prefix fails to open.
|
|
401
|
+
- **`RefusingScanner` is the default.** A misconfiguration that silently
|
|
402
|
+
disables virus scanning is indistinguishable from working software until it
|
|
403
|
+
matters; one that refuses uploads is noticed in minutes.
|
|
404
|
+
- **`MemoryStorage` is exported.** Every service consuming `StoragePort` lives
|
|
405
|
+
in another repository and needs to test its upload flow without a bucket.
|
|
406
|
+
|
|
407
|
+
### `comms`
|
|
408
|
+
|
|
409
|
+
- **Signed per-request inbound addresses.** The address is the credential, so it
|
|
410
|
+
carries an HMAC tag; without one a predictable local part lets a stranger post
|
|
411
|
+
documents into a firm's workflow. Its own secret, because an address lives for
|
|
412
|
+
years in sent folders while a link expires in days.
|
|
413
|
+
- **A MIME parser rather than a dependency.** Inbound mail is the most hostile
|
|
414
|
+
input the system accepts. Eighty readable lines tested against what actually
|
|
415
|
+
arrives is a smaller permanent surface than a parser that knows every corner
|
|
416
|
+
of MIME in order to be asked about six.
|
|
417
|
+
- **A partial unique index on the provider's message id.** Providers redeliver;
|
|
418
|
+
without it a forwarded bank statement is attached three times. A constraint
|
|
419
|
+
rather than a check, because two redeliveries can arrive at once.
|
|
420
|
+
- **The message body is never logged.** A reminder is innocuous; inbound mail
|
|
421
|
+
here is bank statements.
|
|
422
|
+
- **Ports only for sending.** Providers are Phase 5; the seam exists now so the
|
|
423
|
+
one thing that needs sending sooner has somewhere to go.
|
|
424
|
+
|
|
425
|
+
### A defect in the scaffolding, found by the editor
|
|
426
|
+
|
|
427
|
+
`scripts/new-package.mjs` generated a single `tsconfig.json` that both emitted
|
|
428
|
+
to `dist` and excluded `*.test.ts` — so a new package's tests belonged to no
|
|
429
|
+
project and were type-checked by nothing. The build passed while the editor
|
|
430
|
+
showed errors, which is how three genuine type errors in `envelope.test.ts`
|
|
431
|
+
survived a green run.
|
|
432
|
+
|
|
433
|
+
`files`, `comms` and `workflow` now carry the standard pair the other twelve
|
|
434
|
+
packages already had, and the scaffold writes both. Nothing published changes:
|
|
435
|
+
`dist` never contained tests either way.
|
|
436
|
+
|
|
437
|
+
### A bug this found
|
|
438
|
+
|
|
439
|
+
The inbound tag was base64url at first, and every address failed to verify
|
|
440
|
+
itself. `parse` lowercases the address on the way in — correctly, because
|
|
441
|
+
providers lowercase local parts — which destroys a case-sensitive tag. Hex
|
|
442
|
+
costs a few characters in an address nobody types by hand.
|
|
443
|
+
|
|
444
|
+
## observability 1.0.1
|
|
445
|
+
|
|
446
|
+
Never published. An interrupted publish left `1.0.0` partially staged and npm
|
|
447
|
+
rejected a retry, so the version was stepped over — and then the staged upload
|
|
448
|
+
finalised on npm's side after all. `1.0.0` is the real release; `1.0.1` does
|
|
449
|
+
not exist.
|
|
450
|
+
|
|
451
|
+
## observability 1.1.0, jobs 1.1.0
|
|
452
|
+
|
|
453
|
+
Everything a worker needs to be observable. Found by building `starter-worker`,
|
|
454
|
+
whose specification asks for queue depth, job duration, failure rate and
|
|
455
|
+
scanner lag — none of which anything recorded.
|
|
456
|
+
|
|
457
|
+
### Added
|
|
458
|
+
|
|
459
|
+
- **`JobWorkers` records `job_duration_ms`, `jobs_total` (labelled by outcome)
|
|
460
|
+
and `jobs_dead_lettered_total`.** In the runner rather than in each handler:
|
|
461
|
+
how many ran, how many failed and how long they took are properties of the
|
|
462
|
+
runner and identical in every service. One counter with a `status` label
|
|
463
|
+
rather than two counters, because failure rate is a ratio and both halves
|
|
464
|
+
must share their labels. Defaults to a no-op registry.
|
|
465
|
+
|
|
466
|
+
- **`WindowScanner` records `scanner_scan_duration_ms`, `scanner_items_total`
|
|
467
|
+
and `scanner_last_success_timestamp_ms`.** The last is the one worth alerting
|
|
468
|
+
on: a scanner that has stopped logs nothing and errors nothing, it simply
|
|
469
|
+
stops finding work, and the first anyone hears is a customer asking why they
|
|
470
|
+
were never reminded. A timestamp rather than an age, because a gauge written
|
|
471
|
+
only on success cannot grow while the scanner is dead.
|
|
472
|
+
|
|
473
|
+
- **`JobQueues` rejects a job id containing `:`**, naming the job and the id.
|
|
474
|
+
BullMQ uses the colon as a key separator and refuses such an id with an error
|
|
475
|
+
that mentions neither — and `` `reminder:${id}` `` is the natural thing to
|
|
476
|
+
write, so that error is reached often and explains nothing.
|
|
477
|
+
|
|
478
|
+
- **`JobsModule` passes the container's metrics registry** to the worker
|
|
479
|
+
registry, so this costs a consumer nothing to switch on.
|
|
480
|
+
|
|
481
|
+
- **`InMemoryMetrics.snapshot()`**, returning every series held. A `/metrics`
|
|
482
|
+
endpoint has to enumerate what exists, and `value()` could only answer about
|
|
483
|
+
a name the caller already knew. Histograms report count, sum, min and max;
|
|
484
|
+
bucketing is a presentation decision belonging to whatever scrapes it.
|
|
485
|
+
|
|
486
|
+
### Fixed
|
|
487
|
+
|
|
488
|
+
- **Histogram labels are stored beside their observations** rather than
|
|
489
|
+
recovered by parsing the storage key. A label value containing `=` or `,`
|
|
490
|
+
would not have survived the round trip.
|
|
491
|
+
|
|
492
|
+
## 1.0.0
|
|
493
|
+
|
|
494
|
+
The version numbers become meaningful.
|
|
495
|
+
|
|
496
|
+
Until now every package shared one version and all twelve were republished
|
|
497
|
+
together. That does not survive contact with per-package releases while the
|
|
498
|
+
major is `0`: under semver a `^0.2.0` range excludes `0.3.0`, so changing one
|
|
499
|
+
package and releasing only it leaves every dependent pinned to the old copy —
|
|
500
|
+
and npm resolves that by installing both. Two copies of `observability` means
|
|
501
|
+
two distinct `MORTAR_LOGGER` symbols, and dependency injection stops working
|
|
502
|
+
with an error that names neither.
|
|
503
|
+
|
|
504
|
+
At `1.x` a caret range accepts later minors, so a package can be released on
|
|
505
|
+
its own and its dependents pick it up on their next install. From here:
|
|
506
|
+
|
|
507
|
+
- **patch** — a fix that changes no signature
|
|
508
|
+
- **minor** — anything added
|
|
509
|
+
- **major** — anything removed or changed in shape
|
|
510
|
+
|
|
511
|
+
### Added
|
|
512
|
+
|
|
513
|
+
- **`DatabaseModule` can run migrations at boot** — `migrationsRun: true`.
|
|
514
|
+
|
|
515
|
+
Guarded by a Postgres advisory lock, so several replicas starting at once are
|
|
516
|
+
safe: one applies while the others wait, then find nothing pending. TypeORM
|
|
517
|
+
takes no lock of its own, and without one the second replica to reach a
|
|
518
|
+
`CREATE TABLE` fails and that container crash-loops. Also exported directly
|
|
519
|
+
as `runMigrationsWithLock` for release-step scripts.
|
|
520
|
+
|
|
521
|
+
- **`LoggerModule` provides `NestLoggerAdapter` and `LoggingInterceptor`.**
|
|
522
|
+
Both were exported but never registered, so `app.get(NestLoggerAdapter)` and
|
|
523
|
+
`{ provide: APP_INTERCEPTOR, useExisting: LoggingInterceptor }` — the two
|
|
524
|
+
documented ways to use them — both failed. Constructing them by hand still
|
|
525
|
+
works.
|
|
526
|
+
|
|
527
|
+
- **`PUBLIC_ROUTE_KEY` and `PublicRoute()` in `@birtalanrobert/http`**, and the
|
|
528
|
+
health controller now carries them. `@birtalanrobert/auth` re-exports the key
|
|
529
|
+
as `PUBLIC_KEY`, unchanged, so `PermissionsGuard` and `@Public()` behave
|
|
530
|
+
exactly as before — but a globally registered guard no longer 401s the
|
|
531
|
+
readiness probe, which previously left pods that never joined the load
|
|
532
|
+
balancer.
|
|
533
|
+
|
|
534
|
+
- **`auditEntities` and `idempotencyEntities`**, so every package that ships
|
|
535
|
+
entities exports them as an array the same way it exports its migrations.
|
|
536
|
+
|
|
537
|
+
### Fixed
|
|
538
|
+
|
|
539
|
+
- **A circular import between `logger.module.ts` and the two classes it now
|
|
540
|
+
provides** left `MORTAR_LOGGER` `undefined` at decorator evaluation time, so
|
|
541
|
+
`@Inject(MORTAR_LOGGER)` silently degraded to reflected-type injection and
|
|
542
|
+
Nest reported that it could not resolve `Function`. The tokens moved to a
|
|
543
|
+
leaf module. Under CommonJS this class of bug fails at wiring time, never at
|
|
544
|
+
build time.
|
|
545
|
+
|
|
546
|
+
### Testing
|
|
547
|
+
|
|
548
|
+
`@nestjs/testing` and `unplugin-swc` are now dev dependencies, and the Nest
|
|
549
|
+
modules are exercised by building a real container rather than by inspecting
|
|
550
|
+
the `DynamicModule` object. Every defect above was invisible to a test that
|
|
551
|
+
asserts on `module.providers` and obvious to one that calls `moduleRef.get()`.
|
|
552
|
+
|
|
553
|
+
## 0.2.0
|
|
554
|
+
|
|
555
|
+
Composing the packages into a real application surfaced three problems that
|
|
556
|
+
package-level tests could not.
|
|
557
|
+
|
|
558
|
+
### Added
|
|
559
|
+
|
|
560
|
+
- **`forRootAsync` on every configurable module** — `LoggerModule`,
|
|
561
|
+
`DatabaseModule`, `RedisModule`, `HttpModule`, `TenancyModule`, `AuthModule`,
|
|
562
|
+
`IdempotencyModule` and `JobsModule`.
|
|
563
|
+
|
|
564
|
+
Previously each module took its options synchronously, which meant a consumer
|
|
565
|
+
had to read `process.env` at import time — before anything had validated it —
|
|
566
|
+
to configure a database URL or a Redis connection. That defeats having a
|
|
567
|
+
configuration layer at all. Options can now come from any provider, including
|
|
568
|
+
the validated config.
|
|
569
|
+
|
|
570
|
+
- **`ConfigModule.token()`**, so a wiring site can write
|
|
571
|
+
`inject: [ConfigModule.token()]` rather than importing the raw symbol.
|
|
572
|
+
|
|
573
|
+
- **`AsyncModuleOptions<T>`** in `@birtalanrobert/context`: the shared shape for
|
|
574
|
+
the above.
|
|
575
|
+
|
|
576
|
+
### Fixed
|
|
577
|
+
|
|
578
|
+
- **`HttpModule` and `TenancyModule` no longer hold module options in static
|
|
579
|
+
fields.** Both middlewares now receive their options through dependency
|
|
580
|
+
injection. The previous arrangement meant a second `forRoot()` call silently
|
|
581
|
+
overwrote the first — which is exactly what happens when a test suite builds
|
|
582
|
+
more than one application in a process.
|
|
583
|
+
|
|
584
|
+
- **`@birtalanrobert/http` accepts `class-validator` 0.15**, which is current.
|
|
585
|
+
The peer range previously stopped at 0.14 and produced an unmet-peer warning
|
|
586
|
+
on every install.
|
|
587
|
+
|
|
588
|
+
- **Internal dependencies publish as `^x.y.z` rather than an exact pin.** Exact
|
|
589
|
+
pins across a family released together make npm install several copies of the
|
|
590
|
+
same package as soon as two versions coexist in one tree.
|
|
591
|
+
|
|
592
|
+
### Note on compatibility
|
|
593
|
+
|
|
594
|
+
`HttpModule.contextOptions` and `TenancyModule.resolvers` are no longer present
|
|
595
|
+
as static properties. They were declared `private` and were never part of the
|
|
596
|
+
documented surface — TypeScript consumers could not reach them — but a
|
|
597
|
+
JavaScript consumer reading them would break. Nothing else changed shape.
|
|
598
|
+
|
|
599
|
+
## 0.1.0
|
|
600
|
+
|
|
601
|
+
First release.
|