@nacre.work/core 0.0.0 → 0.2.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.
Files changed (89) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +9 -0
  3. package/dist/audit.d.ts +41 -0
  4. package/dist/audit.d.ts.map +1 -0
  5. package/dist/audit.js +18 -0
  6. package/dist/audit.js.map +1 -0
  7. package/dist/authz/cache.d.ts +20 -0
  8. package/dist/authz/cache.d.ts.map +1 -1
  9. package/dist/authz/cache.js +29 -0
  10. package/dist/authz/cache.js.map +1 -1
  11. package/dist/authz/filter.d.ts +54 -3
  12. package/dist/authz/filter.d.ts.map +1 -1
  13. package/dist/authz/filter.js +45 -9
  14. package/dist/authz/filter.js.map +1 -1
  15. package/dist/authz/index.d.ts +3 -3
  16. package/dist/authz/index.d.ts.map +1 -1
  17. package/dist/authz/index.js +2 -2
  18. package/dist/authz/index.js.map +1 -1
  19. package/dist/config.d.ts +55 -1
  20. package/dist/config.d.ts.map +1 -1
  21. package/dist/config.js +245 -7
  22. package/dist/config.js.map +1 -1
  23. package/dist/db/client.d.ts +39 -0
  24. package/dist/db/client.d.ts.map +1 -1
  25. package/dist/db/client.js +82 -0
  26. package/dist/db/client.js.map +1 -1
  27. package/dist/db/migrate.d.ts.map +1 -1
  28. package/dist/db/migrate.js +49 -0
  29. package/dist/db/migrate.js.map +1 -1
  30. package/dist/index.d.ts +21 -3
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/index.js +12 -3
  33. package/dist/index.js.map +1 -1
  34. package/dist/lifecycle.d.ts +57 -0
  35. package/dist/lifecycle.d.ts.map +1 -0
  36. package/dist/lifecycle.js +84 -0
  37. package/dist/lifecycle.js.map +1 -0
  38. package/dist/logging.d.ts +46 -0
  39. package/dist/logging.d.ts.map +1 -0
  40. package/dist/logging.js +110 -0
  41. package/dist/logging.js.map +1 -0
  42. package/dist/metadata.d.ts +50 -0
  43. package/dist/metadata.d.ts.map +1 -0
  44. package/dist/metadata.js +127 -0
  45. package/dist/metadata.js.map +1 -0
  46. package/dist/metrics.d.ts +19 -1
  47. package/dist/metrics.d.ts.map +1 -1
  48. package/dist/metrics.js +84 -2
  49. package/dist/metrics.js.map +1 -1
  50. package/dist/migrations/0008_authenticating_lookup.sql +124 -0
  51. package/dist/migrations/0009_refresh_tokens.sql +73 -0
  52. package/dist/migrations/0010_definer_search_path.sql +22 -0
  53. package/dist/migrations/0011_sweep_lease.sql +36 -0
  54. package/dist/migrations/0012_prune.sql +118 -0
  55. package/dist/migrations/0013_reindex.sql +65 -0
  56. package/dist/migrations/0014_retired_collections.sql +68 -0
  57. package/dist/oauth.d.ts +45 -0
  58. package/dist/oauth.d.ts.map +1 -0
  59. package/dist/oauth.js +53 -0
  60. package/dist/oauth.js.map +1 -0
  61. package/dist/observability.d.ts.map +1 -1
  62. package/dist/observability.js +100 -13
  63. package/dist/observability.js.map +1 -1
  64. package/dist/passwords.d.ts +37 -0
  65. package/dist/passwords.d.ts.map +1 -0
  66. package/dist/passwords.js +234 -0
  67. package/dist/passwords.js.map +1 -0
  68. package/dist/redis.d.ts +46 -0
  69. package/dist/redis.d.ts.map +1 -0
  70. package/dist/redis.js +234 -0
  71. package/dist/redis.js.map +1 -0
  72. package/dist/reindex.d.ts +87 -0
  73. package/dist/reindex.d.ts.map +1 -0
  74. package/dist/reindex.js +97 -0
  75. package/dist/reindex.js.map +1 -0
  76. package/dist/s3.d.ts +111 -0
  77. package/dist/s3.d.ts.map +1 -0
  78. package/dist/s3.js +242 -0
  79. package/dist/s3.js.map +1 -0
  80. package/dist/tsconfig.tsbuildinfo +1 -1
  81. package/dist/vector/query.d.ts +49 -4
  82. package/dist/vector/query.d.ts.map +1 -1
  83. package/dist/vector/query.js +43 -10
  84. package/dist/vector/query.js.map +1 -1
  85. package/dist/vector/search.d.ts +175 -4
  86. package/dist/vector/search.d.ts.map +1 -1
  87. package/dist/vector/search.js +320 -7
  88. package/dist/vector/search.js.map +1 -1
  89. package/package.json +26 -8
@@ -0,0 +1,97 @@
1
+ /**
2
+ * The state of a layer's move to a different embedding model.
3
+ *
4
+ * Shared between the API, which starts one and reports on it, and the worker,
5
+ * which does the work — so the shape is defined once rather than agreed twice.
6
+ *
7
+ * ## What is built, and what is not
8
+ *
9
+ * `docs/architecture.md` specifies six steps. Four are here: add the new named
10
+ * vector, reindex existing documents in bounded batches, switch `vector_name`
11
+ * atomically, keep search available throughout.
12
+ *
13
+ * Two are not, and are named rather than quietly skipped:
14
+ *
15
+ * - **The Recall@10 gate.** Step 4 checks the new index against "the layer's
16
+ * reference query set" before switching. There is no reference query set
17
+ * anywhere in this product — not a table, not an endpoint, not a
18
+ * configuration file — so building the gate would mean inventing the surface
19
+ * it reads from, and a gate that checks against an empty set is a gate that
20
+ * passes. Until it exists, a switch is a decision the operator makes by
21
+ * starting the reindex, and `docs/architecture.md` says so.
22
+ * - **Dropping the old vector after a rollback window.** Step 6. The old vector
23
+ * stays. It costs storage and it is what makes a rollback a one-statement
24
+ * change, which is worth more than the space while this path is new.
25
+ */
26
+ /**
27
+ * How far along, as a ratio, for `nacre_reindex_progress_ratio`.
28
+ *
29
+ * Clamped at 1 rather than allowed past it. `total` is counted once when the
30
+ * reindex starts and documents ingested afterwards are picked up by the same
31
+ * pass, so `done` legitimately overshoots — and a progress gauge reading 1.4
32
+ * makes an operator think the number is broken rather than that more work
33
+ * arrived. A reindex that is complete reads 1 whatever the counts say.
34
+ *
35
+ * Zero documents is 1, not 0. An empty layer is finished, and a gauge that sits
36
+ * at zero forever for a layer with nothing in it is the stuck-alert shape.
37
+ */
38
+ export function reindexProgress(state) {
39
+ if (state.status === 'complete')
40
+ return 1;
41
+ // The copy has no per-document measure — it moves points, and how many an
42
+ // organization has is not the layer's `total`. Reporting zero through it is
43
+ // honest: nothing this layer is waiting for has been done yet.
44
+ if (state.phase === 'copying')
45
+ return 0;
46
+ if (state.total <= 0)
47
+ return 1;
48
+ return Math.min(1, state.done / state.total);
49
+ }
50
+ /** The JSON shape stored in the column. Snake case, like every other jsonb here. */
51
+ export function toStateJson(state) {
52
+ return {
53
+ status: state.status,
54
+ phase: state.phase,
55
+ shadow_vector: state.shadowVector,
56
+ provider_id: state.providerId,
57
+ started_at: state.startedAt,
58
+ ...(state.finishedAt === undefined ? {} : { finished_at: state.finishedAt }),
59
+ total: state.total,
60
+ done: state.done,
61
+ failed: state.failed,
62
+ ...(state.error === undefined ? {} : { error: state.error }),
63
+ };
64
+ }
65
+ /**
66
+ * Read it back, or `undefined` for a column that holds nothing usable.
67
+ *
68
+ * Undefined rather than a thrown error or a default: a layer with no reindex
69
+ * and a layer whose state cannot be parsed both mean "there is nothing to
70
+ * report", and the caller has no different action to take. The check constraint
71
+ * added in `0013` is what makes the second case not happen.
72
+ */
73
+ export function fromStateJson(raw) {
74
+ if (raw === null || typeof raw !== 'object')
75
+ return undefined;
76
+ const o = raw;
77
+ const status = o.status;
78
+ if (status !== 'running' && status !== 'complete' && status !== 'failed')
79
+ return undefined;
80
+ if (typeof o.shadow_vector !== 'string')
81
+ return undefined;
82
+ return {
83
+ status,
84
+ // Defaulted rather than refused: a state written before the phase existed
85
+ // is an embedding-phase state, because the copy is what introduced it.
86
+ phase: o.phase === 'copying' ? 'copying' : 'embedding',
87
+ shadowVector: o.shadow_vector,
88
+ providerId: typeof o.provider_id === 'string' ? o.provider_id : '',
89
+ startedAt: typeof o.started_at === 'string' ? o.started_at : '',
90
+ ...(typeof o.finished_at === 'string' ? { finishedAt: o.finished_at } : {}),
91
+ total: typeof o.total === 'number' ? o.total : 0,
92
+ done: typeof o.done === 'number' ? o.done : 0,
93
+ failed: typeof o.failed === 'number' ? o.failed : 0,
94
+ ...(typeof o.error === 'string' ? { error: o.error } : {}),
95
+ };
96
+ }
97
+ //# sourceMappingURL=reindex.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reindex.js","sourceRoot":"","sources":["../reindex.ts"],"names":[],"mappings":"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"}
package/dist/s3.d.ts ADDED
@@ -0,0 +1,111 @@
1
+ /**
2
+ * Object storage, signed by hand.
3
+ *
4
+ * `NACRE_S3_*` has been in `docs/config.md` since before there was a server and
5
+ * read by nothing — not used, not validated, so a wrong endpoint or a missing
6
+ * credential was silent while the `full` profile started a MinIO nobody talked
7
+ * to. This is the client that ends that.
8
+ *
9
+ * ─── why not the AWS SDK ───
10
+ *
11
+ * Four operations are needed: put, get, delete, head. `@aws-sdk/client-s3` is
12
+ * tens of megabytes and hundreds of transitive packages, in a container whose
13
+ * job is to read documents other people send it — which is the argument
14
+ * `metrics.ts` already makes for not taking prom-client. SigV4 is a documented
15
+ * algorithm with a published set of test vectors, it fits in this file, and it
16
+ * has no release cadence to track.
17
+ *
18
+ * The rule that comes with that choice: **anything here is verified against a
19
+ * real MinIO before it is believed**, because a signing bug produces a 403 that
20
+ * says nothing about which of the six inputs was wrong.
21
+ *
22
+ * ─── what is deliberately not here ───
23
+ *
24
+ * No multipart upload, so an object is one PUT and `NACRE_MAX_DOCUMENT_BYTES`
25
+ * bounds it. No listing, because nothing needs to enumerate a bucket and a
26
+ * paginated list is where this file would stop being small. No retries: the
27
+ * callers are the ingest queue and the collector, both of which already retry
28
+ * whole units of work and would otherwise retry twice.
29
+ */
30
+ export interface S3Options {
31
+ /** Including the scheme and port, e.g. `http://minio:9000`. No trailing slash. */
32
+ readonly endpoint: string;
33
+ readonly bucket: string;
34
+ readonly region: string;
35
+ readonly accessKey: string;
36
+ readonly secretKey: string;
37
+ /**
38
+ * `{endpoint}/{bucket}/{key}` rather than `{bucket}.{endpoint}/{key}`.
39
+ *
40
+ * True for MinIO and for anything reached by IP or through a name that is not
41
+ * a wildcard DNS entry, which is every self-hosted deployment. AWS itself
42
+ * wants it false.
43
+ */
44
+ readonly forcePathStyle: boolean;
45
+ }
46
+ export declare class S3Error extends Error {
47
+ readonly status: number;
48
+ readonly method: string;
49
+ readonly key: string;
50
+ constructor(status: number, method: string, key: string, body: string);
51
+ }
52
+ export declare class S3 {
53
+ #private;
54
+ constructor(options: S3Options);
55
+ get bucket(): string;
56
+ /** Store an object, overwriting whatever was there. */
57
+ put(key: string, body: Uint8Array, contentType?: string): Promise<void>;
58
+ /** Fetch an object. `undefined` when it is not there, which is not an error. */
59
+ get(key: string): Promise<Uint8Array | undefined>;
60
+ /**
61
+ * Remove an object. Absent is success.
62
+ *
63
+ * S3 answers `204` whether or not the key existed, and the caller — the
64
+ * collector, purging a deleted document — must be able to run twice.
65
+ */
66
+ remove(key: string): Promise<void>;
67
+ /**
68
+ * A URL that fetches one object, for a while, without a credential.
69
+ *
70
+ * Query-string SigV4 rather than the header form: the point is a link a
71
+ * client can follow, so everything that authenticates it has to be in the
72
+ * URL. `host` is the only signed header, and the payload is unsigned because
73
+ * a GET has none.
74
+ *
75
+ * **This is a bearer capability and it leaves the permission model behind.**
76
+ * Nacre checks `read` once, when the link is minted, and the object store
77
+ * checks nothing afterwards — a revocation during the window does not reach a
78
+ * URL already handed out, and neither does a delete. That is what presigning
79
+ * is, in every implementation of it, and it is why the lifetime is a
80
+ * configured number rather than a constant: `NACRE_PRESIGN_TTL` is how long a
81
+ * deployment is willing for that to be true.
82
+ *
83
+ * It is also why the caller of this decides, not this: minting one per search
84
+ * hit would issue ten capabilities to answer a question about relevance, most
85
+ * never followed. See the note on the search response.
86
+ */
87
+ presign(key: string, ttlSeconds: number): string;
88
+ /**
89
+ * Whether the bucket answers, for readiness.
90
+ *
91
+ * A HEAD on the bucket rather than on an object: it needs no key to exist and
92
+ * it fails for the three reasons that matter — unreachable, wrong bucket,
93
+ * wrong credential. A TCP connect would pass all three.
94
+ */
95
+ ready(): Promise<boolean>;
96
+ }
97
+ /**
98
+ * Where a document's bytes live.
99
+ *
100
+ * Derived from identity — organization, layer, external id — and never from the
101
+ * content. Content addressing would let two documents share an object, and then
102
+ * deleting one would either strand the other or need a reference count that
103
+ * nothing else in this system has. One document, one object, and a re-ingest
104
+ * overwrites in place.
105
+ *
106
+ * The external id is hashed rather than escaped: it is caller-chosen text that
107
+ * may contain anything, and a key derived by escaping is a key whose length and
108
+ * shape a caller controls.
109
+ */
110
+ export declare function documentKey(orgId: string, layerId: string, externalId: string): string;
111
+ //# sourceMappingURL=s3.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"s3.d.ts","sourceRoot":"","sources":["../s3.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,MAAM,WAAW,SAAS;IACxB,kFAAkF;IAClF,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B;;;;;;OAMG;IACH,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAA;CACjC;AA8BD,qBAAa,OAAQ,SAAQ,KAAK;IAE9B,QAAQ,CAAC,MAAM,EAAE,MAAM;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM;IACvB,QAAQ,CAAC,GAAG,EAAE,MAAM;gBAFX,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,EACpB,IAAI,EAAE,MAAM;CAQf;AAED,qBAAa,EAAE;;gBAGD,OAAO,EAAE,SAAS;IAI9B,IAAI,MAAM,IAAI,MAAM,CAEnB;IAuGD,uDAAuD;IACjD,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE,WAAW,SAA6B,GAAG,OAAO,CAAC,IAAI,CAAC;IAKjG,gFAAgF;IAC1E,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC;IAOvD;;;;;OAKG;IACG,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAOxC;;;;;;;;;;;;;;;;;;;OAmBG;IACH,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM;IA2ChD;;;;;;OAMG;IACG,KAAK,IAAI,OAAO,CAAC,OAAO,CAAC;CAiBhC;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAEtF"}
package/dist/s3.js ADDED
@@ -0,0 +1,242 @@
1
+ import { createHash, createHmac } from 'node:crypto';
2
+ const UNSIGNED = 'UNSIGNED-PAYLOAD';
3
+ const ALGORITHM = 'AWS4-HMAC-SHA256';
4
+ const SERVICE = 's3';
5
+ const sha256 = (data) => createHash('sha256').update(data).digest('hex');
6
+ const hmac = (key, data) => createHmac('sha256', key).update(data, 'utf8').digest();
7
+ /**
8
+ * Percent-encode one path segment the way SigV4 wants it.
9
+ *
10
+ * `encodeURIComponent` leaves `!'()*` alone and AWS does not, and it encodes
11
+ * nothing else differently — so this is that function plus five characters. A
12
+ * key containing any of them signs one way and is requested another, which is a
13
+ * 403 with no clue in it.
14
+ */
15
+ function encodeSegment(segment) {
16
+ return encodeURIComponent(segment).replace(/[!'()*]/g, (c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`);
17
+ }
18
+ /** Keys are `a/b/c`; each segment is encoded and the separators are not. */
19
+ const encodeKey = (key) => key.split('/').map(encodeSegment).join('/');
20
+ export class S3Error extends Error {
21
+ status;
22
+ method;
23
+ key;
24
+ constructor(status, method, key, body) {
25
+ // The body, because S3 and MinIO both answer with an XML document naming
26
+ // the exact code — SignatureDoesNotMatch, NoSuchBucket, AccessDenied — and
27
+ // a status alone sends the reader to the wrong one of six inputs.
28
+ super(`s3 ${method} ${key} failed: ${status} ${body.slice(0, 512)}`);
29
+ this.status = status;
30
+ this.method = method;
31
+ this.key = key;
32
+ this.name = 'S3Error';
33
+ }
34
+ }
35
+ export class S3 {
36
+ #options;
37
+ constructor(options) {
38
+ this.#options = { ...options, endpoint: options.endpoint.replace(/\/+$/, '') };
39
+ }
40
+ get bucket() {
41
+ return this.#options.bucket;
42
+ }
43
+ #url(key) {
44
+ const { endpoint, bucket, forcePathStyle } = this.#options;
45
+ if (forcePathStyle)
46
+ return new URL(`${endpoint}/${bucket}/${encodeKey(key)}`);
47
+ const url = new URL(endpoint);
48
+ url.host = `${bucket}.${url.host}`;
49
+ url.pathname = `/${encodeKey(key)}`;
50
+ return url;
51
+ }
52
+ /**
53
+ * Sign a request, returning the headers to send with it.
54
+ *
55
+ * The payload hash goes in `x-amz-content-sha256` and into the canonical
56
+ * request, so a body that changes in flight fails the signature rather than
57
+ * being stored. `UNSIGNED-PAYLOAD` is used for GET and DELETE, which have
58
+ * none — hashing the empty string would also work and this is what every
59
+ * other implementation sends.
60
+ */
61
+ #sign(input) {
62
+ const { region, accessKey, secretKey } = this.#options;
63
+ const amzDate = input.now.toISOString().replace(/[-:]|\.\d{3}/g, '');
64
+ const dateStamp = amzDate.slice(0, 8);
65
+ const headers = {
66
+ ...input.headers,
67
+ host: input.url.host,
68
+ 'x-amz-content-sha256': input.payloadHash,
69
+ 'x-amz-date': amzDate,
70
+ };
71
+ // Lowercased, sorted, values trimmed — the canonical form. Getting the
72
+ // order wrong is a 403 that reads exactly like a wrong secret.
73
+ const canonicalHeaderNames = Object.keys(headers)
74
+ .map((h) => h.toLowerCase())
75
+ .sort();
76
+ const canonicalHeaders = canonicalHeaderNames
77
+ .map((name) => {
78
+ const key = Object.keys(headers).find((h) => h.toLowerCase() === name);
79
+ return `${name}:${String(headers[key]).trim()}\n`;
80
+ })
81
+ .join('');
82
+ const signedHeaders = canonicalHeaderNames.join(';');
83
+ const canonicalRequest = [
84
+ input.method,
85
+ input.url.pathname,
86
+ // No request here takes a query string. An empty one is still a line.
87
+ '',
88
+ canonicalHeaders,
89
+ signedHeaders,
90
+ input.payloadHash,
91
+ ].join('\n');
92
+ const scope = `${dateStamp}/${region}/${SERVICE}/aws4_request`;
93
+ const stringToSign = [ALGORITHM, amzDate, scope, sha256(canonicalRequest)].join('\n');
94
+ const signingKey = hmac(hmac(hmac(hmac(`AWS4${secretKey}`, dateStamp), region), SERVICE), 'aws4_request');
95
+ const signature = createHmac('sha256', signingKey).update(stringToSign, 'utf8').digest('hex');
96
+ return {
97
+ ...headers,
98
+ authorization: `${ALGORITHM} Credential=${accessKey}/${scope}, ` +
99
+ `SignedHeaders=${signedHeaders}, Signature=${signature}`,
100
+ };
101
+ }
102
+ async #send(method, key, body, extraHeaders = {}) {
103
+ const url = this.#url(key);
104
+ const headers = this.#sign({
105
+ method,
106
+ url,
107
+ payloadHash: body === undefined ? UNSIGNED : sha256(body),
108
+ headers: body === undefined
109
+ ? extraHeaders
110
+ : { ...extraHeaders, 'content-length': String(body.length) },
111
+ now: new Date(),
112
+ });
113
+ return fetch(url, {
114
+ method,
115
+ headers,
116
+ ...(body === undefined ? {} : { body }),
117
+ });
118
+ }
119
+ /** Store an object, overwriting whatever was there. */
120
+ async put(key, body, contentType = 'application/octet-stream') {
121
+ const response = await this.#send('PUT', key, body, { 'content-type': contentType });
122
+ if (!response.ok)
123
+ throw new S3Error(response.status, 'PUT', key, await response.text());
124
+ }
125
+ /** Fetch an object. `undefined` when it is not there, which is not an error. */
126
+ async get(key) {
127
+ const response = await this.#send('GET', key);
128
+ if (response.status === 404)
129
+ return undefined;
130
+ if (!response.ok)
131
+ throw new S3Error(response.status, 'GET', key, await response.text());
132
+ return new Uint8Array(await response.arrayBuffer());
133
+ }
134
+ /**
135
+ * Remove an object. Absent is success.
136
+ *
137
+ * S3 answers `204` whether or not the key existed, and the caller — the
138
+ * collector, purging a deleted document — must be able to run twice.
139
+ */
140
+ async remove(key) {
141
+ const response = await this.#send('DELETE', key);
142
+ if (!response.ok && response.status !== 404) {
143
+ throw new S3Error(response.status, 'DELETE', key, await response.text());
144
+ }
145
+ }
146
+ /**
147
+ * A URL that fetches one object, for a while, without a credential.
148
+ *
149
+ * Query-string SigV4 rather than the header form: the point is a link a
150
+ * client can follow, so everything that authenticates it has to be in the
151
+ * URL. `host` is the only signed header, and the payload is unsigned because
152
+ * a GET has none.
153
+ *
154
+ * **This is a bearer capability and it leaves the permission model behind.**
155
+ * Nacre checks `read` once, when the link is minted, and the object store
156
+ * checks nothing afterwards — a revocation during the window does not reach a
157
+ * URL already handed out, and neither does a delete. That is what presigning
158
+ * is, in every implementation of it, and it is why the lifetime is a
159
+ * configured number rather than a constant: `NACRE_PRESIGN_TTL` is how long a
160
+ * deployment is willing for that to be true.
161
+ *
162
+ * It is also why the caller of this decides, not this: minting one per search
163
+ * hit would issue ten capabilities to answer a question about relevance, most
164
+ * never followed. See the note on the search response.
165
+ */
166
+ presign(key, ttlSeconds) {
167
+ const { region, accessKey, secretKey } = this.#options;
168
+ const url = this.#url(key);
169
+ const now = new Date();
170
+ const amzDate = now.toISOString().replace(/[-:]|\.\d{3}/g, '');
171
+ const dateStamp = amzDate.slice(0, 8);
172
+ const scope = `${dateStamp}/${region}/${SERVICE}/aws4_request`;
173
+ // Sorted by key, and encoded the same way the canonical request will be —
174
+ // a mismatch between the query that is signed and the query that is sent is
175
+ // a 403 with nothing in it about which of the two was wrong.
176
+ const params = [
177
+ ['X-Amz-Algorithm', ALGORITHM],
178
+ ['X-Amz-Credential', `${accessKey}/${scope}`],
179
+ ['X-Amz-Date', amzDate],
180
+ ['X-Amz-Expires', String(Math.max(1, Math.floor(ttlSeconds)))],
181
+ ['X-Amz-SignedHeaders', 'host'],
182
+ ];
183
+ const canonicalQuery = params
184
+ .map(([k, v]) => [encodeSegment(k), encodeSegment(v)])
185
+ .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))
186
+ .map(([k, v]) => `${k}=${v}`)
187
+ .join('&');
188
+ const canonicalRequest = [
189
+ 'GET',
190
+ url.pathname,
191
+ canonicalQuery,
192
+ `host:${url.host}\n`,
193
+ 'host',
194
+ UNSIGNED,
195
+ ].join('\n');
196
+ const stringToSign = [ALGORITHM, amzDate, scope, sha256(canonicalRequest)].join('\n');
197
+ const signingKey = hmac(hmac(hmac(hmac(`AWS4${secretKey}`, dateStamp), region), SERVICE), 'aws4_request');
198
+ const signature = createHmac('sha256', signingKey).update(stringToSign, 'utf8').digest('hex');
199
+ return `${url.origin}${url.pathname}?${canonicalQuery}&X-Amz-Signature=${signature}`;
200
+ }
201
+ /**
202
+ * Whether the bucket answers, for readiness.
203
+ *
204
+ * A HEAD on the bucket rather than on an object: it needs no key to exist and
205
+ * it fails for the three reasons that matter — unreachable, wrong bucket,
206
+ * wrong credential. A TCP connect would pass all three.
207
+ */
208
+ async ready() {
209
+ const url = this.#options.forcePathStyle
210
+ ? new URL(`${this.#options.endpoint}/${this.#options.bucket}`)
211
+ : this.#url('');
212
+ const headers = this.#sign({
213
+ method: 'HEAD',
214
+ url,
215
+ payloadHash: UNSIGNED,
216
+ headers: {},
217
+ now: new Date(),
218
+ });
219
+ const response = await fetch(url, { method: 'HEAD', headers });
220
+ if (!response.ok) {
221
+ throw new S3Error(response.status, 'HEAD', this.#options.bucket, response.statusText);
222
+ }
223
+ return true;
224
+ }
225
+ }
226
+ /**
227
+ * Where a document's bytes live.
228
+ *
229
+ * Derived from identity — organization, layer, external id — and never from the
230
+ * content. Content addressing would let two documents share an object, and then
231
+ * deleting one would either strand the other or need a reference count that
232
+ * nothing else in this system has. One document, one object, and a re-ingest
233
+ * overwrites in place.
234
+ *
235
+ * The external id is hashed rather than escaped: it is caller-chosen text that
236
+ * may contain anything, and a key derived by escaping is a key whose length and
237
+ * shape a caller controls.
238
+ */
239
+ export function documentKey(orgId, layerId, externalId) {
240
+ return `org/${orgId}/layer/${layerId}/${sha256(externalId)}`;
241
+ }
242
+ //# sourceMappingURL=s3.js.map
package/dist/s3.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"s3.js","sourceRoot":"","sources":["../s3.ts"],"names":[],"mappings":"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"}