Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Separate out the ServiceClient #1

Merged
merged 14 commits into from
Mar 12, 2024
43 changes: 43 additions & 0 deletions lib/debug.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Factory+ JS Service Client
* Debugging / logging support.
* Copyright 2022 AMRC.
*/

import util from "util";

export class Debug {
constructor (opts) {
opts ??= {};
const verb = opts.verbose
?? opts.default
?? "";

this.levels = new Set();
this.suppress = new Set();
this.verbose = false;

for (const lev of verb.split(",")) {
if (lev == "1" || lev == "ALL")
this.verbose = true;
else if (lev.startsWith("!"))
this.suppress.add(lev.slice(1))
else
this.levels.add(lev);
}
}

log (level, msg, ...args) {
const want = this.verbose || this.levels.has(level);
if (!want || this.suppress.has(level))
return;

const out = util.format(msg, ...args);
const spc = " ".repeat(Math.max(0, 8 - level.length));
console.log(`${level}${spc} : ${out}`);
}

bound (level) {
return this.log.bind(this, level);
}
}
34 changes: 34 additions & 0 deletions lib/deps.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* Factory+ NodeJS Utilities
* Re-exports of libraries we use.
* Copyright 2024 AMRC
*/

/* No GSS on Windows. */
export const GSS = await
import("gssapi.js")
.then(mod => mod.default)
.catch(e => undefined);

/* Annoying re-export syntax... If you find yourself having to document
* 'you can't do `export Foo from "foo"`' then maybe you should design
* the syntax so you can... ? */
export { default as MQTT } from "mqtt";

export async function build_node_fetch () {
/* We have to go round the houses a bit here... */
const { got } = await import("got");
const { createFetch } = await import("got-fetch");

const configured_got = got.extend({
cacheOptions: { shared: false },
});
const got_fetch = createFetch(configured_got);

/* Bug fix */
return (url, opts) => got_fetch(`${url}`, opts);
}

import sparkplug_payload from "sparkplug-payload";
export const SpB = sparkplug_payload.get("spBv1.0");

10 changes: 10 additions & 0 deletions lib/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export * from "./debug.js";
export * from "./service-client.js";
export * from "./service/service-interface.js"

export * as UUIDs from "./uuids.js";

export * from "./sparkplug/util.js";

/* XXX I don't want to export these; maybe I want a /deps library? */
export { GSS, MQTT, SpB } from "./deps.js";
86 changes: 86 additions & 0 deletions lib/service-client.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/*
* Factory+ NodeJS Utilities
* Service client library.
* Copyright 2022 AMRC
*/

import { Debug } from "./debug.js";
import { Service } from "./uuids.js";

import Auth from "./service/auth.js";
import CmdEsc from "./service/cmdesc.js";
import ConfigDB from "./service/configdb.js";
import Directory from "./service/directory.js";
import Discovery from "./service/discovery.js";
import Fetch from "./service/fetch.js";
import Git from "./service/git.js";
import MQTTInterface from "./service/mqtt.js";

function opts_from_env (env) {
const opts = `
AUTHN_URL
CONFIGDB_URL
DIRECTORY_URL
MQTT_URL
ROOT_PRINCIPAL
SERVICE_USERNAME:username
SERVICE_PASSWORD:password
VERBOSE
` .split(/\s+/)
.map(v => v.includes(":") ? v.split(":") : [v, v.toLowerCase()])
.filter(v => v[0] in env)
.map(v => [v[1], env[v[0]]]);
return Object.fromEntries(opts);
}

export class ServiceClient {
constructor (opts) {
opts ??= {};
this.opts = "env" in opts
? { ...opts_from_env(opts.env), ...opts }
: opts;
delete this.opts.env;

this.debug = new Debug(opts);
}

async init () {
return this;
}

static define_interfaces (...interfaces) {
for (const [name, klass, methlist] of interfaces) {
Object.defineProperty(this.prototype, name, {
configurable: true,
get () { return this[`_${name}`] ??= new klass(this); },
});

const meths = methlist.split(/\s+/).filter(s => s.length);
for (const meth of meths) {
const [mine, theirs] = meth.split(":");
Object.defineProperty(this.prototype, mine, {
configurable: true,
writable: true,
value (...args) {
return this[name][theirs ?? mine](...args);
},
});
}
}
}
}

/* The methods delegeted here from the ServiceClient should be
* considered backwards-compatible shims. Future service methods will
* mostly be defined only on the service interface. */
ServiceClient.define_interfaces(
["Auth", Auth, `check_acl fetch_acl resolve_principal`],
["CmdEsc", CmdEsc, ``],
["ConfigDB", ConfigDB, `fetch_configdb:get_config`],
["Directory", Directory, ``],
["Discovery", Discovery,
`set_service_url set_service_discovery service_url service_urls`],
["Fetch", Fetch, `fetch`],
["Git", Git, ``],
["MQTT", MQTTInterface, `mqtt_client`],
);
Loading