@jini-ai/core 0.4.3 → 0.4.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # @jini-ai/core
2
2
 
3
+ ## 0.4.5 — 2026-10-13
4
+
5
+ - `FetchQueryCache`: a `replace()` that lands after a read's transport resolves but before publication now wins; arbitration and publication share one continuation.
6
+
7
+ ## 0.4.4 — 2026-10-12
8
+
9
+ - New `./records` subpath exports `isRecord` and `isNonArrayRecord` without loading the core barrel; the root and `registration-kit` keep re-exporting both.
10
+
3
11
  ## 0.4.2 — pending release
4
12
 
5
13
  - Add source-backed `./plugin-directory` publisher identity and live metadata lookup. Warn and omit invalid advisory keywords; scope legacy `unknown/<name>` IDs to each plugin family, with optional family selection for ambiguous lookups.
@@ -91,20 +91,26 @@ export class FetchQueryCache {
91
91
  this.publish(entry, { ...entry.state, isFetching: false });
92
92
  return this.runOnline({ run });
93
93
  }).then(value => {
94
- const data = replacement === entry.replacement ? value : entry.state.data;
95
94
  // Undefined is never successful query data, including an initial or background read.
96
- if (data === undefined)
95
+ // Thrown here so the rejection path below handles it (a replacement still supersedes it).
96
+ if (value === undefined)
97
97
  throw new Error(`${JSON.stringify(entry.key)} data is undefined`);
98
- return data;
99
- }).then(data => {
98
+ return value;
99
+ }).then(value => {
100
100
  // Only the current request may publish or clear invalidation. An older transport
101
101
  // need not support abort: its callers follow the replacement request instead.
102
102
  if (entry.pending !== pending)
103
103
  return (entry.pending ?? entry.state.data);
104
+ // Arbitration and publication share this continuation: a microtask between them would
105
+ // let an older result overwrite a replacement accepted after arbitration.
106
+ if (replacement !== entry.replacement) {
107
+ this.publish(entry, { ...entry.state, isFetching: false });
108
+ return entry.state.data;
109
+ }
104
110
  entry.updatedAt = this.clock.nowMs();
105
111
  entry.invalidated = false;
106
- this.publish(entry, { data, error: null, status: 'success', isFetching: false });
107
- return data;
112
+ this.publish(entry, { data: value, error: null, status: 'success', isFetching: false });
113
+ return value;
108
114
  }, error => {
109
115
  if (entry.pending !== pending)
110
116
  return (entry.pending ?? entry.state.data);
@@ -0,0 +1,24 @@
1
+ /** @file Pure object-shape predicates shared by tool input readers and render-time decoders. */
2
+ /** Accept any non-null object, including arrays; never sanitizes or reads properties.
3
+ * @complexity O(1) time and space.
4
+ * @example isRecord({ value: [] }); // true
5
+ */
6
+ export declare function isRecord(required: {
7
+ value: unknown;
8
+ }, _optional?: Record<string, never>): required is {
9
+ value: Record<string, unknown>;
10
+ };
11
+ /**
12
+ * Accepts non-null, non-array objects, including class instances and null-prototype records.
13
+ * This is a shape check, not plain-object validation or sanitization. Keep `isRecord`'s
14
+ * broader contract: existing tool readers deliberately continue to accept arrays.
15
+ * @param required.value - The untrusted value whose object shape is checked.
16
+ * @complexity O(1) time and space.
17
+ * @example isNonArrayRecord({ value: [] }); // false
18
+ */
19
+ export declare function isNonArrayRecord(required: {
20
+ value: unknown;
21
+ }, _optional?: Record<string, never>): required is {
22
+ value: Record<string, unknown>;
23
+ };
24
+ //# sourceMappingURL=records.d.ts.map
@@ -0,0 +1,21 @@
1
+ /** @file Pure object-shape predicates shared by tool input readers and render-time decoders. */
2
+ /** Accept any non-null object, including arrays; never sanitizes or reads properties.
3
+ * @complexity O(1) time and space.
4
+ * @example isRecord({ value: [] }); // true
5
+ */
6
+ export function isRecord(required, _optional = {}) {
7
+ const { value } = required;
8
+ return typeof value === "object" && value !== null;
9
+ }
10
+ /**
11
+ * Accepts non-null, non-array objects, including class instances and null-prototype records.
12
+ * This is a shape check, not plain-object validation or sanitization. Keep `isRecord`'s
13
+ * broader contract: existing tool readers deliberately continue to accept arrays.
14
+ * @param required.value - The untrusted value whose object shape is checked.
15
+ * @complexity O(1) time and space.
16
+ * @example isNonArrayRecord({ value: [] }); // false
17
+ */
18
+ export function isNonArrayRecord(required, _optional = {}) {
19
+ return isRecord(required) && !Array.isArray(required.value);
20
+ }
21
+ //# sourceMappingURL=records.js.map
@@ -1,3 +1,4 @@
1
+ export { isRecord, isNonArrayRecord } from "./records.js";
1
2
  /**
2
3
  * @file The cross-domain half of the agent-tool wiring split: everything a domain's own
3
4
  * `tool-registrations.ts` needs that is NOT specific to that domain.
@@ -134,22 +135,6 @@ export declare function indexCatalogById<T extends {
134
135
  }>(required: {
135
136
  catalog: readonly T[];
136
137
  }, _optional?: Record<string, never>): ReadonlyMap<string, T>;
137
- export declare function isRecord(required: {
138
- value: unknown;
139
- }, _optional?: Record<string, never>): required is {
140
- value: Record<string, unknown>;
141
- };
142
- /**
143
- * Accepts non-null, non-array objects, including class instances and null-prototype records.
144
- * This is a shape check, not plain-object validation or sanitization. Keep `isRecord`'s
145
- * broader contract: existing tool readers deliberately continue to accept arrays.
146
- * @param required.value - The untrusted value whose object shape is checked.
147
- */
148
- export declare function isNonArrayRecord(required: {
149
- value: unknown;
150
- }, _optional?: Record<string, never>): required is {
151
- value: Record<string, unknown>;
152
- };
153
138
  /** Narrows `ctx.input` to a record, refusing anything else. The first line of most handlers. */
154
139
  export declare function requireInputRecord(required: {
155
140
  input: unknown;
@@ -1,3 +1,5 @@
1
+ import { isRecord } from "./records.js";
2
+ export { isRecord, isNonArrayRecord } from "./records.js";
1
3
  /**
2
4
  * @file The cross-domain half of the agent-tool wiring split: everything a domain's own
3
5
  * `tool-registrations.ts` needs that is NOT specific to that domain.
@@ -100,19 +102,6 @@ export function indexCatalogById(required, _optional = {}) {
100
102
  // ---------------------------------------------------------------------------
101
103
  // Input readers — the vocabulary every handler uses to read `ctx.input`.
102
104
  // ---------------------------------------------------------------------------
103
- export function isRecord(required, _optional = {}) {
104
- const { value } = required;
105
- return typeof value === "object" && value !== null;
106
- }
107
- /**
108
- * Accepts non-null, non-array objects, including class instances and null-prototype records.
109
- * This is a shape check, not plain-object validation or sanitization. Keep `isRecord`'s
110
- * broader contract: existing tool readers deliberately continue to accept arrays.
111
- * @param required.value - The untrusted value whose object shape is checked.
112
- */
113
- export function isNonArrayRecord(required, _optional = {}) {
114
- return isRecord(required) && !Array.isArray(required.value);
115
- }
116
105
  /** Narrows `ctx.input` to a record, refusing anything else. The first line of most handlers. */
117
106
  export function requireInputRecord(required, _optional = {}) {
118
107
  const { input } = required;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jini-ai/core",
3
- "version": "0.4.3",
3
+ "version": "0.4.5",
4
4
  "description": "The Jini kernel: typed DI tokens, pack composition and lifecycle, the tool registry, redaction, and origin/API-token auth primitives. Zero dependencies.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -19,6 +19,7 @@
19
19
  "./model-facing-tool-errors": "universal",
20
20
  "./contribution-registry": "universal",
21
21
  "./naming": "universal",
22
+ "./records": "universal",
22
23
  "./primitives": "universal",
23
24
  "./fetch": "universal",
24
25
  "./text": "universal",
@@ -42,7 +43,8 @@
42
43
  ]
43
44
  },
44
45
  "shared": [
45
- "src/tool-permissions.ts"
46
+ "src/tool-permissions.ts",
47
+ "src/records.ts"
46
48
  ],
47
49
  "forbidden": {
48
50
  "universal": [
@@ -101,6 +103,11 @@
101
103
  "import": "./dist/naming.js",
102
104
  "default": "./dist/naming.js"
103
105
  },
106
+ "./records": {
107
+ "types": "./dist/records.d.ts",
108
+ "import": "./dist/records.js",
109
+ "default": "./dist/records.js"
110
+ },
104
111
  "./primitives": {
105
112
  "types": "./dist/primitives/index.d.ts",
106
113
  "import": "./dist/primitives/index.js",