@yolk-sdk/emulators 0.1.0-canary.96 → 0.1.0-canary.97
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 +72 -42
- package/dist/dropbox/state.mjs +4 -4
- package/dist/dropbox/state.mjs.map +1 -1
- package/dist/github/state.d.mts.map +1 -1
- package/dist/github/state.mjs +8 -8
- package/dist/github/state.mjs.map +1 -1
- package/dist/google/state.mjs +4 -4
- package/dist/google/state.mjs.map +1 -1
- package/dist/linkedin-search/state.d.mts.map +1 -1
- package/dist/linkedin-search/state.mjs +3 -3
- package/dist/linkedin-search/state.mjs.map +1 -1
- package/dist/mcp/afloat-recordings.d.mts +8 -0
- package/dist/mcp/afloat-recordings.d.mts.map +1 -0
- package/dist/mcp/afloat-recordings.mjs +918 -0
- package/dist/mcp/afloat-recordings.mjs.map +1 -0
- package/dist/mcp/api.d.mts +3 -2
- package/dist/mcp/api.d.mts.map +1 -1
- package/dist/mcp/api.mjs +77 -25
- package/dist/mcp/api.mjs.map +1 -1
- package/dist/mcp/state.d.mts +17 -2
- package/dist/mcp/state.d.mts.map +1 -1
- package/dist/mcp/state.mjs +17 -2
- package/dist/mcp/state.mjs.map +1 -1
- package/dist/mcp.d.mts +3 -2
- package/dist/mcp.d.mts.map +1 -1
- package/dist/mcp.mjs +81 -58
- package/dist/mcp.mjs.map +1 -1
- package/dist/microsoft/state.d.mts.map +1 -1
- package/dist/microsoft/state.mjs +1 -1
- package/dist/microsoft/state.mjs.map +1 -1
- package/dist/notion/state.d.mts.map +1 -1
- package/dist/notion/state.mjs +1 -1
- package/dist/notion/state.mjs.map +1 -1
- package/dist/r2-guard.d.mts.map +1 -1
- package/dist/r2-guard.mjs +1 -0
- package/dist/r2-guard.mjs.map +1 -1
- package/dist/router.d.mts +1 -1
- package/dist/router.mjs +1 -1
- package/dist/router.mjs.map +1 -1
- package/dist/stateful-emulator.d.mts +13 -0
- package/dist/stateful-emulator.d.mts.map +1 -1
- package/dist/stateful-emulator.mjs +25 -5
- package/dist/stateful-emulator.mjs.map +1 -1
- package/dist/telegram/state.mjs +1 -1
- package/dist/telegram/state.mjs.map +1 -1
- package/package.json +7 -7
- package/src/dropbox/state.ts +4 -4
- package/src/github/state.ts +8 -8
- package/src/google/state.ts +4 -4
- package/src/linkedin-search/state.ts +3 -3
- package/src/mcp/afloat-recordings.ts +1040 -0
- package/src/mcp/api.ts +135 -25
- package/src/mcp/state.ts +20 -1
- package/src/mcp.ts +101 -56
- package/src/microsoft/state.ts +1 -1
- package/src/notion/state.ts +1 -1
- package/src/r2-guard.ts +1 -0
- package/src/router.ts +1 -1
- package/src/stateful-emulator.ts +54 -5
- package/src/telegram/state.ts +1 -1
package/README.md
CHANGED
|
@@ -21,15 +21,15 @@ conformance cases use. The Dropbox, Notion, Todoist, Telegram, GitHub, Google, L
|
|
|
21
21
|
MCP emulators are stateful, fixture-only stand-ins for the Dropbox RPC and upload routes, the Notion
|
|
22
22
|
`/v1` routes, the Todoist API v1 routes, the Telegram Bot API routes, the GitHub REST routes, the
|
|
23
23
|
Gmail, Calendar, and Drive routes, the Exa and Enrich Layer routes, and the two synthetic MCP
|
|
24
|
-
servers their conformance cases use: they answer only what the fixtures
|
|
25
|
-
else with a 400 not-emulated.
|
|
24
|
+
servers and the Afloat MCP endpoint their conformance cases use: they answer only what the fixtures
|
|
25
|
+
show and refuse everything else with a 400 not-emulated.
|
|
26
26
|
|
|
27
27
|
Canary APIs are unstable. Keep all `@yolk-sdk/*` packages on the same version.
|
|
28
28
|
|
|
29
29
|
## Install
|
|
30
30
|
|
|
31
31
|
```bash
|
|
32
|
-
pnpm add -D @yolk-sdk/emulators@canary effect@4.0.0
|
|
32
|
+
pnpm add -D @yolk-sdk/emulators@canary effect@4.0.0
|
|
33
33
|
```
|
|
34
34
|
|
|
35
35
|
## Subpaths
|
|
@@ -57,7 +57,7 @@ There is no root export. Import an explicit subpath:
|
|
|
57
57
|
| `@yolk-sdk/emulators/github` | `makeGithubEmulator`, `githubEmulatorRoutes`, seed and fault schemas (Node only) |
|
|
58
58
|
| `@yolk-sdk/emulators/google` | `makeGoogleEmulator`, `googleEmulatorRoutes`, seed and fault schemas (Gmail, Calendar, Drive; Node only) |
|
|
59
59
|
| `@yolk-sdk/emulators/linkedin-search` | `makeLinkedInSearchEmulator`, `linkedInSearchEmulatorRoutes`, seed and fault schemas (Exa, Enrich Layer; Node only) |
|
|
60
|
-
| `@yolk-sdk/emulators/mcp` | `makeMcpEmulator`, `mcpEmulatorRoutes`, seed and fault schemas (synthetic MCP servers; Node only)
|
|
60
|
+
| `@yolk-sdk/emulators/mcp` | `makeMcpEmulator`, `mcpEmulatorRoutes`, seed and fault schemas (synthetic and Afloat MCP servers; Node only) |
|
|
61
61
|
|
|
62
62
|
## Routing
|
|
63
63
|
|
|
@@ -72,7 +72,7 @@ Pick a transport by swapping `HttpClient` layers. Code under test keeps calling
|
|
|
72
72
|
|
|
73
73
|
```ts
|
|
74
74
|
import { Effect, Layer } from 'effect'
|
|
75
|
-
import { FetchHttpClient } from 'effect/
|
|
75
|
+
import { FetchHttpClient } from 'effect/http'
|
|
76
76
|
import { makeGatewayEmulator } from '@yolk-sdk/emulators/gateway'
|
|
77
77
|
import { serveFetchHandler } from '@yolk-sdk/emulators/node'
|
|
78
78
|
import { EmulatedHttpClient, EmulatorRoute, InProcessHttpClient } from '@yolk-sdk/emulators/router'
|
|
@@ -1932,11 +1932,20 @@ exactly one LinkedIn search case, only to prove that case catches it.
|
|
|
1932
1932
|
`{ fetch, fetchOn, ledger, faults, reset, seed, snapshot, coverage, close }`. Each call has its own
|
|
1933
1933
|
state and session counter; `await close()` when done. It emulates the two synthetic servers of the
|
|
1934
1934
|
`@yolk-sdk/mcp/conformance` fixtures, so `@yolk-sdk/mcp/client` and the MCP conformance cases run
|
|
1935
|
-
unchanged against it: profile `synthetic-modern` on `https://mcp.example.test/modern/mcp`
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
`
|
|
1935
|
+
unchanged against it: profile `synthetic-modern` on `https://mcp.example.test/modern/mcp` (stateless
|
|
1936
|
+
`2026-07-28`, JSON answers) and profile `synthetic-legacy` on `https://mcp.example.test/legacy/mcp`
|
|
1937
|
+
(an `initialize` handshake, sessions, SSE answers). Both answer only on that origin
|
|
1938
|
+
(`mcpEmulatorOrigin`); behind a loopback rewrite, serve `fetchOn(mcpEmulatorOrigin)`. A third
|
|
1939
|
+
profile, `afloat`, answers on the provider endpoint `https://useafloat.com/mcp`
|
|
1940
|
+
(`mcpEmulatorAfloatOrigin` plus `mcpEmulatorAfloatPath`; stateless `2026-07-28`, JSON answers, no
|
|
1941
|
+
session, one listing page) from the eight `@yolk-sdk/connectors/afloat/conformance` fixtures
|
|
1942
|
+
(`mcpEmulatorAfloatFixtures`; derived from the provider's source, not recorded live, unverified):
|
|
1943
|
+
the published nine-tool subset, the `list-invoices` read call, its `VALIDATION_ERROR` tool error,
|
|
1944
|
+
the absent tool's JSON-RPC error (HTTP 200), and the 401. Answering the two derived `tools/call`
|
|
1945
|
+
fixtures, rather than only recorded calls, is in scope by the owner's decision; they stay unverified
|
|
1946
|
+
until a live call is recorded. Route `https://useafloat.com` to it (or serve
|
|
1947
|
+
`fetchOn(mcpEmulatorAfloatOrigin)`), and send an Afloat-shaped synthetic key (see the credential
|
|
1948
|
+
rule below):
|
|
1940
1949
|
|
|
1941
1950
|
```ts
|
|
1942
1951
|
import { makeMcpEmulator, mcpEmulatorOrigin } from '@yolk-sdk/emulators/mcp'
|
|
@@ -1967,12 +1976,16 @@ each profile, plus the legacy `GET` row; every row is a read, and every request
|
|
|
1967
1976
|
| `/legacy/mcp#tools/call` | The three recorded SSE call answers, on a ready session |
|
|
1968
1977
|
| `GET /legacy/mcp` | 405, no body, on a ready session |
|
|
1969
1978
|
| `/{modern,legacy}/mcp#server/discover` | With the reserved invalid credential: the recorded 401, byte for byte |
|
|
1979
|
+
| `useafloat.com/mcp#server/discover` | The Afloat discover result; with `afloat_yolkconformanceinvalid0000`, the 401 |
|
|
1980
|
+
| `useafloat.com/mcp#tools/list` | The Afloat listing (the published tool subset, one page) |
|
|
1981
|
+
| `useafloat.com/mcp#tools/call` | The `list-invoices` result, its tool error, or the absent tool's error |
|
|
1970
1982
|
|
|
1971
1983
|
Every answer comes from a fixture, byte for byte (the copies live in `mcpEmulatorFixtures`), with
|
|
1972
1984
|
exactly three request-derived or minted substitutions:
|
|
1973
1985
|
|
|
1974
|
-
- **The request id**, at exactly the recorded place: the top-level `id` of a JSON answer
|
|
1975
|
-
|
|
1986
|
+
- **The request id**, at exactly the recorded place: the top-level `id` of a JSON answer (the Afloat
|
|
1987
|
+
answers carry it last, in the server SDK's member order), or the `id` of the SSE response event's
|
|
1988
|
+
payload. Notification events and SSE `id:` lines stay byte for
|
|
1976
1989
|
byte, and an answer that does not carry the recorded request id (the legacy era probe's
|
|
1977
1990
|
`id: null` error, the 401) is unchanged.
|
|
1978
1991
|
- **The session id.** `initialize` mints `yolk-emu-session-<n>`, `n` from a counter that never
|
|
@@ -2010,35 +2023,51 @@ which they compare only with the digest of the public reserved invalid credentia
|
|
|
2010
2023
|
`yolk-conformance-invalid-credential-0000` (`mcpEmulatorReservedInvalidCredential`, itself a
|
|
2011
2024
|
recognisable bearer) to answer the recorded 401 on the era probe.
|
|
2012
2025
|
|
|
2026
|
+
**The Afloat credential rule.** An Afloat key is `afloat_` (`mcpEmulatorAfloatKeyPrefix`) and a
|
|
2027
|
+
remainder, and `a` is a hex digit, so the key fails the shared first-character rule. On
|
|
2028
|
+
`https://useafloat.com` the emulator (the wrapper's opt-in `bearerPrefixes`, as the Telegram
|
|
2029
|
+
emulator guards a bot token's secret part) recognises a bearer only as `afloat_<remainder>` whose
|
|
2030
|
+
remainder is a recognisable bearer, and guards the remainder: it is what every repeat check, the
|
|
2031
|
+
output guard, and the digest take, and it is never stored, ledgered, or echoed. Use a synthetic key
|
|
2032
|
+
such as `afloat_synthetic-key-0001`; a real-format key whose remainder starts with a hex digit is
|
|
2033
|
+
not emulated, nor is a bearer without `afloat_` there or an `afloat_` key on the synthetic origin.
|
|
2034
|
+
The public reserved invalid key `afloat_yolkconformanceinvalid0000`
|
|
2035
|
+
(`mcpEmulatorAfloatReservedInvalidCredential`) answers the recorded 401 (`WWW-Authenticate: Bearer`)
|
|
2036
|
+
on the Afloat era probe.
|
|
2037
|
+
|
|
2013
2038
|
- **Request-shape latitude (`/mcp`, the only accepted deviations).** Any bearer value in the RFC
|
|
2014
2039
|
6750 `b64token` syntax (`[A-Za-z0-9\-._~+/]+=*`) of at least 8 characters, starting with a
|
|
2015
2040
|
character in `[G-Zg-z\-._~+/]` other than `n`, `r`, `t`, `u`, with at least one outside
|
|
2016
|
-
`[0-9.eE+-]
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
`
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
`
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2041
|
+
`[0-9.eE+-]` (on the Afloat profile, `afloat_` followed by such a value: an Afloat key starts with
|
|
2042
|
+
the hex digit `a`, so its remainder after `afloat_` is the value checked, guarded, and digested),
|
|
2043
|
+
that occurs nowhere else in the request (any header name or value included) and in no answer or
|
|
2044
|
+
value the request would store (never stored or ledgered; only its digest is compared, with the
|
|
2045
|
+
digest of the public reserved invalid credential `yolk-conformance-invalid-credential-0000`, or on
|
|
2046
|
+
the Afloat profile of the remainder of `afloat_yolkconformanceinvalid0000`, each answering the
|
|
2047
|
+
recorded 401 on its profile's era probe); extra request headers, except `mcp-*` headers other than
|
|
2048
|
+
`mcp-method`, `mcp-name`, `mcp-protocol-version`, and `mcp-session-id`; a recorded header value
|
|
2049
|
+
sent as several headers that the HTTP layer joins into the recorded value; JSON key order; any
|
|
2050
|
+
JSON-RPC request id that is an integer from 0 to 2^53 - 1 or 1 to 64 printable ASCII characters
|
|
2051
|
+
where the recording has an id; any non-empty `name` and `version` (and no other key) in the
|
|
2052
|
+
`_meta` client info (`io.modelcontextprotocol/clientInfo`) of a modern request; a session id this
|
|
2053
|
+
emulator minted since the last reset or seed where the recording sends `mcp-session-id`
|
|
2054
|
+
(initializing for `notifications/initialized`, ready otherwise); and, with the seed's `two-pages`
|
|
2055
|
+
listing, the cursor this emulator issued in the current generation on the second page.
|
|
2056
|
+
`Authorization` must be exactly `Bearer <token>` (that spelling, one space). Everything else
|
|
2057
|
+
(another origin or path, any query, other HTTP methods such as `DELETE` or a `GET` on the modern
|
|
2058
|
+
or Afloat profile, a bearer without `afloat_` on the Afloat profile or with it on the synthetic
|
|
2059
|
+
profiles, an Afloat key whose remainder fails the rule above (such as one starting with a hex
|
|
2060
|
+
digit), JSON-RPC methods no fixture of the profile records such as `ping`, `resources/*`, or
|
|
2061
|
+
`prompts/*`, batches and client-sent responses, other members, a JSON body repeating a key
|
|
2062
|
+
(compared after unescaping), a `null`, negative, or fractional id, other params (other tools,
|
|
2063
|
+
arguments, protocol versions, or capabilities, extra client-info keys, a cursor on the Afloat
|
|
2064
|
+
listing, and a legacy `initialize` client info other than the recorded one), the MCP headers
|
|
2065
|
+
`accept`, `content-type`, `mcp-method`, `mcp-protocol-version`, `mcp-name`, and `last-event-id`
|
|
2066
|
+
other than the recorded values or present where none is recorded, any other `mcp-*` header (such
|
|
2067
|
+
as `mcp-param-*`), `mcp-session-id` missing where recorded or present where not, an unknown
|
|
2068
|
+
session or one in the wrong phase, a cursor not issued in the current generation, a reserved
|
|
2069
|
+
invalid credential on anything but its profile's era probe, a bearer repeated anywhere in the
|
|
2070
|
+
request, and a bearer an answer or a minted session id or cursor would repeat) is not emulated.
|
|
2042
2071
|
|
|
2043
2072
|
Not emulated (a constant-text 400 that writes nothing and uses up no fault): `DELETE` (the client
|
|
2044
2073
|
never sends it), `ping`, `resources/*`, `prompts/*`, `logging/*`, `completion/*`, `tasks/*`,
|
|
@@ -2068,8 +2097,9 @@ plane behave as in the Dropbox emulator; `/_emulate/state` also reports `nextSes
|
|
|
2068
2097
|
**Drill knobs (tests only).** `drills: { discoverCarriesErrorResponse, discoverWithoutResultType,
|
|
2069
2098
|
sessionIdNotVisibleAscii, discoverAnsweredTwice, writeToolMarkedReadOnly, readCallAnswersToolError,
|
|
2070
2099
|
invalidCallAnswersRpcError, absentCallAnswersResult, unauthorizedWithoutChallenge }` (booleans) each
|
|
2071
|
-
make the emulator disagree with exactly one MCP conformance case,
|
|
2072
|
-
|
|
2100
|
+
make the emulator disagree with exactly one MCP conformance case, on every profile whose answers
|
|
2101
|
+
they change (the Afloat profile included, except the legacy-only session knob), only to prove that
|
|
2102
|
+
case catches it.
|
|
2073
2103
|
|
|
2074
2104
|
## Evidence
|
|
2075
2105
|
|
|
@@ -2105,8 +2135,8 @@ implementation, verifies it.
|
|
|
2105
2135
|
All Fortnox, Microsoft, Dropbox, Notion, Todoist, Telegram, GitHub, Google, LinkedIn search, and
|
|
2106
2136
|
MCP routes are currently `unverified` (no live recording yet), including four Fortnox, eleven
|
|
2107
2137
|
Microsoft, five Dropbox, two Notion, five Todoist, one Telegram, six GitHub, and fifteen Google
|
|
2108
|
-
connector write routes (the three LinkedIn search routes and the
|
|
2109
|
-
them needs an entry). Until an
|
|
2138
|
+
connector write routes (the three LinkedIn search routes and the twelve MCP rows are reads, so none
|
|
2139
|
+
of them needs an entry). Until an
|
|
2110
2140
|
owner-approved live run verifies them, the repository lists them in a visible, time-bounded
|
|
2111
2141
|
allowlist (`scripts/emulator-evidence-pending.json`, which holds each entry's expiry date): the
|
|
2112
2142
|
check reports them as PENDING warnings until that date and fails again after it.
|
package/dist/dropbox/state.mjs
CHANGED
|
@@ -18,11 +18,11 @@ import * as Schema from "effect/Schema";
|
|
|
18
18
|
* @experimental
|
|
19
19
|
*/
|
|
20
20
|
/** A Dropbox revision: lower-case hex, at least nine digits (what the connector accepts). */
|
|
21
|
-
const Rev = Schema.String.check(Schema.isPattern(/^[0-9a-f]{9,}$/));
|
|
21
|
+
const Rev = Schema.String.check(Schema.isPattern(/^[0-9a-f]{9,}$/u));
|
|
22
22
|
/** A Dropbox file or folder id (`id:` form). */
|
|
23
|
-
const EntryId = Schema.String.check(Schema.isPattern(/^id:\S+$/));
|
|
23
|
+
const EntryId = Schema.String.check(Schema.isPattern(/^id:\S+$/u));
|
|
24
24
|
/** A Dropbox timestamp as the fixtures write it (`2026-09-20T10:00:00Z`). */
|
|
25
|
-
const Timestamp = Schema.String.check(Schema.isPattern(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$/));
|
|
25
|
+
const Timestamp = Schema.String.check(Schema.isPattern(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$/u));
|
|
26
26
|
/** One path component: non-empty, no `/`, not `.` or `..`. */
|
|
27
27
|
const EntryName = Schema.String.check(Schema.makeFilter((name) => name.length > 0 && !name.includes("/") && name !== "." && name !== ".." ? true : "an entry name is one non-empty path component"));
|
|
28
28
|
/** File fields (absent on folders). */
|
|
@@ -71,7 +71,7 @@ const DropboxEmulatorStateSchema = Schema.Struct({
|
|
|
71
71
|
counters: Counters
|
|
72
72
|
});
|
|
73
73
|
/** An absolute path in display casing: `/`-separated non-empty components, no trailing `/`. */
|
|
74
|
-
const SeedPath = Schema.String.check(Schema.isPattern(/^(?:\/[^/]+)+$/));
|
|
74
|
+
const SeedPath = Schema.String.check(Schema.isPattern(/^(?:\/[^/]+)+$/u));
|
|
75
75
|
/**
|
|
76
76
|
* A seeded entry, addressed by its display path (its parent folder must be seeded too). File
|
|
77
77
|
* fields default to synthetic values; a folder takes none.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"state.mjs","names":[],"sources":["../../src/dropbox/state.ts"],"sourcesContent":["/**\n * Dropbox emulator state: the typed entries, deleted-entry records, the seed input, the default\n * seed, and the profiles (internal; re-exported by `src/dropbox.ts`).\n *\n * Entry shapes and the default entries follow the synthetic Dropbox conformance fixtures, copied\n * as data (the same paths, ids, revs, sizes, content hashes, and timestamps as the fixtures and\n * `dropboxConformanceFixtureSeeds`), never imported from SDK code. The parent folders the seeded\n * paths need (`/Conformance`, `/Conformance/Paging`, `/Conformance/Search`, `/Conformance/Work`)\n * are `implied`: no fixture shows their metadata, so they hold paths together but any answer that\n * would render one is not emulated.\n *\n * Minted values never repeat seeded ones: the id and rev counters start above the highest seeded\n * id (`id:SyntheticEntryNNNNNNNN`) and rev (`a1b2c3d4e5f6NNNN`) in the minted form.\n *\n * @experimental\n */\nimport { Predicate, Result } from 'effect'\nimport * as Schema from 'effect/Schema'\n\n/** A Dropbox revision: lower-case hex, at least nine digits (what the connector accepts). */\nconst Rev = Schema.String.check(Schema.isPattern(/^[0-9a-f]{9,}$/))\n\n/** A Dropbox file or folder id (`id:` form). */\nconst EntryId = Schema.String.check(Schema.isPattern(/^id:\\S+$/))\n\n/** A Dropbox timestamp as the fixtures write it (`2026-09-20T10:00:00Z`). */\nconst Timestamp = Schema.String.check(Schema.isPattern(/^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$/))\n\n/** One path component: non-empty, no `/`, not `.` or `..`. */\nconst EntryName = Schema.String.check(\n Schema.makeFilter(name =>\n name.length > 0 && !name.includes('/') && name !== '.' && name !== '..'\n ? true\n : 'an entry name is one non-empty path component'\n )\n)\n\n/** File fields (absent on folders). */\nexport const DropboxEmulatorFile = Schema.Struct({\n rev: Rev,\n size: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)),\n contentHash: Schema.String,\n clientModified: Timestamp,\n serverModified: Timestamp\n})\n\nexport type DropboxEmulatorFile = typeof DropboxEmulatorFile.Type\n\n/** A stored file or folder. Its path is its parents' names and its own, in stored casing. */\nexport const DropboxEmulatorEntry = Schema.Struct({\n id: EntryId,\n /** Parent folder id; `null` in the root folder. */\n parentId: Schema.NullOr(EntryId),\n name: EntryName,\n /** `null` for a folder. */\n file: Schema.NullOr(DropboxEmulatorFile),\n /**\n * `true` for an entry no fixture shows, held only because a seeded path needs it: lookups pass\n * through it, but an answer that would render it is not emulated.\n */\n implied: Schema.Boolean\n})\n\nexport type DropboxEmulatorEntry = typeof DropboxEmulatorEntry.Type\n\n/**\n * What Dropbox keeps of a deleted entry (`include_deleted` lookups answer it): its name and paths\n * as they were. `delete_v2` records one only for an empty folder, the only delete whose\n * `include_deleted` answer a fixture records.\n */\nexport const DropboxEmulatorDeleted = Schema.Struct({\n name: EntryName,\n pathLower: Schema.String,\n pathDisplay: Schema.String\n})\n\nexport type DropboxEmulatorDeleted = typeof DropboxEmulatorDeleted.Type\n\nconst Counter = Schema.Int.check(Schema.isGreaterThanOrEqualTo(1))\n\nconst Counters = Schema.Struct({\n /** Next number in created entry ids (`id:SyntheticEntry00000001`). */\n nextIdNumber: Counter,\n /** Next number in minted revs (`a1b2c3d4e5f60101`). */\n nextRevNumber: Counter\n})\n\n/** The whole emulator state (JSON-compatible; what `snapshot()` returns). */\nexport const DropboxEmulatorStateSchema = Schema.Struct({\n entries: Schema.Array(DropboxEmulatorEntry),\n deleted: Schema.Array(DropboxEmulatorDeleted),\n counters: Counters\n})\n\n/**\n * The emulator state. The container is mutable (routes replace whole lists); every entity is\n * replaced, never edited in place.\n */\nexport type DropboxEmulatorState = {\n entries: ReadonlyArray<DropboxEmulatorEntry>\n deleted: ReadonlyArray<DropboxEmulatorDeleted>\n counters: typeof Counters.Type\n}\n\n/** An absolute path in display casing: `/`-separated non-empty components, no trailing `/`. */\nconst SeedPath = Schema.String.check(Schema.isPattern(/^(?:\\/[^/]+)+$/))\n\n/**\n * A seeded entry, addressed by its display path (its parent folder must be seeded too). File\n * fields default to synthetic values; a folder takes none.\n */\nexport const DropboxEmulatorEntrySeed = Schema.Struct({\n path: SeedPath,\n id: EntryId,\n kind: Schema.Literals(['file', 'folder']),\n rev: Schema.optionalKey(Rev),\n size: Schema.optionalKey(Schema.Int.check(Schema.isGreaterThanOrEqualTo(0))),\n contentHash: Schema.optionalKey(Schema.String),\n clientModified: Schema.optionalKey(Timestamp),\n serverModified: Schema.optionalKey(Timestamp),\n /** An entry no fixture shows (default `false`); see `DropboxEmulatorEntry.implied`. */\n implied: Schema.optionalKey(Schema.Boolean)\n})\n\nexport type DropboxEmulatorEntrySeed = typeof DropboxEmulatorEntrySeed.Type\n\n/** Account-variance profiles for the default seed. */\nexport const DropboxEmulatorProfile = Schema.Literals(['default', 'empty'])\n\nexport type DropboxEmulatorProfile = typeof DropboxEmulatorProfile.Type\n\n/**\n * A typed seed. Start from `profile` (default `'default'`, the fixture entries); `entries` and\n * `deleted`, when given, replace that part of the profile.\n */\nexport const DropboxEmulatorSeed = Schema.Struct({\n profile: Schema.optionalKey(DropboxEmulatorProfile),\n entries: Schema.optionalKey(Schema.Array(DropboxEmulatorEntrySeed)),\n deleted: Schema.optionalKey(Schema.Array(DropboxEmulatorDeleted))\n})\n\nexport type DropboxEmulatorSeed = typeof DropboxEmulatorSeed.Type\n\nconst strict = { onExcessProperty: 'error' } as const\n\nconst decodeSeedInput = Schema.decodeUnknownResult(DropboxEmulatorSeed, strict)\n\nconst decodeStateInput = Schema.decodeUnknownResult(DropboxEmulatorStateSchema, strict)\n\nconst issueMessage = (issue: Schema.SchemaError['issue']): string =>\n new Schema.SchemaError(issue).message\n\n/** A synthetic 64-digit content hash ending in `suffix`, as the fixtures write them. */\nexport const syntheticContentHash = (suffix: string): string => suffix.padStart(64, '0')\n\n// Default entries. Fixture-derived: every file, the `paging-two` folder, and every path, id, rev,\n// size, content hash, and timestamp of them. Implied (never rendered): the `/Conformance`,\n// `/Conformance/Paging`, `/Conformance/Search`, and `/Conformance/Work` folders.\n\nconst file = (\n path: string,\n id: string,\n revSuffix: string,\n size: number,\n hashSuffix: string,\n clientModified: string,\n serverModified: string = clientModified\n): DropboxEmulatorEntrySeed => ({\n path,\n id,\n kind: 'file',\n rev: `a1b2c3d4e5f6${revSuffix}`,\n size,\n contentHash: syntheticContentHash(hashSuffix),\n clientModified,\n serverModified\n})\n\nconst folder = (path: string, id: string): DropboxEmulatorEntrySeed => ({\n path,\n id,\n kind: 'folder'\n})\n\n/** A parent folder no fixture shows. */\nconst impliedFolder = (path: string, id: string): DropboxEmulatorEntrySeed => ({\n ...folder(path, id),\n implied: true\n})\n\nconst defaultEntries: ReadonlyArray<DropboxEmulatorEntrySeed> = [\n impliedFolder('/Conformance', 'id:SyntheticConformanceFolder'),\n impliedFolder('/Conformance/Paging', 'id:SyntheticPagingFolder0000'),\n file(\n '/Conformance/Paging/paging-one.txt',\n 'id:SyntheticPagingFile0001',\n '0001',\n 12,\n '1',\n '2026-09-20T10:00:00Z'\n ),\n folder('/Conformance/Paging/paging-two', 'id:SyntheticPagingFolder0002'),\n file(\n '/Conformance/Paging/paging-three.txt',\n 'id:SyntheticPagingFile0003',\n '0003',\n 14,\n '3',\n '2026-09-20T10:05:00Z'\n ),\n file(\n '/Conformance/Mixed Case Notes.txt',\n 'id:SyntheticMixedCaseFile01',\n '0010',\n 20,\n '10',\n '2026-09-20T11:00:00Z'\n ),\n impliedFolder('/Conformance/Search', 'id:SyntheticSearchFolder0000'),\n file(\n '/Conformance/Search/yolk-search-probe-1.txt',\n 'id:SyntheticSearchFile0001',\n '0021',\n 16,\n '21',\n '2026-09-20T12:00:00Z'\n ),\n file(\n '/Conformance/Search/yolk-search-probe-2.txt',\n 'id:SyntheticSearchFile0002',\n '0022',\n 16,\n '22',\n '2026-09-20T12:00:00Z'\n ),\n impliedFolder('/Conformance/Work', 'id:SyntheticWorkFolder00000'),\n file(\n '/Conformance/copy-source.txt',\n 'id:SyntheticCopySource001',\n '0030',\n 42,\n '42',\n '2026-09-20T13:00:00Z',\n '2026-09-29T13:00:00Z'\n )\n]\n\nconst profileEntries = (\n profile: DropboxEmulatorProfile\n): ReadonlyArray<DropboxEmulatorEntrySeed> => (profile === 'default' ? defaultEntries : [])\n\nconst defaultTimestamp = '2026-09-20T10:00:00Z'\n\n/** The form of minted ids (`id:SyntheticEntry00000001`) and revs (`a1b2c3d4e5f60101`). */\nconst mintedIdPattern = /^id:SyntheticEntry(\\d+)$/\n\nconst mintedRevPattern = /^a1b2c3d4e5f6(\\d+)$/\n\n/** The highest number among `values` in a minted form (0 when none is). */\nconst highestMinted = (values: ReadonlyArray<string>, pattern: RegExp): number =>\n Math.max(0, ...values.map(value => Number(pattern.exec(value)?.[1] ?? 0)))\n\nconst duplicate = (values: ReadonlyArray<string>): string | undefined =>\n values.find((value, index) => values.indexOf(value) !== index)\n\nconst parentPath = (path: string): string => path.slice(0, path.lastIndexOf('/'))\n\nconst lastName = (path: string): string => path.slice(path.lastIndexOf('/') + 1)\n\n/** Build the emulator state for a decoded seed; a string is an integrity problem. */\nconst stateFromSeed = (seed: DropboxEmulatorSeed): DropboxEmulatorState | string => {\n const seeded = seed.entries ?? profileEntries(seed.profile ?? 'default')\n\n const duplicates: ReadonlyArray<readonly [string, string | undefined]> = [\n ['entry id', duplicate(seeded.map(entry => entry.id))],\n ['entry path', duplicate(seeded.map(entry => entry.path.toLowerCase()))]\n ]\n\n for (const [label, value] of duplicates) {\n if (value !== undefined) return `duplicate ${label} ${value}`\n }\n\n const byPath = new Map(seeded.map(entry => [entry.path.toLowerCase(), entry]))\n const entries: Array<DropboxEmulatorEntry> = []\n\n for (const entry of seeded) {\n const name = lastName(entry.path)\n\n if (name === '.' || name === '..') return `entry ${entry.id} has an invalid path`\n\n const parent = parentPath(entry.path)\n const parentEntry = parent === '' ? undefined : byPath.get(parent.toLowerCase())\n\n if (parent !== '' && (parentEntry === undefined || parentEntry.kind !== 'folder')) {\n return `entry ${entry.path} needs a seeded parent folder ${parent}`\n }\n\n const hasFileFields = [\n entry.rev,\n entry.size,\n entry.contentHash,\n entry.clientModified,\n entry.serverModified\n ].some(Predicate.isNotUndefined)\n\n if (entry.kind === 'folder' && hasFileFields) {\n return `folder ${entry.path} cannot take file fields`\n }\n\n const clientModified = entry.clientModified ?? defaultTimestamp\n\n entries.push({\n id: entry.id,\n parentId: parentEntry?.id ?? null,\n name,\n implied: entry.implied ?? false,\n file:\n entry.kind === 'folder'\n ? null\n : {\n rev: entry.rev ?? 'a1b2c3d4e5f60000',\n size: entry.size ?? 0,\n contentHash: entry.contentHash ?? syntheticContentHash('0'),\n clientModified,\n serverModified: entry.serverModified ?? clientModified\n }\n })\n }\n\n return {\n entries,\n deleted: seed.deleted ?? [],\n counters: {\n nextIdNumber:\n highestMinted(\n entries.map(entry => entry.id),\n mintedIdPattern\n ) + 1,\n nextRevNumber: Math.max(\n 101,\n highestMinted(\n entries.flatMap(entry => entry.file?.rev ?? []),\n mintedRevPattern\n ) + 1\n )\n }\n }\n}\n\n/** Decode and build a seed; a string is the reason it is invalid. */\nexport const buildSeedState = (input: unknown): DropboxEmulatorState | string => {\n const decoded = decodeSeedInput(input)\n\n return Result.isFailure(decoded)\n ? issueMessage(decoded.failure.issue)\n : stateFromSeed(decoded.success)\n}\n\n/** Decode a full state (a restored snapshot); a string is the reason it is invalid. */\nexport const decodeState = (input: unknown): DropboxEmulatorState | string => {\n const decoded = decodeStateInput(input)\n\n return Result.isFailure(decoded) ? issueMessage(decoded.failure.issue) : { ...decoded.success }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"state.mjs","names":[],"sources":["../../src/dropbox/state.ts"],"sourcesContent":["/**\n * Dropbox emulator state: the typed entries, deleted-entry records, the seed input, the default\n * seed, and the profiles (internal; re-exported by `src/dropbox.ts`).\n *\n * Entry shapes and the default entries follow the synthetic Dropbox conformance fixtures, copied\n * as data (the same paths, ids, revs, sizes, content hashes, and timestamps as the fixtures and\n * `dropboxConformanceFixtureSeeds`), never imported from SDK code. The parent folders the seeded\n * paths need (`/Conformance`, `/Conformance/Paging`, `/Conformance/Search`, `/Conformance/Work`)\n * are `implied`: no fixture shows their metadata, so they hold paths together but any answer that\n * would render one is not emulated.\n *\n * Minted values never repeat seeded ones: the id and rev counters start above the highest seeded\n * id (`id:SyntheticEntryNNNNNNNN`) and rev (`a1b2c3d4e5f6NNNN`) in the minted form.\n *\n * @experimental\n */\nimport { Predicate, Result } from 'effect'\nimport * as Schema from 'effect/Schema'\n\n/** A Dropbox revision: lower-case hex, at least nine digits (what the connector accepts). */\nconst Rev = Schema.String.check(Schema.isPattern(/^[0-9a-f]{9,}$/u))\n\n/** A Dropbox file or folder id (`id:` form). */\nconst EntryId = Schema.String.check(Schema.isPattern(/^id:\\S+$/u))\n\n/** A Dropbox timestamp as the fixtures write it (`2026-09-20T10:00:00Z`). */\nconst Timestamp = Schema.String.check(Schema.isPattern(/^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$/u))\n\n/** One path component: non-empty, no `/`, not `.` or `..`. */\nconst EntryName = Schema.String.check(\n Schema.makeFilter(name =>\n name.length > 0 && !name.includes('/') && name !== '.' && name !== '..'\n ? true\n : 'an entry name is one non-empty path component'\n )\n)\n\n/** File fields (absent on folders). */\nexport const DropboxEmulatorFile = Schema.Struct({\n rev: Rev,\n size: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)),\n contentHash: Schema.String,\n clientModified: Timestamp,\n serverModified: Timestamp\n})\n\nexport type DropboxEmulatorFile = typeof DropboxEmulatorFile.Type\n\n/** A stored file or folder. Its path is its parents' names and its own, in stored casing. */\nexport const DropboxEmulatorEntry = Schema.Struct({\n id: EntryId,\n /** Parent folder id; `null` in the root folder. */\n parentId: Schema.NullOr(EntryId),\n name: EntryName,\n /** `null` for a folder. */\n file: Schema.NullOr(DropboxEmulatorFile),\n /**\n * `true` for an entry no fixture shows, held only because a seeded path needs it: lookups pass\n * through it, but an answer that would render it is not emulated.\n */\n implied: Schema.Boolean\n})\n\nexport type DropboxEmulatorEntry = typeof DropboxEmulatorEntry.Type\n\n/**\n * What Dropbox keeps of a deleted entry (`include_deleted` lookups answer it): its name and paths\n * as they were. `delete_v2` records one only for an empty folder, the only delete whose\n * `include_deleted` answer a fixture records.\n */\nexport const DropboxEmulatorDeleted = Schema.Struct({\n name: EntryName,\n pathLower: Schema.String,\n pathDisplay: Schema.String\n})\n\nexport type DropboxEmulatorDeleted = typeof DropboxEmulatorDeleted.Type\n\nconst Counter = Schema.Int.check(Schema.isGreaterThanOrEqualTo(1))\n\nconst Counters = Schema.Struct({\n /** Next number in created entry ids (`id:SyntheticEntry00000001`). */\n nextIdNumber: Counter,\n /** Next number in minted revs (`a1b2c3d4e5f60101`). */\n nextRevNumber: Counter\n})\n\n/** The whole emulator state (JSON-compatible; what `snapshot()` returns). */\nexport const DropboxEmulatorStateSchema = Schema.Struct({\n entries: Schema.Array(DropboxEmulatorEntry),\n deleted: Schema.Array(DropboxEmulatorDeleted),\n counters: Counters\n})\n\n/**\n * The emulator state. The container is mutable (routes replace whole lists); every entity is\n * replaced, never edited in place.\n */\nexport type DropboxEmulatorState = {\n entries: ReadonlyArray<DropboxEmulatorEntry>\n deleted: ReadonlyArray<DropboxEmulatorDeleted>\n counters: typeof Counters.Type\n}\n\n/** An absolute path in display casing: `/`-separated non-empty components, no trailing `/`. */\nconst SeedPath = Schema.String.check(Schema.isPattern(/^(?:\\/[^/]+)+$/u))\n\n/**\n * A seeded entry, addressed by its display path (its parent folder must be seeded too). File\n * fields default to synthetic values; a folder takes none.\n */\nexport const DropboxEmulatorEntrySeed = Schema.Struct({\n path: SeedPath,\n id: EntryId,\n kind: Schema.Literals(['file', 'folder']),\n rev: Schema.optionalKey(Rev),\n size: Schema.optionalKey(Schema.Int.check(Schema.isGreaterThanOrEqualTo(0))),\n contentHash: Schema.optionalKey(Schema.String),\n clientModified: Schema.optionalKey(Timestamp),\n serverModified: Schema.optionalKey(Timestamp),\n /** An entry no fixture shows (default `false`); see `DropboxEmulatorEntry.implied`. */\n implied: Schema.optionalKey(Schema.Boolean)\n})\n\nexport type DropboxEmulatorEntrySeed = typeof DropboxEmulatorEntrySeed.Type\n\n/** Account-variance profiles for the default seed. */\nexport const DropboxEmulatorProfile = Schema.Literals(['default', 'empty'])\n\nexport type DropboxEmulatorProfile = typeof DropboxEmulatorProfile.Type\n\n/**\n * A typed seed. Start from `profile` (default `'default'`, the fixture entries); `entries` and\n * `deleted`, when given, replace that part of the profile.\n */\nexport const DropboxEmulatorSeed = Schema.Struct({\n profile: Schema.optionalKey(DropboxEmulatorProfile),\n entries: Schema.optionalKey(Schema.Array(DropboxEmulatorEntrySeed)),\n deleted: Schema.optionalKey(Schema.Array(DropboxEmulatorDeleted))\n})\n\nexport type DropboxEmulatorSeed = typeof DropboxEmulatorSeed.Type\n\nconst strict = { onExcessProperty: 'error' } as const\n\nconst decodeSeedInput = Schema.decodeUnknownResult(DropboxEmulatorSeed, strict)\n\nconst decodeStateInput = Schema.decodeUnknownResult(DropboxEmulatorStateSchema, strict)\n\nconst issueMessage = (issue: Schema.SchemaError['issue']): string =>\n new Schema.SchemaError(issue).message\n\n/** A synthetic 64-digit content hash ending in `suffix`, as the fixtures write them. */\nexport const syntheticContentHash = (suffix: string): string => suffix.padStart(64, '0')\n\n// Default entries. Fixture-derived: every file, the `paging-two` folder, and every path, id, rev,\n// size, content hash, and timestamp of them. Implied (never rendered): the `/Conformance`,\n// `/Conformance/Paging`, `/Conformance/Search`, and `/Conformance/Work` folders.\n\nconst file = (\n path: string,\n id: string,\n revSuffix: string,\n size: number,\n hashSuffix: string,\n clientModified: string,\n serverModified: string = clientModified\n): DropboxEmulatorEntrySeed => ({\n path,\n id,\n kind: 'file',\n rev: `a1b2c3d4e5f6${revSuffix}`,\n size,\n contentHash: syntheticContentHash(hashSuffix),\n clientModified,\n serverModified\n})\n\nconst folder = (path: string, id: string): DropboxEmulatorEntrySeed => ({\n path,\n id,\n kind: 'folder'\n})\n\n/** A parent folder no fixture shows. */\nconst impliedFolder = (path: string, id: string): DropboxEmulatorEntrySeed => ({\n ...folder(path, id),\n implied: true\n})\n\nconst defaultEntries: ReadonlyArray<DropboxEmulatorEntrySeed> = [\n impliedFolder('/Conformance', 'id:SyntheticConformanceFolder'),\n impliedFolder('/Conformance/Paging', 'id:SyntheticPagingFolder0000'),\n file(\n '/Conformance/Paging/paging-one.txt',\n 'id:SyntheticPagingFile0001',\n '0001',\n 12,\n '1',\n '2026-09-20T10:00:00Z'\n ),\n folder('/Conformance/Paging/paging-two', 'id:SyntheticPagingFolder0002'),\n file(\n '/Conformance/Paging/paging-three.txt',\n 'id:SyntheticPagingFile0003',\n '0003',\n 14,\n '3',\n '2026-09-20T10:05:00Z'\n ),\n file(\n '/Conformance/Mixed Case Notes.txt',\n 'id:SyntheticMixedCaseFile01',\n '0010',\n 20,\n '10',\n '2026-09-20T11:00:00Z'\n ),\n impliedFolder('/Conformance/Search', 'id:SyntheticSearchFolder0000'),\n file(\n '/Conformance/Search/yolk-search-probe-1.txt',\n 'id:SyntheticSearchFile0001',\n '0021',\n 16,\n '21',\n '2026-09-20T12:00:00Z'\n ),\n file(\n '/Conformance/Search/yolk-search-probe-2.txt',\n 'id:SyntheticSearchFile0002',\n '0022',\n 16,\n '22',\n '2026-09-20T12:00:00Z'\n ),\n impliedFolder('/Conformance/Work', 'id:SyntheticWorkFolder00000'),\n file(\n '/Conformance/copy-source.txt',\n 'id:SyntheticCopySource001',\n '0030',\n 42,\n '42',\n '2026-09-20T13:00:00Z',\n '2026-09-29T13:00:00Z'\n )\n]\n\nconst profileEntries = (\n profile: DropboxEmulatorProfile\n): ReadonlyArray<DropboxEmulatorEntrySeed> => (profile === 'default' ? defaultEntries : [])\n\nconst defaultTimestamp = '2026-09-20T10:00:00Z'\n\n/** The form of minted ids (`id:SyntheticEntry00000001`) and revs (`a1b2c3d4e5f60101`). */\nconst mintedIdPattern = /^id:SyntheticEntry(\\d+)$/\n\nconst mintedRevPattern = /^a1b2c3d4e5f6(\\d+)$/\n\n/** The highest number among `values` in a minted form (0 when none is). */\nconst highestMinted = (values: ReadonlyArray<string>, pattern: RegExp): number =>\n Math.max(0, ...values.map(value => Number(pattern.exec(value)?.[1] ?? 0)))\n\nconst duplicate = (values: ReadonlyArray<string>): string | undefined =>\n values.find((value, index) => values.indexOf(value) !== index)\n\nconst parentPath = (path: string): string => path.slice(0, path.lastIndexOf('/'))\n\nconst lastName = (path: string): string => path.slice(path.lastIndexOf('/') + 1)\n\n/** Build the emulator state for a decoded seed; a string is an integrity problem. */\nconst stateFromSeed = (seed: DropboxEmulatorSeed): DropboxEmulatorState | string => {\n const seeded = seed.entries ?? profileEntries(seed.profile ?? 'default')\n\n const duplicates: ReadonlyArray<readonly [string, string | undefined]> = [\n ['entry id', duplicate(seeded.map(entry => entry.id))],\n ['entry path', duplicate(seeded.map(entry => entry.path.toLowerCase()))]\n ]\n\n for (const [label, value] of duplicates) {\n if (value !== undefined) return `duplicate ${label} ${value}`\n }\n\n const byPath = new Map(seeded.map(entry => [entry.path.toLowerCase(), entry]))\n const entries: Array<DropboxEmulatorEntry> = []\n\n for (const entry of seeded) {\n const name = lastName(entry.path)\n\n if (name === '.' || name === '..') return `entry ${entry.id} has an invalid path`\n\n const parent = parentPath(entry.path)\n const parentEntry = parent === '' ? undefined : byPath.get(parent.toLowerCase())\n\n if (parent !== '' && (parentEntry === undefined || parentEntry.kind !== 'folder')) {\n return `entry ${entry.path} needs a seeded parent folder ${parent}`\n }\n\n const hasFileFields = [\n entry.rev,\n entry.size,\n entry.contentHash,\n entry.clientModified,\n entry.serverModified\n ].some(Predicate.isNotUndefined)\n\n if (entry.kind === 'folder' && hasFileFields) {\n return `folder ${entry.path} cannot take file fields`\n }\n\n const clientModified = entry.clientModified ?? defaultTimestamp\n\n entries.push({\n id: entry.id,\n parentId: parentEntry?.id ?? null,\n name,\n implied: entry.implied ?? false,\n file:\n entry.kind === 'folder'\n ? null\n : {\n rev: entry.rev ?? 'a1b2c3d4e5f60000',\n size: entry.size ?? 0,\n contentHash: entry.contentHash ?? syntheticContentHash('0'),\n clientModified,\n serverModified: entry.serverModified ?? clientModified\n }\n })\n }\n\n return {\n entries,\n deleted: seed.deleted ?? [],\n counters: {\n nextIdNumber:\n highestMinted(\n entries.map(entry => entry.id),\n mintedIdPattern\n ) + 1,\n nextRevNumber: Math.max(\n 101,\n highestMinted(\n entries.flatMap(entry => entry.file?.rev ?? []),\n mintedRevPattern\n ) + 1\n )\n }\n }\n}\n\n/** Decode and build a seed; a string is the reason it is invalid. */\nexport const buildSeedState = (input: unknown): DropboxEmulatorState | string => {\n const decoded = decodeSeedInput(input)\n\n return Result.isFailure(decoded)\n ? issueMessage(decoded.failure.issue)\n : stateFromSeed(decoded.success)\n}\n\n/** Decode a full state (a restored snapshot); a string is the reason it is invalid. */\nexport const decodeState = (input: unknown): DropboxEmulatorState | string => {\n const decoded = decodeStateInput(input)\n\n return Result.isFailure(decoded) ? issueMessage(decoded.failure.issue) : { ...decoded.success }\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"state.d.mts","names":[],"sources":["../../src/github/state.ts"],"mappings":";;;;cA8Ba,kBAAA,EAAkB,
|
|
1
|
+
{"version":3,"file":"state.d.mts","names":[],"sources":["../../src/github/state.ts"],"mappings":";;;;cA8Ba,kBAAA,EAAkB,MAAyD;AAAxF;AAAA,cAGa,iBAAA,EAAiB,MAA6B;;cAG9C,sBAAA,EAAsB,MAAuC;AANc;AAGxF;;;AAHwF,cAY3E,oBAAA;AAT8C;AAAA,cAY9C,qBAAA,EAAqB,MAA+C;;cAGpE,sBAAA,EAAsB,MAA4C;;cAOlE,oBAAA;AAbb;AAAA,cAkBa,kBAAA;;cAOA,kBAAA,EAAkB,MAAA,CAAA,MAAA;EAAA;;;;;KAOnB,kBAAA,UAA4B,kBAAA,CAAmB,IAAI;AA1B/D;AAAA,cA6Ba,wBAAA,EAAwB,MAAA,CAAA,MAAA;EAAA;gCA7B0C;EAAA;;;KA4CnE,wBAAA,UAAkC,wBAAA,CAAyB,IAAI;AArC1C;AAAA,cAwCpB,mBAAA,EAAmB,MAAA,CAAA,MAAA;EAAA;;gCAnCD;EAAA;;;;KA6CnB,mBAAA,UAA6B,mBAAA,CAAoB,IAAI;;cAGpD,uBAAA,EAAuB,MAAA,CAAA,MAAA;EAAA;;;;;;;;;;;;;;;;;;;KAgBxB,uBAAA,UAAiC,uBAAA,CAAwB,IAAI;;cAG5D,mBAAA,EAAmB,MAAA,CAAA,MAAA;EArDQ;;;;;;;;;;;;;;;;;;;;KA2D5B,mBAAA,UAA6B,mBAAA,CAAoB,IAAI;;cAGpD,qBAAA,EAAqB,MAAA,CAAA,MAAA;EAAA;;;;;;;;;;;;;KAUtB,qBAAA,UAA+B,qBAAA,CAAsB,IAAI;;cAGxD,kBAAA,EAAkB,MAAA,CAAA,MAAA;EAAA;;;;KAOnB,kBAAA,UAA4B,kBAAA,CAAmB,IAAI;AAAA,cAEzD,QAAA,EAAQ,MAAA,CAAA,MAAA;EA/DkB;;;;cAuEnB,yBAAA,EAAyB,MAAA,CAAA,MAAA;;;;;;;;;;;;;;;;kCA7DG;IAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KA8E7B,mBAAA;EACV,MAAA,EAAQ,kBAAA;EACR,UAAA,EAAY,wBAAA;EACZ,MAAA,EAAQ,aAAA,CAAc,mBAAA;EACtB,MAAA,EAAQ,aAAA,CAAc,mBAAA;EACtB,QAAA,EAAU,aAAA,CAAc,qBAAA;EACxB,eAAA,EAAiB,aAAA;EACjB,KAAA,EAAO,aAAA,CAAc,kBAAA;EACrB,QAAA,SAAiB,QAAA,CAAS,IAAA;AAAA;;cAIf,qBAAA,EAAqB,MAAA,CAAA,QAAA;AAAA,KAEtB,qBAAA,UAA+B,qBAAA,CAAsB,IAAI;;;;;;cAOxD,kBAAA,EAAkB,MAAA,CAAA,MAAA;EAAA;;;;;;;;;;;;;;;;kCAhF0C;IAAA;;;;;;;;;;;;;;;;;;;;;;;;;kCAGzC;IAAA;;;;;;KAwFpB,kBAAA,UAA4B,kBAAA,CAAmB,IAAI;;cAYlD,iBAAA;AAAA,cAEA,iBAAA,GAAqB,MAAc;AAAA,cAEnC,mBAAA,GAAuB,EAAU;;cAkMjC,cAAA,GAAkB,KAAA,cAAiB,mBAAmB;;cAStD,WAAA,GAAe,KAAA,cAAiB,mBAAmB"}
|
package/dist/github/state.mjs
CHANGED
|
@@ -28,20 +28,20 @@ import * as Schema from "effect/Schema";
|
|
|
28
28
|
* @experimental
|
|
29
29
|
*/
|
|
30
30
|
/** A GitHub owner login (the connector's owner pattern). */
|
|
31
|
-
const githubOwnerPattern = /^[A-Za-z0-9](?:[A-Za-z0-9]|-(?=[A-Za-z0-9])){0,38}
|
|
31
|
+
const githubOwnerPattern = /^[A-Za-z0-9](?:[A-Za-z0-9]|-(?=[A-Za-z0-9])){0,38}$/u;
|
|
32
32
|
/** A repository name in plain characters (no `.`/`..`, no `.git` suffix; checked below). */
|
|
33
|
-
const githubRepoPattern = /^[A-Za-z0-9._-]{1,100}
|
|
33
|
+
const githubRepoPattern = /^[A-Za-z0-9._-]{1,100}$/u;
|
|
34
34
|
/** A label name in plain characters (the conformance seed pattern; never percent-encoded). */
|
|
35
|
-
const githubLabelNamePattern = /^[A-Za-z0-9][A-Za-z0-9._-]{0,49}
|
|
35
|
+
const githubLabelNamePattern = /^[A-Za-z0-9][A-Za-z0-9._-]{0,49}$/u;
|
|
36
36
|
/**
|
|
37
37
|
* The source of a relative file path whose segments never start with a dot (the conformance seed
|
|
38
38
|
* pattern), unanchored, so the route's raw pattern can add its length bound explicitly.
|
|
39
39
|
*/
|
|
40
40
|
const githubFilePathSource = "[A-Za-z0-9_-][A-Za-z0-9._-]*(?:/[A-Za-z0-9_-][A-Za-z0-9._-]*)*";
|
|
41
41
|
/** A relative file path whose segments never start with a dot (the conformance seed pattern). */
|
|
42
|
-
const githubFilePathPattern = new RegExp(`^${githubFilePathSource}
|
|
42
|
+
const githubFilePathPattern = new RegExp(`^${githubFilePathSource}$`, "u");
|
|
43
43
|
/** A GitHub timestamp as the fixtures write it: whole seconds, UTC. */
|
|
44
|
-
const githubTimestampPattern = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z
|
|
44
|
+
const githubTimestampPattern = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$/u;
|
|
45
45
|
const Timestamp = Schema.String.check(Schema.isPattern(githubTimestampPattern));
|
|
46
46
|
const PositiveInt = Schema.Int.check(Schema.isGreaterThanOrEqualTo(1));
|
|
47
47
|
/** The largest issue number the emulator addresses (ten digits, the route pattern). */
|
|
@@ -64,7 +64,7 @@ const GithubEmulatorRepository = Schema.Struct({
|
|
|
64
64
|
repo: Schema.String.check(Schema.isPattern(githubRepoPattern), Schema.makeFilter((repo) => repo !== "." && repo !== ".." && !repo.endsWith(".git") || "a repository name is not . or .. and does not end in .git")),
|
|
65
65
|
/** The id the `Link` URLs name (`/repositories/<id>/labels`). */
|
|
66
66
|
id: PositiveInt,
|
|
67
|
-
defaultBranch: Schema.String.check(Schema.isPattern(/^[A-Za-z0-9._-]{1,100}$/))
|
|
67
|
+
defaultBranch: Schema.String.check(Schema.isPattern(/^[A-Za-z0-9._-]{1,100}$/u))
|
|
68
68
|
});
|
|
69
69
|
/** A repository label. */
|
|
70
70
|
const GithubEmulatorLabel = Schema.Struct({
|
|
@@ -72,7 +72,7 @@ const GithubEmulatorLabel = Schema.Struct({
|
|
|
72
72
|
nodeId: Schema.String,
|
|
73
73
|
name: LabelName,
|
|
74
74
|
/** Six hex digits, as the fixtures write colors. */
|
|
75
|
-
color: Schema.String.check(Schema.isPattern(/^[0-9a-f]{6}$/)),
|
|
75
|
+
color: Schema.String.check(Schema.isPattern(/^[0-9a-f]{6}$/u)),
|
|
76
76
|
default: Schema.Boolean,
|
|
77
77
|
description: Schema.NullOr(Schema.String)
|
|
78
78
|
});
|
|
@@ -112,7 +112,7 @@ const GithubEmulatorComment = Schema.Struct({
|
|
|
112
112
|
const GithubEmulatorFile = Schema.Struct({
|
|
113
113
|
path: Schema.String.check(Schema.isPattern(githubFilePathPattern), Schema.isMaxLength(200)),
|
|
114
114
|
/** The blob sha (40 lower-case hex digits). */
|
|
115
|
-
sha: Schema.String.check(Schema.isPattern(/^[0-9a-f]{40}$/)),
|
|
115
|
+
sha: Schema.String.check(Schema.isPattern(/^[0-9a-f]{40}$/u)),
|
|
116
116
|
text: Schema.String
|
|
117
117
|
});
|
|
118
118
|
const Counters = Schema.Struct({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"state.mjs","names":[],"sources":["../../src/github/state.ts"],"sourcesContent":["/**\n * GitHub emulator state: the authenticated user, the practice repository, its labels, issues,\n * comments, and files, the record of deleted comments, the counters, the seed input, the default\n * seed, and the profiles (internal; re-exported by `src/github.ts`).\n *\n * Entity shapes and the default entities follow the synthetic GitHub conformance fixtures, copied\n * as data (the same owner, repository, labels, work issue, file, user, and ids as the fixtures and\n * `githubConformanceFixtureSeeds`), never imported from SDK code. The repository id is the one the\n * paging fixture's `Link` URLs name, and the default branch is the one the contents fixture's URLs\n * name.\n *\n * Issue numbers below `nextIssueNumber` that the state does not hold are implied (the repository\n * reached them, but no fixture shows them): an answer that would render one is not emulated.\n * Numbers at or above it are not reached yet and answer the not-found fixture's 404. Created issue\n * numbers and comment ids come from counters that only advance; the default seed starts them at the\n * fixtures' created values (issue 42, comment 9000000001), and a seed's counters must lie above\n * every seeded number. Created issue `id` and `node_id` values derive from the minted number in the\n * fixtures' form (`3000000000 + number`, `I_kwSynthetic<number>`), and created comment `node_id`\n * values from the minted id (`IC_kwSynthetic<id>`); a seed may not use those minted forms at or\n * above its counters.\n *\n * Comments exist only as created here (no fixture shows a seeded comment). A deleted comment is\n * kept as a record (`deletedComments`): deleting it again answers the comment fixture's 404.\n *\n * @experimental\n */\nimport { Result } from 'effect'\nimport * as Schema from 'effect/Schema'\n\n/** A GitHub owner login (the connector's owner pattern). */\nexport const githubOwnerPattern = /^[A-Za-z0-9](?:[A-Za-z0-9]|-(?=[A-Za-z0-9])){0,38}$/\n\n/** A repository name in plain characters (no `.`/`..`, no `.git` suffix; checked below). */\nexport const githubRepoPattern = /^[A-Za-z0-9._-]{1,100}$/\n\n/** A label name in plain characters (the conformance seed pattern; never percent-encoded). */\nexport const githubLabelNamePattern = /^[A-Za-z0-9][A-Za-z0-9._-]{0,49}$/\n\n/**\n * The source of a relative file path whose segments never start with a dot (the conformance seed\n * pattern), unanchored, so the route's raw pattern can add its length bound explicitly.\n */\nexport const githubFilePathSource = '[A-Za-z0-9_-][A-Za-z0-9._-]*(?:/[A-Za-z0-9_-][A-Za-z0-9._-]*)*'\n\n/** A relative file path whose segments never start with a dot (the conformance seed pattern). */\nexport const githubFilePathPattern = new RegExp(`^${githubFilePathSource}$`)\n\n/** A GitHub timestamp as the fixtures write it: whole seconds, UTC. */\nexport const githubTimestampPattern = /^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$/\n\nconst Timestamp = Schema.String.check(Schema.isPattern(githubTimestampPattern))\n\nconst PositiveInt = Schema.Int.check(Schema.isGreaterThanOrEqualTo(1))\n\n/** The largest issue number the emulator addresses (ten digits, the route pattern). */\nexport const githubMaxIssueNumber = 9_999_999_999\n\nconst IssueNumber = PositiveInt.check(Schema.isLessThanOrEqualTo(githubMaxIssueNumber))\n\n/** The largest comment id the emulator addresses (fifteen digits, the delete route's pattern). */\nexport const githubMaxCommentId = 999_999_999_999_999\n\nconst CommentId = PositiveInt.check(Schema.isLessThanOrEqualTo(githubMaxCommentId))\n\nconst LabelName = Schema.String.check(Schema.isPattern(githubLabelNamePattern))\n\n/** A user as the fixtures render it (`login`, `id`, `type`, `site_admin`). */\nexport const GithubEmulatorUser = Schema.Struct({\n login: Schema.String.check(Schema.isPattern(githubOwnerPattern)),\n id: PositiveInt,\n type: Schema.String,\n siteAdmin: Schema.Boolean\n})\n\nexport type GithubEmulatorUser = typeof GithubEmulatorUser.Type\n\n/** The practice repository: its owner and name, its numeric id, and its default branch. */\nexport const GithubEmulatorRepository = Schema.Struct({\n owner: Schema.String.check(Schema.isPattern(githubOwnerPattern)),\n repo: Schema.String.check(\n Schema.isPattern(githubRepoPattern),\n Schema.makeFilter(\n (repo: string) =>\n (repo !== '.' && repo !== '..' && !repo.endsWith('.git')) ||\n 'a repository name is not . or .. and does not end in .git'\n )\n ),\n /** The id the `Link` URLs name (`/repositories/<id>/labels`). */\n id: PositiveInt,\n defaultBranch: Schema.String.check(Schema.isPattern(/^[A-Za-z0-9._-]{1,100}$/))\n})\n\nexport type GithubEmulatorRepository = typeof GithubEmulatorRepository.Type\n\n/** A repository label. */\nexport const GithubEmulatorLabel = Schema.Struct({\n id: PositiveInt,\n nodeId: Schema.String,\n name: LabelName,\n /** Six hex digits, as the fixtures write colors. */\n color: Schema.String.check(Schema.isPattern(/^[0-9a-f]{6}$/)),\n default: Schema.Boolean,\n description: Schema.NullOr(Schema.String)\n})\n\nexport type GithubEmulatorLabel = typeof GithubEmulatorLabel.Type\n\n/** A seeded issue (its labels by name; never a pull request, never locked). */\nexport const GithubEmulatorSeedIssue = Schema.Struct({\n number: IssueNumber,\n id: PositiveInt,\n nodeId: Schema.String,\n title: Schema.String.check(Schema.isMinLength(1)),\n body: Schema.NullOr(Schema.String),\n state: Schema.Literals(['open', 'closed']),\n stateReason: Schema.NullOr(Schema.String),\n labels: Schema.Array(LabelName),\n user: GithubEmulatorUser,\n authorAssociation: Schema.String,\n createdAt: Timestamp,\n updatedAt: Timestamp,\n closedAt: Schema.NullOr(Timestamp)\n})\n\nexport type GithubEmulatorSeedIssue = typeof GithubEmulatorSeedIssue.Type\n\n/** A stored issue: a seeded one, or one created through the recorded create flow. */\nexport const GithubEmulatorIssue = Schema.Struct({\n ...GithubEmulatorSeedIssue.fields,\n /** Created here (only such issues are renamed or closed). */\n createdHere: Schema.Boolean\n})\n\nexport type GithubEmulatorIssue = typeof GithubEmulatorIssue.Type\n\n/** A comment created here on an issue. */\nexport const GithubEmulatorComment = Schema.Struct({\n id: CommentId,\n issueNumber: IssueNumber,\n body: Schema.String,\n user: GithubEmulatorUser,\n authorAssociation: Schema.String,\n createdAt: Timestamp,\n updatedAt: Timestamp\n})\n\nexport type GithubEmulatorComment = typeof GithubEmulatorComment.Type\n\n/** A UTF-8 text file in the default branch. */\nexport const GithubEmulatorFile = Schema.Struct({\n path: Schema.String.check(Schema.isPattern(githubFilePathPattern), Schema.isMaxLength(200)),\n /** The blob sha (40 lower-case hex digits). */\n sha: Schema.String.check(Schema.isPattern(/^[0-9a-f]{40}$/)),\n text: Schema.String\n})\n\nexport type GithubEmulatorFile = typeof GithubEmulatorFile.Type\n\nconst Counters = Schema.Struct({\n /** The number the next created issue takes (one past the maximum when exhausted). */\n nextIssueNumber: PositiveInt.check(Schema.isLessThanOrEqualTo(githubMaxIssueNumber + 1)),\n /** The id the next created comment takes (one past the maximum when exhausted). */\n nextCommentId: PositiveInt.check(Schema.isLessThanOrEqualTo(githubMaxCommentId + 1))\n})\n\n/** The whole emulator state (JSON-compatible; what `snapshot()` returns). */\nexport const GithubEmulatorStateSchema = Schema.Struct({\n /** The authenticated user: the author of everything created here. */\n viewer: GithubEmulatorUser,\n repository: GithubEmulatorRepository,\n labels: Schema.Array(GithubEmulatorLabel),\n issues: Schema.Array(GithubEmulatorIssue),\n comments: Schema.Array(GithubEmulatorComment),\n /** Ids of comments deleted here (a second delete answers the recorded 404). */\n deletedComments: Schema.Array(CommentId),\n files: Schema.Array(GithubEmulatorFile),\n counters: Counters\n})\n\n/**\n * The emulator state. The container is mutable (routes replace whole lists); every entity is\n * replaced, never edited in place.\n */\nexport type GithubEmulatorState = {\n viewer: GithubEmulatorUser\n repository: GithubEmulatorRepository\n labels: ReadonlyArray<GithubEmulatorLabel>\n issues: ReadonlyArray<GithubEmulatorIssue>\n comments: ReadonlyArray<GithubEmulatorComment>\n deletedComments: ReadonlyArray<number>\n files: ReadonlyArray<GithubEmulatorFile>\n counters: typeof Counters.Type\n}\n\n/** Account-variance profiles for the default seed. */\nexport const GithubEmulatorProfile = Schema.Literals(['default', 'empty'])\n\nexport type GithubEmulatorProfile = typeof GithubEmulatorProfile.Type\n\n/**\n * A typed seed. Start from `profile` (default `'default'`, the fixture entities); every other key,\n * when given, replaces that part of the profile. Counters default to the profile's, raised above\n * the highest seeded issue number; a counter given at or below a seeded value is invalid.\n */\nexport const GithubEmulatorSeed = Schema.Struct({\n profile: Schema.optionalKey(GithubEmulatorProfile),\n viewer: Schema.optionalKey(GithubEmulatorUser),\n repository: Schema.optionalKey(GithubEmulatorRepository),\n labels: Schema.optionalKey(Schema.Array(GithubEmulatorLabel)),\n issues: Schema.optionalKey(Schema.Array(GithubEmulatorSeedIssue)),\n files: Schema.optionalKey(Schema.Array(GithubEmulatorFile)),\n nextIssueNumber: Schema.optionalKey(IssueNumber),\n nextCommentId: Schema.optionalKey(CommentId)\n})\n\nexport type GithubEmulatorSeed = typeof GithubEmulatorSeed.Type\n\nconst strict = { onExcessProperty: 'error' } as const\n\nconst decodeSeedInput = Schema.decodeUnknownResult(GithubEmulatorSeed, strict)\n\nconst decodeStateInput = Schema.decodeUnknownResult(GithubEmulatorStateSchema, strict)\n\nconst issueMessage = (issue: Schema.SchemaError['issue']): string =>\n new Schema.SchemaError(issue).message\n\n/** Minted issue `id` values: `3000000000 + number`, as the fixtures write them. */\nexport const mintedIssueIdBase = 3_000_000_000\n\nexport const mintedIssueNodeId = (number: number): string => `I_kwSynthetic${number}`\n\nexport const mintedCommentNodeId = (id: number): string => `IC_kwSynthetic${id}`\n\n// Default entities, copied from the fixtures: the user, the practice repository (the paging\n// fixture's repository id, the contents fixture's branch), the five labels of the paging fixture,\n// the work issue of the label fixture, and the text file of the contents fixture.\n\nconst seededAt = '2026-09-30T12:00:00Z'\n\nconst defaultViewer: GithubEmulatorUser = {\n login: 'yolk-synthetic-bot',\n id: 1000001,\n type: 'User',\n siteAdmin: false\n}\n\nconst defaultRepository: GithubEmulatorRepository = {\n owner: 'yolk-synthetic',\n repo: 'conformance-practice',\n id: 100000001,\n defaultBranch: 'main'\n}\n\nconst label = (id: number, name: string, color: string): GithubEmulatorLabel => ({\n id,\n nodeId: `LA_kwSynthetic${id}`,\n name,\n color,\n default: false,\n description: `Synthetic ${name} label`\n})\n\nconst defaultLabels: ReadonlyArray<GithubEmulatorLabel> = [\n label(700000001, 'bug', 'd73a4a'),\n label(700000002, 'documentation', '0075ca'),\n label(700000003, 'enhancement', 'a2eeef'),\n label(700000004, 'question', 'd876e3'),\n label(700000005, 'synthetic-conformance', 'ededed')\n]\n\nconst workIssue: GithubEmulatorSeedIssue = {\n number: 1,\n id: mintedIssueIdBase + 1,\n nodeId: mintedIssueNodeId(1),\n title: 'Synthetic work issue for conformance runs',\n body: 'Synthetic work issue: conformance cases add and remove a label and a comment here.',\n state: 'open',\n stateReason: null,\n labels: ['bug'],\n user: defaultViewer,\n authorAssociation: 'OWNER',\n createdAt: seededAt,\n updatedAt: seededAt,\n closedAt: null\n}\n\nconst notesFile: GithubEmulatorFile = {\n path: 'docs/synthetic-notes.txt',\n sha: '5f1c0ffee5f1c0ffee5f1c0ffee5f1c0ffee0001',\n text:\n 'Synthetic conformance notes for the practice repository: café, naïve, façade.\\n' +\n 'Second synthetic line.\\n'\n}\n\ntype ProfileParts = {\n readonly viewer: GithubEmulatorUser\n readonly repository: GithubEmulatorRepository\n readonly labels: ReadonlyArray<GithubEmulatorLabel>\n readonly issues: ReadonlyArray<GithubEmulatorSeedIssue>\n readonly files: ReadonlyArray<GithubEmulatorFile>\n readonly counters: typeof Counters.Type\n}\n\nconst profileParts = (profile: GithubEmulatorProfile): ProfileParts =>\n profile === 'default'\n ? {\n viewer: defaultViewer,\n repository: defaultRepository,\n labels: defaultLabels,\n issues: [workIssue],\n files: [notesFile],\n // The lifecycle fixture's created issue and the comment fixture's created comment.\n counters: { nextIssueNumber: 42, nextCommentId: 9000000001 }\n }\n : {\n viewer: defaultViewer,\n repository: defaultRepository,\n labels: [],\n issues: [],\n files: [],\n counters: { nextIssueNumber: 1, nextCommentId: 1 }\n }\n\nconst duplicate = <A>(values: ReadonlyArray<A>): A | undefined =>\n values.find((value, index) => values.indexOf(value) !== index)\n\n/** Integrity problems a decoded seed can still have (duplicates, dangling or minted values). */\nconst seedProblem = (parts: ProfileParts): string | undefined => {\n const duplicates: ReadonlyArray<readonly [string, unknown]> = [\n ['label name', duplicate(parts.labels.map(entry => entry.name))],\n ['label id', duplicate(parts.labels.map(entry => entry.id))],\n ['issue number', duplicate(parts.issues.map(issue => issue.number))],\n ['issue id', duplicate(parts.issues.map(issue => issue.id))],\n // Node ids are global across types (labels and issues alike).\n [\n 'node id',\n duplicate([...parts.labels.map(entry => entry.nodeId), ...parts.issues.map(i => i.nodeId)])\n ],\n ['file path', duplicate(parts.files.map(file => file.path))]\n ]\n\n for (const [kind, value] of duplicates) {\n if (value !== undefined) return `duplicate ${kind} ${String(value)}`\n }\n\n const labelNames = new Set(parts.labels.map(entry => entry.name))\n\n for (const issue of parts.issues) {\n const missing = issue.labels.find(name => !labelNames.has(name))\n\n if (missing !== undefined) return `issue ${issue.number} names missing label ${missing}`\n\n if (duplicate(issue.labels) !== undefined) return `issue ${issue.number} repeats a label`\n\n if (issue.number >= parts.counters.nextIssueNumber) {\n return `nextIssueNumber must lie above seeded issue ${issue.number}`\n }\n\n // Minted ids never collide with seeded ones.\n if (issue.id >= mintedIssueIdBase + parts.counters.nextIssueNumber) {\n return `issue ${issue.number} uses an id in the minted form at or above nextIssueNumber`\n }\n }\n\n // Every minted node id form is reserved across all seeded node ids, of issues and labels alike.\n const reserved = [...parts.labels.map(entry => entry.nodeId), ...parts.issues.map(i => i.nodeId)]\n .map(nodeId => {\n const issueNumber = /^I_kwSynthetic(\\d+)$/.exec(nodeId)?.[1]\n const commentId = /^IC_kwSynthetic(\\d+)$/.exec(nodeId)?.[1]\n\n return (issueNumber !== undefined && Number(issueNumber) >= parts.counters.nextIssueNumber) ||\n (commentId !== undefined && Number(commentId) >= parts.counters.nextCommentId)\n ? nodeId\n : undefined\n })\n .find(nodeId => nodeId !== undefined)\n\n if (reserved !== undefined) {\n return `node id ${reserved} uses a minted form at or above its counter`\n }\n\n const prefix = parts.files.find(file =>\n parts.files.some(other => other.path.startsWith(`${file.path}/`))\n )\n\n return prefix === undefined ? undefined : `file ${prefix.path} is also a folder`\n}\n\n/** Build the emulator state for a decoded seed; a string is an integrity problem. */\nconst stateFromSeed = (seed: GithubEmulatorSeed): GithubEmulatorState | string => {\n const profile = profileParts(seed.profile ?? 'default')\n const issues = seed.issues ?? profile.issues\n const highest = Math.max(0, ...issues.map(issue => issue.number))\n\n const parts: ProfileParts = {\n viewer: seed.viewer ?? profile.viewer,\n repository: seed.repository ?? profile.repository,\n labels: seed.labels ?? profile.labels,\n issues,\n files: seed.files ?? profile.files,\n counters: {\n // Raised above the seeded numbers unless the seed names the counter itself (then checked).\n nextIssueNumber:\n seed.nextIssueNumber ?? Math.max(profile.counters.nextIssueNumber, highest + 1),\n nextCommentId: seed.nextCommentId ?? profile.counters.nextCommentId\n }\n }\n\n const problem = seedProblem(parts)\n\n return (\n problem ?? {\n viewer: parts.viewer,\n repository: parts.repository,\n labels: parts.labels,\n issues: parts.issues.map(issue => ({ ...issue, createdHere: false })),\n comments: [],\n deletedComments: [],\n files: parts.files,\n counters: parts.counters\n }\n )\n}\n\n/** Decode and build a seed; a string is the reason it is invalid. */\nexport const buildSeedState = (input: unknown): GithubEmulatorState | string => {\n const decoded = decodeSeedInput(input)\n\n return Result.isFailure(decoded)\n ? issueMessage(decoded.failure.issue)\n : stateFromSeed(decoded.success)\n}\n\n/** Decode a full state (a restored snapshot); a string is the reason it is invalid. */\nexport const decodeState = (input: unknown): GithubEmulatorState | string => {\n const decoded = decodeStateInput(input)\n\n return Result.isFailure(decoded) ? issueMessage(decoded.failure.issue) : { ...decoded.success }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"state.mjs","names":[],"sources":["../../src/github/state.ts"],"sourcesContent":["/**\n * GitHub emulator state: the authenticated user, the practice repository, its labels, issues,\n * comments, and files, the record of deleted comments, the counters, the seed input, the default\n * seed, and the profiles (internal; re-exported by `src/github.ts`).\n *\n * Entity shapes and the default entities follow the synthetic GitHub conformance fixtures, copied\n * as data (the same owner, repository, labels, work issue, file, user, and ids as the fixtures and\n * `githubConformanceFixtureSeeds`), never imported from SDK code. The repository id is the one the\n * paging fixture's `Link` URLs name, and the default branch is the one the contents fixture's URLs\n * name.\n *\n * Issue numbers below `nextIssueNumber` that the state does not hold are implied (the repository\n * reached them, but no fixture shows them): an answer that would render one is not emulated.\n * Numbers at or above it are not reached yet and answer the not-found fixture's 404. Created issue\n * numbers and comment ids come from counters that only advance; the default seed starts them at the\n * fixtures' created values (issue 42, comment 9000000001), and a seed's counters must lie above\n * every seeded number. Created issue `id` and `node_id` values derive from the minted number in the\n * fixtures' form (`3000000000 + number`, `I_kwSynthetic<number>`), and created comment `node_id`\n * values from the minted id (`IC_kwSynthetic<id>`); a seed may not use those minted forms at or\n * above its counters.\n *\n * Comments exist only as created here (no fixture shows a seeded comment). A deleted comment is\n * kept as a record (`deletedComments`): deleting it again answers the comment fixture's 404.\n *\n * @experimental\n */\nimport { Result } from 'effect'\nimport * as Schema from 'effect/Schema'\n\n/** A GitHub owner login (the connector's owner pattern). */\nexport const githubOwnerPattern = /^[A-Za-z0-9](?:[A-Za-z0-9]|-(?=[A-Za-z0-9])){0,38}$/u\n\n/** A repository name in plain characters (no `.`/`..`, no `.git` suffix; checked below). */\nexport const githubRepoPattern = /^[A-Za-z0-9._-]{1,100}$/u\n\n/** A label name in plain characters (the conformance seed pattern; never percent-encoded). */\nexport const githubLabelNamePattern = /^[A-Za-z0-9][A-Za-z0-9._-]{0,49}$/u\n\n/**\n * The source of a relative file path whose segments never start with a dot (the conformance seed\n * pattern), unanchored, so the route's raw pattern can add its length bound explicitly.\n */\nexport const githubFilePathSource = '[A-Za-z0-9_-][A-Za-z0-9._-]*(?:/[A-Za-z0-9_-][A-Za-z0-9._-]*)*'\n\n/** A relative file path whose segments never start with a dot (the conformance seed pattern). */\nexport const githubFilePathPattern = new RegExp(`^${githubFilePathSource}$`, 'u')\n\n/** A GitHub timestamp as the fixtures write it: whole seconds, UTC. */\nexport const githubTimestampPattern = /^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$/u\n\nconst Timestamp = Schema.String.check(Schema.isPattern(githubTimestampPattern))\n\nconst PositiveInt = Schema.Int.check(Schema.isGreaterThanOrEqualTo(1))\n\n/** The largest issue number the emulator addresses (ten digits, the route pattern). */\nexport const githubMaxIssueNumber = 9_999_999_999\n\nconst IssueNumber = PositiveInt.check(Schema.isLessThanOrEqualTo(githubMaxIssueNumber))\n\n/** The largest comment id the emulator addresses (fifteen digits, the delete route's pattern). */\nexport const githubMaxCommentId = 999_999_999_999_999\n\nconst CommentId = PositiveInt.check(Schema.isLessThanOrEqualTo(githubMaxCommentId))\n\nconst LabelName = Schema.String.check(Schema.isPattern(githubLabelNamePattern))\n\n/** A user as the fixtures render it (`login`, `id`, `type`, `site_admin`). */\nexport const GithubEmulatorUser = Schema.Struct({\n login: Schema.String.check(Schema.isPattern(githubOwnerPattern)),\n id: PositiveInt,\n type: Schema.String,\n siteAdmin: Schema.Boolean\n})\n\nexport type GithubEmulatorUser = typeof GithubEmulatorUser.Type\n\n/** The practice repository: its owner and name, its numeric id, and its default branch. */\nexport const GithubEmulatorRepository = Schema.Struct({\n owner: Schema.String.check(Schema.isPattern(githubOwnerPattern)),\n repo: Schema.String.check(\n Schema.isPattern(githubRepoPattern),\n Schema.makeFilter(\n (repo: string) =>\n (repo !== '.' && repo !== '..' && !repo.endsWith('.git')) ||\n 'a repository name is not . or .. and does not end in .git'\n )\n ),\n /** The id the `Link` URLs name (`/repositories/<id>/labels`). */\n id: PositiveInt,\n defaultBranch: Schema.String.check(Schema.isPattern(/^[A-Za-z0-9._-]{1,100}$/u))\n})\n\nexport type GithubEmulatorRepository = typeof GithubEmulatorRepository.Type\n\n/** A repository label. */\nexport const GithubEmulatorLabel = Schema.Struct({\n id: PositiveInt,\n nodeId: Schema.String,\n name: LabelName,\n /** Six hex digits, as the fixtures write colors. */\n color: Schema.String.check(Schema.isPattern(/^[0-9a-f]{6}$/u)),\n default: Schema.Boolean,\n description: Schema.NullOr(Schema.String)\n})\n\nexport type GithubEmulatorLabel = typeof GithubEmulatorLabel.Type\n\n/** A seeded issue (its labels by name; never a pull request, never locked). */\nexport const GithubEmulatorSeedIssue = Schema.Struct({\n number: IssueNumber,\n id: PositiveInt,\n nodeId: Schema.String,\n title: Schema.String.check(Schema.isMinLength(1)),\n body: Schema.NullOr(Schema.String),\n state: Schema.Literals(['open', 'closed']),\n stateReason: Schema.NullOr(Schema.String),\n labels: Schema.Array(LabelName),\n user: GithubEmulatorUser,\n authorAssociation: Schema.String,\n createdAt: Timestamp,\n updatedAt: Timestamp,\n closedAt: Schema.NullOr(Timestamp)\n})\n\nexport type GithubEmulatorSeedIssue = typeof GithubEmulatorSeedIssue.Type\n\n/** A stored issue: a seeded one, or one created through the recorded create flow. */\nexport const GithubEmulatorIssue = Schema.Struct({\n ...GithubEmulatorSeedIssue.fields,\n /** Created here (only such issues are renamed or closed). */\n createdHere: Schema.Boolean\n})\n\nexport type GithubEmulatorIssue = typeof GithubEmulatorIssue.Type\n\n/** A comment created here on an issue. */\nexport const GithubEmulatorComment = Schema.Struct({\n id: CommentId,\n issueNumber: IssueNumber,\n body: Schema.String,\n user: GithubEmulatorUser,\n authorAssociation: Schema.String,\n createdAt: Timestamp,\n updatedAt: Timestamp\n})\n\nexport type GithubEmulatorComment = typeof GithubEmulatorComment.Type\n\n/** A UTF-8 text file in the default branch. */\nexport const GithubEmulatorFile = Schema.Struct({\n path: Schema.String.check(Schema.isPattern(githubFilePathPattern), Schema.isMaxLength(200)),\n /** The blob sha (40 lower-case hex digits). */\n sha: Schema.String.check(Schema.isPattern(/^[0-9a-f]{40}$/u)),\n text: Schema.String\n})\n\nexport type GithubEmulatorFile = typeof GithubEmulatorFile.Type\n\nconst Counters = Schema.Struct({\n /** The number the next created issue takes (one past the maximum when exhausted). */\n nextIssueNumber: PositiveInt.check(Schema.isLessThanOrEqualTo(githubMaxIssueNumber + 1)),\n /** The id the next created comment takes (one past the maximum when exhausted). */\n nextCommentId: PositiveInt.check(Schema.isLessThanOrEqualTo(githubMaxCommentId + 1))\n})\n\n/** The whole emulator state (JSON-compatible; what `snapshot()` returns). */\nexport const GithubEmulatorStateSchema = Schema.Struct({\n /** The authenticated user: the author of everything created here. */\n viewer: GithubEmulatorUser,\n repository: GithubEmulatorRepository,\n labels: Schema.Array(GithubEmulatorLabel),\n issues: Schema.Array(GithubEmulatorIssue),\n comments: Schema.Array(GithubEmulatorComment),\n /** Ids of comments deleted here (a second delete answers the recorded 404). */\n deletedComments: Schema.Array(CommentId),\n files: Schema.Array(GithubEmulatorFile),\n counters: Counters\n})\n\n/**\n * The emulator state. The container is mutable (routes replace whole lists); every entity is\n * replaced, never edited in place.\n */\nexport type GithubEmulatorState = {\n viewer: GithubEmulatorUser\n repository: GithubEmulatorRepository\n labels: ReadonlyArray<GithubEmulatorLabel>\n issues: ReadonlyArray<GithubEmulatorIssue>\n comments: ReadonlyArray<GithubEmulatorComment>\n deletedComments: ReadonlyArray<number>\n files: ReadonlyArray<GithubEmulatorFile>\n counters: typeof Counters.Type\n}\n\n/** Account-variance profiles for the default seed. */\nexport const GithubEmulatorProfile = Schema.Literals(['default', 'empty'])\n\nexport type GithubEmulatorProfile = typeof GithubEmulatorProfile.Type\n\n/**\n * A typed seed. Start from `profile` (default `'default'`, the fixture entities); every other key,\n * when given, replaces that part of the profile. Counters default to the profile's, raised above\n * the highest seeded issue number; a counter given at or below a seeded value is invalid.\n */\nexport const GithubEmulatorSeed = Schema.Struct({\n profile: Schema.optionalKey(GithubEmulatorProfile),\n viewer: Schema.optionalKey(GithubEmulatorUser),\n repository: Schema.optionalKey(GithubEmulatorRepository),\n labels: Schema.optionalKey(Schema.Array(GithubEmulatorLabel)),\n issues: Schema.optionalKey(Schema.Array(GithubEmulatorSeedIssue)),\n files: Schema.optionalKey(Schema.Array(GithubEmulatorFile)),\n nextIssueNumber: Schema.optionalKey(IssueNumber),\n nextCommentId: Schema.optionalKey(CommentId)\n})\n\nexport type GithubEmulatorSeed = typeof GithubEmulatorSeed.Type\n\nconst strict = { onExcessProperty: 'error' } as const\n\nconst decodeSeedInput = Schema.decodeUnknownResult(GithubEmulatorSeed, strict)\n\nconst decodeStateInput = Schema.decodeUnknownResult(GithubEmulatorStateSchema, strict)\n\nconst issueMessage = (issue: Schema.SchemaError['issue']): string =>\n new Schema.SchemaError(issue).message\n\n/** Minted issue `id` values: `3000000000 + number`, as the fixtures write them. */\nexport const mintedIssueIdBase = 3_000_000_000\n\nexport const mintedIssueNodeId = (number: number): string => `I_kwSynthetic${number}`\n\nexport const mintedCommentNodeId = (id: number): string => `IC_kwSynthetic${id}`\n\n// Default entities, copied from the fixtures: the user, the practice repository (the paging\n// fixture's repository id, the contents fixture's branch), the five labels of the paging fixture,\n// the work issue of the label fixture, and the text file of the contents fixture.\n\nconst seededAt = '2026-09-30T12:00:00Z'\n\nconst defaultViewer: GithubEmulatorUser = {\n login: 'yolk-synthetic-bot',\n id: 1000001,\n type: 'User',\n siteAdmin: false\n}\n\nconst defaultRepository: GithubEmulatorRepository = {\n owner: 'yolk-synthetic',\n repo: 'conformance-practice',\n id: 100000001,\n defaultBranch: 'main'\n}\n\nconst label = (id: number, name: string, color: string): GithubEmulatorLabel => ({\n id,\n nodeId: `LA_kwSynthetic${id}`,\n name,\n color,\n default: false,\n description: `Synthetic ${name} label`\n})\n\nconst defaultLabels: ReadonlyArray<GithubEmulatorLabel> = [\n label(700000001, 'bug', 'd73a4a'),\n label(700000002, 'documentation', '0075ca'),\n label(700000003, 'enhancement', 'a2eeef'),\n label(700000004, 'question', 'd876e3'),\n label(700000005, 'synthetic-conformance', 'ededed')\n]\n\nconst workIssue: GithubEmulatorSeedIssue = {\n number: 1,\n id: mintedIssueIdBase + 1,\n nodeId: mintedIssueNodeId(1),\n title: 'Synthetic work issue for conformance runs',\n body: 'Synthetic work issue: conformance cases add and remove a label and a comment here.',\n state: 'open',\n stateReason: null,\n labels: ['bug'],\n user: defaultViewer,\n authorAssociation: 'OWNER',\n createdAt: seededAt,\n updatedAt: seededAt,\n closedAt: null\n}\n\nconst notesFile: GithubEmulatorFile = {\n path: 'docs/synthetic-notes.txt',\n sha: '5f1c0ffee5f1c0ffee5f1c0ffee5f1c0ffee0001',\n text:\n 'Synthetic conformance notes for the practice repository: café, naïve, façade.\\n' +\n 'Second synthetic line.\\n'\n}\n\ntype ProfileParts = {\n readonly viewer: GithubEmulatorUser\n readonly repository: GithubEmulatorRepository\n readonly labels: ReadonlyArray<GithubEmulatorLabel>\n readonly issues: ReadonlyArray<GithubEmulatorSeedIssue>\n readonly files: ReadonlyArray<GithubEmulatorFile>\n readonly counters: typeof Counters.Type\n}\n\nconst profileParts = (profile: GithubEmulatorProfile): ProfileParts =>\n profile === 'default'\n ? {\n viewer: defaultViewer,\n repository: defaultRepository,\n labels: defaultLabels,\n issues: [workIssue],\n files: [notesFile],\n // The lifecycle fixture's created issue and the comment fixture's created comment.\n counters: { nextIssueNumber: 42, nextCommentId: 9000000001 }\n }\n : {\n viewer: defaultViewer,\n repository: defaultRepository,\n labels: [],\n issues: [],\n files: [],\n counters: { nextIssueNumber: 1, nextCommentId: 1 }\n }\n\nconst duplicate = <A>(values: ReadonlyArray<A>): A | undefined =>\n values.find((value, index) => values.indexOf(value) !== index)\n\n/** Integrity problems a decoded seed can still have (duplicates, dangling or minted values). */\nconst seedProblem = (parts: ProfileParts): string | undefined => {\n const duplicates: ReadonlyArray<readonly [string, unknown]> = [\n ['label name', duplicate(parts.labels.map(entry => entry.name))],\n ['label id', duplicate(parts.labels.map(entry => entry.id))],\n ['issue number', duplicate(parts.issues.map(issue => issue.number))],\n ['issue id', duplicate(parts.issues.map(issue => issue.id))],\n // Node ids are global across types (labels and issues alike).\n [\n 'node id',\n duplicate([...parts.labels.map(entry => entry.nodeId), ...parts.issues.map(i => i.nodeId)])\n ],\n ['file path', duplicate(parts.files.map(file => file.path))]\n ]\n\n for (const [kind, value] of duplicates) {\n if (value !== undefined) return `duplicate ${kind} ${String(value)}`\n }\n\n const labelNames = new Set(parts.labels.map(entry => entry.name))\n\n for (const issue of parts.issues) {\n const missing = issue.labels.find(name => !labelNames.has(name))\n\n if (missing !== undefined) return `issue ${issue.number} names missing label ${missing}`\n\n if (duplicate(issue.labels) !== undefined) return `issue ${issue.number} repeats a label`\n\n if (issue.number >= parts.counters.nextIssueNumber) {\n return `nextIssueNumber must lie above seeded issue ${issue.number}`\n }\n\n // Minted ids never collide with seeded ones.\n if (issue.id >= mintedIssueIdBase + parts.counters.nextIssueNumber) {\n return `issue ${issue.number} uses an id in the minted form at or above nextIssueNumber`\n }\n }\n\n // Every minted node id form is reserved across all seeded node ids, of issues and labels alike.\n const reserved = [...parts.labels.map(entry => entry.nodeId), ...parts.issues.map(i => i.nodeId)]\n .map(nodeId => {\n const issueNumber = /^I_kwSynthetic(\\d+)$/.exec(nodeId)?.[1]\n const commentId = /^IC_kwSynthetic(\\d+)$/.exec(nodeId)?.[1]\n\n return (issueNumber !== undefined && Number(issueNumber) >= parts.counters.nextIssueNumber) ||\n (commentId !== undefined && Number(commentId) >= parts.counters.nextCommentId)\n ? nodeId\n : undefined\n })\n .find(nodeId => nodeId !== undefined)\n\n if (reserved !== undefined) {\n return `node id ${reserved} uses a minted form at or above its counter`\n }\n\n const prefix = parts.files.find(file =>\n parts.files.some(other => other.path.startsWith(`${file.path}/`))\n )\n\n return prefix === undefined ? undefined : `file ${prefix.path} is also a folder`\n}\n\n/** Build the emulator state for a decoded seed; a string is an integrity problem. */\nconst stateFromSeed = (seed: GithubEmulatorSeed): GithubEmulatorState | string => {\n const profile = profileParts(seed.profile ?? 'default')\n const issues = seed.issues ?? profile.issues\n const highest = Math.max(0, ...issues.map(issue => issue.number))\n\n const parts: ProfileParts = {\n viewer: seed.viewer ?? profile.viewer,\n repository: seed.repository ?? profile.repository,\n labels: seed.labels ?? profile.labels,\n issues,\n files: seed.files ?? profile.files,\n counters: {\n // Raised above the seeded numbers unless the seed names the counter itself (then checked).\n nextIssueNumber:\n seed.nextIssueNumber ?? Math.max(profile.counters.nextIssueNumber, highest + 1),\n nextCommentId: seed.nextCommentId ?? profile.counters.nextCommentId\n }\n }\n\n const problem = seedProblem(parts)\n\n return (\n problem ?? {\n viewer: parts.viewer,\n repository: parts.repository,\n labels: parts.labels,\n issues: parts.issues.map(issue => ({ ...issue, createdHere: false })),\n comments: [],\n deletedComments: [],\n files: parts.files,\n counters: parts.counters\n }\n )\n}\n\n/** Decode and build a seed; a string is the reason it is invalid. */\nexport const buildSeedState = (input: unknown): GithubEmulatorState | string => {\n const decoded = decodeSeedInput(input)\n\n return Result.isFailure(decoded)\n ? issueMessage(decoded.failure.issue)\n : stateFromSeed(decoded.success)\n}\n\n/** Decode a full state (a restored snapshot); a string is the reason it is invalid. */\nexport const decodeState = (input: unknown): GithubEmulatorState | string => {\n const decoded = decodeStateInput(input)\n\n return Result.isFailure(decoded) ? issueMessage(decoded.failure.issue) : { ...decoded.success }\n}\n"],"mappings":"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"}
|
package/dist/google/state.mjs
CHANGED
|
@@ -31,7 +31,7 @@ import * as Schema from "effect/Schema";
|
|
|
31
31
|
*/
|
|
32
32
|
const JsonObject = Schema.Record(Schema.String, Schema.Json);
|
|
33
33
|
/** A Gmail message, thread, label, or attachment id as the fixtures use them. */
|
|
34
|
-
const GmailId = Schema.String.check(Schema.isPattern(/^[A-Za-z0-9_-]{1,100}$/));
|
|
34
|
+
const GmailId = Schema.String.check(Schema.isPattern(/^[A-Za-z0-9_-]{1,100}$/u));
|
|
35
35
|
const NonNegativeInt = Schema.Int.check(Schema.isGreaterThanOrEqualTo(0));
|
|
36
36
|
/**
|
|
37
37
|
* The practice account's own address: exactly one plain `local@domain` (the shape the send case's
|
|
@@ -39,7 +39,7 @@ const NonNegativeInt = Schema.Int.check(Schema.isGreaterThanOrEqualTo(0));
|
|
|
39
39
|
* the event creator. A seed may set another one, but the send answers only while it is the
|
|
40
40
|
* recorded `practice@example.test` (the recorded `sizeEstimate` of the sent message covers it).
|
|
41
41
|
*/
|
|
42
|
-
const GoogleEmulatorPracticeAddress = Schema.String.check(Schema.isMaxLength(254), Schema.isPattern(/^[A-Za-z0-9_%+-]+(?:\.[A-Za-z0-9_%+-]+)*@[A-Za-z0-9](?:[A-Za-z0-9-]*[A-Za-z0-9])?(?:\.[A-Za-z0-9](?:[A-Za-z0-9-]*[A-Za-z0-9])?)+$/), Schema.makeFilter((value) => value.indexOf("@") <= 64));
|
|
42
|
+
const GoogleEmulatorPracticeAddress = Schema.String.check(Schema.isMaxLength(254), Schema.isPattern(/^[A-Za-z0-9_%+-]+(?:\.[A-Za-z0-9_%+-]+)*@[A-Za-z0-9](?:[A-Za-z0-9-]*[A-Za-z0-9])?(?:\.[A-Za-z0-9](?:[A-Za-z0-9-]*[A-Za-z0-9])?)+$/u), Schema.makeFilter((value) => value.indexOf("@") <= 64));
|
|
43
43
|
/**
|
|
44
44
|
* A stored Gmail message with the renderings a fixture records for it: `minimal` (true when a
|
|
45
45
|
* fixture records `format=minimal`), and the `metadata` and `full` payloads (null when none is
|
|
@@ -98,7 +98,7 @@ const GoogleEmulatorEventBoundary = Schema.Union([Schema.Struct({
|
|
|
98
98
|
/** A stored calendar event with its wire fields (creator and organizer derive from the state). */
|
|
99
99
|
const GoogleEmulatorCalendarEvent = Schema.Struct({
|
|
100
100
|
calendarId: Schema.String,
|
|
101
|
-
id: Schema.String.check(Schema.isPattern(/^[A-Za-z0-9_-]{1,1024}$/)),
|
|
101
|
+
id: Schema.String.check(Schema.isPattern(/^[A-Za-z0-9_-]{1,1024}$/u)),
|
|
102
102
|
etag: Schema.String,
|
|
103
103
|
status: Schema.Literals(["confirmed", "cancelled"]),
|
|
104
104
|
htmlLink: Schema.String,
|
|
@@ -113,7 +113,7 @@ const GoogleEmulatorCalendarEvent = Schema.Struct({
|
|
|
113
113
|
});
|
|
114
114
|
/** A Drive file or folder with the fields the connector's `fields` selection answers. */
|
|
115
115
|
const GoogleEmulatorDriveFile = Schema.Struct({
|
|
116
|
-
id: Schema.String.check(Schema.isPattern(/^[A-Za-z0-9_-]{10,200}$/)),
|
|
116
|
+
id: Schema.String.check(Schema.isPattern(/^[A-Za-z0-9_-]{10,200}$/u)),
|
|
117
117
|
name: Schema.String,
|
|
118
118
|
mimeType: Schema.String,
|
|
119
119
|
starred: Schema.Boolean,
|