@atproto/bsky 0.3.13 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +33 -0
- package/dist/api/app/bsky/notification/getGroupedNotifications.d.ts.map +1 -1
- package/dist/api/app/bsky/notification/getGroupedNotifications.js +14 -3
- package/dist/api/app/bsky/notification/getGroupedNotifications.js.map +1 -1
- package/dist/api/blob-resolver.d.ts.map +1 -1
- package/dist/api/blob-resolver.js +9 -8
- package/dist/api/blob-resolver.js.map +1 -1
- package/dist/config.d.ts +2 -2
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +7 -6
- package/dist/config.js.map +1 -1
- package/dist/hydration/external.d.ts +52 -45
- package/dist/hydration/external.d.ts.map +1 -1
- package/dist/hydration/external.js +132 -114
- package/dist/hydration/external.js.map +1 -1
- package/dist/hydration/hydrator.d.ts +10 -8
- package/dist/hydration/hydrator.d.ts.map +1 -1
- package/dist/hydration/hydrator.js +82 -189
- package/dist/hydration/hydrator.js.map +1 -1
- package/dist/image/server.d.ts.map +1 -1
- package/dist/image/server.js +12 -1
- package/dist/image/server.js.map +1 -1
- package/dist/lexicons/tools/ozone/moderation/emitEvent.defs.d.ts +2 -2
- package/dist/lexicons/tools/ozone/moderation/emitEvent.defs.js.map +1 -1
- package/dist/lexicons/tools/ozone/report/closeReports.defs.d.ts +1 -1
- package/dist/lexicons/tools/ozone/report/closeReports.defs.d.ts.map +1 -1
- package/dist/lexicons/tools/ozone/report/closeReports.defs.js +1 -1
- package/dist/lexicons/tools/ozone/report/closeReports.defs.js.map +1 -1
- package/dist/telemetry/events.d.ts +11 -4
- package/dist/telemetry/events.d.ts.map +1 -1
- package/dist/telemetry/events.js +29 -5
- package/dist/telemetry/events.js.map +1 -1
- package/dist/util/external/standard-site.d.ts +2 -3
- package/dist/util/external/standard-site.d.ts.map +1 -1
- package/dist/util/external/standard-site.js +2 -3
- package/dist/util/external/standard-site.js.map +1 -1
- package/dist/util/uris.d.ts +5 -2
- package/dist/util/uris.d.ts.map +1 -1
- package/dist/util/uris.js +14 -1
- package/dist/util/uris.js.map +1 -1
- package/dist/views/index.d.ts +2 -2
- package/dist/views/index.d.ts.map +1 -1
- package/dist/views/index.js +17 -11
- package/dist/views/index.js.map +1 -1
- package/package.json +11 -12
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/image/server.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,SAAS,CAAA;AAI5B,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AAa3C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAA;AAC/C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAMjD,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,UAAU,EACf,EAAE,MAAY,EAAE,GAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAA;CAAO,GACzC,UAAU,
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/image/server.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,SAAS,CAAA;AAI5B,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AAa3C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAA;AAC/C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAMjD,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,UAAU,EACf,EAAE,MAAY,EAAE,GAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAA;CAAO,GACzC,UAAU,CAsJZ;AAsBD,MAAM,WAAW,SAAS;IACxB,GAAG,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,GAAG;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IACzD,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACpD,KAAK,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACpC,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CAC1B;AAED,qBAAa,aAAc,YAAW,SAAS;IAC7C,OAAO,EAAE,MAAM,CAAA;IACf,YAAY,QAAQ,CAAC,EAAE,MAAM,EAW5B;IAEK,GAAG,CAAC,MAAM,EAAE,MAAM;;OAcvB;IAEK,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,iBAWzC;IAEK,KAAK,CAAC,MAAM,EAAE,MAAM,iBAGzB;IAEK,QAAQ,kBAEb;CACF"}
|
package/dist/image/server.js
CHANGED
|
@@ -21,6 +21,8 @@ export function createMiddleware(ctx, { prefix = '/' } = {}) {
|
|
|
21
21
|
return (req, res, next) => next();
|
|
22
22
|
}
|
|
23
23
|
const cache = new BlobDiskCache(ctx.cfg.blobCacheLocation);
|
|
24
|
+
// @TODO this should be from configuration
|
|
25
|
+
const imageMaxSize = 100 * 1024 * 1024; // 100 MB
|
|
24
26
|
return async (req, res, next) => {
|
|
25
27
|
if (res.destroyed)
|
|
26
28
|
return;
|
|
@@ -72,9 +74,18 @@ export function createMiddleware(ctx, { prefix = '/' } = {}) {
|
|
|
72
74
|
if (isImageMime(upstream.headers['content-type']) === false) {
|
|
73
75
|
throw createError(400, 'Not an image');
|
|
74
76
|
}
|
|
77
|
+
const contentLength = upstream.headers['content-length']
|
|
78
|
+
? Number(upstream.headers['content-length'])
|
|
79
|
+
: undefined;
|
|
80
|
+
if (contentLength && contentLength > imageMaxSize) {
|
|
81
|
+
throw createError(400, 'Image too large');
|
|
82
|
+
}
|
|
75
83
|
const streams = [
|
|
76
84
|
// decompress
|
|
77
|
-
...createDecoders(upstream.headers['content-encoding']
|
|
85
|
+
...createDecoders(upstream.headers['content-encoding'], {
|
|
86
|
+
maxSize: imageMaxSize,
|
|
87
|
+
maxSizeError: () => createError(400, 'Image too large'),
|
|
88
|
+
}),
|
|
78
89
|
// verify
|
|
79
90
|
new VerifyCidTransform(cid),
|
|
80
91
|
// upscale
|
package/dist/image/server.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/image/server.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,SAAS,CAAA;AAC5B,OAAO,EAAE,MAAM,kBAAkB,CAAA;AACjC,OAAO,EAAE,MAAM,SAAS,CAAA;AACxB,OAAO,IAAI,MAAM,WAAW,CAAA;AAE5B,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AACtC,OAAO,EAAE,QAAQ,IAAI,eAAe,EAAE,MAAM,sBAAsB,CAAA;AAClE,OAAO,WAAW,EAAE,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AACtD,OAAO,EACL,cAAc,EACd,kBAAkB,EAClB,cAAc,EACd,MAAM,EACN,gBAAgB,GACjB,MAAM,iBAAiB,CAAA;AACxB,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAA;AACjD,OAAO,EAA0B,UAAU,EAAE,MAAM,yBAAyB,CAAA;AAG5E,OAAO,GAAG,MAAM,aAAa,CAAA;AAC7B,OAAO,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAA;AACtE,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,UAAU,CAAA;AACxD,OAAO,EAAgB,cAAc,EAAE,MAAM,WAAW,CAAA;AAExD,MAAM,UAAU,gBAAgB,CAC9B,GAAe,EACf,EAAE,MAAM,GAAG,GAAG,EAAE,GAAwB,EAAE;IAE1C,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QACrD,MAAM,IAAI,SAAS,CAAC,wCAAwC,CAAC,CAAA;IAC/D,CAAC;IAED,mDAAmD;IACnD,IAAI,GAAG,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC;QACnB,OAAO,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,CAAA;IACnC,CAAC;IAED,MAAM,KAAK,GAAG,IAAI,aAAa,CAAC,GAAG,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAA;IAE1D,OAAO,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE;QAC9B,IAAI,GAAG,CAAC,SAAS;YAAE,OAAM;QACzB,IAAI,GAAG,CAAC,MAAM,KAAK,KAAK,IAAI,GAAG,CAAC,MAAM,KAAK,MAAM;YAAE,OAAO,IAAI,EAAE,CAAA;QAChE,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,UAAU,CAAC,MAAM,CAAC;YAAE,OAAO,IAAI,EAAE,CAAA;QAC/C,MAAM,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,OAAO,EAAE,GAAG,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;QAC3E,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,IAAI,KAAK,GAAG;YAAE,OAAO,IAAI,EAAE,CAAA;QAExD,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,eAAe,CAAC,UAAU,CAAC,IAAI,CAAC,CAAA;YAChD,MAAM,UAAU,GAAG,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,CAAA;YAE1C,MAAM,QAAQ,GAAG,CAAC,OAAO,CAAC,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;YAEtE,cAAc;YAEd,IAAI,CAAC;gBACH,MAAM,WAAW,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAA;gBAC7C,GAAG,CAAC,UAAU,GAAG,GAAG,CAAA;gBACpB,GAAG,CAAC,SAAS,CAAC,SAAS,EAAE,KAAK,CAAC,CAAA;gBAC/B,GAAG,CAAC,SAAS,CAAC,cAAc,EAAE,UAAU,CAAC,CAAA;gBACzC,GAAG,CAAC,SAAS,CAAC,eAAe,EAAE,0BAA0B,CAAC,CAAA,CAAC,SAAS;gBACpE,GAAG,CAAC,SAAS,CAAC,gBAAgB,EAAE,WAAW,CAAC,IAAI,CAAC,CAAA;gBACjD,MAAM,eAAe,CAAC,WAAW,EAAE,GAAG,CAAC,CAAA;gBACvC,OAAM;YACR,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,IAAI,CAAC,CAAC,GAAG,YAAY,iBAAiB,CAAC,EAAE,CAAC;oBACxC,GAAG,CAAC,KAAK,CAAC,EAAE,QAAQ,EAAE,GAAG,EAAE,EAAE,8BAA8B,CAAC,CAAA;gBAC9D,CAAC;gBAED,IAAI,GAAG,CAAC,WAAW,IAAI,GAAG,CAAC,SAAS,EAAE,CAAC;oBACrC,GAAG,CAAC,OAAO,EAAE,CAAA;oBACb,OAAM,CAAC,uBAAuB;gBAChC,CAAC;qBAAM,CAAC;oBACN,yCAAyC;oBACzC,GAAG,CAAC,YAAY,CAAC,SAAS,CAAC,CAAA;oBAC3B,GAAG,CAAC,YAAY,CAAC,cAAc,CAAC,CAAA;oBAChC,GAAG,CAAC,YAAY,CAAC,eAAe,CAAC,CAAA;oBACjC,GAAG,CAAC,YAAY,CAAC,gBAAgB,CAAC,CAAA;gBACpC,CAAC;YACH,CAAC;YAED,kBAAkB;YAElB,MAAM,aAAa,GAAsB;gBACvC,GAAG,EAAE,OAAO,CAAC,GAAG;gBAChB,GAAG,EAAE,OAAO,CAAC,GAAG;aACjB,CAAA;YAED,MAAM,UAAU,CAAC,GAAG,EAAE,aAAa,EAAE,CAAC,QAAQ,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE,EAAE;gBACnE,mDAAmD;gBACnD,IAAI,WAAW,CAAC,QAAQ,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC,KAAK,KAAK,EAAE,CAAC;oBAC5D,MAAM,WAAW,CAAC,GAAG,EAAE,cAAc,CAAC,CAAA;gBACxC,CAAC;gBAED,MAAM,OAAO,GAAG;oBACd,aAAa;oBACb,GAAG,cAAc,CAAC,QAAQ,CAAC,OAAO,CAAC,kBAAkB,CAAC,CAAC;oBACvD,SAAS;oBACT,IAAI,kBAAkB,CAAC,GAAG,CAAC;oBAC3B,UAAU;oBACV,mBAAmB,CAAC,OAAO,CAAC;oBAC5B,qBAAqB;oBACrB,oBAAoB,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE;wBAClD,2DAA2D;wBAC3D,IAAI,CAAC,GAAG,CAAC,SAAS,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC;4BACvC,GAAG,CAAC,SAAS,CAAC,gBAAgB,EAAE,IAAI,CAAC,IAAI,CAAC,CAAA;wBAC5C,CAAC;oBACH,CAAC,CAAC;oBACF,sEAAsE;oBACtE,uDAAuD;oBACvD,MAAM,CAAC,GAAG,EAAE,CAAC,MAAM,EAAE,EAAE;wBACrB,KAAK,KAAK;6BACP,GAAG,CAAC,QAAQ,EAAE,MAAM,CAAC;6BACrB,OAAO,CAAC,GAAG,EAAE;4BACZ,gEAAgE;4BAChE,2DAA2D;4BAC3D,iCAAiC;4BACjC,MAAM,CAAC,OAAO,EAAE,CAAA;wBAClB,CAAC,CAAC;6BACD,KAAK,CAAC,CAAC,GAAY,EAAE,EAAE;4BACtB,GAAG,CAAC,IAAI,CACN,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,QAAQ,EAAE,EAAE,GAAG,EAAE,GAAG,CAAC,MAAM,EAAE,EAClD,iCAAiC,CAClC,CAAA;wBACH,CAAC,CAAC,CAAA;oBACN,CAAC,CAAC;iBACH,CAAA;gBAED,GAAG,CAAC,UAAU,GAAG,GAAG,CAAA;gBACpB,GAAG,CAAC,SAAS,CAAC,cAAc,EAAE,UAAU,CAAC,CAAA;gBACzC,GAAG,CAAC,SAAS,CAAC,eAAe,EAAE,0BAA0B,CAAC,CAAA,CAAC,SAAS;gBACpE,GAAG,CAAC,SAAS,CAAC,SAAS,EAAE,MAAM,CAAC,CAAA;gBAEhC,QAAQ,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;oBACxB,IAAI,GAAG,EAAE,CAAC;wBACR,GAAG,CAAC,IAAI,CACN,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,QAAQ,EAAE,EAAE,GAAG,EAAE,GAAG,CAAC,MAAM,EAAE,EAClD,4CAA4C,CAC7C,CAAA;oBACH,CAAC;gBACH,CAAC,CAAC,CAAA;gBAEF,OAAO,OAAO,CAAC,CAAC,CAAE,CAAA;YACpB,CAAC,CAAC,CAAA;QACJ,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,GAAG,CAAC,WAAW,IAAI,GAAG,CAAC,SAAS,EAAE,CAAC;gBACrC,GAAG,CAAC,OAAO,EAAE,CAAA;YACf,CAAC;iBAAM,CAAC;gBACN,GAAG,CAAC,YAAY,CAAC,cAAc,CAAC,CAAA;gBAChC,GAAG,CAAC,YAAY,CAAC,gBAAgB,CAAC,CAAA;gBAClC,GAAG,CAAC,YAAY,CAAC,eAAe,CAAC,CAAA;gBACjC,GAAG,CAAC,YAAY,CAAC,SAAS,CAAC,CAAA;gBAE3B,IAAI,GAAG,YAAY,YAAY,EAAE,CAAC;oBAChC,IAAI,CAAC,WAAW,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,CAAA;gBAC7B,CAAC;qBAAM,IAAI,GAAG,YAAY,cAAc,EAAE,CAAC;oBACzC,IAAI,CAAC,WAAW,CAAC,GAAG,EAAE,gBAAgB,EAAE,GAAG,CAAC,CAAC,CAAA;gBAC/C,CAAC;qBAAM,IAAI,WAAW,CAAC,GAAG,CAAC,EAAE,CAAC;oBAC5B,IAAI,CAAC,GAAG,CAAC,CAAA;gBACX,CAAC;qBAAM,CAAC;oBACN,IAAI,CAAC,WAAW,CAAC,GAAG,EAAE,gBAAgB,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC,CAAA;gBAC1D,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC,CAAA;AACH,CAAC;AAED,SAAS,WAAW,CAClB,WAA0C;IAE1C,IAAI,WAAW,IAAI,IAAI,IAAI,WAAW,KAAK,0BAA0B,EAAE,CAAC;QACtE,OAAO,SAAS,CAAA,CAAC,QAAQ;IAC3B,CAAC;IACD,IAAI,KAAK,CAAC,OAAO,CAAC,WAAW,CAAC,EAAE,CAAC;QAC/B,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,SAAS,CAAA,CAAC,sBAAsB;QACrE,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,WAAW,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAA;QAChE,OAAO,WAAW,CAAC,KAAK,CAAC,WAAW,CAAC,CAAA,CAAC,8BAA8B;IACtE,CAAC;IACD,OAAO,WAAW,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAA;AACzC,CAAC;AAED,SAAS,OAAO,CAAC,MAAyB;IACxC,MAAM,IAAI,GAAG,cAAc,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;IACvC,IAAI,CAAC,IAAI;QAAE,MAAM,IAAI,KAAK,CAAC,gBAAgB,CAAC,CAAA;IAC5C,OAAO,IAAI,CAAA;AACb,CAAC;AASD,MAAM,OAAO,aAAa;IAExB,YAAY,QAAiB;QAC3B,IAAI,CAAC,OAAO,GAAG,QAAQ,IAAI,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,wBAAwB,CAAC,CAAA;QAC3E,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;YACnC,MAAM,IAAI,KAAK,CAAC,+BAA+B,CAAC,CAAA;QAClD,CAAC;QACD,IAAI,CAAC;YACH,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;QACrD,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,uCAAuC;YACvC,IAAI,gBAAgB,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,IAAI,KAAK,QAAQ;gBAAE,OAAM;QAC5D,CAAC;IACH,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,MAAc;QACtB,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,EAAE,GAAG,CAAC,CAAA;YAClE,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,MAAM,CAAC,IAAI,EAAE,CAAA;YACpC,IAAI,IAAI,KAAK,CAAC,EAAE,CAAC;gBACf,MAAM,IAAI,iBAAiB,EAAE,CAAA;YAC/B,CAAC;YACD,OAAO,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,gBAAgB,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,CAAA;QAC3D,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,gBAAgB,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;gBACnD,MAAM,IAAI,iBAAiB,EAAE,CAAA;YAC/B,CAAC;YACD,MAAM,GAAG,CAAA;QACX,CAAC;IACH,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,MAAc,EAAE,MAAgB;QACxC,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAA;QAChD,IAAI,CAAC;YACH,MAAM,EAAE,CAAC,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAA;QACtD,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,wDAAwD;YACxD,IAAI,gBAAgB,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,IAAI,KAAK,QAAQ;gBAAE,OAAM;YAC1D,MAAM,GAAG,CAAA;QACX,CAAC;gBAAS,CAAC;YACT,MAAM,CAAC,OAAO,EAAE,CAAA;QAClB,CAAC;IACH,CAAC;IAED,KAAK,CAAC,KAAK,CAAC,MAAc;QACxB,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAA;QAChD,MAAM,EAAE,CAAC,EAAE,CAAC,QAAQ,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAA;IACxC,CAAC;IAED,KAAK,CAAC,QAAQ;QACZ,MAAM,EAAE,CAAC,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAA;IAC7D,CAAC;CACF","sourcesContent":["import fsSync from 'node:fs'\nimport fs from 'node:fs/promises'\nimport os from 'node:os'\nimport path from 'node:path'\nimport type { Readable } from 'node:stream'\nimport { pipeline } from 'node:stream'\nimport { pipeline as pipelinePromise } from 'node:stream/promises'\nimport createError, { isHttpError } from 'http-errors'\nimport {\n VerifyCidError,\n VerifyCidTransform,\n createDecoders,\n fanOut,\n isErrnoException,\n} from '@atproto/common'\nimport { BlobNotFoundError } from '@atproto/repo'\nimport { type StreamBlobOptions, streamBlob } from '../api/blob-resolver.js'\nimport type { AppContext } from '../context.js'\nimport type { Middleware } from '../util/http.js'\nimport log from './logger.js'\nimport { createImageProcessor, createImageUpscaler } from './sharp.js'\nimport { BadPathError, ImageUriBuilder } from './uri.js'\nimport { type Options, formatsToMimes } from './util.js'\n\nexport function createMiddleware(\n ctx: AppContext,\n { prefix = '/' }: { prefix?: string } = {},\n): Middleware {\n if (!prefix.startsWith('/') || !prefix.endsWith('/')) {\n throw new TypeError('Prefix must start and end with a slash')\n }\n\n // If there is a CDN, we don't need to serve images\n if (ctx.cfg.cdnUrl) {\n return (req, res, next) => next()\n }\n\n const cache = new BlobDiskCache(ctx.cfg.blobCacheLocation)\n\n return async (req, res, next) => {\n if (res.destroyed) return\n if (req.method !== 'GET' && req.method !== 'HEAD') return next()\n if (!req.url?.startsWith(prefix)) return next()\n const { 0: path, 1: _search } = req.url.slice(prefix.length - 1).split('?')\n if (!path.startsWith('/') || path === '/') return next()\n\n try {\n const options = ImageUriBuilder.getOptions(path)\n const outputType = getMime(options.format)\n\n const cacheKey = [options.did, options.cid, options.preset].join('::')\n\n // Cached flow\n\n try {\n const cachedImage = await cache.get(cacheKey)\n res.statusCode = 200\n res.setHeader('x-cache', 'hit')\n res.setHeader('content-type', outputType)\n res.setHeader('cache-control', `public, max-age=31536000`) // 1 year\n res.setHeader('content-length', cachedImage.size)\n await pipelinePromise(cachedImage, res)\n return\n } catch (err) {\n if (!(err instanceof BlobNotFoundError)) {\n log.error({ cacheKey, err }, 'failed to serve cached image')\n }\n\n if (res.headersSent || res.destroyed) {\n res.destroy()\n return // nothing we can do...\n } else {\n // Ignore and move on to non-cached flow.\n res.removeHeader('x-cache')\n res.removeHeader('content-type')\n res.removeHeader('cache-control')\n res.removeHeader('content-length')\n }\n }\n\n // Non-cached flow\n\n const streamOptions: StreamBlobOptions = {\n did: options.did,\n cid: options.cid,\n }\n\n await streamBlob(ctx, streamOptions, (upstream, { did, cid, url }) => {\n // Definitely not an image ? Let's fail right away.\n if (isImageMime(upstream.headers['content-type']) === false) {\n throw createError(400, 'Not an image')\n }\n\n const streams = [\n // decompress\n ...createDecoders(upstream.headers['content-encoding']),\n // verify\n new VerifyCidTransform(cid),\n // upscale\n createImageUpscaler(options),\n // format (jpeg/webp)\n createImageProcessor(options).once('info', (info) => {\n // @NOTE sharp does emit this in time to be set as a header\n if (!res.destroyed && !res.headersSent) {\n res.setHeader('content-length', info.size)\n }\n }),\n // send downstream and save to cache in parallel. Applies backpressure\n // based on slowest consumer, avoiding memory pressure.\n fanOut(res, (branch) => {\n void cache\n .put(cacheKey, branch)\n .finally(() => {\n // Fail-safe: Make sure that the fanOut is no longer waiting for\n // this branch to drain (note that the cache implementation\n // should do this automatically).\n branch.destroy()\n })\n .catch((err: unknown) => {\n log.warn(\n { err, did, cid: cid.toString(), pds: url.origin },\n 'failed to cache processed image',\n )\n })\n }),\n ]\n\n res.statusCode = 200\n res.setHeader('content-type', outputType)\n res.setHeader('cache-control', `public, max-age=31536000`) // 1 year\n res.setHeader('x-cache', 'miss')\n\n pipeline(streams, (err) => {\n if (err) {\n log.warn(\n { err, did, cid: cid.toString(), pds: url.origin },\n 'blob resolution failed during transmission',\n )\n }\n })\n\n return streams[0]!\n })\n } catch (err) {\n if (res.headersSent || res.destroyed) {\n res.destroy()\n } else {\n res.removeHeader('content-type')\n res.removeHeader('content-length')\n res.removeHeader('cache-control')\n res.removeHeader('x-cache')\n\n if (err instanceof BadPathError) {\n next(createError(400, err))\n } else if (err instanceof VerifyCidError) {\n next(createError(404, 'Blob not found', err))\n } else if (isHttpError(err)) {\n next(err)\n } else {\n next(createError(502, 'Upstream Error', { cause: err }))\n }\n }\n }\n }\n}\n\nfunction isImageMime(\n contentType: string | string[] | undefined,\n): undefined | boolean {\n if (contentType == null || contentType === 'application/octet-stream') {\n return undefined // maybe\n }\n if (Array.isArray(contentType)) {\n if (contentType.length === 0) return undefined // should never happen\n if (contentType.length === 1) return isImageMime(contentType[0])\n return contentType.every(isImageMime) // Should we throw a 502 here?\n }\n return contentType.startsWith('image/')\n}\n\nfunction getMime(format: Options['format']) {\n const mime = formatsToMimes.get(format)\n if (!mime) throw new Error('Unknown format')\n return mime\n}\n\nexport interface BlobCache {\n get(fileId: string): Promise<Readable & { size: number }>\n put(fileId: string, stream: Readable): Promise<void>\n clear(fileId: string): Promise<void>\n clearAll(): Promise<void>\n}\n\nexport class BlobDiskCache implements BlobCache {\n tempDir: string\n constructor(basePath?: string) {\n this.tempDir = basePath || path.join(os.tmpdir(), 'bsky--processed-images')\n if (!path.isAbsolute(this.tempDir)) {\n throw new Error('Must provide an absolute path')\n }\n try {\n fsSync.mkdirSync(this.tempDir, { recursive: true })\n } catch (err) {\n // All good if cache dir already exists\n if (isErrnoException(err) && err.code === 'EEXIST') return\n }\n }\n\n async get(fileId: string) {\n try {\n const handle = await fs.open(path.join(this.tempDir, fileId), 'r')\n const { size } = await handle.stat()\n if (size === 0) {\n throw new BlobNotFoundError()\n }\n return Object.assign(handle.createReadStream(), { size })\n } catch (err) {\n if (isErrnoException(err) && err.code === 'ENOENT') {\n throw new BlobNotFoundError()\n }\n throw err\n }\n }\n\n async put(fileId: string, stream: Readable) {\n const filename = path.join(this.tempDir, fileId)\n try {\n await fs.writeFile(filename, stream, { flag: 'wx' })\n } catch (err) {\n // Do not overwrite existing file, just ignore the error\n if (isErrnoException(err) && err.code === 'EEXIST') return\n throw err\n } finally {\n stream.destroy()\n }\n }\n\n async clear(fileId: string) {\n const filename = path.join(this.tempDir, fileId)\n await fs.rm(filename, { force: true })\n }\n\n async clearAll() {\n await fs.rm(this.tempDir, { recursive: true, force: true })\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/image/server.ts"],"names":[],"mappings":"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","sourcesContent":["import fsSync from 'node:fs'\nimport fs from 'node:fs/promises'\nimport os from 'node:os'\nimport path from 'node:path'\nimport type { Readable } from 'node:stream'\nimport { pipeline } from 'node:stream'\nimport { pipeline as pipelinePromise } from 'node:stream/promises'\nimport createError, { isHttpError } from 'http-errors'\nimport {\n VerifyCidError,\n VerifyCidTransform,\n createDecoders,\n fanOut,\n isErrnoException,\n} from '@atproto/common'\nimport { BlobNotFoundError } from '@atproto/repo'\nimport { type StreamBlobOptions, streamBlob } from '../api/blob-resolver.js'\nimport type { AppContext } from '../context.js'\nimport type { Middleware } from '../util/http.js'\nimport log from './logger.js'\nimport { createImageProcessor, createImageUpscaler } from './sharp.js'\nimport { BadPathError, ImageUriBuilder } from './uri.js'\nimport { type Options, formatsToMimes } from './util.js'\n\nexport function createMiddleware(\n ctx: AppContext,\n { prefix = '/' }: { prefix?: string } = {},\n): Middleware {\n if (!prefix.startsWith('/') || !prefix.endsWith('/')) {\n throw new TypeError('Prefix must start and end with a slash')\n }\n\n // If there is a CDN, we don't need to serve images\n if (ctx.cfg.cdnUrl) {\n return (req, res, next) => next()\n }\n\n const cache = new BlobDiskCache(ctx.cfg.blobCacheLocation)\n\n // @TODO this should be from configuration\n const imageMaxSize = 100 * 1024 * 1024 // 100 MB\n\n return async (req, res, next) => {\n if (res.destroyed) return\n if (req.method !== 'GET' && req.method !== 'HEAD') return next()\n if (!req.url?.startsWith(prefix)) return next()\n const { 0: path, 1: _search } = req.url.slice(prefix.length - 1).split('?')\n if (!path.startsWith('/') || path === '/') return next()\n\n try {\n const options = ImageUriBuilder.getOptions(path)\n const outputType = getMime(options.format)\n\n const cacheKey = [options.did, options.cid, options.preset].join('::')\n\n // Cached flow\n\n try {\n const cachedImage = await cache.get(cacheKey)\n res.statusCode = 200\n res.setHeader('x-cache', 'hit')\n res.setHeader('content-type', outputType)\n res.setHeader('cache-control', `public, max-age=31536000`) // 1 year\n res.setHeader('content-length', cachedImage.size)\n await pipelinePromise(cachedImage, res)\n return\n } catch (err) {\n if (!(err instanceof BlobNotFoundError)) {\n log.error({ cacheKey, err }, 'failed to serve cached image')\n }\n\n if (res.headersSent || res.destroyed) {\n res.destroy()\n return // nothing we can do...\n } else {\n // Ignore and move on to non-cached flow.\n res.removeHeader('x-cache')\n res.removeHeader('content-type')\n res.removeHeader('cache-control')\n res.removeHeader('content-length')\n }\n }\n\n // Non-cached flow\n\n const streamOptions: StreamBlobOptions = {\n did: options.did,\n cid: options.cid,\n }\n\n await streamBlob(ctx, streamOptions, (upstream, { did, cid, url }) => {\n // Definitely not an image ? Let's fail right away.\n if (isImageMime(upstream.headers['content-type']) === false) {\n throw createError(400, 'Not an image')\n }\n\n const contentLength = upstream.headers['content-length']\n ? Number(upstream.headers['content-length'])\n : undefined\n if (contentLength && contentLength > imageMaxSize) {\n throw createError(400, 'Image too large')\n }\n\n const streams = [\n // decompress\n ...createDecoders(upstream.headers['content-encoding'], {\n maxSize: imageMaxSize,\n maxSizeError: () => createError(400, 'Image too large'),\n }),\n // verify\n new VerifyCidTransform(cid),\n // upscale\n createImageUpscaler(options),\n // format (jpeg/webp)\n createImageProcessor(options).once('info', (info) => {\n // @NOTE sharp does emit this in time to be set as a header\n if (!res.destroyed && !res.headersSent) {\n res.setHeader('content-length', info.size)\n }\n }),\n // send downstream and save to cache in parallel. Applies backpressure\n // based on slowest consumer, avoiding memory pressure.\n fanOut(res, (branch) => {\n void cache\n .put(cacheKey, branch)\n .finally(() => {\n // Fail-safe: Make sure that the fanOut is no longer waiting for\n // this branch to drain (note that the cache implementation\n // should do this automatically).\n branch.destroy()\n })\n .catch((err: unknown) => {\n log.warn(\n { err, did, cid: cid.toString(), pds: url.origin },\n 'failed to cache processed image',\n )\n })\n }),\n ]\n\n res.statusCode = 200\n res.setHeader('content-type', outputType)\n res.setHeader('cache-control', `public, max-age=31536000`) // 1 year\n res.setHeader('x-cache', 'miss')\n\n pipeline(streams, (err) => {\n if (err) {\n log.warn(\n { err, did, cid: cid.toString(), pds: url.origin },\n 'blob resolution failed during transmission',\n )\n }\n })\n\n return streams[0]!\n })\n } catch (err) {\n if (res.headersSent || res.destroyed) {\n res.destroy()\n } else {\n res.removeHeader('content-type')\n res.removeHeader('content-length')\n res.removeHeader('cache-control')\n res.removeHeader('x-cache')\n\n if (err instanceof BadPathError) {\n next(createError(400, err))\n } else if (err instanceof VerifyCidError) {\n next(createError(404, 'Blob not found', err))\n } else if (isHttpError(err)) {\n next(err)\n } else {\n next(createError(502, 'Upstream Error', { cause: err }))\n }\n }\n }\n }\n}\n\nfunction isImageMime(\n contentType: string | string[] | undefined,\n): undefined | boolean {\n if (contentType == null || contentType === 'application/octet-stream') {\n return undefined // maybe\n }\n if (Array.isArray(contentType)) {\n if (contentType.length === 0) return undefined // should never happen\n if (contentType.length === 1) return isImageMime(contentType[0])\n return contentType.every(isImageMime) // Should we throw a 502 here?\n }\n return contentType.startsWith('image/')\n}\n\nfunction getMime(format: Options['format']) {\n const mime = formatsToMimes.get(format)\n if (!mime) throw new Error('Unknown format')\n return mime\n}\n\nexport interface BlobCache {\n get(fileId: string): Promise<Readable & { size: number }>\n put(fileId: string, stream: Readable): Promise<void>\n clear(fileId: string): Promise<void>\n clearAll(): Promise<void>\n}\n\nexport class BlobDiskCache implements BlobCache {\n tempDir: string\n constructor(basePath?: string) {\n this.tempDir = basePath || path.join(os.tmpdir(), 'bsky--processed-images')\n if (!path.isAbsolute(this.tempDir)) {\n throw new Error('Must provide an absolute path')\n }\n try {\n fsSync.mkdirSync(this.tempDir, { recursive: true })\n } catch (err) {\n // All good if cache dir already exists\n if (isErrnoException(err) && err.code === 'EEXIST') return\n }\n }\n\n async get(fileId: string) {\n try {\n const handle = await fs.open(path.join(this.tempDir, fileId), 'r')\n const { size } = await handle.stat()\n if (size === 0) {\n throw new BlobNotFoundError()\n }\n return Object.assign(handle.createReadStream(), { size })\n } catch (err) {\n if (isErrnoException(err) && err.code === 'ENOENT') {\n throw new BlobNotFoundError()\n }\n throw err\n }\n }\n\n async put(fileId: string, stream: Readable) {\n const filename = path.join(this.tempDir, fileId)\n try {\n await fs.writeFile(filename, stream, { flag: 'wx' })\n } catch (err) {\n // Do not overwrite existing file, just ignore the error\n if (isErrnoException(err) && err.code === 'EEXIST') return\n throw err\n } finally {\n stream.destroy()\n }\n }\n\n async clear(fileId: string) {\n const filename = path.join(this.tempDir, fileId)\n await fs.rm(filename, { force: true })\n }\n\n async clearAll() {\n await fs.rm(this.tempDir, { recursive: true, force: true })\n }\n}\n"]}
|
|
@@ -51,11 +51,11 @@ type ReportAction = {
|
|
|
51
51
|
*/
|
|
52
52
|
"ids"?: (number)[];
|
|
53
53
|
/**
|
|
54
|
-
* Target reports matching these report types on the subject (fully qualified NSIDs)
|
|
54
|
+
* Target reports matching these report types on the subject (fully qualified NSIDs). Excludes assigned and escalated reports.
|
|
55
55
|
*/
|
|
56
56
|
"types"?: (string)[];
|
|
57
57
|
/**
|
|
58
|
-
* Target ALL reports on the subject
|
|
58
|
+
* Target ALL reports on the subject. Excludes assigned and escalated reports.
|
|
59
59
|
*/
|
|
60
60
|
"all"?: boolean;
|
|
61
61
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"emitEvent.defs.js","sourceRoot":"","sources":["../../../../../src/lexicons/tools/ozone/moderation/emitEvent.defs.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * THIS FILE WAS GENERATED BY \"@atproto/lex\". DO NOT EDIT.\n */\n\nimport { l } from \"@atproto/lex\";\nimport * as ModerationDefs from \"./defs.defs.js\";\nimport * as AdminDefs from \"../../../com/atproto/admin/defs.defs.js\";\nimport * as RepoStrongRef from \"../../../com/atproto/repo/strongRef.defs.js\";\n\nconst $nsid = \"tools.ozone.moderation.emitEvent\";\n\ntype $nsid = typeof $nsid;\n\nexport { $nsid };\n\nexport const $params = /*#__PURE__*/ l.params();\n\nexport type $Params = l.InferOutput<typeof $params>;\n\nexport const $input = /*#__PURE__*/ l.jsonPayload({\"event\":/*#__PURE__*/ l.typedUnion([/*#__PURE__*/ l.typedRef<ModerationDefs.ModEventTakedown>((() => ModerationDefs.modEventTakedown) as any),/*#__PURE__*/ l.typedRef<ModerationDefs.ModEventAcknowledge>((() => ModerationDefs.modEventAcknowledge) as any),/*#__PURE__*/ l.typedRef<ModerationDefs.ModEventEscalate>((() => ModerationDefs.modEventEscalate) as any),/*#__PURE__*/ l.typedRef<ModerationDefs.ModEventComment>((() => ModerationDefs.modEventComment) as any),/*#__PURE__*/ l.typedRef<ModerationDefs.ModEventLabel>((() => ModerationDefs.modEventLabel) as any),/*#__PURE__*/ l.typedRef<ModerationDefs.ModEventReport>((() => ModerationDefs.modEventReport) as any),/*#__PURE__*/ l.typedRef<ModerationDefs.ModEventMute>((() => ModerationDefs.modEventMute) as any),/*#__PURE__*/ l.typedRef<ModerationDefs.ModEventUnmute>((() => ModerationDefs.modEventUnmute) as any),/*#__PURE__*/ l.typedRef<ModerationDefs.ModEventMuteReporter>((() => ModerationDefs.modEventMuteReporter) as any),/*#__PURE__*/ l.typedRef<ModerationDefs.ModEventUnmuteReporter>((() => ModerationDefs.modEventUnmuteReporter) as any),/*#__PURE__*/ l.typedRef<ModerationDefs.ModEventReverseTakedown>((() => ModerationDefs.modEventReverseTakedown) as any),/*#__PURE__*/ l.typedRef<ModerationDefs.ModEventResolveAppeal>((() => ModerationDefs.modEventResolveAppeal) as any),/*#__PURE__*/ l.typedRef<ModerationDefs.ModEventEmail>((() => ModerationDefs.modEventEmail) as any),/*#__PURE__*/ l.typedRef<ModerationDefs.ModEventDivert>((() => ModerationDefs.modEventDivert) as any),/*#__PURE__*/ l.typedRef<ModerationDefs.ModEventTag>((() => ModerationDefs.modEventTag) as any),/*#__PURE__*/ l.typedRef<ModerationDefs.AccountEvent>((() => ModerationDefs.accountEvent) as any),/*#__PURE__*/ l.typedRef<ModerationDefs.IdentityEvent>((() => ModerationDefs.identityEvent) as any),/*#__PURE__*/ l.typedRef<ModerationDefs.RecordEvent>((() => ModerationDefs.recordEvent) as any),/*#__PURE__*/ l.typedRef<ModerationDefs.ModEventPriorityScore>((() => ModerationDefs.modEventPriorityScore) as any),/*#__PURE__*/ l.typedRef<ModerationDefs.AgeAssuranceEvent>((() => ModerationDefs.ageAssuranceEvent) as any),/*#__PURE__*/ l.typedRef<ModerationDefs.AgeAssuranceOverrideEvent>((() => ModerationDefs.ageAssuranceOverrideEvent) as any),/*#__PURE__*/ l.typedRef<ModerationDefs.AgeAssurancePurgeEvent>((() => ModerationDefs.ageAssurancePurgeEvent) as any),/*#__PURE__*/ l.typedRef<ModerationDefs.RevokeAccountCredentialsEvent>((() => ModerationDefs.revokeAccountCredentialsEvent) as any),/*#__PURE__*/ l.typedRef<ModerationDefs.ScheduleTakedownEvent>((() => ModerationDefs.scheduleTakedownEvent) as any),/*#__PURE__*/ l.typedRef<ModerationDefs.CancelScheduledTakedownEvent>((() => ModerationDefs.cancelScheduledTakedownEvent) as any)], false),\"subject\":/*#__PURE__*/ l.typedUnion([/*#__PURE__*/ l.typedRef<AdminDefs.RepoRef>((() => AdminDefs.repoRef) as any),/*#__PURE__*/ l.typedRef<RepoStrongRef.Main>((() => RepoStrongRef.main) as any)], false),\"subjectBlobCids\":/*#__PURE__*/ l.optional(/*#__PURE__*/ l.array(/*#__PURE__*/ l.string({\"format\":\"cid\"}), )),\"createdBy\":/*#__PURE__*/ l.string({\"format\":\"did\"}),\"modTool\":/*#__PURE__*/ l.optional(/*#__PURE__*/ l.ref<ModerationDefs.ModTool>((() => ModerationDefs.modTool) as any)),\"externalId\":/*#__PURE__*/ l.optional(/*#__PURE__*/ l.string()),\"reportAction\":/*#__PURE__*/ l.optional(/*#__PURE__*/ l.ref<ReportAction>((() => reportAction) as any))});\n\nexport type $Input<B = l.BinaryData> = l.InferPayload<typeof $input, B>;\nexport type $InputBody<B = l.BinaryData> = l.InferPayloadBody<typeof $input, B>;\n\nexport const $output = /*#__PURE__*/ l.payload(\"application/json\", /*#__PURE__*/ l.ref<ModerationDefs.ModEventView>((() => ModerationDefs.modEventView) as any));\n\nexport type $Output<B = l.BinaryData> = l.InferPayload<typeof $output, B>;\nexport type $OutputBody<B = l.BinaryData> = l.InferPayloadBody<typeof $output, B>;\n\n/** Take a moderation action on an actor. */\nconst main = /*#__PURE__*/ l.procedure($nsid, $params, $input, $output, [\"SubjectHasAction\",\"DuplicateExternalId\"]);\n\nexport { main };\n\nconst $lxm = $nsid;\n\ntype $lxm = typeof $lxm;\n\nexport { $lxm };\n\n/** Target specific reports when emitting a moderation event */\ntype ReportAction = { $type?: \"tools.ozone.moderation.emitEvent#reportAction\";\n\n /**\n * Target specific report IDs\n */\n \"ids\"?:(number)[];\n\n /**\n * Target reports matching these report types on the subject (fully qualified NSIDs)\n */\n \"types\"?:(string)[];\n\n /**\n * Target ALL reports on the subject\n */\n \"all\"?:boolean;\n\n /**\n * Note to send to reporter(s) when actioning their report\n */\n \"note\"?:string;\n\n /**\n * Set true when the report activities are created by an automated process.\n */\n \"isAutomated\"?:boolean };\n\nexport type { ReportAction };\n\n/** Target specific reports when emitting a moderation event */\nconst reportAction = /*#__PURE__*/ l.typedObject<ReportAction>($nsid, \"reportAction\", /*#__PURE__*/ l.object({\"ids\":/*#__PURE__*/ l.optional(/*#__PURE__*/ l.array(/*#__PURE__*/ l.integer(), )),\"types\":/*#__PURE__*/ l.optional(/*#__PURE__*/ l.array(/*#__PURE__*/ l.string(), )),\"all\":/*#__PURE__*/ l.optional(/*#__PURE__*/ l.boolean()),\"note\":/*#__PURE__*/ l.optional(/*#__PURE__*/ l.string()),\"isAutomated\":/*#__PURE__*/ l.optional(/*#__PURE__*/ l.boolean())}));\n\nexport { reportAction };\n"]}
|
|
1
|
+
{"version":3,"file":"emitEvent.defs.js","sourceRoot":"","sources":["../../../../../src/lexicons/tools/ozone/moderation/emitEvent.defs.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * THIS FILE WAS GENERATED BY \"@atproto/lex\". DO NOT EDIT.\n */\n\nimport { l } from \"@atproto/lex\";\nimport * as ModerationDefs from \"./defs.defs.js\";\nimport * as AdminDefs from \"../../../com/atproto/admin/defs.defs.js\";\nimport * as RepoStrongRef from \"../../../com/atproto/repo/strongRef.defs.js\";\n\nconst $nsid = \"tools.ozone.moderation.emitEvent\";\n\ntype $nsid = typeof $nsid;\n\nexport { $nsid };\n\nexport const $params = /*#__PURE__*/ l.params();\n\nexport type $Params = l.InferOutput<typeof $params>;\n\nexport const $input = /*#__PURE__*/ l.jsonPayload({\"event\":/*#__PURE__*/ l.typedUnion([/*#__PURE__*/ l.typedRef<ModerationDefs.ModEventTakedown>((() => ModerationDefs.modEventTakedown) as any),/*#__PURE__*/ l.typedRef<ModerationDefs.ModEventAcknowledge>((() => ModerationDefs.modEventAcknowledge) as any),/*#__PURE__*/ l.typedRef<ModerationDefs.ModEventEscalate>((() => ModerationDefs.modEventEscalate) as any),/*#__PURE__*/ l.typedRef<ModerationDefs.ModEventComment>((() => ModerationDefs.modEventComment) as any),/*#__PURE__*/ l.typedRef<ModerationDefs.ModEventLabel>((() => ModerationDefs.modEventLabel) as any),/*#__PURE__*/ l.typedRef<ModerationDefs.ModEventReport>((() => ModerationDefs.modEventReport) as any),/*#__PURE__*/ l.typedRef<ModerationDefs.ModEventMute>((() => ModerationDefs.modEventMute) as any),/*#__PURE__*/ l.typedRef<ModerationDefs.ModEventUnmute>((() => ModerationDefs.modEventUnmute) as any),/*#__PURE__*/ l.typedRef<ModerationDefs.ModEventMuteReporter>((() => ModerationDefs.modEventMuteReporter) as any),/*#__PURE__*/ l.typedRef<ModerationDefs.ModEventUnmuteReporter>((() => ModerationDefs.modEventUnmuteReporter) as any),/*#__PURE__*/ l.typedRef<ModerationDefs.ModEventReverseTakedown>((() => ModerationDefs.modEventReverseTakedown) as any),/*#__PURE__*/ l.typedRef<ModerationDefs.ModEventResolveAppeal>((() => ModerationDefs.modEventResolveAppeal) as any),/*#__PURE__*/ l.typedRef<ModerationDefs.ModEventEmail>((() => ModerationDefs.modEventEmail) as any),/*#__PURE__*/ l.typedRef<ModerationDefs.ModEventDivert>((() => ModerationDefs.modEventDivert) as any),/*#__PURE__*/ l.typedRef<ModerationDefs.ModEventTag>((() => ModerationDefs.modEventTag) as any),/*#__PURE__*/ l.typedRef<ModerationDefs.AccountEvent>((() => ModerationDefs.accountEvent) as any),/*#__PURE__*/ l.typedRef<ModerationDefs.IdentityEvent>((() => ModerationDefs.identityEvent) as any),/*#__PURE__*/ l.typedRef<ModerationDefs.RecordEvent>((() => ModerationDefs.recordEvent) as any),/*#__PURE__*/ l.typedRef<ModerationDefs.ModEventPriorityScore>((() => ModerationDefs.modEventPriorityScore) as any),/*#__PURE__*/ l.typedRef<ModerationDefs.AgeAssuranceEvent>((() => ModerationDefs.ageAssuranceEvent) as any),/*#__PURE__*/ l.typedRef<ModerationDefs.AgeAssuranceOverrideEvent>((() => ModerationDefs.ageAssuranceOverrideEvent) as any),/*#__PURE__*/ l.typedRef<ModerationDefs.AgeAssurancePurgeEvent>((() => ModerationDefs.ageAssurancePurgeEvent) as any),/*#__PURE__*/ l.typedRef<ModerationDefs.RevokeAccountCredentialsEvent>((() => ModerationDefs.revokeAccountCredentialsEvent) as any),/*#__PURE__*/ l.typedRef<ModerationDefs.ScheduleTakedownEvent>((() => ModerationDefs.scheduleTakedownEvent) as any),/*#__PURE__*/ l.typedRef<ModerationDefs.CancelScheduledTakedownEvent>((() => ModerationDefs.cancelScheduledTakedownEvent) as any)], false),\"subject\":/*#__PURE__*/ l.typedUnion([/*#__PURE__*/ l.typedRef<AdminDefs.RepoRef>((() => AdminDefs.repoRef) as any),/*#__PURE__*/ l.typedRef<RepoStrongRef.Main>((() => RepoStrongRef.main) as any)], false),\"subjectBlobCids\":/*#__PURE__*/ l.optional(/*#__PURE__*/ l.array(/*#__PURE__*/ l.string({\"format\":\"cid\"}), )),\"createdBy\":/*#__PURE__*/ l.string({\"format\":\"did\"}),\"modTool\":/*#__PURE__*/ l.optional(/*#__PURE__*/ l.ref<ModerationDefs.ModTool>((() => ModerationDefs.modTool) as any)),\"externalId\":/*#__PURE__*/ l.optional(/*#__PURE__*/ l.string()),\"reportAction\":/*#__PURE__*/ l.optional(/*#__PURE__*/ l.ref<ReportAction>((() => reportAction) as any))});\n\nexport type $Input<B = l.BinaryData> = l.InferPayload<typeof $input, B>;\nexport type $InputBody<B = l.BinaryData> = l.InferPayloadBody<typeof $input, B>;\n\nexport const $output = /*#__PURE__*/ l.payload(\"application/json\", /*#__PURE__*/ l.ref<ModerationDefs.ModEventView>((() => ModerationDefs.modEventView) as any));\n\nexport type $Output<B = l.BinaryData> = l.InferPayload<typeof $output, B>;\nexport type $OutputBody<B = l.BinaryData> = l.InferPayloadBody<typeof $output, B>;\n\n/** Take a moderation action on an actor. */\nconst main = /*#__PURE__*/ l.procedure($nsid, $params, $input, $output, [\"SubjectHasAction\",\"DuplicateExternalId\"]);\n\nexport { main };\n\nconst $lxm = $nsid;\n\ntype $lxm = typeof $lxm;\n\nexport { $lxm };\n\n/** Target specific reports when emitting a moderation event */\ntype ReportAction = { $type?: \"tools.ozone.moderation.emitEvent#reportAction\";\n\n /**\n * Target specific report IDs\n */\n \"ids\"?:(number)[];\n\n /**\n * Target reports matching these report types on the subject (fully qualified NSIDs). Excludes assigned and escalated reports.\n */\n \"types\"?:(string)[];\n\n /**\n * Target ALL reports on the subject. Excludes assigned and escalated reports.\n */\n \"all\"?:boolean;\n\n /**\n * Note to send to reporter(s) when actioning their report\n */\n \"note\"?:string;\n\n /**\n * Set true when the report activities are created by an automated process.\n */\n \"isAutomated\"?:boolean };\n\nexport type { ReportAction };\n\n/** Target specific reports when emitting a moderation event */\nconst reportAction = /*#__PURE__*/ l.typedObject<ReportAction>($nsid, \"reportAction\", /*#__PURE__*/ l.object({\"ids\":/*#__PURE__*/ l.optional(/*#__PURE__*/ l.array(/*#__PURE__*/ l.integer(), )),\"types\":/*#__PURE__*/ l.optional(/*#__PURE__*/ l.array(/*#__PURE__*/ l.string(), )),\"all\":/*#__PURE__*/ l.optional(/*#__PURE__*/ l.boolean()),\"note\":/*#__PURE__*/ l.optional(/*#__PURE__*/ l.string()),\"isAutomated\":/*#__PURE__*/ l.optional(/*#__PURE__*/ l.boolean())}));\n\nexport { reportAction };\n"]}
|
|
@@ -23,7 +23,7 @@ export declare const $output: l.Payload<"application/json", l.ObjectSchema<{
|
|
|
23
23
|
}>>;
|
|
24
24
|
export type $Output<B = l.BinaryData> = l.InferPayload<typeof $output, B>;
|
|
25
25
|
export type $OutputBody<B = l.BinaryData> = l.InferPayloadBody<typeof $output, B>;
|
|
26
|
-
/** Close
|
|
26
|
+
/** Close eligible reports on a subject matching the given criteria. Assigned and escalated reports are excluded. Reports whose current status does not permit a transition to closed are skipped silently. Intended for automated flows that resolve reports without taking action on the subject. */
|
|
27
27
|
declare const main: l.Procedure<"tools.ozone.report.closeReports", l.ParamsSchema<{}>, l.Payload<"application/json", l.ObjectSchema<{
|
|
28
28
|
subject: l.StringSchema<{
|
|
29
29
|
readonly format: "uri";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"closeReports.defs.d.ts","sourceRoot":"","sources":["../../../../../src/lexicons/tools/ozone/report/closeReports.defs.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,CAAC,EAAE,MAAM,cAAc,CAAC;AAEjC,QAAA,MAAM,KAAK,oCAAoC,CAAC;AAEhD,KAAK,KAAK,GAAG,OAAO,KAAK,CAAC;AAE1B,OAAO,EAAE,KAAK,EAAE,CAAC;AAEjB,eAAO,MAAM,OAAO,oBAA2B,CAAC;AAEhD,MAAM,MAAM,OAAO,GAAG,CAAC,CAAC,WAAW,CAAC,OAAO,OAAO,CAAC,CAAC;AAEpD,eAAO,MAAM,MAAM;;;;;;;;;;GAA4lB,CAAC;AAEhnB,MAAM,MAAM,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,UAAU,IAAI,CAAC,CAAC,YAAY,CAAC,OAAO,MAAM,EAAE,CAAC,CAAC,CAAC;AACxE,MAAM,MAAM,UAAU,CAAC,CAAC,GAAG,CAAC,CAAC,UAAU,IAAI,CAAC,CAAC,gBAAgB,CAAC,OAAO,MAAM,EAAE,CAAC,CAAC,CAAC;AAEhF,eAAO,MAAM,OAAO;;;GAAwI,CAAC;AAE7J,MAAM,MAAM,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,UAAU,IAAI,CAAC,CAAC,YAAY,CAAC,OAAO,OAAO,EAAE,CAAC,CAAC,CAAC;AAC1E,MAAM,MAAM,WAAW,CAAC,CAAC,GAAG,CAAC,CAAC,UAAU,IAAI,CAAC,CAAC,gBAAgB,CAAC,OAAO,OAAO,EAAE,CAAC,CAAC,CAAC;AAElF,
|
|
1
|
+
{"version":3,"file":"closeReports.defs.d.ts","sourceRoot":"","sources":["../../../../../src/lexicons/tools/ozone/report/closeReports.defs.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,CAAC,EAAE,MAAM,cAAc,CAAC;AAEjC,QAAA,MAAM,KAAK,oCAAoC,CAAC;AAEhD,KAAK,KAAK,GAAG,OAAO,KAAK,CAAC;AAE1B,OAAO,EAAE,KAAK,EAAE,CAAC;AAEjB,eAAO,MAAM,OAAO,oBAA2B,CAAC;AAEhD,MAAM,MAAM,OAAO,GAAG,CAAC,CAAC,WAAW,CAAC,OAAO,OAAO,CAAC,CAAC;AAEpD,eAAO,MAAM,MAAM;;;;;;;;;;GAA4lB,CAAC;AAEhnB,MAAM,MAAM,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,UAAU,IAAI,CAAC,CAAC,YAAY,CAAC,OAAO,MAAM,EAAE,CAAC,CAAC,CAAC;AACxE,MAAM,MAAM,UAAU,CAAC,CAAC,GAAG,CAAC,CAAC,UAAU,IAAI,CAAC,CAAC,gBAAgB,CAAC,OAAO,MAAM,EAAE,CAAC,CAAC,CAAC;AAEhF,eAAO,MAAM,OAAO;;;GAAwI,CAAC;AAE7J,MAAM,MAAM,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,UAAU,IAAI,CAAC,CAAC,YAAY,CAAC,OAAO,OAAO,EAAE,CAAC,CAAC,CAAC;AAC1E,MAAM,MAAM,WAAW,CAAC,CAAC,GAAG,CAAC,CAAC,UAAU,IAAI,CAAC,CAAC,gBAAgB,CAAC,OAAO,OAAO,EAAE,CAAC,CAAC,CAAC;AAElF,sSAAsS;AACtS,QAAA,MAAM,IAAI;;;;;;;;;;;;;eAA6D,CAAC;AAExE,OAAO,EAAE,IAAI,EAAE,CAAC;AAEhB,QAAA,MAAM,IAAI,oCAAQ,CAAC;AAEnB,KAAK,IAAI,GAAG,OAAO,IAAI,CAAC;AAExB,OAAO,EAAE,IAAI,EAAE,CAAC"}
|
|
@@ -7,7 +7,7 @@ export { $nsid };
|
|
|
7
7
|
export const $params = /*#__PURE__*/ l.params();
|
|
8
8
|
export const $input = /*#__PURE__*/ l.jsonPayload({ "subject": /*#__PURE__*/ l.string({ "format": "uri" }), "includeAllOwned": /*#__PURE__*/ l.optional(/*#__PURE__*/ l.withDefault(/*#__PURE__*/ l.boolean(), false)), "reportTypes": /*#__PURE__*/ l.optional(/*#__PURE__*/ l.array(/*#__PURE__*/ l.string())), "excludedReportTypes": /*#__PURE__*/ l.optional(/*#__PURE__*/ l.array(/*#__PURE__*/ l.string())), "internalNote": /*#__PURE__*/ l.optional(/*#__PURE__*/ l.string()), "isAutomated": /*#__PURE__*/ l.optional(/*#__PURE__*/ l.withDefault(/*#__PURE__*/ l.boolean(), false)), "meta": /*#__PURE__*/ l.optional(/*#__PURE__*/ l.lexMap()) });
|
|
9
9
|
export const $output = /*#__PURE__*/ l.jsonPayload({ "closedCount": /*#__PURE__*/ l.integer(), "reportIds": /*#__PURE__*/ l.array(/*#__PURE__*/ l.integer()) });
|
|
10
|
-
/** Close
|
|
10
|
+
/** Close eligible reports on a subject matching the given criteria. Assigned and escalated reports are excluded. Reports whose current status does not permit a transition to closed are skipped silently. Intended for automated flows that resolve reports without taking action on the subject. */
|
|
11
11
|
const main = /*#__PURE__*/ l.procedure($nsid, $params, $input, $output);
|
|
12
12
|
export { main };
|
|
13
13
|
const $lxm = $nsid;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"closeReports.defs.js","sourceRoot":"","sources":["../../../../../src/lexicons/tools/ozone/report/closeReports.defs.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,cAAc,CAAC;AAEjC,MAAM,KAAK,GAAG,iCAAiC,CAAC;AAIhD,OAAO,EAAE,KAAK,EAAE,CAAC;AAEjB,MAAM,CAAC,MAAM,OAAO,GAAG,aAAa,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;AAIhD,MAAM,CAAC,MAAM,MAAM,GAAG,aAAa,CAAC,CAAC,CAAC,WAAW,CAAC,EAAC,SAAS,EAAC,aAAa,CAAC,CAAC,CAAC,MAAM,CAAC,EAAC,QAAQ,EAAC,KAAK,EAAC,CAAC,EAAC,iBAAiB,EAAC,aAAa,CAAC,CAAC,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC,CAAC,WAAW,CAAC,aAAa,CAAC,CAAC,CAAC,OAAO,EAAE,EAAE,KAAK,CAAC,CAAC,EAAC,aAAa,EAAC,aAAa,CAAC,CAAC,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC,CAAC,MAAM,EAAE,CAAG,CAAC,EAAC,qBAAqB,EAAC,aAAa,CAAC,CAAC,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC,CAAC,MAAM,EAAE,CAAG,CAAC,EAAC,cAAc,EAAC,aAAa,CAAC,CAAC,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,EAAC,aAAa,EAAC,aAAa,CAAC,CAAC,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC,CAAC,WAAW,CAAC,aAAa,CAAC,CAAC,CAAC,OAAO,EAAE,EAAE,KAAK,CAAC,CAAC,EAAC,MAAM,EAAC,aAAa,CAAC,CAAC,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,EAAC,CAAC,CAAC;AAKhnB,MAAM,CAAC,MAAM,OAAO,GAAG,aAAa,CAAC,CAAC,CAAC,WAAW,CAAC,EAAC,aAAa,EAAC,aAAa,CAAC,CAAC,CAAC,OAAO,EAAE,EAAC,WAAW,EAAC,aAAa,CAAC,CAAC,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC,CAAC,OAAO,EAAE,CAAG,EAAC,CAAC,CAAC;AAK7J,
|
|
1
|
+
{"version":3,"file":"closeReports.defs.js","sourceRoot":"","sources":["../../../../../src/lexicons/tools/ozone/report/closeReports.defs.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,cAAc,CAAC;AAEjC,MAAM,KAAK,GAAG,iCAAiC,CAAC;AAIhD,OAAO,EAAE,KAAK,EAAE,CAAC;AAEjB,MAAM,CAAC,MAAM,OAAO,GAAG,aAAa,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;AAIhD,MAAM,CAAC,MAAM,MAAM,GAAG,aAAa,CAAC,CAAC,CAAC,WAAW,CAAC,EAAC,SAAS,EAAC,aAAa,CAAC,CAAC,CAAC,MAAM,CAAC,EAAC,QAAQ,EAAC,KAAK,EAAC,CAAC,EAAC,iBAAiB,EAAC,aAAa,CAAC,CAAC,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC,CAAC,WAAW,CAAC,aAAa,CAAC,CAAC,CAAC,OAAO,EAAE,EAAE,KAAK,CAAC,CAAC,EAAC,aAAa,EAAC,aAAa,CAAC,CAAC,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC,CAAC,MAAM,EAAE,CAAG,CAAC,EAAC,qBAAqB,EAAC,aAAa,CAAC,CAAC,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC,CAAC,MAAM,EAAE,CAAG,CAAC,EAAC,cAAc,EAAC,aAAa,CAAC,CAAC,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,EAAC,aAAa,EAAC,aAAa,CAAC,CAAC,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC,CAAC,WAAW,CAAC,aAAa,CAAC,CAAC,CAAC,OAAO,EAAE,EAAE,KAAK,CAAC,CAAC,EAAC,MAAM,EAAC,aAAa,CAAC,CAAC,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,EAAC,CAAC,CAAC;AAKhnB,MAAM,CAAC,MAAM,OAAO,GAAG,aAAa,CAAC,CAAC,CAAC,WAAW,CAAC,EAAC,aAAa,EAAC,aAAa,CAAC,CAAC,CAAC,OAAO,EAAE,EAAC,WAAW,EAAC,aAAa,CAAC,CAAC,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC,CAAC,OAAO,EAAE,CAAG,EAAC,CAAC,CAAC;AAK7J,sSAAsS;AACtS,MAAM,IAAI,GAAG,aAAa,CAAC,CAAC,CAAC,SAAS,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC;AAExE,OAAO,EAAE,IAAI,EAAE,CAAC;AAEhB,MAAM,IAAI,GAAG,KAAK,CAAC;AAInB,OAAO,EAAE,IAAI,EAAE,CAAC","sourcesContent":["/*\n * THIS FILE WAS GENERATED BY \"@atproto/lex\". DO NOT EDIT.\n */\n\nimport { l } from \"@atproto/lex\";\n\nconst $nsid = \"tools.ozone.report.closeReports\";\n\ntype $nsid = typeof $nsid;\n\nexport { $nsid };\n\nexport const $params = /*#__PURE__*/ l.params();\n\nexport type $Params = l.InferOutput<typeof $params>;\n\nexport const $input = /*#__PURE__*/ l.jsonPayload({\"subject\":/*#__PURE__*/ l.string({\"format\":\"uri\"}),\"includeAllOwned\":/*#__PURE__*/ l.optional(/*#__PURE__*/ l.withDefault(/*#__PURE__*/ l.boolean(), false)),\"reportTypes\":/*#__PURE__*/ l.optional(/*#__PURE__*/ l.array(/*#__PURE__*/ l.string(), )),\"excludedReportTypes\":/*#__PURE__*/ l.optional(/*#__PURE__*/ l.array(/*#__PURE__*/ l.string(), )),\"internalNote\":/*#__PURE__*/ l.optional(/*#__PURE__*/ l.string()),\"isAutomated\":/*#__PURE__*/ l.optional(/*#__PURE__*/ l.withDefault(/*#__PURE__*/ l.boolean(), false)),\"meta\":/*#__PURE__*/ l.optional(/*#__PURE__*/ l.lexMap())});\n\nexport type $Input<B = l.BinaryData> = l.InferPayload<typeof $input, B>;\nexport type $InputBody<B = l.BinaryData> = l.InferPayloadBody<typeof $input, B>;\n\nexport const $output = /*#__PURE__*/ l.jsonPayload({\"closedCount\":/*#__PURE__*/ l.integer(),\"reportIds\":/*#__PURE__*/ l.array(/*#__PURE__*/ l.integer(), )});\n\nexport type $Output<B = l.BinaryData> = l.InferPayload<typeof $output, B>;\nexport type $OutputBody<B = l.BinaryData> = l.InferPayloadBody<typeof $output, B>;\n\n/** Close eligible reports on a subject matching the given criteria. Assigned and escalated reports are excluded. Reports whose current status does not permit a transition to closed are skipped silently. Intended for automated flows that resolve reports without taking action on the subject. */\nconst main = /*#__PURE__*/ l.procedure($nsid, $params, $input, $output);\n\nexport { main };\n\nconst $lxm = $nsid;\n\ntype $lxm = typeof $lxm;\n\nexport { $lxm };\n"]}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { NotificationsV2Algorithm } from '../config.js';
|
|
1
2
|
import type { app } from '../lexicons/index.js';
|
|
2
3
|
export type HydrationSource = 'known_likers' | 'known_followers' | 'activity_subscriptions';
|
|
3
4
|
/** XRPC method that owns a hydration; set on `HydrateCtx` from the method's `$lxm`. */
|
|
@@ -19,8 +20,8 @@ type ExternalHydrationAttributes = {
|
|
|
19
20
|
};
|
|
20
21
|
/**
|
|
21
22
|
* Generic record lookup work of one external hydration traversal, across the
|
|
22
|
-
* root and all nested dependency batches. Excludes profile/label hydration
|
|
23
|
-
* backlink sample/count RPCs
|
|
23
|
+
* root and all nested dependency batches. Excludes profile/label hydration
|
|
24
|
+
* and backlink sample/count RPCs.
|
|
24
25
|
*/
|
|
25
26
|
export type ExternalHydrationWork = {
|
|
26
27
|
/**
|
|
@@ -84,10 +85,16 @@ declare class EventReporter {
|
|
|
84
85
|
externalHydrationTraversal({ xrpcEndpoint, root, outcome, durationMs, recordLookups, batches, maxPass, roots, rootsResolved, dependencies, dependenciesResolved, }: ExternalHydrationWork & ExternalHydrationAttributes & {
|
|
85
86
|
durationMs: number;
|
|
86
87
|
}): void;
|
|
87
|
-
/** Reports
|
|
88
|
-
|
|
88
|
+
/** Reports compute time once per grouping invocation, excluding dataplane reads. */
|
|
89
|
+
notificationGrouping({ algorithm, durationMs, }: {
|
|
90
|
+
algorithm: NotificationsV2Algorithm;
|
|
91
|
+
durationMs: number;
|
|
92
|
+
}): void;
|
|
93
|
+
/** Reports dataplane fetches and raw-item counts once per completed page. */
|
|
94
|
+
groupedNotifications({ itemsFetched, itemsReturned, dataplaneFetches, }: {
|
|
89
95
|
itemsFetched: number;
|
|
90
96
|
itemsReturned: number;
|
|
97
|
+
dataplaneFetches: number;
|
|
91
98
|
}): void;
|
|
92
99
|
}
|
|
93
100
|
export declare const events: EventReporter;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../../src/telemetry/events.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../../src/telemetry/events.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,cAAc,CAAA;AAC5D,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,sBAAsB,CAAA;AAO/C,MAAM,MAAM,eAAe,GACzB,cAAc,GAAG,iBAAiB,GAAG,wBAAwB,CAAA;AAE/D,uFAAuF;AACvF,MAAM,MAAM,qBAAqB,GAC7B,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,oBAAoB,CAAC,IAAI,GACxC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,uBAAuB,CAAC,IAAI,CAAA;AAEnD,iEAAiE;AACjE,MAAM,MAAM,6BAA6B,GAAG,qBAAqB,GAAG,SAAS,CAAA;AAE7E,6DAA6D;AAC7D,MAAM,MAAM,qBAAqB,GAAG,MAAM,GAAG,MAAM,GAAG,cAAc,CAAA;AACpE;;;GAGG;AACH,MAAM,MAAM,wBAAwB,GAClC,WAAW,GAAG,QAAQ,GAAG,SAAS,GAAG,QAAQ,CAAA;AAE/C,oEAAoE;AACpE,KAAK,2BAA2B,GAAG;IACjC,YAAY,EAAE,6BAA6B,CAAA;IAC3C,IAAI,EAAE,qBAAqB,CAAA;IAC3B,OAAO,EAAE,wBAAwB,CAAA;CAClC,CAAA;AAED;;;;GAIG;AACH,MAAM,MAAM,qBAAqB,GAAG;IAClC;;;;OAIG;IACH,aAAa,EAAE,MAAM,CAAA;IACrB,4EAA4E;IAC5E,OAAO,EAAE,MAAM,CAAA;IACf,wEAAwE;IACxE,OAAO,EAAE,MAAM,CAAA;IACf;;;OAGG;IACH,KAAK,EAAE,MAAM,CAAA;IACb,2DAA2D;IAC3D,aAAa,EAAE,MAAM,CAAA;IACrB;;;;OAIG;IACH,YAAY,EAAE,MAAM,CAAA;IACpB,kEAAkE;IAClE,oBAAoB,EAAE,MAAM,CAAA;CAC7B,CAAA;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,cAAM,aAAa;;IAmJjB,eAAe,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,EAAE;QAAE,MAAM,EAAE,eAAe,CAAC;QAAC,GAAG,EAAE,OAAO,CAAA;KAAE,QAQzE;IAED;;;;;OAKG;IACH,0BAA0B,CAAC,EACzB,YAAY,EACZ,IAAI,EACJ,OAAO,EACP,UAAU,EACV,aAAa,EACb,OAAO,EACP,OAAO,EACP,KAAK,EACL,aAAa,EACb,YAAY,EACZ,oBAAoB,GACrB,EAAE,qBAAqB,GACtB,2BAA2B,GAAG;QAAE,UAAU,EAAE,MAAM,CAAA;KAAE,QA0CrD;IAED,oFAAoF;IACpF,oBAAoB,CAAC,EACnB,SAAS,EACT,UAAU,GACX,EAAE;QACD,SAAS,EAAE,wBAAwB,CAAA;QACnC,UAAU,EAAE,MAAM,CAAA;KACnB,QAMA;IAED,6EAA6E;IAC7E,oBAAoB,CAAC,EACnB,YAAY,EACZ,aAAa,EACb,gBAAgB,GACjB,EAAE;QACD,YAAY,EAAE,MAAM,CAAA;QACpB,aAAa,EAAE,MAAM,CAAA;QACrB,gBAAgB,EAAE,MAAM,CAAA;KACzB,QAQA;CACF;AAED,eAAO,MAAM,MAAM,EAAE,aAAmC,CAAA"}
|
package/dist/telemetry/events.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { ValueType, diag, metrics } from '@opentelemetry/api';
|
|
2
|
-
import { SeverityNumber, logs } from '@opentelemetry/api-logs';
|
|
1
|
+
import { SeverityNumber, ValueType, diag, logs, metrics, } from '@opentelemetry/api';
|
|
3
2
|
import { eventsLogger } from '../logger.js';
|
|
4
3
|
import { isAbortError, isTimeoutError } from './util.js';
|
|
5
4
|
const meter = metrics.getMeter('@atproto/bsky');
|
|
@@ -61,6 +60,22 @@ class EventReporter {
|
|
|
61
60
|
unit: '{record}',
|
|
62
61
|
valueType: ValueType.INT,
|
|
63
62
|
});
|
|
63
|
+
#notificationGroupingDuration = meter.createHistogram('notifications.grouped.duration', {
|
|
64
|
+
description: 'Compute time of each notification grouping algorithm invocation',
|
|
65
|
+
unit: 'ms',
|
|
66
|
+
valueType: ValueType.DOUBLE,
|
|
67
|
+
advice: {
|
|
68
|
+
explicitBucketBoundaries: [0.1, 0.25, 0.5, 1, 2.5, 5, 10, 25, 50, 100],
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
#groupedNotificationsDataplaneFetches = meter.createHistogram('notifications.grouped.dataplane_fetches', {
|
|
72
|
+
description: 'Dataplane notification fetches per grouped notifications response',
|
|
73
|
+
unit: '{fetch}',
|
|
74
|
+
valueType: ValueType.INT,
|
|
75
|
+
advice: {
|
|
76
|
+
explicitBucketBoundaries: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
|
|
77
|
+
},
|
|
78
|
+
});
|
|
64
79
|
#groupedNotificationsItemsFetched = meter.createHistogram('notifications.grouped.items_fetched', {
|
|
65
80
|
description: 'Raw notifications fetched across all dataplane reads per grouped notifications response',
|
|
66
81
|
unit: '{notification}',
|
|
@@ -162,12 +177,21 @@ class EventReporter {
|
|
|
162
177
|
diag.error('Failed to record external_hydration_traversal:', err);
|
|
163
178
|
}
|
|
164
179
|
}
|
|
165
|
-
/** Reports
|
|
166
|
-
|
|
167
|
-
|
|
180
|
+
/** Reports compute time once per grouping invocation, excluding dataplane reads. */
|
|
181
|
+
notificationGrouping({ algorithm, durationMs, }) {
|
|
182
|
+
try {
|
|
183
|
+
this.#notificationGroupingDuration.record(durationMs, { algorithm });
|
|
184
|
+
}
|
|
185
|
+
catch (err) {
|
|
186
|
+
diag.error('Failed to record notification_grouping:', err);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
/** Reports dataplane fetches and raw-item counts once per completed page. */
|
|
190
|
+
groupedNotifications({ itemsFetched, itemsReturned, dataplaneFetches, }) {
|
|
168
191
|
try {
|
|
169
192
|
this.#groupedNotificationsItemsFetched.record(itemsFetched);
|
|
170
193
|
this.#groupedNotificationsItemsReturned.record(itemsReturned);
|
|
194
|
+
this.#groupedNotificationsDataplaneFetches.record(dataplaneFetches);
|
|
171
195
|
}
|
|
172
196
|
catch (err) {
|
|
173
197
|
diag.error('Failed to record grouped_notifications:', err);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"events.js","sourceRoot":"","sources":["../../src/telemetry/events.ts"],"names":[],"mappings":"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","sourcesContent":["import { type Meter, ValueType, diag, metrics } from '@opentelemetry/api'\nimport { type Logger, SeverityNumber, logs } from '@opentelemetry/api-logs'\nimport type { app } from '../lexicons/index.js'\nimport { eventsLogger } from '../logger.js'\nimport { isAbortError, isTimeoutError } from './util.js'\n\nconst meter: Meter = metrics.getMeter('@atproto/bsky')\nconst logger: Logger = logs.getLogger('@atproto/bsky')\n\nexport type HydrationSource =\n 'known_likers' | 'known_followers' | 'activity_subscriptions'\n\n/** XRPC method that owns a hydration; set on `HydrateCtx` from the method's `$lxm`. */\nexport type HydrationXrpcEndpoint =\n | app.bsky.embed.getEmbedExternalView.$lxm\n | app.bsky.unspecced.getAtmosphereExploreTab.$lxm\n\n/** Reported XRPC endpoint of an external hydration traversal. */\nexport type ExternalHydrationXrpcEndpoint = HydrationXrpcEndpoint | 'unknown'\n\n/** Entry point that owns an external hydration traversal. */\nexport type ExternalHydrationRoot = 'uris' | 'refs' | 'dependencies'\n/**\n * `partial` is a returned traversal that recovered from a best-effort\n * dependency failure; `capped` takes precedence over it.\n */\nexport type ExternalHydrationOutcome =\n 'completed' | 'capped' | 'partial' | 'failed'\n\n/** Bounded metric attributes of an external hydration traversal. */\ntype ExternalHydrationAttributes = {\n xrpcEndpoint: ExternalHydrationXrpcEndpoint\n root: ExternalHydrationRoot\n outcome: ExternalHydrationOutcome\n}\n\n/**\n * Generic record lookup work of one external hydration traversal, across the\n * root and all nested dependency batches. Excludes profile/label hydration,\n * backlink sample/count RPCs, and legacy Standard Site reads.\n */\nexport type ExternalHydrationWork = {\n /**\n * Accepted latest/exact lookup identities scheduled for fetching, after\n * filtering duplicates and seen keys. Unavailable results and lookups in a\n * failed batch count; lookups skipped at the pass cap do not.\n */\n recordLookups: number\n /** Nonempty batches that scheduled generic fetches; not a raw RPC count. */\n batches: number\n /** Deepest fetched batch, where the root batch is pass 1; 0 if none. */\n maxPass: number\n /**\n * Refs inspected by the traversal's first dependency discovery. A traversal\n * whose root batch failed has none.\n */\n roots: number\n /** Roots available after takedown and owner moderation. */\n rootsResolved: number\n /**\n * Refs inspected by later dependency discoveries: records returned by\n * successful nested batches. Lookups lost to a failed batch or the pass cap\n * are excluded.\n */\n dependencies: number\n /** Dependencies available after takedown and owner moderation. */\n dependenciesResolved: number\n}\n\n/**\n * Central hub for reporting AppView events. Each method reports a\n * single event to every relevant sink in one call:\n *\n * 1. a pino logger → stdout/stderr (docker logs)\n * 2. an OTEL counter → aggregated metric (low-cardinality dimensions only)\n * 3. the OTEL Logs SDK → structured, trace-correlated log record (full detail)\n *\n * This keeps the three concerns in sync: there is exactly one call site per\n * event, and the counter / log / attribute definitions live together here\n * rather than being duplicated and drifting across handlers.\n *\n * @note High-cardinality attributes (e.g. `did`, `uri`) belong in the pino and\n * OTEL log records but must stay OUT of the counters, whose attributes must\n * remain low-cardinality to avoid a metric-series explosion.\n *\n * @note The OTEL counters and log records are no-ops unless the corresponding\n * OTEL exporter is configured, so calling these methods is always safe and\n * essentially free when telemetry is disabled.\n */\nclass EventReporter {\n #hydrationFailedCounter = meter.createCounter<{\n source: HydrationSource\n reason: 'abort' | 'timeout' | 'error'\n }>('hydration.failed', {\n description:\n 'Number of fail-open hydration steps that did not produce a result',\n valueType: ValueType.INT,\n })\n\n #externalHydrationLookups =\n meter.createHistogram<ExternalHydrationAttributes>(\n 'hydration.external.record_lookups',\n {\n description:\n 'Generic external record lookups scheduled per hydration traversal, including nested dependency batches',\n unit: '{lookup}',\n valueType: ValueType.INT,\n advice: {\n explicitBucketBoundaries: [\n 0, 1, 2, 5, 10, 25, 50, 100, 250, 500, 1000,\n ],\n },\n },\n )\n\n #externalHydrationBatches =\n meter.createHistogram<ExternalHydrationAttributes>(\n 'hydration.external.batches',\n {\n description:\n 'Generic external record fetch batches per hydration traversal, one per nested pass',\n unit: '{batch}',\n valueType: ValueType.INT,\n // @NOTE Traversals are capped at `MAX_EXTERNAL_HYDRATION_PASSES` (8).\n advice: { explicitBucketBoundaries: [0, 1, 2, 3, 4, 5, 6, 7, 8] },\n },\n )\n\n #externalHydrationDuration =\n meter.createHistogram<ExternalHydrationAttributes>(\n 'hydration.external.duration',\n {\n description:\n 'Wall time of an external hydration traversal, including nested dependency batches',\n unit: 'ms',\n valueType: ValueType.DOUBLE,\n advice: {\n explicitBucketBoundaries: [\n 5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000, 10000,\n ],\n },\n },\n )\n\n #externalHydrationRecords = meter.createCounter<{\n xrpcEndpoint: ExternalHydrationXrpcEndpoint\n kind: 'root' | 'dependency'\n status: 'resolved' | 'unresolved'\n }>('hydration.external.records', {\n description:\n 'Root and dependency records inspected by external hydration traversals, by availability after moderation',\n unit: '{record}',\n valueType: ValueType.INT,\n })\n\n #groupedNotificationsItemsFetched = meter.createHistogram(\n 'notifications.grouped.items_fetched',\n {\n description:\n 'Raw notifications fetched across all dataplane reads per grouped notifications response',\n unit: '{notification}',\n valueType: ValueType.INT,\n advice: {\n explicitBucketBoundaries: [\n 50, 100, 150, 200, 300, 400, 500, 1000, 1500, 2000, 3000,\n ],\n },\n },\n )\n\n #groupedNotificationsItemsReturned = meter.createHistogram(\n 'notifications.grouped.items_returned',\n {\n description:\n 'Raw notifications represented by the final groups per grouped notifications response',\n unit: '{notification}',\n valueType: ValueType.INT,\n advice: {\n explicitBucketBoundaries: [\n 50, 100, 150, 200, 300, 400, 500, 1000, 1500, 2000, 3000,\n ],\n },\n },\n )\n\n /**\n * Fans a single event out to both the pino logger (stdout/stderr) and the\n * OTEL Logs SDK (structured record).\n *\n * @note this method should never throw\n */\n #log(\n eventName: string,\n attributes: Record<string, string | number | boolean | undefined>,\n ) {\n try {\n // Emits an OTEL log for the event (goes to collector).\n logger.emit({\n eventName,\n severityNumber: SeverityNumber.INFO,\n attributes,\n })\n\n // Emits a Pino log for the event (goes to stdout).\n // NOTE: We'll probably migrate towards only using OTEL.\n eventsLogger.info({ eventName, ...attributes })\n } catch (err) {\n diag.error(`Failed to log event ${eventName}:`, err)\n }\n }\n\n hydrationFailed({ source, err }: { source: HydrationSource; err: unknown }) {\n const reason = isAbortError(err)\n ? 'abort'\n : isTimeoutError(err)\n ? 'timeout'\n : 'error'\n this.#log('hydration_failed', { source, reason })\n this.#hydrationFailedCounter.add(1, { source, reason })\n }\n\n /**\n * Reports the totals of one top-level external hydration traversal. Numeric\n * totals are observations and log fields, never metric attributes.\n *\n * @note this method should never throw\n */\n externalHydrationTraversal({\n xrpcEndpoint,\n root,\n outcome,\n durationMs,\n recordLookups,\n batches,\n maxPass,\n roots,\n rootsResolved,\n dependencies,\n dependenciesResolved,\n }: ExternalHydrationWork &\n ExternalHydrationAttributes & { durationMs: number }) {\n this.#log('external_hydration_traversal', {\n xrpcEndpoint,\n root,\n outcome,\n durationMs,\n recordLookups,\n batches,\n maxPass,\n roots,\n rootsResolved,\n dependencies,\n dependenciesResolved,\n })\n try {\n const attributes = { xrpcEndpoint, root, outcome }\n this.#externalHydrationLookups.record(recordLookups, attributes)\n this.#externalHydrationBatches.record(batches, attributes)\n this.#externalHydrationDuration.record(durationMs, attributes)\n this.#externalHydrationRecords.add(rootsResolved, {\n xrpcEndpoint,\n kind: 'root',\n status: 'resolved',\n })\n this.#externalHydrationRecords.add(roots - rootsResolved, {\n xrpcEndpoint,\n kind: 'root',\n status: 'unresolved',\n })\n this.#externalHydrationRecords.add(dependenciesResolved, {\n xrpcEndpoint,\n kind: 'dependency',\n status: 'resolved',\n })\n this.#externalHydrationRecords.add(dependencies - dependenciesResolved, {\n xrpcEndpoint,\n kind: 'dependency',\n status: 'unresolved',\n })\n } catch (err) {\n diag.error('Failed to record external_hydration_traversal:', err)\n }\n }\n\n /** Reports fetched and returned raw-item counts once per completed page. */\n groupedNotifications({\n itemsFetched,\n itemsReturned,\n }: {\n itemsFetched: number\n itemsReturned: number\n }) {\n this.#log('grouped_notifications', { itemsFetched, itemsReturned })\n try {\n this.#groupedNotificationsItemsFetched.record(itemsFetched)\n this.#groupedNotificationsItemsReturned.record(itemsReturned)\n } catch (err) {\n diag.error('Failed to record grouped_notifications:', err)\n }\n }\n}\n\nexport const events: EventReporter = new EventReporter()\n"]}
|
|
1
|
+
{"version":3,"file":"events.js","sourceRoot":"","sources":["../../src/telemetry/events.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n type Logger,\n type Meter,\n SeverityNumber,\n ValueType,\n diag,\n logs,\n metrics,\n} from '@opentelemetry/api'\nimport type { NotificationsV2Algorithm } from '../config.js'\nimport type { app } from '../lexicons/index.js'\nimport { eventsLogger } from '../logger.js'\nimport { isAbortError, isTimeoutError } from './util.js'\n\nconst meter: Meter = metrics.getMeter('@atproto/bsky')\nconst logger: Logger = logs.getLogger('@atproto/bsky')\n\nexport type HydrationSource =\n 'known_likers' | 'known_followers' | 'activity_subscriptions'\n\n/** XRPC method that owns a hydration; set on `HydrateCtx` from the method's `$lxm`. */\nexport type HydrationXrpcEndpoint =\n | app.bsky.embed.getEmbedExternalView.$lxm\n | app.bsky.unspecced.getAtmosphereExploreTab.$lxm\n\n/** Reported XRPC endpoint of an external hydration traversal. */\nexport type ExternalHydrationXrpcEndpoint = HydrationXrpcEndpoint | 'unknown'\n\n/** Entry point that owns an external hydration traversal. */\nexport type ExternalHydrationRoot = 'uris' | 'refs' | 'dependencies'\n/**\n * `partial` is a returned traversal that recovered from a best-effort\n * dependency failure; `capped` takes precedence over it.\n */\nexport type ExternalHydrationOutcome =\n 'completed' | 'capped' | 'partial' | 'failed'\n\n/** Bounded metric attributes of an external hydration traversal. */\ntype ExternalHydrationAttributes = {\n xrpcEndpoint: ExternalHydrationXrpcEndpoint\n root: ExternalHydrationRoot\n outcome: ExternalHydrationOutcome\n}\n\n/**\n * Generic record lookup work of one external hydration traversal, across the\n * root and all nested dependency batches. Excludes profile/label hydration\n * and backlink sample/count RPCs.\n */\nexport type ExternalHydrationWork = {\n /**\n * Accepted latest/exact lookup identities scheduled for fetching, after\n * filtering duplicates and seen keys. Unavailable results and lookups in a\n * failed batch count; lookups skipped at the pass cap do not.\n */\n recordLookups: number\n /** Nonempty batches that scheduled generic fetches; not a raw RPC count. */\n batches: number\n /** Deepest fetched batch, where the root batch is pass 1; 0 if none. */\n maxPass: number\n /**\n * Refs inspected by the traversal's first dependency discovery. A traversal\n * whose root batch failed has none.\n */\n roots: number\n /** Roots available after takedown and owner moderation. */\n rootsResolved: number\n /**\n * Refs inspected by later dependency discoveries: records returned by\n * successful nested batches. Lookups lost to a failed batch or the pass cap\n * are excluded.\n */\n dependencies: number\n /** Dependencies available after takedown and owner moderation. */\n dependenciesResolved: number\n}\n\n/**\n * Central hub for reporting AppView events. Each method reports a\n * single event to every relevant sink in one call:\n *\n * 1. a pino logger → stdout/stderr (docker logs)\n * 2. an OTEL counter → aggregated metric (low-cardinality dimensions only)\n * 3. the OTEL Logs SDK → structured, trace-correlated log record (full detail)\n *\n * This keeps the three concerns in sync: there is exactly one call site per\n * event, and the counter / log / attribute definitions live together here\n * rather than being duplicated and drifting across handlers.\n *\n * @note High-cardinality attributes (e.g. `did`, `uri`) belong in the pino and\n * OTEL log records but must stay OUT of the counters, whose attributes must\n * remain low-cardinality to avoid a metric-series explosion.\n *\n * @note The OTEL counters and log records are no-ops unless the corresponding\n * OTEL exporter is configured, so calling these methods is always safe and\n * essentially free when telemetry is disabled.\n */\nclass EventReporter {\n #hydrationFailedCounter = meter.createCounter<{\n source: HydrationSource\n reason: 'abort' | 'timeout' | 'error'\n }>('hydration.failed', {\n description:\n 'Number of fail-open hydration steps that did not produce a result',\n valueType: ValueType.INT,\n })\n\n #externalHydrationLookups =\n meter.createHistogram<ExternalHydrationAttributes>(\n 'hydration.external.record_lookups',\n {\n description:\n 'Generic external record lookups scheduled per hydration traversal, including nested dependency batches',\n unit: '{lookup}',\n valueType: ValueType.INT,\n advice: {\n explicitBucketBoundaries: [\n 0, 1, 2, 5, 10, 25, 50, 100, 250, 500, 1000,\n ],\n },\n },\n )\n\n #externalHydrationBatches =\n meter.createHistogram<ExternalHydrationAttributes>(\n 'hydration.external.batches',\n {\n description:\n 'Generic external record fetch batches per hydration traversal, one per nested pass',\n unit: '{batch}',\n valueType: ValueType.INT,\n // @NOTE Traversals are capped at `MAX_EXTERNAL_HYDRATION_PASSES` (8).\n advice: { explicitBucketBoundaries: [0, 1, 2, 3, 4, 5, 6, 7, 8] },\n },\n )\n\n #externalHydrationDuration =\n meter.createHistogram<ExternalHydrationAttributes>(\n 'hydration.external.duration',\n {\n description:\n 'Wall time of an external hydration traversal, including nested dependency batches',\n unit: 'ms',\n valueType: ValueType.DOUBLE,\n advice: {\n explicitBucketBoundaries: [\n 5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000, 10000,\n ],\n },\n },\n )\n\n #externalHydrationRecords = meter.createCounter<{\n xrpcEndpoint: ExternalHydrationXrpcEndpoint\n kind: 'root' | 'dependency'\n status: 'resolved' | 'unresolved'\n }>('hydration.external.records', {\n description:\n 'Root and dependency records inspected by external hydration traversals, by availability after moderation',\n unit: '{record}',\n valueType: ValueType.INT,\n })\n\n #notificationGroupingDuration = meter.createHistogram<{\n algorithm: NotificationsV2Algorithm\n }>('notifications.grouped.duration', {\n description:\n 'Compute time of each notification grouping algorithm invocation',\n unit: 'ms',\n valueType: ValueType.DOUBLE,\n advice: {\n explicitBucketBoundaries: [0.1, 0.25, 0.5, 1, 2.5, 5, 10, 25, 50, 100],\n },\n })\n\n #groupedNotificationsDataplaneFetches = meter.createHistogram(\n 'notifications.grouped.dataplane_fetches',\n {\n description:\n 'Dataplane notification fetches per grouped notifications response',\n unit: '{fetch}',\n valueType: ValueType.INT,\n advice: {\n explicitBucketBoundaries: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10],\n },\n },\n )\n\n #groupedNotificationsItemsFetched = meter.createHistogram(\n 'notifications.grouped.items_fetched',\n {\n description:\n 'Raw notifications fetched across all dataplane reads per grouped notifications response',\n unit: '{notification}',\n valueType: ValueType.INT,\n advice: {\n explicitBucketBoundaries: [\n 50, 100, 150, 200, 300, 400, 500, 1000, 1500, 2000, 3000,\n ],\n },\n },\n )\n\n #groupedNotificationsItemsReturned = meter.createHistogram(\n 'notifications.grouped.items_returned',\n {\n description:\n 'Raw notifications represented by the final groups per grouped notifications response',\n unit: '{notification}',\n valueType: ValueType.INT,\n advice: {\n explicitBucketBoundaries: [\n 50, 100, 150, 200, 300, 400, 500, 1000, 1500, 2000, 3000,\n ],\n },\n },\n )\n\n /**\n * Fans a single event out to both the pino logger (stdout/stderr) and the\n * OTEL Logs SDK (structured record).\n *\n * @note this method should never throw\n */\n #log(\n eventName: string,\n attributes: Record<string, string | number | boolean | undefined>,\n ) {\n try {\n // Emits an OTEL log for the event (goes to collector).\n logger.emit({\n eventName,\n severityNumber: SeverityNumber.INFO,\n attributes,\n })\n\n // Emits a Pino log for the event (goes to stdout).\n // NOTE: We'll probably migrate towards only using OTEL.\n eventsLogger.info({ eventName, ...attributes })\n } catch (err) {\n diag.error(`Failed to log event ${eventName}:`, err)\n }\n }\n\n hydrationFailed({ source, err }: { source: HydrationSource; err: unknown }) {\n const reason = isAbortError(err)\n ? 'abort'\n : isTimeoutError(err)\n ? 'timeout'\n : 'error'\n this.#log('hydration_failed', { source, reason })\n this.#hydrationFailedCounter.add(1, { source, reason })\n }\n\n /**\n * Reports the totals of one top-level external hydration traversal. Numeric\n * totals are observations and log fields, never metric attributes.\n *\n * @note this method should never throw\n */\n externalHydrationTraversal({\n xrpcEndpoint,\n root,\n outcome,\n durationMs,\n recordLookups,\n batches,\n maxPass,\n roots,\n rootsResolved,\n dependencies,\n dependenciesResolved,\n }: ExternalHydrationWork &\n ExternalHydrationAttributes & { durationMs: number }) {\n this.#log('external_hydration_traversal', {\n xrpcEndpoint,\n root,\n outcome,\n durationMs,\n recordLookups,\n batches,\n maxPass,\n roots,\n rootsResolved,\n dependencies,\n dependenciesResolved,\n })\n try {\n const attributes = { xrpcEndpoint, root, outcome }\n this.#externalHydrationLookups.record(recordLookups, attributes)\n this.#externalHydrationBatches.record(batches, attributes)\n this.#externalHydrationDuration.record(durationMs, attributes)\n this.#externalHydrationRecords.add(rootsResolved, {\n xrpcEndpoint,\n kind: 'root',\n status: 'resolved',\n })\n this.#externalHydrationRecords.add(roots - rootsResolved, {\n xrpcEndpoint,\n kind: 'root',\n status: 'unresolved',\n })\n this.#externalHydrationRecords.add(dependenciesResolved, {\n xrpcEndpoint,\n kind: 'dependency',\n status: 'resolved',\n })\n this.#externalHydrationRecords.add(dependencies - dependenciesResolved, {\n xrpcEndpoint,\n kind: 'dependency',\n status: 'unresolved',\n })\n } catch (err) {\n diag.error('Failed to record external_hydration_traversal:', err)\n }\n }\n\n /** Reports compute time once per grouping invocation, excluding dataplane reads. */\n notificationGrouping({\n algorithm,\n durationMs,\n }: {\n algorithm: NotificationsV2Algorithm\n durationMs: number\n }) {\n try {\n this.#notificationGroupingDuration.record(durationMs, { algorithm })\n } catch (err) {\n diag.error('Failed to record notification_grouping:', err)\n }\n }\n\n /** Reports dataplane fetches and raw-item counts once per completed page. */\n groupedNotifications({\n itemsFetched,\n itemsReturned,\n dataplaneFetches,\n }: {\n itemsFetched: number\n itemsReturned: number\n dataplaneFetches: number\n }) {\n try {\n this.#groupedNotificationsItemsFetched.record(itemsFetched)\n this.#groupedNotificationsItemsReturned.record(itemsReturned)\n this.#groupedNotificationsDataplaneFetches.record(dataplaneFetches)\n } catch (err) {\n diag.error('Failed to record grouped_notifications:', err)\n }\n }\n}\n\nexport const events: EventReporter = new EventReporter()\n"]}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type { AtUriString } from '@atproto/lex';
|
|
2
2
|
import type { AssociatedSiteStandardRecord, ExternalRecords, SiteStandardDocument, SiteStandardPublication } from '../../hydration/external.js';
|
|
3
|
-
export declare const SITE_STANDARD_NSID_PREFIX = "site.standard.";
|
|
4
3
|
/**
|
|
5
4
|
* Confirm that the supplied SS records actually back `assumedUrl`. The
|
|
6
5
|
* record-side URL is built by concatenating the publication URL (or the
|
|
@@ -16,8 +15,8 @@ export declare const SITE_STANDARD_NSID_PREFIX = "site.standard.";
|
|
|
16
15
|
* side carries the slash.
|
|
17
16
|
*
|
|
18
17
|
* Structural validation of the doc/pub pair (matching `site` ↔ pub URI,
|
|
19
|
-
* no orphan docs that claim a missing publication) happens upstream: in
|
|
20
|
-
* pinned
|
|
18
|
+
* no orphan docs that claim a missing publication) happens upstream: in
|
|
19
|
+
* `getSiteStandardRecordsFromRefs`'s pinned-root/dependency selection
|
|
21
20
|
* (`hydration/external.ts`), or by `validateStandardSiteRecordsForUrl` over
|
|
22
21
|
* `getStandardSiteRecordsForUri`'s selection (which alone leaves an orphan
|
|
23
22
|
* document selected with an absent publication). By the time this
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"standard-site.d.ts","sourceRoot":"","sources":["../../../src/util/external/standard-site.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAA4B,MAAM,cAAc,CAAA;AAEzE,OAAO,KAAK,EACV,4BAA4B,EAE5B,eAAe,EACf,oBAAoB,EACpB,uBAAuB,EACxB,MAAM,6BAA6B,CAAA;
|
|
1
|
+
{"version":3,"file":"standard-site.d.ts","sourceRoot":"","sources":["../../../src/util/external/standard-site.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAA4B,MAAM,cAAc,CAAA;AAEzE,OAAO,KAAK,EACV,4BAA4B,EAE5B,eAAe,EACf,oBAAoB,EACpB,uBAAuB,EACxB,MAAM,6BAA6B,CAAA;AAkFpC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AACH,eAAO,MAAM,0BAA0B,aAEjC;IACE,GAAG,EAAE;QAAE,GAAG,EAAE,MAAM,CAAA;KAAE,CAAA;IACpB,IAAI,EAAE;QAAE,MAAM,EAAE;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,IAAI,CAAC,EAAE,MAAM,CAAA;SAAE,CAAA;KAAE,CAAA;CAClD,GACD,SAAS,eAEX;IAAE,GAAG,EAAE;QAAE,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC;IAAC,IAAI,EAAE;QAAE,MAAM,EAAE;YAAE,GAAG,EAAE,MAAM,CAAA;SAAE,CAAA;KAAE,CAAA;CAAE,GAAG,SAAS,cAC7D,MAAM,KACjB,OA2BF,CAAA;AAED,MAAM,MAAM,2BAA2B,GAAG;IACxC,QAAQ,CAAC,EAAE,4BAA4B,CAAC,oBAAoB,CAAC,CAAA;IAC7D,WAAW,CAAC,EAAE,4BAA4B,CAAC,uBAAuB,CAAC,CAAA;CACpE,CAAA;AAWD;;;;;;;GAOG;AACH,eAAO,MAAM,4BAA4B,iBACzB,WAAW,WAChB,eAAe,GAAG,SAAS,KACnC,2BAiCF,CAAA;AAUD;;;;;;;GAOG;AACH,eAAO,MAAM,iCAAiC,aAClC,4BAA4B,CAAC,oBAAoB,CAAC,GAAG,SAAS,eAEtE,4BAA4B,CAAC,uBAAuB,CAAC,GAAG,SAAS,cACvD,MAAM,KACjB,OAWF,CAAA;AAID;;;;;GAKG;AACH,eAAO,MAAM,0BAA0B,SAC/B,MAAM,KACX,MAAM,GAAG,SAIX,CAAA"}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { parseAtUriString } from '@atproto/syntax';
|
|
2
2
|
import { site } from '../../lexicons/index.js';
|
|
3
|
-
export const SITE_STANDARD_NSID_PREFIX = 'site.standard.';
|
|
4
3
|
/**
|
|
5
4
|
* Parse `url` as HTTP(S) and reduce it to a canonical
|
|
6
5
|
* `protocol://host/path` string for equality comparison: lowercases host,
|
|
@@ -90,8 +89,8 @@ const canonicalUrlMatchesAssumed = (canonicalRecordUrl, canonicalAssumedUrl) =>
|
|
|
90
89
|
* side carries the slash.
|
|
91
90
|
*
|
|
92
91
|
* Structural validation of the doc/pub pair (matching `site` ↔ pub URI,
|
|
93
|
-
* no orphan docs that claim a missing publication) happens upstream: in
|
|
94
|
-
* pinned
|
|
92
|
+
* no orphan docs that claim a missing publication) happens upstream: in
|
|
93
|
+
* `getSiteStandardRecordsFromRefs`'s pinned-root/dependency selection
|
|
95
94
|
* (`hydration/external.ts`), or by `validateStandardSiteRecordsForUrl` over
|
|
96
95
|
* `getStandardSiteRecordsForUri`'s selection (which alone leaves an orphan
|
|
97
96
|
* document selected with an absent publication). By the time this
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"standard-site.js","sourceRoot":"","sources":["../../../src/util/external/standard-site.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AtUriString, InferInput, RecordSchema } from '@atproto/lex'\nimport { parseAtUriString } from '@atproto/syntax'\nimport type {\n AssociatedSiteStandardRecord,\n ExternalRecord,\n ExternalRecords,\n SiteStandardDocument,\n SiteStandardPublication,\n} from '../../hydration/external.js'\nimport type { RecordInfo } from '../../hydration/util.js'\nimport { site } from '../../lexicons/index.js'\n\nexport const SITE_STANDARD_NSID_PREFIX = 'site.standard.'\n\n/**\n * Parse `url` as HTTP(S) and reduce it to a canonical\n * `protocol://host/path` string for equality comparison: lowercases host,\n * strips a trailing slash from the path, and drops query/fragment. Returns\n * `null` when the input isn't a valid HTTP(S) URL.\n */\nconst canonicalizeHttpUrl = (url: string): string | null => {\n let parsed: URL\n try {\n parsed = new URL(url)\n } catch {\n return null\n }\n if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') return null\n const path = parsed.pathname === '/' ? '' : parsed.pathname.replace(/\\/$/, '')\n return `${parsed.protocol}//${parsed.host}${path}`\n}\n\n/**\n * Append `path` to `base` with exactly one slash between, or return `base`\n * unchanged when `path` is empty. Unlike `new URL(path, base)`, a leading\n * slash on `path` does NOT swallow `base`'s pathname — so\n * `joinPath('https://x.com/blog', '/foo')` is `https://x.com/blog/foo`,\n * not `https://x.com/foo`.\n */\nconst joinPath = (base: string, path: string): string => {\n if (!path) return base\n const baseTrimmed = base.endsWith('/') ? base.slice(0, -1) : base\n const pathTrimmed = path.startsWith('/') ? path.slice(1) : path\n return `${baseTrimmed}/${pathTrimmed}`\n}\n\n/**\n * Apex domains whose authors own the full subpath space under their\n * record-claimed URL. Each entry matches itself and any subdomain; e.g.\n * `pckt.blog` matches `pckt.blog` and `waow-tech.pckt.blog`.\n *\n * On these domains, an `assumedUrl` whose pathname extends the canonical\n * record URL with extra segments is treated as valid. Adding to this list\n * is a trust call — only platforms where each record's URL space is\n * authoritatively owned by its author belong here.\n */\nconst SUBPATH_FRIENDLY_DOMAINS = ['pckt.blog', 'leaflet.pub', 'offprint.app']\n\nconst isSubpathFriendlyHost = (host: string): boolean => {\n const lower = host.toLowerCase()\n return SUBPATH_FRIENDLY_DOMAINS.some(\n (domain) => lower === domain || lower.endsWith(`.${domain}`),\n )\n}\n\n/**\n * Return whether `recordUrl` and `assumedUrl` should validate as the same\n * canonical content. Strictly equal canonical forms always match. On\n * subpath-friendly hosts (see `SUBPATH_FRIENDLY_DOMAINS`), an `assumedUrl`\n * whose path extends `recordUrl`'s with extra segments is also accepted.\n *\n * Both inputs are pre-canonicalized strings (`canonicalizeHttpUrl` output)\n * with no trailing slash and no query/fragment.\n */\nconst canonicalUrlMatchesAssumed = (\n canonicalRecordUrl: string,\n canonicalAssumedUrl: string,\n): boolean => {\n if (canonicalRecordUrl === canonicalAssumedUrl) return true\n // Subpath fallback. Both strings are canonicalized, so a real\n // path-segment boundary at `recordUrl + '/'` (e.g. `/foo` vs `/foo-bar`\n // never matches; `/foo` vs `/foo/bar` does).\n if (!canonicalAssumedUrl.startsWith(`${canonicalRecordUrl}/`)) return false\n let host: string\n try {\n host = new URL(canonicalAssumedUrl).host\n } catch {\n return false\n }\n return isSubpathFriendlyHost(host)\n}\n\n/**\n * Confirm that the supplied SS records actually back `assumedUrl`. The\n * record-side URL is built by concatenating the publication URL (or the\n * loose-doc site) with the document's `path` field, then both sides are\n * canonicalized for equality: lowercase host, query/fragment dropped,\n * trailing slash stripped.\n *\n * Path concatenation is `base + '/' + path` semantics — a leading `/` on\n * `path` does NOT swallow the base's pathname (the way\n * `new URL(path, base)` would). So\n * `'https://atproto.com/blog' + '/indexing-standard-site'` resolves to\n * `https://atproto.com/blog/indexing-standard-site` regardless of which\n * side carries the slash.\n *\n * Structural validation of the doc/pub pair (matching `site` ↔ pub URI,\n * no orphan docs that claim a missing publication) happens upstream: in the\n * pinned `getSiteStandardRecordsFromHydrationMapsByRefs`\n * (`hydration/external.ts`), or by `validateStandardSiteRecordsForUrl` over\n * `getStandardSiteRecordsForUri`'s selection (which alone leaves an orphan\n * document selected with an absent publication). By the time this\n * function runs the pair is already known to be structurally consistent,\n * so we only check whether the records back the URL.\n *\n * For document validation, `SUBPATH_FRIENDLY_DOMAINS` (and their\n * subdomains) accept an assumed URL whose path extends the canonical\n * record URL with additional segments — these are platforms where each\n * record's author owns the full subpath space under their claimed URL.\n * Publication-only validation always requires exact match: there's no\n * coherent \"subpath\" of a publication's home page.\n *\n * Cases:\n * - Document + publication: `publication.url + document.path` must\n * canonicalize to `assumedUrl` (or be a subpath-friendly prefix of it).\n * - Loose document (web-URL `site`): `document.site + document.path`\n * must canonicalize to `assumedUrl` (or be a subpath-friendly prefix).\n * (Doc with at-uri `site` but no publication can't reach this function\n * — callers reject it.)\n * - Publication only: `publication.url` must canonicalize exactly to\n * `assumedUrl`.\n * - Neither: vacuously valid; the caller short-circuits before we get\n * here.\n */\nexport const validateStandardSiteForUrl = (\n document:\n | {\n ref: { uri: string }\n info: { record: { site: string; path?: string } }\n }\n | undefined,\n publication:\n { ref: { uri: string }; info: { record: { url: string } } } | undefined,\n assumedUrl: string,\n): boolean => {\n const canonicalAssumed = canonicalizeHttpUrl(assumedUrl)\n if (canonicalAssumed === null) return false\n\n if (document && publication) {\n const joined = canonicalizeHttpUrl(\n joinPath(publication.info.record.url, document.info.record.path ?? ''),\n )\n return (\n joined !== null && canonicalUrlMatchesAssumed(joined, canonicalAssumed)\n )\n }\n if (document) {\n const joined = canonicalizeHttpUrl(\n joinPath(document.info.record.site, document.info.record.path ?? ''),\n )\n return (\n joined !== null && canonicalUrlMatchesAssumed(joined, canonicalAssumed)\n )\n }\n if (publication) {\n // Publication-only matches are exact: `assumedUrl` represents the\n // publication's home page, not an article underneath it. Subpath\n // extensions belong to document validation.\n return canonicalizeHttpUrl(publication.info.record.url) === canonicalAssumed\n }\n return true\n}\n\nexport type StandardSiteRecordSelection = {\n document?: AssociatedSiteStandardRecord<SiteStandardDocument>\n publication?: AssociatedSiteStandardRecord<SiteStandardPublication>\n}\n\nconst matchStoredRecord = <TSchema extends RecordSchema>(\n schema: TSchema,\n uri: AtUriString,\n info: ExternalRecord | null | undefined,\n): AssociatedSiteStandardRecord<RecordInfo<InferInput<TSchema>>> | undefined =>\n info && schema.$matches(info.record, { strict: false })\n ? { ref: { uri, cid: info.cid }, info: { ...info, record: info.record } }\n : undefined\n\n/**\n * Select the Standard.site records backing `canonicalUri` from in-memory\n * hydrated `records`: the canonical record itself and, for a document whose\n * `site` is a publication AT-URI, that publication. Stored bodies are checked\n * against the Lexicon without parsing or defaulting. Nothing is fetched, and a\n * missing or invalid referenced publication leaves that slot empty while the\n * document stays selected.\n */\nexport const getStandardSiteRecordsForUri = (\n canonicalUri: AtUriString,\n records: ExternalRecords | undefined,\n): StandardSiteRecordSelection => {\n const parsed = parseAtUriString(canonicalUri)\n if (!parsed.success) return {}\n const info = records?.get(canonicalUri)\n\n switch (parsed.value.collection) {\n case site.standard.publication.$type: {\n const publication = matchStoredRecord(\n site.standard.publication.main,\n canonicalUri,\n info,\n )\n return publication ? { publication } : {}\n }\n case site.standard.document.$type: {\n const document = matchStoredRecord(\n site.standard.document.main,\n canonicalUri,\n info,\n )\n if (!document) return {}\n const publicationUri = publicationUriOf(document.info.record.site)\n if (!publicationUri) return { document }\n const publication = matchStoredRecord(\n site.standard.publication.main,\n publicationUri,\n records?.get(publicationUri),\n )\n return publication ? { document, publication } : { document }\n }\n default:\n return {}\n }\n}\n\nconst publicationUriOf = (value: string): AtUriString | undefined => {\n const parsed = parseAtUriString(value)\n return parsed.success &&\n parsed.value.collection === site.standard.publication.$type\n ? (value as AtUriString)\n : undefined\n}\n\n/**\n * Whether the selected records form a valid association that backs\n * `assumedUrl`. A document naming a publication AT-URI needs exactly that\n * publication; a supplied publication must be the one the document names.\n * Standalone publications and documents with a web-URL `site` are valid on\n * their own. URL comparison is `validateStandardSiteForUrl`; renderability\n * (e.g. empty titles) is not considered.\n */\nexport const validateStandardSiteRecordsForUrl = (\n document: AssociatedSiteStandardRecord<SiteStandardDocument> | undefined,\n publication:\n AssociatedSiteStandardRecord<SiteStandardPublication> | undefined,\n assumedUrl: string,\n): boolean => {\n if (!document && !publication) return false\n if (document) {\n const documentSite = document.info.record.site\n if (publication) {\n if (documentSite !== publication.ref.uri) return false\n } else if (publicationUriOf(documentSite)) {\n return false\n }\n }\n return validateStandardSiteForUrl(document, publication, assumedUrl)\n}\n\nconst WORDS_PER_MINUTE = 200\n\n/**\n * Estimate reading time in minutes from a plaintext document body. Returns\n * `undefined` when the input has no countable words. Uses a coarse\n * `WORDS_PER_MINUTE` heuristic; swap in a more accurate library here if\n * needed (e.g. `reading-time`).\n */\nexport const estimateReadingTimeMinutes = (\n text: string,\n): number | undefined => {\n const words = text.trim().split(/\\s+/).filter(Boolean).length\n if (!words) return undefined\n return Math.max(1, Math.ceil(words / WORDS_PER_MINUTE))\n}\n"]}
|
|
1
|
+
{"version":3,"file":"standard-site.js","sourceRoot":"","sources":["../../../src/util/external/standard-site.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AtUriString, InferInput, RecordSchema } from '@atproto/lex'\nimport { parseAtUriString } from '@atproto/syntax'\nimport type {\n AssociatedSiteStandardRecord,\n ExternalRecord,\n ExternalRecords,\n SiteStandardDocument,\n SiteStandardPublication,\n} from '../../hydration/external.js'\nimport type { RecordInfo } from '../../hydration/util.js'\nimport { site } from '../../lexicons/index.js'\n\n/**\n * Parse `url` as HTTP(S) and reduce it to a canonical\n * `protocol://host/path` string for equality comparison: lowercases host,\n * strips a trailing slash from the path, and drops query/fragment. Returns\n * `null` when the input isn't a valid HTTP(S) URL.\n */\nconst canonicalizeHttpUrl = (url: string): string | null => {\n let parsed: URL\n try {\n parsed = new URL(url)\n } catch {\n return null\n }\n if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') return null\n const path = parsed.pathname === '/' ? '' : parsed.pathname.replace(/\\/$/, '')\n return `${parsed.protocol}//${parsed.host}${path}`\n}\n\n/**\n * Append `path` to `base` with exactly one slash between, or return `base`\n * unchanged when `path` is empty. Unlike `new URL(path, base)`, a leading\n * slash on `path` does NOT swallow `base`'s pathname — so\n * `joinPath('https://x.com/blog', '/foo')` is `https://x.com/blog/foo`,\n * not `https://x.com/foo`.\n */\nconst joinPath = (base: string, path: string): string => {\n if (!path) return base\n const baseTrimmed = base.endsWith('/') ? base.slice(0, -1) : base\n const pathTrimmed = path.startsWith('/') ? path.slice(1) : path\n return `${baseTrimmed}/${pathTrimmed}`\n}\n\n/**\n * Apex domains whose authors own the full subpath space under their\n * record-claimed URL. Each entry matches itself and any subdomain; e.g.\n * `pckt.blog` matches `pckt.blog` and `waow-tech.pckt.blog`.\n *\n * On these domains, an `assumedUrl` whose pathname extends the canonical\n * record URL with extra segments is treated as valid. Adding to this list\n * is a trust call — only platforms where each record's URL space is\n * authoritatively owned by its author belong here.\n */\nconst SUBPATH_FRIENDLY_DOMAINS = ['pckt.blog', 'leaflet.pub', 'offprint.app']\n\nconst isSubpathFriendlyHost = (host: string): boolean => {\n const lower = host.toLowerCase()\n return SUBPATH_FRIENDLY_DOMAINS.some(\n (domain) => lower === domain || lower.endsWith(`.${domain}`),\n )\n}\n\n/**\n * Return whether `recordUrl` and `assumedUrl` should validate as the same\n * canonical content. Strictly equal canonical forms always match. On\n * subpath-friendly hosts (see `SUBPATH_FRIENDLY_DOMAINS`), an `assumedUrl`\n * whose path extends `recordUrl`'s with extra segments is also accepted.\n *\n * Both inputs are pre-canonicalized strings (`canonicalizeHttpUrl` output)\n * with no trailing slash and no query/fragment.\n */\nconst canonicalUrlMatchesAssumed = (\n canonicalRecordUrl: string,\n canonicalAssumedUrl: string,\n): boolean => {\n if (canonicalRecordUrl === canonicalAssumedUrl) return true\n // Subpath fallback. Both strings are canonicalized, so a real\n // path-segment boundary at `recordUrl + '/'` (e.g. `/foo` vs `/foo-bar`\n // never matches; `/foo` vs `/foo/bar` does).\n if (!canonicalAssumedUrl.startsWith(`${canonicalRecordUrl}/`)) return false\n let host: string\n try {\n host = new URL(canonicalAssumedUrl).host\n } catch {\n return false\n }\n return isSubpathFriendlyHost(host)\n}\n\n/**\n * Confirm that the supplied SS records actually back `assumedUrl`. The\n * record-side URL is built by concatenating the publication URL (or the\n * loose-doc site) with the document's `path` field, then both sides are\n * canonicalized for equality: lowercase host, query/fragment dropped,\n * trailing slash stripped.\n *\n * Path concatenation is `base + '/' + path` semantics — a leading `/` on\n * `path` does NOT swallow the base's pathname (the way\n * `new URL(path, base)` would). So\n * `'https://atproto.com/blog' + '/indexing-standard-site'` resolves to\n * `https://atproto.com/blog/indexing-standard-site` regardless of which\n * side carries the slash.\n *\n * Structural validation of the doc/pub pair (matching `site` ↔ pub URI,\n * no orphan docs that claim a missing publication) happens upstream: in\n * `getSiteStandardRecordsFromRefs`'s pinned-root/dependency selection\n * (`hydration/external.ts`), or by `validateStandardSiteRecordsForUrl` over\n * `getStandardSiteRecordsForUri`'s selection (which alone leaves an orphan\n * document selected with an absent publication). By the time this\n * function runs the pair is already known to be structurally consistent,\n * so we only check whether the records back the URL.\n *\n * For document validation, `SUBPATH_FRIENDLY_DOMAINS` (and their\n * subdomains) accept an assumed URL whose path extends the canonical\n * record URL with additional segments — these are platforms where each\n * record's author owns the full subpath space under their claimed URL.\n * Publication-only validation always requires exact match: there's no\n * coherent \"subpath\" of a publication's home page.\n *\n * Cases:\n * - Document + publication: `publication.url + document.path` must\n * canonicalize to `assumedUrl` (or be a subpath-friendly prefix of it).\n * - Loose document (web-URL `site`): `document.site + document.path`\n * must canonicalize to `assumedUrl` (or be a subpath-friendly prefix).\n * (Doc with at-uri `site` but no publication can't reach this function\n * — callers reject it.)\n * - Publication only: `publication.url` must canonicalize exactly to\n * `assumedUrl`.\n * - Neither: vacuously valid; the caller short-circuits before we get\n * here.\n */\nexport const validateStandardSiteForUrl = (\n document:\n | {\n ref: { uri: string }\n info: { record: { site: string; path?: string } }\n }\n | undefined,\n publication:\n { ref: { uri: string }; info: { record: { url: string } } } | undefined,\n assumedUrl: string,\n): boolean => {\n const canonicalAssumed = canonicalizeHttpUrl(assumedUrl)\n if (canonicalAssumed === null) return false\n\n if (document && publication) {\n const joined = canonicalizeHttpUrl(\n joinPath(publication.info.record.url, document.info.record.path ?? ''),\n )\n return (\n joined !== null && canonicalUrlMatchesAssumed(joined, canonicalAssumed)\n )\n }\n if (document) {\n const joined = canonicalizeHttpUrl(\n joinPath(document.info.record.site, document.info.record.path ?? ''),\n )\n return (\n joined !== null && canonicalUrlMatchesAssumed(joined, canonicalAssumed)\n )\n }\n if (publication) {\n // Publication-only matches are exact: `assumedUrl` represents the\n // publication's home page, not an article underneath it. Subpath\n // extensions belong to document validation.\n return canonicalizeHttpUrl(publication.info.record.url) === canonicalAssumed\n }\n return true\n}\n\nexport type StandardSiteRecordSelection = {\n document?: AssociatedSiteStandardRecord<SiteStandardDocument>\n publication?: AssociatedSiteStandardRecord<SiteStandardPublication>\n}\n\nconst matchStoredRecord = <TSchema extends RecordSchema>(\n schema: TSchema,\n uri: AtUriString,\n info: ExternalRecord | null | undefined,\n): AssociatedSiteStandardRecord<RecordInfo<InferInput<TSchema>>> | undefined =>\n info && schema.$matches(info.record, { strict: false })\n ? { ref: { uri, cid: info.cid }, info: { ...info, record: info.record } }\n : undefined\n\n/**\n * Select the Standard.site records backing `canonicalUri` from in-memory\n * hydrated `records`: the canonical record itself and, for a document whose\n * `site` is a publication AT-URI, that publication. Stored bodies are checked\n * against the Lexicon without parsing or defaulting. Nothing is fetched, and a\n * missing or invalid referenced publication leaves that slot empty while the\n * document stays selected.\n */\nexport const getStandardSiteRecordsForUri = (\n canonicalUri: AtUriString,\n records: ExternalRecords | undefined,\n): StandardSiteRecordSelection => {\n const parsed = parseAtUriString(canonicalUri)\n if (!parsed.success) return {}\n const info = records?.get(canonicalUri)\n\n switch (parsed.value.collection) {\n case site.standard.publication.$type: {\n const publication = matchStoredRecord(\n site.standard.publication.main,\n canonicalUri,\n info,\n )\n return publication ? { publication } : {}\n }\n case site.standard.document.$type: {\n const document = matchStoredRecord(\n site.standard.document.main,\n canonicalUri,\n info,\n )\n if (!document) return {}\n const publicationUri = publicationUriOf(document.info.record.site)\n if (!publicationUri) return { document }\n const publication = matchStoredRecord(\n site.standard.publication.main,\n publicationUri,\n records?.get(publicationUri),\n )\n return publication ? { document, publication } : { document }\n }\n default:\n return {}\n }\n}\n\nconst publicationUriOf = (value: string): AtUriString | undefined => {\n const parsed = parseAtUriString(value)\n return parsed.success &&\n parsed.value.collection === site.standard.publication.$type\n ? (value as AtUriString)\n : undefined\n}\n\n/**\n * Whether the selected records form a valid association that backs\n * `assumedUrl`. A document naming a publication AT-URI needs exactly that\n * publication; a supplied publication must be the one the document names.\n * Standalone publications and documents with a web-URL `site` are valid on\n * their own. URL comparison is `validateStandardSiteForUrl`; renderability\n * (e.g. empty titles) is not considered.\n */\nexport const validateStandardSiteRecordsForUrl = (\n document: AssociatedSiteStandardRecord<SiteStandardDocument> | undefined,\n publication:\n AssociatedSiteStandardRecord<SiteStandardPublication> | undefined,\n assumedUrl: string,\n): boolean => {\n if (!document && !publication) return false\n if (document) {\n const documentSite = document.info.record.site\n if (publication) {\n if (documentSite !== publication.ref.uri) return false\n } else if (publicationUriOf(documentSite)) {\n return false\n }\n }\n return validateStandardSiteForUrl(document, publication, assumedUrl)\n}\n\nconst WORDS_PER_MINUTE = 200\n\n/**\n * Estimate reading time in minutes from a plaintext document body. Returns\n * `undefined` when the input has no countable words. Uses a coarse\n * `WORDS_PER_MINUTE` heuristic; swap in a more accurate library here if\n * needed (e.g. `reading-time`).\n */\nexport const estimateReadingTimeMinutes = (\n text: string,\n): number | undefined => {\n const words = text.trim().split(/\\s+/).filter(Boolean).length\n if (!words) return undefined\n return Math.max(1, Math.ceil(words / WORDS_PER_MINUTE))\n}\n"]}
|
package/dist/util/uris.d.ts
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
1
|
+
import { type AtUriString, type NsidString } from '@atproto/lex';
|
|
1
2
|
import { type DidString } from '@atproto/syntax';
|
|
2
3
|
import { type StrongRef } from '../views/types.js';
|
|
4
|
+
/** A complete DID-based record URI in the expected collection, with no query or fragment. */
|
|
5
|
+
export declare function recordUriOf(value: string, collection: NsidString): AtUriString | undefined;
|
|
3
6
|
/**
|
|
4
7
|
* Convert a post URI to a threadgate URI. If the URI is not a valid
|
|
5
8
|
* post URI, return URI unchanged. Threadgate lookups will then fail.
|
|
6
9
|
*/
|
|
7
|
-
export declare function postUriToThreadgateUri(postUri: string):
|
|
10
|
+
export declare function postUriToThreadgateUri(postUri: string): AtUriString;
|
|
8
11
|
/**
|
|
9
12
|
* Convert a post URI to a postgate URI. If the URI is not a valid
|
|
10
13
|
* post URI, return URI unchanged. Postgate lookups will then fail.
|
|
11
14
|
*/
|
|
12
|
-
export declare function postUriToPostgateUri(postUri: string):
|
|
15
|
+
export declare function postUriToPostgateUri(postUri: string): AtUriString;
|
|
13
16
|
export declare function uriToDid(uri: string): DidString;
|
|
14
17
|
export declare function safePinnedPost(value: unknown): StrongRef | undefined;
|
|
15
18
|
//# sourceMappingURL=uris.d.ts.map
|
package/dist/util/uris.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"uris.d.ts","sourceRoot":"","sources":["../../src/util/uris.ts"],"names":[],"mappings":"AAAA,OAAO,EAAS,KAAK,SAAS,
|
|
1
|
+
{"version":3,"file":"uris.d.ts","sourceRoot":"","sources":["../../src/util/uris.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,WAAW,EAAE,KAAK,UAAU,EAAe,MAAM,cAAc,CAAA;AAC7E,OAAO,EAAS,KAAK,SAAS,EAAoB,MAAM,iBAAiB,CAAA;AAEzE,OAAO,EAAE,KAAK,SAAS,EAAqB,MAAM,mBAAmB,CAAA;AAErE,6FAA6F;AAC7F,wBAAgB,WAAW,CACzB,KAAK,EAAE,MAAM,EACb,UAAU,EAAE,UAAU,GACrB,WAAW,GAAG,SAAS,CAUzB;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,MAAM,eAMrD;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,eAMnD;AAED,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAG/C;AAGD,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,SAAS,GAAG,SAAS,CAGpE"}
|