@fairfox/polly 0.23.0 → 0.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +55 -1
- package/dist/src/elysia/index.js +2 -2
- package/dist/src/elysia/index.js.map +2 -2
- package/dist/src/elysia/peer-repo-plugin.d.ts +1 -1
- package/dist/src/mesh-node.d.ts +89 -0
- package/dist/src/mesh-node.js +594 -0
- package/dist/src/mesh-node.js.map +14 -0
- package/dist/src/mesh.d.ts +10 -0
- package/dist/src/mesh.js +926 -24
- package/dist/src/mesh.js.map +17 -9
- package/dist/src/peer.d.ts +1 -0
- package/dist/src/peer.js +105 -84
- package/dist/src/peer.js.map +11 -10
- package/dist/src/shared/lib/blob-cache.d.ts +58 -0
- package/dist/src/shared/lib/blob-store-impl.d.ts +33 -0
- package/dist/src/shared/lib/blob-store.d.ts +87 -0
- package/dist/src/shared/lib/blob-transfer.d.ts +58 -0
- package/dist/src/shared/lib/crdt-specialised.d.ts +1 -1
- package/dist/src/shared/lib/crdt-state.d.ts +1 -1
- package/dist/src/shared/lib/keyring-storage.d.ts +57 -0
- package/dist/src/shared/lib/mesh-client.d.ts +91 -0
- package/dist/src/shared/lib/mesh-network-adapter.d.ts +1 -1
- package/dist/src/shared/lib/mesh-signaling-client.d.ts +6 -0
- package/dist/src/shared/lib/mesh-state.d.ts +1 -1
- package/dist/src/shared/lib/mesh-webrtc-adapter.d.ts +20 -1
- package/dist/src/shared/lib/peer-relay-adapter.d.ts +1 -1
- package/dist/src/shared/lib/peer-repo-server.d.ts +1 -1
- package/dist/src/shared/lib/peer-state.d.ts +1 -1
- package/dist/src/shared/lib/wasm-init.d.ts +17 -0
- package/dist/tools/quality/src/cli.js +8 -1
- package/dist/tools/quality/src/cli.js.map +3 -3
- package/dist/tools/quality/src/index.d.ts +25 -0
- package/dist/tools/quality/src/index.js +196 -0
- package/dist/tools/quality/src/index.js.map +10 -0
- package/dist/tools/quality/src/no-as-casting.d.ts +44 -0
- package/package.json +22 -2
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* blob-store-impl — peer-to-peer blob store backed by WebRTC data channels.
|
|
3
|
+
*
|
|
4
|
+
* The store piggybacks on an existing MeshWebRTCAdapter. Blob messages ride
|
|
5
|
+
* on the same data channel as Automerge sync traffic, distinguished by a
|
|
6
|
+
* "blob-" prefix on the message type field.
|
|
7
|
+
*
|
|
8
|
+
* Lifecycle:
|
|
9
|
+
* - put: hash-verify → cache plaintext locally → announce blob-have
|
|
10
|
+
* - get: check cache → if miss, send blob-request(s) → receive chunks
|
|
11
|
+
* → decrypt each chunk as it arrives → reassemble plaintext
|
|
12
|
+
* → hash-verify → cache
|
|
13
|
+
*
|
|
14
|
+
* Encryption model: chunk-then-encrypt. The sender chunks the plaintext
|
|
15
|
+
* into 64 KiB pieces, encrypts each chunk independently under the configured
|
|
16
|
+
* key (with a fresh random nonce per chunk), and sends the sealed envelope.
|
|
17
|
+
* The receiver decrypts each chunk on arrival. Peak sender memory is ~1
|
|
18
|
+
* chunk of ciphertext at a time rather than the entire ciphertext blob.
|
|
19
|
+
*
|
|
20
|
+
* Per-op keys: callers can override the store's default encryption key per
|
|
21
|
+
* put/get via BlobTransferOptions.key. This supports per-document keys
|
|
22
|
+
* without requiring the store to track document ownership.
|
|
23
|
+
*
|
|
24
|
+
* Multi-source fetch: the get() flow requests from a single peer initially,
|
|
25
|
+
* but the re-request timer rotates through peers that have announced
|
|
26
|
+
* availability of the blob, so a transfer that stalls on one peer recovers
|
|
27
|
+
* against another.
|
|
28
|
+
*/
|
|
29
|
+
import type { BlobStore, BlobStoreOptions } from "./blob-store";
|
|
30
|
+
import type { MeshWebRTCAdapter } from "./mesh-webrtc-adapter";
|
|
31
|
+
/** Create a blob store that transfers blobs peer-to-peer over a
|
|
32
|
+
* MeshWebRTCAdapter's data channels. */
|
|
33
|
+
export declare function createBlobStore(adapter: MeshWebRTCAdapter, options?: BlobStoreOptions): BlobStore;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* blob-store — types and interfaces for Polly's content-addressed blob storage.
|
|
3
|
+
*
|
|
4
|
+
* This file contains only type definitions and interfaces. It has no runtime
|
|
5
|
+
* imports from tweetnacl or other crypto libraries, so peer-only consumers
|
|
6
|
+
* tree-shake cleanly when importing from @fairfox/polly/mesh.
|
|
7
|
+
*/
|
|
8
|
+
import type { BlobRef } from "./blob-ref";
|
|
9
|
+
export type BlobProgressPhase = "encrypting" | "uploading" | "downloading" | "decrypting";
|
|
10
|
+
export interface BlobProgressEvent {
|
|
11
|
+
/** Bytes processed so far in the current phase. */
|
|
12
|
+
loaded: number;
|
|
13
|
+
/** Total bytes expected. Undefined when unknown (e.g. start of download). */
|
|
14
|
+
total: number | undefined;
|
|
15
|
+
/** Current operation phase. */
|
|
16
|
+
phase: BlobProgressPhase;
|
|
17
|
+
}
|
|
18
|
+
export type BlobProgressCallback = (event: BlobProgressEvent) => void;
|
|
19
|
+
export interface BlobTransferOptions {
|
|
20
|
+
onProgress?: BlobProgressCallback;
|
|
21
|
+
signal?: AbortSignal;
|
|
22
|
+
/** Override the store's default encryption key for this operation.
|
|
23
|
+
* Useful for per-document keys: pass the document's key here so the
|
|
24
|
+
* blob is encrypted under that key rather than the store default.
|
|
25
|
+
* Ignored if the store has no encryption configured. */
|
|
26
|
+
key?: Uint8Array;
|
|
27
|
+
}
|
|
28
|
+
/** Storage backend for blob bytes. Implementations must store Uint8Array
|
|
29
|
+
* values keyed by SHA-256 hex hash without serialisation loss. */
|
|
30
|
+
export interface BlobCache {
|
|
31
|
+
get(hash: string): Promise<Uint8Array | undefined>;
|
|
32
|
+
put(hash: string, bytes: Uint8Array): Promise<void>;
|
|
33
|
+
has(hash: string): Promise<boolean>;
|
|
34
|
+
delete(hash: string): Promise<void>;
|
|
35
|
+
dispose(): void;
|
|
36
|
+
/** Mark a hash as pinned — it will not be evicted until unpinned.
|
|
37
|
+
* Applications typically pin blobs referenced by documents they
|
|
38
|
+
* currently hold locally. */
|
|
39
|
+
pin(hash: string): Promise<void>;
|
|
40
|
+
/** Remove the pinned mark. The blob becomes eligible for eviction. */
|
|
41
|
+
unpin(hash: string): Promise<void>;
|
|
42
|
+
/** Total bytes stored in the cache. */
|
|
43
|
+
size(): Promise<number>;
|
|
44
|
+
/** Evict unpinned entries in LRU order until total size ≤ maxBytes.
|
|
45
|
+
* Returns the number of bytes freed. */
|
|
46
|
+
evict(maxBytes: number): Promise<number>;
|
|
47
|
+
}
|
|
48
|
+
export interface BlobStore {
|
|
49
|
+
/** Store bytes locally and announce availability to connected peers.
|
|
50
|
+
* Verifies that the hash of `bytes` matches `ref.hash` before storing. */
|
|
51
|
+
put(ref: BlobRef, bytes: Uint8Array, options?: BlobTransferOptions): Promise<void>;
|
|
52
|
+
/** Retrieve bytes by hash. Checks local cache first; if not cached,
|
|
53
|
+
* requests from connected peers. Returns undefined if unavailable. */
|
|
54
|
+
get(hash: string, options?: BlobTransferOptions): Promise<Uint8Array | undefined>;
|
|
55
|
+
/** Return an object URL for rendering (e.g. <img src>). Cached per hash;
|
|
56
|
+
* repeated calls with the same hash return the same URL. Returns
|
|
57
|
+
* undefined if the blob is not in the local cache. All URLs are revoked
|
|
58
|
+
* on dispose(). */
|
|
59
|
+
url(hash: string): Promise<string | undefined>;
|
|
60
|
+
/** Pin a blob so it won't be evicted. Applications should pin blobs
|
|
61
|
+
* referenced by documents they currently hold. */
|
|
62
|
+
pin(hash: string): Promise<void>;
|
|
63
|
+
/** Unpin a blob. Removing a document that references a blob is a
|
|
64
|
+
* natural place to unpin. */
|
|
65
|
+
unpin(hash: string): Promise<void>;
|
|
66
|
+
/** Total bytes stored in the local cache. */
|
|
67
|
+
size(): Promise<number>;
|
|
68
|
+
/** Evict unpinned entries in LRU order until total size ≤ maxBytes.
|
|
69
|
+
* Returns bytes freed. */
|
|
70
|
+
evict(maxBytes: number): Promise<number>;
|
|
71
|
+
/** Revoke all outstanding object URLs and release resources. */
|
|
72
|
+
dispose(): void;
|
|
73
|
+
}
|
|
74
|
+
export interface BlobStoreOptions {
|
|
75
|
+
/** Maximum blob size in bytes. Defaults to 100 MiB. */
|
|
76
|
+
maxBlobSize?: number;
|
|
77
|
+
/** Default encryption config. When configured, blobs are encrypted
|
|
78
|
+
* with this key unless overridden per-op via BlobTransferOptions.key.
|
|
79
|
+
* Omit for plaintext transfer (DTLS only). */
|
|
80
|
+
encrypt?: {
|
|
81
|
+
key: Uint8Array;
|
|
82
|
+
};
|
|
83
|
+
/** Custom cache implementation. Defaults to MemoryBlobCache (suitable
|
|
84
|
+
* for tests and Node). Browser consumers should pass an
|
|
85
|
+
* IndexedDBBlobCache instance. */
|
|
86
|
+
cache?: BlobCache;
|
|
87
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* blob-transfer — chunking, reassembly, and wire format for peer-to-peer
|
|
3
|
+
* blob transfer over WebRTC data channels.
|
|
4
|
+
*
|
|
5
|
+
* Blobs are split into fixed-size chunks (64 KiB) and sent as individual
|
|
6
|
+
* data channel messages. The wire format reuses the same length-prefixed
|
|
7
|
+
* header + binary payload layout that MeshWebRTCAdapter uses for Automerge
|
|
8
|
+
* sync messages. Blob messages are distinguished by a `type` field in the
|
|
9
|
+
* JSON header that starts with "blob-".
|
|
10
|
+
*
|
|
11
|
+
* Three message types:
|
|
12
|
+
* - blob-chunk: a single chunk of a blob transfer
|
|
13
|
+
* - blob-request: ask a peer to send chunks for a specific hash
|
|
14
|
+
* - blob-have: announce local availability of a blob
|
|
15
|
+
*/
|
|
16
|
+
/** Default chunk size in bytes: 64 KiB. Stays well within WebRTC SCTP
|
|
17
|
+
* message size limits (~256 KiB in most browsers). */
|
|
18
|
+
export declare const BLOB_CHUNK_SIZE = 65536;
|
|
19
|
+
/** High-water mark for RTCDataChannel.bufferedAmount. The sender pauses
|
|
20
|
+
* when the buffer exceeds this threshold. */
|
|
21
|
+
export declare const BLOB_BUFFER_HIGH_WATER: number;
|
|
22
|
+
export interface BlobChunkHeader {
|
|
23
|
+
type: "blob-chunk";
|
|
24
|
+
hash: string;
|
|
25
|
+
index: number;
|
|
26
|
+
total: number;
|
|
27
|
+
}
|
|
28
|
+
export interface BlobRequestHeader {
|
|
29
|
+
type: "blob-request";
|
|
30
|
+
hash: string;
|
|
31
|
+
/** Chunk indices needed. Omit to request all chunks. */
|
|
32
|
+
missing?: number[];
|
|
33
|
+
}
|
|
34
|
+
export interface BlobHaveHeader {
|
|
35
|
+
type: "blob-have";
|
|
36
|
+
hash: string;
|
|
37
|
+
}
|
|
38
|
+
export type BlobMessageHeader = BlobChunkHeader | BlobRequestHeader | BlobHaveHeader;
|
|
39
|
+
/** Split bytes into fixed-size chunks. The last chunk may be smaller. */
|
|
40
|
+
export declare function chunkBlob(bytes: Uint8Array, chunkSize?: number): Uint8Array[];
|
|
41
|
+
/** Reassemble chunks into a single Uint8Array. Throws if any chunk index
|
|
42
|
+
* in [0, total) is missing from the map. */
|
|
43
|
+
export declare function reassembleChunks(chunks: Map<number, Uint8Array>, total: number): Uint8Array;
|
|
44
|
+
/** Return the indices of chunks missing from a partial chunk map. */
|
|
45
|
+
export declare function missingChunkIndices(chunks: Map<number, Uint8Array>, total: number): number[];
|
|
46
|
+
/** Serialise a blob message into the shared wire format:
|
|
47
|
+
* [4-byte BE header length][JSON header bytes][binary payload].
|
|
48
|
+
* Returns an ArrayBuffer-backed Uint8Array usable by RTCDataChannel.send. */
|
|
49
|
+
export declare function serialiseBlobMessage(header: BlobMessageHeader, data?: Uint8Array): Uint8Array<ArrayBuffer>;
|
|
50
|
+
/** Peek at the header of a wire-format message without full
|
|
51
|
+
* deserialisation. Returns the parsed header and the data slice,
|
|
52
|
+
* or undefined if the bytes are too short or malformed. */
|
|
53
|
+
export declare function parseBlobMessage(bytes: Uint8Array): {
|
|
54
|
+
header: BlobMessageHeader;
|
|
55
|
+
data: Uint8Array;
|
|
56
|
+
} | undefined;
|
|
57
|
+
/** Check whether a wire-format message header type starts with "blob-". */
|
|
58
|
+
export declare function isBlobMessageType(bytes: Uint8Array): boolean;
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
* and the {@link MigratableState} interface. Variants differ only in the
|
|
34
34
|
* `extractValue` and `applyWrite` hooks they pass.
|
|
35
35
|
*/
|
|
36
|
-
import { Counter, type DocHandle } from "@automerge/automerge-repo";
|
|
36
|
+
import { Counter, type DocHandle } from "@automerge/automerge-repo/slim";
|
|
37
37
|
import type { Access } from "./access";
|
|
38
38
|
import { type MigratableState } from "./migrate-primitive";
|
|
39
39
|
import { type PrimitiveKind } from "./primitive-registry";
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
* counters, and lists (which require type-specific operation capture to
|
|
23
23
|
* preserve concurrent-edit semantics) land in Phase 1's crdt-specialised.ts.
|
|
24
24
|
*/
|
|
25
|
-
import type { DocHandle } from "@automerge/automerge-repo";
|
|
25
|
+
import type { DocHandle } from "@automerge/automerge-repo/slim";
|
|
26
26
|
import type { Access } from "./access";
|
|
27
27
|
import { type MigratableState } from "./migrate-primitive";
|
|
28
28
|
import { type PrimitiveKind } from "./primitive-registry";
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* keyring-storage — persistence abstraction for {@link MeshKeyring}.
|
|
3
|
+
*
|
|
4
|
+
* The keyring itself is a plain structural object of `Map`s, `Set`s, and a
|
|
5
|
+
* signing keypair; it is deliberately not coupled to any persistence layer.
|
|
6
|
+
* This module defines a storage interface that applications implement once
|
|
7
|
+
* for their runtime (IndexedDB, the filesystem, a keychain, a secret
|
|
8
|
+
* manager, whatever) and wire into {@link createMeshClient} via its
|
|
9
|
+
* `keyring.storage` option.
|
|
10
|
+
*
|
|
11
|
+
* A canonical JSON-with-base64 serialisation is provided by
|
|
12
|
+
* {@link serialiseKeyring} and {@link deserialiseKeyring}. It is inspectable
|
|
13
|
+
* by humans, survives manual edits, and round-trips every field of the
|
|
14
|
+
* keyring. Storage implementations that write plain strings (files,
|
|
15
|
+
* localStorage, `kv` stores) can lean on these helpers; storage
|
|
16
|
+
* implementations that persist structured data (IndexedDB, a keychain API)
|
|
17
|
+
* can serialise differently if they prefer.
|
|
18
|
+
*/
|
|
19
|
+
import type { MeshKeyring } from "./mesh-network-adapter";
|
|
20
|
+
/**
|
|
21
|
+
* A load/save pair for a single {@link MeshKeyring}. Implementations are
|
|
22
|
+
* free to choose where and how the keyring is stored; the factory only
|
|
23
|
+
* cares that `load()` returns the previously-saved keyring or `null`, and
|
|
24
|
+
* that `save(keyring)` durably persists it.
|
|
25
|
+
*/
|
|
26
|
+
export interface KeyringStorage {
|
|
27
|
+
/** Load the previously-saved keyring, or return `null` if none exists.
|
|
28
|
+
* Implementations may throw for truly exceptional conditions (disk
|
|
29
|
+
* errors, permission failures); a missing keyring is not exceptional. */
|
|
30
|
+
load(): Promise<MeshKeyring | null>;
|
|
31
|
+
/** Durably persist the keyring. Implementations should atomically replace
|
|
32
|
+
* any existing stored value; partial writes must not leave the store in
|
|
33
|
+
* an inconsistent state. */
|
|
34
|
+
save(keyring: MeshKeyring): Promise<void>;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* In-memory storage. Useful for tests, ephemeral tools, and the first-run
|
|
38
|
+
* bootstrap path where the keyring only lives for the duration of the
|
|
39
|
+
* process. Calling `save` holds the keyring in a closed-over variable;
|
|
40
|
+
* `load` returns it on subsequent calls within the same process.
|
|
41
|
+
*/
|
|
42
|
+
export declare function memoryKeyringStorage(): KeyringStorage;
|
|
43
|
+
/**
|
|
44
|
+
* Encode a {@link MeshKeyring} to a canonical JSON string. Every
|
|
45
|
+
* `Uint8Array` field (identity keys, public keys, document keys) is
|
|
46
|
+
* base64-encoded; `Map`s and `Set`s become plain objects and arrays. The
|
|
47
|
+
* output is pretty-printed so a human operator can eyeball or hand-edit
|
|
48
|
+
* the file on disk.
|
|
49
|
+
*/
|
|
50
|
+
export declare function serialiseKeyring(keyring: MeshKeyring): string;
|
|
51
|
+
/**
|
|
52
|
+
* Decode a keyring from the format produced by {@link serialiseKeyring}.
|
|
53
|
+
* Throws with a descriptive message when the input is malformed, so
|
|
54
|
+
* corrupt storage surfaces as an actionable error rather than a silent
|
|
55
|
+
* downgrade.
|
|
56
|
+
*/
|
|
57
|
+
export declare function deserialiseKeyring(text: string): MeshKeyring;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* mesh-client — first-class factory for constructing a Polly mesh client.
|
|
3
|
+
*
|
|
4
|
+
* The mesh transport stack has five pieces that have to be wired together:
|
|
5
|
+
* a {@link MeshSignalingClient} talking to the relay server, a
|
|
6
|
+
* {@link MeshWebRTCAdapter} that owns the per-peer RTCPeerConnections, a
|
|
7
|
+
* {@link MeshNetworkAdapter} that signs and encrypts every message on the
|
|
8
|
+
* way through, an Automerge `Repo` that drives sync, and a `MeshKeyring`
|
|
9
|
+
* that holds the crypto material. Prior to this module, every consuming
|
|
10
|
+
* application had to assemble the five pieces by hand — and in Node or
|
|
11
|
+
* Bun had to monkey-patch `globalThis.WebSocket` / `globalThis.RTCPeerConnection`
|
|
12
|
+
* because the lower-level primitives reached for those globals.
|
|
13
|
+
*
|
|
14
|
+
* `createMeshClient` takes options, hands back a `MeshClient`, and also
|
|
15
|
+
* calls `configureMeshState(client.repo)` so `$meshState` works without
|
|
16
|
+
* a second setup step. The WebSocket and RTCPeerConnection implementations
|
|
17
|
+
* are injectable; defaults read from `globalThis` for browser ergonomics.
|
|
18
|
+
* The companion `@fairfox/polly/mesh/node` subpath provides a CLI bootstrap
|
|
19
|
+
* helper that wires werift (or `@roamhq/wrtc` if the consumer prefers) and
|
|
20
|
+
* a file-backed keyring store.
|
|
21
|
+
*/
|
|
22
|
+
import { Repo, type StorageAdapterInterface } from "@automerge/automerge-repo/slim";
|
|
23
|
+
import type { KeyringStorage } from "./keyring-storage";
|
|
24
|
+
import { type MeshKeyring, MeshNetworkAdapter } from "./mesh-network-adapter";
|
|
25
|
+
import { MeshSignalingClient, type MeshSignalingClientOptions } from "./mesh-signaling-client";
|
|
26
|
+
import { MeshWebRTCAdapter, type MeshWebRTCAdapterOptions } from "./mesh-webrtc-adapter";
|
|
27
|
+
/** Options for {@link createMeshClient}. */
|
|
28
|
+
export interface CreateMeshClientOptions {
|
|
29
|
+
/** Signalling-server configuration. `peerId` must be the same identity
|
|
30
|
+
* this client's keyring was paired with on other peers. */
|
|
31
|
+
signaling: {
|
|
32
|
+
url: string;
|
|
33
|
+
peerId: string;
|
|
34
|
+
/** Optional WebSocket ctor override (e.g. `ws` on old Node). Defaults
|
|
35
|
+
* to `globalThis.WebSocket`. */
|
|
36
|
+
WebSocket?: MeshSignalingClientOptions["WebSocket"];
|
|
37
|
+
/** Forwarded error callback for diagnostic UI. */
|
|
38
|
+
onError?: MeshSignalingClientOptions["onError"];
|
|
39
|
+
};
|
|
40
|
+
/** WebRTC configuration. On browsers the defaults are fine; in Node or
|
|
41
|
+
* Bun pass the `RTCPeerConnection` ctor from `werift` or `@roamhq/wrtc`. */
|
|
42
|
+
rtc?: {
|
|
43
|
+
RTCPeerConnection?: MeshWebRTCAdapterOptions["RTCPeerConnection"];
|
|
44
|
+
iceServers?: RTCIceServer[];
|
|
45
|
+
dataChannelLabel?: string;
|
|
46
|
+
};
|
|
47
|
+
/** The local peer's keyring — either a fully-constructed instance, or a
|
|
48
|
+
* persistence adapter to load one from. When a storage adapter is given
|
|
49
|
+
* and `storage.load()` resolves to `null`, the factory throws with a
|
|
50
|
+
* message pointing at the bootstrap helper in `@fairfox/polly/mesh/node`;
|
|
51
|
+
* we deliberately do not generate an identity silently. */
|
|
52
|
+
keyring: MeshKeyring | {
|
|
53
|
+
storage: KeyringStorage;
|
|
54
|
+
};
|
|
55
|
+
/** Optional Automerge-Repo storage adapter. Applications that want
|
|
56
|
+
* durable local state pass an IndexedDB adapter in browsers or a
|
|
57
|
+
* filesystem adapter in Node; omitting it keeps the Repo in-memory. */
|
|
58
|
+
repoStorage?: StorageAdapterInterface;
|
|
59
|
+
/** When `false`, signs but does not encrypt. Defaults to `true` — the
|
|
60
|
+
* full $meshState posture where the server is off the data path. */
|
|
61
|
+
encryptionEnabled?: boolean;
|
|
62
|
+
}
|
|
63
|
+
/** Handle returned by {@link createMeshClient}. */
|
|
64
|
+
export interface MeshClient {
|
|
65
|
+
/** The Automerge Repo. `$meshState` has already been configured against
|
|
66
|
+
* this repo, so primitives just work — but the repo is exposed in case
|
|
67
|
+
* the application needs it directly (server-side cron, bulk exports,
|
|
68
|
+
* migration tools). */
|
|
69
|
+
repo: Repo;
|
|
70
|
+
/** The configured keyring. Exposed so the application can inspect or
|
|
71
|
+
* mutate it (add authorised peers, apply revocations) and then
|
|
72
|
+
* re-persist via its storage. */
|
|
73
|
+
keyring: MeshKeyring;
|
|
74
|
+
/** The signalling client. Exposed for applications that need to hook
|
|
75
|
+
* lifecycle events or send custom signalling payloads. */
|
|
76
|
+
signaling: MeshSignalingClient;
|
|
77
|
+
/** The WebRTC network adapter. Exposed for advanced use (blob store
|
|
78
|
+
* wiring, peer-connection introspection). */
|
|
79
|
+
networkAdapter: MeshNetworkAdapter;
|
|
80
|
+
/** The underlying WebRTC adapter wrapped by {@link networkAdapter}. */
|
|
81
|
+
webrtcAdapter: MeshWebRTCAdapter;
|
|
82
|
+
/** Close the signalling WebSocket, tear down every RTCPeerConnection,
|
|
83
|
+
* and shut the Repo cleanly. Idempotent. */
|
|
84
|
+
close(): Promise<void>;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Construct a fully-wired mesh client. Resolves once the signalling
|
|
88
|
+
* connection is open and the Repo is ready to mutate documents; WebRTC
|
|
89
|
+
* peer connections negotiate asynchronously in the background.
|
|
90
|
+
*/
|
|
91
|
+
export declare function createMeshClient(options: CreateMeshClientOptions): Promise<MeshClient>;
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
* threading the document context through the network subsystem (which
|
|
40
40
|
* needs upstream support). A follow-up will address this.
|
|
41
41
|
*/
|
|
42
|
-
import { type Message, NetworkAdapter, type PeerId, type PeerMetadata } from "@automerge/automerge-repo";
|
|
42
|
+
import { type Message, NetworkAdapter, type PeerId, type PeerMetadata } from "@automerge/automerge-repo/slim";
|
|
43
43
|
import { type SigningKeyPair } from "./signing";
|
|
44
44
|
/** The well-known document id used for the Phase 2 first-cut single-key
|
|
45
45
|
* encryption mode. See the file-level comment for the per-document key
|
|
@@ -41,6 +41,11 @@ export interface MeshSignalingClientOptions {
|
|
|
41
41
|
/** Optional callback for the open and close lifecycle events. */
|
|
42
42
|
onOpen?: () => void;
|
|
43
43
|
onClose?: () => void;
|
|
44
|
+
/** WebSocket constructor. Defaults to `globalThis.WebSocket`. Inject a
|
|
45
|
+
* different implementation (e.g. `ws` package's `WebSocket`) when running
|
|
46
|
+
* in an environment without a native WebSocket global, or to use a custom
|
|
47
|
+
* subclass for tests or instrumentation. */
|
|
48
|
+
WebSocket?: typeof WebSocket;
|
|
44
49
|
}
|
|
45
50
|
/**
|
|
46
51
|
* Thin wrapper around a WebSocket connection to a Polly signalling server.
|
|
@@ -61,6 +66,7 @@ export declare class MeshSignalingClient {
|
|
|
61
66
|
private readonly onClose?;
|
|
62
67
|
private socket;
|
|
63
68
|
private joined;
|
|
69
|
+
private readonly WebSocketCtor;
|
|
64
70
|
constructor(options: MeshSignalingClientOptions);
|
|
65
71
|
/**
|
|
66
72
|
* Open the WebSocket and send the join message. Resolves once the
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
* RTCDataChannel; for tests and for the early Phase 2 cut, an in-memory
|
|
33
33
|
* loopback adapter pair satisfies the same contract.
|
|
34
34
|
*/
|
|
35
|
-
import type { Repo } from "@automerge/automerge-repo";
|
|
35
|
+
import type { Repo } from "@automerge/automerge-repo/slim";
|
|
36
36
|
import type { Access } from "./access";
|
|
37
37
|
import { type SpecialisedPrimitive } from "./crdt-specialised";
|
|
38
38
|
import { type CrdtPrimitive } from "./crdt-state";
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
* - Disconnect tears down every peer connection and closes the
|
|
45
45
|
* signalling client.
|
|
46
46
|
*/
|
|
47
|
-
import { type Message, NetworkAdapter, type PeerId, type PeerMetadata } from "@automerge/automerge-repo";
|
|
47
|
+
import { type Message, NetworkAdapter, type PeerId, type PeerMetadata } from "@automerge/automerge-repo/slim";
|
|
48
48
|
import type { MeshSignalingClient } from "./mesh-signaling-client";
|
|
49
49
|
/** Standard STUN servers for NAT traversal. In production, callers who
|
|
50
50
|
* need TURN fallback for peers behind symmetric NATs should replace this
|
|
@@ -72,6 +72,11 @@ export interface MeshWebRTCAdapterOptions {
|
|
|
72
72
|
* that share a signalling server between multiple meshes may want
|
|
73
73
|
* distinct labels per mesh. */
|
|
74
74
|
dataChannelLabel?: string;
|
|
75
|
+
/** RTCPeerConnection constructor. Defaults to
|
|
76
|
+
* `globalThis.RTCPeerConnection`. Inject a different implementation
|
|
77
|
+
* (e.g. `werift` or `@roamhq/wrtc`) when running outside a browser, or
|
|
78
|
+
* to use a custom subclass for tests or instrumentation. */
|
|
79
|
+
RTCPeerConnection?: typeof RTCPeerConnection;
|
|
75
80
|
}
|
|
76
81
|
/**
|
|
77
82
|
* Automerge-Repo NetworkAdapter backed by real WebRTC data channels.
|
|
@@ -83,9 +88,14 @@ export declare class MeshWebRTCAdapter extends NetworkAdapter {
|
|
|
83
88
|
readonly iceServers: RTCIceServer[];
|
|
84
89
|
readonly dataChannelLabel: string;
|
|
85
90
|
readonly knownPeerIds: string[];
|
|
91
|
+
private readonly RTCPeerConnectionCtor;
|
|
86
92
|
private readonly slots;
|
|
87
93
|
private ready;
|
|
88
94
|
private readyResolver;
|
|
95
|
+
/** Callback for incoming blob messages. Set by the blob store.
|
|
96
|
+
* Called with the sender's peer ID, the raw header object, and the
|
|
97
|
+
* binary payload (chunk data). */
|
|
98
|
+
onBlobMessage?: (peerId: string, header: Record<string, unknown>, data: Uint8Array) => void;
|
|
89
99
|
constructor(options: MeshWebRTCAdapterOptions);
|
|
90
100
|
isReady(): boolean;
|
|
91
101
|
whenReady(): Promise<void>;
|
|
@@ -120,6 +130,15 @@ export declare class MeshWebRTCAdapter extends NetworkAdapter {
|
|
|
120
130
|
private wireConnection;
|
|
121
131
|
private wireDataChannel;
|
|
122
132
|
private dispatchMessage;
|
|
133
|
+
/** Peer IDs with an open data channel, suitable for blob requests. */
|
|
134
|
+
get connectedPeerIds(): string[];
|
|
135
|
+
/** Send a pre-serialised blob message to a specific peer. Returns false
|
|
136
|
+
* if the peer is not connected or the send buffer is above the high-water
|
|
137
|
+
* mark (caller should retry after a delay). */
|
|
138
|
+
sendBlobMessage(peerId: string, bytes: Uint8Array<ArrayBuffer>): boolean;
|
|
139
|
+
/** Send bytes on a data channel if the buffer is below the high-water
|
|
140
|
+
* mark. Returns true if sent, false if backpressure applies. */
|
|
141
|
+
private trySendOnChannel;
|
|
123
142
|
/** Pack an Automerge Message into binary for transmission over the
|
|
124
143
|
* data channel. The format mirrors MeshNetworkAdapter's internal
|
|
125
144
|
* serialisation: a length-prefixed JSON header followed by the raw
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
* // connectionState is a Signal<"connecting" | "connected" | "disconnected">
|
|
26
26
|
* ```
|
|
27
27
|
*/
|
|
28
|
-
import { Repo } from "@automerge/automerge-repo";
|
|
28
|
+
import { Repo } from "@automerge/automerge-repo/slim";
|
|
29
29
|
import { WebSocketClientAdapter } from "@automerge/automerge-repo-network-websocket";
|
|
30
30
|
import { type Signal } from "@preact/signals";
|
|
31
31
|
import { type MeshKeyring } from "./mesh-network-adapter";
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
* await server.close();
|
|
35
35
|
* ```
|
|
36
36
|
*/
|
|
37
|
-
import { Repo } from "@automerge/automerge-repo";
|
|
37
|
+
import { Repo } from "@automerge/automerge-repo/slim";
|
|
38
38
|
import { WebSocketServerAdapter } from "@automerge/automerge-repo-network-websocket";
|
|
39
39
|
import { NodeFSStorageAdapter } from "@automerge/automerge-repo-storage-nodefs";
|
|
40
40
|
import * as ws from "ws";
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
* via the same configuration path; Phase 2's mesh adapter does the same for
|
|
34
34
|
* $meshState.
|
|
35
35
|
*/
|
|
36
|
-
import type { Repo } from "@automerge/automerge-repo";
|
|
36
|
+
import type { Repo } from "@automerge/automerge-repo/slim";
|
|
37
37
|
import type { Access } from "./access";
|
|
38
38
|
import { type SpecialisedPrimitive } from "./crdt-specialised";
|
|
39
39
|
import { type CrdtPrimitive } from "./crdt-state";
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Eagerly initialise Automerge's WebAssembly runtime by pointing the library
|
|
3
|
+
* at the raw `.wasm` file shipped in `@automerge/automerge`.
|
|
4
|
+
*
|
|
5
|
+
* The `import.meta.url` pattern below is recognised by every modern bundler
|
|
6
|
+
* (Bun, Vite, webpack 5+, esbuild) as an asset reference: the `.wasm` gets
|
|
7
|
+
* copied into the consumer's output alongside the JavaScript, and the URL is
|
|
8
|
+
* rewritten to point at the copied file. `initializeWasm` then fetches and
|
|
9
|
+
* instantiates it at runtime. That keeps the WebAssembly out of the JavaScript
|
|
10
|
+
* bundle entirely — no megabyte-scale base64 string inlined per subpath — and
|
|
11
|
+
* lets the browser parse the JS and stream-compile the `.wasm` in parallel.
|
|
12
|
+
*
|
|
13
|
+
* The top-level `await` makes this module async so any consumer that
|
|
14
|
+
* transitively imports it inherits the "wait for WASM" behaviour through the
|
|
15
|
+
* import graph, without needing an explicit init call.
|
|
16
|
+
*/
|
|
17
|
+
export {};
|
|
@@ -134,8 +134,15 @@ function findViolations(relative, content) {
|
|
|
134
134
|
const results = [];
|
|
135
135
|
const lines = content.split(`
|
|
136
136
|
`);
|
|
137
|
+
let insideTemplate = false;
|
|
137
138
|
for (let i = 0;i < lines.length; i++) {
|
|
138
139
|
const line = lines[i] ?? "";
|
|
140
|
+
const backticks = (line.match(/`/g) ?? []).length;
|
|
141
|
+
const startedInTemplate = insideTemplate;
|
|
142
|
+
if (backticks % 2 === 1)
|
|
143
|
+
insideTemplate = !insideTemplate;
|
|
144
|
+
if (startedInTemplate && backticks === 0 && !line.includes("${"))
|
|
145
|
+
continue;
|
|
139
146
|
if (!isLineClean(line)) {
|
|
140
147
|
results.push({
|
|
141
148
|
file: relative,
|
|
@@ -203,4 +210,4 @@ var result = await checkNoAsCasting({
|
|
|
203
210
|
result.print();
|
|
204
211
|
process.exit(result.violations.length > 0 ? 1 : 0);
|
|
205
212
|
|
|
206
|
-
//# debugId=
|
|
213
|
+
//# debugId=D4F70D746137C24364756E2164756E21
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../tools/quality/src/no-as-casting.ts", "../tools/quality/src/cli.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"/**\n * No-as-casting conformance check.\n *\n * Bans all TypeScript type assertions (`as Type`) except the allowed\n * patterns: `as const` (literal narrowing), `as unknown as` (explicit\n * escape hatch), import/export renames, and `as` inside strings or\n * comments. Violations include pattern-specific fix advice.\n *\n * This module exports the check logic as a library so consuming\n * applications can import it from `@fairfox/polly/quality` and run it\n * programmatically. Polly's own `scripts/check-no-as-casting.ts` is a\n * thin CLI wrapper around these exports.\n */\n\nimport { readFileSync } from \"node:fs\";\nimport { Glob } from \"bun\";\n\nexport interface Violation {\n file: string;\n line: number;\n content: string;\n advice?: string;\n}\n\nexport interface CheckResult {\n violations: Violation[];\n print: () => void;\n}\n\nexport interface CheckOptions {\n rootDir: string;\n exclude?: string[];\n excludePackages?: string[];\n excludeFiles?: string[];\n filePatterns?: string;\n}\n\n/**\n * Check whether a line contains a forbidden `as` type assertion.\n * Returns true if the line is clean (no violation), false if it violates.\n */\n// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: Source-text scanning with many skip rules is inherently branchy.\nexport function isLineClean(line: string): boolean {\n if (!line.includes(\" as \")) return true;\n\n const trimmed = line.trim();\n\n // Full-line comments\n if (trimmed.startsWith(\"//\") || trimmed.startsWith(\"*\") || trimmed.startsWith(\"/*\")) {\n return true;\n }\n\n // as const (literal narrowing)\n if (line.match(/\\bas const\\b/)) {\n const withoutAsConst = line.replace(/\\bas const\\b/g, \"\");\n if (!withoutAsConst.includes(\" as \")) return true;\n }\n\n // as unknown as (explicit escape hatch)\n if (line.includes(\" as unknown as \") || line.trimEnd().endsWith(\"as unknown as\")) {\n const withoutEscapeHatch = line.replace(/\\bas unknown as\\b/g, \"\");\n if (!withoutEscapeHatch.includes(\" as \")) return true;\n }\n\n // Import/export renames\n if (\n line.match(/\\b(import|export)\\s+.*\\s+as\\s+\\w+/) ||\n line.match(/\\b(import|export)\\s+\\*\\s+as\\s+\\w+/) ||\n line.match(/\\b(import|export)\\s+type\\s+.*\\s+as\\s+\\w+/) ||\n line.match(/^\\s*\\w+\\s+as\\s+\\w+,?\\s*$/) ||\n line.match(/^\\s*type\\s+\\w+\\s+as\\s+\\w+,?\\s*$/)\n ) {\n return true;\n }\n\n // Property declarations: as= or as: or as,\n if (line.match(/\\bas\\s*[=:,]/)) return true;\n\n // String literal detection: count quotes before each ` as ` occurrence.\n // If any quote type has an odd count, the ` as ` is inside a string.\n if (everyAsInsideString(line)) return true;\n\n // JSX text: ` as ` between > and < with no code syntax around it\n if (isJsxText(trimmed)) return true;\n\n // Plain text heuristic: indented line with no code syntax characters\n // before ` as ` — catches multiline JSX text and template literal bodies.\n if (isPlainText(trimmed)) return true;\n\n // Inline comment: ` as ` appears only after //\n const commentIdx = line.indexOf(\"//\");\n if (commentIdx >= 0 && line.indexOf(\" as \", commentIdx) >= 0) {\n const beforeComment = line.substring(0, commentIdx);\n if (!beforeComment.includes(\" as \")) return true;\n }\n\n // SQL alias: `) as column_name`\n if (line.match(/\"\\)\\s+as\\s+\\w+\"/)) return true;\n\n if (line.includes(\" satisfies \")) return true;\n\n return false;\n}\n\n/**\n * Returns true when every ` as ` occurrence in the line falls inside a\n * string literal (single-quoted, double-quoted, or backtick).\n */\nfunction everyAsInsideString(line: string): boolean {\n let searchFrom = 0;\n while (true) {\n const idx = line.indexOf(\" as \", searchFrom);\n if (idx < 0) return true; // no more ` as ` to check\n const before = line.substring(0, idx);\n const singleQuotes = (before.match(/'/g) ?? []).length;\n const doubleQuotes = (before.match(/\"/g) ?? []).length;\n const backticks = (before.match(/`/g) ?? []).length;\n if (singleQuotes % 2 === 0 && doubleQuotes % 2 === 0 && backticks % 2 === 0) {\n return false; // this ` as ` is outside any string\n }\n searchFrom = idx + 4;\n }\n}\n\n/**\n * Detects JSX text content — ` as ` appearing between > and < with no\n * code-like syntax around it (no braces, semicolons, equals signs).\n */\nfunction isJsxText(trimmed: string): boolean {\n // Classic JSX text: starts after > or is plain text ending before <\n if (trimmed.match(/^[^{};=()]*\\bas\\b[^{};=()]*$/)) {\n // No code syntax at all — could be JSX text or template literal body.\n // Reject if it looks like a type assertion (word ` as ` TypeName pattern\n // where TypeName starts with uppercase, or is a known TS type).\n if (\n !trimmed.match(/\\bas\\s+[A-Z]\\w*/) &&\n !trimmed.match(/\\bas\\s+(string|number|boolean|any|unknown|never)\\b/)\n ) {\n return true;\n }\n }\n return false;\n}\n\n/**\n * Heuristic for plain text in template literals or JSX: the line has no\n * code-like characters before ` as ` — no `=`, `{`, `}`, `:`, `;`, `(`.\n */\nfunction isPlainText(trimmed: string): boolean {\n const idx = trimmed.indexOf(\" as \");\n if (idx < 0) return false;\n const before = trimmed.substring(0, idx);\n // If nothing before ` as ` looks like code, it's probably prose.\n return (\n !before.match(/[={}:;(]/) &&\n !before.match(/\\b(const|let|var|type|interface|function|return|await)\\b/)\n );\n}\n\n/**\n * Suggest a concrete fix for a specific violation pattern.\n */\n// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: Pattern-matching advice is a linear chain of if-returns.\nexport function suggestFix(line: string): string | undefined {\n if (line.includes(\"JSON.parse\")) {\n return \"Use a validation function or type guard to parse and validate the result.\";\n }\n if (\n line.includes(\"as HTMLInputElement\") ||\n line.includes(\"as HTMLTextAreaElement\") ||\n line.includes(\"as HTMLButtonElement\")\n ) {\n return \"Use instanceof: if (el instanceof HTMLInputElement) { el.value ... }\";\n }\n if (line.includes(\"as HTMLElement\") || line.includes(\"as Element\")) {\n return \"Use instanceof: if (el instanceof HTMLElement) { ... }\";\n }\n if (line.includes(\".doc()\") && line.includes(\"as \")) {\n return \"Type the DocHandle generic: repo.find<MyType>(id) returns DocHandle<MyType>.\";\n }\n if (\n line.includes(\"Record<string, unknown>\") &&\n (line.includes(\"window\") || line.includes(\"globalThis\"))\n ) {\n return \"Extract a type guard: function getGlobalProp(name: string): unknown { ... }\";\n }\n if (line.includes(\"Record<string, unknown>\")) {\n return \"Use a type guard function that narrows the unknown value to the target shape.\";\n }\n if (line.includes(\"as PeerId\") || line.includes(\"as DocumentId\")) {\n return \"Use the library's branded-type constructor if available, or centralise the cast in a factory.\";\n }\n if (line.includes(\"as string\") || line.includes(\"as number\") || line.includes(\"as boolean\")) {\n return \"Narrow with typeof: if (typeof x === 'string') { ... }\";\n }\n if (line.includes(\"as any\")) {\n return \"Replace 'any' with 'unknown' and add a type guard or validation at the boundary.\";\n }\n return undefined;\n}\n\nfunction isFileExcluded(\n relative: string,\n excludeDirs: Set<string>,\n excludePackages: Set<string>,\n excludeFiles: Set<string>\n): boolean {\n const segments = relative.split(\"/\");\n if (segments.some((s) => excludeDirs.has(s))) return true;\n if (excludePackages.size > 0 && segments.some((s) => excludePackages.has(s))) return true;\n const basename = segments[segments.length - 1] ?? \"\";\n return excludeFiles.has(basename) || excludeFiles.has(relative);\n}\n\nfunction findViolations(relative: string, content: string): Violation[] {\n const results: Violation[] = [];\n const lines = content.split(\"\\n\");\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i] ?? \"\";\n if (!isLineClean(line)) {\n results.push({\n file: relative,\n line: i + 1,\n content: line.trim(),\n advice: suggestFix(line.trim()),\n });\n }\n }\n return results;\n}\n\nfunction printViolations(violations: Violation[]): void {\n if (violations.length === 0) {\n console.log(\"[no-as-casting] ✅ No violations found.\");\n return;\n }\n console.log(`[no-as-casting] ❌ ${violations.length} violation(s) found:\\n`);\n for (const v of violations) {\n console.log(` ${v.file}:${v.line}`);\n console.log(` ${v.content}`);\n if (v.advice) console.log(` 💡 ${v.advice}`);\n console.log();\n }\n console.log(\"[no-as-casting] Use type guards, validation, or fix the types at the source.\");\n console.log('[no-as-casting] Only \"as const\" and \"as unknown as\" are allowed.');\n}\n\n/**\n * Run the no-as-casting check against a directory. Returns a result\n * object with the violations and a print function for CLI output.\n */\nexport async function checkNoAsCasting(options: CheckOptions): Promise<CheckResult> {\n const rootDir = options.rootDir;\n const excludeDirs = new Set(options.exclude ?? [\"node_modules\", \"dist\", \".git\", \".bun\"]);\n const excludePackages = new Set(options.excludePackages ?? []);\n const excludeFiles = new Set(options.excludeFiles ?? []);\n const pattern = options.filePatterns ?? \"**/*.{ts,tsx}\";\n const glob = new Glob(pattern);\n const violations: Violation[] = [];\n\n for await (const file of glob.scan({ cwd: rootDir, absolute: true })) {\n const relative = file.replace(`${rootDir}/`, \"\");\n if (isFileExcluded(relative, excludeDirs, excludePackages, excludeFiles)) continue;\n const content = readFileSync(file, \"utf-8\");\n violations.push(...findViolations(relative, content));\n }\n\n return { violations, print: () => printViolations(violations) };\n}\n",
|
|
5
|
+
"/**\n * No-as-casting conformance check.\n *\n * Bans all TypeScript type assertions (`as Type`) except the allowed\n * patterns: `as const` (literal narrowing), `as unknown as` (explicit\n * escape hatch), import/export renames, and `as` inside strings or\n * comments. Violations include pattern-specific fix advice.\n *\n * This module exports the check logic as a library so consuming\n * applications can import it from `@fairfox/polly/quality` and run it\n * programmatically. Polly's own `scripts/check-no-as-casting.ts` is a\n * thin CLI wrapper around these exports.\n */\n\nimport { readFileSync } from \"node:fs\";\nimport { Glob } from \"bun\";\n\nexport interface Violation {\n file: string;\n line: number;\n content: string;\n advice?: string;\n}\n\nexport interface CheckResult {\n violations: Violation[];\n print: () => void;\n}\n\nexport interface CheckOptions {\n rootDir: string;\n exclude?: string[];\n excludePackages?: string[];\n excludeFiles?: string[];\n filePatterns?: string;\n}\n\n/**\n * Check whether a line contains a forbidden `as` type assertion.\n * Returns true if the line is clean (no violation), false if it violates.\n */\n// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: Source-text scanning with many skip rules is inherently branchy.\nexport function isLineClean(line: string): boolean {\n if (!line.includes(\" as \")) return true;\n\n const trimmed = line.trim();\n\n // Full-line comments\n if (trimmed.startsWith(\"//\") || trimmed.startsWith(\"*\") || trimmed.startsWith(\"/*\")) {\n return true;\n }\n\n // as const (literal narrowing)\n if (line.match(/\\bas const\\b/)) {\n const withoutAsConst = line.replace(/\\bas const\\b/g, \"\");\n if (!withoutAsConst.includes(\" as \")) return true;\n }\n\n // as unknown as (explicit escape hatch)\n if (line.includes(\" as unknown as \") || line.trimEnd().endsWith(\"as unknown as\")) {\n const withoutEscapeHatch = line.replace(/\\bas unknown as\\b/g, \"\");\n if (!withoutEscapeHatch.includes(\" as \")) return true;\n }\n\n // Import/export renames\n if (\n line.match(/\\b(import|export)\\s+.*\\s+as\\s+\\w+/) ||\n line.match(/\\b(import|export)\\s+\\*\\s+as\\s+\\w+/) ||\n line.match(/\\b(import|export)\\s+type\\s+.*\\s+as\\s+\\w+/) ||\n line.match(/^\\s*\\w+\\s+as\\s+\\w+,?\\s*$/) ||\n line.match(/^\\s*type\\s+\\w+\\s+as\\s+\\w+,?\\s*$/)\n ) {\n return true;\n }\n\n // Property declarations: as= or as: or as,\n if (line.match(/\\bas\\s*[=:,]/)) return true;\n\n // String literal detection: count quotes before each ` as ` occurrence.\n // If any quote type has an odd count, the ` as ` is inside a string.\n if (everyAsInsideString(line)) return true;\n\n // JSX text: ` as ` between > and < with no code syntax around it\n if (isJsxText(trimmed)) return true;\n\n // Plain text heuristic: indented line with no code syntax characters\n // before ` as ` — catches multiline JSX text and template literal bodies.\n if (isPlainText(trimmed)) return true;\n\n // Inline comment: ` as ` appears only after //\n const commentIdx = line.indexOf(\"//\");\n if (commentIdx >= 0 && line.indexOf(\" as \", commentIdx) >= 0) {\n const beforeComment = line.substring(0, commentIdx);\n if (!beforeComment.includes(\" as \")) return true;\n }\n\n // SQL alias: `) as column_name`\n if (line.match(/\"\\)\\s+as\\s+\\w+\"/)) return true;\n\n if (line.includes(\" satisfies \")) return true;\n\n return false;\n}\n\n/**\n * Returns true when every ` as ` occurrence in the line falls inside a\n * string literal (single-quoted, double-quoted, or backtick).\n */\nfunction everyAsInsideString(line: string): boolean {\n let searchFrom = 0;\n while (true) {\n const idx = line.indexOf(\" as \", searchFrom);\n if (idx < 0) return true; // no more ` as ` to check\n const before = line.substring(0, idx);\n const singleQuotes = (before.match(/'/g) ?? []).length;\n const doubleQuotes = (before.match(/\"/g) ?? []).length;\n const backticks = (before.match(/`/g) ?? []).length;\n if (singleQuotes % 2 === 0 && doubleQuotes % 2 === 0 && backticks % 2 === 0) {\n return false; // this ` as ` is outside any string\n }\n searchFrom = idx + 4;\n }\n}\n\n/**\n * Detects JSX text content — ` as ` appearing between > and < with no\n * code-like syntax around it (no braces, semicolons, equals signs).\n */\nfunction isJsxText(trimmed: string): boolean {\n // Classic JSX text: starts after > or is plain text ending before <\n if (trimmed.match(/^[^{};=()]*\\bas\\b[^{};=()]*$/)) {\n // No code syntax at all — could be JSX text or template literal body.\n // Reject if it looks like a type assertion (word ` as ` TypeName pattern\n // where TypeName starts with uppercase, or is a known TS type).\n if (\n !trimmed.match(/\\bas\\s+[A-Z]\\w*/) &&\n !trimmed.match(/\\bas\\s+(string|number|boolean|any|unknown|never)\\b/)\n ) {\n return true;\n }\n }\n return false;\n}\n\n/**\n * Heuristic for plain text in template literals or JSX: the line has no\n * code-like characters before ` as ` — no `=`, `{`, `}`, `:`, `;`, `(`.\n */\nfunction isPlainText(trimmed: string): boolean {\n const idx = trimmed.indexOf(\" as \");\n if (idx < 0) return false;\n const before = trimmed.substring(0, idx);\n // If nothing before ` as ` looks like code, it's probably prose.\n return (\n !before.match(/[={}:;(]/) &&\n !before.match(/\\b(const|let|var|type|interface|function|return|await)\\b/)\n );\n}\n\n/**\n * Suggest a concrete fix for a specific violation pattern.\n */\n// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: Pattern-matching advice is a linear chain of if-returns.\nexport function suggestFix(line: string): string | undefined {\n if (line.includes(\"JSON.parse\")) {\n return \"Use a validation function or type guard to parse and validate the result.\";\n }\n if (\n line.includes(\"as HTMLInputElement\") ||\n line.includes(\"as HTMLTextAreaElement\") ||\n line.includes(\"as HTMLButtonElement\")\n ) {\n return \"Use instanceof: if (el instanceof HTMLInputElement) { el.value ... }\";\n }\n if (line.includes(\"as HTMLElement\") || line.includes(\"as Element\")) {\n return \"Use instanceof: if (el instanceof HTMLElement) { ... }\";\n }\n if (line.includes(\".doc()\") && line.includes(\"as \")) {\n return \"Type the DocHandle generic: repo.find<MyType>(id) returns DocHandle<MyType>.\";\n }\n if (\n line.includes(\"Record<string, unknown>\") &&\n (line.includes(\"window\") || line.includes(\"globalThis\"))\n ) {\n return \"Extract a type guard: function getGlobalProp(name: string): unknown { ... }\";\n }\n if (line.includes(\"Record<string, unknown>\")) {\n return \"Use a type guard function that narrows the unknown value to the target shape.\";\n }\n if (line.includes(\"as PeerId\") || line.includes(\"as DocumentId\")) {\n return \"Use the library's branded-type constructor if available, or centralise the cast in a factory.\";\n }\n if (line.includes(\"as string\") || line.includes(\"as number\") || line.includes(\"as boolean\")) {\n return \"Narrow with typeof: if (typeof x === 'string') { ... }\";\n }\n if (line.includes(\"as any\")) {\n return \"Replace 'any' with 'unknown' and add a type guard or validation at the boundary.\";\n }\n return undefined;\n}\n\nfunction isFileExcluded(\n relative: string,\n excludeDirs: Set<string>,\n excludePackages: Set<string>,\n excludeFiles: Set<string>\n): boolean {\n const segments = relative.split(\"/\");\n if (segments.some((s) => excludeDirs.has(s))) return true;\n if (excludePackages.size > 0 && segments.some((s) => excludePackages.has(s))) return true;\n const basename = segments[segments.length - 1] ?? \"\";\n return excludeFiles.has(basename) || excludeFiles.has(relative);\n}\n\nfunction findViolations(relative: string, content: string): Violation[] {\n const results: Violation[] = [];\n const lines = content.split(\"\\n\");\n let insideTemplate = false;\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i] ?? \"\";\n const backticks = (line.match(/`/g) ?? []).length;\n const startedInTemplate = insideTemplate;\n if (backticks % 2 === 1) insideTemplate = !insideTemplate;\n\n // Line is entirely inside a multi-line template literal and has no\n // interpolation — treat as string content (e.g. SQL column aliases).\n if (startedInTemplate && backticks === 0 && !line.includes(\"${\")) continue;\n\n if (!isLineClean(line)) {\n results.push({\n file: relative,\n line: i + 1,\n content: line.trim(),\n advice: suggestFix(line.trim()),\n });\n }\n }\n return results;\n}\n\nfunction printViolations(violations: Violation[]): void {\n if (violations.length === 0) {\n console.log(\"[no-as-casting] ✅ No violations found.\");\n return;\n }\n console.log(`[no-as-casting] ❌ ${violations.length} violation(s) found:\\n`);\n for (const v of violations) {\n console.log(` ${v.file}:${v.line}`);\n console.log(` ${v.content}`);\n if (v.advice) console.log(` 💡 ${v.advice}`);\n console.log();\n }\n console.log(\"[no-as-casting] Use type guards, validation, or fix the types at the source.\");\n console.log('[no-as-casting] Only \"as const\" and \"as unknown as\" are allowed.');\n}\n\n/**\n * Run the no-as-casting check against a directory. Returns a result\n * object with the violations and a print function for CLI output.\n */\nexport async function checkNoAsCasting(options: CheckOptions): Promise<CheckResult> {\n const rootDir = options.rootDir;\n const excludeDirs = new Set(options.exclude ?? [\"node_modules\", \"dist\", \".git\", \".bun\"]);\n const excludePackages = new Set(options.excludePackages ?? []);\n const excludeFiles = new Set(options.excludeFiles ?? []);\n const pattern = options.filePatterns ?? \"**/*.{ts,tsx}\";\n const glob = new Glob(pattern);\n const violations: Violation[] = [];\n\n for await (const file of glob.scan({ cwd: rootDir, absolute: true })) {\n const relative = file.replace(`${rootDir}/`, \"\");\n if (isFileExcluded(relative, excludeDirs, excludePackages, excludeFiles)) continue;\n const content = readFileSync(file, \"utf-8\");\n violations.push(...findViolations(relative, content));\n }\n\n return { violations, print: () => printViolations(violations) };\n}\n",
|
|
6
6
|
"#!/usr/bin/env bun\n\n/**\n * CLI entry point for Polly quality checks.\n *\n * polly quality [--root <dir>] [--exclude <dirs>] [--pattern <glob>]\n * [--exclude-packages <names>] [--exclude-files <names>]\n *\n * Runs all conformance checks (currently: no-as-casting) against the\n * target directory and exits non-zero when violations are found.\n */\n\nimport { checkNoAsCasting } from \"./no-as-casting\";\n\nconst args = process.argv.slice(2);\n\nfunction getFlag(name: string): string | undefined {\n const idx = args.indexOf(`--${name}`);\n return idx >= 0 ? args[idx + 1] : undefined;\n}\n\nconst rootDir = getFlag(\"root\") ?? process.cwd();\nconst exclude = getFlag(\"exclude\")?.split(\",\") ?? [\"node_modules\", \"dist\", \".git\", \".bun\"];\nconst excludePackages = getFlag(\"exclude-packages\")?.split(\",\");\nconst excludeFiles = getFlag(\"exclude-files\")?.split(\",\");\nconst filePatterns = getFlag(\"pattern\");\n\nconst result = await checkNoAsCasting({\n rootDir,\n exclude,\n ...(excludePackages ? { excludePackages } : {}),\n ...(excludeFiles ? { excludeFiles } : {}),\n ...(filePatterns ? { filePatterns } : {}),\n});\n\nresult.print();\nprocess.exit(result.violations.length > 0 ? 1 : 0);\n"
|
|
7
7
|
],
|
|
8
|
-
"mappings": "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
|
|
9
|
-
"debugId": "
|
|
8
|
+
"mappings": "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",
|
|
9
|
+
"debugId": "D4F70D746137C24364756E2164756E21",
|
|
10
10
|
"names": []
|
|
11
11
|
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fairfox/polly/quality — conformance checks for Polly applications.
|
|
3
|
+
*
|
|
4
|
+
* Exports the same quality rules that Polly enforces on itself, so
|
|
5
|
+
* consuming applications can adopt the same standards. The flagship
|
|
6
|
+
* check is `isLineClean` which detects forbidden `as` type assertions;
|
|
7
|
+
* applications wire it into their own CI or pre-commit hook via the
|
|
8
|
+
* companion `checkNoAsCasting` runner.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```typescript
|
|
12
|
+
* import { checkNoAsCasting } from "@fairfox/polly/quality";
|
|
13
|
+
*
|
|
14
|
+
* const result = await checkNoAsCasting({
|
|
15
|
+
* rootDir: process.cwd(),
|
|
16
|
+
* exclude: ["node_modules", "dist"],
|
|
17
|
+
* });
|
|
18
|
+
*
|
|
19
|
+
* if (result.violations.length > 0) {
|
|
20
|
+
* result.print();
|
|
21
|
+
* process.exit(1);
|
|
22
|
+
* }
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
export { type CheckResult, checkNoAsCasting, isLineClean, suggestFix, type Violation, } from "./no-as-casting";
|