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