@pi-r/azure 0.6.6 → 0.7.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/README.md CHANGED
@@ -1,10 +1,7 @@
1
- # @pi-r/azure
1
+ ### @pi-r/azure
2
2
 
3
- ## Documentation
3
+ https://e-mc.readthedocs.io/en/latest/cloud/azure.html
4
4
 
5
- - [E-mc](https://e-mc.readthedocs.io/en/latest/cloud/azure.html)
6
- - [squared](https://squared.readthedocs.io)
7
-
8
- ## LICENSE
5
+ ### LICENSE
9
6
 
10
7
  MIT
package/client/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  "use strict";
2
- exports.executeBatchQuery = exports.executeQuery = exports.deleteObjectsV2 = exports.deleteObjects = exports.setBucketWebsite = exports.createBucketV2 = exports.createBucket = exports.createDatabaseClient = exports.createStorageClient = exports.validateDatabase = exports.validateStorage = void 0;
2
+ exports.CLOUD_DOWNLOAD_CHUNK = exports.CLOUD_UPLOAD_CHUNK = exports.CLOUD_UPLOAD_STREAM = exports.executeBatchQuery = exports.executeQuery = exports.deleteObjectsV2 = exports.deleteObjects = exports.setBucketWebsite = exports.createBucketV2 = exports.createBucket = exports.createDatabaseClient = exports.createStorageClient = exports.validateDatabase = exports.validateStorage = void 0;
3
3
  const path = require("path");
4
4
  const storage = require("@azure/storage-blob");
5
5
  const cosmos = require("@azure/cosmos");
@@ -35,7 +35,7 @@ function createStorageClient(credential) {
35
35
  }
36
36
  }
37
37
  else {
38
- credential.accountName || (credential.accountName = /^https:\/\/([a-z\d]+)\./.exec(sharedAccessSignature)?.[1]);
38
+ credential.accountName ||= /^https:\/\/([a-z\d]+)\./.exec(sharedAccessSignature)?.[1];
39
39
  }
40
40
  return new storage.BlobServiceClient(sharedAccessSignature);
41
41
  }
@@ -68,7 +68,7 @@ async function createBucketV2(credential, bucket, access, options, service = 'az
68
68
  break;
69
69
  }
70
70
  const containerClient = createStorageClient.call(this, credential).getContainerClient(bucket);
71
- const setAccessPolicy = async () => containerClient.setAccessPolicy(access).catch(err => this.formatMessage(64, service, ["Unable to configure container", bucket], err, { ...Cloud.LOG_CLOUD_WARN }));
71
+ const setAccessPolicy = async () => containerClient.setAccessPolicy(access).catch((err) => this.formatMessage(64, service, ["Unable to configure container", bucket], err, { ...Cloud.LOG_CLOUD_WARN }));
72
72
  if (await containerClient.exists().catch(() => false)) {
73
73
  if (access) {
74
74
  await setAccessPolicy();
@@ -119,15 +119,15 @@ async function setBucketWebsite(credential, bucket, options, service = 'azure')
119
119
  .then(response => {
120
120
  const { requestId, errorCode } = response;
121
121
  if (errorCode) {
122
- this.formatMessage(64, service, ["Bucket configured" + ' with errors', bucket], (0, types_1.errorMessage)("Error code", errorCode), { ...Cloud.LOG_CLOUD_WARN });
122
+ this.formatMessage(64, service, ["Bucket website configured" + ' with errors', bucket], (0, types_1.errorMessage)("Error code", errorCode), { ...Cloud.LOG_CLOUD_WARN });
123
123
  }
124
124
  else {
125
- this.formatMessage(64, service, ["Bucket configured", bucket], requestId, { ...Cloud.LOG_CLOUD_COMMAND });
125
+ this.formatMessage(64, service, ["Bucket website configured", bucket], requestId, { ...Cloud.LOG_CLOUD_COMMAND });
126
126
  }
127
127
  return true;
128
128
  })
129
- .catch(err => {
130
- this.formatFail(64, service, ["Unable to configure bucket", bucket], err, { ...Cloud.LOG_CLOUD_FAIL, fatal: false });
129
+ .catch((err) => {
130
+ this.formatFail(64, service, ["Unable to set bucket website", bucket], err, { ...Cloud.LOG_CLOUD_FAIL, fatal: false });
131
131
  return false;
132
132
  });
133
133
  }
@@ -143,11 +143,12 @@ async function deleteObjectsV2(credential, bucket, recursive = true, service = '
143
143
  const tasks = [];
144
144
  let fileCount = 0;
145
145
  for await (const blob of containerClient.listBlobsFlat({ includeUncommitedBlobs: true })) {
146
- if (recursive || !blob.name.includes('/')) {
147
- tasks.push(containerClient.deleteBlob(blob.name, { versionId: blob.versionId }).catch(() => {
148
- --fileCount;
149
- }));
146
+ if (!recursive && blob.name.includes('/')) {
147
+ continue;
150
148
  }
149
+ tasks.push(containerClient.deleteBlob(blob.name, { versionId: blob.versionId }).catch(() => {
150
+ --fileCount;
151
+ }));
151
152
  }
152
153
  fileCount = tasks.length;
153
154
  return this.allSettled(tasks, ["Unable to delete blob", bucket], 64).then(() => {
@@ -171,7 +172,7 @@ async function executeBatchQuery(credential, batch, sessionKey) {
171
172
  const cacheValue = { value: this.valueOfKey(credential, 'cache'), sessionKey };
172
173
  let client;
173
174
  const createClient = (name, table) => {
174
- client || (client = createDatabaseClient.call(this, credential));
175
+ client ||= createDatabaseClient.call(this, credential);
175
176
  return client.database(name).container(table);
176
177
  };
177
178
  const closeClient = () => client?.dispose();
@@ -192,7 +193,7 @@ async function executeBatchQuery(credential, batch, sessionKey) {
192
193
  let rows, queryString = caching && ignoreCache !== true ? name + '_' + table + '_' : '';
193
194
  if (storedProcedureId && params) {
194
195
  const { options } = item;
195
- if (queryString && (rows = getCache(queryString += Cloud.asString(partitionKey, true) + '_' + storedProcedureId + Cloud.asString(params, true) + Cloud.asString(options, true)))) {
196
+ if (queryString && (rows = getCache(queryString += (partitionKey ? Cloud.asString(partitionKey, true) : '') + '_' + storedProcedureId + '_' + Cloud.asString(params, true) + Cloud.asString(options, true)))) {
196
197
  result[i] = rows;
197
198
  continue;
198
199
  }
@@ -204,7 +205,7 @@ async function executeBatchQuery(credential, batch, sessionKey) {
204
205
  else if (id) {
205
206
  const { options } = item;
206
207
  if (queryString) {
207
- queryString += Cloud.asString(partitionKey, true) + '_' + id + Cloud.asString(options, true);
208
+ queryString += (partitionKey ? Cloud.asString(partitionKey, true) : '') + '_' + id + '_' + Cloud.asString(options, true);
208
209
  if (!update && (rows = getCache(queryString))) {
209
210
  result[i] = rows;
210
211
  continue;
@@ -219,10 +220,10 @@ async function executeBatchQuery(credential, batch, sessionKey) {
219
220
  else {
220
221
  let { options } = item;
221
222
  if (partitionKey) {
222
- (options || (options = {})).partitionKey = partitionKey;
223
+ (options ||= {}).partitionKey = partitionKey;
223
224
  }
224
225
  if (limit > 0) {
225
- (options || (options = {})).maxItemCount = limit;
226
+ (options ||= {}).maxItemCount = limit;
226
227
  }
227
228
  if (query || (0, types_1.isPlainObject)(options)) {
228
229
  if (queryString && (rows = getCache(queryString += (query ? Cloud.asString(query, true) : '') + Cloud.asString(options, true)))) {
@@ -243,3 +244,6 @@ async function executeBatchQuery(credential, batch, sessionKey) {
243
244
  return result;
244
245
  }
245
246
  exports.executeBatchQuery = executeBatchQuery;
247
+ exports.CLOUD_UPLOAD_STREAM = true;
248
+ exports.CLOUD_UPLOAD_CHUNK = true;
249
+ exports.CLOUD_DOWNLOAD_CHUNK = true;
package/download/index.js CHANGED
@@ -1,26 +1,42 @@
1
1
  "use strict";
2
2
  const types_1 = require("@e-mc/types");
3
- const Module = require("@e-mc/module");
3
+ const path = require("path");
4
4
  const Cloud = require("@e-mc/cloud");
5
5
  const client_1 = require("@pi-r/azure");
6
6
  module.exports = function download(credential, service = 'azure') {
7
7
  const blobServiceClient = client_1.createStorageClient.call(this, credential);
8
8
  return (data, callback) => {
9
9
  const { bucket: Bucket, download: target } = data;
10
- const Key = target.filename;
10
+ const Key = target.keyname || target.filename;
11
11
  if (!Bucket || !Key) {
12
12
  callback((0, types_1.errorValue)('Missing property', !Bucket ? 'Container' : 'Name'));
13
13
  return;
14
14
  }
15
- const location = Module.joinPath(Bucket, Key);
16
- const blobClient = blobServiceClient.getContainerClient(Bucket);
17
- blobClient.getBlockBlobClient(Key).downloadToBuffer()
18
- .then(value => {
19
- callback(null, value);
15
+ let tempDir;
16
+ const downloadResponse = (err, url) => {
17
+ callback(err, url);
18
+ if (tempDir) {
19
+ queueMicrotask(() => Cloud.removeDir(tempDir));
20
+ }
21
+ };
22
+ const { flags = 0 } = target;
23
+ const containerClient = blobServiceClient.getContainerClient(Bucket);
24
+ const blobClient = containerClient.getBlockBlobClient(Key);
25
+ const blockSize = (flags & 4) && (0, types_1.alignSize)(target.chunkSize, 1024) || 0;
26
+ let diskClient, destination;
27
+ if (blockSize >= 2097152000 && (tempDir = this.getTempDir({ uuidDir: true }))) {
28
+ diskClient = blobClient.downloadToFile(destination = path.join(tempDir, path.basename(target.filename || Key)), 0, undefined, { snapshot: target.versionId, abortSignal: this.signal });
29
+ }
30
+ (diskClient || blobClient.downloadToBuffer(0, 0, { ...target.options, blockSize, concurrency: blockSize > 0 ? target.chunkLimit : undefined, abortSignal: this.signal }))
31
+ .then((value) => {
32
+ downloadResponse(null, diskClient && destination || value);
20
33
  const deleteObject = target.deleteObject;
21
34
  if (deleteObject) {
22
- blobClient.delete((0, types_1.isPlainObject)(deleteObject) ? (deleteObject.abortSignal = this.signal, deleteObject) : { abortSignal: this.signal })
23
- .then(() => this.formatMessage(64, service, "Delete success", location, { ...Cloud.LOG_CLOUD_DELETE }))
35
+ const location = Cloud.joinPath(Bucket, Key);
36
+ containerClient.delete((0, types_1.isPlainObject)(deleteObject) ? (deleteObject.abortSignal = this.signal, deleteObject) : { abortSignal: this.signal })
37
+ .then(() => {
38
+ this.formatMessage(64, service, "Delete success", location, { ...Cloud.LOG_CLOUD_DELETE });
39
+ })
24
40
  .catch((err) => {
25
41
  if (!(err instanceof Error && err.code === 'BlobNotFound')) {
26
42
  this.formatFail(64, service, ["Delete failed", location], err, { ...Cloud.LOG_CLOUD_FAIL, fatal: !!target.active });
@@ -28,6 +44,6 @@ module.exports = function download(credential, service = 'azure') {
28
44
  });
29
45
  }
30
46
  })
31
- .catch(err => callback(err));
47
+ .catch((err) => downloadResponse(err));
32
48
  };
33
49
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-r/azure",
3
- "version": "0.6.6",
3
+ "version": "0.7.0",
4
4
  "description": "Azure cloud functions for E-mc.",
5
5
  "main": "client/index.js",
6
6
  "publishConfig": {
@@ -20,11 +20,11 @@
20
20
  "license": "MIT",
21
21
  "homepage": "https://github.com/anpham6/pi-r#readme",
22
22
  "dependencies": {
23
- "@e-mc/cloud": "^0.8.7",
24
- "@e-mc/module": "^0.8.7",
25
- "@e-mc/types": "^0.8.7",
23
+ "@e-mc/cloud": "^0.9.0",
24
+ "@e-mc/module": "^0.9.0",
25
+ "@e-mc/types": "^0.9.0",
26
26
  "@azure/cosmos": "^4.0.0",
27
- "@azure/identity": "^4.0.1",
27
+ "@azure/identity": "^4.1.0",
28
28
  "@azure/storage-blob": "^12.17.0"
29
29
  }
30
30
  }
@@ -0,0 +1,24 @@
1
+ import type { CloudDatabase, CloudStorage } from '@e-mc/types/lib/cloud';
2
+
3
+ import type { CosmosClientOptions, FeedOptions, PartitionKey, PatchRequestBody, RequestOptions, SqlQuerySpec } from '@azure/cosmos';
4
+
5
+ export type AzureStorage = CloudStorage<AzureStorageCredential, "azure" | "az">;
6
+
7
+ export interface AzureStorageCredential {
8
+ accountName?: string;
9
+ accountKey?: string;
10
+ connectionString?: string;
11
+ sharedAccessSignature?: string;
12
+ }
13
+
14
+ export interface AzureDatabaseQuery<T = FeedOptions | RequestOptions> extends CloudDatabase<string | SqlQuerySpec, T, PatchRequestBody, unknown[], AzureDatabaseCredential> {
15
+ source: "cloud";
16
+ service: "azure" | "az";
17
+ partitionKey?: PartitionKey;
18
+ storedProcedureId?: string;
19
+ }
20
+
21
+ export interface AzureDatabaseCredential extends AuthValue, CosmosClientOptions {
22
+ tenantId?: string;
23
+ clientId?: string;
24
+ }
package/upload/index.js CHANGED
@@ -1,19 +1,19 @@
1
1
  "use strict";
2
2
  const path = require("path");
3
+ const fs = require("fs");
4
+ const stream = require("stream");
3
5
  const util_1 = require("@e-mc/cloud/util");
4
6
  const types_1 = require("@e-mc/types");
5
- const Module = require("@e-mc/module");
6
7
  const Cloud = require("@e-mc/cloud");
7
8
  const client_1 = require("@pi-r/azure");
8
9
  const BUCKET_SESSION = new Set();
9
10
  const BUCKET_RESPONSE = {};
10
- const getBucketKey = (credential, bucket, acl = '') => Module.asString(credential, true) + bucket + '_' + acl;
11
+ const getBucketKey = (credential, bucket, acl = '') => Cloud.asString(credential, true) + bucket + '_' + acl;
11
12
  module.exports = function upload(credential, service = 'azure') {
12
13
  const blobServiceClient = client_1.createStorageClient.call(this, credential);
13
14
  return async (data, callback) => {
14
- var _a, _b;
15
15
  const { bucket, localUri } = data;
16
- const { pathname = '', fileGroup, contentType, metadata, tags, endpoint, admin = {}, overwrite, options } = data.upload;
16
+ const { pathname = '', flags = 0, fileGroup, contentType, metadata, tags, endpoint, admin = {}, overwrite, options } = data.upload;
17
17
  const containerClient = blobServiceClient.getContainerClient(bucket);
18
18
  let filename = data.upload.filename || path.basename(localUri), bucketKey;
19
19
  const cleanup = () => {
@@ -30,7 +30,7 @@ module.exports = function upload(credential, service = 'azure') {
30
30
  const configBucket = admin.configBucket;
31
31
  if (!BUCKET_SESSION.has(bucket)) {
32
32
  const bucketAcl = admin.publicRead ? 'blob' : admin.acl;
33
- const response = BUCKET_RESPONSE[_a = bucketKey = getBucketKey(credential, bucket, bucketAcl)] || (BUCKET_RESPONSE[_a] = client_1.createBucketV2.call(this, credential, bucket, bucketAcl, configBucket?.create));
33
+ const response = BUCKET_RESPONSE[bucketKey = getBucketKey(credential, bucket, bucketAcl)] ||= client_1.createBucketV2.call(this, credential, bucket, bucketAcl, configBucket?.create);
34
34
  if (!await response) {
35
35
  errorResponse(null);
36
36
  return;
@@ -42,12 +42,19 @@ module.exports = function upload(credential, service = 'azure') {
42
42
  if (!Array.isArray(retentionPolicy)) {
43
43
  retentionPolicy = [retentionPolicy];
44
44
  }
45
+ try {
46
+ if (this.hasCoerce(service, 'options')) {
47
+ retentionPolicy.forEach(policy => (0, types_1.coerceObject)(policy));
48
+ }
49
+ }
50
+ catch {
51
+ }
45
52
  containerClient.setAccessPolicy('container', retentionPolicy)
46
53
  .then(() => {
47
54
  const { expiresOn, startsOn, permissions } = retentionPolicy[0].accessPolicy;
48
55
  this.formatMessage(64, service, ["Bucket configured" + ' (retention policy)', bucket], expiresOn ? expiresOn.toLocaleString() + ' (expires)' : startsOn ? startsOn.toLocaleString() + ' (starts)' : permissions, { ...Cloud.LOG_CLOUD_COMMAND });
49
56
  })
50
- .catch(err => addLog(err));
57
+ .catch((err) => addLog(err));
51
58
  }
52
59
  if (!overwrite) {
53
60
  try {
@@ -61,7 +68,7 @@ module.exports = function upload(credential, service = 'azure') {
61
68
  break;
62
69
  }
63
70
  }
64
- const name = pathname ? Module.joinPath(pathname, filename) : filename;
71
+ const name = pathname ? Cloud.joinPath(pathname, filename) : filename;
65
72
  for await (const blob of containerClient.listBlobsFlat({ includeUncommitedBlobs: true })) {
66
73
  if (blob.name === name) {
67
74
  exists = true;
@@ -78,14 +85,50 @@ module.exports = function upload(credential, service = 'azure') {
78
85
  return;
79
86
  }
80
87
  }
88
+ const chunkSize = flags & 4 ? (0, types_1.alignSize)(data.upload.chunkSize, 1024) : 0;
89
+ const maxSingleShotSize = Math.max(chunkSize, 8388608);
81
90
  const Key = [filename];
82
91
  const Body = [data.buffer];
92
+ const Path = [];
93
+ const Stream = [];
83
94
  const ContentType = [contentType];
95
+ const getChunkSize = () => chunkSize > 0 ? maxSingleShotSize : undefined;
96
+ if (Body[0].length === 0) {
97
+ try {
98
+ if (chunkSize > 0 && fs.statSync(localUri).size > maxSingleShotSize) {
99
+ Path[0] = localUri;
100
+ }
101
+ else if (flags & 2) {
102
+ Stream[0] = fs.createReadStream(localUri, { highWaterMark: getChunkSize(), signal: this.signal });
103
+ }
104
+ else {
105
+ Body[0] = fs.readFileSync(localUri);
106
+ }
107
+ }
108
+ catch (err) {
109
+ errorResponse(err);
110
+ return;
111
+ }
112
+ }
113
+ else if ((flags & 2) || chunkSize > 0) {
114
+ Stream[0] = stream.Readable.from(Body[0], { highWaterMark: getChunkSize() });
115
+ }
84
116
  if (fileGroup) {
85
- const [key, body, type] = (0, util_1.createKeyAndBody)(filename, fileGroup, addLog);
117
+ const [key, body, type] = (0, util_1.createKeyAndBody)(filename, fileGroup, chunkSize > 0 ? maxSingleShotSize : 0, addLog, flags);
86
118
  Key.push(...key);
87
- Body.push(...body);
88
119
  ContentType.push(...type);
120
+ for (let i = 0; i < body.length; ++i) {
121
+ const item = body[i];
122
+ if (typeof item === 'string') {
123
+ Path[i + 1] = item;
124
+ }
125
+ else if (Buffer.isBuffer(item)) {
126
+ Body[i + 1] = item;
127
+ }
128
+ else {
129
+ Stream[i + 1] = item;
130
+ }
131
+ }
89
132
  }
90
133
  for (let i = 0; i < Key.length; ++i) {
91
134
  const first = i === 0;
@@ -95,28 +138,47 @@ module.exports = function upload(credential, service = 'azure') {
95
138
  }
96
139
  return;
97
140
  }
98
- const blobName = Module.joinPath(pathname, Key[i]);
141
+ const blobName = Cloud.joinPath(pathname, Key[i]);
99
142
  const params = { ...options, abortSignal: this.signal };
143
+ const headers = params.blobHTTPHeaders ||= {};
100
144
  if (first) {
101
- if ((0, types_1.isPlainObject)(metadata)) {
145
+ if ((0, types_1.isObject)(metadata)) {
102
146
  params.metadata = metadata;
103
147
  }
104
- if ((0, types_1.isPlainObject)(tags)) {
148
+ if ((0, types_1.isObject)(tags)) {
105
149
  params.tags = tags;
106
150
  }
151
+ headers.blobContentType ||= ContentType[i];
152
+ }
153
+ else {
154
+ headers.blobContentType = ContentType[i];
155
+ }
156
+ const blobClient = containerClient.getBlockBlobClient(blobName);
157
+ let uploadClient;
158
+ if (Stream[i]) {
159
+ uploadClient = blobClient.uploadStream(Stream[i], getChunkSize(), chunkSize > 0 ? data.upload.chunkLimit : undefined, params);
160
+ }
161
+ else if (Path[i]) {
162
+ params.maxSingleShotSize = maxSingleShotSize;
163
+ params.concurrency = data.upload.chunkLimit;
164
+ uploadClient = blobClient.uploadFile(Path[i], params);
165
+ }
166
+ else if (chunkSize > 0 && Body[i].byteLength > maxSingleShotSize) {
167
+ params.maxSingleShotSize = maxSingleShotSize;
168
+ params.concurrency = data.upload.chunkLimit;
169
+ uploadClient = blobClient.uploadData(Body[i], params);
107
170
  }
108
- (_b = (params.blobHTTPHeaders || (params.blobHTTPHeaders = {}))).blobContentType || (_b.blobContentType = ContentType[i]);
109
- containerClient.getBlockBlobClient(blobName).upload(Body[i], Body[i].byteLength, params)
171
+ (uploadClient || blobClient.upload(Body[i], Body[i].byteLength, params))
110
172
  .then(result => {
111
173
  let requestUrl = result._response.request.url;
112
- const url = endpoint ? Module.joinPath(endpoint, blobName) : Module.isFile(requestUrl = decodeURIComponent(requestUrl), 'http/s') ? requestUrl : Module.joinPath(`https://${credential.accountName}.blob.core.windows.net`, bucket, blobName);
174
+ const url = endpoint ? Cloud.joinPath(endpoint, blobName) : Cloud.isFile(requestUrl = decodeURIComponent(requestUrl), 'http/s') ? requestUrl : Cloud.joinPath(`https://${credential.accountName}.blob.core.windows.net`, bucket, blobName);
113
175
  this.formatMessage(64, service, "Upload success", url, { ...Cloud.LOG_CLOUD_UPLOAD });
114
176
  if (first) {
115
177
  cleanup();
116
178
  callback(null, url);
117
179
  }
118
180
  })
119
- .catch(err => {
181
+ .catch((err) => {
120
182
  if (first) {
121
183
  errorResponse(err);
122
184
  }