Skip to content

Instantly share code, notes, and snippets.

@matthewgream
Created October 14, 2023 11:19
Show Gist options
  • Save matthewgream/436b184afb1adb15415b6f86640a8ecf to your computer and use it in GitHub Desktop.
Save matthewgream/436b184afb1adb15415b6f86640a8ecf to your computer and use it in GitHub Desktop.
// -----------------------------------------------------------------------------------------------------------------------------------------
// -----------------------------------------------------------------------------------------------------------------------------------------
// https://cloud.google.com/storage/docs/gcsfuse-quickstart-mount-bucket
function gcloud_storage_test () { __TEST_SETUP ();
}
// -----------------------------------------------------------------------------------------------------------------------------------------
// -----------------------------------------------------------------------------------------------------------------------------------------
// https://cloud.google.com/storage/docs/json_api/v1
const gcloud_storage_path = (bucket, type = undefined) => `https://storage.googleapis.com/${type ? (type + '/') : ''}storage/v1/b/${bucket}/`;
const gcloud_storage_get = (bucket, name, type = 'text') => __gcloud_request_media_get (gcloud_storage_path (bucket) + `o/${name}?alt=media`, 'GET', type);
const gcloud_storage_put = (bucket, name, data, type) => __gcloud_request_media_put (gcloud_storage_path (bucket, 'upload') + `o?uploadType=media&name=${name}`, 'POST', data, type);
// -----------------------------------------------------------------------------------------------------------------------------------------
// -----------------------------------------------------------------------------------------------------------------------------------------
const __gcloud_firebase_encode = (data) => __gcloud_firebase_data_to (data);
const __gcloud_firebase_decode = (data) => !util_is_null (data) && !util_is_null (data.fields) ? __gcloud_firebase_data_fr (data.fields) : undefined;
const __gcloud_firebase_enpack = (data) => GZip.compressToBase64 (data);
const __gcloud_firebase_depack = (data) => !util_is_null (data) ? GZip.decompressFromBase64 (data) : undefined;
const __gcloud_firebase_sizeok = (data, mesg) => data.length <= 1048487 ? data : util_throw (`data too large at: ${mesg} [${data.length}]`);
const __gcloud_firebase_data_type_handle = (handlers, type, callback) =>
handlers [type] ? callback (handlers [type], type): util_throw (`unexpected type '${type}'`);
const __gcloud_firebase_data_type_mapper = (handlers, object, callback_type, callback_data) => Object.keys (object).reduce ((mapping, key) =>
__gcloud_firebase_data_type_handle (handlers, callback_type (object, key), (handler, type) => ({ ...mapping, [key]: handler (callback_data (object, key, type)) })), {});
const __gcloud_firebase_data_to = (object) => {
const __type_mapper = (object) => __gcloud_firebase_data_type_mapper (__type_handle, object, (obj, key) => typeof obj [key], (obj, key) => obj [key]);
const __type_handle = { 'undefined': (nullValue) => ({ nullValue }), number: (doubleValue) => ({ doubleValue }), string: (stringValue) => ({ stringValue }),
boolean: (booleanValue) => ({ booleanValue }), object: (objectValue) => { if (objectValue === null) return { 'nullValue': null }; // OK
else if (Array.isArray (objectValue)) return { 'arrayValue': { 'values': objectValue.map (v => __gcloud_firebase_data_type_handle (__type_handle, typeof v, (handler) => handler (v))) } };
else return { 'mapValue': { 'fields': __type_mapper (objectValue) } }; } };
return __type_mapper (object);
}
const __gcloud_firebase_data_fr = (object) => {
const __type_mapper = (object) => __gcloud_firebase_data_type_mapper (__type_handle, object, (obj, key) => Object.keys (obj [key]) [0], (obj, key, type) => obj [key] [type]);
const __type_handle = { nullValue: (v) => v, doubleValue: (v) => v, integerValue: (v) => v, stringValue: (v) => v, booleanValue: (v) => v,
mapValue: (v) => v.fields ? __type_mapper (v.fields) : {},
arrayValue: (v) => v.values ? v.values.map (v => __gcloud_firebase_data_type_handle (__type_handle, Object.keys (v) [0], (handler, type) => handler (v [type]))) : Array () };
return __type_mapper (object);
}
const __gcloud_firebase_data_is_bad = (x) => util_obj_has_invalid (x);
const __gcloud_firebase_data_field = (fieldPath) => ({ fieldPath });
const __gcloud_firebase_data_op_field = (o) => ({ 'has': 'ARRAY_CONTAINS', 'has_any': 'ARRAY_CONTAINS_ANY', 'in': 'IN', '!in': 'NOT_IN',
'<' : 'LESS_THAN', '<=': 'LESS_THAN_OR_EQUAL', '=': 'EQUAL', '>': 'GREATER_THAN', '>=': 'GREATER_THAN_OR_EQUAL', '!=': 'NOT_EQUAL' } [o])
|| util_throw (`bad operator: '${o}'`)
const __gcloud_firebase_data_op_unary = (o, v) => ((o == '=' || o == '!=') && (v == undefined || isNaN (v))) ? // OK
((o == '=') ? (isNaN (v) ? 'IS_NAN' : 'IS_NULL') : (isNaN (v) ? 'IS_NOT_NAN' : 'IS_NOT_NULL')) : util_throw (`bad operator/value combination: '${o}'/'${v}'`);
const __gcloud_firebase_data_value = (v) => ({ 'undefined': { 'nullValue': v }, 'string': { 'stringValue' : v }, 'number': { 'doubleValue': v }, 'boolean': { 'booleanValue': v }, }
[v == undefined ? 'undefined' : typeof v]) || util_throw (`unsupported type: '${typeof v}'`); // eslint-disable-line no-unexpected-multiline
const __gcloud_firebase_filter_field = (f, o, v) =>
({ 'fieldFilter': { 'field': __gcloud_firebase_data_field (f), 'op': __gcloud_firebase_data_op_field (o), 'value': __gcloud_firebase_data_value (v) }});
const __gcloud_firebase_filter_unary = (f, o, v) =>
({ 'unaryFilter': { 'field': __gcloud_firebase_data_field (f), 'op': __gcloud_firebase_data_op_unary (o, v) }});
const __gcloud_firebase_filter = (f, o, v) =>
(v == undefined || (typeof v != 'string' && isNaN (v))) ? __gcloud_firebase_filter_unary (f, o, v) : __gcloud_firebase_filter_field (f, o, v); // OK
// -----------------------------------------------------------------------------------------------------------------------------------------
// -----------------------------------------------------------------------------------------------------------------------------------------
const __gcloud_firebase_errok = (result) => result && !result.error ? result : undefined;
const __gcloud_firebase_pathx = (path) => Array.isArray (path) ? path.join ('/') : path;
const __gcloud_firebase_field = (data, fields) => data ? fields.map (fields => fields.reduce ((posn, field) => posn?.[field] ? posn [field] : undefined, data.fields)) : undefined;
const gcloud_firebase_patch = (project, path, data) => __gcloud_firebase_errok (__gcloud_firebase_request (project, __gcloud_firebase_pathx (path), 'PATCH', { 'fields': data }));
const gcloud_firebase_post = (project, path, data = undefined) => __gcloud_firebase_errok (__gcloud_firebase_request (project, __gcloud_firebase_pathx (path), 'POST', data));
const gcloud_firebase_get = (project, path) => __gcloud_firebase_errok (__gcloud_firebase_request (project, __gcloud_firebase_pathx (path), 'GET'));
// -----------------------------------------------------------------------------------------------------------------------------------------
const gcloud_firebase_puts = (project, path, data, time) => gcloud_firebase_patch (project, path, { 'content': { 'stringValue': data }, '_time': { 'stringValue': time } });
const gcloud_firebase_gets = (project, path) => __gcloud_firebase_field (gcloud_firebase_get (project, path), [ ['content', 'stringValue'], ['_time', 'stringValue'] ]);
const gcloud_firebase_list = (project, path) => __gcloud_firebase_request_paged (
(coll, page) => page.documents ? [...coll, ...page.documents.map (v => v.name.substring (v.name.lastIndexOf ('/') + 1))] : coll, Array (),
project, __gcloud_firebase_pathx (path) + '?mask.fieldPaths=__name__').flat (Infinity);
const __gcloud_firebase_batchPath = (project, path) =>
__gcloud_firebase_db (project) + __gcloud_firebase_pathx (path);
const __gcloud_firebase_batchWrite = (project, operations) => {
var counter = 0, retries = 5; while (counter < operations.length && retries > 0) {
const response = util_exception_wrapper3 (() => gcloud_firebase_post (project, 'documents:batchWrite', { 'writes': operations.slice (counter, counter + 500) }));
if (response) {
if (response.writeResults)
counter += response.writeResults.length;
} else retries --;
}
return counter;
}
const __gcloud_firebase_batchGet = (project, paths) => {
var counter = 0, documents = Array (), retries = 5; while (counter < paths.length && retries > 0) {
const response = util_exception_wrapper3 (() => gcloud_firebase_post (project, 'documents:batchGet', { 'documents': paths.slice (counter, counter + 250) }));
if (response) {
documents.push (response.filter (r => r.found).map (r => r.found));
counter += response.length;
} else retries --;
}
return documents.flat ();
}
const gcloud_firebase_deletes = (project, paths) =>
__gcloud_firebase_batchWrite (project, paths.map (path => ({ 'delete': __gcloud_firebase_batchPath (project, path) })));
const gcloud_firebase_writes = (project, paths_data) =>
__gcloud_firebase_batchWrite (project, paths_data.map (([path, data]) => ({ 'update': { 'name': __gcloud_firebase_batchPath (project, path), 'fields': data } })));
const gcloud_firebase_reads = (project, paths) =>
__gcloud_firebase_batchGet (project, paths.map (path => __gcloud_firebase_batchPath (project, path)));
// https://firebase.google.com/docs/firestore/reference/rest/v1/projects.databases.documents/runAggregationQuery
// https://firebase.google.com/docs/firestore/reference/rest/v1/StructuredQuery
class gcloud_firebase_query {
constructor () { this._query = { }; }
__init (x, v, z) {
if (! x [v]) x [v] = z; return this; }
__push (x, v, y) {
Object.entries (y).forEach (([z, a]) => util_array_always (a).forEach (_z => x [v].push ({ [z]: _z }))); return this; }
__make () {
const structuredQuery = { 'structuredQuery': this._query };
if (this._count) {
const aggregations = this._count.map ((upTo, i) => ({ 'count': upTo > 0 ? { upTo } : { }, ...(this._as && this._as [i]) ? { 'alias': this._as [i] } : { } }));
return [`runAggregationQuery`, { 'structuredAggregationQuery': ({ ...structuredQuery, aggregations }) } ];
} else
return [`runQuery`, structuredQuery];
}
select (field) { return this.__init (this._query, 'select', { 'fields': Array () })
.__push (this._query ['select'], 'fields', __gcloud_firebase_data_field (field)); }
from (collectionId) { return this.__init (this._query, 'from', Array ())
.__push (this._query, 'from', { collectionId }); }
where (field, op, value = undefined) {
return this.__init (this._query, 'where', { 'compositeFilter': { 'op': 'AND', 'filters': Array () } })
.__push (this._query ['where'] ['compositeFilter'], 'filters', __gcloud_firebase_filter (field, op, value)); }
limit (x) { // > 0
this._query ['limit'] = x; return this; }
count (upto = 0) { // only 5 allowed
if (!this._count) this._count = Array (); this._count.push (String (upto)); return this; }
as (name) { // must already have a count
if (!this._as) this._as = Array (); this._as.push (name); return this; }
request (project) {
const [command, content] = this.__make ();
return util_exception_wrapper3 (() => gcloud_firebase_post (project, `documents:${command}`, content)); }
}
// -----------------------------------------------------------------------------------------------------------------------------------------
// https://firebase.google.com/docs/firestore/reference/rest/v1/projects.databases.documents/listDocuments
const __gcloud_firebase_api = () => `https://firestore.googleapis.com/v1/`;
const __gcloud_firebase_db = (project, database = '(default)') => `projects/${project}/databases/${database}/`;
const __gcloud_firebase_path = (project, database = '(default)') => __gcloud_firebase_api () + __gcloud_firebase_db (project, database);
const __gcloud_firebase_request = (project, path, method = 'GET', payload = undefined) => __gcloud_request (__gcloud_firebase_path (project) + path, method, payload);
const __gcloud_firebase_request_paged = (callback, x, project, path, method = 'GET', payload = undefined) => { var page, pageToken = undefined, pageSize = 100000;
do { page = __gcloud_firebase_errok (__gcloud_firebase_request (project, util_str_url_args_append (path, { pageSize, pageToken }), method, payload));
} while (!util_is_null (page) && !util_is_null (x = callback (x, page)) && !util_is_null (pageToken = page.nextPageToken)); return x; }
// -----------------------------------------------------------------------------------------------------------------------------------------
// -----------------------------------------------------------------------------------------------------------------------------------------
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment