@oino-ts/blob-azure 1.0.2 → 1.0.4

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,213 @@
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.OINOBlobAzure = void 0;
9
+ const node_buffer_1 = require("node:buffer");
10
+ const storage_blob_1 = require("@azure/storage-blob");
11
+ const identity_1 = require("@azure/identity");
12
+ const common_1 = require("@oino-ts/common");
13
+ const common_2 = require("@oino-ts/common");
14
+ const blob_1 = require("@oino-ts/blob");
15
+ /**
16
+ * Azure Blob Storage implementation of `OINOBlob`.
17
+ *
18
+ * Authenticates using an Azure Storage connection string. Connection parameters map as:
19
+ * - `params.url` → blob service endpoint, e.g. `https://<account>.blob.core.windows.net`
20
+ * - `params.container` → container name
21
+ * - `params.connectionStr` → Azure Storage connection string (e.g. `DefaultEndpointsProtocol=https;AccountName=...`)
22
+ *
23
+ * Register and use via the factory:
24
+ * ```ts
25
+ * import { OINOBlobFactory } from "@oino-ts/blob"
26
+ * import { OINOBlobAzure } from "@oino-ts/blob-azure"
27
+ *
28
+ * OINOBlobFactory.registerBlob("OINOBlobAzure", OINOBlobAzure)
29
+ *
30
+ * const blob = await OINOBlobFactory.createBlob({
31
+ * type: "OINOBlobAzure",
32
+ * container: "my-container",
33
+ * credentials: either connectionStr or url and clientId
34
+ * })
35
+ * const api = await OINOBlobFactory.createApi(blob, {
36
+ * apiName: "files",
37
+ * tableName: "uploads/" // blob prefix / folder
38
+ * })
39
+ * ```
40
+ */
41
+ class OINOBlobAzure extends blob_1.OINOBlob {
42
+ _containerClient = null;
43
+ constructor(params) {
44
+ super(params);
45
+ if ((!this.blobParams.credentials?.connectionStr) && !(this.blobParams.credentials?.url)) { // && this.blobParams.credentials?.clientId)) {
46
+ throw new Error("OINOBlobAzure: missing or invalid credentials (provide either connectionStr or url and clientId)");
47
+ }
48
+ }
49
+ /**
50
+ * Initialise the Azure SDK client. Does not perform any network call.
51
+ */
52
+ async connect() {
53
+ const result = new common_2.OINOResult();
54
+ let serviceClient;
55
+ try {
56
+ if (this.blobParams.credentials?.connectionStr) {
57
+ serviceClient = storage_blob_1.BlobServiceClient.fromConnectionString(this.blobParams.credentials.connectionStr);
58
+ }
59
+ else if (this.blobParams.credentials?.url) { // && this.blobParams.credentials?.clientId) {
60
+ // Use ContainerClient directly to avoid double-container path when combining service URL + container
61
+ serviceClient = new storage_blob_1.BlobServiceClient(this.blobParams.credentials.url, new identity_1.DefaultAzureCredential());
62
+ this.isConnected = true;
63
+ }
64
+ this._containerClient = serviceClient.getContainerClient(this.blobParams.container);
65
+ this.isConnected = true;
66
+ }
67
+ catch (e) {
68
+ result.setError(500, "OINOBlobAzure connect failed: " + e.message, "connect");
69
+ common_1.OINOLog.exception("@oino-ts/blob-azure", "OINOBlobAzure", "connect", "OINOBlobAzure connect failed", { error: e, stack: e.stack });
70
+ }
71
+ return result;
72
+ }
73
+ /**
74
+ * Verify that the target container exists and is accessible.
75
+ */
76
+ async validate() {
77
+ if (!this._containerClient) {
78
+ return new common_2.OINOResult({ success: false, status: 500, statusText: "OINOBlobAzure: not connected" });
79
+ }
80
+ try {
81
+ const exists = await this._containerClient.exists();
82
+ if (!exists) {
83
+ return new common_2.OINOResult({
84
+ success: false,
85
+ status: 404,
86
+ statusText: "OINOBlobAzure: container '" + this.blobParams.container + "' not found"
87
+ });
88
+ }
89
+ this.isValidated = true;
90
+ }
91
+ catch (e) {
92
+ return new common_2.OINOResult({ success: false, status: 500, statusText: "OINOBlobAzure validate failed: " + e.message });
93
+ }
94
+ return new common_2.OINOResult();
95
+ }
96
+ /**
97
+ * Release the client reference (Azure SDK is stateless per-request so nothing to close).
98
+ */
99
+ async disconnect() {
100
+ this._containerClient = null;
101
+ this.isConnected = false;
102
+ this.isValidated = false;
103
+ }
104
+ // ── OINOBlob operations ───────────────────────────────────────────────
105
+ /**
106
+ * List all blobs, applying native Azure query filtering where possible and
107
+ * in-memory result filtering for predicates that cannot be expressed as a
108
+ * native query.
109
+ *
110
+ * - The `name` field supports server-side prefix filtering via the Azure
111
+ * `listBlobsFlat` `prefix` option (query filtering).
112
+ * - All other field predicates (`etag`, `lastModified`, `contentLength`,
113
+ * `contentType`) are evaluated in-memory after the listing (result
114
+ * filtering).
115
+ *
116
+ * @param filter optional query filter to apply
117
+ */
118
+ async listEntries(filter) {
119
+ if (!this._containerClient) {
120
+ throw new Error("OINOBlobAzure: not connected");
121
+ }
122
+ const queryPrefix = (filter && !filter.isEmpty())
123
+ ? blob_1.OINOBlob.extractNamePrefix(filter)
124
+ : undefined;
125
+ const entries = [];
126
+ for await (const blob of this._containerClient.listBlobsFlat({ prefix: queryPrefix })) {
127
+ entries.push({
128
+ name: blob.name,
129
+ etag: blob.properties.etag ?? "",
130
+ lastModified: blob.properties.lastModified,
131
+ contentLength: blob.properties.contentLength ?? 0,
132
+ contentType: blob.properties.contentType ?? "application/octet-stream"
133
+ });
134
+ }
135
+ if (!filter || filter.isEmpty()) {
136
+ return entries;
137
+ }
138
+ return entries.filter(e => blob_1.OINOBlob.matchesEntry(e, filter));
139
+ }
140
+ /**
141
+ * Download the raw content of a named blob.
142
+ *
143
+ * @param name full blob name (path within the container)
144
+ */
145
+ async fetchEntry(name) {
146
+ if (!this._containerClient) {
147
+ throw new Error("OINOBlobAzure: not connected");
148
+ }
149
+ const blobClient = this._containerClient.getBlobClient(name);
150
+ const downloadResponse = await blobClient.download(0);
151
+ const contentType = downloadResponse.contentType ?? "application/octet-stream";
152
+ const stream = downloadResponse.readableStreamBody;
153
+ if (!stream) {
154
+ throw new Error("OINOBlobAzure: no readable stream returned for blob '" + name + "'");
155
+ }
156
+ const chunks = [];
157
+ for await (const chunk of stream) {
158
+ chunks.push(chunk instanceof node_buffer_1.Buffer ? chunk : node_buffer_1.Buffer.from(chunk));
159
+ }
160
+ return {
161
+ content: new Uint8Array(node_buffer_1.Buffer.concat(chunks)),
162
+ contentType
163
+ };
164
+ }
165
+ /**
166
+ * Upload (create or replace) a blob with the given binary content.
167
+ *
168
+ * @param name full blob name (path within the container)
169
+ * @param content binary content to store
170
+ * @param contentType MIME type of the content (e.g. `"image/jpeg"`)
171
+ */
172
+ async uploadEntry(name, content, contentType) {
173
+ if (!this._containerClient) {
174
+ throw new Error("OINOBlobAzure: not connected");
175
+ }
176
+ const blockBlobClient = this._containerClient.getBlockBlobClient(name);
177
+ const headers = { blobDataType: contentType };
178
+ await blockBlobClient.upload(content, content.length, { blobHTTPHeaders: headers });
179
+ }
180
+ /**
181
+ * Delete a named blob.
182
+ *
183
+ * @param name full blob name (path within the container)
184
+ */
185
+ async deleteEntry(name) {
186
+ if (!this._containerClient) {
187
+ throw new Error("OINOBlobAzure: not connected");
188
+ }
189
+ const blobClient = this._containerClient.getBlobClient(name);
190
+ await blobClient.delete();
191
+ }
192
+ // ── OINODataSource datamodel initialisation ───────────────────────────
193
+ /**
194
+ * Attach a static `OINOBlobDataModel` to the given API, adding all five
195
+ * standard fields that Azure Blob Storage returns in a listing.
196
+ *
197
+ * @param api the `OINOBlobApi` whose data model is to be initialised
198
+ */
199
+ async initializeApiDatamodel(api) {
200
+ const blobApi = api;
201
+ const datamodel = new blob_1.OINOBlobDataModel(blobApi);
202
+ const ds = this;
203
+ const FIELD = { isPrimaryKey: false, isForeignKey: false, isAutoInc: false, isNotNull: false };
204
+ const PK = { isPrimaryKey: true, isForeignKey: false, isAutoInc: false, isNotNull: true };
205
+ datamodel.addField(new common_2.OINOStringDataField(ds, "name", "TEXT", PK, 1024));
206
+ datamodel.addField(new common_2.OINOStringDataField(ds, "etag", "TEXT", FIELD, 256));
207
+ datamodel.addField(new common_2.OINODatetimeDataField(ds, "lastModified", "DATETIME", FIELD));
208
+ datamodel.addField(new common_2.OINONumberDataField(ds, "contentLength", "INTEGER", FIELD));
209
+ datamodel.addField(new common_2.OINOStringDataField(ds, "contentType", "TEXT", FIELD, 256));
210
+ blobApi.initializeDatamodel(datamodel);
211
+ }
212
+ }
213
+ exports.OINOBlobAzure = OINOBlobAzure;
package/dist/cjs/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.OINOBlobAzureTable = void 0;
4
- var OINOBlobAzureTable_js_1 = require("./OINOBlobAzureTable.js");
5
- Object.defineProperty(exports, "OINOBlobAzureTable", { enumerable: true, get: function () { return OINOBlobAzureTable_js_1.OINOBlobAzureTable; } });
3
+ exports.OINOBlobAzure = void 0;
4
+ var OINOBlobAzure_js_1 = require("./OINOBlobAzure.js");
5
+ Object.defineProperty(exports, "OINOBlobAzure", { enumerable: true, get: function () { return OINOBlobAzure_js_1.OINOBlobAzure; } });
@@ -0,0 +1,209 @@
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 { Buffer } from "node:buffer";
7
+ import { BlobServiceClient } from "@azure/storage-blob";
8
+ import { DefaultAzureCredential } from "@azure/identity";
9
+ import { OINOLog } from "@oino-ts/common";
10
+ import { OINOResult, OINOStringDataField, OINONumberDataField, OINODatetimeDataField } from "@oino-ts/common";
11
+ import { OINOBlob, OINOBlobDataModel } from "@oino-ts/blob";
12
+ /**
13
+ * Azure Blob Storage implementation of `OINOBlob`.
14
+ *
15
+ * Authenticates using an Azure Storage connection string. Connection parameters map as:
16
+ * - `params.url` → blob service endpoint, e.g. `https://<account>.blob.core.windows.net`
17
+ * - `params.container` → container name
18
+ * - `params.connectionStr` → Azure Storage connection string (e.g. `DefaultEndpointsProtocol=https;AccountName=...`)
19
+ *
20
+ * Register and use via the factory:
21
+ * ```ts
22
+ * import { OINOBlobFactory } from "@oino-ts/blob"
23
+ * import { OINOBlobAzure } from "@oino-ts/blob-azure"
24
+ *
25
+ * OINOBlobFactory.registerBlob("OINOBlobAzure", OINOBlobAzure)
26
+ *
27
+ * const blob = await OINOBlobFactory.createBlob({
28
+ * type: "OINOBlobAzure",
29
+ * container: "my-container",
30
+ * credentials: either connectionStr or url and clientId
31
+ * })
32
+ * const api = await OINOBlobFactory.createApi(blob, {
33
+ * apiName: "files",
34
+ * tableName: "uploads/" // blob prefix / folder
35
+ * })
36
+ * ```
37
+ */
38
+ export class OINOBlobAzure extends OINOBlob {
39
+ _containerClient = null;
40
+ constructor(params) {
41
+ super(params);
42
+ if ((!this.blobParams.credentials?.connectionStr) && !(this.blobParams.credentials?.url)) { // && this.blobParams.credentials?.clientId)) {
43
+ throw new Error("OINOBlobAzure: missing or invalid credentials (provide either connectionStr or url and clientId)");
44
+ }
45
+ }
46
+ /**
47
+ * Initialise the Azure SDK client. Does not perform any network call.
48
+ */
49
+ async connect() {
50
+ const result = new OINOResult();
51
+ let serviceClient;
52
+ try {
53
+ if (this.blobParams.credentials?.connectionStr) {
54
+ serviceClient = BlobServiceClient.fromConnectionString(this.blobParams.credentials.connectionStr);
55
+ }
56
+ else if (this.blobParams.credentials?.url) { // && this.blobParams.credentials?.clientId) {
57
+ // Use ContainerClient directly to avoid double-container path when combining service URL + container
58
+ serviceClient = new BlobServiceClient(this.blobParams.credentials.url, new DefaultAzureCredential());
59
+ this.isConnected = true;
60
+ }
61
+ this._containerClient = serviceClient.getContainerClient(this.blobParams.container);
62
+ this.isConnected = true;
63
+ }
64
+ catch (e) {
65
+ result.setError(500, "OINOBlobAzure connect failed: " + e.message, "connect");
66
+ OINOLog.exception("@oino-ts/blob-azure", "OINOBlobAzure", "connect", "OINOBlobAzure connect failed", { error: e, stack: e.stack });
67
+ }
68
+ return result;
69
+ }
70
+ /**
71
+ * Verify that the target container exists and is accessible.
72
+ */
73
+ async validate() {
74
+ if (!this._containerClient) {
75
+ return new OINOResult({ success: false, status: 500, statusText: "OINOBlobAzure: not connected" });
76
+ }
77
+ try {
78
+ const exists = await this._containerClient.exists();
79
+ if (!exists) {
80
+ return new OINOResult({
81
+ success: false,
82
+ status: 404,
83
+ statusText: "OINOBlobAzure: container '" + this.blobParams.container + "' not found"
84
+ });
85
+ }
86
+ this.isValidated = true;
87
+ }
88
+ catch (e) {
89
+ return new OINOResult({ success: false, status: 500, statusText: "OINOBlobAzure validate failed: " + e.message });
90
+ }
91
+ return new OINOResult();
92
+ }
93
+ /**
94
+ * Release the client reference (Azure SDK is stateless per-request so nothing to close).
95
+ */
96
+ async disconnect() {
97
+ this._containerClient = null;
98
+ this.isConnected = false;
99
+ this.isValidated = false;
100
+ }
101
+ // ── OINOBlob operations ───────────────────────────────────────────────
102
+ /**
103
+ * List all blobs, applying native Azure query filtering where possible and
104
+ * in-memory result filtering for predicates that cannot be expressed as a
105
+ * native query.
106
+ *
107
+ * - The `name` field supports server-side prefix filtering via the Azure
108
+ * `listBlobsFlat` `prefix` option (query filtering).
109
+ * - All other field predicates (`etag`, `lastModified`, `contentLength`,
110
+ * `contentType`) are evaluated in-memory after the listing (result
111
+ * filtering).
112
+ *
113
+ * @param filter optional query filter to apply
114
+ */
115
+ async listEntries(filter) {
116
+ if (!this._containerClient) {
117
+ throw new Error("OINOBlobAzure: not connected");
118
+ }
119
+ const queryPrefix = (filter && !filter.isEmpty())
120
+ ? OINOBlob.extractNamePrefix(filter)
121
+ : undefined;
122
+ const entries = [];
123
+ for await (const blob of this._containerClient.listBlobsFlat({ prefix: queryPrefix })) {
124
+ entries.push({
125
+ name: blob.name,
126
+ etag: blob.properties.etag ?? "",
127
+ lastModified: blob.properties.lastModified,
128
+ contentLength: blob.properties.contentLength ?? 0,
129
+ contentType: blob.properties.contentType ?? "application/octet-stream"
130
+ });
131
+ }
132
+ if (!filter || filter.isEmpty()) {
133
+ return entries;
134
+ }
135
+ return entries.filter(e => OINOBlob.matchesEntry(e, filter));
136
+ }
137
+ /**
138
+ * Download the raw content of a named blob.
139
+ *
140
+ * @param name full blob name (path within the container)
141
+ */
142
+ async fetchEntry(name) {
143
+ if (!this._containerClient) {
144
+ throw new Error("OINOBlobAzure: not connected");
145
+ }
146
+ const blobClient = this._containerClient.getBlobClient(name);
147
+ const downloadResponse = await blobClient.download(0);
148
+ const contentType = downloadResponse.contentType ?? "application/octet-stream";
149
+ const stream = downloadResponse.readableStreamBody;
150
+ if (!stream) {
151
+ throw new Error("OINOBlobAzure: no readable stream returned for blob '" + name + "'");
152
+ }
153
+ const chunks = [];
154
+ for await (const chunk of stream) {
155
+ chunks.push(chunk instanceof Buffer ? chunk : Buffer.from(chunk));
156
+ }
157
+ return {
158
+ content: new Uint8Array(Buffer.concat(chunks)),
159
+ contentType
160
+ };
161
+ }
162
+ /**
163
+ * Upload (create or replace) a blob with the given binary content.
164
+ *
165
+ * @param name full blob name (path within the container)
166
+ * @param content binary content to store
167
+ * @param contentType MIME type of the content (e.g. `"image/jpeg"`)
168
+ */
169
+ async uploadEntry(name, content, contentType) {
170
+ if (!this._containerClient) {
171
+ throw new Error("OINOBlobAzure: not connected");
172
+ }
173
+ const blockBlobClient = this._containerClient.getBlockBlobClient(name);
174
+ const headers = { blobDataType: contentType };
175
+ await blockBlobClient.upload(content, content.length, { blobHTTPHeaders: headers });
176
+ }
177
+ /**
178
+ * Delete a named blob.
179
+ *
180
+ * @param name full blob name (path within the container)
181
+ */
182
+ async deleteEntry(name) {
183
+ if (!this._containerClient) {
184
+ throw new Error("OINOBlobAzure: not connected");
185
+ }
186
+ const blobClient = this._containerClient.getBlobClient(name);
187
+ await blobClient.delete();
188
+ }
189
+ // ── OINODataSource datamodel initialisation ───────────────────────────
190
+ /**
191
+ * Attach a static `OINOBlobDataModel` to the given API, adding all five
192
+ * standard fields that Azure Blob Storage returns in a listing.
193
+ *
194
+ * @param api the `OINOBlobApi` whose data model is to be initialised
195
+ */
196
+ async initializeApiDatamodel(api) {
197
+ const blobApi = api;
198
+ const datamodel = new OINOBlobDataModel(blobApi);
199
+ const ds = this;
200
+ const FIELD = { isPrimaryKey: false, isForeignKey: false, isAutoInc: false, isNotNull: false };
201
+ const PK = { isPrimaryKey: true, isForeignKey: false, isAutoInc: false, isNotNull: true };
202
+ datamodel.addField(new OINOStringDataField(ds, "name", "TEXT", PK, 1024));
203
+ datamodel.addField(new OINOStringDataField(ds, "etag", "TEXT", FIELD, 256));
204
+ datamodel.addField(new OINODatetimeDataField(ds, "lastModified", "DATETIME", FIELD));
205
+ datamodel.addField(new OINONumberDataField(ds, "contentLength", "INTEGER", FIELD));
206
+ datamodel.addField(new OINOStringDataField(ds, "contentType", "TEXT", FIELD, 256));
207
+ blobApi.initializeDatamodel(datamodel);
208
+ }
209
+ }
package/dist/esm/index.js CHANGED
@@ -1 +1 @@
1
- export { OINOBlobAzureTable } from "./OINOBlobAzureTable.js";
1
+ export { OINOBlobAzure } from "./OINOBlobAzure.js";
@@ -0,0 +1,85 @@
1
+ import { OINOApi, OINOResult, OINOQueryFilter } from "@oino-ts/common";
2
+ import { OINOBlob, OINOBlobParams, type OINOBlobEntry, type OINOBlobFetchResult } from "@oino-ts/blob";
3
+ /**
4
+ * Azure Blob Storage implementation of `OINOBlob`.
5
+ *
6
+ * Authenticates using an Azure Storage connection string. Connection parameters map as:
7
+ * - `params.url` → blob service endpoint, e.g. `https://<account>.blob.core.windows.net`
8
+ * - `params.container` → container name
9
+ * - `params.connectionStr` → Azure Storage connection string (e.g. `DefaultEndpointsProtocol=https;AccountName=...`)
10
+ *
11
+ * Register and use via the factory:
12
+ * ```ts
13
+ * import { OINOBlobFactory } from "@oino-ts/blob"
14
+ * import { OINOBlobAzure } from "@oino-ts/blob-azure"
15
+ *
16
+ * OINOBlobFactory.registerBlob("OINOBlobAzure", OINOBlobAzure)
17
+ *
18
+ * const blob = await OINOBlobFactory.createBlob({
19
+ * type: "OINOBlobAzure",
20
+ * container: "my-container",
21
+ * credentials: either connectionStr or url and clientId
22
+ * })
23
+ * const api = await OINOBlobFactory.createApi(blob, {
24
+ * apiName: "files",
25
+ * tableName: "uploads/" // blob prefix / folder
26
+ * })
27
+ * ```
28
+ */
29
+ export declare class OINOBlobAzure extends OINOBlob {
30
+ private _containerClient;
31
+ constructor(params: OINOBlobParams);
32
+ /**
33
+ * Initialise the Azure SDK client. Does not perform any network call.
34
+ */
35
+ connect(): Promise<OINOResult>;
36
+ /**
37
+ * Verify that the target container exists and is accessible.
38
+ */
39
+ validate(): Promise<OINOResult>;
40
+ /**
41
+ * Release the client reference (Azure SDK is stateless per-request so nothing to close).
42
+ */
43
+ disconnect(): Promise<void>;
44
+ /**
45
+ * List all blobs, applying native Azure query filtering where possible and
46
+ * in-memory result filtering for predicates that cannot be expressed as a
47
+ * native query.
48
+ *
49
+ * - The `name` field supports server-side prefix filtering via the Azure
50
+ * `listBlobsFlat` `prefix` option (query filtering).
51
+ * - All other field predicates (`etag`, `lastModified`, `contentLength`,
52
+ * `contentType`) are evaluated in-memory after the listing (result
53
+ * filtering).
54
+ *
55
+ * @param filter optional query filter to apply
56
+ */
57
+ listEntries(filter?: OINOQueryFilter): Promise<OINOBlobEntry[]>;
58
+ /**
59
+ * Download the raw content of a named blob.
60
+ *
61
+ * @param name full blob name (path within the container)
62
+ */
63
+ 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
+ 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
+ deleteEntry(name: string): Promise<void>;
78
+ /**
79
+ * Attach a static `OINOBlobDataModel` to the given API, adding all five
80
+ * standard fields that Azure Blob Storage returns in a listing.
81
+ *
82
+ * @param api the `OINOBlobApi` whose data model is to be initialised
83
+ */
84
+ initializeApiDatamodel(api: OINOApi): Promise<void>;
85
+ }
@@ -1 +1 @@
1
- export { OINOBlobAzureTable } from "./OINOBlobAzureTable.js";
1
+ export { OINOBlobAzure } from "./OINOBlobAzure.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oino-ts/blob-azure",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "description": "OINO TS package for using Azure Blob Storage as a REST API.",
5
5
  "author": "Matias Kiviniemi (pragmatta)",
6
6
  "license": "MPL-2.0",
@@ -21,11 +21,12 @@
21
21
  "types": "./dist/types/index.d.ts",
22
22
  "dependencies": {
23
23
  "@azure/storage-blob": "^12.0.0",
24
- "@oino-ts/blob": "1.0.2",
25
- "@oino-ts/common": "1.0.2"
24
+ "@azure/identity": "^3.0.0",
25
+ "@oino-ts/blob": "1.0.4",
26
+ "@oino-ts/common": "1.0.4"
26
27
  },
27
28
  "devDependencies": {
28
- "@oino-ts/types": "1.0.2",
29
+ "@oino-ts/types": "1.0.4",
29
30
  "@types/bun": "^1.1.14",
30
31
  "@types/node": "^22.0.00",
31
32
  "typescript": "~5.9.0"
@@ -8,12 +8,13 @@ import { Buffer } from "node:buffer"
8
8
 
9
9
  import {
10
10
  BlobServiceClient,
11
- type ContainerClient
11
+ ContainerClient
12
12
  } from "@azure/storage-blob"
13
+ import { DefaultAzureCredential } from "@azure/identity"
13
14
 
15
+ import { OINOLog } from "@oino-ts/common"
14
16
  import { OINOApi, OINOResult, OINOQueryFilter, OINOStringDataField, OINONumberDataField, OINODatetimeDataField, type OINODataFieldParams } from "@oino-ts/common"
15
- import { OINOBlob, OINOBlobDataModel, OINOBlobApi } from "@oino-ts/blob"
16
- import { type OINOBlobEntry, type OINOBlobFetchResult } from "@oino-ts/blob"
17
+ import { OINOBlob, OINOBlobParams, OINOBlobDataModel, OINOBlobApi, type OINOBlobEntry, type OINOBlobFetchResult } from "@oino-ts/blob"
17
18
 
18
19
  /**
19
20
  * Azure Blob Storage implementation of `OINOBlob`.
@@ -26,15 +27,14 @@ import { type OINOBlobEntry, type OINOBlobFetchResult } from "@oino-ts/blob"
26
27
  * Register and use via the factory:
27
28
  * ```ts
28
29
  * import { OINOBlobFactory } from "@oino-ts/blob"
29
- * import { OINOBlobAzureTable } from "@oino-ts/blob-azure"
30
+ * import { OINOBlobAzure } from "@oino-ts/blob-azure"
30
31
  *
31
- * OINOBlobFactory.registerBlob("OINOBlobAzureTable", OINOBlobAzureTable)
32
+ * OINOBlobFactory.registerBlob("OINOBlobAzure", OINOBlobAzure)
32
33
  *
33
34
  * const blob = await OINOBlobFactory.createBlob({
34
- * type: "OINOBlobAzureTable",
35
- * url: "https://myaccount.blob.core.windows.net",
35
+ * type: "OINOBlobAzure",
36
36
  * container: "my-container",
37
- * connectionStr: process.env.AZURE_STORAGE_CONNECTION_STRING
37
+ * credentials: either connectionStr or url and clientId
38
38
  * })
39
39
  * const api = await OINOBlobFactory.createApi(blob, {
40
40
  * apiName: "files",
@@ -42,31 +42,40 @@ import { type OINOBlobEntry, type OINOBlobFetchResult } from "@oino-ts/blob"
42
42
  * })
43
43
  * ```
44
44
  */
45
- export class OINOBlobAzureTable extends OINOBlob {
45
+ export class OINOBlobAzure extends OINOBlob {
46
46
  private _containerClient: ContainerClient | null = null
47
47
 
48
- // ── OINODataSource lifecycle ──────────────────────────────────────────
48
+ constructor(params: OINOBlobParams) {
49
+ super(params)
50
+ if ((!this.blobParams.credentials?.connectionStr) && !(this.blobParams.credentials?.url)) { // && this.blobParams.credentials?.clientId)) {
51
+ throw new Error("OINOBlobAzure: missing or invalid credentials (provide either connectionStr or url and clientId)")
52
+ }
53
+ }
49
54
 
50
55
  /**
51
56
  * Initialise the Azure SDK client. Does not perform any network call.
52
57
  */
53
58
  async connect(): Promise<OINOResult> {
54
59
  const result = new OINOResult()
60
+ let serviceClient: BlobServiceClient
55
61
  try {
56
- let serviceClient: BlobServiceClient
57
- if (this.blobParams.connectionStr) {
58
- serviceClient = BlobServiceClient.fromConnectionString(this.blobParams.connectionStr)
59
- } else {
60
- return new OINOResult({
61
- success: false,
62
- status: 400,
63
- statusText: "OINOBlobAzureTable: params.connectionStr is required"
64
- })
62
+ if (this.blobParams.credentials?.connectionStr) {
63
+ serviceClient = BlobServiceClient.fromConnectionString(this.blobParams.credentials.connectionStr)
64
+
65
+ } else if (this.blobParams.credentials?.url) { // && this.blobParams.credentials?.clientId) {
66
+ // Use ContainerClient directly to avoid double-container path when combining service URL + container
67
+ serviceClient = new BlobServiceClient(
68
+ this.blobParams.credentials.url,
69
+ new DefaultAzureCredential()
70
+ )
71
+ this.isConnected = true
65
72
  }
66
- this._containerClient = serviceClient.getContainerClient(this.blobParams.container)
73
+ this._containerClient = serviceClient!.getContainerClient(this.blobParams.container)
67
74
  this.isConnected = true
75
+
68
76
  } catch (e: any) {
69
- return new OINOResult({ success: false, status: 500, statusText: "OINOBlobAzureTable connect failed: " + e.message })
77
+ result.setError(500, "OINOBlobAzure connect failed: " + e.message, "connect")
78
+ OINOLog.exception("@oino-ts/blob-azure", "OINOBlobAzure", "connect", "OINOBlobAzure connect failed", { error: e, stack: e.stack })
70
79
  }
71
80
  return result
72
81
  }
@@ -76,7 +85,7 @@ export class OINOBlobAzureTable extends OINOBlob {
76
85
  */
77
86
  async validate(): Promise<OINOResult> {
78
87
  if (!this._containerClient) {
79
- return new OINOResult({ success: false, status: 500, statusText: "OINOBlobAzureTable: not connected" })
88
+ return new OINOResult({ success: false, status: 500, statusText: "OINOBlobAzure: not connected" })
80
89
  }
81
90
  try {
82
91
  const exists = await this._containerClient.exists()
@@ -84,12 +93,12 @@ export class OINOBlobAzureTable extends OINOBlob {
84
93
  return new OINOResult({
85
94
  success: false,
86
95
  status: 404,
87
- statusText: "OINOBlobAzureTable: container '" + this.blobParams.container + "' not found"
96
+ statusText: "OINOBlobAzure: container '" + this.blobParams.container + "' not found"
88
97
  })
89
98
  }
90
99
  this.isValidated = true
91
100
  } catch (e: any) {
92
- return new OINOResult({ success: false, status: 500, statusText: "OINOBlobAzureTable validate failed: " + e.message })
101
+ return new OINOResult({ success: false, status: 500, statusText: "OINOBlobAzure validate failed: " + e.message })
93
102
  }
94
103
  return new OINOResult()
95
104
  }
@@ -120,7 +129,7 @@ export class OINOBlobAzureTable extends OINOBlob {
120
129
  */
121
130
  async listEntries(filter?: OINOQueryFilter): Promise<OINOBlobEntry[]> {
122
131
  if (!this._containerClient) {
123
- throw new Error("OINOBlobAzureTable: not connected")
132
+ throw new Error("OINOBlobAzure: not connected")
124
133
  }
125
134
 
126
135
  const queryPrefix = (filter && !filter.isEmpty())
@@ -153,14 +162,14 @@ export class OINOBlobAzureTable extends OINOBlob {
153
162
  */
154
163
  async fetchEntry(name: string): Promise<OINOBlobFetchResult> {
155
164
  if (!this._containerClient) {
156
- throw new Error("OINOBlobAzureTable: not connected")
165
+ throw new Error("OINOBlobAzure: not connected")
157
166
  }
158
167
  const blobClient = this._containerClient.getBlobClient(name)
159
168
  const downloadResponse = await blobClient.download(0)
160
169
  const contentType = downloadResponse.contentType ?? "application/octet-stream"
161
170
  const stream = downloadResponse.readableStreamBody
162
171
  if (!stream) {
163
- throw new Error("OINOBlobAzureTable: no readable stream returned for blob '" + name + "'")
172
+ throw new Error("OINOBlobAzure: no readable stream returned for blob '" + name + "'")
164
173
  }
165
174
  const chunks: Buffer[] = []
166
175
  for await (const chunk of stream) {
@@ -181,7 +190,7 @@ export class OINOBlobAzureTable extends OINOBlob {
181
190
  */
182
191
  async uploadEntry(name: string, content: Uint8Array, contentType: string): Promise<void> {
183
192
  if (!this._containerClient) {
184
- throw new Error("OINOBlobAzureTable: not connected")
193
+ throw new Error("OINOBlobAzure: not connected")
185
194
  }
186
195
  const blockBlobClient = this._containerClient.getBlockBlobClient(name)
187
196
  const headers:any = { blobDataType: contentType }
@@ -195,7 +204,7 @@ export class OINOBlobAzureTable extends OINOBlob {
195
204
  */
196
205
  async deleteEntry(name: string): Promise<void> {
197
206
  if (!this._containerClient) {
198
- throw new Error("OINOBlobAzureTable: not connected")
207
+ throw new Error("OINOBlobAzure: not connected")
199
208
  }
200
209
  const blobClient = this._containerClient.getBlobClient(name)
201
210
  await blobClient.delete()
package/src/index.ts CHANGED
@@ -1 +1 @@
1
- export { OINOBlobAzureTable } from "./OINOBlobAzureTable.js"
1
+ export { OINOBlobAzure } from "./OINOBlobAzure.js"