@hasna/contacts 0.7.0 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +83 -13
- package/dist/cli/commands/core.d.ts.map +1 -1
- package/dist/cli/index.js +82 -33
- package/dist/cloud/http-storage.d.ts +20 -12
- package/dist/cloud/http-storage.d.ts.map +1 -1
- package/dist/cloud/resolver-inputs.d.ts +51 -0
- package/dist/cloud/resolver-inputs.d.ts.map +1 -0
- package/dist/db/paths.d.ts +4 -4
- package/dist/db/paths.d.ts.map +1 -1
- package/dist/generated/storage-kit/backend.d.ts +19 -0
- package/dist/generated/storage-kit/backend.d.ts.map +1 -0
- package/dist/generated/storage-kit/index.d.ts +2 -1
- package/dist/generated/storage-kit/index.d.ts.map +1 -1
- package/dist/generated/storage-kit/migrations.d.ts +21 -0
- package/dist/generated/storage-kit/migrations.d.ts.map +1 -1
- package/dist/generated/storage-kit/own.d.ts +11 -0
- package/dist/generated/storage-kit/own.d.ts.map +1 -0
- package/dist/generated/storage-kit/pool.d.ts +5 -17
- package/dist/generated/storage-kit/pool.d.ts.map +1 -1
- package/dist/generated/storage-kit/query.d.ts +1 -1
- package/dist/generated/storage-kit/query.d.ts.map +1 -1
- package/dist/generated/storage-kit/tls.d.ts +30 -3
- package/dist/generated/storage-kit/tls.d.ts.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +105 -24
- package/dist/mcp/index.d.ts +9 -0
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +112 -18
- package/dist/mcp/startup-gate.d.ts +48 -0
- package/dist/mcp/startup-gate.d.ts.map +1 -0
- package/dist/sdk/index.d.ts +70 -3
- package/dist/sdk/index.d.ts.map +1 -1
- package/dist/sdk/index.js +990 -7
- package/dist/server/index.js +201 -43
- package/hasna.contract.json +20 -6
- package/package.json +3 -3
- package/dist/lib/config.d.ts +0 -7
- package/dist/lib/config.d.ts.map +0 -1
package/README.md
CHANGED
|
@@ -14,33 +14,87 @@ npm install -g @hasna/contacts
|
|
|
14
14
|
## Configure the client
|
|
15
15
|
|
|
16
16
|
Every CLI, MCP, and package data operation uses one authenticated HTTPS `/v1`
|
|
17
|
-
authority
|
|
17
|
+
authority resolved by the shared `@hasna/contracts` client chain
|
|
18
|
+
(hasna/apps#1720), resolved fresh on every request. There is no local database
|
|
19
|
+
fallback and no per-app env chain of its own.
|
|
20
|
+
|
|
21
|
+
Once a contacts API key resolves from any tier, the authority defaults to the
|
|
22
|
+
fleet gateway `https://api.hasna.com/contacts` — no URL configuration is
|
|
23
|
+
needed on a station. The credential tiers, in order:
|
|
24
|
+
|
|
25
|
+
1. explicit arguments / deliberate pointers — `HASNA_CONTACTS_API_KEY_OVERRIDE`, `HASNA_PROFILE`, `HASNA_CONTACTS_API_KEY_REF` (secrets vault)
|
|
26
|
+
2. macOS Keychain — `hasna.credentials.contacts.api-key` / `.api-url`, account `HASNA_STATION` → `hostname -s` → `$USER`
|
|
27
|
+
3. disk — `~/.hasna/contacts/config/credentials` (owner-only 0400/0600, `HASNA_CONTACTS_API_KEY=…` or the `HASNA_CONTACTS_API_URL=…` alias)
|
|
28
|
+
4. environment — `HASNA_CONTACTS_API_KEY`
|
|
29
|
+
|
|
30
|
+
| Env var | Meaning |
|
|
31
|
+
|---|---|
|
|
32
|
+
| `HASNA_CONTACTS_API_URL` | Explicit API base URL (HTTPS; overrides the fleet gateway). Legacy alias: `CONTACTS_API_URL`. |
|
|
33
|
+
| `HASNA_CONTACTS_API_KEY` | API key (env tier). Legacy alias: `CONTACTS_API_KEY`. |
|
|
34
|
+
| `HASNA_CONTACTS_API_KEY_OVERRIDE` | Deliberate per-run override that outranks the Keychain and disk. |
|
|
35
|
+
| `HASNA_CONTACTS_API_KEY_REF` | Secrets-vault pointer (`namespace/app/live/api_key`); terminal when unresolvable. |
|
|
36
|
+
| `HASNA_PROFILE` | Selects which identity (`credentials-<profile>`) the disk tier reads. |
|
|
37
|
+
| `HASNA_STATION` | Keychain account when set; else short hostname, then `$USER`. |
|
|
38
|
+
| `HASNA_HOME` | Replaces `~` for the `~/.hasna/…` credential/disk root. |
|
|
39
|
+
| `HASNA_CONFIG_HOME` | Replaces `~/.hasna/<app>/config` entirely. |
|
|
18
40
|
|
|
19
41
|
```bash
|
|
42
|
+
# Fully explicit:
|
|
20
43
|
export HASNA_CONTACTS_API_URL="https://contacts.example.com"
|
|
21
|
-
#
|
|
22
|
-
# example, put HASNA_CONTACTS_API_KEY in the shared fleet/config credential chain,
|
|
23
|
-
# or configure HASNA_CONTACTS_API_KEY_REF for the secrets client.
|
|
44
|
+
export HASNA_CONTACTS_API_KEY="…" # or configure the Keychain/disk tiers
|
|
24
45
|
contacts connection --json
|
|
25
46
|
```
|
|
26
47
|
|
|
27
|
-
An absent
|
|
28
|
-
`
|
|
29
|
-
|
|
30
|
-
|
|
48
|
+
An absent URL and key fails closed: operations exit non-zero and never open a
|
|
49
|
+
local store; `contacts connection` reports `transport: "unconfigured"`.
|
|
50
|
+
The fail-closed message starts on the FIRST stderr line — it names what is
|
|
51
|
+
missing and where the credential should live (`HASNA_CONTACTS_API_KEY`, the
|
|
52
|
+
Keychain item, the credentials-file path), never a value.
|
|
53
|
+
`HASNA_CONTACTS_STORAGE_MODE`, `CONTACTS_STORAGE_MODE`, contacts DB-path
|
|
54
|
+
variables, and contacts database URLs are rejected in client processes.
|
|
55
|
+
PostgreSQL URLs belong only to `contacts-serve` and the migration task.
|
|
31
56
|
|
|
32
57
|
## CLI Usage
|
|
33
58
|
|
|
34
59
|
```bash
|
|
35
|
-
contacts status # CLI version,
|
|
60
|
+
contacts status # CLI version, resolved /v1 authority + sources, storage mode, record counts
|
|
36
61
|
contacts status --json
|
|
37
62
|
contacts --help
|
|
38
63
|
```
|
|
39
64
|
|
|
40
|
-
`contacts status`
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
65
|
+
`contacts status` reports the authority the shared resolver actually decided
|
|
66
|
+
(`api`, the `/v1` base URL) and where each half came from — `api_url_source`,
|
|
67
|
+
`api_key_source`, `api_key_tier`: an env key name, a Keychain item reference,
|
|
68
|
+
a credentials-file path, or `default` for the fleet gateway; never a value.
|
|
69
|
+
It answers even on a box without an API key: an unconfigured client reports
|
|
70
|
+
storage `unconfigured` with the resolver's `issue` (a failed request on a
|
|
71
|
+
configured box reports storage `error` with the failure message) instead of
|
|
72
|
+
crashing, so agents can observe the configuration drift the command exists to
|
|
73
|
+
expose. `status` and `connection` are diagnostics and exit 0 with that report;
|
|
74
|
+
every data verb fails closed (non-zero exit, no local store).
|
|
75
|
+
|
|
76
|
+
## SDK
|
|
77
|
+
|
|
78
|
+
```ts
|
|
79
|
+
import { createContactsClient, ContactsV1Client } from "@hasna/contacts/sdk";
|
|
80
|
+
|
|
81
|
+
// Through the fleet resolver — the same @hasna/contracts chain the CLI and MCP
|
|
82
|
+
// server use: credential and authority resolved at construction, the key
|
|
83
|
+
// re-resolved on every request, the authority pinned. Nothing resolving throws.
|
|
84
|
+
const client = createContactsClient();
|
|
85
|
+
const { contacts } = await client.listContacts();
|
|
86
|
+
|
|
87
|
+
// Explicit pin: a caller-supplied baseUrl always requires a caller-supplied
|
|
88
|
+
// apiKey — the SDK never attaches an ambient credential to it.
|
|
89
|
+
const pinned = new ContactsV1Client({ baseUrl: "https://contacts.example.com", apiKey: "…" });
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
`createContactsClient()` resolves the literal tiers only — an explicit
|
|
93
|
+
`apiKey`, `HASNA_CONTACTS_API_KEY_OVERRIDE`, the Keychain item, the
|
|
94
|
+
credentials file, `HASNA_CONTACTS_API_KEY`. The secrets-vault pointer
|
|
95
|
+
`HASNA_CONTACTS_API_KEY_REF` is refused with
|
|
96
|
+
`CONTACTS_CREDENTIAL_POINTER_UNSUPPORTED` (the SDK resolves synchronously per
|
|
97
|
+
request and cannot complete a vault lookup); the CLI and MCP server accept it.
|
|
44
98
|
|
|
45
99
|
|
|
46
100
|
## Audiences, consent, and suppression
|
|
@@ -76,6 +130,22 @@ suppressed addresses; the audience `--policy` (`opt_in`, `opt_out`,
|
|
|
76
130
|
contacts-mcp
|
|
77
131
|
```
|
|
78
132
|
|
|
133
|
+
`contacts-mcp` fails closed at startup: with no credential resolvable through
|
|
134
|
+
the chain above it exits non-zero BEFORE the stdio transport is connected or
|
|
135
|
+
the HTTP port is bound — an `initialize` request is never answered by an
|
|
136
|
+
unauthenticated server — and creates nothing under the app home. The first
|
|
137
|
+
stderr line names where the credential should live (the Keychain item, the
|
|
138
|
+
credentials-file path, `HASNA_CONTACTS_API_KEY`), never a value.
|
|
139
|
+
`--help` / `--version` answer ahead of the gate; every tool still re-resolves
|
|
140
|
+
the credential per request once the server is up.
|
|
141
|
+
|
|
142
|
+
The deliberate tiers are honoured at the gate as well. The secrets-vault
|
|
143
|
+
pointer `HASNA_CONTACTS_API_KEY_REF` is dereferenced once at startup (the
|
|
144
|
+
chain alone only checks the pointer's shape): a pointer the vault cannot
|
|
145
|
+
complete, a `HASNA_PROFILE` whose credentials file is missing, or an unsafe
|
|
146
|
+
(not owner-only) credentials file is a one-line refusal naming the pointer,
|
|
147
|
+
the file, or the item — never resolved around, never a stack trace.
|
|
148
|
+
|
|
79
149
|
## HTTP mode
|
|
80
150
|
|
|
81
151
|
Long-lived Streamable HTTP transport (stateless, bind `127.0.0.1` only):
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"core.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/core.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAqBzC,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,
|
|
1
|
+
{"version":3,"file":"core.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/core.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAqBzC,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAu5B5E"}
|
package/dist/cli/index.js
CHANGED
|
@@ -3621,6 +3621,13 @@ function parseMatcherExpression(value, label) {
|
|
|
3621
3621
|
negated: false
|
|
3622
3622
|
};
|
|
3623
3623
|
}
|
|
3624
|
+
function webhookTargetPolicyFromEnv() {
|
|
3625
|
+
const value = process.env.HASNA_EVENTS_ALLOW_PRIVATE_WEBHOOK_TARGETS;
|
|
3626
|
+
if (!value)
|
|
3627
|
+
return;
|
|
3628
|
+
const hosts = value.split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0);
|
|
3629
|
+
return hosts.length > 0 ? { allowPrivateHosts: hosts } : undefined;
|
|
3630
|
+
}
|
|
3624
3631
|
var DEFAULT_EVENT_LIST_LIMIT = 100;
|
|
3625
3632
|
function parseJsonObject(value, fallback) {
|
|
3626
3633
|
if (!value)
|
|
@@ -3646,7 +3653,7 @@ function parseHeaders(values) {
|
|
|
3646
3653
|
function createClient(options) {
|
|
3647
3654
|
if (options.createClient)
|
|
3648
3655
|
return options.createClient();
|
|
3649
|
-
return new EventsClient({ store: new JsonEventsStore(options.dataDir) });
|
|
3656
|
+
return new EventsClient({ store: new JsonEventsStore(options.dataDir), webhookTargetPolicy: webhookTargetPolicyFromEnv() });
|
|
3650
3657
|
}
|
|
3651
3658
|
function print(value, json, text) {
|
|
3652
3659
|
if (json)
|
|
@@ -3727,6 +3734,8 @@ function registerChannelCommands(program, options) {
|
|
|
3727
3734
|
metadata: parseJsonObject(actionOptions.metadata, {})
|
|
3728
3735
|
}, { honorFilters: actionOptions.honorFilters });
|
|
3729
3736
|
print(result, json, `${result.status}: ${result.channelId}`);
|
|
3737
|
+
if (result.status === "failed")
|
|
3738
|
+
process.exitCode = 1;
|
|
3730
3739
|
} catch (error) {
|
|
3731
3740
|
fail(error, json);
|
|
3732
3741
|
}
|
|
@@ -3840,6 +3849,7 @@ import chalk2 from "chalk";
|
|
|
3840
3849
|
|
|
3841
3850
|
// src/cloud/http-storage.ts
|
|
3842
3851
|
import {
|
|
3852
|
+
ClientTransportConfigurationError,
|
|
3843
3853
|
createHasnaHttpTransport,
|
|
3844
3854
|
resolveCredential,
|
|
3845
3855
|
resolveClientTransport as resolveSharedClientTransport
|
|
@@ -3929,6 +3939,27 @@ function assertConfigurationUnchanged(env, expected) {
|
|
|
3929
3939
|
invalid();
|
|
3930
3940
|
}
|
|
3931
3941
|
|
|
3942
|
+
// src/cloud/resolver-inputs.ts
|
|
3943
|
+
import {
|
|
3944
|
+
clientTransportEnvKeys as clientTransportEnvKeys2,
|
|
3945
|
+
credentialOverrideEnvKey,
|
|
3946
|
+
credentialPointerEnvKey,
|
|
3947
|
+
CREDENTIAL_PROFILE_ENV_KEY
|
|
3948
|
+
} from "@hasna/contracts/client";
|
|
3949
|
+
var CONTRACTS_AMBIENT_ENVIRONMENT = Symbol.for("hasna:contracts:ambientClientEnvironment");
|
|
3950
|
+
function isAmbientContactsEnv(env) {
|
|
3951
|
+
if (typeof process !== "undefined" && env === process.env)
|
|
3952
|
+
return true;
|
|
3953
|
+
return env[CONTRACTS_AMBIENT_ENVIRONMENT] === true;
|
|
3954
|
+
}
|
|
3955
|
+
function contactsResolverCredentials(env, credentials = {}) {
|
|
3956
|
+
const keychain = { ...credentials.keychain };
|
|
3957
|
+
if (keychain.enabled === undefined && keychain.run === undefined) {
|
|
3958
|
+
keychain.enabled = isAmbientContactsEnv(env);
|
|
3959
|
+
}
|
|
3960
|
+
return { ...credentials, keychain };
|
|
3961
|
+
}
|
|
3962
|
+
|
|
3932
3963
|
// src/cloud/http-storage.ts
|
|
3933
3964
|
var RETIRED_CLIENT_SELECTOR_KEYS = [
|
|
3934
3965
|
"HASNA_CONTACTS_STORAGE_MODE",
|
|
@@ -3964,37 +3995,44 @@ class ContactsClientConfigurationError extends Error {
|
|
|
3964
3995
|
this.name = "ContactsClientConfigurationError";
|
|
3965
3996
|
}
|
|
3966
3997
|
}
|
|
3967
|
-
function unconfiguredResolution(resolution,
|
|
3998
|
+
function unconfiguredResolution(issue, resolution, warning = null) {
|
|
3968
3999
|
return {
|
|
3969
4000
|
transport: "unconfigured",
|
|
3970
4001
|
baseUrl: null,
|
|
3971
|
-
apiUrlSource: resolution
|
|
3972
|
-
apiKeyPresent: resolution
|
|
3973
|
-
apiKeySource: resolution
|
|
3974
|
-
apiKeyTier: resolution
|
|
4002
|
+
apiUrlSource: resolution?.apiUrlSource ?? null,
|
|
4003
|
+
apiKeyPresent: resolution?.apiKeyPresent ?? false,
|
|
4004
|
+
apiKeySource: resolution?.apiKeySource ?? null,
|
|
4005
|
+
apiKeyTier: resolution?.apiKeyTier ?? null,
|
|
3975
4006
|
configured: false,
|
|
3976
4007
|
misconfigured: true,
|
|
3977
4008
|
issue,
|
|
3978
|
-
warning
|
|
4009
|
+
warning
|
|
3979
4010
|
};
|
|
3980
4011
|
}
|
|
3981
|
-
function resolveContactsClientTransport(name, env = process.env) {
|
|
4012
|
+
function resolveContactsClientTransport(name, env = process.env, credentials = {}) {
|
|
3982
4013
|
if (name !== "contacts") {
|
|
3983
4014
|
throw new ContactsClientConfigurationError("CONTACTS_CLIENT_NAME_INVALID", "This resolver only accepts the contacts app slug.");
|
|
3984
4015
|
}
|
|
3985
4016
|
assertNoRetiredClientSelectors(env);
|
|
3986
4017
|
const stamp = clientConfigurationStamp(env);
|
|
3987
|
-
|
|
3988
|
-
|
|
3989
|
-
|
|
3990
|
-
|
|
3991
|
-
|
|
4018
|
+
let resolution;
|
|
4019
|
+
try {
|
|
4020
|
+
resolution = resolveSharedClientTransport(name, env, {
|
|
4021
|
+
credentials: contactsResolverCredentials(env, credentials)
|
|
4022
|
+
});
|
|
4023
|
+
} catch (error) {
|
|
4024
|
+
assertConfigurationUnchanged(env, stamp);
|
|
4025
|
+
if (error instanceof ClientTransportConfigurationError) {
|
|
4026
|
+
return unconfiguredResolution(error.message, null);
|
|
4027
|
+
}
|
|
4028
|
+
throw error;
|
|
3992
4029
|
}
|
|
4030
|
+
assertConfigurationUnchanged(env, stamp);
|
|
3993
4031
|
try {
|
|
3994
4032
|
assertHttpsBaseUrl(resolution.baseUrl);
|
|
3995
4033
|
} catch (error) {
|
|
3996
4034
|
return {
|
|
3997
|
-
...unconfiguredResolution(
|
|
4035
|
+
...unconfiguredResolution(error instanceof Error ? error.message : String(error), resolution),
|
|
3998
4036
|
apiKeyPresent: resolution.apiKeyPresent
|
|
3999
4037
|
};
|
|
4000
4038
|
}
|
|
@@ -4008,7 +4046,7 @@ function resolveContactsClientTransport(name, env = process.env) {
|
|
|
4008
4046
|
configured: true,
|
|
4009
4047
|
misconfigured: false,
|
|
4010
4048
|
issue: null,
|
|
4011
|
-
warning:
|
|
4049
|
+
warning: resolution.warning
|
|
4012
4050
|
};
|
|
4013
4051
|
}
|
|
4014
4052
|
function resourcePath(resource) {
|
|
@@ -4062,20 +4100,21 @@ function createStorageClient(name, transport) {
|
|
|
4062
4100
|
}
|
|
4063
4101
|
};
|
|
4064
4102
|
}
|
|
4065
|
-
function resolveContactsStorageClient(name, env = process.env) {
|
|
4066
|
-
const resolution = resolveContactsClientTransport(name, env);
|
|
4103
|
+
function resolveContactsStorageClient(name, env = process.env, credentials = {}) {
|
|
4104
|
+
const resolution = resolveContactsClientTransport(name, env, credentials);
|
|
4067
4105
|
if (!resolution.configured || !resolution.baseUrl) {
|
|
4068
4106
|
throw new ContactsClientConfigurationError("CONTACTS_API_NOT_CONFIGURED", `${resolution.issue ?? "The contacts API client is not configured."} ` + "Configure HASNA_CONTACTS_API_URL and a contacts API key; the client will not read or create a local SQLite database.");
|
|
4069
4107
|
}
|
|
4070
4108
|
const baseUrl = resolution.baseUrl;
|
|
4109
|
+
const chainOptions = contactsResolverCredentials(env, credentials);
|
|
4071
4110
|
const request = async (method, path, body, opts) => {
|
|
4072
4111
|
const stamp = clientConfigurationStamp(env);
|
|
4073
4112
|
const snapshot = { ...env };
|
|
4074
|
-
const current = resolveContactsClientTransport(name, snapshot);
|
|
4113
|
+
const current = resolveContactsClientTransport(name, snapshot, chainOptions);
|
|
4075
4114
|
if (!current.configured || current.baseUrl !== baseUrl) {
|
|
4076
4115
|
throw new ContactsClientConfigurationError("CONTACTS_AUTHORITY_CHANGED", "Client authority changed or disappeared; construct a new client before sending data.");
|
|
4077
4116
|
}
|
|
4078
|
-
const credential = resolveCredential(name, snapshot);
|
|
4117
|
+
const credential = resolveCredential(name, snapshot, chainOptions);
|
|
4079
4118
|
if (!credential)
|
|
4080
4119
|
throw new ContactsClientConfigurationError("CONTACTS_API_NOT_CONFIGURED", "No credential is available.");
|
|
4081
4120
|
assertConfigurationUnchanged(env, stamp);
|
|
@@ -5375,12 +5414,22 @@ function collect(val, prev) {
|
|
|
5375
5414
|
}
|
|
5376
5415
|
function registerCoreCommands(program2, version) {
|
|
5377
5416
|
program2.command("status").description("Show CLI version, API endpoint, storage mode, and record counts").option("--json", "Output as JSON").action(async (opts) => {
|
|
5378
|
-
|
|
5379
|
-
|
|
5417
|
+
let apiLine = "(not configured \u2014 set HASNA_CONTACTS_API_URL plus a contacts API key)";
|
|
5418
|
+
let apiUrlSource = null;
|
|
5419
|
+
let apiKeySource = null;
|
|
5420
|
+
let apiKeyTier = null;
|
|
5421
|
+
let issue = null;
|
|
5380
5422
|
let counts;
|
|
5381
5423
|
let storage;
|
|
5382
5424
|
let failure;
|
|
5383
5425
|
try {
|
|
5426
|
+
const resolution = resolveContactsClientTransport("contacts");
|
|
5427
|
+
apiUrlSource = resolution.apiUrlSource;
|
|
5428
|
+
apiKeySource = resolution.apiKeySource;
|
|
5429
|
+
apiKeyTier = resolution.apiKeyTier;
|
|
5430
|
+
issue = resolution.issue;
|
|
5431
|
+
if (resolution.configured && resolution.baseUrl)
|
|
5432
|
+
apiLine = resolution.baseUrl;
|
|
5384
5433
|
const store = getStore();
|
|
5385
5434
|
const [contacts, companies] = await Promise.all([
|
|
5386
5435
|
store.listContacts({ limit: 1 }),
|
|
@@ -5400,7 +5449,11 @@ function registerCoreCommands(program2, version) {
|
|
|
5400
5449
|
service: "contacts",
|
|
5401
5450
|
version,
|
|
5402
5451
|
api: apiLine,
|
|
5452
|
+
api_url_source: apiUrlSource,
|
|
5453
|
+
api_key_source: apiKeySource,
|
|
5454
|
+
api_key_tier: apiKeyTier,
|
|
5403
5455
|
storage,
|
|
5456
|
+
...issue !== null ? { issue } : {},
|
|
5404
5457
|
...failure !== undefined ? { error: failure } : {},
|
|
5405
5458
|
...counts ? { counts } : {}
|
|
5406
5459
|
};
|
|
@@ -5410,7 +5463,13 @@ function registerCoreCommands(program2, version) {
|
|
|
5410
5463
|
}
|
|
5411
5464
|
console.log(chalk2.bold(`contacts v${version}`));
|
|
5412
5465
|
console.log(`API: ${apiLine}`);
|
|
5466
|
+
if (apiUrlSource)
|
|
5467
|
+
console.log(`API URL source: ${apiUrlSource}`);
|
|
5468
|
+
if (apiKeySource)
|
|
5469
|
+
console.log(`API key source: ${apiKeySource}${apiKeyTier ? ` (${apiKeyTier})` : ""}`);
|
|
5413
5470
|
console.log(`Storage: ${storage}`);
|
|
5471
|
+
if (issue !== null)
|
|
5472
|
+
console.log(chalk2.yellow(`Issue: ${issue}`));
|
|
5414
5473
|
if (failure !== undefined)
|
|
5415
5474
|
console.log(chalk2.red(`Error: ${failure}`));
|
|
5416
5475
|
if (counts) {
|
|
@@ -12177,10 +12236,8 @@ import chalk7 from "chalk";
|
|
|
12177
12236
|
import { homedir as pathsResolverHomedir2 } from "os";
|
|
12178
12237
|
import { join as pathsResolverJoin2 } from "path";
|
|
12179
12238
|
var PATHS_RESOLVER_KIND_ENV2 = {
|
|
12180
|
-
config: "HASNA_CONFIG_HOME",
|
|
12181
12239
|
data: "HASNA_DATA_HOME",
|
|
12182
|
-
state: "HASNA_STATE_HOME"
|
|
12183
|
-
cache: "HASNA_CACHE_HOME"
|
|
12240
|
+
state: "HASNA_STATE_HOME"
|
|
12184
12241
|
};
|
|
12185
12242
|
var PATHS_RESOLVER_APP_SLUG_RE2 = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
12186
12243
|
function pathsResolverAssertApp2(app) {
|
|
@@ -12206,24 +12263,17 @@ function pathsResolverBaseDir2(kind, options) {
|
|
|
12206
12263
|
const platform = options.platform ?? process.platform;
|
|
12207
12264
|
if (platform === "darwin") {
|
|
12208
12265
|
switch (kind) {
|
|
12209
|
-
case "config":
|
|
12210
12266
|
case "data":
|
|
12211
12267
|
return pathsResolverJoin2(home, "Library", "Application Support", "Hasna");
|
|
12212
|
-
case "cache":
|
|
12213
|
-
return pathsResolverJoin2(home, "Library", "Caches", "Hasna");
|
|
12214
12268
|
case "state":
|
|
12215
12269
|
return pathsResolverJoin2(home, "Library", "Logs", "Hasna");
|
|
12216
12270
|
}
|
|
12217
12271
|
}
|
|
12218
12272
|
switch (kind) {
|
|
12219
|
-
case "config":
|
|
12220
|
-
return pathsResolverJoin2(home, ".config", "hasna");
|
|
12221
12273
|
case "data":
|
|
12222
12274
|
return pathsResolverJoin2(home, ".local", "share", "hasna");
|
|
12223
12275
|
case "state":
|
|
12224
12276
|
return pathsResolverJoin2(home, ".local", "state", "hasna");
|
|
12225
|
-
case "cache":
|
|
12226
|
-
return pathsResolverJoin2(home, ".cache", "hasna");
|
|
12227
12277
|
}
|
|
12228
12278
|
}
|
|
12229
12279
|
function pathsResolverResolve2(kind, options) {
|
|
@@ -12409,8 +12459,7 @@ registerEventCommands2(program, { source: "contacts", eventsCommandName: "hasna-
|
|
|
12409
12459
|
program.parseAsync(process.argv).catch((err) => {
|
|
12410
12460
|
const message = err instanceof Error ? err.message : String(err);
|
|
12411
12461
|
const isApiUnavailable = err instanceof Error && err.name === "ApiUnavailableError";
|
|
12412
|
-
console.error(`
|
|
12413
|
-
` + (isApiUnavailable ? chalk8.yellow(message) : chalk8.red(message)) + `
|
|
12462
|
+
console.error((isApiUnavailable ? chalk8.yellow(message) : chalk8.red(message)) + `
|
|
12414
12463
|
`);
|
|
12415
12464
|
process.exit(1);
|
|
12416
12465
|
});
|
|
@@ -2,18 +2,20 @@
|
|
|
2
2
|
* Canonical contacts client transport.
|
|
3
3
|
*
|
|
4
4
|
* Public clients have exactly one data path: an authenticated HTTPS `/v1`
|
|
5
|
-
* authority
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
5
|
+
* authority, resolved by `@hasna/contracts/client` — the ONE fleet resolver.
|
|
6
|
+
* The authority defaults to the fleet gateway `https://api.hasna.com/contacts`
|
|
7
|
+
* once any credential tier resolves, and `HASNA_CONTACTS_API_URL`, the
|
|
8
|
+
* Keychain `api-url` item, or the credentials file override it. API-key
|
|
9
|
+
* resolution and per-request rotation are owned by the shared resolver, so
|
|
10
|
+
* key material is never exposed by status objects or cached in this package.
|
|
9
11
|
*
|
|
10
12
|
* SQLite, PostgreSQL DSNs, and storage/deployment modes are not client
|
|
11
13
|
* transports. A stale selector is a configuration error, not a reason to read
|
|
12
14
|
* a different data set.
|
|
13
15
|
*/
|
|
14
|
-
import { type
|
|
15
|
-
|
|
16
|
-
export type { QueryParams };
|
|
16
|
+
import { type CredentialChainOptions, type CredentialTier, type HasnaHttpTransport, type HasnaRequestOptions, type QueryParams } from "@hasna/contracts/client";
|
|
17
|
+
import type { Env } from "./resolver-inputs.js";
|
|
18
|
+
export type { Env, QueryParams };
|
|
17
19
|
export declare const RETIRED_CLIENT_SELECTOR_KEYS: readonly ["HASNA_CONTACTS_STORAGE_MODE", "CONTACTS_STORAGE_MODE", "HASNA_CONTACTS_MODE", "CONTACTS_MODE", "HASNA_CONTACTS_DB_PATH", "CONTACTS_DB_PATH", "HASNA_CONTACTS_DATABASE_URL", "CONTACTS_DATABASE_URL"];
|
|
18
20
|
export declare class ContactsClientConfigurationError extends Error {
|
|
19
21
|
readonly code: string;
|
|
@@ -25,17 +27,23 @@ export interface ClientTransportResolution {
|
|
|
25
27
|
apiUrlSource: string | null;
|
|
26
28
|
apiKeyPresent: boolean;
|
|
27
29
|
apiKeySource: string | null;
|
|
28
|
-
apiKeyTier:
|
|
30
|
+
apiKeyTier: CredentialTier | null;
|
|
29
31
|
configured: boolean;
|
|
30
32
|
misconfigured: boolean;
|
|
31
33
|
issue: string | null;
|
|
32
34
|
warning: string | null;
|
|
33
35
|
}
|
|
34
36
|
/**
|
|
35
|
-
* Resolve value-free connection diagnostics.
|
|
36
|
-
*
|
|
37
|
+
* Resolve value-free connection diagnostics.
|
|
38
|
+
*
|
|
39
|
+
* @hasna/contracts 1.0.2 THROWS `ClientTransportConfigurationError` for every
|
|
40
|
+
* incomplete or invalid configuration — nothing resolves to a local or
|
|
41
|
+
* partial transport any more. A refusal of that class is reported here as
|
|
42
|
+
* `unconfigured` (never as a usable transport); the shared resolver's own
|
|
43
|
+
* hard refusals — a blank declared variable, a conflict, an unreadable
|
|
44
|
+
* credential file — stay hard errors, exactly as the resolver throws them.
|
|
37
45
|
*/
|
|
38
|
-
export declare function resolveContactsClientTransport(name: string, env?: Env): ClientTransportResolution;
|
|
46
|
+
export declare function resolveContactsClientTransport(name: string, env?: Env, credentials?: CredentialChainOptions): ClientTransportResolution;
|
|
39
47
|
export interface StorageClient {
|
|
40
48
|
readonly name: string;
|
|
41
49
|
readonly baseUrl: string;
|
|
@@ -55,5 +63,5 @@ export interface ResolveStorageClientResult {
|
|
|
55
63
|
resolution: ClientTransportResolution;
|
|
56
64
|
}
|
|
57
65
|
/** Build the sole contacts client. Any incomplete configuration is terminal. */
|
|
58
|
-
export declare function resolveContactsStorageClient(name: string, env?: Env): ResolveStorageClientResult;
|
|
66
|
+
export declare function resolveContactsStorageClient(name: string, env?: Env, credentials?: CredentialChainOptions): ResolveStorageClientResult;
|
|
59
67
|
//# sourceMappingURL=http-storage.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http-storage.d.ts","sourceRoot":"","sources":["../../src/cloud/http-storage.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"http-storage.d.ts","sourceRoot":"","sources":["../../src/cloud/http-storage.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,EAML,KAAK,sBAAsB,EAC3B,KAAK,cAAc,EACnB,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,EACxB,KAAK,WAAW,EACjB,MAAM,yBAAyB,CAAC;AAGjC,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,sBAAsB,CAAC;AAEhD,YAAY,EAAE,GAAG,EAAE,WAAW,EAAE,CAAC;AAEjC,eAAO,MAAM,4BAA4B,iNAS/B,CAAC;AA2BX,qBAAa,gCAAiC,SAAQ,KAAK;IAC7C,QAAQ,CAAC,IAAI,EAAE,MAAM;gBAAZ,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;CAInD;AAED,MAAM,WAAW,yBAAyB;IACxC,SAAS,EAAE,OAAO,GAAG,cAAc,CAAC;IACpC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,aAAa,EAAE,OAAO,CAAC;IACvB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,UAAU,EAAE,cAAc,GAAG,IAAI,CAAC;IAClC,UAAU,EAAE,OAAO,CAAC;IACpB,aAAa,EAAE,OAAO,CAAC;IACvB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB;AAwBD;;;;;;;;;GASG;AACH,wBAAgB,8BAA8B,CAC5C,IAAI,EAAE,MAAM,EACZ,GAAG,GAAE,GAAiB,EACtB,WAAW,GAAE,sBAA2B,GACvC,yBAAyB,CA4C3B;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,SAAS,EAAE,kBAAkB,CAAC;IACvC,IAAI,CAAC,CAAC,GAAG,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IAC5E,GAAG,CAAC,CAAC,GAAG,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IAC9F,MAAM,CAAC,CAAC,GAAG,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IAC7F,MAAM,CAAC,CAAC,GAAG,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,mBAAmB,GAAG;QAAE,MAAM,CAAC,EAAE,OAAO,GAAG,KAAK,CAAA;KAAE,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IACzI,MAAM,CAAC,CAAC,GAAG,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC;CACvG;AAqBD,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,kBAAkB,GAAG,aAAa,CAgC9F;AAED,MAAM,WAAW,0BAA0B;IACzC,SAAS,EAAE,OAAO,CAAC;IACnB,MAAM,EAAE,aAAa,CAAC;IACtB,UAAU,EAAE,yBAAyB,CAAC;CACvC;AAED,gFAAgF;AAChF,wBAAgB,4BAA4B,CAC1C,IAAI,EAAE,MAAM,EACZ,GAAG,GAAE,GAAiB,EACtB,WAAW,GAAE,sBAA2B,GACvC,0BAA0B,CAkD5B"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The credential-chain seam between @hasna/contracts 1.0.2 and this package.
|
|
3
|
+
*
|
|
4
|
+
* Contacts never builds its own credential or authority chain: the CLI, the
|
|
5
|
+
* MCP server and the store all funnel through
|
|
6
|
+
* `resolveContactsClientTransport`, which hands @hasna/contracts the
|
|
7
|
+
* environment unchanged, per request, fresh. The one thing this module owns
|
|
8
|
+
* is the ambient gate for the resolver's Keychain tier (#1788).
|
|
9
|
+
*
|
|
10
|
+
* The Keychain belongs to the MACHINE, so the resolver consults it only when
|
|
11
|
+
* it is handed the live process environment (or a snapshot the resolver
|
|
12
|
+
* itself marked ambient) — a caller-built env object is the hermetic seam and
|
|
13
|
+
* must never drag a machine identity into a test. A copy made on the way in
|
|
14
|
+
* silently loses that gate, and the per-request re-resolution in
|
|
15
|
+
* `resolveContactsStorageClient` does make a copy: a snapshot of the env so a
|
|
16
|
+
* concurrent mutation cannot tear one resolution. A copy that drops the
|
|
17
|
+
* Keychain gate would, on a Mac station whose only credential is the Keychain
|
|
18
|
+
* item, turn every request into a fresh failure — or, with a credential on
|
|
19
|
+
* disk, into a DIFFERENT principal with no notice.
|
|
20
|
+
*
|
|
21
|
+
* So the ambient answer is decided HERE, on the ORIGINAL env, before any copy
|
|
22
|
+
* exists, and carried across as the documented `keychain.enabled` control.
|
|
23
|
+
* A caller's explicit `keychain` options always win; the injected `run`
|
|
24
|
+
* runner (which @hasna/contracts already treats as "enabled") is left alone.
|
|
25
|
+
*/
|
|
26
|
+
import { type CredentialChainOptions } from "@hasna/contracts/client";
|
|
27
|
+
export type Env = Record<string, string | undefined>;
|
|
28
|
+
/**
|
|
29
|
+
* Is this the environment the machine's ambient credential stores belong to?
|
|
30
|
+
*
|
|
31
|
+
* The same test @hasna/contracts performs, run on the env BEFORE any copy
|
|
32
|
+
* exists — which is the whole point of asking here.
|
|
33
|
+
*/
|
|
34
|
+
export declare function isAmbientContactsEnv(env: Env): boolean;
|
|
35
|
+
/** Every env name the resolver may consult for the contacts authority or
|
|
36
|
+
* credential — canonical names, the legacy aliases, the deliberate pointers,
|
|
37
|
+
* and the profile selector. */
|
|
38
|
+
export declare function contactsAuthorityEnvKeys(): string[];
|
|
39
|
+
/**
|
|
40
|
+
* The credential chain options a contacts surface hands the resolver.
|
|
41
|
+
*
|
|
42
|
+
* On the identity path (`env === process.env`, or a snapshot @hasna/contracts
|
|
43
|
+
* itself marked ambient) this passes through untouched and the resolver runs
|
|
44
|
+
* its own ambient test. On a CALLER-BUILT env — every hermetic test env, and
|
|
45
|
+
* the per-request snapshot in `resolveContactsStorageClient` — the Keychain
|
|
46
|
+
* tier would silently switch off the moment a copy is made; the caller's
|
|
47
|
+
* explicit controls still win, and otherwise the tier is pinned to what the
|
|
48
|
+
* ORIGINAL env was: ambient in, enabled; hermetic in, disabled.
|
|
49
|
+
*/
|
|
50
|
+
export declare function contactsResolverCredentials(env: Env, credentials?: CredentialChainOptions): CredentialChainOptions;
|
|
51
|
+
//# sourceMappingURL=resolver-inputs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolver-inputs.d.ts","sourceRoot":"","sources":["../../src/cloud/resolver-inputs.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,OAAO,EAKL,KAAK,sBAAsB,EAE5B,MAAM,yBAAyB,CAAC;AAEjC,MAAM,MAAM,GAAG,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;AAMrD;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,CAGtD;AAED;;+BAE+B;AAC/B,wBAAgB,wBAAwB,IAAI,MAAM,EAAE,CASnD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,2BAA2B,CACzC,GAAG,EAAE,GAAG,EACR,WAAW,GAAE,sBAA2B,GACvC,sBAAsB,CAMxB"}
|
package/dist/db/paths.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type PathKind = "
|
|
1
|
+
export type PathKind = "data" | "state";
|
|
2
2
|
export interface PathsResolverOptions {
|
|
3
3
|
app: string;
|
|
4
4
|
internal?: boolean;
|
|
@@ -9,12 +9,12 @@ export interface PathsResolverOptions {
|
|
|
9
9
|
export declare function dataDir(options: PathsResolverOptions): string;
|
|
10
10
|
export declare function stateDir(options: PathsResolverOptions): string;
|
|
11
11
|
/**
|
|
12
|
-
* The
|
|
13
|
-
*
|
|
12
|
+
* The data root for retained server/legacy utilities. It creates only the
|
|
13
|
+
* requested target directory and never scans or adopts older homes.
|
|
14
14
|
*/
|
|
15
15
|
export declare function getDataDir(): string;
|
|
16
16
|
/**
|
|
17
|
-
* The
|
|
17
|
+
* The state root for retained server/legacy utilities.
|
|
18
18
|
*/
|
|
19
19
|
export declare function getStateDir(): string;
|
|
20
20
|
export declare function getDbPath(): string;
|
package/dist/db/paths.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../../src/db/paths.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../../src/db/paths.ts"],"names":[],"mappings":"AA6BA,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,OAAO,CAAC;AAOxC,MAAM,WAAW,oBAAoB;IACnC,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;CAC1C;AAmDD,wBAAgB,OAAO,CAAC,OAAO,EAAE,oBAAoB,GAAG,MAAM,CAE7D;AACD,wBAAgB,QAAQ,CAAC,OAAO,EAAE,oBAAoB,GAAG,MAAM,CAE9D;AAWD;;;GAGG;AACH,wBAAgB,UAAU,IAAI,MAAM,CAKnC;AAED;;GAEG;AACH,wBAAgB,WAAW,IAAI,MAAM,CAKpC;AAED,wBAAgB,SAAS,IAAI,MAAM,CAIlC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export declare const SERVER_DATA_BACKENDS: readonly ["postgresql"];
|
|
2
|
+
export type ServerDataBackend = (typeof SERVER_DATA_BACKENDS)[number];
|
|
3
|
+
export type Env = Record<string, string | undefined>;
|
|
4
|
+
/** Upper-snake env token for an app name, e.g. `todos` -> `TODOS`. */
|
|
5
|
+
export declare function envToken(name: string): string;
|
|
6
|
+
export interface ServerDataBackendEnvKeys {
|
|
7
|
+
databaseUrlKeys: string[];
|
|
8
|
+
}
|
|
9
|
+
export declare function serverDataBackendEnvKeys(name: string): ServerDataBackendEnvKeys;
|
|
10
|
+
export interface ServerDataBackendResolution {
|
|
11
|
+
backend: ServerDataBackend;
|
|
12
|
+
source: string;
|
|
13
|
+
databaseUrlPresent: boolean;
|
|
14
|
+
databaseUrlSource: string;
|
|
15
|
+
}
|
|
16
|
+
export declare function resolveServerDataBackend(name: string, env?: Env): ServerDataBackendResolution;
|
|
17
|
+
/** Resolve the required PostgreSQL URL without logging it. */
|
|
18
|
+
export declare function resolveDatabaseUrl(name: string, env?: Env): string;
|
|
19
|
+
//# sourceMappingURL=backend.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"backend.d.ts","sourceRoot":"","sources":["../../../src/generated/storage-kit/backend.ts"],"names":[],"mappings":"AAaA,eAAO,MAAM,oBAAoB,yBAA0B,CAAC;AAC5D,MAAM,MAAM,iBAAiB,GAAG,CAAC,OAAO,oBAAoB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEtE,MAAM,MAAM,GAAG,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;AAErD,sEAAsE;AACtE,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE7C;AAED,MAAM,WAAW,wBAAwB;IACvC,eAAe,EAAE,MAAM,EAAE,CAAC;CAC3B;AAED,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,wBAAwB,CAK/E;AA8CD,MAAM,WAAW,2BAA2B;IAC1C,OAAO,EAAE,iBAAiB,CAAC;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,kBAAkB,EAAE,OAAO,CAAC;IAC5B,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAED,wBAAgB,wBAAwB,CACtC,IAAI,EAAE,MAAM,EACZ,GAAG,GAAE,GAAiB,GACrB,2BAA2B,CAS7B;AAED,8DAA8D;AAC9D,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,GAAE,GAAiB,GAAG,MAAM,CAG/E"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/generated/storage-kit/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/generated/storage-kit/index.ts"],"names":[],"mappings":"AAaA,eAAO,MAAM,WAAW,UAAU,CAAC;AAEnC,cAAc,cAAc,CAAC;AAC7B,cAAc,UAAU,CAAC;AACzB,cAAc,YAAY,CAAC;AAC3B,cAAc,WAAW,CAAC;AAC1B,cAAc,iBAAiB,CAAC;AAChC,cAAc,aAAa,CAAC"}
|
|
@@ -27,11 +27,32 @@ export declare function checksumSql(sql: string): string;
|
|
|
27
27
|
export declare function defineMigration(id: string, sql: string): Migration;
|
|
28
28
|
export interface MigrationRunnerOptions {
|
|
29
29
|
ledgerTable?: string;
|
|
30
|
+
/**
|
|
31
|
+
* Applied-ledger rows whose ids the build ACKNOWLEDGES as non-reproducible
|
|
32
|
+
* history: a migration that was applied to the ledger by an out-of-band
|
|
33
|
+
* operation or by a build whose id scheme no longer exists, so no current
|
|
34
|
+
* source can reproduce its id or its SQL.
|
|
35
|
+
*
|
|
36
|
+
* An acknowledged id:
|
|
37
|
+
* - passes the downgrade guard (it IS recognized — as history),
|
|
38
|
+
* - is never checksum-compared (its SQL is gone, so no checksum can be
|
|
39
|
+
* computed for it; storing an arbitrary placeholder in `checksum` is
|
|
40
|
+
* what the prod ledger already holds for such rows),
|
|
41
|
+
* - is never re-applied and never re-inserted (it is already in the
|
|
42
|
+
* ledger; the plan covers declared migrations only).
|
|
43
|
+
*
|
|
44
|
+
* The list is EXPLICIT and OPT-IN: an acknowledged id may not also be a
|
|
45
|
+
* declared migration (enforced at construction), and any OTHER applied row
|
|
46
|
+
* unknown to the build still fails the downgrade guard. Every declared
|
|
47
|
+
* migration keeps its checksum bind unchanged.
|
|
48
|
+
*/
|
|
49
|
+
acknowledgedLegacyIds?: readonly string[];
|
|
30
50
|
}
|
|
31
51
|
export declare class MigrationLedger {
|
|
32
52
|
private readonly client;
|
|
33
53
|
private readonly migrations;
|
|
34
54
|
private readonly ledgerTable;
|
|
55
|
+
private readonly acknowledgedLegacyIds;
|
|
35
56
|
constructor(client: TypedQueryClient, migrations: readonly Migration[], options?: MigrationRunnerOptions);
|
|
36
57
|
ensureLedger(): Promise<void>;
|
|
37
58
|
listApplied(): Promise<AppliedMigration[]>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"migrations.d.ts","sourceRoot":"","sources":["../../../src/generated/storage-kit/migrations.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"migrations.d.ts","sourceRoot":"","sources":["../../../src/generated/storage-kit/migrations.ts"],"names":[],"mappings":"AAqBA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAuEnD,2EAA2E;AAC3E,eAAO,MAAM,8BAA8B,sBAAsB,CAAC;AAElE,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,MAAM,cAAc,GAAG,iBAAiB,GAAG,SAAS,CAAC;AAE3D,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IAC9B,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC;CAChC;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,gBAAgB,EAAE,CAAC;IACrC,QAAQ,CAAC,IAAI,EAAE,iBAAiB,EAAE,CAAC;CACpC;AAED,yDAAyD;AACzD,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAG/C;AAED,0EAA0E;AAC1E,wBAAgB,eAAe,CAAC,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,SAAS,CAElE;AAQD,MAAM,WAAW,sBAAsB;IACrC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;;;;;;;;;;;;;;OAkBG;IACH,qBAAqB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC3C;AAED,qBAAa,eAAe;IAKxB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,UAAU;IAL7B,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAAsB;gBAGzC,MAAM,EAAE,gBAAgB,EACxB,UAAU,EAAE,SAAS,SAAS,EAAE,EACjD,OAAO,GAAE,sBAA2B;IA2BhC,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;IAU7B,WAAW,IAAI,OAAO,CAAC,gBAAgB,EAAE,CAAC;YAKlC,WAAW;IAWzB,oEAAoE;IACpE,OAAO,CAAC,SAAS;IA0BjB,yEAAyE;IACnE,OAAO,CAAC,IAAI,GAAE;QAAE,MAAM,CAAC,EAAE,OAAO,CAAA;KAAO,GAAG,OAAO,CAAC,eAAe,CAAC;CAqBzE;AAED,kEAAkE;AAClE,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,gBAAgB,EACxB,UAAU,EAAE,SAAS,SAAS,EAAE,EAChC,OAAO,GAAE,sBAA2B,GACnC,eAAe,CAEjB"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read `key` from `source` only when `source` OWNS it.
|
|
3
|
+
*
|
|
4
|
+
* Returns `undefined` for an inherited property and for a null/undefined or
|
|
5
|
+
* non-object source, so a guarded read is a drop-in for `source?.[key]` that
|
|
6
|
+
* cannot be answered by the prototype chain.
|
|
7
|
+
*/
|
|
8
|
+
export declare function ownProp<T>(source: unknown, key: string): T | undefined;
|
|
9
|
+
/** `ownProp` narrowed to a string, so a polluted non-string cannot slip through. */
|
|
10
|
+
export declare function ownString(source: unknown, key: string): string | undefined;
|
|
11
|
+
//# sourceMappingURL=own.d.ts.map
|