Created
October 14, 2023 11:19
-
-
Save matthewgream/436b184afb1adb15415b6f86640a8ecf to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ----------------------------------------------------------------------------------------------------------------------------------------- | |
// ----------------------------------------------------------------------------------------------------------------------------------------- | |
// 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