lambder 8.1.1 → 8.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +164 -0
- package/README.md +8 -7
- package/dist/api/LambderApiGuards.d.ts +2 -17
- package/dist/api/LambderApiRateLimits.d.ts +2 -29
- package/dist/build/ContractTypePrinter.d.ts +39 -9
- package/dist/build/ContractTypePrinter.js +89 -31
- package/dist/build/generatedTables.d.ts +72 -0
- package/dist/build/generatedTables.js +99 -0
- package/dist/build/writeApiContract.d.ts +2 -1
- package/dist/build/writeApiContract.js +2 -1
- package/dist/build/writeApiGuardParams.d.ts +60 -0
- package/dist/build/writeApiGuardParams.js +85 -0
- package/dist/build/writeApiOptions.d.ts +68 -0
- package/dist/build/writeApiOptions.js +102 -0
- package/dist/build.d.ts +10 -4
- package/dist/build.js +7 -4
- package/dist/client/LambderUploadRunner.d.ts +7 -7
- package/dist/client/LambderUploadRunner.js +23 -32
- package/dist/client.d.ts +7 -0
- package/dist/client.js +11 -0
- package/dist/core/Lambder.d.ts +21 -0
- package/dist/core/Lambder.js +69 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +13 -0
- package/dist/mock/LambderMockApp.d.ts +34 -17
- package/dist/mock/LambderMockApp.js +67 -21
- package/dist/mock/LambderMockCreateOptions.d.ts +68 -5
- package/dist/mock/LambderMockTypes.d.ts +29 -10
- package/dist/mock/lambderMockPoliciesFrom.d.ts +51 -0
- package/dist/mock/lambderMockPoliciesFrom.js +46 -0
- package/dist/mock.d.ts +3 -0
- package/dist/mock.js +3 -0
- package/dist/secrets/LambderOneShotSecrets.d.ts +166 -0
- package/dist/secrets/LambderOneShotSecrets.js +217 -0
- package/dist/session/LambderSessionCrypto.js +6 -16
- package/dist/shared/contracts/LambderIdempotencyStore.d.ts +3 -2
- package/dist/shared/contracts/LambderOneShotSecretStore.d.ts +122 -0
- package/dist/shared/contracts/LambderOneShotSecretStore.js +38 -0
- package/dist/shared/util/LambderBackoffTimer.d.ts +82 -0
- package/dist/shared/util/LambderBackoffTimer.js +86 -0
- package/dist/shared/util/LambderBase64.d.ts +14 -0
- package/dist/shared/util/LambderBase64.js +17 -0
- package/dist/shared/util/LambderSignedClaims.d.ts +78 -0
- package/dist/shared/util/LambderSignedClaims.js +109 -0
- package/dist/shared/util/LambderTextDigest.d.ts +19 -5
- package/dist/shared/util/LambderTextDigest.js +30 -5
- package/dist/shared/util/assertPlainData.d.ts +9 -0
- package/dist/shared/util/assertPlainData.js +41 -0
- package/dist/shared/util/escapeXmlText.d.ts +8 -0
- package/dist/shared/util/escapeXmlText.js +8 -0
- package/dist/shared/wire/LambderApiOptionEntries.d.ts +148 -0
- package/dist/shared/wire/LambderApiOptionEntries.js +35 -0
- package/dist/shared/wire/LambderUploadObjectFields.js +2 -2
- package/dist/stores/LambderDdbOneShotSecretStore.d.ts +64 -0
- package/dist/stores/LambderDdbOneShotSecretStore.js +266 -0
- package/dist/stores/LambderMemoryIdempotencyStore.d.ts +3 -2
- package/dist/stores/LambderMemoryIdempotencyStore.js +3 -2
- package/dist/stores/LambderMemoryOneShotSecretStore.d.ts +36 -0
- package/dist/stores/LambderMemoryOneShotSecretStore.js +93 -0
- package/dist/stores/LambderMemoryUploadBucket.js +2 -2
- package/dist/testing/LambderConformanceRunner.d.ts +46 -0
- package/dist/testing/LambderConformanceRunner.js +21 -0
- package/dist/testing/lambderIdempotencyStoreConformance.d.ts +33 -0
- package/dist/testing/lambderIdempotencyStoreConformance.js +237 -0
- package/dist/testing/lambderOneShotSecretStoreConformance.d.ts +43 -0
- package/dist/testing/lambderOneShotSecretStoreConformance.js +224 -0
- package/dist/testing/lambderRateLimiterConformance.d.ts +20 -0
- package/dist/testing/lambderRateLimiterConformance.js +72 -0
- package/dist/testing/lambderSessionStoreConformance.d.ts +27 -0
- package/dist/testing/lambderSessionStoreConformance.js +165 -0
- package/dist/testing.d.ts +14 -0
- package/dist/testing.js +12 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -9,6 +9,170 @@ sit on its first published patch, and later patches list only what they changed.
|
|
|
9
9
|
Releases up to 3.2.6 carry git tags; the ones after it were published without
|
|
10
10
|
one, so versions are not cross-linked to tag comparisons here.
|
|
11
11
|
|
|
12
|
+
## [8.3.1] - 2026-09-27
|
|
13
|
+
|
|
14
|
+
Six additions, each a thing an app otherwise writes for itself once per kind
|
|
15
|
+
of token, secret, retry loop, copied declaration or store: signed claims
|
|
16
|
+
tokens, one-shot secrets over a store that settles their races, the declared
|
|
17
|
+
API options as generated files of plain data, a backoff timer, the digest and
|
|
18
|
+
random secret behind stored secrets, and the store conformance suites for a
|
|
19
|
+
store an app writes over its own database. Nothing changes on the wire, and
|
|
20
|
+
every existing option keeps its meaning.
|
|
21
|
+
|
|
22
|
+
### Added
|
|
23
|
+
|
|
24
|
+
- **`writeApiOptions` in `lambder/build`: the declared options as a generated
|
|
25
|
+
file.** The contract carries every API's `guards`, `rateLimit` and
|
|
26
|
+
`idempotency` options as types; code that decides something at runtime with
|
|
27
|
+
them (a mock restating the server's policies, a test walking the public
|
|
28
|
+
surface, a screen asking which permission an endpoint needs) copied them by
|
|
29
|
+
hand and held the copies honest with tests that read the source.
|
|
30
|
+
`writeApiOptions({ module, exportName, file, check })` writes them once, as
|
|
31
|
+
three `as const` tables of plain data (`apiOptions`, `rateLimitPolicies`,
|
|
32
|
+
`guardDeclarations`) from the new `lambder.apiOptionEntries()`, sorted by
|
|
33
|
+
name, and `check: true` fails a stale file naming what moved per table.
|
|
34
|
+
Nothing in the file is code: a guard parameter that is not plain data fails
|
|
35
|
+
the write by API name, a policy keyed by a handler is written as `per:
|
|
36
|
+
"custom"` and no more, and a guard's schema as its input mode alone.
|
|
37
|
+
- Readers in `lambder/client`, typed to the tables' literals:
|
|
38
|
+
`LambderApisWithGuard`, `LambderApisGuardedBy`, `LambderApisWithMode`,
|
|
39
|
+
`LambderGuardParamOf` and `apiGuardParam(apiOptions, name, guard)`.
|
|
40
|
+
- `writeApiGuardParams({ module, exportName, guard, file, check })` writes
|
|
41
|
+
one guard's parameters beside it, as an `as const` table (`guardParams`)
|
|
42
|
+
of the APIs that declare the guard and what each gives it, with nothing
|
|
43
|
+
else about any API and no import: the least a browser gating a screen on
|
|
44
|
+
that guard needs, where importing `apiOptions` as a value would ship
|
|
45
|
+
every endpoint name and every guard's parameter, reasons included.
|
|
46
|
+
- `lambderMockPoliciesFrom(rateLimitPolicies, { keys })` in `lambder/mock`
|
|
47
|
+
rebuilds the policy configs `create()` takes from the table, requiring a
|
|
48
|
+
key handler for exactly the custom-keyed policies; `create()` takes a
|
|
49
|
+
`guardDeclarations` option that holds each mock guard to the server
|
|
50
|
+
guard's input mode and session requirement (`LambderMockGuardShapeOf`);
|
|
51
|
+
and an `apiOptions` option, the table itself, from which every entry's
|
|
52
|
+
guards, rate limit and idempotency are read. Given it, an entry is its
|
|
53
|
+
handler alone, a restated option is a compile error and a throw, the
|
|
54
|
+
table must cover the contract under each endpoint's mode
|
|
55
|
+
(`LambderMockApiOptionsCover`), and a `restNotMocked` answer runs under
|
|
56
|
+
the mode the table gives the name, so an unmocked session endpoint reads
|
|
57
|
+
the session first.
|
|
58
|
+
- The entry types `LambderApiOptionEntries`, `LambderApiOptionEntry`,
|
|
59
|
+
`LambderRateLimitPolicyEntry` and `LambderGuardDeclarationEntry` live in
|
|
60
|
+
`shared/wire`, with `LambderGuardRunAt`, `LambderRateLimitBudget` and
|
|
61
|
+
`LambderRateLimitChargeAt`, which moved down there unchanged.
|
|
62
|
+
|
|
63
|
+
See [the options as a generated file](./docs/apis.md#the-options-as-a-generated-file).
|
|
64
|
+
|
|
65
|
+
- **`LambderSignedClaims`: signed tokens that are their own record.** One
|
|
66
|
+
instance per kind of token, built once with the secret, a version and the
|
|
67
|
+
zod schema of its claims; `sign(claims)` writes `<version>.<base64url
|
|
68
|
+
claims>.<base64url HMAC-SHA256>`, `verify(token, { now? })` answers the
|
|
69
|
+
claims or null for a forged, foreign, malformed, refused or expired token
|
|
70
|
+
alike. An optional `exp` claim in epoch seconds is judged on every verify,
|
|
71
|
+
against an injectable clock. WebCrypto only, exported from `lambder` and
|
|
72
|
+
`lambder/client` for the edge runtimes and shared backend packages that
|
|
73
|
+
verify where the secret is at hand. Beside it, `keyedDigest(secret,
|
|
74
|
+
value)`, the HMAC-SHA256 as base64url a stored secret rests as;
|
|
75
|
+
`randomSecret(bytes?)`; and `constantTimeEquals`.
|
|
76
|
+
|
|
77
|
+
- **`LambderOneShotSecrets`: codes and tokens handed out once and taken back
|
|
78
|
+
once.** The code emailed to an address, the link in an activation mail, the
|
|
79
|
+
code texted before a document opens, the code read out to pair a device:
|
|
80
|
+
one life (minted, sent, stored as a digest, tried against, spent), written
|
|
81
|
+
once, over a `LambderOneShotSecretStore` that settles its races in six
|
|
82
|
+
methods. An app declares its kinds (a `code` of an alphabet and length with
|
|
83
|
+
a ceiling on tries, or a `token` redeemed by value: random bytes, or an
|
|
84
|
+
alphabet's characters for one somebody types) and names, per secret,
|
|
85
|
+
the scope it proves; `issue(kind, scope, { cooldownSeconds?, meta? })`
|
|
86
|
+
answers the plaintext exactly once, `redeem(kind, scope, candidate)` and
|
|
87
|
+
`redeemToken(kind, candidate)` answer `accepted`, `wrong` (with the tries
|
|
88
|
+
left), `expired`, `exhausted` or `none`, and `retire(scope)` ends what the
|
|
89
|
+
scope holds. One record is live per scope; a cooldown is a condition on
|
|
90
|
+
the issuing write; a token's digest is claimed by one scope at a time in
|
|
91
|
+
that same write, and a secret drawn onto a digest another scope holds is
|
|
92
|
+
drawn again (up to five times), so two scopes that drew the same short
|
|
93
|
+
code never redeem each other's; a try is counted in the write that reads
|
|
94
|
+
the digest; a redemption is a conditional consume.
|
|
95
|
+
`LambderDdbOneShotSecretStore` keeps a code as one item under `OTS#` in
|
|
96
|
+
the policy table and a token as two, written in one transaction, and sends
|
|
97
|
+
a write DynamoDB refused for a concurrent transaction on its item again, up
|
|
98
|
+
to three times; `LambderMemoryOneShotSecretStore` keeps the same in a map, and
|
|
99
|
+
`lambderOneShotSecretStoreConformance` holds both, and an app's own store,
|
|
100
|
+
to one set of rules. The class sits in a new `src/secrets/` layer beside
|
|
101
|
+
`session/`.
|
|
102
|
+
|
|
103
|
+
- **`LambderBackoffTimer`: waiting longer after each failure, once.** One
|
|
104
|
+
pending wait at a time: `retry(run)` and `wait(signal?)` climb a jittered
|
|
105
|
+
ladder (`baseMs` the shortest wait, `maxMs` the longest, `factor`,
|
|
106
|
+
`jitter`), `after(ms, run)` waits off
|
|
107
|
+
it, `reset()` and `cancel()`; `wait` rejects with the signal's reason on
|
|
108
|
+
abort and with an Error when dropped, so an await on it always settles.
|
|
109
|
+
Exported from `lambder` and `lambder/client`.
|
|
110
|
+
|
|
111
|
+
See [Secrets and retries](./docs/secrets.md).
|
|
112
|
+
|
|
113
|
+
- **Store conformance suites in `lambder/testing`.** The rules each store
|
|
114
|
+
interface promises its engine were asserted in Lambder's own test suite,
|
|
115
|
+
where only Lambder's stores could meet them; a store an app writes over its
|
|
116
|
+
own database had nothing to hold it to the same rules. They are now
|
|
117
|
+
exported, one suite per interface: `lambderSessionStoreConformance`,
|
|
118
|
+
`lambderIdempotencyStoreConformance`, `lambderRateLimiterConformance` and
|
|
119
|
+
`lambderOneShotSecretStoreConformance`. Each takes the runner's own `it`
|
|
120
|
+
and `expect` (any jest-style `expect`; Lambder imports no runner) and a
|
|
121
|
+
`create` that builds a store for one case, handed the case's clock as
|
|
122
|
+
`now`. The one-shot suite also takes what a store over existing rows needs
|
|
123
|
+
in place of its defaults: two `scopes` it can hold, the kind it keeps for
|
|
124
|
+
each shape it holds (`kinds: { code?, token? }`, which decides whether the
|
|
125
|
+
cases about tries or the ones about digests run), the `meta`, and the
|
|
126
|
+
`lifetimeSeconds` it derives an expiry from. Among its rules: two issues
|
|
127
|
+
racing for one scope leave exactly one live record, two scopes racing for
|
|
128
|
+
one token digest leave it with exactly one, and `attempt` and `consume`
|
|
129
|
+
name a record by its scope and id together, so a record of another scope
|
|
130
|
+
is never the one named. Lambder's memory and DynamoDB stores run through
|
|
131
|
+
the same suites.
|
|
132
|
+
|
|
133
|
+
See [A store of your own](./docs/testing.md#a-store-of-your-own).
|
|
134
|
+
|
|
135
|
+
### Changed
|
|
136
|
+
|
|
137
|
+
- **`LambderUploadRunner` waits on a `LambderBackoffTimer` between tries at
|
|
138
|
+
storage.** The ladder is the timer's: each wait is `baseDelayMs` plus a
|
|
139
|
+
random share of a ceiling that starts at `baseDelayMs` and doubles per
|
|
140
|
+
failed attempt, the whole never past `maxDelayMs`. The first wait is
|
|
141
|
+
unchanged (between the base and twice it); a later one may be shorter than
|
|
142
|
+
before, since the ceiling now counts from the base rather than from twice
|
|
143
|
+
it.
|
|
144
|
+
- **The session crypto shares its HMAC and its constant-time comparison** with
|
|
145
|
+
the new module through `shared/util/LambderTextDigest.ts` rather than
|
|
146
|
+
keeping copies of its own. Behaviour is unchanged.
|
|
147
|
+
|
|
148
|
+
## [8.1.2] - 2026-09-26
|
|
149
|
+
|
|
150
|
+
### Fixed
|
|
151
|
+
|
|
152
|
+
- **`writeApiContract` names no longer follow the order APIs are registered
|
|
153
|
+
in.** Two types that want one name (an interface `Row` in two modules) were
|
|
154
|
+
told apart by a number given in the order the printer met them, so
|
|
155
|
+
reordering registrations, or adding an API that reaches one of them first,
|
|
156
|
+
swapped `Row` and `Row2` and reported every API using either as changed.
|
|
157
|
+
The printer now finds every declaration first and numbers them by where
|
|
158
|
+
each is declared, the one declared first (by file, then position) keeping
|
|
159
|
+
the name, and a number never takes the name another type is declared
|
|
160
|
+
under. A file already written may be renamed once, the first time it is
|
|
161
|
+
written with this version.
|
|
162
|
+
- **`LambderUploadRunner` randomises its first wait too.** The ceiling of the
|
|
163
|
+
first wait before trying storage again was the base itself, so browsers
|
|
164
|
+
dropped together all came back exactly `baseDelayMs` later. Each wait is
|
|
165
|
+
now between `baseDelayMs` and twice it, doubling with every failed attempt
|
|
166
|
+
up to `maxDelayMs`.
|
|
167
|
+
- **`LambderUploadRunner` renews a ticket on `ExpiredToken`,** which S3
|
|
168
|
+
answers when the temporary credentials that signed the ticket ran out
|
|
169
|
+
before the ticket did. It is renewed like an expired ticket, at once and
|
|
170
|
+
spending no attempt; before, the upload failed as `storageRejected`.
|
|
171
|
+
- **`LambderUploadRunner` no longer holds the file's bytes through the
|
|
172
|
+
upload.** The buffer it hashes, as large as the file, was kept in a local
|
|
173
|
+
for the whole post and every retry; it is now read straight into the
|
|
174
|
+
digest.
|
|
175
|
+
|
|
12
176
|
## [8.1.1] - 2026-09-26
|
|
13
177
|
|
|
14
178
|
Two additions, and three breaking changes a minor line carries here on
|
package/README.md
CHANGED
|
@@ -113,10 +113,10 @@ The package ships five entry points; pick by where the code runs:
|
|
|
113
113
|
| Entry | Runs in | Carries |
|
|
114
114
|
| --- | --- | --- |
|
|
115
115
|
| `lambder` | Server (Lambda) | The full framework: pipeline, sessions, DDB stores, policies, plus the API core's building blocks and everything from `lambder/client` except the two request-compression helpers, `compressPayloadGzip` and `isRequestCompressionAvailable`, which stay on the client entry where a payload is compressed |
|
|
116
|
-
| `lambder/client` | Browser and isomorphic shared code | `LambderCaller`, `LambderApiRefusal`/`refuse`, the API contract and envelope types, `html`/`xml` tagged templates, `createLambderI18n` |
|
|
116
|
+
| `lambder/client` | Browser and isomorphic shared code | `LambderCaller`, `LambderApiRefusal`/`refuse`, the API contract and envelope types, `LambderUploadRunner`, `LambderBackoffTimer`, `LambderSignedClaims`, `html`/`xml` tagged templates, `createLambderI18n` |
|
|
117
117
|
| `lambder/mock` | Browser and Node, in development and tests | `LambderMockApp`, the mock runtime: your typed contract served from mock handlers over the real API pipeline and memory stores |
|
|
118
|
-
| `lambder/testing` | Node, in tests | `lambderTestApp`: your real instance under test in this process, memory stores put under it in place, simulated browsers with typed callers in front of it, and the outcome assertions |
|
|
119
|
-
| `lambder/build` | Node, in a build step | `writeApiSignatures`: the signature file both sides ship, written or checked from your instance; `writeApiContract`: the contract as plain types a client compiles instead of the server |
|
|
118
|
+
| `lambder/testing` | Node, in tests | `lambderTestApp`: your real instance under test in this process, memory stores put under it in place, simulated browsers with typed callers in front of it, and the outcome assertions; the store conformance suites, to hold a store you write to the rules Lambder's own meet |
|
|
119
|
+
| `lambder/build` | Node, in a build step | `writeApiSignatures`: the signature file both sides ship, written or checked from your instance; `writeApiOptions`: every API's declared options, policies and guard declarations as plain data, for the code that decides with them; `writeApiGuardParams`: one guard's parameters and nothing else, what a browser gating on that guard carries; `writeApiContract`: the contract as plain types a client compiles instead of the server |
|
|
120
120
|
|
|
121
121
|
Frontends and shared isomorphic packages should import from `lambder/client`
|
|
122
122
|
only; the entry's module graph contains no AWS SDK, Node built-ins, or server
|
|
@@ -126,14 +126,15 @@ tree-shaking.
|
|
|
126
126
|
Source layout mirrors this: `src/api/` (the isomorphic API core: request,
|
|
127
127
|
answer, envelope, pipeline, and the declarative policies the pipeline runs),
|
|
128
128
|
`src/core/` (the Lambda server adapter: routes, files, hooks, finalization),
|
|
129
|
-
`src/session/` (the session manager, controller and crypto), `src/
|
|
129
|
+
`src/session/` (the session manager, controller and crypto), `src/secrets/`
|
|
130
|
+
(one-shot secrets over their store), `src/stores/`
|
|
130
131
|
(every store and file-source implementation, DynamoDB and in-memory alike,
|
|
131
132
|
and the helpers the two caches share), `src/client/`,
|
|
132
133
|
`src/invoke/` (the lambda-to-lambda caller and the in-process handler
|
|
133
|
-
transport), `src/mock/` (the mock runtime), `src/testing/` (the test app),
|
|
134
|
+
transport), `src/mock/` (the mock runtime), `src/testing/` (the test app and the store conformance suites),
|
|
134
135
|
`src/build/` (what a generator script runs at build time), and `src/shared/`
|
|
135
136
|
(isomorphic modules every entry re-exports, grouped into `wire/` for the
|
|
136
|
-
format both sides speak, `contracts/` for the
|
|
137
|
+
format both sides speak, `contracts/` for the six store and source
|
|
137
138
|
interfaces, `transport/` for the caller-to-server seam, and `util/` for
|
|
138
139
|
helpers).
|
|
139
140
|
Directories are layers and imports only ever point down;
|
|
@@ -156,7 +157,7 @@ guide that matches what you are building. The full index lives in
|
|
|
156
157
|
| [Sessions](./docs/sessions.md) | Sessions over a store, cookie scope, secrets at rest, `dataRefresh`, the controller API |
|
|
157
158
|
| [API policies](./docs/api-policies.md) | Declarative rate limits, guards and idempotency, and mandatory authorization declarations |
|
|
158
159
|
| [Calling another lambda](./docs/invoke.md) | `LambderInvokeCaller`: invoking a Lambder app in another function, its contract, failures and compression |
|
|
159
|
-
| [Testing](./docs/testing.md) | `lambderTestApp`: the real instance under test with no HTTP and no AWS, visitors, sign-in without a login endpoint, outcome assertions, crashes, time |
|
|
160
|
+
| [Testing](./docs/testing.md) | `lambderTestApp`: the real instance under test with no HTTP and no AWS, visitors, sign-in without a login endpoint, outcome assertions, crashes, time, store conformance suites |
|
|
160
161
|
| [The API core](./docs/api-core.md) | `LambderApiPipeline`: the one pipeline the server and the mock runtime run, the store interfaces, the transports |
|
|
161
162
|
| [Frontend client](./docs/client.md) | `LambderCaller`: typed calls, failure outcomes, timeouts, guard inputs, request compression, transports |
|
|
162
163
|
| [Frontend hosting](./docs/frontend-hosting.md) | File sources, `servePublicFiles`, `serveIndexHtml`, `res.templateFile` |
|
|
@@ -37,22 +37,8 @@ type LambderGuardAnswerCheck<TOutput> = [
|
|
|
37
37
|
type LambderGuardOf<TOutput, TGuard> = LambderGuardAnswerCheck<TOutput> extends LambderGuardMustNotAnswer ? LambderGuardMustNotAnswer : TGuard;
|
|
38
38
|
/** One guard handler: the adapter's context, the validated input slice (undefined in the no-input mode), and the per-API parameter. */
|
|
39
39
|
type LambderGuardHandler<TCtx, TPayload, TParam, TOutput> = (ctx: TCtx, payload: TPayload, param: TParam) => TOutput | Promise<TOutput>;
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
*
|
|
43
|
-
* - "beforeInputValidation" (default): an unauthorized caller learns nothing
|
|
44
|
-
* about the input, and no async refinement in the schema runs for it.
|
|
45
|
-
* - "afterInputValidation": for a guard that spends something on the
|
|
46
|
-
* request, such as a single-use captcha token, which a request refused for
|
|
47
|
-
* a mistyped field would otherwise waste. The API's input schema then runs
|
|
48
|
-
* for callers this guard would refuse, so keep lookups (an "email is free"
|
|
49
|
-
* refinement) out of it, in the handler.
|
|
50
|
-
*
|
|
51
|
-
* Guards run in their declared order within each, and the limits keyed by
|
|
52
|
-
* caller data are charged after both unless their policy says otherwise
|
|
53
|
-
* (LambderRateLimitChargeAt).
|
|
54
|
-
*/
|
|
55
|
-
export type LambderGuardRunAt = "beforeInputValidation" | "afterInputValidation";
|
|
40
|
+
import type { LambderGuardRunAt } from "../shared/wire/LambderApiOptionEntries.js";
|
|
41
|
+
export type { LambderGuardRunAt };
|
|
56
42
|
/** Where in the call a guard runs: see LambderGuardRunAt. */
|
|
57
43
|
export type LambderGuardPlacement = {
|
|
58
44
|
/** Default: "beforeInputValidation". */
|
|
@@ -331,4 +317,3 @@ export declare class LambderApiGuardsEngine {
|
|
|
331
317
|
*/
|
|
332
318
|
run(request: LambderApiRequest, ctx: LambderApiCallContext, guardsOption: LambderGuardsOptionValue | undefined, trace: LambderApiCallTrace, runAt: LambderGuardRunAt): Promise<void>;
|
|
333
319
|
}
|
|
334
|
-
export {};
|
|
@@ -78,34 +78,8 @@ export type LambderRateLimitKeyBuilder<TCtx> = {
|
|
|
78
78
|
export declare const lambderRateLimitKeyBuilder: <TCtx>() => LambderRateLimitKeyBuilder<TCtx>;
|
|
79
79
|
/** What one rate-limit counter tracks: the client IP, the session identity, or a custom payload-derived key. */
|
|
80
80
|
export type LambderRateLimitPer<TCtx = any> = "ip" | "session" | LambderRateLimitKeyFn<any, TCtx>;
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
*
|
|
84
|
-
* - "perApi" (default): every API referencing the policy gets its own
|
|
85
|
-
* counter, so the windows are a per-API ceiling (three APIs referencing a
|
|
86
|
-
* 60/min policy allow one subject 180/min in total). An API may tune the
|
|
87
|
-
* windows in its declaration: `rateLimit: { name: { perMin: 20 } }`.
|
|
88
|
-
* - "perPolicy": every API referencing the policy shares ONE counter, so the
|
|
89
|
-
* windows are one combined budget (e.g. one per-email allowance across
|
|
90
|
-
* send, register, and reset). The policy IS the group: to give user APIs
|
|
91
|
-
* and report APIs separate shared budgets, declare two policies.
|
|
92
|
-
*/
|
|
93
|
-
export type LambderRateLimitBudget = "perApi" | "perPolicy";
|
|
94
|
-
/**
|
|
95
|
-
* When a custom-keyed policy is charged, relative to the guards and the input
|
|
96
|
-
* schema.
|
|
97
|
-
*
|
|
98
|
-
* - "afterGuards" (default): after every guard and the input schema passed.
|
|
99
|
-
* The key is a value the caller chose (an email in the payload), so a
|
|
100
|
-
* caller who never passes a captcha guard cannot spend a victim's budget
|
|
101
|
-
* and lock them out of reset, register and send-code.
|
|
102
|
-
* - "beforeGuards": before the guards and the input schema, so an attempt
|
|
103
|
-
* they refuse is counted too. For a limit on guessing a secret a guard or
|
|
104
|
-
* the schema checks (a one-time code checked by a guard, keyed per email):
|
|
105
|
-
* charged after them, a wrong guess is refused before it is ever counted.
|
|
106
|
-
* Pair it with an IP limit, since anyone may spend this budget.
|
|
107
|
-
*/
|
|
108
|
-
export type LambderRateLimitChargeAt = "beforeGuards" | "afterGuards";
|
|
81
|
+
import type { LambderRateLimitBudget, LambderRateLimitChargeAt } from "../shared/wire/LambderApiOptionEntries.js";
|
|
82
|
+
export type { LambderRateLimitBudget, LambderRateLimitChargeAt };
|
|
109
83
|
/**
|
|
110
84
|
* A named rate-limit policy: fixed windows, the key one counter tracks, and
|
|
111
85
|
* what one budget spans.
|
|
@@ -357,4 +331,3 @@ export declare class LambderApiRateLimitsEngine {
|
|
|
357
331
|
*/
|
|
358
332
|
private requestKeyOf;
|
|
359
333
|
}
|
|
360
|
-
export {};
|
|
@@ -22,18 +22,40 @@ export declare class ContractTypePrinter {
|
|
|
22
22
|
private readonly program;
|
|
23
23
|
private readonly checker;
|
|
24
24
|
private readonly style;
|
|
25
|
-
private
|
|
26
|
-
/** Every
|
|
27
|
-
private
|
|
25
|
+
private failures;
|
|
26
|
+
/** Every declaration by the type it stands for. */
|
|
27
|
+
private declarations;
|
|
28
|
+
/** The name each declaration is printed under, settled from all of them; empty on the pass that finds them. */
|
|
29
|
+
private settledNames;
|
|
28
30
|
/** Names a declaration may not take: the default library's, and the contract's own. */
|
|
29
|
-
private readonly
|
|
31
|
+
private readonly reservedNames;
|
|
30
32
|
/** The anonymous types being printed: meeting one again inside itself is recursion, and it needs a name. */
|
|
31
33
|
private readonly inProgress;
|
|
32
34
|
/** Under exactOptionalPropertyTypes an optional member's type carries the compiler's own "missing" undefined, which its source never wrote. */
|
|
33
35
|
private readonly exactOptionalProperties;
|
|
34
36
|
constructor(ts: typeof import("typescript"), program: ts.Program, checker: ts.TypeChecker, style: ContractPrintStyle, contractName: string);
|
|
35
|
-
/**
|
|
37
|
+
/**
|
|
38
|
+
* Prints each member of the contract type, sorted by name, and every
|
|
39
|
+
* declaration they refer to.
|
|
40
|
+
*
|
|
41
|
+
* In two passes: the first finds every type that needs a declaration,
|
|
42
|
+
* and the second prints with their names settled from all of them. Named
|
|
43
|
+
* as the printer met them, two types wanting one name would trade it
|
|
44
|
+
* whenever the APIs were registered in another order, or a union's
|
|
45
|
+
* members created in another, and the file would move with no API
|
|
46
|
+
* changed.
|
|
47
|
+
*/
|
|
36
48
|
printContract(contract: ts.Type): PrintedContract;
|
|
49
|
+
/**
|
|
50
|
+
* A name for every declaration the first pass found. Of the types that
|
|
51
|
+
* want one name, the one declared first (by file, then position) keeps
|
|
52
|
+
* it, and the others take the lowest free number after it once every
|
|
53
|
+
* type has claimed its own name, so a number never takes the name
|
|
54
|
+
* another type is declared under. Types declared at one place (two
|
|
55
|
+
* instantiations of a generic) keep the order the entries reached them
|
|
56
|
+
* in, by entry name.
|
|
57
|
+
*/
|
|
58
|
+
private settleNames;
|
|
37
59
|
/**
|
|
38
60
|
* `label value`, with a union too long for one line starting on the next
|
|
39
61
|
* line, one member per line: what a property, an index signature and a
|
|
@@ -43,16 +65,25 @@ export declare class ContractTypePrinter {
|
|
|
43
65
|
private print;
|
|
44
66
|
/** A union, an intersection or an object: printed in place, or as a reference to a declaration of its own. */
|
|
45
67
|
private printComposite;
|
|
46
|
-
/**
|
|
47
|
-
private
|
|
68
|
+
/** A type's declaration, under the name it is settled to once the first pass has settled them. */
|
|
69
|
+
private declare;
|
|
70
|
+
/** The name a type is declared under and where, when it is one to print as a declaration: non-generic, and not the default library's. */
|
|
71
|
+
private ownDeclarationOf;
|
|
72
|
+
/** A symbol's name to declare a type under, and where the symbol is declared. */
|
|
73
|
+
private declaredAs;
|
|
74
|
+
/** Where a symbol is declared, as text that orders by file and then position; empty for one declared nowhere. */
|
|
75
|
+
private originOf;
|
|
48
76
|
/** The name a symbol is declared under, read off its declaration, so `export default interface Customer` is Customer; undefined when it has none to print. */
|
|
49
77
|
private nameOf;
|
|
50
78
|
/**
|
|
51
79
|
* A name for a type that refers to itself and has none of its own: what it
|
|
52
80
|
* instantiates followed by its arguments (a JSON mapping of a Tree is
|
|
53
81
|
* `JsonOfTree`, a `Tree<string>` is `TreeString`), or `RecursiveType`.
|
|
82
|
+
* Its origin is where what it instantiates is declared, then where each
|
|
83
|
+
* argument is, so two instantiations named alike are told apart by their
|
|
84
|
+
* arguments.
|
|
54
85
|
*/
|
|
55
|
-
private
|
|
86
|
+
private recursiveDeclarationOf;
|
|
56
87
|
private printStructure;
|
|
57
88
|
private printUnion;
|
|
58
89
|
private printUnionOf;
|
|
@@ -80,6 +111,5 @@ export declare class ContractTypePrinter {
|
|
|
80
111
|
private wrapped;
|
|
81
112
|
keyOf(name: string): string;
|
|
82
113
|
private quoted;
|
|
83
|
-
private takeName;
|
|
84
114
|
private fail;
|
|
85
115
|
}
|
|
@@ -37,10 +37,12 @@ export class ContractTypePrinter {
|
|
|
37
37
|
checker;
|
|
38
38
|
style;
|
|
39
39
|
failures = [];
|
|
40
|
-
/** Every
|
|
40
|
+
/** Every declaration by the type it stands for. */
|
|
41
41
|
declarations = new Map();
|
|
42
|
+
/** The name each declaration is printed under, settled from all of them; empty on the pass that finds them. */
|
|
43
|
+
settledNames = new Map();
|
|
42
44
|
/** Names a declaration may not take: the default library's, and the contract's own. */
|
|
43
|
-
|
|
45
|
+
reservedNames = new Set();
|
|
44
46
|
/** The anonymous types being printed: meeting one again inside itself is recursion, and it needs a name. */
|
|
45
47
|
inProgress = new Set();
|
|
46
48
|
/** Under exactOptionalPropertyTypes an optional member's type carries the compiler's own "missing" undefined, which its source never wrote. */
|
|
@@ -50,7 +52,7 @@ export class ContractTypePrinter {
|
|
|
50
52
|
this.program = program;
|
|
51
53
|
this.checker = checker;
|
|
52
54
|
this.style = style;
|
|
53
|
-
this.
|
|
55
|
+
this.reservedNames.add(contractName);
|
|
54
56
|
this.exactOptionalProperties = !!program.getCompilerOptions().exactOptionalPropertyTypes;
|
|
55
57
|
// A declaration named after a global the printed text refers to by
|
|
56
58
|
// name (Date) would shadow it.
|
|
@@ -60,20 +62,65 @@ export class ContractTypePrinter {
|
|
|
60
62
|
for (const statement of sourceFile.statements) {
|
|
61
63
|
if ((ts.isInterfaceDeclaration(statement) || ts.isTypeAliasDeclaration(statement) || ts.isClassDeclaration(statement) || ts.isModuleDeclaration(statement))
|
|
62
64
|
&& statement.name && ts.isIdentifier(statement.name))
|
|
63
|
-
this.
|
|
65
|
+
this.reservedNames.add(statement.name.text);
|
|
64
66
|
}
|
|
65
67
|
}
|
|
66
68
|
}
|
|
67
|
-
/**
|
|
69
|
+
/**
|
|
70
|
+
* Prints each member of the contract type, sorted by name, and every
|
|
71
|
+
* declaration they refer to.
|
|
72
|
+
*
|
|
73
|
+
* In two passes: the first finds every type that needs a declaration,
|
|
74
|
+
* and the second prints with their names settled from all of them. Named
|
|
75
|
+
* as the printer met them, two types wanting one name would trade it
|
|
76
|
+
* whenever the APIs were registered in another order, or a union's
|
|
77
|
+
* members created in another, and the file would move with no API
|
|
78
|
+
* changed.
|
|
79
|
+
*/
|
|
68
80
|
printContract(contract) {
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
|
|
81
|
+
const members = [...this.checker.getPropertiesOfType(contract)].sort((a, b) => byCodeUnits(a.name, b.name));
|
|
82
|
+
const printEntries = () => members.map((entry) => ({ name: entry.name, text: this.print(this.checker.getTypeOfSymbol(entry), this.keyOf(entry.name)).text }));
|
|
83
|
+
printEntries();
|
|
84
|
+
this.settledNames = this.settleNames();
|
|
85
|
+
this.failures = [];
|
|
86
|
+
this.declarations = new Map();
|
|
87
|
+
const entries = printEntries();
|
|
72
88
|
const declarations = [...this.declarations.values()]
|
|
73
89
|
.map(({ name, text }) => ({ name, text: text ?? "never" }))
|
|
74
90
|
.sort((a, b) => byCodeUnits(a.name, b.name));
|
|
75
91
|
return { entries, declarations, failures: this.failures };
|
|
76
92
|
}
|
|
93
|
+
/**
|
|
94
|
+
* A name for every declaration the first pass found. Of the types that
|
|
95
|
+
* want one name, the one declared first (by file, then position) keeps
|
|
96
|
+
* it, and the others take the lowest free number after it once every
|
|
97
|
+
* type has claimed its own name, so a number never takes the name
|
|
98
|
+
* another type is declared under. Types declared at one place (two
|
|
99
|
+
* instantiations of a generic) keep the order the entries reached them
|
|
100
|
+
* in, by entry name.
|
|
101
|
+
*/
|
|
102
|
+
settleNames() {
|
|
103
|
+
const wanting = [...this.declarations].sort(([, a], [, b]) => byCodeUnits(a.base, b.base) || byCodeUnits(a.origin, b.origin));
|
|
104
|
+
const taken = new Set(this.reservedNames);
|
|
105
|
+
const settled = new Map();
|
|
106
|
+
const numbered = [];
|
|
107
|
+
for (const [type, { base }] of wanting) {
|
|
108
|
+
if (taken.has(base)) {
|
|
109
|
+
numbered.push([type, base]);
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
taken.add(base);
|
|
113
|
+
settled.set(type, base);
|
|
114
|
+
}
|
|
115
|
+
for (const [type, base] of numbered) {
|
|
116
|
+
let suffix = 2;
|
|
117
|
+
while (taken.has(`${base}${suffix}`))
|
|
118
|
+
suffix++;
|
|
119
|
+
taken.add(`${base}${suffix}`);
|
|
120
|
+
settled.set(type, `${base}${suffix}`);
|
|
121
|
+
}
|
|
122
|
+
return settled;
|
|
123
|
+
}
|
|
77
124
|
/**
|
|
78
125
|
* `label value`, with a union too long for one line starting on the next
|
|
79
126
|
* line, one member per line: what a property, an index signature and a
|
|
@@ -142,20 +189,16 @@ export class ContractTypePrinter {
|
|
|
142
189
|
const known = this.declarations.get(type);
|
|
143
190
|
if (known)
|
|
144
191
|
return atom(known.name);
|
|
145
|
-
const
|
|
146
|
-
if (
|
|
192
|
+
const own = this.ownDeclarationOf(type);
|
|
193
|
+
if (own) {
|
|
147
194
|
// Registered before the body is printed, so a reference to itself
|
|
148
195
|
// inside the body finds the name.
|
|
149
|
-
const declaration =
|
|
150
|
-
this.declarations.set(type, declaration);
|
|
196
|
+
const declaration = this.declare(type, own);
|
|
151
197
|
declaration.text = this.printStructure(type, path).text;
|
|
152
198
|
return atom(declaration.name);
|
|
153
199
|
}
|
|
154
|
-
if (this.inProgress.has(type))
|
|
155
|
-
|
|
156
|
-
this.declarations.set(type, declaration);
|
|
157
|
-
return atom(declaration.name);
|
|
158
|
-
}
|
|
200
|
+
if (this.inProgress.has(type))
|
|
201
|
+
return atom(this.declare(type, this.recursiveDeclarationOf(type)).name);
|
|
159
202
|
this.inProgress.add(type);
|
|
160
203
|
const printed = this.printStructure(type, path);
|
|
161
204
|
this.inProgress.delete(type);
|
|
@@ -166,11 +209,17 @@ export class ContractTypePrinter {
|
|
|
166
209
|
recursive.text = printed.text;
|
|
167
210
|
return atom(recursive.name);
|
|
168
211
|
}
|
|
169
|
-
/**
|
|
170
|
-
|
|
212
|
+
/** A type's declaration, under the name it is settled to once the first pass has settled them. */
|
|
213
|
+
declare(type, { base, origin }) {
|
|
214
|
+
const declaration = { base, origin, name: this.settledNames.get(type) ?? base, text: null };
|
|
215
|
+
this.declarations.set(type, declaration);
|
|
216
|
+
return declaration;
|
|
217
|
+
}
|
|
218
|
+
/** The name a type is declared under and where, when it is one to print as a declaration: non-generic, and not the default library's. */
|
|
219
|
+
ownDeclarationOf(type) {
|
|
171
220
|
const { ObjectFlags, TypeFlags } = this.ts;
|
|
172
221
|
if (type.aliasSymbol) {
|
|
173
|
-
return type.aliasTypeArguments?.length || this.isDefaultLibrary(type.aliasSymbol) ? undefined : this.
|
|
222
|
+
return type.aliasTypeArguments?.length || this.isDefaultLibrary(type.aliasSymbol) ? undefined : this.declaredAs(type.aliasSymbol);
|
|
174
223
|
}
|
|
175
224
|
if (!(type.flags & TypeFlags.Object))
|
|
176
225
|
return undefined;
|
|
@@ -182,7 +231,17 @@ export class ContractTypePrinter {
|
|
|
182
231
|
// it instead, and are printed in place.
|
|
183
232
|
if (objectFlags & ObjectFlags.Reference && (type.target !== type || type.typeParameters?.length))
|
|
184
233
|
return undefined;
|
|
185
|
-
return this.isDefaultLibrary(type.symbol) ? undefined : this.
|
|
234
|
+
return this.isDefaultLibrary(type.symbol) ? undefined : this.declaredAs(type.symbol);
|
|
235
|
+
}
|
|
236
|
+
/** A symbol's name to declare a type under, and where the symbol is declared. */
|
|
237
|
+
declaredAs(symbol) {
|
|
238
|
+
const base = this.nameOf(symbol);
|
|
239
|
+
return base === undefined ? undefined : { base, origin: this.originOf(symbol) };
|
|
240
|
+
}
|
|
241
|
+
/** Where a symbol is declared, as text that orders by file and then position; empty for one declared nowhere. */
|
|
242
|
+
originOf(symbol) {
|
|
243
|
+
const declaration = symbol?.declarations?.[0];
|
|
244
|
+
return declaration ? `${declaration.getSourceFile().fileName}\0${String(declaration.pos).padStart(10, "0")}` : "";
|
|
186
245
|
}
|
|
187
246
|
/** The name a symbol is declared under, read off its declaration, so `export default interface Customer` is Customer; undefined when it has none to print. */
|
|
188
247
|
nameOf(symbol) {
|
|
@@ -195,8 +254,11 @@ export class ContractTypePrinter {
|
|
|
195
254
|
* A name for a type that refers to itself and has none of its own: what it
|
|
196
255
|
* instantiates followed by its arguments (a JSON mapping of a Tree is
|
|
197
256
|
* `JsonOfTree`, a `Tree<string>` is `TreeString`), or `RecursiveType`.
|
|
257
|
+
* Its origin is where what it instantiates is declared, then where each
|
|
258
|
+
* argument is, so two instantiations named alike are told apart by their
|
|
259
|
+
* arguments.
|
|
198
260
|
*/
|
|
199
|
-
|
|
261
|
+
recursiveDeclarationOf(type) {
|
|
200
262
|
const { ObjectFlags, TypeFlags } = this.ts;
|
|
201
263
|
let instantiated;
|
|
202
264
|
if (type.aliasSymbol) {
|
|
@@ -209,11 +271,14 @@ export class ContractTypePrinter {
|
|
|
209
271
|
}
|
|
210
272
|
const base = instantiated && this.nameOf(instantiated.symbol);
|
|
211
273
|
if (!instantiated || !base)
|
|
212
|
-
return "RecursiveType";
|
|
274
|
+
return { base: "RecursiveType", origin: this.originOf(type.symbol) };
|
|
213
275
|
const argumentNames = instantiated.typeArguments
|
|
214
276
|
.map((argument) => (argument.aliasSymbol && this.nameOf(argument.aliasSymbol)) ?? (argument.symbol && this.nameOf(argument.symbol)) ?? this.checker.typeToString(argument))
|
|
215
277
|
.filter((name) => IDENTIFIER.test(name));
|
|
216
|
-
return
|
|
278
|
+
return {
|
|
279
|
+
base: `${base}${argumentNames.map((name) => name[0].toUpperCase() + name.slice(1)).join("")}`,
|
|
280
|
+
origin: [instantiated.symbol, ...instantiated.typeArguments.map((argument) => argument.aliasSymbol ?? argument.symbol)].map((symbol) => this.originOf(symbol)).join("\n"),
|
|
281
|
+
};
|
|
217
282
|
}
|
|
218
283
|
printStructure(type, path) {
|
|
219
284
|
const { TypeFlags } = this.ts;
|
|
@@ -388,13 +453,6 @@ export class ContractTypePrinter {
|
|
|
388
453
|
// never the tail of an escaped backslash.
|
|
389
454
|
return `'${doubleQuoted.slice(1, -1).replace(/\\"/g, "\"").replace(/'/g, "\\'")}'`;
|
|
390
455
|
}
|
|
391
|
-
takeName(base) {
|
|
392
|
-
let name = base;
|
|
393
|
-
for (let suffix = 2; this.takenNames.has(name); suffix++)
|
|
394
|
-
name = `${base}${suffix}`;
|
|
395
|
-
this.takenNames.add(name);
|
|
396
|
-
return name;
|
|
397
|
-
}
|
|
398
456
|
fail(path, reason) {
|
|
399
457
|
this.failures.push(`${path}: ${reason}`);
|
|
400
458
|
return atom("unknown");
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { LambderApiOptionEntries } from "../shared/wire/LambderApiOptionEntries.js";
|
|
2
|
+
import { type LambderModuleLocation } from "./moduleLocation.js";
|
|
3
|
+
/** What the generators read the options from: a Lambder instance, or anything else that reports them the same way. */
|
|
4
|
+
export type LambderApiOptionsSource = {
|
|
5
|
+
apiOptionEntries(): LambderApiOptionEntries;
|
|
6
|
+
};
|
|
7
|
+
/** Which names of one table moved: entries that changed, entries the file did not have, entries the file had and the instance no longer reports. */
|
|
8
|
+
export type LambderNameChanges = {
|
|
9
|
+
changed: string[];
|
|
10
|
+
added: string[];
|
|
11
|
+
removed: string[];
|
|
12
|
+
};
|
|
13
|
+
/** Imports the module, finds the instance under its export, and answers what it reports. `generator` names the caller in the errors. */
|
|
14
|
+
export declare const loadApiOptionEntries: (location: {
|
|
15
|
+
module: LambderModuleLocation;
|
|
16
|
+
exportName?: string;
|
|
17
|
+
}, generator: string) => Promise<LambderApiOptionEntries>;
|
|
18
|
+
/** One table of a generated file, read back as data, or null for a file that does not hold it as written. */
|
|
19
|
+
export declare const readGeneratedTable: (contents: string, exportName: string) => Record<string, unknown> | null;
|
|
20
|
+
/** How one table's entries differ from the ones a file held, by name, compared as canonical JSON so key order is not a change. */
|
|
21
|
+
export declare const nameChangesOf: (current: Record<string, unknown>, previous: Record<string, unknown>) => LambderNameChanges;
|
|
22
|
+
/** The opening comment of a generated file, one `//` line per line of the header. */
|
|
23
|
+
export declare const headerLinesOf: (header: string) => string[];
|
|
24
|
+
/**
|
|
25
|
+
* One table as a statement: its doc comment, the `// prettier-ignore` that
|
|
26
|
+
* keeps a formatter off the JSON a check reads back, and the table itself,
|
|
27
|
+
* `as const` with whatever `satisfies` clause the caller gives.
|
|
28
|
+
*/
|
|
29
|
+
export declare const tableStatementLines: (table: {
|
|
30
|
+
name: string;
|
|
31
|
+
doc: string;
|
|
32
|
+
value: unknown;
|
|
33
|
+
satisfies?: string;
|
|
34
|
+
semicolon: string;
|
|
35
|
+
}) => string[];
|
|
36
|
+
/**
|
|
37
|
+
* The end of a generator's run. With `check`, it answers whether the file
|
|
38
|
+
* holds what the instance reports and writes nothing. Otherwise it writes
|
|
39
|
+
* the file unless it already holds these tables, however it is formatted, so
|
|
40
|
+
* a watcher or an incremental build sees no change where there is none (a
|
|
41
|
+
* change of header or semicolons shows the next time a table changes).
|
|
42
|
+
* Either way the lines say what moved.
|
|
43
|
+
*/
|
|
44
|
+
export declare const settleGeneratedFile: (run: {
|
|
45
|
+
/** The file as the caller named it, for the lines. */
|
|
46
|
+
name: string;
|
|
47
|
+
/** The absolute path. */
|
|
48
|
+
file: string;
|
|
49
|
+
check: boolean | undefined;
|
|
50
|
+
/** The file's text before this run, or null when there was none. */
|
|
51
|
+
previousText: string | null;
|
|
52
|
+
/** Whether that text held the tables as written; false for a file rewritten by hand. */
|
|
53
|
+
readBack: boolean;
|
|
54
|
+
/** Each table the file holds, under its exported name: how many entries it has now, and which of them moved. */
|
|
55
|
+
tables: readonly {
|
|
56
|
+
name: string;
|
|
57
|
+
count: number;
|
|
58
|
+
changes: LambderNameChanges;
|
|
59
|
+
}[];
|
|
60
|
+
/** What the tables hold, for the lines: "6 APIs, 4 policies, 5 guards". */
|
|
61
|
+
held: string;
|
|
62
|
+
/** What the file holds when it is as written, for the line when it is not: "the three tables". */
|
|
63
|
+
tablesNoun: string;
|
|
64
|
+
/** The line when nothing moved: "no options changed". */
|
|
65
|
+
unmovedLine: string;
|
|
66
|
+
/** The file's contents, rendered only when it is written. */
|
|
67
|
+
render: () => string;
|
|
68
|
+
}) => {
|
|
69
|
+
ok: boolean;
|
|
70
|
+
written: boolean;
|
|
71
|
+
lines: string[];
|
|
72
|
+
};
|