@jini-ai/core 0.4.8 → 0.6.10

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 (77) hide show
  1. package/CHANGELOG.md +23 -2
  2. package/README.md +5 -0
  3. package/dist/agent-tools.d.ts +1 -1
  4. package/dist/approval.d.ts +15 -0
  5. package/dist/approval.js +87 -22
  6. package/dist/batch-tool-inputs.d.ts +147 -0
  7. package/dist/batch-tool-inputs.js +257 -0
  8. package/dist/feature-flags.d.ts +21 -0
  9. package/dist/feature-flags.js +17 -0
  10. package/dist/fetch/index.d.ts +2 -1
  11. package/dist/fetch/index.js +1 -0
  12. package/dist/fetch/loader-read.d.ts +16 -0
  13. package/dist/fetch/loader-read.js +22 -0
  14. package/dist/fetch/types.d.ts +12 -2
  15. package/dist/gated-mutations/gateway.js +32 -27
  16. package/dist/index.d.ts +8 -1
  17. package/dist/index.js +7 -1
  18. package/dist/object-store.d.ts +115 -0
  19. package/dist/object-store.js +70 -0
  20. package/dist/origin-validation.js +64 -57
  21. package/dist/primitives/http.d.ts +2 -2
  22. package/dist/primitives/index.d.ts +1 -0
  23. package/dist/primitives/index.js +1 -0
  24. package/dist/primitives/paths.js +17 -10
  25. package/dist/primitives/tasks.d.ts +10 -0
  26. package/dist/primitives/tasks.js +8 -0
  27. package/dist/provider-catalog.d.ts +76 -0
  28. package/dist/provider-catalog.js +6 -0
  29. package/dist/provider-registry.d.ts +144 -0
  30. package/dist/provider-registry.js +138 -0
  31. package/dist/records.d.ts +24 -0
  32. package/dist/records.js +25 -0
  33. package/dist/redact.d.ts +32 -0
  34. package/dist/redact.js +91 -34
  35. package/dist/registration-kit.d.ts +7 -4
  36. package/dist/registration-kit.js +81 -62
  37. package/dist/settings/agent-value-write-tools.d.ts +19 -0
  38. package/dist/settings/agent-value-write-tools.js +131 -70
  39. package/dist/settings/ensure-definitions.d.ts +4 -2
  40. package/dist/settings/ensure-definitions.js +79 -63
  41. package/dist/settings/errors.d.ts +13 -0
  42. package/dist/settings/errors.js +15 -0
  43. package/dist/settings/express/cms-adapter.d.ts +2 -2
  44. package/dist/settings/express/cms-adapter.js +34 -22
  45. package/dist/settings/express/http-request.d.ts +33 -0
  46. package/dist/settings/express/http-request.js +49 -0
  47. package/dist/settings/express/http.d.ts +2 -2
  48. package/dist/settings/express/http.js +16 -39
  49. package/dist/settings/express/stream.d.ts +2 -2
  50. package/dist/settings/express/stream.js +49 -27
  51. package/dist/settings/index.d.ts +2 -2
  52. package/dist/settings/index.js +1 -1
  53. package/dist/settings/ports.d.ts +7 -1
  54. package/dist/settings/repo.memory.js +7 -4
  55. package/dist/settings/settings.js +54 -60
  56. package/dist/settings/tool-registrations.d.ts +2 -3
  57. package/dist/settings/tool-registrations.js +64 -39
  58. package/dist/settings/write-service.d.ts +42 -4
  59. package/dist/settings/write-service.js +72 -5
  60. package/dist/target-scope.d.ts +2 -0
  61. package/dist/target-scope.js +18 -2
  62. package/dist/text/escape.js +2 -2
  63. package/dist/text/sanitize-untrusted-text.d.ts +5 -0
  64. package/dist/text/sanitize-untrusted-text.js +55 -35
  65. package/dist/tool-exposure.d.ts +79 -0
  66. package/dist/tool-exposure.js +163 -0
  67. package/dist/tool-input-errors.d.ts +23 -0
  68. package/dist/tool-input-errors.js +24 -0
  69. package/dist/tool-input-normalization.d.ts +37 -0
  70. package/dist/tool-input-normalization.js +188 -0
  71. package/dist/tool-metadata-types.d.ts +26 -0
  72. package/dist/tool-metadata-types.js +2 -0
  73. package/dist/tool-metadata.d.ts +2 -23
  74. package/dist/tool-registry.d.ts +38 -22
  75. package/dist/tool-registry.js +35 -20
  76. package/package.json +20 -2
  77. package/plugin-directory.js +72 -33
@@ -0,0 +1,21 @@
1
+ /** A boolean rollout decision with its winning layer; feature flags never grant permissions. */
2
+ export interface FeatureFlagDecision {
3
+ enabled: boolean;
4
+ source: 'environment' | 'runtime' | 'workspace' | 'default';
5
+ }
6
+ /** Evaluate one flag. Environment is the deployment override; runtime precedes persisted workspace.
7
+ * An explicitly malformed layer disables the feature instead of falling through to an enabling layer.
8
+ * @param requiredArgs - Flag key for a stable caller contract (the host owns lookup).
9
+ * @param optionalArgs - Layer values, with absent layers represented by undefined. Defaults off.
10
+ * @returns The winning decision and source, with no I/O or mutation.
11
+ * @complexity O(1) time and space.
12
+ */
13
+ export declare function evaluateFeatureFlag(_requiredArgs: {
14
+ key: string;
15
+ }, { environment, runtime, workspace, defaultEnabled }?: {
16
+ environment?: unknown;
17
+ runtime?: unknown;
18
+ workspace?: unknown;
19
+ defaultEnabled?: boolean;
20
+ }): FeatureFlagDecision;
21
+ //# sourceMappingURL=feature-flags.d.ts.map
@@ -0,0 +1,17 @@
1
+ /** Evaluate one flag. Environment is the deployment override; runtime precedes persisted workspace.
2
+ * An explicitly malformed layer disables the feature instead of falling through to an enabling layer.
3
+ * @param requiredArgs - Flag key for a stable caller contract (the host owns lookup).
4
+ * @param optionalArgs - Layer values, with absent layers represented by undefined. Defaults off.
5
+ * @returns The winning decision and source, with no I/O or mutation.
6
+ * @complexity O(1) time and space.
7
+ */
8
+ export function evaluateFeatureFlag(_requiredArgs, { environment, runtime, workspace, defaultEnabled = false } = {}) {
9
+ if (environment !== undefined)
10
+ return { enabled: environment === 'true' || environment === '1', source: 'environment' };
11
+ if (runtime !== undefined)
12
+ return { enabled: runtime === true, source: 'runtime' };
13
+ if (workspace !== undefined)
14
+ return { enabled: workspace === true, source: 'workspace' };
15
+ return { enabled: defaultEnabled === true, source: 'default' };
16
+ }
17
+ //# sourceMappingURL=feature-flags.js.map
@@ -1,5 +1,6 @@
1
1
  /** Framework-neutral query contracts and cache; hosts supply transport callbacks and environment ports. */
2
2
  export { DEFAULT_STALE_TIME, IDLE_RETENTION, FetchQueryCache, toQueryError } from './cache.js';
3
3
  export type { QueryCacheSchedulerPort, FetchQueryEnvironmentPort, FetchQueryCacheOptions, QueryState, QueryObserver, CacheEntry } from './cache.js';
4
- export type { QueryKey, QueryStatus, QueryResult, FetchQueryOptions, MutationStatus, MutationResult, FetchMutationOptions, CachedLoaderOptions, CachedLoader, FetchQueryAdapter } from './types.js';
4
+ export type { QueryKey, QueryStatus, QueryResult, FetchQueryOptions, MutationStatus, MutationResult, FetchMutationOptions, CachedLoaderOptions, CachedLoaderDefaults, CachedLoader, FetchQueryAdapter } from './types.js';
5
+ export { missingCachedLoaderFetchError, withCachedLoaderFetch } from './loader-read.js';
5
6
  //# sourceMappingURL=index.d.ts.map
@@ -1,3 +1,4 @@
1
1
  /** Framework-neutral query contracts and cache; hosts supply transport callbacks and environment ports. */
2
2
  export { DEFAULT_STALE_TIME, IDLE_RETENTION, FetchQueryCache, toQueryError } from './cache.js';
3
+ export { missingCachedLoaderFetchError, withCachedLoaderFetch } from './loader-read.js';
3
4
  //# sourceMappingURL=index.js.map
@@ -0,0 +1,16 @@
1
+ import type { CachedLoaderDefaults, CachedLoaderOptions, QueryKey } from './types.js';
2
+ /**
3
+ * The error an argument-less `load()` rejects with when its loader was created without a default
4
+ * `fetch`. Named, rather than left to a `TypeError: fetch is not a function` from deep inside a cache
5
+ * adapter, so the misuse points at the call site that needs to pass `{ key, fetch }`.
6
+ */
7
+ export declare function missingCachedLoaderFetchError(key: QueryKey): Error;
8
+ /**
9
+ * Runs `read` with a fully specified `{ key, fetch }`, or rejects with
10
+ * {@link missingCachedLoaderFetchError} when `required` (a loader's own defaults, for an argument-less
11
+ * `load()`) carries no fetch. It rejects BEFORE `read` runs, so a misuse never enters the shared cache:
12
+ * a failed fetch there would publish an error state to every observer of that key.
13
+ * Shared by every `FetchQueryAdapter` so each adapter's `load` stays a single straight-line call.
14
+ */
15
+ export declare function withCachedLoaderFetch<T>(required: CachedLoaderDefaults<T>, read: (resolved: Pick<CachedLoaderOptions<T>, 'key' | 'fetch'>) => Promise<T>): Promise<T>;
16
+ //# sourceMappingURL=loader-read.d.ts.map
@@ -0,0 +1,22 @@
1
+ /**
2
+ * The error an argument-less `load()` rejects with when its loader was created without a default
3
+ * `fetch`. Named, rather than left to a `TypeError: fetch is not a function` from deep inside a cache
4
+ * adapter, so the misuse points at the call site that needs to pass `{ key, fetch }`.
5
+ */
6
+ export function missingCachedLoaderFetchError(key) {
7
+ return new Error(`Cached loader for key ${JSON.stringify(key)} has no default fetch: call load({ key, fetch }) with a fetch of its own`);
8
+ }
9
+ /**
10
+ * Runs `read` with a fully specified `{ key, fetch }`, or rejects with
11
+ * {@link missingCachedLoaderFetchError} when `required` (a loader's own defaults, for an argument-less
12
+ * `load()`) carries no fetch. It rejects BEFORE `read` runs, so a misuse never enters the shared cache:
13
+ * a failed fetch there would publish an error state to every observer of that key.
14
+ * Shared by every `FetchQueryAdapter` so each adapter's `load` stays a single straight-line call.
15
+ */
16
+ export function withCachedLoaderFetch(required, read) {
17
+ const { key, fetch } = required;
18
+ if (fetch === undefined)
19
+ return Promise.reject(missingCachedLoaderFetchError(key));
20
+ return read({ key, fetch });
21
+ }
22
+ //# sourceMappingURL=loader-read.js.map
@@ -67,13 +67,23 @@ export interface CachedLoaderOptions<T> {
67
67
  fetch: () => Promise<T>;
68
68
  staleTime?: number;
69
69
  }
70
+ /**
71
+ * What a loader hook is created with: the key its `peek`/`replace` (and an argument-less `load`)
72
+ * address, plus an optional default `fetch`. The default is optional because a loader whose every
73
+ * `load` names its own `{ key, fetch }` (per-id detail lookups, per-path file reads) would otherwise
74
+ * have to invent a fetch that can never run. An argument-less `load()` on a loader created without
75
+ * one rejects with {@link missingCachedLoaderFetchError} instead of touching the cache.
76
+ */
77
+ export type CachedLoaderDefaults<T> = Pick<CachedLoaderOptions<T>, "key"> & {
78
+ fetch?: CachedLoaderOptions<T>["fetch"] | undefined;
79
+ };
70
80
  /**
71
81
  * A plain promise loader serves components that own their loading UI rather than consuming
72
82
  * a hook result. It shares the provider cache, including one in-flight request per key.
73
83
  */
74
84
  export interface CachedLoader<T> {
75
85
  peek: () => T | undefined;
76
- load: (required?: Pick<CachedLoaderOptions<T>, "key" | "fetch">) => Promise<T>;
86
+ load: (required?: Pick<CachedLoaderOptions<T>, "key" | "fetch">, optional?: Pick<CachedLoaderOptions<T>, "staleTime">) => Promise<T>;
77
87
  replace: (required: {
78
88
  value: T;
79
89
  }) => void;
@@ -88,6 +98,6 @@ export interface FetchQueryAdapter {
88
98
  useInvalidate: () => (required: {
89
99
  key: QueryKey;
90
100
  }) => void;
91
- useCachedLoader: <T>(required: Pick<CachedLoaderOptions<T>, "key" | "fetch">, optional?: Omit<CachedLoaderOptions<T>, "key" | "fetch">) => CachedLoader<T>;
101
+ useCachedLoader: <T>(required: CachedLoaderDefaults<T>, optional?: Omit<CachedLoaderOptions<T>, "key" | "fetch">) => CachedLoader<T>;
92
102
  }
93
103
  //# sourceMappingURL=types.d.ts.map
@@ -40,13 +40,36 @@ export async function authorizeForHooks(required) {
40
40
  }
41
41
  return deps.authorizeInstance({ principalId, permission });
42
42
  }
43
+ /** Fresh authorization gate shared by plan/confirm/execute; refuses with NOT_AUTHORIZED. */
44
+ async function requireAuthorization(required) {
45
+ const { principalId, permission } = required;
46
+ const authResult = await authorizeForHooks(required);
47
+ if (!authResult.allowed) {
48
+ throw new ForbiddenError({ message: `principal '${principalId}' is not authorized for '${permission}' (${authResult.reason})`, reasonCode: "NOT_AUTHORIZED" });
49
+ }
50
+ }
51
+ /** The refusal for a token that cannot be redeemed: already redeemed, otherwise expired. */
52
+ function unredeemableTokenError(record) {
53
+ if (record?.status === "redeemed") {
54
+ return new TokenAlreadyRedeemedError({ message: `confirmation token has already been redeemed` });
55
+ }
56
+ return new TokenExpiredError({ message: `confirmation token has expired` });
57
+ }
58
+ /** Look up the presented token and refuse it unless it is still minted and unexpired. */
59
+ async function loadRedeemableToken(required) {
60
+ const { deps, confirmationToken } = required;
61
+ const record = await deps.tokens.findByToken({ token: confirmationToken });
62
+ if (!record) {
63
+ throw new TokenExpiredError({ message: `confirmation token was not found` });
64
+ }
65
+ if (!isRedeemable({ record, now: nowIso({ clock: deps.clock }) }))
66
+ throw unredeemableTokenError(record);
67
+ return record;
68
+ }
43
69
  // Planning is read-only: check read permission and derive details, never invoke executeMutation.
44
70
  export async function plan(required, _optional = {}) {
45
71
  const { deps, principalId, hooks } = required;
46
- const authResult = await authorizeForHooks({ deps, hooks, principalId, permission: hooks.readPermission });
47
- if (!authResult.allowed) {
48
- throw new ForbiddenError({ message: `principal '${principalId}' is not authorized for '${hooks.readPermission}' (${authResult.reason})`, reasonCode: "NOT_AUTHORIZED" });
49
- }
72
+ await requireAuthorization({ deps, hooks, principalId, permission: hooks.readPermission });
50
73
  const { planHash, details } = await hooks.computePlan({});
51
74
  return { domain: hooks.domain, planId: deps.idGen.newId(), planHash, details };
52
75
  }
@@ -57,10 +80,7 @@ export async function confirm(required, _optional = {}) {
57
80
  if (principalKind === "agent") {
58
81
  throw new ForbiddenError({ message: `agent principals may not confirm a gated mutation`, reasonCode: "AGENT_CANNOT_CONFIRM" });
59
82
  }
60
- const authResult = await authorizeForHooks({ deps, hooks, principalId, permission: hooks.mutatePermission });
61
- if (!authResult.allowed) {
62
- throw new ForbiddenError({ message: `principal '${principalId}' is not authorized for '${hooks.mutatePermission}' (${authResult.reason})`, reasonCode: "NOT_AUTHORIZED" });
63
- }
83
+ await requireAuthorization({ deps, hooks, principalId, permission: hooks.mutatePermission });
64
84
  const token = mintToken({
65
85
  planId,
66
86
  planHash,
@@ -82,20 +102,8 @@ export async function confirm(required, _optional = {}) {
82
102
  export async function execute(required, _optional = {}) {
83
103
  // Reevaluate authorization here; confirm-time permission is not authority to execute later.
84
104
  const { deps, principalId, principalKind, hooks, confirmationToken } = required;
85
- const authResult = await authorizeForHooks({ deps, hooks, principalId, permission: hooks.mutatePermission });
86
- if (!authResult.allowed) {
87
- throw new ForbiddenError({ message: `principal '${principalId}' is not authorized for '${hooks.mutatePermission}' (${authResult.reason})`, reasonCode: "NOT_AUTHORIZED" });
88
- }
89
- const record = await deps.tokens.findByToken({ token: confirmationToken });
90
- if (!record) {
91
- throw new TokenExpiredError({ message: `confirmation token was not found` });
92
- }
93
- if (!isRedeemable({ record, now: nowIso({ clock: deps.clock }) })) {
94
- if (record.status === "redeemed") {
95
- throw new TokenAlreadyRedeemedError({ message: `confirmation token has already been redeemed` });
96
- }
97
- throw new TokenExpiredError({ message: `confirmation token has expired` });
98
- }
105
+ await requireAuthorization({ deps, hooks, principalId, permission: hooks.mutatePermission });
106
+ const record = await loadRedeemableToken({ deps, confirmationToken });
99
107
  const resolvedIdentity = await hooks.resolveActorClassIdentity({ principalId, principalKind });
100
108
  if (resolvedIdentity !== record.confirmerPrincipalId) {
101
109
  throw new ForbiddenError({ message: `principal '${principalId}' (kind '${principalKind}') is not the confirmer of this token`, reasonCode: "ACTOR_CLASS_MISMATCH" });
@@ -113,11 +121,8 @@ export async function execute(required, _optional = {}) {
113
121
  // Concurrent executions can pass every earlier check; atomic redemption makes the loser fail
114
122
  // before its domain mutation, preventing a second application of the same approval.
115
123
  const { redeemed, record: latestRecord } = await deps.tokens.tryRedeem({ token: confirmationToken, now: nowIso({ clock: deps.clock }) });
116
- if (!redeemed) {
117
- if (!latestRecord || latestRecord.status !== "redeemed")
118
- throw new TokenExpiredError({ message: `confirmation token has expired` });
119
- throw new TokenAlreadyRedeemedError({ message: `confirmation token has already been redeemed` });
120
- }
124
+ if (!redeemed)
125
+ throw unredeemableTokenError(latestRecord);
121
126
  return hooks.executeMutation({ planHash, details });
122
127
  }
123
128
  //# sourceMappingURL=gateway.js.map
package/dist/index.d.ts CHANGED
@@ -12,11 +12,14 @@ export * from './origin-validation.js';
12
12
  export * from './principal.js';
13
13
  export * from './surface-exchanges.js';
14
14
  export type { AuthorizationDecision, RunRef, SurfaceEmission, SurfaceEmitter, ToolAuthorizationContext, ToolDescriptor, ToolExecutionContext, ToolExecutionOptions, ToolHandler, ToolPolicy, ToolRegistration, ToolRegistry, } from './tool-registry.js';
15
- export { createToolRegistrationScope, toolRegistryRevision, createToolRegistry, isReadOnlyTool, ToolInputError } from './tool-registry.js';
15
+ export { createToolRegistrationScope, toolRegistryRevision, createToolRegistry, isReadOnlyTool, toolDiscoveryMetadata, ToolInputError } from './tool-registry.js';
16
16
  export * from './tool-tokens.js';
17
+ export * from './tool-exposure.js';
17
18
  export * from './gated-mutations.js';
18
19
  export * from './model-facing-tool-errors.js';
19
20
  export * from './contribution-registry.js';
21
+ export * from './provider-registry.js';
22
+ export * from './object-store.js';
20
23
  export * from './naming.js';
21
24
  export type { AgentToolSideEffect, AgentToolActorClassRule, AgentToolDefinition } from './agent-tools.js';
22
25
  export * from "./registration-kit.js";
@@ -24,4 +27,8 @@ export * from "./approval.js";
24
27
  export * from './target-scope.js';
25
28
  export * from "./native-regexp.js";
26
29
  export { ForbiddenError as ToolPermissionDeniedError, requireToolPermission, adaptLegacyAuthorize, type LegacyToolAuthorizeFn, type AuthorizationPort as ToolPermissionAuthorizationPort, type RequireToolPermissionRequired, type RequireToolPermissionOptional, } from "./tool-permissions.js";
30
+ export { evaluateFeatureFlag, type FeatureFlagDecision } from './feature-flags.js';
31
+ export { normalizeToolInput, withNormalizationNotes, ToolInputNormalizationError, type NormalizedToolInput, type ToolInputNormalizer } from './tool-input-normalization.js';
32
+ export { applyBatchInputs, BATCH_GUIDANCE_MARKER, withoutBatchGuidance, batchOutcomeFor, batchDescriptorFor, batchEligibilityFor, defaultBatchableTools, withBatchInput, DEFAULT_MAX_BATCH_ITEMS, type BatchableTool, type BatchEligibility, type BatchOptOut, type BatchResult, type BatchItemResult } from './batch-tool-inputs.js';
33
+ export type { MediaProviderCatalogPolicy, MediaProviderOption, ProviderPreset, ProviderPresetKind } from './provider-catalog.js';
27
34
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -9,15 +9,21 @@ export * from './api-token-auth.js';
9
9
  export * from './origin-validation.js';
10
10
  export * from './principal.js';
11
11
  export * from './surface-exchanges.js';
12
- export { createToolRegistrationScope, toolRegistryRevision, createToolRegistry, isReadOnlyTool, ToolInputError } from './tool-registry.js';
12
+ export { createToolRegistrationScope, toolRegistryRevision, createToolRegistry, isReadOnlyTool, toolDiscoveryMetadata, ToolInputError } from './tool-registry.js';
13
13
  export * from './tool-tokens.js';
14
+ export * from './tool-exposure.js';
14
15
  export * from './gated-mutations.js';
15
16
  export * from './model-facing-tool-errors.js';
16
17
  export * from './contribution-registry.js';
18
+ export * from './provider-registry.js';
19
+ export * from './object-store.js';
17
20
  export * from './naming.js';
18
21
  export * from "./registration-kit.js";
19
22
  export * from "./approval.js";
20
23
  export * from './target-scope.js';
21
24
  export * from "./native-regexp.js";
22
25
  export { ForbiddenError as ToolPermissionDeniedError, requireToolPermission, adaptLegacyAuthorize, } from "./tool-permissions.js";
26
+ export { evaluateFeatureFlag } from './feature-flags.js';
27
+ export { normalizeToolInput, withNormalizationNotes, ToolInputNormalizationError } from './tool-input-normalization.js';
28
+ export { applyBatchInputs, BATCH_GUIDANCE_MARKER, withoutBatchGuidance, batchOutcomeFor, batchDescriptorFor, batchEligibilityFor, defaultBatchableTools, withBatchInput, DEFAULT_MAX_BATCH_ITEMS } from './batch-tool-inputs.js';
23
29
  //# sourceMappingURL=index.js.map
@@ -0,0 +1,115 @@
1
+ /**
2
+ * @file The ONE object-storage contract (2026-10-21): key → bytes, with an optional public-read
3
+ * capability. It absorbs four overlapping shapes (cms `BlobStorePort`, capability-providers
4
+ * `StorageProvider`, devops `ArtifactStorePort`, platform `BlobStorage`); those stay as thin views
5
+ * over this one until their consumers move. Providers are keyed in a `createProviderRegistry`
6
+ * (`contract: 'object_store'`), default `local`.
7
+ *
8
+ * Universal: types and pure helpers only, so every storage package (all of which already depend
9
+ * on core) can implement it. Filesystem and network adapters live in node/host packages.
10
+ *
11
+ * Data-plane calls REJECT on a storage fault (as every existing byte port did), and report absence
12
+ * as `null`/`false`, never as a rejection: a caller must never mistake "the store failed" for "the
13
+ * object is gone". Only `ObjectStoreProvider.create` returns a `ProviderResult`, so a bad or missing
14
+ * setting is a refusal the host can report, not a boot crash.
15
+ */
16
+ import { type ProviderDefinition, type ProviderRegistry, type ProviderResult } from './provider-registry.js';
17
+ /** Registry contract name for object-store providers. */
18
+ export declare const OBJECT_STORE_CONTRACT = "object_store";
19
+ /** The built-in default provider id (local filesystem). */
20
+ export declare const DEFAULT_OBJECT_STORE_PROVIDER_ID = "local";
21
+ export interface ObjectStorePutOptions {
22
+ readonly contentType?: string;
23
+ /** Extra stored response headers (e.g. `content-disposition`); a provider may drop unsupported ones. */
24
+ readonly headers?: Readonly<Record<string, string>>;
25
+ /**
26
+ * Create-only write: lands `bytes` iff no object occupies `key`, atomically with respect to any
27
+ * other concurrent writer of the SAME key. No adapter may implement this as a separate existence
28
+ * check followed by a write — that pair is exactly the TOCTOU gap this flag exists to close.
29
+ */
30
+ readonly ifAbsent?: boolean;
31
+ readonly signal?: AbortSignal;
32
+ }
33
+ export interface ObjectStoreCallOptions {
34
+ readonly signal?: AbortSignal;
35
+ }
36
+ /** Anonymous read access, present only on a store whose objects are publicly reachable. */
37
+ export interface PublicObjectAccess {
38
+ /** The public URL a recipient fetches `key` at. */
39
+ url(required: {
40
+ readonly key: string;
41
+ }): string;
42
+ /** Anonymous read check: present with `bytes`, or a confirmed 404/410 when `present` is false. Errors are not absence. */
43
+ verify(required: {
44
+ readonly key: string;
45
+ readonly url: string;
46
+ readonly present: boolean;
47
+ readonly bytes?: number;
48
+ }, optional?: ObjectStoreCallOptions): Promise<boolean>;
49
+ }
50
+ export interface ObjectStore {
51
+ /** `written: false` only when `ifAbsent` was set and the key was already occupied. */
52
+ put(required: {
53
+ readonly key: string;
54
+ readonly bytes: Uint8Array;
55
+ }, optional?: ObjectStorePutOptions): Promise<{
56
+ readonly written: boolean;
57
+ }>;
58
+ /** `null` only when the key is absent; every other fault rejects. */
59
+ get(required: {
60
+ readonly key: string;
61
+ }, optional?: ObjectStoreCallOptions): Promise<Uint8Array | null>;
62
+ /** Byte count without reading the payload; `null` only when the key is absent. */
63
+ head(required: {
64
+ readonly key: string;
65
+ }, optional?: ObjectStoreCallOptions): Promise<{
66
+ readonly size: number;
67
+ } | null>;
68
+ /** Idempotent — deleting an absent key is not an error. */
69
+ delete(required: {
70
+ readonly key: string;
71
+ }, optional?: ObjectStoreCallOptions): Promise<void>;
72
+ readonly publicAccess?: PublicObjectAccess;
73
+ }
74
+ /** What a host hands a provider: non-secret settings, a pre-resolved credential, and its own kit. */
75
+ export interface ObjectStoreCreateInput<H = unknown> {
76
+ readonly settings: Readonly<Record<string, string | undefined>>;
77
+ /** Pre-resolved by the host (`fields` non-secret, `secret` the one secret value); never looked up by the provider. */
78
+ readonly credential?: {
79
+ readonly fields: Readonly<Record<string, string | undefined>>;
80
+ readonly secret: string;
81
+ };
82
+ /** Host capabilities a plain-JS plugin cannot import (signing, guarded fetch, observability). */
83
+ readonly host: H;
84
+ }
85
+ export interface ObjectStoreProvider<H = unknown> {
86
+ create(required: ObjectStoreCreateInput<H>, optional?: Record<string, never>): ProviderResult<ObjectStore>;
87
+ }
88
+ /**
89
+ * Shape check for a provider value, usable as a registry `validate` or a plugin module `validate`.
90
+ * @returns The reason it is not a provider, or `undefined`.
91
+ * @complexity O(1).
92
+ */
93
+ export declare function objectStoreProviderFault(required: {
94
+ readonly provider: unknown;
95
+ }): string | undefined;
96
+ /**
97
+ * A keyed registry for object-store providers whose default is the local filesystem provider.
98
+ * The `local` provider itself is node-only, so the host registers it (`seed` or `register`).
99
+ * @complexity O(seed).
100
+ */
101
+ export declare function createObjectStoreRegistry<H = unknown>(_required?: Record<string, never>, optional?: {
102
+ readonly seed?: readonly ProviderDefinition<ObjectStoreProvider<H>>[];
103
+ }): ProviderRegistry<ObjectStoreProvider<H>>;
104
+ /**
105
+ * An `ObjectStore` whose real store is resolved on first use — for hosts whose boot must hand out
106
+ * a store before the provider choice (a setting, an installed plugin) can be read. A successful
107
+ * load is kept; a failed load rejects that call and is retried by the next one, so a transient
108
+ * plugin or settings fault does not pin the store broken for the life of the process.
109
+ * `publicAccess` is not forwarded: a deferred store cannot know it synchronously.
110
+ * @complexity O(1) per call after the first successful load.
111
+ */
112
+ export declare function deferObjectStore(required: {
113
+ readonly load: () => Promise<ObjectStore>;
114
+ }, _optional?: Record<string, never>): ObjectStore;
115
+ //# sourceMappingURL=object-store.d.ts.map
@@ -0,0 +1,70 @@
1
+ /**
2
+ * @file The ONE object-storage contract (2026-10-21): key → bytes, with an optional public-read
3
+ * capability. It absorbs four overlapping shapes (cms `BlobStorePort`, capability-providers
4
+ * `StorageProvider`, devops `ArtifactStorePort`, platform `BlobStorage`); those stay as thin views
5
+ * over this one until their consumers move. Providers are keyed in a `createProviderRegistry`
6
+ * (`contract: 'object_store'`), default `local`.
7
+ *
8
+ * Universal: types and pure helpers only, so every storage package (all of which already depend
9
+ * on core) can implement it. Filesystem and network adapters live in node/host packages.
10
+ *
11
+ * Data-plane calls REJECT on a storage fault (as every existing byte port did), and report absence
12
+ * as `null`/`false`, never as a rejection: a caller must never mistake "the store failed" for "the
13
+ * object is gone". Only `ObjectStoreProvider.create` returns a `ProviderResult`, so a bad or missing
14
+ * setting is a refusal the host can report, not a boot crash.
15
+ */
16
+ import { createProviderRegistry } from './provider-registry.js';
17
+ /** Registry contract name for object-store providers. */
18
+ export const OBJECT_STORE_CONTRACT = 'object_store';
19
+ /** The built-in default provider id (local filesystem). */
20
+ export const DEFAULT_OBJECT_STORE_PROVIDER_ID = 'local';
21
+ /**
22
+ * Shape check for a provider value, usable as a registry `validate` or a plugin module `validate`.
23
+ * @returns The reason it is not a provider, or `undefined`.
24
+ * @complexity O(1).
25
+ */
26
+ export function objectStoreProviderFault(required) {
27
+ const { provider } = required;
28
+ if (typeof provider !== 'object' || provider === null)
29
+ return 'an object-store provider must be an object';
30
+ return typeof provider.create === 'function' ? undefined : 'an object-store provider must define create()';
31
+ }
32
+ /**
33
+ * A keyed registry for object-store providers whose default is the local filesystem provider.
34
+ * The `local` provider itself is node-only, so the host registers it (`seed` or `register`).
35
+ * @complexity O(seed).
36
+ */
37
+ export function createObjectStoreRegistry(_required = {}, optional = {}) {
38
+ return createProviderRegistry({ contract: OBJECT_STORE_CONTRACT }, {
39
+ defaultProviderId: DEFAULT_OBJECT_STORE_PROVIDER_ID,
40
+ validate: objectStoreProviderFault,
41
+ ...(optional.seed === undefined ? {} : { seed: optional.seed }),
42
+ });
43
+ }
44
+ /**
45
+ * An `ObjectStore` whose real store is resolved on first use — for hosts whose boot must hand out
46
+ * a store before the provider choice (a setting, an installed plugin) can be read. A successful
47
+ * load is kept; a failed load rejects that call and is retried by the next one, so a transient
48
+ * plugin or settings fault does not pin the store broken for the life of the process.
49
+ * `publicAccess` is not forwarded: a deferred store cannot know it synchronously.
50
+ * @complexity O(1) per call after the first successful load.
51
+ */
52
+ export function deferObjectStore(required, _optional = {}) {
53
+ let loaded;
54
+ const current = () => {
55
+ if (!loaded) {
56
+ const pending = required.load();
57
+ loaded = pending;
58
+ pending.catch(() => { if (loaded === pending)
59
+ loaded = undefined; });
60
+ }
61
+ return loaded;
62
+ };
63
+ return {
64
+ put: async (input, options) => (await current()).put(input, options),
65
+ get: async (input, options) => (await current()).get(input, options),
66
+ head: async (input, options) => (await current()).head(input, options),
67
+ delete: async (input, options) => (await current()).delete(input, options),
68
+ };
69
+ }
70
+ //# sourceMappingURL=object-store.js.map