@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.
- package/LICENSE +202 -0
- package/NOTICE +9 -0
- package/dist/audit.d.ts +41 -0
- package/dist/audit.d.ts.map +1 -0
- package/dist/audit.js +18 -0
- package/dist/audit.js.map +1 -0
- package/dist/authz/cache.d.ts +20 -0
- package/dist/authz/cache.d.ts.map +1 -1
- package/dist/authz/cache.js +29 -0
- package/dist/authz/cache.js.map +1 -1
- package/dist/authz/filter.d.ts +54 -3
- package/dist/authz/filter.d.ts.map +1 -1
- package/dist/authz/filter.js +45 -9
- package/dist/authz/filter.js.map +1 -1
- package/dist/authz/index.d.ts +3 -3
- package/dist/authz/index.d.ts.map +1 -1
- package/dist/authz/index.js +2 -2
- package/dist/authz/index.js.map +1 -1
- package/dist/config.d.ts +55 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +245 -7
- package/dist/config.js.map +1 -1
- package/dist/db/client.d.ts +39 -0
- package/dist/db/client.d.ts.map +1 -1
- package/dist/db/client.js +82 -0
- package/dist/db/client.js.map +1 -1
- package/dist/db/migrate.d.ts.map +1 -1
- package/dist/db/migrate.js +49 -0
- package/dist/db/migrate.js.map +1 -1
- package/dist/index.d.ts +21 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -3
- package/dist/index.js.map +1 -1
- package/dist/lifecycle.d.ts +57 -0
- package/dist/lifecycle.d.ts.map +1 -0
- package/dist/lifecycle.js +84 -0
- package/dist/lifecycle.js.map +1 -0
- package/dist/logging.d.ts +46 -0
- package/dist/logging.d.ts.map +1 -0
- package/dist/logging.js +110 -0
- package/dist/logging.js.map +1 -0
- package/dist/metadata.d.ts +50 -0
- package/dist/metadata.d.ts.map +1 -0
- package/dist/metadata.js +127 -0
- package/dist/metadata.js.map +1 -0
- package/dist/metrics.d.ts +19 -1
- package/dist/metrics.d.ts.map +1 -1
- package/dist/metrics.js +84 -2
- package/dist/metrics.js.map +1 -1
- package/dist/migrations/0008_authenticating_lookup.sql +124 -0
- package/dist/migrations/0009_refresh_tokens.sql +73 -0
- package/dist/migrations/0010_definer_search_path.sql +22 -0
- package/dist/migrations/0011_sweep_lease.sql +36 -0
- package/dist/migrations/0012_prune.sql +118 -0
- package/dist/migrations/0013_reindex.sql +65 -0
- package/dist/migrations/0014_retired_collections.sql +68 -0
- package/dist/oauth.d.ts +45 -0
- package/dist/oauth.d.ts.map +1 -0
- package/dist/oauth.js +53 -0
- package/dist/oauth.js.map +1 -0
- package/dist/observability.d.ts.map +1 -1
- package/dist/observability.js +100 -13
- package/dist/observability.js.map +1 -1
- package/dist/passwords.d.ts +37 -0
- package/dist/passwords.d.ts.map +1 -0
- package/dist/passwords.js +234 -0
- package/dist/passwords.js.map +1 -0
- package/dist/redis.d.ts +46 -0
- package/dist/redis.d.ts.map +1 -0
- package/dist/redis.js +234 -0
- package/dist/redis.js.map +1 -0
- package/dist/reindex.d.ts +87 -0
- package/dist/reindex.d.ts.map +1 -0
- package/dist/reindex.js +97 -0
- package/dist/reindex.js.map +1 -0
- package/dist/s3.d.ts +111 -0
- package/dist/s3.d.ts.map +1 -0
- package/dist/s3.js +242 -0
- package/dist/s3.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/vector/query.d.ts +49 -4
- package/dist/vector/query.d.ts.map +1 -1
- package/dist/vector/query.js +43 -10
- package/dist/vector/query.js.map +1 -1
- package/dist/vector/search.d.ts +175 -4
- package/dist/vector/search.d.ts.map +1 -1
- package/dist/vector/search.js +320 -7
- package/dist/vector/search.js.map +1 -1
- package/package.json +26 -8
package/dist/db/migrate.js
CHANGED
|
@@ -3,6 +3,19 @@ import { join } from 'node:path';
|
|
|
3
3
|
import { fileURLToPath } from 'node:url';
|
|
4
4
|
import { Client } from 'pg';
|
|
5
5
|
const MIGRATIONS_DIR = fileURLToPath(new URL('../migrations/', import.meta.url));
|
|
6
|
+
/**
|
|
7
|
+
* How long a migration may wait for a lock before giving up. See the call site.
|
|
8
|
+
*
|
|
9
|
+
* A migration that needs longer can say so: `SET LOCAL lock_timeout` inside the
|
|
10
|
+
* file overrides this for the rest of that transaction, and it is the right
|
|
11
|
+
* place for the exception because the migration is what knows it is unusual.
|
|
12
|
+
*
|
|
13
|
+
* `statement_timeout` is deliberately not touched. Some managed platforms set
|
|
14
|
+
* one globally and a long index build will hit it — but that is the operator's
|
|
15
|
+
* configuration, the failure rolls back cleanly, and quietly overriding a bound
|
|
16
|
+
* somebody chose is worse than failing where they can see it.
|
|
17
|
+
*/
|
|
18
|
+
const LOCK_TIMEOUT = '10s';
|
|
6
19
|
/**
|
|
7
20
|
* Migrations in filename order. Numbered prefixes are compared as numbers, so
|
|
8
21
|
* `0010` sorts after `0009` rather than between `0001` and `0002`.
|
|
@@ -81,6 +94,30 @@ export async function migrate(connectionString, migrations = loadMigrations()) {
|
|
|
81
94
|
}
|
|
82
95
|
await client.query('BEGIN');
|
|
83
96
|
try {
|
|
97
|
+
// Bounded waiting, set inside the transaction so it reverts on its own.
|
|
98
|
+
//
|
|
99
|
+
// `ALTER TABLE` takes ACCESS EXCLUSIVE, which conflicts with everything
|
|
100
|
+
// including a plain SELECT. With no timeout the sequence on a busy
|
|
101
|
+
// database is: the ALTER waits behind one long-running query, and every
|
|
102
|
+
// statement arriving afterwards queues behind the ALTER — because a
|
|
103
|
+
// lock request is not overtaken by weaker ones. One slow reporting
|
|
104
|
+
// query therefore stops the entire application for as long as it runs,
|
|
105
|
+
// and the migration that appeared to be "just adding a column" is what
|
|
106
|
+
// the outage gets blamed on.
|
|
107
|
+
//
|
|
108
|
+
// `lock_timeout`, not `statement_timeout`: the two failures are not
|
|
109
|
+
// alike. Waiting for a lock is a scheduling problem and retrying in a
|
|
110
|
+
// quieter minute fixes it, so failing fast is free. Doing the work is
|
|
111
|
+
// not — a backfill or an index build on a large table legitimately
|
|
112
|
+
// takes longer than any bound worth setting, and killing it halfway
|
|
113
|
+
// achieves nothing except a rollback. So the wait is capped and the
|
|
114
|
+
// work is not.
|
|
115
|
+
//
|
|
116
|
+
// Ten seconds because it is far longer than any lock this schema takes
|
|
117
|
+
// when the queue is empty, and far shorter than a deploy timeout — the
|
|
118
|
+
// migration fails with `55P03 lock_not_available`, which names the
|
|
119
|
+
// problem, instead of hanging until someone kills it and has to guess.
|
|
120
|
+
await client.query(`SET LOCAL lock_timeout = '${LOCK_TIMEOUT}'`);
|
|
84
121
|
await client.query(migration.sql);
|
|
85
122
|
await client.query('INSERT INTO schema_migrations (name, checksum) VALUES ($1, $2)', [
|
|
86
123
|
migration.name,
|
|
@@ -91,6 +128,18 @@ export async function migrate(connectionString, migrations = loadMigrations()) {
|
|
|
91
128
|
}
|
|
92
129
|
catch (cause) {
|
|
93
130
|
await client.query('ROLLBACK');
|
|
131
|
+
// 55P03 is `lock_not_available`, and it is worth naming: it is the one
|
|
132
|
+
// failure here that says nothing about the migration. The SQL is fine,
|
|
133
|
+
// something else was holding the table, and the fix is to look at
|
|
134
|
+
// pg_stat_activity rather than at the file.
|
|
135
|
+
if (cause.code === '55P03') {
|
|
136
|
+
throw new Error(`migration ${migration.name} gave up waiting for a lock after ${LOCK_TIMEOUT} ` +
|
|
137
|
+
'and was rolled back. Something else is holding the table — a long query, an ' +
|
|
138
|
+
'idle-in-transaction session, another migration — so this is not a problem with ' +
|
|
139
|
+
'the migration itself and re-running it once the holder is gone will work. ' +
|
|
140
|
+
'The wait is capped deliberately: an ALTER queued behind one slow query blocks ' +
|
|
141
|
+
'every statement that arrives after it, which takes the application down.', { cause });
|
|
142
|
+
}
|
|
94
143
|
throw new Error(`migration ${migration.name} failed and was rolled back`, { cause });
|
|
95
144
|
}
|
|
96
145
|
}
|
package/dist/db/migrate.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"migrate.js","sourceRoot":"","sources":["../../db/migrate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AACnD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAChC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAExC,OAAO,EAAE,MAAM,EAAE,MAAM,IAAI,CAAA;AAE3B,MAAM,cAAc,GAAG,aAAa,CAAC,IAAI,GAAG,CAAC,gBAAgB,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAA;
|
|
1
|
+
{"version":3,"file":"migrate.js","sourceRoot":"","sources":["../../db/migrate.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,16 +1,34 @@
|
|
|
1
1
|
export * from './types.js';
|
|
2
2
|
export * from './authz/index.js';
|
|
3
|
-
export {
|
|
3
|
+
export { parseFilters, parseMetadata, MetadataError, MAX_METADATA_KEYS, MAX_METADATA_KEY_LENGTH, MAX_METADATA_LIST, MAX_METADATA_VALUE_LENGTH, } from './metadata.js';
|
|
4
|
+
export type { Metadata } from './metadata.js';
|
|
5
|
+
export { loadConfig, loadJwtKeys, ConfigError } from './config.js';
|
|
6
|
+
export { protectedResourceMetadata, PROTECTED_RESOURCE_PATH } from './oauth.js';
|
|
7
|
+
export type { ProtectedResourceMetadata } from './oauth.js';
|
|
8
|
+
export type { JwtKeys } from './config.js';
|
|
4
9
|
export type { Config } from './config.js';
|
|
5
|
-
export { createPool, withOrg } from './db/client.js';
|
|
10
|
+
export { acrossOrganizations, createPool, whileAuthenticating, withOrg } from './db/client.js';
|
|
6
11
|
export type { DbOptions, WithOrgOptions } from './db/client.js';
|
|
7
12
|
export { migrate, loadMigrations } from './db/migrate.js';
|
|
8
13
|
export { loadGrants, loadGroupsVersion, loadScopeTree, PostgresGroupGraph } from './authz/store.js';
|
|
9
|
-
export { VectorStore } from './vector/search.js';
|
|
14
|
+
export { explainQdrant, MetadataIndexer, METADATA_INDEX_LIMIT, VectorStore } from './vector/search.js';
|
|
15
|
+
export { MAX_AUDITED_QUERY, queryAudit } from './audit.js';
|
|
16
|
+
export type { QueryAudit } from './audit.js';
|
|
17
|
+
export { configureLogging, createLogger, logger } from './logging.js';
|
|
18
|
+
export type { Logger, LoggerOptions, LogFormat, LogLevel } from './logging.js';
|
|
19
|
+
export { documentKey, S3, S3Error } from './s3.js';
|
|
20
|
+
export type { S3Options } from './s3.js';
|
|
10
21
|
export type { Hit, SearchRequest, VectorStoreOptions } from './vector/search.js';
|
|
11
22
|
export { buildHybridQuery, collectionConfig, collectionName, PAYLOAD_INDEXES, vectorName } from './vector/query.js';
|
|
12
23
|
export type { Branch, HybridQuery } from './vector/query.js';
|
|
13
24
|
export { Counter, Gauge, Histogram, Registry, createMetrics } from './metrics.js';
|
|
14
25
|
export type { Labels, Metrics } from './metrics.js';
|
|
15
26
|
export { collectDatabaseGauges } from './observability.js';
|
|
27
|
+
export { Redis, RedisError } from './redis.js';
|
|
28
|
+
export type { RedisOptions } from './redis.js';
|
|
29
|
+
export { reindexProgress, toStateJson, fromStateJson } from './reindex.js';
|
|
30
|
+
export type { ReindexState } from './reindex.js';
|
|
31
|
+
export { hashingLoad, hashPassword, needsRehash, spendVerificationTime, TooBusy, verifyPassword, } from './passwords.js';
|
|
32
|
+
export { installGuards, onListenError } from './lifecycle.js';
|
|
33
|
+
export type { Guards } from './lifecycle.js';
|
|
16
34
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAA;AAC1B,cAAc,kBAAkB,CAAA;AAChC,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAA;AAC1B,cAAc,kBAAkB,CAAA;AAChC,OAAO,EACL,YAAY,EACZ,aAAa,EACb,aAAa,EACb,iBAAiB,EACjB,uBAAuB,EACvB,iBAAiB,EACjB,yBAAyB,GAC1B,MAAM,eAAe,CAAA;AACtB,YAAY,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AAC7C,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAClE,OAAO,EAAE,yBAAyB,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAA;AAC/E,YAAY,EAAE,yBAAyB,EAAE,MAAM,YAAY,CAAA;AAC3D,YAAY,EAAE,OAAO,EAAE,MAAM,aAAa,CAAA;AAC1C,YAAY,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACzC,OAAO,EAAE,mBAAmB,EAAE,UAAU,EAAE,mBAAmB,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAA;AAC9F,YAAY,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAC/D,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AACzD,OAAO,EAAE,UAAU,EAAE,iBAAiB,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAA;AACnG,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,oBAAoB,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AACtG,OAAO,EAAE,iBAAiB,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAC1D,YAAY,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAC5C,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,cAAc,CAAA;AACrE,YAAY,EAAE,MAAM,EAAE,aAAa,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAA;AAC9E,OAAO,EAAE,WAAW,EAAE,EAAE,EAAE,OAAO,EAAE,MAAM,SAAS,CAAA;AAClD,YAAY,EAAE,SAAS,EAAE,MAAM,SAAS,CAAA;AACxC,YAAY,EAAE,GAAG,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAA;AAChF,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,cAAc,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AACnH,YAAY,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAC5D,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;AACjF,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AACnD,OAAO,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAA;AAC1D,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAC9C,YAAY,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAC9C,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;AAC1E,YAAY,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AAChD,OAAO,EACL,WAAW,EACX,YAAY,EACZ,WAAW,EACX,qBAAqB,EACrB,OAAO,EACP,cAAc,GACf,MAAM,gBAAgB,CAAA;AACvB,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAC7D,YAAY,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -1,11 +1,20 @@
|
|
|
1
1
|
export * from './types.js';
|
|
2
2
|
export * from './authz/index.js';
|
|
3
|
-
export {
|
|
4
|
-
export {
|
|
3
|
+
export { parseFilters, parseMetadata, MetadataError, MAX_METADATA_KEYS, MAX_METADATA_KEY_LENGTH, MAX_METADATA_LIST, MAX_METADATA_VALUE_LENGTH, } from './metadata.js';
|
|
4
|
+
export { loadConfig, loadJwtKeys, ConfigError } from './config.js';
|
|
5
|
+
export { protectedResourceMetadata, PROTECTED_RESOURCE_PATH } from './oauth.js';
|
|
6
|
+
export { acrossOrganizations, createPool, whileAuthenticating, withOrg } from './db/client.js';
|
|
5
7
|
export { migrate, loadMigrations } from './db/migrate.js';
|
|
6
8
|
export { loadGrants, loadGroupsVersion, loadScopeTree, PostgresGroupGraph } from './authz/store.js';
|
|
7
|
-
export { VectorStore } from './vector/search.js';
|
|
9
|
+
export { explainQdrant, MetadataIndexer, METADATA_INDEX_LIMIT, VectorStore } from './vector/search.js';
|
|
10
|
+
export { MAX_AUDITED_QUERY, queryAudit } from './audit.js';
|
|
11
|
+
export { configureLogging, createLogger, logger } from './logging.js';
|
|
12
|
+
export { documentKey, S3, S3Error } from './s3.js';
|
|
8
13
|
export { buildHybridQuery, collectionConfig, collectionName, PAYLOAD_INDEXES, vectorName } from './vector/query.js';
|
|
9
14
|
export { Counter, Gauge, Histogram, Registry, createMetrics } from './metrics.js';
|
|
10
15
|
export { collectDatabaseGauges } from './observability.js';
|
|
16
|
+
export { Redis, RedisError } from './redis.js';
|
|
17
|
+
export { reindexProgress, toStateJson, fromStateJson } from './reindex.js';
|
|
18
|
+
export { hashingLoad, hashPassword, needsRehash, spendVerificationTime, TooBusy, verifyPassword, } from './passwords.js';
|
|
19
|
+
export { installGuards, onListenError } from './lifecycle.js';
|
|
11
20
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAA;AAC1B,cAAc,kBAAkB,CAAA;AAChC,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAA;AAC1B,cAAc,kBAAkB,CAAA;AAChC,OAAO,EACL,YAAY,EACZ,aAAa,EACb,aAAa,EACb,iBAAiB,EACjB,uBAAuB,EACvB,iBAAiB,EACjB,yBAAyB,GAC1B,MAAM,eAAe,CAAA;AAEtB,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAClE,OAAO,EAAE,yBAAyB,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAA;AAI/E,OAAO,EAAE,mBAAmB,EAAE,UAAU,EAAE,mBAAmB,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAA;AAE9F,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AACzD,OAAO,EAAE,UAAU,EAAE,iBAAiB,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAA;AACnG,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,oBAAoB,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AACtG,OAAO,EAAE,iBAAiB,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAE1D,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,cAAc,CAAA;AAErE,OAAO,EAAE,WAAW,EAAE,EAAE,EAAE,OAAO,EAAE,MAAM,SAAS,CAAA;AAGlD,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,cAAc,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAEnH,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;AAEjF,OAAO,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAA;AAC1D,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAE9C,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;AAE1E,OAAO,EACL,WAAW,EACX,YAAY,EACZ,WAAW,EACX,qBAAqB,EACrB,OAAO,EACP,cAAc,GACf,MAAM,gBAAgB,CAAA;AACvB,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What every process does when it is asked to stop, and when it is about to
|
|
3
|
+
* die for a reason nobody wrote down.
|
|
4
|
+
*
|
|
5
|
+
* Three processes ship here — api, mcp, worker — and none of them had any of
|
|
6
|
+
* this. The consequences were all of the same shape: a failure that produced no
|
|
7
|
+
* log line, so the operator saw a container restart and had nothing to read.
|
|
8
|
+
*
|
|
9
|
+
* - **No `unhandledRejection` handler.** Node terminates the process on one,
|
|
10
|
+
* with a raw stack on stderr and no service name, no timestamp in the format
|
|
11
|
+
* every other line uses, and nothing to join to a request.
|
|
12
|
+
* - **No `error` listener on `listen`.** `EADDRINUSE` is emitted on the server
|
|
13
|
+
* *after* `main()` has already resolved, so `main().catch` never sees it and
|
|
14
|
+
* the careful ConfigError path — which exits 2 with an explanation — is
|
|
15
|
+
* bypassed by the one startup failure an operator hits most.
|
|
16
|
+
* - **No deadline on shutdown.** `server.close()` waits for in-flight requests
|
|
17
|
+
* with no upper bound. One request blocked on a slow dependency means the
|
|
18
|
+
* callback never runs, the pool is never released, and the orchestrator
|
|
19
|
+
* SIGKILLs at the end of its grace period — turning every rolling deploy into
|
|
20
|
+
* an abrupt one, silently.
|
|
21
|
+
*/
|
|
22
|
+
export interface Guards {
|
|
23
|
+
/** Named in every line, so two processes in one log stream are tellable apart. */
|
|
24
|
+
readonly service: string;
|
|
25
|
+
/**
|
|
26
|
+
* Release what the process holds. Called once, with a deadline.
|
|
27
|
+
*
|
|
28
|
+
* Whatever it does, it must not be the only thing standing between the
|
|
29
|
+
* process and exiting: if it hangs, the deadline below exits anyway.
|
|
30
|
+
*/
|
|
31
|
+
readonly shutdown?: () => Promise<void> | void;
|
|
32
|
+
/** How long `shutdown` gets. Shorter than any orchestrator's grace period. */
|
|
33
|
+
readonly graceMs?: number;
|
|
34
|
+
/** Injected in tests. Nothing else should be replacing this. */
|
|
35
|
+
readonly exit?: (code: number) => void;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Install the handlers. Returns a `stop` to call from a signal handler.
|
|
39
|
+
*
|
|
40
|
+
* Idempotent: a second SIGTERM while shutting down is ignored rather than
|
|
41
|
+
* starting a second shutdown, because an orchestrator that sends one and then
|
|
42
|
+
* loses patience sends another.
|
|
43
|
+
*/
|
|
44
|
+
export declare function installGuards(guards: Guards): {
|
|
45
|
+
stop: (signal: string) => void;
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* Fail a `listen` the way every other startup failure fails.
|
|
49
|
+
*
|
|
50
|
+
* `server.listen(port, cb)` reports a bound port through the callback and a
|
|
51
|
+
* refusal through an `error` event — which arrives after `main()` has resolved,
|
|
52
|
+
* so nothing was catching it.
|
|
53
|
+
*/
|
|
54
|
+
export declare function onListenError(server: {
|
|
55
|
+
on(event: 'error', listener: (error: NodeJS.ErrnoException) => void): unknown;
|
|
56
|
+
}, service: string, port: number, exit?: (code: number) => void): void;
|
|
57
|
+
//# sourceMappingURL=lifecycle.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lifecycle.d.ts","sourceRoot":"","sources":["../lifecycle.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,MAAM,WAAW,MAAM;IACrB,kFAAkF;IAClF,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB;;;;;OAKG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;IAC9C,8EAA8E;IAC9E,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAA;IACzB,gEAAgE;IAChE,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;CACvC;AAkBD;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAA;CAAE,CAmDhF;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE;IAAE,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,cAAc,KAAK,IAAI,GAAG,OAAO,CAAA;CAAE,EACzF,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EACZ,IAAI,GAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAmC,GAC1D,IAAI,CAWN"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { logger } from './logging.js';
|
|
2
|
+
/**
|
|
3
|
+
* Through the process logger, so `NACRE_LOG_FORMAT` reaches these too.
|
|
4
|
+
*
|
|
5
|
+
* The level is a parameter rather than always `error`: a clean shutdown is not
|
|
6
|
+
* a failure, and reporting it as one is how a dashboard counting errors turns
|
|
7
|
+
* every deploy into an incident. What is an error stays an error.
|
|
8
|
+
*/
|
|
9
|
+
const log = (level, service, msg, extra = {}) => {
|
|
10
|
+
logger[level](msg, { service, ...extra });
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Install the handlers. Returns a `stop` to call from a signal handler.
|
|
14
|
+
*
|
|
15
|
+
* Idempotent: a second SIGTERM while shutting down is ignored rather than
|
|
16
|
+
* starting a second shutdown, because an orchestrator that sends one and then
|
|
17
|
+
* loses patience sends another.
|
|
18
|
+
*/
|
|
19
|
+
export function installGuards(guards) {
|
|
20
|
+
const { service, shutdown, graceMs = 10_000, exit = (code) => process.exit(code) } = guards;
|
|
21
|
+
let stopping = false;
|
|
22
|
+
process.on('unhandledRejection', (reason) => {
|
|
23
|
+
// Logged and then rethrown by exiting, rather than swallowed. A promise
|
|
24
|
+
// nobody awaited failing means the process is in a state its author did not
|
|
25
|
+
// plan for, and continuing from there is how one bad request becomes a
|
|
26
|
+
// corrupt one.
|
|
27
|
+
log('error', service, 'unhandled rejection; exiting', { error: String(reason).slice(0, 500) });
|
|
28
|
+
exit(1);
|
|
29
|
+
});
|
|
30
|
+
process.on('uncaughtException', (error) => {
|
|
31
|
+
log('error', service, 'uncaught exception; exiting', {
|
|
32
|
+
error: String(error).slice(0, 500),
|
|
33
|
+
stack: error.stack?.split('\n').slice(1, 6).join(' | '),
|
|
34
|
+
});
|
|
35
|
+
exit(1);
|
|
36
|
+
});
|
|
37
|
+
const stop = (signal) => {
|
|
38
|
+
if (stopping)
|
|
39
|
+
return;
|
|
40
|
+
stopping = true;
|
|
41
|
+
log('info', service, 'shutting down', { signal });
|
|
42
|
+
// The deadline runs whatever happens, and `unref` keeps it from being the
|
|
43
|
+
// thing that holds the process open once everything else has let go.
|
|
44
|
+
const deadline = setTimeout(() => {
|
|
45
|
+
log('error', service, 'shutdown did not finish in time; exiting anyway', { grace_ms: graceMs });
|
|
46
|
+
exit(1);
|
|
47
|
+
}, graceMs);
|
|
48
|
+
deadline.unref();
|
|
49
|
+
void (async () => {
|
|
50
|
+
try {
|
|
51
|
+
await shutdown?.();
|
|
52
|
+
clearTimeout(deadline);
|
|
53
|
+
exit(0);
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
log('error', service, 'shutdown failed', { error: String(error).slice(0, 500) });
|
|
57
|
+
clearTimeout(deadline);
|
|
58
|
+
exit(1);
|
|
59
|
+
}
|
|
60
|
+
})();
|
|
61
|
+
};
|
|
62
|
+
process.on('SIGTERM', () => stop('SIGTERM'));
|
|
63
|
+
process.on('SIGINT', () => stop('SIGINT'));
|
|
64
|
+
return { stop };
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Fail a `listen` the way every other startup failure fails.
|
|
68
|
+
*
|
|
69
|
+
* `server.listen(port, cb)` reports a bound port through the callback and a
|
|
70
|
+
* refusal through an `error` event — which arrives after `main()` has resolved,
|
|
71
|
+
* so nothing was catching it.
|
|
72
|
+
*/
|
|
73
|
+
export function onListenError(server, service, port, exit = (code) => process.exit(code)) {
|
|
74
|
+
server.on('error', (error) => {
|
|
75
|
+
const detail = error.code === 'EADDRINUSE'
|
|
76
|
+
? `port ${port} is already in use`
|
|
77
|
+
: error.code === 'EACCES'
|
|
78
|
+
? `port ${port} needs privileges this process does not have`
|
|
79
|
+
: String(error);
|
|
80
|
+
log('error', service, 'could not listen', { port, detail });
|
|
81
|
+
exit(2);
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=lifecycle.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lifecycle.js","sourceRoot":"","sources":["../lifecycle.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Logging, at the level and in the format the deployment asked for.
|
|
3
|
+
*
|
|
4
|
+
* `NACRE_LOG_LEVEL` and `NACRE_LOG_FORMAT` were validated at startup and read
|
|
5
|
+
* by nothing: every process wrote structured JSON at one level, so an operator
|
|
6
|
+
* setting `warn` still got every `info` line and one setting `text` still got
|
|
7
|
+
* JSON. Both are among the first variables anyone sets.
|
|
8
|
+
*
|
|
9
|
+
* ─── what does not go through here ───
|
|
10
|
+
*
|
|
11
|
+
* **Configuration errors.** They happen before a configuration exists, so there
|
|
12
|
+
* is nothing to ask about the level, and a process that cannot start must say
|
|
13
|
+
* why whatever anyone configured. Those stay on `console.error`.
|
|
14
|
+
*
|
|
15
|
+
* **`init` and `migrate` output.** Those are commands a person runs and reads —
|
|
16
|
+
* the printed org id, the applied migrations. That is program output, like
|
|
17
|
+
* `--help`, and putting it behind a level would mean `NACRE_LOG_LEVEL=warn`
|
|
18
|
+
* silently swallows the one line the command exists to produce.
|
|
19
|
+
*
|
|
20
|
+
* ─── what a field may hold ───
|
|
21
|
+
*
|
|
22
|
+
* The same rule as everywhere else in this repository: no document contents and
|
|
23
|
+
* no full query text. This module cannot enforce that — it takes what it is
|
|
24
|
+
* given — so it is stated where the fields are named rather than here.
|
|
25
|
+
*/
|
|
26
|
+
export type LogLevel = 'debug' | 'info' | 'warn' | 'error';
|
|
27
|
+
export type LogFormat = 'json' | 'text';
|
|
28
|
+
export interface Logger {
|
|
29
|
+
debug(message: string, fields?: Record<string, unknown>): void;
|
|
30
|
+
info(message: string, fields?: Record<string, unknown>): void;
|
|
31
|
+
warn(message: string, fields?: Record<string, unknown>): void;
|
|
32
|
+
error(message: string, fields?: Record<string, unknown>): void;
|
|
33
|
+
}
|
|
34
|
+
export interface LoggerOptions {
|
|
35
|
+
readonly level: LogLevel;
|
|
36
|
+
readonly format: LogFormat;
|
|
37
|
+
/** Injected so a test can assert on a line rather than on a moving timestamp. */
|
|
38
|
+
readonly now?: () => Date;
|
|
39
|
+
/** Injected for the same reason. Defaults to the console. */
|
|
40
|
+
readonly write?: (level: LogLevel, line: string) => void;
|
|
41
|
+
}
|
|
42
|
+
export declare function createLogger(options: LoggerOptions): Logger;
|
|
43
|
+
export declare function configureLogging(options: LoggerOptions): void;
|
|
44
|
+
/** Delegates on every call, so a module may hold this and still see the change. */
|
|
45
|
+
export declare const logger: Logger;
|
|
46
|
+
//# sourceMappingURL=logging.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logging.d.ts","sourceRoot":"","sources":["../logging.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAA;AAC1D,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,MAAM,CAAA;AAIvC,MAAM,WAAW,MAAM;IACrB,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAA;IAC9D,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAA;IAC7D,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAA;IAC7D,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAA;CAC/D;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAA;IACxB,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAA;IAC1B,iFAAiF;IACjF,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,IAAI,CAAA;IACzB,6DAA6D;IAC7D,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;CACzD;AA+BD,wBAAgB,YAAY,CAAC,OAAO,EAAE,aAAa,GAAG,MAAM,CA6B3D;AAiBD,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,aAAa,GAAG,IAAI,CAE7D;AAED,mFAAmF;AACnF,eAAO,MAAM,MAAM,EAAE,MAKpB,CAAA"}
|
package/dist/logging.js
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Logging, at the level and in the format the deployment asked for.
|
|
3
|
+
*
|
|
4
|
+
* `NACRE_LOG_LEVEL` and `NACRE_LOG_FORMAT` were validated at startup and read
|
|
5
|
+
* by nothing: every process wrote structured JSON at one level, so an operator
|
|
6
|
+
* setting `warn` still got every `info` line and one setting `text` still got
|
|
7
|
+
* JSON. Both are among the first variables anyone sets.
|
|
8
|
+
*
|
|
9
|
+
* ─── what does not go through here ───
|
|
10
|
+
*
|
|
11
|
+
* **Configuration errors.** They happen before a configuration exists, so there
|
|
12
|
+
* is nothing to ask about the level, and a process that cannot start must say
|
|
13
|
+
* why whatever anyone configured. Those stay on `console.error`.
|
|
14
|
+
*
|
|
15
|
+
* **`init` and `migrate` output.** Those are commands a person runs and reads —
|
|
16
|
+
* the printed org id, the applied migrations. That is program output, like
|
|
17
|
+
* `--help`, and putting it behind a level would mean `NACRE_LOG_LEVEL=warn`
|
|
18
|
+
* silently swallows the one line the command exists to produce.
|
|
19
|
+
*
|
|
20
|
+
* ─── what a field may hold ───
|
|
21
|
+
*
|
|
22
|
+
* The same rule as everywhere else in this repository: no document contents and
|
|
23
|
+
* no full query text. This module cannot enforce that — it takes what it is
|
|
24
|
+
* given — so it is stated where the fields are named rather than here.
|
|
25
|
+
*/
|
|
26
|
+
const ORDER = { debug: 10, info: 20, warn: 30, error: 40 };
|
|
27
|
+
/**
|
|
28
|
+
* `warn` and `error` to stderr, the rest to stdout.
|
|
29
|
+
*
|
|
30
|
+
* Not a style choice: a container that pipes stdout into a log shipper and
|
|
31
|
+
* leaves stderr on the terminal is a real deployment, and a failure that went
|
|
32
|
+
* to stdout with everything else is a failure nobody saw at 3am.
|
|
33
|
+
*/
|
|
34
|
+
function toConsole(level, line) {
|
|
35
|
+
if (level === 'warn')
|
|
36
|
+
console.warn(line);
|
|
37
|
+
else if (level === 'error')
|
|
38
|
+
console.error(line);
|
|
39
|
+
else
|
|
40
|
+
console.log(line);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* `key=value`, with anything ambiguous quoted.
|
|
44
|
+
*
|
|
45
|
+
* Text format is for a person reading a terminal, so it is optimised for
|
|
46
|
+
* scanning rather than for parsing — but a value containing a space or a quote
|
|
47
|
+
* still has to survive, or two fields read as one.
|
|
48
|
+
*/
|
|
49
|
+
function render(value) {
|
|
50
|
+
if (value === null)
|
|
51
|
+
return 'null';
|
|
52
|
+
if (typeof value === 'string') {
|
|
53
|
+
return /[\s"=]/.test(value) ? JSON.stringify(value) : value;
|
|
54
|
+
}
|
|
55
|
+
if (typeof value === 'number' || typeof value === 'boolean')
|
|
56
|
+
return String(value);
|
|
57
|
+
return JSON.stringify(value);
|
|
58
|
+
}
|
|
59
|
+
export function createLogger(options) {
|
|
60
|
+
const threshold = ORDER[options.level];
|
|
61
|
+
const now = options.now ?? (() => new Date());
|
|
62
|
+
const write = options.write ?? toConsole;
|
|
63
|
+
const emit = (level, message, fields) => {
|
|
64
|
+
if (ORDER[level] < threshold)
|
|
65
|
+
return;
|
|
66
|
+
const ts = now().toISOString();
|
|
67
|
+
if (options.format === 'json') {
|
|
68
|
+
// `msg` first and named the same as before, because every existing line
|
|
69
|
+
// was `{"msg": ...}` and anything already grepping for it should keep
|
|
70
|
+
// working. The level and timestamp are additions, not a replacement.
|
|
71
|
+
write(level, JSON.stringify({ level, ts, msg: message, ...fields }));
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
const rest = Object.entries(fields ?? {})
|
|
75
|
+
.map(([key, value]) => ` ${key}=${render(value)}`)
|
|
76
|
+
.join('');
|
|
77
|
+
write(level, `${ts} ${level.toUpperCase().padEnd(5)} ${message}${rest}`);
|
|
78
|
+
};
|
|
79
|
+
return {
|
|
80
|
+
debug: (m, f) => emit('debug', m, f),
|
|
81
|
+
info: (m, f) => emit('info', m, f),
|
|
82
|
+
warn: (m, f) => emit('warn', m, f),
|
|
83
|
+
error: (m, f) => emit('error', m, f),
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* The process-wide logger.
|
|
88
|
+
*
|
|
89
|
+
* A module-level default that `configureLogging` replaces once the
|
|
90
|
+
* configuration is read. Deliberately the one piece of global state in this
|
|
91
|
+
* repository, and it is safe to be: it carries no authorization semantics, the
|
|
92
|
+
* worst a wrong one does is print at the wrong level, and the alternative —
|
|
93
|
+
* threading a logger through every adapter and port — buys nothing a reader of
|
|
94
|
+
* those signatures would want.
|
|
95
|
+
*
|
|
96
|
+
* The default is `info`/`json`, so a module that logs before `configureLogging`
|
|
97
|
+
* runs behaves exactly as the whole system did before this existed.
|
|
98
|
+
*/
|
|
99
|
+
let current = createLogger({ level: 'info', format: 'json' });
|
|
100
|
+
export function configureLogging(options) {
|
|
101
|
+
current = createLogger(options);
|
|
102
|
+
}
|
|
103
|
+
/** Delegates on every call, so a module may hold this and still see the change. */
|
|
104
|
+
export const logger = {
|
|
105
|
+
debug: (m, f) => current.debug(m, f),
|
|
106
|
+
info: (m, f) => current.info(m, f),
|
|
107
|
+
warn: (m, f) => current.warn(m, f),
|
|
108
|
+
error: (m, f) => current.error(m, f),
|
|
109
|
+
};
|
|
110
|
+
//# sourceMappingURL=logging.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logging.js","sourceRoot":"","sources":["../logging.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAKH,MAAM,KAAK,GAA6B,EAAE,KAAK,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,CAAA;AAkBpF;;;;;;GAMG;AACH,SAAS,SAAS,CAAC,KAAe,EAAE,IAAY;IAC9C,IAAI,KAAK,KAAK,MAAM;QAAE,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;SACnC,IAAI,KAAK,KAAK,OAAO;QAAE,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;;QAC1C,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;AACxB,CAAC;AAED;;;;;;GAMG;AACH,SAAS,MAAM,CAAC,KAAc;IAC5B,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,MAAM,CAAA;IACjC,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,OAAO,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAA;IAC7D,CAAC;IACD,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,OAAO,KAAK,KAAK,SAAS;QAAE,OAAO,MAAM,CAAC,KAAK,CAAC,CAAA;IACjF,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAA;AAC9B,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,OAAsB;IACjD,MAAM,SAAS,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAA;IACtC,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,IAAI,EAAE,CAAC,CAAA;IAC7C,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,SAAS,CAAA;IAExC,MAAM,IAAI,GAAG,CAAC,KAAe,EAAE,OAAe,EAAE,MAAgC,EAAQ,EAAE;QACxF,IAAI,KAAK,CAAC,KAAK,CAAC,GAAG,SAAS;YAAE,OAAM;QAEpC,MAAM,EAAE,GAAG,GAAG,EAAE,CAAC,WAAW,EAAE,CAAA;QAC9B,IAAI,OAAO,CAAC,MAAM,KAAK,MAAM,EAAE,CAAC;YAC9B,wEAAwE;YACxE,sEAAsE;YACtE,qEAAqE;YACrE,KAAK,CAAC,KAAK,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,MAAM,EAAE,CAAC,CAAC,CAAA;YACpE,OAAM;QACR,CAAC;QAED,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC,MAAM,IAAI,EAAE,CAAC;aACtC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,IAAI,GAAG,IAAI,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;aACjD,IAAI,CAAC,EAAE,CAAC,CAAA;QACX,KAAK,CAAC,KAAK,EAAE,GAAG,EAAE,IAAI,KAAK,CAAC,WAAW,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,OAAO,GAAG,IAAI,EAAE,CAAC,CAAA;IAC1E,CAAC,CAAA;IAED,OAAO;QACL,KAAK,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC,CAAC;QACpC,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC;QAClC,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC;QAClC,KAAK,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC,CAAC;KACrC,CAAA;AACH,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,IAAI,OAAO,GAAW,YAAY,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAA;AAErE,MAAM,UAAU,gBAAgB,CAAC,OAAsB;IACrD,OAAO,GAAG,YAAY,CAAC,OAAO,CAAC,CAAA;AACjC,CAAC;AAED,mFAAmF;AACnF,MAAM,CAAC,MAAM,MAAM,GAAW;IAC5B,KAAK,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC;IACpC,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC;IAClC,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC;IAClC,KAAK,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC;CACrC,CAAA"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { MetadataValue } from './authz/filter.js';
|
|
2
|
+
/**
|
|
3
|
+
* Document metadata: what a caller may write, and what they may narrow a search
|
|
4
|
+
* by.
|
|
5
|
+
*
|
|
6
|
+
* One module for both ends on purpose. Ingest and search accept the same shape
|
|
7
|
+
* from the same untrusted place, and two validators would drift — the ingest
|
|
8
|
+
* side would grow a type the search side could not express, or the search side
|
|
9
|
+
* would accept a key ingest had refused, and a caller would be filtering on a
|
|
10
|
+
* field that can never exist.
|
|
11
|
+
*
|
|
12
|
+
* Everything here is a bound. Metadata is written into the vector payload of
|
|
13
|
+
* *every chunk of a document*, so a key is stored once per point and a
|
|
14
|
+
* thousand-chunk document multiplies whatever arrives here by a thousand. The
|
|
15
|
+
* limits are deliberately small; a knowledge index wants `source`, `team`,
|
|
16
|
+
* `updated_at`, not a document's worth of JSON a second time.
|
|
17
|
+
*/
|
|
18
|
+
/** Keys per document. */
|
|
19
|
+
export declare const MAX_METADATA_KEYS = 32;
|
|
20
|
+
export declare const MAX_METADATA_KEY_LENGTH = 64;
|
|
21
|
+
/** A string value, and every string inside a list value. */
|
|
22
|
+
export declare const MAX_METADATA_VALUE_LENGTH = 256;
|
|
23
|
+
/** Values in one list. */
|
|
24
|
+
export declare const MAX_METADATA_LIST = 32;
|
|
25
|
+
export type Metadata = Readonly<Record<string, MetadataValue>>;
|
|
26
|
+
export declare class MetadataError extends Error {
|
|
27
|
+
constructor(message: string);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Validate and normalise metadata arriving from a request.
|
|
31
|
+
*
|
|
32
|
+
* Raises rather than dropping what it does not like. A silently discarded key
|
|
33
|
+
* is a document a caller believes is tagged and a filter that will never match
|
|
34
|
+
* it — the same class of failure as a search parameter read by nothing, and the
|
|
35
|
+
* reason `filters` answered 400 rather than being ignored.
|
|
36
|
+
*/
|
|
37
|
+
export declare function parseMetadata(raw: unknown, what?: string): Metadata;
|
|
38
|
+
/**
|
|
39
|
+
* The same bounds, plus the one rule that belongs only to filtering.
|
|
40
|
+
*
|
|
41
|
+
* An empty list is a legitimate *value* — a document may be tagged with no
|
|
42
|
+
* teams — and an impossible *restriction*: "team is one of nothing" matches
|
|
43
|
+
* nothing at all. `buildFilter` refuses to encode it, deliberately, so that
|
|
44
|
+
* what an impossible restriction means stays the caller's decision rather than
|
|
45
|
+
* the query builder's. This is where that decision is made for a request: a 400
|
|
46
|
+
* naming the key, because a client that built a list and put nothing in it has
|
|
47
|
+
* a bug, and answering with an empty result would hide it.
|
|
48
|
+
*/
|
|
49
|
+
export declare function parseFilters(raw: unknown): Metadata;
|
|
50
|
+
//# sourceMappingURL=metadata.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"metadata.d.ts","sourceRoot":"","sources":["../metadata.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAA;AAEtD;;;;;;;;;;;;;;;GAeG;AAEH,yBAAyB;AACzB,eAAO,MAAM,iBAAiB,KAAK,CAAA;AACnC,eAAO,MAAM,uBAAuB,KAAK,CAAA;AACzC,4DAA4D;AAC5D,eAAO,MAAM,yBAAyB,MAAM,CAAA;AAC5C,0BAA0B;AAC1B,eAAO,MAAM,iBAAiB,KAAK,CAAA;AAanC,MAAM,MAAM,QAAQ,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC,CAAA;AAE9D,qBAAa,aAAc,SAAQ,KAAK;gBAC1B,OAAO,EAAE,MAAM;CAI5B;AA6BD;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,SAAa,GAAG,QAAQ,CAsCvE;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,OAAO,GAAG,QAAQ,CAWnD"}
|