@doubling/types 0.2.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,8 @@
1
1
  # @doubling/types
2
2
 
3
- Shared TypeScript interfaces and pure helpers consumed by `web/` and `sync/`. Single source of truth for Firestore document shapes (file/folder entities, org membership, teams, user profiles), file-extension to MIME mappings, and storage-path helpers.
3
+ Shared TypeScript interfaces and pure helpers consumed by `web/` and `sync/` (verified via each package's `package.json` dependency and `import … from '@doubling/types'` sites). Single source of truth for Firestore document shapes (file/folder entities, org membership, teams, user profiles), file-extension to MIME mappings, and storage-path helpers.
4
+
5
+ `functions/` does **not** depend on this package: it deliberately keeps a local mirror of `OrgMember` (see `functions/src/index.ts`, "kept local because functions/…") pending a later migration. Root [`../DESIGN.md` §5](../DESIGN.md) describes the shapes as consumed by web, sync, and functions — that is a *structural* statement (functions stores the same documents), not a package dependency.
4
6
 
5
7
  Lives at the repo root as an npm workspace (`@doubling/types`). The compiled output is shipped at `dist/`, regenerated on `npm install` via the `prepare` script and rebuilt explicitly via `npm run build`.
6
8
 
@@ -31,4 +33,16 @@ Edit `EXT_TO_MIME` in `src/mime.ts`. That's it. Both `web/` and `sync/` pick up
31
33
  npm run -w types build
32
34
  ```
33
35
 
34
- Emits `dist/index.js` plus `dist/*.d.ts`. The `dist/` directory is gitignored; CI runs the build automatically via the `prepare` script on `npm install`.
36
+ Emits `dist/index.js` plus `dist/*.d.ts`. The `dist/` directory is gitignored; CI runs the build automatically via the `prepare` script on `npm install` (`prepack` re-runs `tsc` before packaging). Only `dist/` and `README.md` are shipped in the published tarball (see the `files` field in `package.json`).
37
+
38
+ ## Publish
39
+
40
+ Published to the public npm registry as `@doubling/types`. The [`.github/workflows/publish-types.yml`](../.github/workflows/publish-types.yml) workflow (**Publish Types to npm**) fires on every published GitHub Release, gated on the `@doubling/types@` tag prefix so a sibling `sync` release does not trigger it. It runs `npm ci`, `npm run build -w types`, skips cleanly if `types/package.json`'s version is already on npm, then `npm publish --access public` via npm Trusted Publishing (OIDC — no `NPM_TOKEN`). `workflow_dispatch` is exposed for bootstrap and transient-failure recovery. Version bumps are cut by Changesets (see `CHANGELOG.md`).
41
+
42
+ The package is published because `@doubling/compound-sync` is itself a published npm package that depends on `@doubling/types` at runtime, so the dependency must resolve from the registry outside the monorepo.
43
+
44
+ ## Docs
45
+
46
+ - [`DESIGN.md`](DESIGN.md) — detailed design (exported symbols, path scheme, trade-offs).
47
+ - Root [`../DESIGN.md` §5](../DESIGN.md) — canonical data model.
48
+ - [`../docs/features/sync/index.md`](../docs/features/sync/index.md) — shared-schema bullet in the sync feature docs.
@@ -19,6 +19,7 @@ export interface FileEntity extends EntityBase {
19
19
  storagePath?: string;
20
20
  content?: string | null;
21
21
  frontmatter?: unknown;
22
+ contentLoadFailed?: true;
22
23
  }
23
24
  export interface FolderEntity extends EntityBase {
24
25
  type: 'folder';
@@ -37,6 +38,7 @@ export interface OrgMember {
37
38
  email?: string;
38
39
  role?: string;
39
40
  orgName?: string;
41
+ teamIds?: string[];
40
42
  }
41
43
  export interface Team {
42
44
  id: string;
@@ -1 +1 @@
1
- {"version":3,"file":"firestore.d.ts","sourceRoot":"","sources":["../src/firestore.ts"],"names":[],"mappings":"AAoBA,MAAM,MAAM,KAAK,GAAG,MAAM,GAAG,SAAS,CAAC;AAKvC,UAAU,UAAU;IAClB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,KAAK,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC;IACvB,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC5B,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CAC7B;AASD,MAAM,WAAW,UAAW,SAAQ,UAAU;IAC5C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAGD,MAAM,WAAW,YAAa,SAAQ,UAAU;IAC9C,IAAI,EAAE,QAAQ,CAAC;CAChB;AAID,MAAM,MAAM,MAAM,GAAG,UAAU,GAAG,YAAY,CAAC;AAM/C,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;CAClB;AAKD,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAGD,MAAM,WAAW,IAAI;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAGD,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAKD,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB"}
1
+ {"version":3,"file":"firestore.d.ts","sourceRoot":"","sources":["../src/firestore.ts"],"names":[],"mappings":"AAoBA,MAAM,MAAM,KAAK,GAAG,MAAM,GAAG,SAAS,CAAC;AAKvC,UAAU,UAAU;IAClB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,KAAK,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC;IACvB,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC5B,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CAC7B;AASD,MAAM,WAAW,UAAW,SAAQ,UAAU;IAC5C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,WAAW,CAAC,EAAE,OAAO,CAAC;IAQtB,iBAAiB,CAAC,EAAE,IAAI,CAAC;CAC1B;AAGD,MAAM,WAAW,YAAa,SAAQ,UAAU;IAC9C,IAAI,EAAE,QAAQ,CAAC;CAChB;AAID,MAAM,MAAM,MAAM,GAAG,UAAU,GAAG,YAAY,CAAC;AAM/C,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;CAClB;AAKD,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IAQjB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACpB;AAGD,MAAM,WAAW,IAAI;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAGD,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAKD,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB"}
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export * from './firestore.js';
2
2
  export * from './mime.js';
3
3
  export * from './paths.js';
4
+ export * from './scrub.js';
4
5
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAMA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,WAAW,CAAC;AAC1B,cAAc,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAMA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,WAAW,CAAC;AAC1B,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC"}
package/dist/index.js CHANGED
@@ -7,4 +7,5 @@
7
7
  export * from './firestore.js';
8
8
  export * from './mime.js';
9
9
  export * from './paths.js';
10
+ export * from './scrub.js';
10
11
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,mEAAmE;AACnE,EAAE;AACF,6DAA6D;AAC7D,qEAAqE;AACrE,6DAA6D;AAC7D,qCAAqC;AACrC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,WAAW,CAAC;AAC1B,cAAc,YAAY,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,mEAAmE;AACnE,EAAE;AACF,6DAA6D;AAC7D,qEAAqE;AACrE,6DAA6D;AAC7D,qCAAqC;AACrC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,WAAW,CAAC;AAC1B,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * The only shape allowed for context we attach deliberately.
3
+ *
4
+ * Everything here is non-identifying metadata that the operation log
5
+ * already models (see sync/operation-log.ts). Notably absent, and
6
+ * absent on purpose: `path`, `name`, `content`, `email`.
7
+ */
8
+ export interface SafeContext {
9
+ orgId?: string;
10
+ userId?: string;
11
+ teamId?: string;
12
+ opType?: string;
13
+ scope?: string;
14
+ entityType?: 'file' | 'folder';
15
+ /** Extension only — 'md', 'pdf'. Never the filename. */
16
+ ext?: string;
17
+ mimeType?: string;
18
+ sizeBytes?: number;
19
+ /** Shape of the path, not its content. */
20
+ pathDepth?: number;
21
+ nameLength?: number;
22
+ errorCode?: string;
23
+ agentVersion?: string;
24
+ }
25
+ /**
26
+ * Redact a single string.
27
+ *
28
+ * Order matters: query strings first (Firebase Storage signed URLs
29
+ * carry `?token=`), then the most specific path forms, then bare
30
+ * filenames, then the length cap.
31
+ */
32
+ export declare function redactString(input: string): string;
33
+ /**
34
+ * Scrub an error event in place-safe fashion (returns a new object).
35
+ *
36
+ * Returns `null` if scrubbing itself throws. Failing closed is the only
37
+ * acceptable behaviour here: dropping one event costs a diagnostic,
38
+ * leaking one costs customer data.
39
+ */
40
+ export declare function scrubEvent<T extends object>(event: T): T | null;
41
+ //# sourceMappingURL=scrub.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scrub.d.ts","sourceRoot":"","sources":["../src/scrub.ts"],"names":[],"mappings":"AA2BA;;;;;;GAMG;AACH,MAAM,WAAW,WAAW;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,GAAG,QAAQ,CAAC;IAC/B,wDAAwD;IACxD,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,0CAA0C;IAC1C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AA8DD;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CA6BlD;AAmCD;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,CAAC,SAAS,MAAM,EAAE,KAAK,EAAE,CAAC,GAAG,CAAC,GAAG,IAAI,CA0E/D"}
package/dist/scrub.js ADDED
@@ -0,0 +1,206 @@
1
+ // PII scrubbing for error telemetry.
2
+ //
3
+ // Compound is a file-sync product. File paths, file names and file
4
+ // CONTENTS are customer data, and the daemon runs on customer machines.
5
+ // Nothing resembling any of it may leave with an error report.
6
+ //
7
+ // Two layers, deliberately:
8
+ //
9
+ // 1. SafeContext — a typed allowlist for anything we attach on
10
+ // purpose. With `exactOptionalPropertyTypes` on, passing `{ path }`
11
+ // is a COMPILE error. PII discipline enforced by the compiler
12
+ // rather than by review.
13
+ //
14
+ // 2. scrubEvent — a redaction pass over everything the SDK populates
15
+ // on its own: messages, exception values, stack frames, breadcrumbs,
16
+ // request data. Layer 1 cannot help there, because none of it is
17
+ // ours to choose.
18
+ //
19
+ // Lives in @doubling/types so web, sync and desktop share one
20
+ // implementation. `functions/` keeps a small local copy: `firebase
21
+ // deploy` runs a standalone `npm ci --workspaces=false`, which cannot
22
+ // resolve a workspace dependency.
23
+ //
24
+ // Deliberately dependency-free and framework-agnostic — it takes and
25
+ // returns a plain object, so it is trivially testable without a Sentry
26
+ // SDK present.
27
+ // Folder names that begin a sync-relative path. Kept as literals rather
28
+ // than imported from paths.ts so this module stays dependency-free and
29
+ // so a rename there cannot silently narrow the redaction.
30
+ const SYNC_ROOTS = ['Private', 'Shared with Me', 'Shared by Me'];
31
+ const EMAIL_RE = /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g;
32
+ // Absolute filesystem paths. These leak the OS username as well as the
33
+ // file, which is why they are redacted even though the daemon's own
34
+ // install directory is not itself secret.
35
+ const ABS_PATH_RE = /(?:\/Users\/|\/home\/|\/var\/folders\/|[A-Za-z]:\\Users\\)[^\s"')\]]+/g;
36
+ // Extensions we recognise. Used both to anchor path matching and to
37
+ // catch bare filenames.
38
+ const EXT_ALT = 'md|markdown|txt|json|ya?ml|csv|pdf|png|jpe?g|gif|heic|docx?|xlsx?|base';
39
+ const SYNC_ROOT_ALT = `(?:${SYNC_ROOTS.map(escapeRegExp).join('|')}|[^\\s"']+ Teamspace)`;
40
+ // A sync-relative path ENDING IN A KNOWN EXTENSION, allowing spaces
41
+ // inside segments.
42
+ //
43
+ // Spaces matter here: "Private/notes/Q3 Budget.md" is an entirely
44
+ // ordinary filename, and a pattern that stops at the first space
45
+ // reports it as two tokens with a misleading depth and extension. Both
46
+ // halves still get redacted — nothing leaks either way — but the shape
47
+ // metadata is the whole point of keeping a token at all, so it should
48
+ // be right.
49
+ //
50
+ // Non-greedy and anchored on the extension so it cannot run on into the
51
+ // prose that follows the path in a log line.
52
+ const SYNC_PATH_EXT_RE = new RegExp(`${SYNC_ROOT_ALT}/[^"')\\]]+?\\.(?:${EXT_ALT})\\b`, 'gi');
53
+ // Extension-less sync paths (folders). No space allowance: without an
54
+ // extension to anchor on, permitting spaces would swallow the rest of
55
+ // the sentence.
56
+ const SYNC_PATH_RE = new RegExp(`${SYNC_ROOT_ALT}/[^\\s"')\\]]+`, 'g');
57
+ // Any remaining bare filename with a plausible extension.
58
+ const FILENAME_RE = new RegExp(`[^\\s"'/\\\\)\\]]+\\.(?:${EXT_ALT})\\b`, 'gi');
59
+ // Content interpolated into a message is the failure mode this cannot
60
+ // enumerate its way out of, so there is a blunt length cap as backstop.
61
+ const MAX_STRING = 200;
62
+ function escapeRegExp(s) {
63
+ return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
64
+ }
65
+ function depthOf(p) {
66
+ return p.split('/').filter(Boolean).length;
67
+ }
68
+ /** Replace a matched path with its shape: depth, extension, name length. */
69
+ function summarisePath(match) {
70
+ const name = match.split('/').pop() ?? '';
71
+ const dot = name.lastIndexOf('.');
72
+ const ext = dot > 0 ? name.slice(dot + 1).toLowerCase() : 'none';
73
+ return `<path:d${depthOf(match)}:ext=${ext}:len=${name.length}>`;
74
+ }
75
+ /**
76
+ * Redact a single string.
77
+ *
78
+ * Order matters: query strings first (Firebase Storage signed URLs
79
+ * carry `?token=`), then the most specific path forms, then bare
80
+ * filenames, then the length cap.
81
+ */
82
+ export function redactString(input) {
83
+ if (typeof input !== 'string' || input.length === 0)
84
+ return input;
85
+ let out = input;
86
+ // Strip query strings and fragments wholesale. A Storage download URL
87
+ // carries an access token; a redacted path with a live token attached
88
+ // would be worse than the path alone.
89
+ out = out.replace(/([?#])[^\s"')\]]*/g, '$1<redacted>');
90
+ out = out.replace(EMAIL_RE, '<email>');
91
+ out = out.replace(ABS_PATH_RE, (m) => `<abs-path:d${depthOf(m)}>`);
92
+ // Extension-anchored first: it is the more specific pattern, and
93
+ // running the looser one first would truncate at the first space.
94
+ out = out.replace(SYNC_PATH_EXT_RE, summarisePath);
95
+ out = out.replace(SYNC_PATH_RE, summarisePath);
96
+ out = out.replace(FILENAME_RE, (m) => {
97
+ const dot = m.lastIndexOf('.');
98
+ return `<file:ext=${m.slice(dot + 1).toLowerCase()}:len=${m.length}>`;
99
+ });
100
+ if (out.length > MAX_STRING) {
101
+ out = `${out.slice(0, MAX_STRING)}…[truncated]`;
102
+ }
103
+ return out;
104
+ }
105
+ /** Recursively redact every string in a value, depth-capped. */
106
+ function redactDeep(value, depth = 0) {
107
+ if (depth > 4)
108
+ return '<depth-capped>';
109
+ if (typeof value === 'string')
110
+ return redactString(value);
111
+ if (Array.isArray(value))
112
+ return value.map((v) => redactDeep(v, depth + 1));
113
+ if (value !== null && typeof value === 'object') {
114
+ const out = {};
115
+ for (const [k, v] of Object.entries(value)) {
116
+ out[k] = redactDeep(v, depth + 1);
117
+ }
118
+ return out;
119
+ }
120
+ return value;
121
+ }
122
+ /**
123
+ * Scrub an error event in place-safe fashion (returns a new object).
124
+ *
125
+ * Returns `null` if scrubbing itself throws. Failing closed is the only
126
+ * acceptable behaviour here: dropping one event costs a diagnostic,
127
+ * leaking one costs customer data.
128
+ */
129
+ export function scrubEvent(event) {
130
+ try {
131
+ // `T extends object` rather than `T extends ScrubbableEvent`: the
132
+ // point of this module is to stay usable without the Sentry SDK,
133
+ // and an SDK's own ErrorEvent type does not structurally satisfy a
134
+ // hand-written interface (its optional fields are narrower than the
135
+ // `unknown` used here). Callers get their exact type back; the
136
+ // internal view is the structural subset we actually walk.
137
+ const out = { ...event };
138
+ if (typeof out.message === 'string')
139
+ out.message = redactString(out.message);
140
+ if (typeof out.transaction === 'string')
141
+ out.transaction = redactString(out.transaction);
142
+ if (typeof out.culprit === 'string')
143
+ out.culprit = redactString(out.culprit);
144
+ if (out.logentry)
145
+ out.logentry = redactDeep(out.logentry);
146
+ if (out.exception?.values) {
147
+ out.exception = {
148
+ ...out.exception,
149
+ values: out.exception.values.map((v) => {
150
+ const val = { ...v };
151
+ if (typeof val['value'] === 'string')
152
+ val['value'] = redactString(val['value']);
153
+ if (typeof val['type'] === 'string')
154
+ val['type'] = redactString(val['type']);
155
+ const st = val['stacktrace'];
156
+ if (st?.frames) {
157
+ val['stacktrace'] = {
158
+ ...st,
159
+ frames: st.frames.map((f) => {
160
+ const frame = { ...f };
161
+ for (const k of ['filename', 'abs_path', 'module']) {
162
+ if (typeof frame[k] === 'string')
163
+ frame[k] = redactString(frame[k]);
164
+ }
165
+ // Local variables can hold an entire file body: the
166
+ // daemon throws from inside pushFileToCloud, whose
167
+ // scope holds `data: string | Buffer`. Never send them.
168
+ delete frame['vars'];
169
+ return frame;
170
+ }),
171
+ };
172
+ }
173
+ return val;
174
+ }),
175
+ };
176
+ }
177
+ if (out.breadcrumbs)
178
+ out.breadcrumbs = redactDeep(out.breadcrumbs);
179
+ if (out.request) {
180
+ // Keep only a redacted URL. Bodies, headers and cookies carry
181
+ // auth tokens and file content and are never worth the risk.
182
+ const url = out.request['url'];
183
+ out.request = typeof url === 'string' ? { url: redactString(url) } : {};
184
+ }
185
+ // Identify the user by opaque uid only. Email is PII and the
186
+ // username is often the person's real name.
187
+ if (out.user) {
188
+ const id = out.user['id'];
189
+ out.user = id === undefined ? {} : { id };
190
+ }
191
+ // Dropped wholesale rather than redacted: `extra` is the field
192
+ // callers reach for when they have something interesting and
193
+ // unstructured, which is exactly when it holds a path or a body.
194
+ // Deliberate context goes through SafeContext instead.
195
+ delete out.extra;
196
+ if (out.contexts)
197
+ out.contexts = redactDeep(out.contexts);
198
+ if (out.tags)
199
+ out.tags = redactDeep(out.tags);
200
+ return out;
201
+ }
202
+ catch {
203
+ return null;
204
+ }
205
+ }
206
+ //# sourceMappingURL=scrub.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scrub.js","sourceRoot":"","sources":["../src/scrub.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@doubling/types",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "description": "Shared Firestore document interfaces and MIME / path helpers consumed by web/ and sync/. Single source of truth so the two consumers can never drift on a document shape (DOU-182, TS Phase 2 of the monorepo TypeScript migration).",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -17,6 +17,7 @@
17
17
  ],
18
18
  "scripts": {
19
19
  "build": "tsc",
20
+ "test": "node --test \"src/**/*.test.ts\"",
20
21
  "typecheck": "tsc --noEmit",
21
22
  "prepare": "tsc",
22
23
  "prepack": "tsc"