@eik/core 2.1.75 → 2.1.77
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 +14 -0
- package/lib/handlers/map.put.js +23 -12
- package/lib/handlers/pkg.put.js +24 -12
- package/lib/handlers/versions.get.js +71 -45
- package/package.json +1 -1
- package/types/handlers/map.put.d.ts +3 -20
- package/types/handlers/pkg.put.d.ts +3 -20
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
## [2.1.77](https://github.com/eik-lib/core/compare/v2.1.76...v2.1.77) (2026-08-17)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* propagate sink errors in versions.get as 502 rather than 404 ([#636](https://github.com/eik-lib/core/issues/636)) ([21b8619](https://github.com/eik-lib/core/commit/21b8619eeff9150914adcfa2ae6a233e2c10ec60))
|
|
7
|
+
|
|
8
|
+
## [2.1.76](https://github.com/eik-lib/core/compare/v2.1.75...v2.1.76) (2026-08-14)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Bug Fixes
|
|
12
|
+
|
|
13
|
+
* propagate sink errors in _readVersions rather than silently resetting version history ([#635](https://github.com/eik-lib/core/issues/635)) ([b248bb7](https://github.com/eik-lib/core/commit/b248bb7b644ccd8fb7116157c44917b1ccf7990e))
|
|
14
|
+
|
|
1
15
|
## [2.1.75](https://github.com/eik-lib/core/compare/v2.1.74...v2.1.75) (2026-08-14)
|
|
2
16
|
|
|
3
17
|
|
package/lib/handlers/map.put.js
CHANGED
|
@@ -172,25 +172,32 @@ const MapPut = class MapPut {
|
|
|
172
172
|
|
|
173
173
|
/**
|
|
174
174
|
* @param {any} incoming
|
|
175
|
+
* @returns {Promise<InstanceType<typeof Versions>>}
|
|
175
176
|
*/
|
|
176
177
|
async _readVersions(incoming) {
|
|
177
178
|
const path = createFilePathToVersion(incoming);
|
|
178
|
-
|
|
179
|
+
|
|
180
|
+
if (!this._sink) throw new Error("No sink configured");
|
|
181
|
+
|
|
179
182
|
try {
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
this.
|
|
183
|
-
`map:put - Successfully read version meta file from sink - Pathname: ${path}`,
|
|
184
|
-
);
|
|
185
|
-
// eslint-disable-next-line no-unused-vars
|
|
186
|
-
} catch (error) {
|
|
187
|
-
// File does not exist, its probably a new package
|
|
188
|
-
versions = new Versions(incoming);
|
|
183
|
+
await this._sink.exist(path);
|
|
184
|
+
} catch {
|
|
185
|
+
// File does not exist — this is a new import map.
|
|
189
186
|
this._log.info(
|
|
190
187
|
`map:put - Version meta file did not exist in sink - Create new - Pathname: ${path}`,
|
|
191
188
|
);
|
|
189
|
+
return new Versions(incoming);
|
|
192
190
|
}
|
|
193
|
-
|
|
191
|
+
|
|
192
|
+
// File exists. Any failure here must propagate rather than be
|
|
193
|
+
// swallowed — silently falling back to an empty Versions object
|
|
194
|
+
// would cause _writeVersions to overwrite the index and erase the
|
|
195
|
+
// entire version history.
|
|
196
|
+
const obj = await readJSON(this._sink, path);
|
|
197
|
+
this._log.info(
|
|
198
|
+
`map:put - Successfully read version meta file from sink - Pathname: ${path}`,
|
|
199
|
+
);
|
|
200
|
+
return new Versions(obj);
|
|
194
201
|
}
|
|
195
202
|
|
|
196
203
|
/**
|
|
@@ -251,7 +258,11 @@ const MapPut = class MapPut {
|
|
|
251
258
|
org,
|
|
252
259
|
});
|
|
253
260
|
|
|
254
|
-
const versions = await this._readVersions(incoming)
|
|
261
|
+
const versions = await this._readVersions(incoming).catch(() => {
|
|
262
|
+
const e = new HttpError.BadGateway();
|
|
263
|
+
end({ labels: { success: false, status: e.status, type: "map" } });
|
|
264
|
+
throw e;
|
|
265
|
+
});
|
|
255
266
|
|
|
256
267
|
if (!versions.check(pVersion)) {
|
|
257
268
|
this._log.info(
|
package/lib/handlers/pkg.put.js
CHANGED
|
@@ -138,25 +138,32 @@ const PkgPut = class PkgPut {
|
|
|
138
138
|
|
|
139
139
|
/**
|
|
140
140
|
* @param {any} incoming
|
|
141
|
+
* @returns {Promise<InstanceType<typeof Versions>>}
|
|
141
142
|
*/
|
|
142
143
|
async _readVersions(incoming) {
|
|
143
144
|
const path = createFilePathToVersion(incoming);
|
|
144
|
-
|
|
145
|
+
|
|
146
|
+
if (!this._sink) throw new Error("No sink configured");
|
|
147
|
+
|
|
145
148
|
try {
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
this.
|
|
149
|
-
`pkg:put - Successfully read version meta file from sink - Pathname: ${path}`,
|
|
150
|
-
);
|
|
151
|
-
// eslint-disable-next-line no-unused-vars
|
|
152
|
-
} catch (error) {
|
|
153
|
-
// File does not exist, its probably a new package
|
|
154
|
-
versions = new Versions(incoming);
|
|
149
|
+
await this._sink.exist(path);
|
|
150
|
+
} catch {
|
|
151
|
+
// File does not exist — this is a new package.
|
|
155
152
|
this._log.info(
|
|
156
153
|
`pkg:put - Version meta file did not exist in sink - Create new - Pathname: ${path}`,
|
|
157
154
|
);
|
|
155
|
+
return new Versions(incoming);
|
|
158
156
|
}
|
|
159
|
-
|
|
157
|
+
|
|
158
|
+
// File exists. Any failure here (GCS error, corrupt JSON, network
|
|
159
|
+
// timeout) must propagate rather than be swallowed — silently falling
|
|
160
|
+
// back to an empty Versions object would cause _writeVersions to
|
|
161
|
+
// overwrite the index and erase the entire version history.
|
|
162
|
+
const obj = await readJSON(this._sink, path);
|
|
163
|
+
this._log.info(
|
|
164
|
+
`pkg:put - Successfully read version meta file from sink - Pathname: ${path}`,
|
|
165
|
+
);
|
|
166
|
+
return new Versions(obj);
|
|
160
167
|
}
|
|
161
168
|
|
|
162
169
|
/**
|
|
@@ -251,7 +258,12 @@ const PkgPut = class PkgPut {
|
|
|
251
258
|
throw e;
|
|
252
259
|
}
|
|
253
260
|
|
|
254
|
-
const versions = await this._readVersions(incoming)
|
|
261
|
+
const versions = await this._readVersions(incoming).catch(() => {
|
|
262
|
+
const e = new HttpError.BadGateway();
|
|
263
|
+
end({ labels: { success: false, status: e.status, type } });
|
|
264
|
+
throw e;
|
|
265
|
+
});
|
|
266
|
+
|
|
255
267
|
const { pkg, eikJson } = await this._parser(incoming);
|
|
256
268
|
versions.setVersion(pVersion, pkg.integrity);
|
|
257
269
|
|
|
@@ -81,57 +81,83 @@ const VersionsGet = class VersionsGet {
|
|
|
81
81
|
}
|
|
82
82
|
const path = createFilePathToVersion({ org, type, name: pName });
|
|
83
83
|
|
|
84
|
-
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
if (this._etag) {
|
|
91
|
-
outgoing.etag = file.etag;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
if (this._etag && req.headers["if-none-match"] === file.etag) {
|
|
95
|
-
outgoing.statusCode = 304;
|
|
96
|
-
file.stream.destroy();
|
|
97
|
-
} else {
|
|
98
|
-
outgoing.statusCode = 200;
|
|
99
|
-
outgoing.stream = file.stream;
|
|
100
|
-
|
|
101
|
-
const outgoingStream = outgoing.stream;
|
|
102
|
-
if (outgoingStream) {
|
|
103
|
-
outgoingStream.on(
|
|
104
|
-
"error",
|
|
105
|
-
(/** @type {NodeJS.ErrnoException} */ err) => {
|
|
106
|
-
// ERR_STREAM_PREMATURE_CLOSE means the client closed the
|
|
107
|
-
// connection before the transfer finished. This is normal
|
|
108
|
-
// browser behaviour (navigation, cancelled requests, etc.)
|
|
109
|
-
// and not a server-side error worth alerting on.
|
|
110
|
-
if (err.code === "ERR_STREAM_PREMATURE_CLOSE") {
|
|
111
|
-
this._log.debug(`pkg:latest - File stream error - ${err}`);
|
|
112
|
-
} else {
|
|
113
|
-
this._log.info(`pkg:latest - File stream error - ${err}`);
|
|
114
|
-
}
|
|
115
|
-
end({ labels: { success: false, status: 503, type } });
|
|
116
|
-
},
|
|
117
|
-
);
|
|
118
|
-
|
|
119
|
-
outgoingStream.on("end", () => {
|
|
120
|
-
end({ labels: { status: outgoing.statusCode, type } });
|
|
121
|
-
});
|
|
122
|
-
}
|
|
123
|
-
}
|
|
84
|
+
if (!this._sink) {
|
|
85
|
+
const e = new HttpError.NotFound();
|
|
86
|
+
end({ labels: { success: false, status: e.status, type } });
|
|
87
|
+
throw e;
|
|
88
|
+
}
|
|
124
89
|
|
|
125
|
-
|
|
90
|
+
// Check existence first to distinguish a genuinely missing package
|
|
91
|
+
// from a transient infrastructure error. Without this distinction,
|
|
92
|
+
// any GCS or network failure would surface as 404, which callers
|
|
93
|
+
// may misinterpret as "package does not exist".
|
|
94
|
+
try {
|
|
95
|
+
await this._sink.exist(path);
|
|
96
|
+
} catch {
|
|
97
|
+
this._log.debug(
|
|
98
|
+
`pkg:latest - Package versions not found - Pathname: ${path}`,
|
|
99
|
+
);
|
|
100
|
+
const e = new HttpError.NotFound();
|
|
101
|
+
end({ labels: { success: false, status: e.status, type } });
|
|
102
|
+
throw e;
|
|
103
|
+
}
|
|
126
104
|
|
|
127
|
-
|
|
128
|
-
|
|
105
|
+
// File exists — any read failure here is an infrastructure error.
|
|
106
|
+
let file;
|
|
107
|
+
try {
|
|
108
|
+
file = await this._sink.read(path);
|
|
129
109
|
} catch (error) {
|
|
130
|
-
this._log.
|
|
131
|
-
|
|
110
|
+
this._log.error(
|
|
111
|
+
`pkg:latest - Failed to read package versions from sink - Pathname: ${path}`,
|
|
112
|
+
);
|
|
113
|
+
this._log.trace(error);
|
|
114
|
+
const e = new HttpError.BadGateway();
|
|
132
115
|
end({ labels: { success: false, status: e.status, type } });
|
|
133
116
|
throw e;
|
|
134
117
|
}
|
|
118
|
+
|
|
119
|
+
const outgoing = new HttpOutgoing();
|
|
120
|
+
outgoing.cacheControl = this._cacheControl;
|
|
121
|
+
outgoing.mimeType = "application/json";
|
|
122
|
+
|
|
123
|
+
if (this._etag) {
|
|
124
|
+
outgoing.etag = file.etag;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (this._etag && req.headers["if-none-match"] === file.etag) {
|
|
128
|
+
outgoing.statusCode = 304;
|
|
129
|
+
file.stream.destroy();
|
|
130
|
+
} else {
|
|
131
|
+
outgoing.statusCode = 200;
|
|
132
|
+
outgoing.stream = file.stream;
|
|
133
|
+
|
|
134
|
+
const outgoingStream = outgoing.stream;
|
|
135
|
+
if (outgoingStream) {
|
|
136
|
+
outgoingStream.on(
|
|
137
|
+
"error",
|
|
138
|
+
(/** @type {NodeJS.ErrnoException} */ err) => {
|
|
139
|
+
// ERR_STREAM_PREMATURE_CLOSE means the client closed the
|
|
140
|
+
// connection before the transfer finished. This is normal
|
|
141
|
+
// browser behaviour (navigation, cancelled requests, etc.)
|
|
142
|
+
// and not a server-side error worth alerting on.
|
|
143
|
+
if (err.code === "ERR_STREAM_PREMATURE_CLOSE") {
|
|
144
|
+
this._log.debug(`pkg:latest - File stream error - ${err}`);
|
|
145
|
+
} else {
|
|
146
|
+
this._log.info(`pkg:latest - File stream error - ${err}`);
|
|
147
|
+
}
|
|
148
|
+
end({ labels: { success: false, status: 503, type } });
|
|
149
|
+
},
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
outgoingStream.on("end", () => {
|
|
153
|
+
end({ labels: { status: outgoing.statusCode, type } });
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
this._log.debug(`pkg:latest - Package versions found - Pathname: ${path}`);
|
|
159
|
+
|
|
160
|
+
return outgoing;
|
|
135
161
|
}
|
|
136
162
|
};
|
|
137
163
|
export default VersionsGet;
|
package/package.json
CHANGED
|
@@ -42,27 +42,9 @@ declare const MapPut: {
|
|
|
42
42
|
}): Promise<string>;
|
|
43
43
|
/**
|
|
44
44
|
* @param {any} incoming
|
|
45
|
+
* @returns {Promise<InstanceType<typeof Versions>>}
|
|
45
46
|
*/
|
|
46
|
-
_readVersions(incoming: any): Promise<
|
|
47
|
-
_versions: Map<any, any>;
|
|
48
|
-
_type: string;
|
|
49
|
-
_name: string;
|
|
50
|
-
_org: string;
|
|
51
|
-
get versions(): [any, any][];
|
|
52
|
-
get type(): string;
|
|
53
|
-
get name(): string;
|
|
54
|
-
get org(): string;
|
|
55
|
-
setVersion(version: string, integrity: string): void;
|
|
56
|
-
getVersion(major: number): any;
|
|
57
|
-
check(version: string): boolean;
|
|
58
|
-
toJSON(): {
|
|
59
|
-
versions: [any, any][];
|
|
60
|
-
type: string;
|
|
61
|
-
name: string;
|
|
62
|
-
org: string;
|
|
63
|
-
};
|
|
64
|
-
get [Symbol.toStringTag](): string;
|
|
65
|
-
}>;
|
|
47
|
+
_readVersions(incoming: any): Promise<InstanceType<typeof Versions>>;
|
|
66
48
|
/**
|
|
67
49
|
* @param {any} incoming
|
|
68
50
|
* @param {any} versions
|
|
@@ -102,3 +84,4 @@ declare const MapPut: {
|
|
|
102
84
|
};
|
|
103
85
|
import abslog from "abslog";
|
|
104
86
|
import Metrics from "@metrics/client";
|
|
87
|
+
import Versions from "../classes/versions.js";
|
|
@@ -78,27 +78,9 @@ declare const PkgPut: {
|
|
|
78
78
|
_writeEikJson(incoming: any, capturedEikJson: Record<string, any> | null): Promise<void>;
|
|
79
79
|
/**
|
|
80
80
|
* @param {any} incoming
|
|
81
|
+
* @returns {Promise<InstanceType<typeof Versions>>}
|
|
81
82
|
*/
|
|
82
|
-
_readVersions(incoming: any): Promise<
|
|
83
|
-
_versions: Map<any, any>;
|
|
84
|
-
_type: string;
|
|
85
|
-
_name: string;
|
|
86
|
-
_org: string;
|
|
87
|
-
get versions(): [any, any][];
|
|
88
|
-
get type(): string;
|
|
89
|
-
get name(): string;
|
|
90
|
-
get org(): string;
|
|
91
|
-
setVersion(version: string, integrity: string): void;
|
|
92
|
-
getVersion(major: number): any;
|
|
93
|
-
check(version: string): boolean;
|
|
94
|
-
toJSON(): {
|
|
95
|
-
versions: [any, any][];
|
|
96
|
-
type: string;
|
|
97
|
-
name: string;
|
|
98
|
-
org: string;
|
|
99
|
-
};
|
|
100
|
-
get [Symbol.toStringTag](): string;
|
|
101
|
-
}>;
|
|
83
|
+
_readVersions(incoming: any): Promise<InstanceType<typeof Versions>>;
|
|
102
84
|
/**
|
|
103
85
|
* @param {any} incoming
|
|
104
86
|
*/
|
|
@@ -143,3 +125,4 @@ declare const PkgPut: {
|
|
|
143
125
|
};
|
|
144
126
|
import abslog from "abslog";
|
|
145
127
|
import Metrics from "@metrics/client";
|
|
128
|
+
import Versions from "../classes/versions.js";
|