@oino-ts/blob 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/OINOBlob.js +194 -0
- package/dist/cjs/OINOBlobApi.js +180 -0
- package/dist/cjs/OINOBlobConstants.js +7 -0
- package/dist/cjs/OINOBlobDataModel.js +69 -0
- package/dist/cjs/OINOBlobFactory.js +71 -0
- package/dist/cjs/index.js +12 -0
- package/dist/esm/OINOBlob.js +190 -0
- package/dist/esm/OINOBlobApi.js +175 -0
- package/dist/esm/OINOBlobConstants.js +6 -0
- package/dist/esm/OINOBlobDataModel.js +65 -0
- package/dist/esm/OINOBlobFactory.js +67 -0
- package/dist/esm/index.js +4 -0
- package/dist/types/OINOBlob.d.ts +78 -0
- package/dist/types/OINOBlobApi.d.ts +49 -0
- package/dist/types/OINOBlobConstants.d.ts +37 -0
- package/dist/types/OINOBlobDataModel.d.ts +33 -0
- package/dist/types/OINOBlobFactory.d.ts +40 -0
- package/dist/types/index.d.ts +5 -0
- package/package.json +37 -0
- package/src/OINOBlob.ts +238 -0
- package/src/OINOBlobApi.test.ts +393 -0
- package/src/OINOBlobApi.ts +220 -0
- package/src/OINOBlobConstants.ts +47 -0
- package/src/OINOBlobDataModel.ts +66 -0
- package/src/OINOBlobFactory.ts +80 -0
- package/src/index.ts +5 -0
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
4
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
5
|
+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.OINOBlob = void 0;
|
|
9
|
+
const common_1 = require("@oino-ts/common");
|
|
10
|
+
const BLOB_LIKE_ESCAPE_REGEX = /[.*+?^${}()|[\]\\]/g;
|
|
11
|
+
const BLOB_LIKE_PERCENT_REGEX = /%/g;
|
|
12
|
+
const BLOB_LIKE_UNDERSCORE_REGEX = /_/g;
|
|
13
|
+
/**
|
|
14
|
+
* Abstract base class for blob storage backends. Subclasses implement
|
|
15
|
+
* the two core operations (`listEntries` and `fetchEntry`) for a specific
|
|
16
|
+
* provider (e.g. Azure Blob Storage, S3, …).
|
|
17
|
+
*
|
|
18
|
+
* The SQL-formatting methods inherited from `OINODataSource` are not used
|
|
19
|
+
* by blob operations; they are implemented here as passthrough stubs so
|
|
20
|
+
* that the blob datasource can still be composed with `OINODataField`.
|
|
21
|
+
*/
|
|
22
|
+
class OINOBlob extends common_1.OINODataSource {
|
|
23
|
+
blobParams;
|
|
24
|
+
/** Container / bucket name */
|
|
25
|
+
name;
|
|
26
|
+
/**
|
|
27
|
+
* Constructor for `OINOBlob`.
|
|
28
|
+
* @param params blob storage connection parameters
|
|
29
|
+
*/
|
|
30
|
+
constructor(params) {
|
|
31
|
+
super();
|
|
32
|
+
this.blobParams = { ...params };
|
|
33
|
+
this.name = this.blobParams.container;
|
|
34
|
+
}
|
|
35
|
+
// ── OINODataSource passthrough stubs ──────────────────────────────────
|
|
36
|
+
// These are required by the abstract base class but are not meaningful
|
|
37
|
+
// for blob storage. They return sensible no-op values so that
|
|
38
|
+
// OINODataField instances created by OINOBlobDataModel can function
|
|
39
|
+
// correctly for serialisation purposes.
|
|
40
|
+
printTableName(name) {
|
|
41
|
+
return name;
|
|
42
|
+
}
|
|
43
|
+
printColumnName(name) {
|
|
44
|
+
return name;
|
|
45
|
+
}
|
|
46
|
+
printCellAsValue(cellValue, _sqlType) {
|
|
47
|
+
if (cellValue === null || cellValue === undefined) {
|
|
48
|
+
return "";
|
|
49
|
+
}
|
|
50
|
+
if (cellValue instanceof Date) {
|
|
51
|
+
return cellValue.toISOString();
|
|
52
|
+
}
|
|
53
|
+
return String(cellValue);
|
|
54
|
+
}
|
|
55
|
+
printStringValue(s) {
|
|
56
|
+
return s;
|
|
57
|
+
}
|
|
58
|
+
parseValueAsCell(v, nativeType) {
|
|
59
|
+
if (nativeType === "DATETIME" && typeof v === "string" && v !== "") {
|
|
60
|
+
return new Date(v);
|
|
61
|
+
}
|
|
62
|
+
return v;
|
|
63
|
+
}
|
|
64
|
+
// ── Blob-specific filter helper ───────────────────────────────────────
|
|
65
|
+
/**
|
|
66
|
+
* Test whether a blob entry matches an `OINOQueryFilter` predicate.
|
|
67
|
+
* Used for in-memory (result) filtering when the storage backend cannot
|
|
68
|
+
* translate the predicate to a native query.
|
|
69
|
+
*
|
|
70
|
+
* @param entry blob entry to test
|
|
71
|
+
* @param filter filter predicate to evaluate
|
|
72
|
+
*/
|
|
73
|
+
static matchesEntry(entry, filter) {
|
|
74
|
+
if (filter.isEmpty())
|
|
75
|
+
return true;
|
|
76
|
+
const op = filter.operator;
|
|
77
|
+
if (op === common_1.OINOQueryBooleanOperation.and) {
|
|
78
|
+
return OINOBlob.matchesEntry(entry, filter.leftSide) &&
|
|
79
|
+
OINOBlob.matchesEntry(entry, filter.rightSide);
|
|
80
|
+
}
|
|
81
|
+
if (op === common_1.OINOQueryBooleanOperation.or) {
|
|
82
|
+
return OINOBlob.matchesEntry(entry, filter.leftSide) ||
|
|
83
|
+
OINOBlob.matchesEntry(entry, filter.rightSide);
|
|
84
|
+
}
|
|
85
|
+
if (op === common_1.OINOQueryBooleanOperation.not) {
|
|
86
|
+
return !OINOBlob.matchesEntry(entry, filter.rightSide);
|
|
87
|
+
}
|
|
88
|
+
const fieldName = filter.leftSide;
|
|
89
|
+
const compareValue = filter.rightSide;
|
|
90
|
+
let fieldValue;
|
|
91
|
+
switch (fieldName) {
|
|
92
|
+
case "name":
|
|
93
|
+
fieldValue = entry.name;
|
|
94
|
+
break;
|
|
95
|
+
case "etag":
|
|
96
|
+
fieldValue = entry.etag;
|
|
97
|
+
break;
|
|
98
|
+
case "lastModified":
|
|
99
|
+
fieldValue = entry.lastModified;
|
|
100
|
+
break;
|
|
101
|
+
case "contentLength":
|
|
102
|
+
fieldValue = entry.contentLength;
|
|
103
|
+
break;
|
|
104
|
+
case "contentType":
|
|
105
|
+
fieldValue = entry.contentType;
|
|
106
|
+
break;
|
|
107
|
+
default: return true;
|
|
108
|
+
}
|
|
109
|
+
if (op === common_1.OINOQueryNullCheck.isnull)
|
|
110
|
+
return fieldValue === null;
|
|
111
|
+
if (op === common_1.OINOQueryNullCheck.isNotNull)
|
|
112
|
+
return fieldValue !== null;
|
|
113
|
+
if (fieldValue === null)
|
|
114
|
+
return false;
|
|
115
|
+
if (fieldValue instanceof Date) {
|
|
116
|
+
const ms = fieldValue.getTime();
|
|
117
|
+
const cmpMs = new Date(compareValue).getTime();
|
|
118
|
+
switch (op) {
|
|
119
|
+
case common_1.OINOQueryComparison.lt: return ms < cmpMs;
|
|
120
|
+
case common_1.OINOQueryComparison.le: return ms <= cmpMs;
|
|
121
|
+
case common_1.OINOQueryComparison.eq: return ms === cmpMs;
|
|
122
|
+
case common_1.OINOQueryComparison.ne: return ms !== cmpMs;
|
|
123
|
+
case common_1.OINOQueryComparison.ge: return ms >= cmpMs;
|
|
124
|
+
case common_1.OINOQueryComparison.gt: return ms > cmpMs;
|
|
125
|
+
default: return true;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
if (typeof fieldValue === "number") {
|
|
129
|
+
const cmpNum = Number(compareValue);
|
|
130
|
+
switch (op) {
|
|
131
|
+
case common_1.OINOQueryComparison.lt: return fieldValue < cmpNum;
|
|
132
|
+
case common_1.OINOQueryComparison.le: return fieldValue <= cmpNum;
|
|
133
|
+
case common_1.OINOQueryComparison.eq: return fieldValue === cmpNum;
|
|
134
|
+
case common_1.OINOQueryComparison.ne: return fieldValue !== cmpNum;
|
|
135
|
+
case common_1.OINOQueryComparison.ge: return fieldValue >= cmpNum;
|
|
136
|
+
case common_1.OINOQueryComparison.gt: return fieldValue > cmpNum;
|
|
137
|
+
default: return true;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
const strValue = String(fieldValue);
|
|
141
|
+
switch (op) {
|
|
142
|
+
case common_1.OINOQueryComparison.lt: return strValue < compareValue;
|
|
143
|
+
case common_1.OINOQueryComparison.le: return strValue <= compareValue;
|
|
144
|
+
case common_1.OINOQueryComparison.eq: return strValue === compareValue;
|
|
145
|
+
case common_1.OINOQueryComparison.ne: return strValue !== compareValue;
|
|
146
|
+
case common_1.OINOQueryComparison.ge: return strValue >= compareValue;
|
|
147
|
+
case common_1.OINOQueryComparison.gt: return strValue > compareValue;
|
|
148
|
+
case common_1.OINOQueryComparison.like: {
|
|
149
|
+
const escaped = compareValue
|
|
150
|
+
.replace(BLOB_LIKE_ESCAPE_REGEX, "\\$&")
|
|
151
|
+
.replace(BLOB_LIKE_PERCENT_REGEX, ".*")
|
|
152
|
+
.replace(BLOB_LIKE_UNDERSCORE_REGEX, ".");
|
|
153
|
+
return new RegExp("^" + escaped + "$", "i").test(strValue);
|
|
154
|
+
}
|
|
155
|
+
default: return true;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Extract a blob/object name prefix from the filter that can be forwarded
|
|
160
|
+
* to the storage backend as a server-side query optimisation.
|
|
161
|
+
*
|
|
162
|
+
* Only two cases translate to a prefix:
|
|
163
|
+
* - `(name)-eq(value)` → exact name match (use as prefix)
|
|
164
|
+
* - `(name)-like(prefix%)` → trailing-wildcard prefix match
|
|
165
|
+
*
|
|
166
|
+
* AND-combined filters are explored recursively so that a name constraint
|
|
167
|
+
* nested inside a larger AND predicate is still extracted.
|
|
168
|
+
*
|
|
169
|
+
* @param filter filter to inspect
|
|
170
|
+
*/
|
|
171
|
+
static extractNamePrefix(filter) {
|
|
172
|
+
if (filter.isEmpty())
|
|
173
|
+
return undefined;
|
|
174
|
+
const op = filter.operator;
|
|
175
|
+
if (typeof filter.leftSide === "string" && filter.leftSide === "name") {
|
|
176
|
+
if (op === common_1.OINOQueryComparison.eq) {
|
|
177
|
+
return filter.rightSide;
|
|
178
|
+
}
|
|
179
|
+
if (op === common_1.OINOQueryComparison.like) {
|
|
180
|
+
const pattern = filter.rightSide;
|
|
181
|
+
const body = pattern.slice(0, -1);
|
|
182
|
+
if (pattern.endsWith("%") && !body.includes("%") && !body.includes("_")) {
|
|
183
|
+
return body;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
if (op === common_1.OINOQueryBooleanOperation.and) {
|
|
188
|
+
return OINOBlob.extractNamePrefix(filter.leftSide) ??
|
|
189
|
+
OINOBlob.extractNamePrefix(filter.rightSide);
|
|
190
|
+
}
|
|
191
|
+
return undefined;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
exports.OINOBlob = OINOBlob;
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
4
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
5
|
+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.OINOBlobApi = exports.OINOBlobApiResult = void 0;
|
|
9
|
+
const common_1 = require("@oino-ts/common");
|
|
10
|
+
class OINOBlobApiResult extends common_1.OINOApiResult {
|
|
11
|
+
/** Binary content of the blob (for GET with id) */
|
|
12
|
+
blobData;
|
|
13
|
+
/** Content-Type of the blob (for GET with id) */
|
|
14
|
+
blobDataType;
|
|
15
|
+
constructor(request, data, blobData, blobDataType) {
|
|
16
|
+
super(request, data);
|
|
17
|
+
this.blobData = blobData;
|
|
18
|
+
this.blobDataType = blobDataType;
|
|
19
|
+
}
|
|
20
|
+
async writeApiResponse(headers = {}) {
|
|
21
|
+
if (this.blobData) {
|
|
22
|
+
const response_headers = new Headers(headers);
|
|
23
|
+
response_headers.set("Content-Length", this.blobData.length.toString());
|
|
24
|
+
if (this.blobDataType) {
|
|
25
|
+
response_headers.set("Content-Type", this.blobDataType);
|
|
26
|
+
}
|
|
27
|
+
if (this.request.responseDownload) {
|
|
28
|
+
response_headers.set("Content-Disposition", `attachment; filename="${this.request.responseDownload}"`);
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
response_headers.set("Content-Disposition", "inline");
|
|
32
|
+
}
|
|
33
|
+
return new Response(this.blobData, {
|
|
34
|
+
status: this.status,
|
|
35
|
+
statusText: this.statusText,
|
|
36
|
+
headers: response_headers
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
return super.writeApiResponse(headers);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
exports.OINOBlobApiResult = OINOBlobApiResult;
|
|
45
|
+
/**
|
|
46
|
+
* REST API for blob storage.
|
|
47
|
+
*
|
|
48
|
+
* Supports two GET variants:
|
|
49
|
+
* - **GET without id** – lists all blobs under the configured prefix and
|
|
50
|
+
* returns the metadata as JSON (or CSV) using `OINOModelSet`.
|
|
51
|
+
* - **GET with id** – downloads the named blob as a binary HTTP response
|
|
52
|
+
* with the blob's own `Content-Type`.
|
|
53
|
+
*
|
|
54
|
+
* All other HTTP methods return `405 Method Not Allowed`.
|
|
55
|
+
*/
|
|
56
|
+
class OINOBlobApi extends common_1.OINOApi {
|
|
57
|
+
/** Blob storage backend */
|
|
58
|
+
blob;
|
|
59
|
+
/** Blob-specific data model (populated by `initializeDatamodel`) */
|
|
60
|
+
blobDatamodel = null;
|
|
61
|
+
/**
|
|
62
|
+
* Constructor.
|
|
63
|
+
*
|
|
64
|
+
* NOTE: `initializeDatamodel` (or `OINOBlobFactory.createApi`) must be
|
|
65
|
+
* called before the first request is dispatched.
|
|
66
|
+
*
|
|
67
|
+
* @param blob blob storage backend
|
|
68
|
+
* @param params API parameters (`tableName` is used as the blob prefix)
|
|
69
|
+
*/
|
|
70
|
+
constructor(blob, params) {
|
|
71
|
+
super(blob, params);
|
|
72
|
+
this.blob = blob;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Attach the static blob data model and mark the API as initialised.
|
|
76
|
+
*
|
|
77
|
+
* @param datamodel `OINOBlobDataModel` instance for this API
|
|
78
|
+
*/
|
|
79
|
+
initializeDatamodel(datamodel) {
|
|
80
|
+
this.blobDatamodel = datamodel;
|
|
81
|
+
this.datamodel = datamodel;
|
|
82
|
+
this.initialized = true;
|
|
83
|
+
}
|
|
84
|
+
// ── OINOApi abstract implementations ─────────────────────────────────
|
|
85
|
+
async doApiRequest(request) {
|
|
86
|
+
if (!this.initialized) {
|
|
87
|
+
throw new Error(common_1.OINO_ERROR_PREFIX + ": OINOBlobApi is not initialized yet!");
|
|
88
|
+
}
|
|
89
|
+
common_1.OINOLog.debug("@oino-ts/blob", "OINOBlobApi", "doApiRequest", "Request", { method: request.method, id: request.rowId });
|
|
90
|
+
const result = new OINOBlobApiResult(request);
|
|
91
|
+
if (request.method === "GET") {
|
|
92
|
+
if (!request.rowId) {
|
|
93
|
+
// ── List blobs ───────────────────────────────────────────────
|
|
94
|
+
try {
|
|
95
|
+
const entries = await this.blob.listEntries(request.queryParams?.filter);
|
|
96
|
+
const dataset = this.blobDatamodel.entriesToDataset(entries);
|
|
97
|
+
result.data = new common_1.OINOModelSet(this.datamodel, dataset, request.queryParams);
|
|
98
|
+
}
|
|
99
|
+
catch (e) {
|
|
100
|
+
result.setError(500, "Error listing blobs: " + e.message, "DoGet");
|
|
101
|
+
common_1.OINOLog.exception("@oino-ts/blob", "OINOBlobApi", "doApiRequest", "exception in list request", { message: e.message, stack: e.stack });
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
// ── Download blob ────────────────────────────────────────────
|
|
106
|
+
try {
|
|
107
|
+
const name = decodeURIComponent(request.rowId);
|
|
108
|
+
const fetch_result = await this.blob.fetchEntry(name);
|
|
109
|
+
result.blobData = fetch_result.content;
|
|
110
|
+
result.blobDataType = fetch_result.contentType;
|
|
111
|
+
}
|
|
112
|
+
catch (e) {
|
|
113
|
+
result.setError(500, "Error fetching blob: " + e.message, "DoGet");
|
|
114
|
+
common_1.OINOLog.exception("@oino-ts/blob", "OINOBlobApi", "doApiRequest", "exception in fetch request", { message: e.message, stack: e.stack });
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
else if (request.method === "POST" || request.method === "PUT") {
|
|
119
|
+
if (!request.rowId) {
|
|
120
|
+
result.setError(400, "HTTP " + request.method + " method requires an URL ID (blob name)!", "DoRequest");
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
try {
|
|
124
|
+
const name = decodeURIComponent(request.rowId);
|
|
125
|
+
const content_type = request.headers.get("content-type") ?? "application/octet-stream";
|
|
126
|
+
const data = request.rowData;
|
|
127
|
+
const content = data instanceof Uint8Array ? data : request.bodyAsBuffer();
|
|
128
|
+
await this.blob.uploadEntry(name, content, content_type);
|
|
129
|
+
}
|
|
130
|
+
catch (e) {
|
|
131
|
+
result.setError(500, "Error uploading blob: " + e.message, "DoPost");
|
|
132
|
+
common_1.OINOLog.exception("@oino-ts/blob", "OINOBlobApi", "doApiRequest", "exception in upload request", { message: e.message, stack: e.stack });
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
else if (request.method === "DELETE") {
|
|
137
|
+
if (!request.rowId) {
|
|
138
|
+
result.setError(400, "HTTP DELETE method requires an URL ID (blob name)!", "DoRequest");
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
try {
|
|
142
|
+
const name = decodeURIComponent(request.rowId);
|
|
143
|
+
await this.blob.deleteEntry(name);
|
|
144
|
+
}
|
|
145
|
+
catch (e) {
|
|
146
|
+
result.setError(500, "Error deleting blob: " + e.message, "DoDelete");
|
|
147
|
+
common_1.OINOLog.exception("@oino-ts/blob", "OINOBlobApi", "doApiRequest", "exception in delete request", { message: e.message, stack: e.stack });
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
result.setError(405, "Unsupported HTTP method '" + request.method + "' for OINOBlobApi", "DoRequest");
|
|
153
|
+
}
|
|
154
|
+
return result;
|
|
155
|
+
}
|
|
156
|
+
async doHttpRequest(request, rowId, rowData, queryParams) {
|
|
157
|
+
const api_request = common_1.OINOApiRequest.fromHttpRequest(request, rowId, rowData, queryParams);
|
|
158
|
+
return this.doApiRequest(api_request);
|
|
159
|
+
}
|
|
160
|
+
async doRequest(method, rowId, rowData, queryParams, contentType = common_1.OINOContentType.json) {
|
|
161
|
+
return this.doApiRequest(new common_1.OINOApiRequest({
|
|
162
|
+
method,
|
|
163
|
+
rowId,
|
|
164
|
+
rowData,
|
|
165
|
+
queryParams,
|
|
166
|
+
requestType: contentType
|
|
167
|
+
}));
|
|
168
|
+
}
|
|
169
|
+
async doBatchUpdate(method, _rowId, _rowData, _queryParams) {
|
|
170
|
+
const result = new common_1.OINOApiResult(new common_1.OINOApiRequest({ method }));
|
|
171
|
+
result.setError(405, "OINOBlobApi does not support batch updates", "DoBatchUpdate");
|
|
172
|
+
return result;
|
|
173
|
+
}
|
|
174
|
+
async doBatchApiRequest(request) {
|
|
175
|
+
const result = new common_1.OINOApiResult(request);
|
|
176
|
+
result.setError(405, "OINOBlobApi does not support batch updates", "DoBatchApiRequest");
|
|
177
|
+
return result;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
exports.OINOBlobApi = OINOBlobApi;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
4
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
5
|
+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
4
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
5
|
+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.OINOBlobDataModel = void 0;
|
|
9
|
+
const common_1 = require("@oino-ts/common");
|
|
10
|
+
/**
|
|
11
|
+
* Static data model for blob listings.
|
|
12
|
+
*
|
|
13
|
+
* Fields are added by the blob implementation's `initializeApiDatamodel`
|
|
14
|
+
* method, so the exact set depends on what the storage backend supports.
|
|
15
|
+
* The canonical order is:
|
|
16
|
+
* 1. `name` – full blob name (primary key, string)
|
|
17
|
+
* 2. `etag` – entity tag (string)
|
|
18
|
+
* 3. `lastModified` – last modification timestamp (datetime)
|
|
19
|
+
* 4. `contentLength` – size in bytes (number)
|
|
20
|
+
* 5. `contentType` – MIME type (string) – omitted when not supported
|
|
21
|
+
*/
|
|
22
|
+
class OINOBlobDataModel extends common_1.OINODataModel {
|
|
23
|
+
/** Reference to the owning blob API */
|
|
24
|
+
blobApi;
|
|
25
|
+
/**
|
|
26
|
+
* Constructor. Fields are added externally by the blob implementation
|
|
27
|
+
* via `initializeApiDatamodel`.
|
|
28
|
+
*
|
|
29
|
+
* @param api the `OINOBlobApi` that owns this data model
|
|
30
|
+
*/
|
|
31
|
+
constructor(api) {
|
|
32
|
+
super(api);
|
|
33
|
+
this.blobApi = api;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Convert an array of blob entries into an in-memory dataset whose
|
|
37
|
+
* columns match the fields present in this model.
|
|
38
|
+
*
|
|
39
|
+
* @param entries blob entries from the storage backend
|
|
40
|
+
*/
|
|
41
|
+
entriesToDataset(entries) {
|
|
42
|
+
const fieldNames = this.fields.map(f => f.name);
|
|
43
|
+
const rows = entries.map(e => {
|
|
44
|
+
const row = [];
|
|
45
|
+
for (const name of fieldNames) {
|
|
46
|
+
switch (name) {
|
|
47
|
+
case "name":
|
|
48
|
+
row.push(e.name);
|
|
49
|
+
break;
|
|
50
|
+
case "etag":
|
|
51
|
+
row.push(e.etag);
|
|
52
|
+
break;
|
|
53
|
+
case "lastModified":
|
|
54
|
+
row.push(e.lastModified);
|
|
55
|
+
break;
|
|
56
|
+
case "contentLength":
|
|
57
|
+
row.push(e.contentLength);
|
|
58
|
+
break;
|
|
59
|
+
case "contentType":
|
|
60
|
+
row.push(e.contentType);
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return row;
|
|
65
|
+
});
|
|
66
|
+
return new common_1.OINOMemoryDataset(rows);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
exports.OINOBlobDataModel = OINOBlobDataModel;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
4
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
5
|
+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.OINOBlobFactory = void 0;
|
|
9
|
+
const OINOBlobApi_js_1 = require("./OINOBlobApi.js");
|
|
10
|
+
/**
|
|
11
|
+
* Static factory for creating `OINOBlob` instances and `OINOBlobApi` instances
|
|
12
|
+
* from registered provider classes.
|
|
13
|
+
*
|
|
14
|
+
* Usage:
|
|
15
|
+
* ```ts
|
|
16
|
+
* OINOBlobFactory.registerBlob("OINOBlobAzureTable", OINOBlobAzureTable)
|
|
17
|
+
* const blob = await OINOBlobFactory.createBlob({ type: "OINOBlobAzureTable", ... })
|
|
18
|
+
* const api = await OINOBlobFactory.createApi(blob, { apiName: "files", tableName: "uploads/" })
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
class OINOBlobFactory {
|
|
22
|
+
static _registry = {};
|
|
23
|
+
/**
|
|
24
|
+
* Register a blob provider class under the given name.
|
|
25
|
+
*
|
|
26
|
+
* @param name name used in `OINOBlobParams.type`
|
|
27
|
+
* @param blobClass constructor of the provider
|
|
28
|
+
*/
|
|
29
|
+
static registerBlob(name, blobClass) {
|
|
30
|
+
this._registry[name] = blobClass;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Create and optionally connect/validate a blob backend from params.
|
|
34
|
+
*
|
|
35
|
+
* @param params connection parameters
|
|
36
|
+
* @param connect if true, calls `connect()` on the backend
|
|
37
|
+
* @param validate if true, calls `validate()` on the backend
|
|
38
|
+
*/
|
|
39
|
+
static async createBlob(params, connect = true, validate = true) {
|
|
40
|
+
const BlobClass = this._registry[params.type];
|
|
41
|
+
if (!BlobClass) {
|
|
42
|
+
throw new Error("Unsupported blob type: " + params.type);
|
|
43
|
+
}
|
|
44
|
+
const blob = new BlobClass(params);
|
|
45
|
+
if (connect) {
|
|
46
|
+
const connect_res = await blob.connect();
|
|
47
|
+
if (!connect_res.success) {
|
|
48
|
+
throw new Error("Blob connection failed: " + connect_res.statusText);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (validate) {
|
|
52
|
+
const validate_res = await blob.validate();
|
|
53
|
+
if (!validate_res.success) {
|
|
54
|
+
throw new Error("Blob validation failed: " + validate_res.statusText);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return blob;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Create an `OINOBlobApi` and initialise its data model.
|
|
61
|
+
*
|
|
62
|
+
* @param blob blob backend to use
|
|
63
|
+
* @param params API parameters (`tableName` is used as the blob prefix)
|
|
64
|
+
*/
|
|
65
|
+
static async createApi(blob, params) {
|
|
66
|
+
const api = new OINOBlobApi_js_1.OINOBlobApi(blob, params);
|
|
67
|
+
await blob.initializeApiDatamodel(api);
|
|
68
|
+
return api;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
exports.OINOBlobFactory = OINOBlobFactory;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.OINOBlobApiResult = exports.OINOBlobApi = exports.OINOBlobFactory = exports.OINOBlobDataModel = exports.OINOBlob = void 0;
|
|
4
|
+
var OINOBlob_js_1 = require("./OINOBlob.js");
|
|
5
|
+
Object.defineProperty(exports, "OINOBlob", { enumerable: true, get: function () { return OINOBlob_js_1.OINOBlob; } });
|
|
6
|
+
var OINOBlobDataModel_js_1 = require("./OINOBlobDataModel.js");
|
|
7
|
+
Object.defineProperty(exports, "OINOBlobDataModel", { enumerable: true, get: function () { return OINOBlobDataModel_js_1.OINOBlobDataModel; } });
|
|
8
|
+
var OINOBlobFactory_js_1 = require("./OINOBlobFactory.js");
|
|
9
|
+
Object.defineProperty(exports, "OINOBlobFactory", { enumerable: true, get: function () { return OINOBlobFactory_js_1.OINOBlobFactory; } });
|
|
10
|
+
var OINOBlobApi_js_1 = require("./OINOBlobApi.js");
|
|
11
|
+
Object.defineProperty(exports, "OINOBlobApi", { enumerable: true, get: function () { return OINOBlobApi_js_1.OINOBlobApi; } });
|
|
12
|
+
Object.defineProperty(exports, "OINOBlobApiResult", { enumerable: true, get: function () { return OINOBlobApi_js_1.OINOBlobApiResult; } });
|