busroot-sdk 0.0.0 → 0.0.2

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,62 @@
1
+ import { PlantSchema, PlantViewModel, ScheduleViewModel, BusrootSignals, SkuViewModel, StationSchema, StationViewModel, AccountViewModel, ApiKeyViewModel } from "./common";
2
+ import mqtt from "mqtt/dist/mqtt.esm";
3
+ export declare const MqttEventType: {
4
+ StationMetricNew: string;
5
+ StationChanged: string;
6
+ ScheduleChanged: string;
7
+ ScheduleStarted: string;
8
+ ScheduleEnded: string;
9
+ ProductionNew: string;
10
+ };
11
+ export type MqttEventType = keyof typeof MqttEventType;
12
+ export type Listener = (msg: any) => void;
13
+ declare class Client {
14
+ config: {
15
+ accountId: string;
16
+ apiKey: string;
17
+ host: string;
18
+ };
19
+ mqttClient: mqtt.MqttClient;
20
+ listenersByEventType: Map<"StationMetricNew" | "StationChanged" | "ScheduleChanged" | "ScheduleStarted" | "ScheduleEnded" | "ProductionNew", Set<Listener>>;
21
+ apiUrl: string;
22
+ constructor(config: {
23
+ accountId: string;
24
+ apiKey: string;
25
+ host: string;
26
+ });
27
+ destroy(): void;
28
+ private get;
29
+ private post;
30
+ get profile(): {
31
+ get: () => Promise<any>;
32
+ };
33
+ get account(): {
34
+ create: (domain: string, plantName: string) => Promise<{
35
+ account: AccountViewModel;
36
+ apiKey: string;
37
+ }>;
38
+ };
39
+ get apiKey(): {
40
+ create: () => Promise<ApiKeyViewModel>;
41
+ };
42
+ get plant(): {
43
+ get: (plantCode: string) => Promise<PlantViewModel>;
44
+ createUpdate: (station: Partial<PlantSchema>) => Promise<PlantViewModel>;
45
+ };
46
+ get station(): {
47
+ get: (stationCode: string) => Promise<StationViewModel[]>;
48
+ createUpdate: (station: Partial<StationSchema>) => Promise<StationViewModel>;
49
+ signal: (stationCode: string, payload: BusrootSignals) => void;
50
+ };
51
+ sku: {
52
+ get: (skuCode: string) => Promise<SkuViewModel>;
53
+ createUpdate: (sku: Partial<SkuViewModel>) => Promise<SkuViewModel>;
54
+ };
55
+ schedule: {
56
+ get: (scheduleId: number) => Promise<ScheduleViewModel[]>;
57
+ createUpdate: (schedule: Partial<ScheduleViewModel>) => Promise<ScheduleViewModel>;
58
+ };
59
+ on(event: MqttEventType, listener: Listener): void;
60
+ off(event: MqttEventType, listener: Listener): void;
61
+ }
62
+ export { Client };
@@ -0,0 +1,176 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.Client = exports.MqttEventType = void 0;
7
+ const mqtt_esm_1 = __importDefault(require("mqtt/dist/mqtt.esm"));
8
+ exports.MqttEventType = {
9
+ StationMetricNew: "STATION_METRIC_NEW", // When a new station window is created and saved to DB.
10
+ StationChanged: "STATION_CHANGED", // When a station is added or edited. NOT IMPLEMENTED.
11
+ ScheduleChanged: "SCHEDULE_CHANGED", // When a schedule is added or edited. NOT IMPLEMENTED.
12
+ ScheduleStarted: "SCHEDULE_STARTED", // When a schedule starts (actualStartAt). NOT IMPLEMENTED.
13
+ ScheduleEnded: "SCHEDULE_ENDED", // When a schedule ends (actualEndAt). NOT IMPLEMENTED.
14
+ ProductionNew: "PRODUCTION_NEW", // When new production is added. NOT IMPLEMENTED FOR MANUAL PRODUCTION YET.
15
+ };
16
+ class Client {
17
+ constructor(config) {
18
+ this.config = config;
19
+ this.listenersByEventType = new Map();
20
+ this.sku = {
21
+ get: async (skuCode) => {
22
+ const res = await this.get("/sku", { skucodes: skuCode });
23
+ return res.result;
24
+ },
25
+ createUpdate: async (sku) => {
26
+ const res = await this.post("/sku", sku);
27
+ return res.result;
28
+ },
29
+ };
30
+ this.schedule = {
31
+ get: async (scheduleId) => {
32
+ const res = await this.get("/schedule", { ids: String(scheduleId) });
33
+ return res.results;
34
+ },
35
+ createUpdate: async (schedule) => {
36
+ const res = await this.post("/schedule", schedule);
37
+ return res.result;
38
+ },
39
+ };
40
+ this.apiUrl = "http://" + config.host + ":3000/api";
41
+ this.mqttClient = mqtt_esm_1.default.connect({
42
+ host: config.host,
43
+ port: 8883,
44
+ username: "api-key",
45
+ password: this.config.apiKey,
46
+ clientId: this.config.accountId + "/sdk",
47
+ });
48
+ this.mqttClient.on("message", (topic, payload) => {
49
+ const eventType = topic.toUpperCase().split("/").at(-1);
50
+ for (const [listenerEventType, listeners] of this.listenersByEventType) {
51
+ if (exports.MqttEventType[listenerEventType] !== eventType) {
52
+ continue;
53
+ }
54
+ for (const listener of listeners) {
55
+ try {
56
+ listener(JSON.parse(payload.toString()));
57
+ }
58
+ catch (e) {
59
+ //
60
+ }
61
+ }
62
+ }
63
+ });
64
+ this.mqttClient.on("error", (error) => {
65
+ console.error(error);
66
+ });
67
+ this.mqttClient.on("connect", () => {
68
+ console.log("Connected to Busroot MQTT...");
69
+ });
70
+ this.mqttClient.on("reconnect", () => {
71
+ console.log("Disconnected from Busroot MQTT...");
72
+ });
73
+ this.mqttClient.subscribe(`busroot/v2/${this.config.accountId}/events/#`);
74
+ }
75
+ destroy() {
76
+ this.mqttClient.end();
77
+ }
78
+ async get(path, query) {
79
+ let url = this.apiUrl + path;
80
+ if (query != null) {
81
+ url +=
82
+ "?" +
83
+ Object.entries(query)
84
+ .map((value) => value[0] + "=" + value[1])
85
+ .join("&");
86
+ }
87
+ const res = await fetch(url, {
88
+ headers: { "x-api-key": this.config.apiKey },
89
+ method: "GET",
90
+ });
91
+ if (res.status !== 200) {
92
+ throw new Error(`${res.status}: ${res.statusText}`);
93
+ }
94
+ const resJson = await res.json();
95
+ return resJson;
96
+ }
97
+ async post(path, body) {
98
+ const res = await fetch(this.apiUrl + path, {
99
+ headers: { "x-api-key": this.config.apiKey, "Content-Type": "application/json" },
100
+ method: "POST",
101
+ body: JSON.stringify(body),
102
+ });
103
+ const resJson = await res.json();
104
+ return resJson;
105
+ }
106
+ get profile() {
107
+ return {
108
+ get: async () => {
109
+ const res = await this.get("/auth/profile");
110
+ return res.result;
111
+ },
112
+ };
113
+ }
114
+ get account() {
115
+ return {
116
+ create: async (domain, plantName) => {
117
+ const res = await this.post("/account/new", { domain, plantName });
118
+ return res.result;
119
+ },
120
+ };
121
+ }
122
+ get apiKey() {
123
+ return {
124
+ create: async () => {
125
+ const res = await this.post("/api-key", {});
126
+ return res.result;
127
+ },
128
+ };
129
+ }
130
+ get plant() {
131
+ return {
132
+ get: async (plantCode) => {
133
+ const res = await this.get("/plant", { plantCode });
134
+ return res.result;
135
+ },
136
+ createUpdate: async (station) => {
137
+ const res = await this.post("/station", station);
138
+ return res.result;
139
+ },
140
+ };
141
+ }
142
+ get station() {
143
+ return {
144
+ get: async (stationCode) => {
145
+ const res = await this.get("/station", { stationcodes: stationCode });
146
+ return res.results;
147
+ },
148
+ createUpdate: async (station) => {
149
+ const res = await this.post("/station", station);
150
+ return res.result;
151
+ },
152
+ signal: (stationCode, payload) => {
153
+ throw new Error("noop");
154
+ },
155
+ };
156
+ }
157
+ on(event, listener) {
158
+ let set = this.listenersByEventType.get(event);
159
+ if (!set) {
160
+ set = new Set();
161
+ this.listenersByEventType.set(event, set);
162
+ }
163
+ set.add(listener);
164
+ }
165
+ off(event, listener) {
166
+ const set = this.listenersByEventType.get(event);
167
+ if (!set) {
168
+ return;
169
+ }
170
+ set.delete(listener);
171
+ if (set.size === 0) {
172
+ this.listenersByEventType.delete(event);
173
+ }
174
+ }
175
+ }
176
+ exports.Client = Client;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const globals_1 = require("@jest/globals");
4
+ (0, globals_1.describe)("test", () => {
5
+ (0, globals_1.test)("test", () => { });
6
+ });
@@ -0,0 +1,276 @@
1
+ import { z } from 'zod';
2
+
3
+ interface BusrootSignals {
4
+ timestamp?: number;
5
+ production_complete?: number;
6
+ bad_production_complete?: number;
7
+ production_count?: number;
8
+ production_count_good?: number;
9
+ bad_production_count?: number;
10
+ production_count_bad?: number;
11
+ sku_code?: string;
12
+ productive?: number;
13
+ line_speed?: number;
14
+ speed?: number;
15
+ status_code?: string;
16
+ electrical_kwh?: number;
17
+ electrical_kwh_interval?: number;
18
+ electrical_v?: number;
19
+ electrical_a?: number;
20
+ electrical_kw?: number;
21
+ electrical_hz?: number;
22
+ electrical_pf?: number;
23
+ rssi?: number;
24
+ }
25
+
26
+ interface PlantSchema {
27
+ code: string;
28
+ name: string;
29
+ timezone: string;
30
+ accountId: string;
31
+ dayStartHour: number;
32
+ createdAt?: string;
33
+ updatedAt?: string;
34
+ archivedAt?: string;
35
+ }
36
+
37
+ declare const StationSchema = z.object({
38
+ // Basics
39
+ accountId: z.string(),
40
+ code: z.string(),
41
+ name: z.string().trim().min(1).max(32),
42
+ groupCode: z.string(),
43
+ shiftPatternId: z.number().int().positive().nullish(),
44
+ moduleVisibility: z.array(z.nativeEnum(STATION_MODULE_VISIBILITY)).nullish(),
45
+ minimumCycleTime: z.number().int().gte(0).lte(TimeIntervals["12h"].ms).nullish(),
46
+ speedScale: z.number().gte(-9999).lte(9999).nullish(),
47
+ speedOffset: z.number().gte(-9999).lte(9999).nullish(),
48
+ speedToProductionRatio: z.number().gte(0).lte(99999).nullish(),
49
+ electricalPowerNominalKw: z.number().gte(0).nullish(),
50
+
51
+ // Defaults
52
+ idleCostPerMinute: z.number().gte(0).lte(99999).nullish(),
53
+
54
+ // Downtime Settings
55
+ downtimeDetectionMode: z.nativeEnum(DOWNTIME_DETECTION_MODE).default(DOWNTIME_DETECTION_MODE.UTILISATION).optional(),
56
+ utilisationDowntimeThreshold: z.preprocess((val) => (val === null ? TimeIntervals["1h"].ms : val), z.number().int().gte(0).optional()),
57
+
58
+ slowDurationThreshold: z.number().int().gte(0).nullish(),
59
+ forceAllShiftTimeAsPlannedProduction: z.boolean().default(true),
60
+
61
+ // Productive Settings
62
+ productiveStatusMode: z.nativeEnum(PRODUCTIVE_STATUS_MODE).default(PRODUCTIVE_STATUS_MODE.PRODUCTIVE_SIGNAL).optional(),
63
+ electricalUsageStoppedThresholdKw: z.number().gte(0).nullish(),
64
+ lineSpeedStoppedThreshold: z.number().gte(0).nullish(),
65
+ productiveHoldOnTime: z.number().int().gte(0).lte(TimeIntervals["1d"].ms).nullish(),
66
+
67
+ // Flags
68
+ allowInterruptionMode: z.boolean().default(false).optional(),
69
+ allowManualProduction: z.boolean().default(false).optional(),
70
+ allowQuickScheduleStart: z.boolean().default(false).optional(),
71
+ allowScheduleCutShort: z.boolean().default(false).optional(),
72
+
73
+ // Current State
74
+ currentOperatorInitials: z.string().min(3).max(3).nullish(),
75
+ statusCode: z.string().nullish(),
76
+ statusCodeUpdatedAt: z.string().nullish(),
77
+
78
+ // Performance Mode
79
+ cycleTime: z.number().int().gt(0).lte(TimeIntervals["12h"].ms).nullish(),
80
+ unitsPerMinute: z.number().int().gt(0).nullish(),
81
+ unitsPerHour: z.number().int().gt(0).nullish(),
82
+ lineSpeedTarget: z.number().gt(0).lte(99999).nullish(),
83
+
84
+ // System info
85
+ createdAt: z.string().nullish(),
86
+ updatedAt: z.string().nullish(),
87
+ archivedAt: z.string().nullish(),
88
+ });
89
+
90
+ type StationSchema = z.infer<typeof StationSchema>;
91
+
92
+ declare const ApiKeySchema = z.object({
93
+ accountId: z.string(),
94
+ key: z.string(),
95
+ createdByUserId: z.number(),
96
+ expiresAt: z.string(),
97
+ createdAt: z.string().optional(),
98
+ updatedAt: z.string().optional(),
99
+ });
100
+ type ApiKeySchema = z.infer<typeof ApiKeySchema>;
101
+
102
+ declare const PlantViewModel = z.object({
103
+ code: z.string(),
104
+ name: z.string(),
105
+ timezone: z.string(),
106
+ now: z.string(),
107
+ stationGroups: z.array(StationGroupSchema.omit({ accountId: true })),
108
+ dayStartHour: z.number(),
109
+ });
110
+
111
+ type PlantViewModel = z.infer<typeof PlantViewModel>;
112
+
113
+ declare const SkuViewModel = z.object({
114
+ code: z.string(),
115
+ name: z.string(),
116
+ cycleTime: z.number().nullish(),
117
+ specifications: z.object({ name: z.string(), value: z.string() }).array().nullish(),
118
+ instructionsUrl: z.string().nullish(),
119
+ lineSpeedTarget: z.number().nullish(),
120
+ downtimeDurationThresholdMultiplier: z.number().nullish(),
121
+ allowUseInQuickSchedule: z.boolean().nullish(),
122
+ unitsPerMinute: z.number().nullish(),
123
+ unitsPerHour: z.number().nullish(),
124
+ batchSize: z.number().nullish(),
125
+ value: z.number().nullish(),
126
+ minimumCycleTime: z.number().nullish(),
127
+ });
128
+ type SkuViewModel = z.infer<typeof SkuViewModel>;
129
+
130
+ type ApiKeyViewModel = ApiKeySchema;
131
+
132
+ declare const ScheduleViewModel = z.object({
133
+ id: z.number(),
134
+ stationCode: z.string(),
135
+ stationName: z.string().optional(),
136
+ accountId: z.string(),
137
+ plannedStartAt: z.string(),
138
+ plannedStartAtTimestamp: z.number(),
139
+ plannedEndAt: z.string().optional(),
140
+ plannedEndAtTimestamp: z.number().optional(),
141
+ workOrderReference: z.string().optional(),
142
+ plannedDuration: z.number().optional(),
143
+ plannedQuantity: z.number().optional(),
144
+ nonProductionReasonCode: z.string().optional(),
145
+ nonProductionReasonDescription: z.string().optional(),
146
+ skuCode: z.string().optional(),
147
+ skuName: z.string().optional(),
148
+ skuValue: z.number().optional(),
149
+ batchSize: z.number().optional(),
150
+ autoEndOnQuantityComplete: z.boolean().optional(),
151
+
152
+ cycleTime: z.number().optional(),
153
+ unitsPerMinute: z.number().optional(),
154
+ unitsPerHour: z.number().optional(),
155
+ lineSpeedTarget: z.number().optional(),
156
+
157
+ effectiveCycleTime: z.number().optional(),
158
+ effectiveUnitsPerMinute: z.number().optional(),
159
+ effectiveUnitsPerHour: z.number().optional(),
160
+ effectiveLineSpeedTarget: z.number().optional(),
161
+ effectiveBatchSize: z.number().optional(),
162
+ effectiveMinimumCycleTime: z.number().optional(),
163
+
164
+ deletedAt: z.string().optional(),
165
+ actualStartAt: z.string().optional(),
166
+ actualStartAtTimestamp: z.number().optional(),
167
+ actualEndAt: z.string().optional(),
168
+ actualEndAtTimestamp: z.number().optional(),
169
+ idealEndAt: z.string().optional(),
170
+ idealEndAtTimestamp: z.number().optional(),
171
+ quantityGood: z.number().optional(),
172
+ quantityBad: z.number().optional(),
173
+ skuGroupCode: z.string().optional(),
174
+ skuGroupName: z.string().optional(),
175
+ extendShift: z.boolean().optional(),
176
+ downtimeDurationThresholdMultiplier: z.number().optional(),
177
+ isStarted: z.boolean(),
178
+ isEnded: z.boolean(),
179
+ status: z.enum(["running", "planned", "deleted", "completed"]),
180
+ allowScheduleCutShort: z.boolean().optional(),
181
+ idealQuantity: z.number().optional(),
182
+ performanceModeFrom: z.enum(["schedule", "sku", "station", "none"]).optional(),
183
+ logs: z.array(z.string()).optional(),
184
+ priorityAt: z.string().optional(),
185
+ });
186
+
187
+ type ScheduleViewModel = z.infer<typeof ScheduleViewModel>;
188
+
189
+ declare const StationViewModel = z.object({
190
+ code: z.string(), // from station table
191
+ name: z.string(), // from station table
192
+ groupCode: z.string().nullish(), // from station table
193
+ groupName: z.string().nullish(), // from join with group table
194
+ allowScheduleStationFlexibility: z.boolean().nullish(), // from join with group table
195
+ plantCode: z.string(), // from station table
196
+ timezone: z.string(), // from join with plant table
197
+ accountId: z.string(), // from station table
198
+ currentOperatorInitials: z.string().nullish(), // from station table
199
+
200
+ cycleTime: z.number().nullish(), // from station table
201
+ unitsPerMinute: z.number().nullish(),
202
+ unitsPerHour: z.number().nullish(),
203
+ lineSpeedTarget: z.number().nullish(), // from station table
204
+
205
+ allowInterruptionMode: z.boolean().nullish(),
206
+ allowManualProduction: z.boolean().nullish(),
207
+ downtimeDetectionMode: z.nativeEnum(DOWNTIME_DETECTION_MODE), // from station table
208
+ utilisationDowntimeThreshold: z.number().nullish(), // from station table
209
+ slowDurationThreshold: z.number().nullish(), // from station table
210
+ electricalUsageStoppedThresholdKw: z.number().nullish(), // from station table
211
+ allowScheduleCutShort: z.boolean().nullish(), // from station table
212
+ productiveStatusMode: z.nativeEnum(PRODUCTIVE_STATUS_MODE), // from station table
213
+ lineSpeedStoppedThreshold: z.number().nullish(), // from station table
214
+ shiftPatternId: z.number().nullish(), // from station table
215
+ productiveHoldOnTime: z.number(), // from station table
216
+ minimumCycleTime: z.number().nullish(), // from station table
217
+ statusCode: z.string().nullish(), // from station table
218
+ statusCodeUpdatedAt: z.string().nullish(), // from station table
219
+ electricalPowerNominalKw: z.number().nullish(), // from station table
220
+
221
+ shiftPattern: ShiftPatternViewModel.optional(),
222
+ shifts: z.array(ShiftSchema), // from join with shift table. station.shiftPatternId or if null, stationGroup.shiftPatternId
223
+
224
+ currentShift: ShiftSchema.optional(), // the shift calculated to be in now. Null if not in any shift.
225
+
226
+ currentProductionSchedule: ProductionScheduleViewModel.nullish(), //The production schedule we are in for this station. null if no active production schedule.
227
+ nextProductionSchedule: ProductionScheduleViewModel.nullish(), //The production schedule that is next. null if no active production schedule.
228
+
229
+ currentNonProductionSchedule: NonProductionScheduleViewModel.nullish(), // The maintenance schedule we are in for this station. null if no active maintenance schedule.
230
+
231
+ currentStopReasonCode: z.string().nullish(), // The reason code for the 'stop' type issue that is ongoing for this station. unknown if no reason given. null if no ongoing 'stop' type issue.
232
+ stoppedSince: z.number().nullish(), // The startAt of the ongoing 'stop' type issue. null if no ongoing issue.
233
+
234
+ // TODO: Change this to stateCode. It doesn't match our API style to return the entire state object.
235
+ state: StationState,
236
+ idleCostPerMinute: z.number().nullish(),
237
+ moduleVisibility: z.array(z.nativeEnum(STATION_MODULE_VISIBILITY)).nullish(),
238
+ cachedAt: z.number().nullish(),
239
+ allowQuickScheduleStart: z.boolean().nullish(),
240
+ speedScale: z.number().nullish(),
241
+ speedOffset: z.number().nullish(),
242
+ speedToProductionRatio: z.number().nullish(),
243
+
244
+ forceAllShiftTimeAsPlannedProduction: z.boolean(), // This will cause any in shift time to be treated as planned production time for the sake of downtime detection and OEE.
245
+
246
+ lastStationWindow: StationWindowViewModel.nullish(),
247
+ });
248
+
249
+ type StationViewModel = z.infer<typeof StationViewModel>;
250
+
251
+ declare const AccountViewModel = AccountSchema.merge(
252
+ z.object({
253
+ cursors: z
254
+ .array(
255
+ z.object({
256
+ type: z.string(),
257
+ timestamp: z.number(),
258
+ value: z.string(),
259
+ valueAgo: z.string(),
260
+ }),
261
+ )
262
+ .optional(),
263
+ redshiftConfig: z
264
+ .object({
265
+ host: z.string(),
266
+ port: z.number(),
267
+ username: z.string(),
268
+ })
269
+ .optional(),
270
+ }),
271
+ );
272
+
273
+ type AccountViewModel = z.infer<typeof AccountViewModel>;
274
+
275
+ export { AccountViewModel, PlantViewModel, ScheduleViewModel, SkuViewModel, StationSchema, StationViewModel };
276
+ export type { ApiKeyViewModel, BusrootSignals, PlantSchema };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const moment_1 = __importDefault(require("moment"));
7
+ const client_1 = require("./client");
8
+ async function main() {
9
+ const busrootOiClient = new client_1.Client({ accountId: "account_000", apiKey: "admin_api_key", host: "localhost" });
10
+ // const profile = await busroot.profile.get();
11
+ // console.log(profile);
12
+ const account = await busrootOiClient.account.create("opind.co", "London");
13
+ // console.log(account);
14
+ const busrootClient = new client_1.Client({ accountId: account.account.id, apiKey: account.apiKey, host: "localhost" });
15
+ // const profile = await busrootClient.profile.get();
16
+ // console.log(profile);
17
+ const station1 = await busrootClient.station.createUpdate({ code: "machine1", name: "Machine 1", groupCode: "unknown" });
18
+ const sku1 = await busrootClient.sku.createUpdate({ code: "PRODUCT1", name: "Product 1" });
19
+ const { id: scheduleId } = await busrootClient.schedule.createUpdate({
20
+ stationCode: station1.code,
21
+ skuCode: sku1.code,
22
+ plannedStartAt: (0, moment_1.default)().toISOString(),
23
+ });
24
+ console.log(scheduleId);
25
+ // busrootClient.station.signal(station1.code, { production_complete: 1 });
26
+ const schedule = await busrootClient.schedule.get(scheduleId);
27
+ console.log(schedule[0]);
28
+ busrootOiClient.destroy();
29
+ busrootClient.destroy();
30
+ }
31
+ main();
@@ -0,0 +1,19 @@
1
+ import { Client } from "./client";
2
+ import z from "zod";
3
+ export declare const ConfigPayloadSchema: z.ZodObject<{
4
+ accountId: z.ZodString;
5
+ apiKey: z.ZodString;
6
+ host: z.ZodString;
7
+ }, "strip", z.ZodTypeAny, {
8
+ accountId: string;
9
+ host: string;
10
+ apiKey: string;
11
+ }, {
12
+ accountId: string;
13
+ host: string;
14
+ apiKey: string;
15
+ }>;
16
+ export declare function useBusroot(): {
17
+ client: Client | undefined;
18
+ logout: () => void;
19
+ };
package/build/hooks.js ADDED
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.ConfigPayloadSchema = void 0;
7
+ exports.useBusroot = useBusroot;
8
+ const react_1 = require("react");
9
+ const client_1 = require("./client");
10
+ const zod_1 = __importDefault(require("zod"));
11
+ // TODO: Account ID and API key should be saved to a local cookie and removed from the query param.
12
+ // TODO: Add logout functionality, that clears the cookies.
13
+ exports.ConfigPayloadSchema = zod_1.default.object({
14
+ accountId: zod_1.default.string().max(255),
15
+ apiKey: zod_1.default.string().max(255),
16
+ host: zod_1.default.string().max(255),
17
+ });
18
+ function useBusroot() {
19
+ const [client, setClient] = (0, react_1.useState)();
20
+ (0, react_1.useEffect)(() => {
21
+ if (typeof window === undefined) {
22
+ return;
23
+ }
24
+ const searchParams = new URLSearchParams(window.location.search);
25
+ const tokenParam = searchParams.get("busroot_token");
26
+ if (tokenParam != null && client == null) {
27
+ try {
28
+ const config = exports.ConfigPayloadSchema.parse(JSON.parse(atob(tokenParam)));
29
+ setClient(new client_1.Client({ accountId: config.accountId, apiKey: config.apiKey, host: config.host }));
30
+ }
31
+ catch (e) {
32
+ console.log(e.message);
33
+ }
34
+ }
35
+ return;
36
+ }, []);
37
+ const logout = () => {
38
+ console.log("not implemented");
39
+ };
40
+ return { client, logout };
41
+ }
package/build/index.js ADDED
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./client"), exports);
18
+ __exportStar(require("./hooks"), exports);
package/package.json CHANGED
@@ -1,30 +1,42 @@
1
1
  {
2
2
  "name": "busroot-sdk",
3
- "version": "0.0.0",
3
+ "version": "0.0.2",
4
+ "description": "An SDK for accessing Busroot from output.industries",
5
+ "homepage": "https://www.output.industries",
4
6
  "main": "./build/index.js",
5
7
  "types": "./build/index.d.ts",
6
8
  "license": "MIT",
7
9
  "scripts": {
8
10
  "example": "ts-node -r dotenv-expand/config ./src/example.ts",
9
11
  "dev": "pnpm run lint && tsc -b --watch",
10
- "build": "pnpm run lint && tsc -b --verbose",
12
+ "build": "pnpm run lint && tsc --build --clean && tsc --build && pnpm run rollup",
11
13
  "lint": "eslint 'src/**/*.ts'",
12
- "test": "jest"
14
+ "test": "jest",
15
+ "rollup": "rollup -c"
13
16
  },
17
+ "files": [
18
+ "build"
19
+ ],
14
20
  "devDependencies": {
15
21
  "@jest/globals": "^29.7.0",
22
+ "@rollup/plugin-commonjs": "^28.0.6",
23
+ "@rollup/plugin-json": "^6.1.0",
24
+ "@rollup/plugin-node-resolve": "^16.0.1",
25
+ "@rollup/plugin-typescript": "^12.1.4",
16
26
  "@types/react": "^18.0.26",
17
27
  "eslint-config-custom": "workspace:*",
28
+ "rollup": "^4.49.0",
18
29
  "ts-jest": "^29.4.0",
19
30
  "tsconfig": "workspace:*",
20
- "typescript-eslint": "^8.38.0"
31
+ "typescript-eslint": "^8.38.0",
32
+ "busroot-common": "workspace:*"
21
33
  },
22
34
  "dependencies": {
23
- "busroot-common": "workspace:*",
24
35
  "jest": "^30.0.4",
25
36
  "moment": "^2.30.1",
26
37
  "mqtt": "^5.14.0",
27
38
  "react": "^18.3.1",
39
+ "rollup-plugin-dts": "^6.2.3",
28
40
  "ts-node": "^10.9.1",
29
41
  "typescript": "^5.8.3",
30
42
  "zod": "^3.25.1"
package/eslint.config.mjs DELETED
@@ -1,8 +0,0 @@
1
- import tseslint from "typescript-eslint";
2
- import custom from "eslint-config-custom";
3
-
4
- const config = tseslint.config(custom, {
5
- languageOptions: { parserOptions: { tsconfigRootDir: "./" } },
6
- });
7
-
8
- export default config;
package/jest.config.ts DELETED
@@ -1,10 +0,0 @@
1
- import type { Config } from "jest";
2
-
3
- export default async (): Promise<Config> => {
4
- return {
5
- verbose: true,
6
- preset: "ts-jest",
7
- testEnvironment: "node",
8
- modulePathIgnorePatterns: ["<rootDir>/build"],
9
- };
10
- };
@@ -1,5 +0,0 @@
1
- import { describe, expect, test, beforeEach } from "@jest/globals";
2
-
3
- describe("test", () => {
4
- test("test", () => {});
5
- });
package/src/client.ts DELETED
@@ -1,226 +0,0 @@
1
- import {
2
- PlantSchema,
3
- PlantViewModel,
4
- ScheduleViewModel,
5
- BusrootSignals,
6
- SkuViewModel,
7
- StationSchema,
8
- StationViewModel,
9
- AccountViewModel,
10
- ApiKeyViewModel,
11
- MqttEventType,
12
- } from "busroot-common";
13
- import mqtt from "mqtt/dist/mqtt.esm";
14
-
15
- export type Listener = (msg: any) => void;
16
-
17
- interface BusrootResponse {
18
- isSuccess: boolean;
19
- errorCode?: string;
20
- result: any;
21
- results: any[];
22
- }
23
-
24
- class Client {
25
- mqttClient: mqtt.MqttClient;
26
- listenersByEventType = new Map<keyof typeof MqttEventType, Set<Listener>>();
27
- apiUrl: string;
28
-
29
- constructor(public config: { accountId: string; apiKey: string; host: string }) {
30
- this.apiUrl = "http://" + config.host + ":3000/api";
31
-
32
- this.mqttClient = mqtt.connect({
33
- host: config.host,
34
- port: 8883,
35
- username: "api-key",
36
- password: this.config.apiKey,
37
- clientId: this.config.accountId + "/sdk",
38
- });
39
-
40
- this.mqttClient.on("message", (topic, payload) => {
41
- const eventType = topic.toUpperCase().split("/").at(-1);
42
-
43
- for (const [listenerEventType, listeners] of this.listenersByEventType) {
44
- if (MqttEventType[listenerEventType] !== eventType) {
45
- continue;
46
- }
47
-
48
- for (const listener of listeners) {
49
- try {
50
- listener(JSON.parse(payload.toString()));
51
- } catch (e) {
52
- //
53
- }
54
- }
55
- }
56
- });
57
-
58
- this.mqttClient.on("error", (error) => {
59
- console.error(error);
60
- });
61
-
62
- this.mqttClient.on("connect", () => {
63
- console.log("Connected to Busroot MQTT...");
64
- });
65
-
66
- this.mqttClient.on("reconnect", () => {
67
- console.log("Disconnected from Busroot MQTT...");
68
- });
69
-
70
- this.mqttClient.subscribe(`busroot/v2/${this.config.accountId}/events/#`);
71
- }
72
-
73
- destroy() {
74
- this.mqttClient.end();
75
- }
76
-
77
- private async get(path: string, query?: { [query: string]: string }) {
78
- let url = this.apiUrl + path;
79
-
80
- if (query != null) {
81
- url +=
82
- "?" +
83
- Object.entries(query)
84
- .map((value) => value[0] + "=" + value[1])
85
- .join("&");
86
- }
87
-
88
- const res = await fetch(url, {
89
- headers: { "x-api-key": this.config.apiKey },
90
- method: "GET",
91
- });
92
-
93
- if (res.status !== 200) {
94
- throw new Error(`${res.status}: ${res.statusText}`);
95
- }
96
-
97
- const resJson: BusrootResponse = await res.json();
98
-
99
- return resJson;
100
- }
101
-
102
- private async post(path: string, body: object) {
103
- const res = await fetch(this.apiUrl + path, {
104
- headers: { "x-api-key": this.config.apiKey, "Content-Type": "application/json" },
105
- method: "POST",
106
- body: JSON.stringify(body),
107
- });
108
-
109
- const resJson: BusrootResponse = await res.json();
110
-
111
- return resJson;
112
- }
113
-
114
- get profile() {
115
- return {
116
- get: async () => {
117
- const res = await this.get("/auth/profile");
118
-
119
- return res.result;
120
- },
121
- };
122
- }
123
-
124
- get account() {
125
- return {
126
- create: async (domain: string, plantName: string): Promise<{ account: AccountViewModel; apiKey: string }> => {
127
- const res = await this.post("/account/new", { domain, plantName });
128
-
129
- return res.result;
130
- },
131
- };
132
- }
133
-
134
- get apiKey() {
135
- return {
136
- create: async (): Promise<ApiKeyViewModel> => {
137
- const res = await this.post("/api-key", {});
138
-
139
- return res.result;
140
- },
141
- };
142
- }
143
-
144
- get plant() {
145
- return {
146
- get: async (plantCode: string): Promise<PlantViewModel> => {
147
- const res = await this.get("/plant", { plantCode });
148
-
149
- return res.result;
150
- },
151
- createUpdate: async (station: Partial<PlantSchema>): Promise<PlantViewModel> => {
152
- const res = await this.post("/station", station);
153
-
154
- return res.result;
155
- },
156
- };
157
- }
158
-
159
- get station() {
160
- return {
161
- get: async (stationCode: string): Promise<StationViewModel[]> => {
162
- const res = await this.get("/station", { stationcodes: stationCode });
163
-
164
- return res.results;
165
- },
166
- createUpdate: async (station: Partial<StationSchema>): Promise<StationViewModel> => {
167
- const res = await this.post("/station", station);
168
-
169
- return res.result;
170
- },
171
- signal: (stationCode: string, payload: BusrootSignals): void => {
172
- throw new Error("noop");
173
- },
174
- };
175
- }
176
-
177
- sku = {
178
- get: async (skuCode: string): Promise<SkuViewModel> => {
179
- const res = await this.get("/sku", { skucodes: skuCode });
180
-
181
- return res.result;
182
- },
183
- createUpdate: async (sku: Partial<SkuViewModel>): Promise<SkuViewModel> => {
184
- const res = await this.post("/sku", sku);
185
-
186
- return res.result;
187
- },
188
- };
189
-
190
- schedule = {
191
- get: async (scheduleId: number): Promise<ScheduleViewModel[]> => {
192
- const res = await this.get("/schedule", { ids: String(scheduleId) });
193
-
194
- return res.results;
195
- },
196
- createUpdate: async (schedule: Partial<ScheduleViewModel>): Promise<ScheduleViewModel> => {
197
- const res = await this.post("/schedule", schedule);
198
-
199
- return res.result;
200
- },
201
- };
202
-
203
- on(event: MqttEventType, listener: Listener) {
204
- let set = this.listenersByEventType.get(event);
205
-
206
- if (!set) {
207
- set = new Set<Listener>();
208
- this.listenersByEventType.set(event, set);
209
- }
210
- set.add(listener);
211
- }
212
-
213
- off(event: MqttEventType, listener: Listener) {
214
- const set = this.listenersByEventType.get(event);
215
- if (!set) {
216
- return;
217
- }
218
-
219
- set.delete(listener);
220
- if (set.size === 0) {
221
- this.listenersByEventType.delete(event);
222
- }
223
- }
224
- }
225
-
226
- export { Client };
package/src/example.ts DELETED
@@ -1,43 +0,0 @@
1
- import moment from "moment";
2
- import { Client } from "./client";
3
-
4
- async function main() {
5
- const busrootOiClient = new Client({ accountId: "account_000", apiKey: "admin_api_key", host: "localhost" });
6
-
7
- // const profile = await busroot.profile.get();
8
-
9
- // console.log(profile);
10
-
11
- const account = await busrootOiClient.account.create("opind.co", "London");
12
-
13
- // console.log(account);
14
-
15
- const busrootClient = new Client({ accountId: account.account.id, apiKey: account.apiKey, host: "localhost" });
16
-
17
- // const profile = await busrootClient.profile.get();
18
-
19
- // console.log(profile);
20
-
21
- const station1 = await busrootClient.station.createUpdate({ code: "machine1", name: "Machine 1", groupCode: "unknown" });
22
-
23
- const sku1 = await busrootClient.sku.createUpdate({ code: "PRODUCT1", name: "Product 1" });
24
-
25
- const { id: scheduleId } = await busrootClient.schedule.createUpdate({
26
- stationCode: station1.code,
27
- skuCode: sku1.code,
28
- plannedStartAt: moment().toISOString(),
29
- });
30
-
31
- console.log(scheduleId);
32
-
33
- // busrootClient.station.signal(station1.code, { production_complete: 1 });
34
-
35
- const schedule = await busrootClient.schedule.get(scheduleId);
36
-
37
- console.log(schedule[0]);
38
-
39
- busrootOiClient.destroy();
40
- busrootClient.destroy();
41
- }
42
-
43
- main();
package/src/hooks.ts DELETED
@@ -1,43 +0,0 @@
1
- import { useEffect, useState } from "react";
2
- import { Client } from "./client";
3
- import z from "zod";
4
-
5
- // TODO: Account ID and API key should be saved to a local cookie and removed from the query param.
6
- // TODO: Add logout functionality, that clears the cookies.
7
-
8
- export const ConfigPayloadSchema = z.object({
9
- accountId: z.string().max(255),
10
- apiKey: z.string().max(255),
11
- host: z.string().max(255),
12
- });
13
-
14
- export function useBusroot() {
15
- const [client, setClient] = useState<Client>();
16
-
17
- useEffect(() => {
18
- if (typeof window === undefined) {
19
- return;
20
- }
21
-
22
- const searchParams = new URLSearchParams(window.location.search);
23
- const tokenParam = searchParams.get("busroot_token");
24
-
25
- if (tokenParam != null && client == null) {
26
- try {
27
- const config = ConfigPayloadSchema.parse(JSON.parse(atob(tokenParam)));
28
-
29
- setClient(new Client({ accountId: config.accountId, apiKey: config.apiKey, host: config.host }));
30
- } catch (e: any) {
31
- console.log(e.message);
32
- }
33
- }
34
-
35
- return;
36
- }, []);
37
-
38
- const logout = () => {
39
- console.log("not implemented");
40
- };
41
-
42
- return { client, logout };
43
- }
package/tsconfig.json DELETED
@@ -1,10 +0,0 @@
1
- {
2
- "extends": "tsconfig/base.json",
3
- "include": ["src/**/*"],
4
- "exclude": ["node_modules", "build"],
5
- "compilerOptions": {
6
- "rootDir": "src",
7
- "outDir": "build",
8
- "target": "es2020",
9
- }
10
- }
@@ -1 +0,0 @@
1
- {"root":["./src/client.test.ts","./src/client.ts","./src/example.ts","./src/hooks.ts","./src/index.ts"],"version":"5.8.3"}
File without changes