@mxpicture/tesla-superchargers-cloud-store 0.1.4

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.
Files changed (57) hide show
  1. package/dist/client/DropboxAuth.d.ts +32 -0
  2. package/dist/client/DropboxAuth.d.ts.map +1 -0
  3. package/dist/client/DropboxAuth.js +57 -0
  4. package/dist/client/DropboxAuth.js.map +1 -0
  5. package/dist/client/DropboxClient.d.ts +26 -0
  6. package/dist/client/DropboxClient.d.ts.map +1 -0
  7. package/dist/client/DropboxClient.js +57 -0
  8. package/dist/client/DropboxClient.js.map +1 -0
  9. package/dist/client/DropboxConfig.d.ts +34 -0
  10. package/dist/client/DropboxConfig.d.ts.map +1 -0
  11. package/dist/client/DropboxConfig.js +67 -0
  12. package/dist/client/DropboxConfig.js.map +1 -0
  13. package/dist/client/index.d.ts +3 -0
  14. package/dist/client/index.d.ts.map +1 -0
  15. package/dist/client/index.js +4 -0
  16. package/dist/client/index.js.map +1 -0
  17. package/dist/common/chargers.common.d.ts +11 -0
  18. package/dist/common/chargers.common.d.ts.map +1 -0
  19. package/dist/common/chargers.common.js +11 -0
  20. package/dist/common/chargers.common.js.map +1 -0
  21. package/dist/common/dropbox.common.d.ts +5 -0
  22. package/dist/common/dropbox.common.d.ts.map +1 -0
  23. package/dist/common/dropbox.common.js +20 -0
  24. package/dist/common/dropbox.common.js.map +1 -0
  25. package/dist/common/failures.common.d.ts +8 -0
  26. package/dist/common/failures.common.d.ts.map +1 -0
  27. package/dist/common/failures.common.js +33 -0
  28. package/dist/common/failures.common.js.map +1 -0
  29. package/dist/common/index.d.ts +2 -0
  30. package/dist/common/index.d.ts.map +1 -0
  31. package/dist/common/index.js +3 -0
  32. package/dist/common/index.js.map +1 -0
  33. package/dist/common/invoicesCsv.common.d.ts +85 -0
  34. package/dist/common/invoicesCsv.common.d.ts.map +1 -0
  35. package/dist/common/invoicesCsv.common.js +185 -0
  36. package/dist/common/invoicesCsv.common.js.map +1 -0
  37. package/dist/common/pricebooks.common.d.ts +47 -0
  38. package/dist/common/pricebooks.common.d.ts.map +1 -0
  39. package/dist/common/pricebooks.common.js +95 -0
  40. package/dist/common/pricebooks.common.js.map +1 -0
  41. package/dist/common/pushover.common.d.ts +23 -0
  42. package/dist/common/pushover.common.d.ts.map +1 -0
  43. package/dist/common/pushover.common.js +52 -0
  44. package/dist/common/pushover.common.js.map +1 -0
  45. package/dist/common/sync.common.d.ts +4 -0
  46. package/dist/common/sync.common.d.ts.map +1 -0
  47. package/dist/common/sync.common.js +10 -0
  48. package/dist/common/sync.common.js.map +1 -0
  49. package/dist/types/dropbox.types.d.ts +23 -0
  50. package/dist/types/dropbox.types.d.ts.map +1 -0
  51. package/dist/types/dropbox.types.js +2 -0
  52. package/dist/types/dropbox.types.js.map +1 -0
  53. package/dist/types/index.d.ts +2 -0
  54. package/dist/types/index.d.ts.map +1 -0
  55. package/dist/types/index.js +3 -0
  56. package/dist/types/index.js.map +1 -0
  57. package/package.json +55 -0
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Read-only Dropbox access: list a directory and download file content.
3
+ *
4
+ * Auth (read from the process environment on first use):
5
+ * - `DROPBOX_KEY` + `DROPBOX_SECRET` + `DROPBOX_REFRESH_TOKEN` — long-lived;
6
+ * the SDK exchanges the refresh token for short-lived access tokens itself.
7
+ * - `DROPBOX_ACCESS_TOKEN` — fallback, e.g. a token generated in the Dropbox
8
+ * App Console (expires after ~4h).
9
+ */
10
+ import { Dropbox, type DropboxOptions } from "dropbox";
11
+ import type { DropboxAuthAccessParams, DropboxAuthClientParams } from "../types/dropbox.types.js";
12
+ export declare class DropboxAuthFactory {
13
+ private constructor();
14
+ static byEnv(env: Record<string, string | undefined>): IDropboxAuth;
15
+ }
16
+ export declare abstract class IDropboxAuth {
17
+ protected dbx: Dropbox | null;
18
+ constructor();
19
+ protected abstract clientParams(): DropboxOptions;
20
+ client(): Dropbox;
21
+ }
22
+ export declare class DropboxAuthAccess extends IDropboxAuth {
23
+ protected readonly auth: DropboxAuthAccessParams;
24
+ constructor(auth: DropboxAuthAccessParams);
25
+ protected clientParams(): DropboxOptions;
26
+ }
27
+ export declare class DropboxAuthClient extends IDropboxAuth {
28
+ protected readonly auth: DropboxAuthClientParams;
29
+ constructor(auth: DropboxAuthClientParams);
30
+ protected clientParams(): DropboxOptions;
31
+ }
32
+ //# sourceMappingURL=DropboxAuth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DropboxAuth.d.ts","sourceRoot":"","sources":["../../src/client/DropboxAuth.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,SAAS,CAAC;AACvD,OAAO,KAAK,EACV,uBAAuB,EACvB,uBAAuB,EACxB,MAAM,2BAA2B,CAAC;AAEnC,qBAAa,kBAAkB;IAC7B,OAAO;WAEO,KAAK,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,GAAG,YAAY;CAsB3E;AAED,8BAAsB,YAAY;IAChC,SAAS,CAAC,GAAG,EAAE,OAAO,GAAG,IAAI,CAAQ;;IAIrC,SAAS,CAAC,QAAQ,CAAC,YAAY,IAAI,cAAc;IAE1C,MAAM,IAAI,OAAO;CAGzB;AAED,qBAAa,iBAAkB,SAAQ,YAAY;IACjD,SAAS,CAAC,QAAQ,CAAC,IAAI,EAAE,uBAAuB,CAAC;gBAE9B,IAAI,EAAE,uBAAuB;cAK7B,YAAY,IAAI,cAAc;CAGlD;AAED,qBAAa,iBAAkB,SAAQ,YAAY;IACjD,SAAS,CAAC,QAAQ,CAAC,IAAI,EAAE,uBAAuB,CAAC;gBAE9B,IAAI,EAAE,uBAAuB;IAKhD,SAAS,CAAC,YAAY,IAAI,cAAc;CAOzC"}
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Read-only Dropbox access: list a directory and download file content.
3
+ *
4
+ * Auth (read from the process environment on first use):
5
+ * - `DROPBOX_KEY` + `DROPBOX_SECRET` + `DROPBOX_REFRESH_TOKEN` — long-lived;
6
+ * the SDK exchanges the refresh token for short-lived access tokens itself.
7
+ * - `DROPBOX_ACCESS_TOKEN` — fallback, e.g. a token generated in the Dropbox
8
+ * App Console (expires after ~4h).
9
+ */
10
+ import { Dropbox } from "dropbox";
11
+ export class DropboxAuthFactory {
12
+ constructor() { }
13
+ static byEnv(env) {
14
+ const { DROPBOX_KEY, DROPBOX_SECRET, DROPBOX_REFRESH_TOKEN, DROPBOX_ACCESS_TOKEN, } = env;
15
+ if (DROPBOX_KEY && DROPBOX_SECRET && DROPBOX_REFRESH_TOKEN)
16
+ return new DropboxAuthClient({
17
+ clientId: DROPBOX_KEY,
18
+ clientSecret: DROPBOX_SECRET,
19
+ refreshToken: DROPBOX_REFRESH_TOKEN,
20
+ });
21
+ if (DROPBOX_ACCESS_TOKEN)
22
+ return new DropboxAuthAccess({ accessToken: DROPBOX_ACCESS_TOKEN });
23
+ throw new Error("Dropbox credentials missing: set DROPBOX_KEY + DROPBOX_SECRET + DROPBOX_REFRESH_TOKEN (or DROPBOX_ACCESS_TOKEN)");
24
+ }
25
+ }
26
+ export class IDropboxAuth {
27
+ dbx = null;
28
+ constructor() { }
29
+ client() {
30
+ return (this.dbx ??= new Dropbox(this.clientParams()));
31
+ }
32
+ }
33
+ export class DropboxAuthAccess extends IDropboxAuth {
34
+ auth;
35
+ constructor(auth) {
36
+ super();
37
+ this.auth = { ...auth };
38
+ }
39
+ clientParams() {
40
+ return { accessToken: this.auth.accessToken };
41
+ }
42
+ }
43
+ export class DropboxAuthClient extends IDropboxAuth {
44
+ auth;
45
+ constructor(auth) {
46
+ super();
47
+ this.auth = { ...auth };
48
+ }
49
+ clientParams() {
50
+ return {
51
+ clientId: this.auth.clientId,
52
+ clientSecret: this.auth.clientSecret,
53
+ refreshToken: this.auth.refreshToken,
54
+ };
55
+ }
56
+ }
57
+ //# sourceMappingURL=DropboxAuth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DropboxAuth.js","sourceRoot":"","sources":["../../src/client/DropboxAuth.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,OAAO,EAAuB,MAAM,SAAS,CAAC;AAMvD,MAAM,OAAO,kBAAkB;IAC7B,gBAAuB,CAAC;IAEjB,MAAM,CAAC,KAAK,CAAC,GAAuC;QACzD,MAAM,EACJ,WAAW,EACX,cAAc,EACd,qBAAqB,EACrB,oBAAoB,GACrB,GAAG,GAAG,CAAC;QAER,IAAI,WAAW,IAAI,cAAc,IAAI,qBAAqB;YACxD,OAAO,IAAI,iBAAiB,CAAC;gBAC3B,QAAQ,EAAE,WAAW;gBACrB,YAAY,EAAE,cAAc;gBAC5B,YAAY,EAAE,qBAAqB;aACpC,CAAC,CAAC;QAEL,IAAI,oBAAoB;YACtB,OAAO,IAAI,iBAAiB,CAAC,EAAE,WAAW,EAAE,oBAAoB,EAAE,CAAC,CAAC;QAEtE,MAAM,IAAI,KAAK,CACb,iHAAiH,CAClH,CAAC;IACJ,CAAC;CACF;AAED,MAAM,OAAgB,YAAY;IACtB,GAAG,GAAmB,IAAI,CAAC;IAErC,gBAAsB,CAAC;IAIhB,MAAM;QACX,OAAO,CAAC,IAAI,CAAC,GAAG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC;IACzD,CAAC;CACF;AAED,MAAM,OAAO,iBAAkB,SAAQ,YAAY;IAC9B,IAAI,CAA0B;IAEjD,YAAmB,IAA6B;QAC9C,KAAK,EAAE,CAAC;QACR,IAAI,CAAC,IAAI,GAAG,EAAE,GAAG,IAAI,EAAE,CAAC;IAC1B,CAAC;IAEkB,YAAY;QAC7B,OAAO,EAAE,WAAW,EAAE,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;IAChD,CAAC;CACF;AAED,MAAM,OAAO,iBAAkB,SAAQ,YAAY;IAC9B,IAAI,CAA0B;IAEjD,YAAmB,IAA6B;QAC9C,KAAK,EAAE,CAAC;QACR,IAAI,CAAC,IAAI,GAAG,EAAE,GAAG,IAAI,EAAE,CAAC;IAC1B,CAAC;IAES,YAAY;QACpB,OAAO;YACL,QAAQ,EAAE,IAAI,CAAC,IAAI,CAAC,QAAQ;YAC5B,YAAY,EAAE,IAAI,CAAC,IAAI,CAAC,YAAY;YACpC,YAAY,EAAE,IAAI,CAAC,IAAI,CAAC,YAAY;SACrC,CAAC;IACJ,CAAC;CACF"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Read-only Dropbox access: list a directory and download file content.
3
+ *
4
+ * Auth (read from the process environment on first use):
5
+ * - `DROPBOX_KEY` + `DROPBOX_SECRET` + `DROPBOX_REFRESH_TOKEN` — long-lived;
6
+ * the SDK exchanges the refresh token for short-lived access tokens itself.
7
+ * - `DROPBOX_ACCESS_TOKEN` — fallback, e.g. a token generated in the Dropbox
8
+ * App Console (expires after ~4h).
9
+ */
10
+ import type { DropboxEntry, DropboxFile } from "../types/dropbox.types.js";
11
+ import { type IDropboxConfig } from "./DropboxConfig.js";
12
+ export declare class DropboxClient {
13
+ protected readonly config: IDropboxConfig;
14
+ constructor(config: IDropboxConfig);
15
+ /**
16
+ * List the files of one directory (non-recursive; sub-folders and deleted
17
+ * entries are skipped). Follows cursor pagination until complete.
18
+ */
19
+ fetchDirectory(path: string): Promise<DropboxEntry[]>;
20
+ fetchBaseDir(): Promise<DropboxEntry[]>;
21
+ /** Download one file and return its metadata plus UTF-8 decoded content. */
22
+ fetchFile(path: string): Promise<DropboxFile>;
23
+ protected client(): import("dropbox").Dropbox;
24
+ }
25
+ export declare const dropboxClientByEnv: () => DropboxClient;
26
+ //# sourceMappingURL=DropboxClient.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DropboxClient.d.ts","sourceRoot":"","sources":["../../src/client/DropboxClient.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAE3E,OAAO,EAAwB,KAAK,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAE/E,qBAAa,aAAa;IACxB,SAAS,CAAC,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAC;gBAEvB,MAAM,EAAE,cAAc;IAIzC;;;OAGG;IACU,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;IAgBrD,YAAY,IAAI,OAAO,CAAC,YAAY,EAAE,CAAC;IAQpD,4EAA4E;IAC/D,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC;IAU1D,SAAS,CAAC,MAAM;CAGjB;AAGD,eAAO,MAAM,kBAAkB,qBAC2C,CAAC"}
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Read-only Dropbox access: list a directory and download file content.
3
+ *
4
+ * Auth (read from the process environment on first use):
5
+ * - `DROPBOX_KEY` + `DROPBOX_SECRET` + `DROPBOX_REFRESH_TOKEN` — long-lived;
6
+ * the SDK exchanges the refresh token for short-lived access tokens itself.
7
+ * - `DROPBOX_ACCESS_TOKEN` — fallback, e.g. a token generated in the Dropbox
8
+ * App Console (expires after ~4h).
9
+ */
10
+ import { normalizePath, toEntry } from "../common/dropbox.common.js";
11
+ import { DropboxConfigFactory } from "./DropboxConfig.js";
12
+ export class DropboxClient {
13
+ config;
14
+ constructor(config) {
15
+ this.config = config;
16
+ }
17
+ /**
18
+ * List the files of one directory (non-recursive; sub-folders and deleted
19
+ * entries are skipped). Follows cursor pagination until complete.
20
+ */
21
+ async fetchDirectory(path) {
22
+ const dbx = this.client();
23
+ const entries = [];
24
+ let result = (await dbx.filesListFolder({ path: normalizePath(path) }))
25
+ .result;
26
+ for (;;) {
27
+ for (const entry of result.entries)
28
+ if (entry[".tag"] === "file")
29
+ entries.push(toEntry(entry));
30
+ if (!result.has_more)
31
+ return entries;
32
+ result = (await dbx.filesListFolderContinue({ cursor: result.cursor }))
33
+ .result;
34
+ }
35
+ }
36
+ async fetchBaseDir() {
37
+ const path = this.config.baseDir();
38
+ if (!path)
39
+ throw new Error(`Dropbox: fetching base dir failed. No path provided`);
40
+ return await this.fetchDirectory(path);
41
+ }
42
+ /** Download one file and return its metadata plus UTF-8 decoded content. */
43
+ async fetchFile(path) {
44
+ const { result } = await this.client().filesDownload({ path });
45
+ // In Node the SDK attaches the raw bytes as `fileBinary` (a Buffer);
46
+ // `fileBlob` is the browser variant and never set here.
47
+ if (!result.fileBinary)
48
+ throw new Error(`Dropbox download of "${path}" returned no fileBinary`);
49
+ return { ...toEntry(result), content: result.fileBinary.toString("utf8") };
50
+ }
51
+ client() {
52
+ return this.config.client();
53
+ }
54
+ }
55
+ let _client = null;
56
+ export const dropboxClientByEnv = () => (_client ??= new DropboxClient(DropboxConfigFactory.byEnv(process.env)));
57
+ //# sourceMappingURL=DropboxClient.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DropboxClient.js","sourceRoot":"","sources":["../../src/client/DropboxClient.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,6BAA6B,CAAC;AACrE,OAAO,EAAE,oBAAoB,EAAuB,MAAM,oBAAoB,CAAC;AAE/E,MAAM,OAAO,aAAa;IACL,MAAM,CAAiB;IAE1C,YAAmB,MAAsB;QACvC,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,CAAC;IAED;;;OAGG;IACI,KAAK,CAAC,cAAc,CAAC,IAAY;QACtC,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;QAC1B,MAAM,OAAO,GAAmB,EAAE,CAAC;QACnC,IAAI,MAAM,GAAG,CAAC,MAAM,GAAG,CAAC,eAAe,CAAC,EAAE,IAAI,EAAE,aAAa,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;aACpE,MAAM,CAAC;QAEV,SAAS,CAAC;YACR,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,OAAO;gBAChC,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,MAAM;oBAAE,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC;YAE7D,IAAI,CAAC,MAAM,CAAC,QAAQ;gBAAE,OAAO,OAAO,CAAC;YACrC,MAAM,GAAG,CAAC,MAAM,GAAG,CAAC,uBAAuB,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;iBACpE,MAAM,CAAC;QACZ,CAAC;IACH,CAAC;IAEM,KAAK,CAAC,YAAY;QACvB,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACnC,IAAI,CAAC,IAAI;YACP,MAAM,IAAI,KAAK,CAAC,qDAAqD,CAAC,CAAC;QAEzE,OAAO,MAAM,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC;IACzC,CAAC;IAED,4EAA4E;IACrE,KAAK,CAAC,SAAS,CAAC,IAAY;QACjC,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,IAAI,CAAC,MAAM,EAAE,CAAC,aAAa,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC;QAC/D,qEAAqE;QACrE,wDAAwD;QACxD,IAAI,CAAC,MAAM,CAAC,UAAU;YACpB,MAAM,IAAI,KAAK,CAAC,wBAAwB,IAAI,0BAA0B,CAAC,CAAC;QAE1E,OAAO,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;IAC7E,CAAC;IAES,MAAM;QACd,OAAO,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;IAC9B,CAAC;CACF;AAED,IAAI,OAAO,GAAyB,IAAI,CAAC;AACzC,MAAM,CAAC,MAAM,kBAAkB,GAAG,GAAG,EAAE,CACrC,CAAC,OAAO,KAAK,IAAI,aAAa,CAAC,oBAAoB,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC"}
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Read-only Dropbox access: list a directory and download file content.
3
+ *
4
+ * Auth (read from the process environment on first use):
5
+ * - `DROPBOX_KEY` + `DROPBOX_SECRET` + `DROPBOX_REFRESH_TOKEN` — long-lived;
6
+ * the SDK exchanges the refresh token for short-lived access tokens itself.
7
+ * - `DROPBOX_ACCESS_TOKEN` — fallback, e.g. a token generated in the Dropbox
8
+ * App Console (expires after ~4h).
9
+ */
10
+ import { Dropbox, type DropboxOptions } from "dropbox";
11
+ import type { DropboxConfigAccessParams, DropboxConfigClientParams, DropboxConfigParams } from "../types/dropbox.types.js";
12
+ export declare class DropboxConfigFactory {
13
+ private constructor();
14
+ static byEnv(env: Record<string, string | undefined>): IDropboxConfig;
15
+ }
16
+ export declare abstract class IDropboxConfig<T extends DropboxConfigParams = DropboxConfigParams> {
17
+ protected dbx: Dropbox | null;
18
+ protected params: T;
19
+ constructor(params: T);
20
+ protected abstract options(): DropboxOptions;
21
+ baseDir(): string | null;
22
+ client(): Dropbox;
23
+ }
24
+ export declare class DropboxConfigAccess extends IDropboxConfig {
25
+ protected readonly config: DropboxConfigAccessParams;
26
+ constructor(config: DropboxConfigAccessParams);
27
+ protected options(): DropboxOptions;
28
+ }
29
+ export declare class DropboxConfigClient extends IDropboxConfig {
30
+ protected readonly config: DropboxConfigClientParams;
31
+ constructor(config: DropboxConfigClientParams);
32
+ protected options(): DropboxOptions;
33
+ }
34
+ //# sourceMappingURL=DropboxConfig.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DropboxConfig.d.ts","sourceRoot":"","sources":["../../src/client/DropboxConfig.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,SAAS,CAAC;AACvD,OAAO,KAAK,EACV,yBAAyB,EACzB,yBAAyB,EACzB,mBAAmB,EACpB,MAAM,2BAA2B,CAAC;AAEnC,qBAAa,oBAAoB;IAC/B,OAAO;WAEO,KAAK,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,GAAG,cAAc;CA2B7E;AAED,8BAAsB,cAAc,CAClC,CAAC,SAAS,mBAAmB,GAAG,mBAAmB;IAEnD,SAAS,CAAC,GAAG,EAAE,OAAO,GAAG,IAAI,CAAQ;IACrC,SAAS,CAAC,MAAM,EAAE,CAAC,CAAC;gBAED,MAAM,EAAE,CAAC;IAI5B,SAAS,CAAC,QAAQ,CAAC,OAAO,IAAI,cAAc;IAErC,OAAO,IAAI,MAAM,GAAG,IAAI;IAIxB,MAAM,IAAI,OAAO;CAGzB;AAED,qBAAa,mBAAoB,SAAQ,cAAc;IACrD,SAAS,CAAC,QAAQ,CAAC,MAAM,EAAE,yBAAyB,CAAC;gBAElC,MAAM,EAAE,yBAAyB;cAKjC,OAAO,IAAI,cAAc;CAG7C;AAED,qBAAa,mBAAoB,SAAQ,cAAc;IACrD,SAAS,CAAC,QAAQ,CAAC,MAAM,EAAE,yBAAyB,CAAC;gBAElC,MAAM,EAAE,yBAAyB;IAKpD,SAAS,CAAC,OAAO,IAAI,cAAc;CAOpC"}
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Read-only Dropbox access: list a directory and download file content.
3
+ *
4
+ * Auth (read from the process environment on first use):
5
+ * - `DROPBOX_KEY` + `DROPBOX_SECRET` + `DROPBOX_REFRESH_TOKEN` — long-lived;
6
+ * the SDK exchanges the refresh token for short-lived access tokens itself.
7
+ * - `DROPBOX_ACCESS_TOKEN` — fallback, e.g. a token generated in the Dropbox
8
+ * App Console (expires after ~4h).
9
+ */
10
+ import { Dropbox } from "dropbox";
11
+ export class DropboxConfigFactory {
12
+ constructor() { }
13
+ static byEnv(env) {
14
+ const { DROPBOX_KEY, DROPBOX_SECRET, DROPBOX_REFRESH_TOKEN, DROPBOX_ACCESS_TOKEN, DROPBOX_BASE_DIR, } = env;
15
+ if (DROPBOX_KEY && DROPBOX_SECRET && DROPBOX_REFRESH_TOKEN)
16
+ return new DropboxConfigClient({
17
+ baseDir: DROPBOX_BASE_DIR,
18
+ clientId: DROPBOX_KEY,
19
+ clientSecret: DROPBOX_SECRET,
20
+ refreshToken: DROPBOX_REFRESH_TOKEN,
21
+ });
22
+ if (DROPBOX_ACCESS_TOKEN)
23
+ return new DropboxConfigAccess({
24
+ baseDir: DROPBOX_BASE_DIR,
25
+ accessToken: DROPBOX_ACCESS_TOKEN,
26
+ });
27
+ throw new Error("Dropbox credentials missing: set DROPBOX_KEY + DROPBOX_SECRET + DROPBOX_REFRESH_TOKEN (or DROPBOX_ACCESS_TOKEN)");
28
+ }
29
+ }
30
+ export class IDropboxConfig {
31
+ dbx = null;
32
+ params;
33
+ constructor(params) {
34
+ this.params = { ...params };
35
+ }
36
+ baseDir() {
37
+ return this.params.baseDir ?? null;
38
+ }
39
+ client() {
40
+ return (this.dbx ??= new Dropbox(this.options()));
41
+ }
42
+ }
43
+ export class DropboxConfigAccess extends IDropboxConfig {
44
+ config;
45
+ constructor(config) {
46
+ super(config);
47
+ this.config = { ...config };
48
+ }
49
+ options() {
50
+ return { accessToken: this.config.accessToken };
51
+ }
52
+ }
53
+ export class DropboxConfigClient extends IDropboxConfig {
54
+ config;
55
+ constructor(config) {
56
+ super(config);
57
+ this.config = { ...config };
58
+ }
59
+ options() {
60
+ return {
61
+ clientId: this.config.clientId,
62
+ clientSecret: this.config.clientSecret,
63
+ refreshToken: this.config.refreshToken,
64
+ };
65
+ }
66
+ }
67
+ //# sourceMappingURL=DropboxConfig.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DropboxConfig.js","sourceRoot":"","sources":["../../src/client/DropboxConfig.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,OAAO,EAAuB,MAAM,SAAS,CAAC;AAOvD,MAAM,OAAO,oBAAoB;IAC/B,gBAAuB,CAAC;IAEjB,MAAM,CAAC,KAAK,CAAC,GAAuC;QACzD,MAAM,EACJ,WAAW,EACX,cAAc,EACd,qBAAqB,EACrB,oBAAoB,EACpB,gBAAgB,GACjB,GAAG,GAAG,CAAC;QAER,IAAI,WAAW,IAAI,cAAc,IAAI,qBAAqB;YACxD,OAAO,IAAI,mBAAmB,CAAC;gBAC7B,OAAO,EAAE,gBAAgB;gBACzB,QAAQ,EAAE,WAAW;gBACrB,YAAY,EAAE,cAAc;gBAC5B,YAAY,EAAE,qBAAqB;aACpC,CAAC,CAAC;QAEL,IAAI,oBAAoB;YACtB,OAAO,IAAI,mBAAmB,CAAC;gBAC7B,OAAO,EAAE,gBAAgB;gBACzB,WAAW,EAAE,oBAAoB;aAClC,CAAC,CAAC;QAEL,MAAM,IAAI,KAAK,CACb,iHAAiH,CAClH,CAAC;IACJ,CAAC;CACF;AAED,MAAM,OAAgB,cAAc;IAGxB,GAAG,GAAmB,IAAI,CAAC;IAC3B,MAAM,CAAI;IAEpB,YAAmB,MAAS;QAC1B,IAAI,CAAC,MAAM,GAAG,EAAE,GAAG,MAAM,EAAE,CAAC;IAC9B,CAAC;IAIM,OAAO;QACZ,OAAO,IAAI,CAAC,MAAM,CAAC,OAAO,IAAI,IAAI,CAAC;IACrC,CAAC;IAEM,MAAM;QACX,OAAO,CAAC,IAAI,CAAC,GAAG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;IACpD,CAAC;CACF;AAED,MAAM,OAAO,mBAAoB,SAAQ,cAAc;IAClC,MAAM,CAA4B;IAErD,YAAmB,MAAiC;QAClD,KAAK,CAAC,MAAM,CAAC,CAAC;QACd,IAAI,CAAC,MAAM,GAAG,EAAE,GAAG,MAAM,EAAE,CAAC;IAC9B,CAAC;IAEkB,OAAO;QACxB,OAAO,EAAE,WAAW,EAAE,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC;IAClD,CAAC;CACF;AAED,MAAM,OAAO,mBAAoB,SAAQ,cAAc;IAClC,MAAM,CAA4B;IAErD,YAAmB,MAAiC;QAClD,KAAK,CAAC,MAAM,CAAC,CAAC;QACd,IAAI,CAAC,MAAM,GAAG,EAAE,GAAG,MAAM,EAAE,CAAC;IAC9B,CAAC;IAES,OAAO;QACf,OAAO;YACL,QAAQ,EAAE,IAAI,CAAC,MAAM,CAAC,QAAQ;YAC9B,YAAY,EAAE,IAAI,CAAC,MAAM,CAAC,YAAY;YACtC,YAAY,EAAE,IAAI,CAAC,MAAM,CAAC,YAAY;SACvC,CAAC;IACJ,CAAC;CACF"}
@@ -0,0 +1,3 @@
1
+ export * from "./DropboxClient.js";
2
+ export * from "./DropboxConfig.js";
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"AACA,cAAc,oBAAoB,CAAC;AACnC,cAAc,oBAAoB,CAAC"}
@@ -0,0 +1,4 @@
1
+ // This file is auto-generated by build-cli. Do not edit manually.
2
+ export * from "./DropboxClient.js";
3
+ export * from "./DropboxConfig.js";
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"AAAA,kEAAkE;AAClE,cAAc,oBAAoB,CAAC;AACnC,cAAc,oBAAoB,CAAC"}
@@ -0,0 +1,11 @@
1
+ import type { PicChargers } from "@mxpicture/tesla-superchargers-generated/public";
2
+ /** Refetch a cached charger whose last fetch is older than this. */
3
+ export declare const CHARGER_STALE_AFTER_DAYS = 5;
4
+ /** Detail fetches per findByCoordinate call — the endpoint is slow and guarded. */
5
+ export declare const MAX_CHARGER_FETCHES = 3;
6
+ /** Candidates that failed this often are tried after the rest, so dead slugs stop burning the fetch budget first. */
7
+ export declare const FAILURE_DEPRIORITIZE_THRESHOLD = 3;
8
+ export declare const isFresh: (row: PicChargers) => boolean;
9
+ /** FK links are not part of the feed; a snapshot re-sync must not wipe them. */
10
+ export declare const withoutFkColumns: (columns: string[]) => string[];
11
+ //# sourceMappingURL=chargers.common.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chargers.common.d.ts","sourceRoot":"","sources":["../../src/common/chargers.common.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iDAAiD,CAAC;AAEnF,oEAAoE;AACpE,eAAO,MAAM,wBAAwB,IAAI,CAAC;AAE1C,mFAAmF;AACnF,eAAO,MAAM,mBAAmB,IAAI,CAAC;AAErC,qHAAqH;AACrH,eAAO,MAAM,8BAA8B,IAAI,CAAC;AAEhD,eAAO,MAAM,OAAO,GAAI,KAAK,WAAW,KAAG,OAEkB,CAAC;AAE9D,gFAAgF;AAChF,eAAO,MAAM,gBAAgB,GAAI,SAAS,MAAM,EAAE,KAAG,MAAM,EACJ,CAAC"}
@@ -0,0 +1,11 @@
1
+ /** Refetch a cached charger whose last fetch is older than this. */
2
+ export const CHARGER_STALE_AFTER_DAYS = 5;
3
+ /** Detail fetches per findByCoordinate call — the endpoint is slow and guarded. */
4
+ export const MAX_CHARGER_FETCHES = 3;
5
+ /** Candidates that failed this often are tried after the rest, so dead slugs stop burning the fetch budget first. */
6
+ export const FAILURE_DEPRIORITIZE_THRESHOLD = 3;
7
+ export const isFresh = (row) => row.fetched_at.getTime() >
8
+ Date.now() - CHARGER_STALE_AFTER_DAYS * 24 * 60 * 60 * 1000;
9
+ /** FK links are not part of the feed; a snapshot re-sync must not wipe them. */
10
+ export const withoutFkColumns = (columns) => columns.filter((column) => column !== "pic_slugs_id");
11
+ //# sourceMappingURL=chargers.common.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chargers.common.js","sourceRoot":"","sources":["../../src/common/chargers.common.ts"],"names":[],"mappings":"AAEA,oEAAoE;AACpE,MAAM,CAAC,MAAM,wBAAwB,GAAG,CAAC,CAAC;AAE1C,mFAAmF;AACnF,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,CAAC;AAErC,qHAAqH;AACrH,MAAM,CAAC,MAAM,8BAA8B,GAAG,CAAC,CAAC;AAEhD,MAAM,CAAC,MAAM,OAAO,GAAG,CAAC,GAAgB,EAAW,EAAE,CACnD,GAAG,CAAC,UAAU,CAAC,OAAO,EAAE;IACxB,IAAI,CAAC,GAAG,EAAE,GAAG,wBAAwB,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;AAE9D,gFAAgF;AAChF,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,OAAiB,EAAY,EAAE,CAC9D,OAAO,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,KAAK,cAAc,CAAC,CAAC"}
@@ -0,0 +1,5 @@
1
+ import type { files } from "dropbox";
2
+ import type { DropboxEntry } from "../types/dropbox.types.js";
3
+ export declare const normalizePath: (path: string) => string;
4
+ export declare const toEntry: (meta: files.FileMetadata) => DropboxEntry;
5
+ //# sourceMappingURL=dropbox.common.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dropbox.common.d.ts","sourceRoot":"","sources":["../../src/common/dropbox.common.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAK9D,eAAO,MAAM,aAAa,GAAI,MAAM,MAAM,WAUzC,CAAC;AAEF,eAAO,MAAM,OAAO,GAAI,MAAM,KAAK,CAAC,YAAY,KAAG,YAKjD,CAAC"}
@@ -0,0 +1,20 @@
1
+ // The API spells the Dropbox root "" and rejects "/". A path without the
2
+ // required leading slash gets one (else Dropbox answers an opaque 500);
3
+ // `id:`/`ns:` references pass through untouched.
4
+ export const normalizePath = (path) => {
5
+ if (path === "/")
6
+ return "";
7
+ if (path === "" ||
8
+ path.startsWith("/") ||
9
+ path.startsWith("id:") ||
10
+ path.startsWith("ns:"))
11
+ return path;
12
+ return `/${path}`;
13
+ };
14
+ export const toEntry = (meta) => ({
15
+ name: meta.name,
16
+ path: meta.path_lower ?? meta.path_display ?? meta.name,
17
+ size: meta.size,
18
+ serverModified: new Date(meta.server_modified),
19
+ });
20
+ //# sourceMappingURL=dropbox.common.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dropbox.common.js","sourceRoot":"","sources":["../../src/common/dropbox.common.ts"],"names":[],"mappings":"AAGA,yEAAyE;AACzE,wEAAwE;AACxE,iDAAiD;AACjD,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,IAAY,EAAE,EAAE;IAC5C,IAAI,IAAI,KAAK,GAAG;QAAE,OAAO,EAAE,CAAC;IAC5B,IACE,IAAI,KAAK,EAAE;QACX,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;QACpB,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC;QACtB,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC;QAEtB,OAAO,IAAI,CAAC;IACd,OAAO,IAAI,IAAI,EAAE,CAAC;AACpB,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,OAAO,GAAG,CAAC,IAAwB,EAAgB,EAAE,CAAC,CAAC;IAClE,IAAI,EAAE,IAAI,CAAC,IAAI;IACf,IAAI,EAAE,IAAI,CAAC,UAAU,IAAI,IAAI,CAAC,YAAY,IAAI,IAAI,CAAC,IAAI;IACvD,IAAI,EAAE,IAAI,CAAC,IAAI;IACf,cAAc,EAAE,IAAI,IAAI,CAAC,IAAI,CAAC,eAAe,CAAC;CAC/C,CAAC,CAAC"}
@@ -0,0 +1,8 @@
1
+ import type { PushoverMessage } from "../types/pushover.types.js";
2
+ import type { FailedSources } from "../types/failures.types.js";
3
+ /**
4
+ * Build the Pushover message for a set of failing rows, or null when every
5
+ * source is empty (nothing to page). Pure — unit-tested without a request.
6
+ */
7
+ export declare const buildFailureMessage: (failed: FailedSources) => PushoverMessage | null;
8
+ //# sourceMappingURL=failures.common.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"failures.common.d.ts","sourceRoot":"","sources":["../../src/common/failures.common.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAClE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAehE;;;GAGG;AACH,eAAO,MAAM,mBAAmB,GAC9B,QAAQ,aAAa,KACpB,eAAe,GAAG,IA2CpB,CAAC"}
@@ -0,0 +1,33 @@
1
+ /** Max rows listed per source section before truncating with a summary line. */
2
+ const MAX_LISTED = 10;
3
+ /** One titled section: up to MAX_LISTED lines, then `…and N more.` if truncated. */
4
+ const section = (title, lines) => {
5
+ const shown = lines.slice(0, MAX_LISTED).join("\n");
6
+ const more = lines.length > MAX_LISTED
7
+ ? `\n…and ${lines.length - MAX_LISTED} more.`
8
+ : "";
9
+ return `${title}\n${shown}${more}`;
10
+ };
11
+ /**
12
+ * Build the Pushover message for a set of failing rows, or null when every
13
+ * source is empty (nothing to page). Pure — unit-tested without a request.
14
+ */
15
+ export const buildFailureMessage = (failed) => {
16
+ const sections = [];
17
+ if (failed.charges.length > 0)
18
+ sections.push(section("Charges:", failed.charges.map((c) => `• charge #${c.charging_processes_id} ` +
19
+ `(${c.start_date ? c.start_date.toISOString() : "?"}): ` +
20
+ `${c.failure_count} failures`)));
21
+ if (failed.chargers.length > 0)
22
+ sections.push(section("Chargers:", failed.chargers.map((c) => `• ${c.name}, ${c.city}: ${c.failure_count} failures`)));
23
+ if (failed.slugs.length > 0)
24
+ sections.push(section("Slugs:", failed.slugs.map((s) => `• ${s.location_url_slug}: ${s.failure_count} failures`)));
25
+ if (sections.length === 0)
26
+ return null;
27
+ return {
28
+ title: "🚫 Supercharger failures",
29
+ message: sections.join("\n\n"),
30
+ priority: 1,
31
+ };
32
+ };
33
+ //# sourceMappingURL=failures.common.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"failures.common.js","sourceRoot":"","sources":["../../src/common/failures.common.ts"],"names":[],"mappings":"AAGA,gFAAgF;AAChF,MAAM,UAAU,GAAG,EAAE,CAAC;AAEtB,oFAAoF;AACpF,MAAM,OAAO,GAAG,CAAC,KAAa,EAAE,KAAe,EAAU,EAAE;IACzD,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACpD,MAAM,IAAI,GACR,KAAK,CAAC,MAAM,GAAG,UAAU;QACvB,CAAC,CAAC,UAAU,KAAK,CAAC,MAAM,GAAG,UAAU,QAAQ;QAC7C,CAAC,CAAC,EAAE,CAAC;IACT,OAAO,GAAG,KAAK,KAAK,KAAK,GAAG,IAAI,EAAE,CAAC;AACrC,CAAC,CAAC;AAEF;;;GAGG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,CACjC,MAAqB,EACG,EAAE;IAC1B,MAAM,QAAQ,GAAa,EAAE,CAAC;IAE9B,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC;QAC3B,QAAQ,CAAC,IAAI,CACX,OAAO,CACL,UAAU,EACV,MAAM,CAAC,OAAO,CAAC,GAAG,CAChB,CAAC,CAAC,EAAE,EAAE,CACJ,aAAa,CAAC,CAAC,qBAAqB,GAAG;YACvC,IAAI,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,GAAG,KAAK;YACxD,GAAG,CAAC,CAAC,aAAa,WAAW,CAChC,CACF,CACF,CAAC;IAEJ,IAAI,MAAM,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC;QAC5B,QAAQ,CAAC,IAAI,CACX,OAAO,CACL,WAAW,EACX,MAAM,CAAC,QAAQ,CAAC,GAAG,CACjB,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,aAAa,WAAW,CAC7D,CACF,CACF,CAAC;IAEJ,IAAI,MAAM,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC;QACzB,QAAQ,CAAC,IAAI,CACX,OAAO,CACL,QAAQ,EACR,MAAM,CAAC,KAAK,CAAC,GAAG,CACd,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC,iBAAiB,KAAK,CAAC,CAAC,aAAa,WAAW,CAC/D,CACF,CACF,CAAC;IAEJ,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAEvC,OAAO;QACL,KAAK,EAAE,0BAA0B;QACjC,OAAO,EAAE,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC;QAC9B,QAAQ,EAAE,CAAC;KACZ,CAAC;AACJ,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export * from "./dropbox.common.js";
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/common/index.ts"],"names":[],"mappings":"AACA,cAAc,qBAAqB,CAAC"}
@@ -0,0 +1,3 @@
1
+ // This file is auto-generated by build-cli. Do not edit manually.
2
+ export * from "./dropbox.common.js";
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/common/index.ts"],"names":[],"mappings":"AAAA,kEAAkE;AAClE,cAAc,qBAAqB,CAAC"}
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Tesla charging-CSV import for pic_invoices.
3
+ *
4
+ * Pipeline: raw CSV string
5
+ * → csv-parse (structure: delimiters, quoting, headers)
6
+ * → zod row schema (semantics: "N/A" → null, "22.1285 kwh" → value+unit,
7
+ * "0.51/kwh" → value+unit, NUMERIC precision checks, ISO dates)
8
+ * → PicInvoicesInitializer
9
+ * → parameterized INSERT (numerics stay strings; pg casts to NUMERIC losslessly)
10
+ *
11
+ * deps: npm i csv-parse postgres zod
12
+ */
13
+ import type { Invoice, Meta } from "@mxpicture/tesla-superchargers-mate/types";
14
+ import { z } from "zod";
15
+ export declare const invoicesCsvSchema: z.ZodObject<{
16
+ ChargeStartDateTime: z.ZodPipe<z.ZodString, z.ZodTransform<Date, string>>;
17
+ Name: z.ZodPreprocess<z.ZodNullable<z.ZodString>>;
18
+ Vin: z.ZodString;
19
+ Model: z.ZodPreprocess<z.ZodNullable<z.ZodString>>;
20
+ Country: z.ZodString;
21
+ SiteLocationName: z.ZodString;
22
+ Description: z.ZodPreprocess<z.ZodNullable<z.ZodString>>;
23
+ QuantityBase: z.ZodPipe<z.ZodString, z.ZodTransform<{
24
+ value: number;
25
+ unit: string;
26
+ }, string>>;
27
+ QuantityTier1: z.ZodPreprocess<z.ZodNullable<z.ZodPipe<z.ZodString, z.ZodTransform<{
28
+ value: number;
29
+ unit: string;
30
+ }, string>>>>;
31
+ QuantityTier2: z.ZodPreprocess<z.ZodNullable<z.ZodPipe<z.ZodString, z.ZodTransform<{
32
+ value: number;
33
+ unit: string;
34
+ }, string>>>>;
35
+ QuantityTier3: z.ZodPreprocess<z.ZodNullable<z.ZodPipe<z.ZodString, z.ZodTransform<{
36
+ value: number;
37
+ unit: string;
38
+ }, string>>>>;
39
+ QuantityTier4: z.ZodPreprocess<z.ZodNullable<z.ZodPipe<z.ZodString, z.ZodTransform<{
40
+ value: number;
41
+ unit: string;
42
+ }, string>>>>;
43
+ InvoiceNumber: z.ZodString;
44
+ UnitCostBase: z.ZodPipe<z.ZodString, z.ZodTransform<{
45
+ value: number;
46
+ unit: string;
47
+ }, string>>;
48
+ UnitCostTier1: z.ZodPreprocess<z.ZodNullable<z.ZodPipe<z.ZodString, z.ZodTransform<{
49
+ value: number;
50
+ unit: string;
51
+ }, string>>>>;
52
+ UnitCostTier2: z.ZodPreprocess<z.ZodNullable<z.ZodPipe<z.ZodString, z.ZodTransform<{
53
+ value: number;
54
+ unit: string;
55
+ }, string>>>>;
56
+ UnitCostTier3: z.ZodPreprocess<z.ZodNullable<z.ZodPipe<z.ZodString, z.ZodTransform<{
57
+ value: number;
58
+ unit: string;
59
+ }, string>>>>;
60
+ UnitCostTier4: z.ZodPreprocess<z.ZodNullable<z.ZodPipe<z.ZodString, z.ZodTransform<{
61
+ value: number;
62
+ unit: string;
63
+ }, string>>>>;
64
+ VAT: z.ZodString;
65
+ "Total Exc. VAT": z.ZodString;
66
+ "Total Inc. VAT": z.ZodString;
67
+ Status: z.ZodPreprocess<z.ZodNullable<z.ZodString>>;
68
+ Invoice: z.ZodPreprocess<z.ZodNullable<z.ZodString>>;
69
+ }, z.core.$strip>;
70
+ export type InvoicesCsv = z.infer<typeof invoicesCsvSchema>;
71
+ export interface InvoicesCsvError {
72
+ line: number;
73
+ issues: z.core.$ZodIssue[];
74
+ }
75
+ /** One line per invalid row: `line 3: QuantityBase: invalid quantity: "x"`. */
76
+ export declare const formatInvoicesCsvErrors: (errors: InvoicesCsvError[]) => string;
77
+ export declare const parseInvoicesCsv: (csvText: string) => {
78
+ invoices: InvoicesCsv[];
79
+ errors: InvoicesCsvError[];
80
+ };
81
+ export declare const fromInvoiceCsv: ({ csv, meta, }: {
82
+ csv: InvoicesCsv;
83
+ meta: Meta;
84
+ }) => Invoice;
85
+ //# sourceMappingURL=invoicesCsv.common.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"invoicesCsv.common.d.ts","sourceRoot":"","sources":["../../src/common/invoicesCsv.common.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,KAAK,EACV,OAAO,EAEP,IAAI,EACL,MAAM,2CAA2C,CAAC;AAEnD,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAmGxB,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAwB5B,CAAC;AAEH,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAM5D,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC;CAC5B;AAED,+EAA+E;AAC/E,eAAO,MAAM,uBAAuB,GAAI,QAAQ,gBAAgB,EAAE,KAAG,MAQtD,CAAC;AAEhB,eAAO,MAAM,gBAAgB,GAC3B,SAAS,MAAM,KACd;IACD,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,MAAM,EAAE,gBAAgB,EAAE,CAAC;CAoB5B,CAAC;AAEF,eAAO,MAAM,cAAc,GAAI,gBAG5B;IACD,GAAG,EAAE,WAAW,CAAC;IACjB,IAAI,EAAE,IAAI,CAAC;CACZ,KAAG,OAgCH,CAAC"}
@@ -0,0 +1,185 @@
1
+ /**
2
+ * Tesla charging-CSV import for pic_invoices.
3
+ *
4
+ * Pipeline: raw CSV string
5
+ * → csv-parse (structure: delimiters, quoting, headers)
6
+ * → zod row schema (semantics: "N/A" → null, "22.1285 kwh" → value+unit,
7
+ * "0.51/kwh" → value+unit, NUMERIC precision checks, ISO dates)
8
+ * → PicInvoicesInitializer
9
+ * → parameterized INSERT (numerics stay strings; pg casts to NUMERIC losslessly)
10
+ *
11
+ * deps: npm i csv-parse postgres zod
12
+ */
13
+ import { removeUndefined } from "@mxpicture/tesla-superchargers-mate/transform";
14
+ import { parse } from "csv-parse/sync";
15
+ import { z } from "zod";
16
+ /* ------------------------------------------------------------------ */
17
+ /* helpers */
18
+ /* ------------------------------------------------------------------ */
19
+ const castNum = (name, str) => {
20
+ const value = str === null ? str : Number(str);
21
+ if (value === null || !Number.isFinite(value))
22
+ throw new Error(`invalid ${name}: "${str}"`);
23
+ return value;
24
+ };
25
+ /** "N/A" / "" / whitespace → null, everything else → trimmed string. */
26
+ const naToNull = (v) => {
27
+ if (typeof v !== "string")
28
+ return v;
29
+ const t = v.trim();
30
+ return t === "" || t.toUpperCase() === "N/A" ? null : t;
31
+ };
32
+ /**
33
+ * Decimal kept as *string* (matches Kanel's typing of pg NUMERIC) with a
34
+ * precision/scale check mirroring the column definition, so bad values fail
35
+ * in validation instead of as a pg error mid-insert.
36
+ */
37
+ const numericString = (precision, scale) => z
38
+ .string()
39
+ .trim()
40
+ .regex(/^-?\d+(\.\d+)?$/, "not a decimal number")
41
+ .refine((s) => {
42
+ const [int = "", frac = ""] = s.replace("-", "").split(".");
43
+ return frac.length <= scale && int.length <= precision - scale;
44
+ }, `exceeds NUMERIC(${precision}, ${scale})`);
45
+ /** "22.1285 kwh" → { value: 22.1285, unit: "kwh" } (unit optional). */
46
+ const quantity = z
47
+ .string()
48
+ .trim()
49
+ .transform((s, ctx) => {
50
+ const m = s.match(/^(-?\d+(?:\.\d+)?)(?:\s+(\S+))?$/);
51
+ if (!m) {
52
+ ctx.addIssue({ code: "custom", message: `invalid quantity: "${s}"` });
53
+ return z.NEVER;
54
+ }
55
+ const value = m[1] === null ? m[1] : Number(m[1]);
56
+ if (value === null || !Number.isFinite(value)) {
57
+ ctx.addIssue({ code: "custom", message: `invalid quantity: "${s}"` });
58
+ return z.NEVER;
59
+ }
60
+ return { value, unit: m[2]?.toLowerCase() ?? null };
61
+ })
62
+ .refine((q) => numericString(8, 4).safeParse(String(q.value)).success, {
63
+ message: "quantity exceeds NUMERIC(8, 4)",
64
+ });
65
+ /** "0.51/kwh" → { value: 0.51, unit: "kwh" }. */
66
+ const unitCost = z
67
+ .string()
68
+ .trim()
69
+ .transform((s, ctx) => {
70
+ const m = s.match(/^(-?\d+(?:\.\d+)?)\s*\/\s*(\S+)$/);
71
+ if (!m) {
72
+ ctx.addIssue({ code: "custom", message: `invalid unit cost: "${s}"` });
73
+ return z.NEVER;
74
+ }
75
+ const value = m[1] === null ? m[1] : Number(m[1]);
76
+ if (value === null || !Number.isFinite(value)) {
77
+ ctx.addIssue({ code: "custom", message: `invalid unit cost: "${s}"` });
78
+ return z.NEVER;
79
+ }
80
+ return { value, unit: m[2].toLowerCase() };
81
+ })
82
+ .refine((c) => numericString(4, 2).safeParse(String(c.value)).success, {
83
+ message: "unit cost exceeds NUMERIC(4, 2)",
84
+ });
85
+ /** ISO-8601 with offset ("2026-03-31T14:56:00+02:00") → Date. */
86
+ const isoDate = z
87
+ .string()
88
+ .trim()
89
+ .transform((s, ctx) => {
90
+ const d = new Date(s);
91
+ if (Number.isNaN(d.getTime())) {
92
+ ctx.addIssue({ code: "custom", message: `invalid date: "${s}"` });
93
+ return z.NEVER;
94
+ }
95
+ return d;
96
+ });
97
+ const nullable = (schema) => z.preprocess(naToNull, schema.nullable());
98
+ const nonEmpty = z.string().trim().min(1);
99
+ /* ------------------------------------------------------------------ */
100
+ /* CSV row schema (keys = exact CSV headers) */
101
+ /* ------------------------------------------------------------------ */
102
+ export const invoicesCsvSchema = z.object({
103
+ ChargeStartDateTime: isoDate,
104
+ Name: nullable(nonEmpty),
105
+ Vin: nonEmpty,
106
+ Model: nullable(nonEmpty),
107
+ Country: nonEmpty,
108
+ SiteLocationName: nonEmpty,
109
+ Description: nullable(nonEmpty),
110
+ QuantityBase: quantity,
111
+ QuantityTier1: nullable(quantity),
112
+ QuantityTier2: nullable(quantity),
113
+ QuantityTier3: nullable(quantity),
114
+ QuantityTier4: nullable(quantity),
115
+ InvoiceNumber: nonEmpty,
116
+ UnitCostBase: unitCost,
117
+ UnitCostTier1: nullable(unitCost),
118
+ UnitCostTier2: nullable(unitCost),
119
+ UnitCostTier3: nullable(unitCost),
120
+ UnitCostTier4: nullable(unitCost),
121
+ VAT: numericString(5, 2),
122
+ "Total Exc. VAT": numericString(5, 2),
123
+ "Total Inc. VAT": numericString(5, 2),
124
+ Status: nullable(nonEmpty),
125
+ Invoice: nullable(z.string().trim().url()),
126
+ });
127
+ /** One line per invalid row: `line 3: QuantityBase: invalid quantity: "x"`. */
128
+ export const formatInvoicesCsvErrors = (errors) => errors
129
+ .map(({ line, issues }) => `line ${line}: ${issues
130
+ .map((issue) => `${issue.path.join(".")}: ${issue.message}`)
131
+ .join("; ")}`)
132
+ .join("\n");
133
+ export const parseInvoicesCsv = (csvText) => {
134
+ // csv-parse handles quoting ("Windischeschenbach, Germany"), BOM, CRLF.
135
+ const records = parse(csvText, {
136
+ columns: true, // first line = header, rows become objects
137
+ bom: true,
138
+ skip_empty_lines: true,
139
+ trim: true,
140
+ });
141
+ const invoices = [];
142
+ const errors = [];
143
+ records.forEach((record, i) => {
144
+ const result = invoicesCsvSchema.safeParse(record);
145
+ if (result.success)
146
+ invoices.push(result.data);
147
+ else
148
+ errors.push({ line: i + 2, issues: result.error.issues });
149
+ });
150
+ return { invoices, errors };
151
+ };
152
+ export const fromInvoiceCsv = ({ csv, meta, }) => {
153
+ const result = {
154
+ id: undefined,
155
+ chargingProcessesId: null,
156
+ fetched_at: meta.fetched_at,
157
+ last_fetch_uuid: meta.last_fetch_uuid,
158
+ chargeStartDateTime: csv.ChargeStartDateTime,
159
+ name: csv.Name,
160
+ vin: csv.Vin,
161
+ model: csv.Model,
162
+ country: csv.Country,
163
+ siteLocationName: csv.SiteLocationName,
164
+ description: csv.Description,
165
+ ucom: csv.QuantityBase.unit,
166
+ quantityBase: csv.QuantityBase.value,
167
+ quantityTier1: csv.QuantityTier1?.value,
168
+ quantityTier2: csv.QuantityTier2?.value,
169
+ quantityTier3: csv.QuantityTier3?.value,
170
+ quantityTier4: csv.QuantityTier4?.value,
171
+ invoiceNumber: csv.InvoiceNumber,
172
+ unitCostBase: csv.UnitCostBase.value,
173
+ unitCostTier1: csv.UnitCostTier1?.value,
174
+ unitCostTier2: csv.UnitCostTier2?.value,
175
+ unitCostTier3: csv.UnitCostTier3?.value,
176
+ unitCostTier4: csv.UnitCostTier4?.value,
177
+ vat: castNum("VAT", csv.VAT),
178
+ totalExcVat: castNum("Total Exc. VAT", csv["Total Exc. VAT"]),
179
+ totalIncVat: castNum("Total Inc. VAT", csv["Total Inc. VAT"]),
180
+ status: csv.Status,
181
+ invoice: csv.Invoice,
182
+ };
183
+ return removeUndefined(result);
184
+ };
185
+ //# sourceMappingURL=invoicesCsv.common.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"invoicesCsv.common.js","sourceRoot":"","sources":["../../src/common/invoicesCsv.common.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Pure pricebook helpers: snapshot-group comparison for the history sync and
3
+ * the time-of-use window filter for prices-at-a-time lookups. No DB access —
4
+ * unit-testable in isolation.
5
+ */
6
+ import type { ChargingRate, PricebookContent } from "../types/pricebooks.types.js";
7
+ /** Feed values identifying the row that prices a Tesla vehicle's charging. */
8
+ export declare const TESLA_VEHICLE_MAKE = "tsla";
9
+ export declare const CHARGING_FEE_TYPE = "charging";
10
+ export declare const KWH_UOM = "kwh";
11
+ /** Canonical `HH:MM:SS` for a TIME value (Date → its UTC wall time). */
12
+ export declare const normalizeTime: (time: Date | string) => string;
13
+ export declare const normalizeRate: (rate: string | number) => string;
14
+ /** One row's content columns as a canonical string, order-independent in `days`. */
15
+ export declare const canonicalRow: (row: PricebookContent) => string;
16
+ /** Local weekday (0=Sunday … 6=Saturday, matching the feed's `days`) and `HH:MM:SS` of `at` in an IANA time zone. */
17
+ export declare const localDayTime: (at: Date, timeZone: string) => {
18
+ weekday: number;
19
+ time: string;
20
+ };
21
+ /**
22
+ * Does this row's time-of-use window cover the instant `at` (in the charger's
23
+ * time zone)? Empty `days` means no day restriction (non-TOU rows). The feed
24
+ * partitions a day into windows that never span into the next day, with
25
+ * `00:00` as `end` meaning end-of-day (e.g. `00:00–16:00`, `16:00–20:00`,
26
+ * `20:00–00:00`); `start == end == 00:00` is a whole-day window. `end` is
27
+ * exclusive — each instant falls into exactly one window of a partition.
28
+ */
29
+ export declare const pricebookCoversInstant: (row: Pick<PricebookContent, "days" | "start_time" | "end_time">, at: Date, timeZone: string) => boolean;
30
+ /**
31
+ * Order-independent multiset equality over the content columns — the "did any
32
+ * price change?" check of the history sync. Meta columns (`fetched_at`,
33
+ * `last_fetch_uuid`, ids) are ignored. Normalization matters: a false
34
+ * "different" here creates a bogus new group on every fetch.
35
+ */
36
+ export declare const samePricebookGroup: (a: PricebookContent[], b: PricebookContent[]) => boolean;
37
+ /**
38
+ * The per-kWh rate a Tesla vehicle pays for charging, picked from the
39
+ * variant rows valid at one instant (member/non-member × vehicle make ×
40
+ * fee type, as `findPrices` returns them): fee type "charging", vehicle
41
+ * make "tesla", the non-member row when both memberships are present
42
+ * (Tesla drivers need no membership). Returns null when no such row
43
+ * exists or the row doesn't bill per kWh — per-minute billing depends on
44
+ * charging power, which stored energy totals don't carry.
45
+ */
46
+ export declare const teslaChargingRate: (rows: PricebookContent[]) => ChargingRate | null;
47
+ //# sourceMappingURL=pricebooks.common.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pricebooks.common.d.ts","sourceRoot":"","sources":["../../src/common/pricebooks.common.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EACV,YAAY,EACZ,gBAAgB,EACjB,MAAM,8BAA8B,CAAC;AAEtC,8EAA8E;AAC9E,eAAO,MAAM,kBAAkB,SAAS,CAAC;AACzC,eAAO,MAAM,iBAAiB,aAAa,CAAC;AAC5C,eAAO,MAAM,OAAO,QAAQ,CAAC;AAE7B,wEAAwE;AACxE,eAAO,MAAM,aAAa,GAAI,MAAM,IAAI,GAAG,MAAM,KAAG,MAInD,CAAC;AAEF,eAAO,MAAM,aAAa,GAAI,MAAM,MAAM,GAAG,MAAM,KAAG,MAC7B,CAAC;AAE1B,oFAAoF;AACpF,eAAO,MAAM,YAAY,GAAI,KAAK,gBAAgB,KAAG,MAcxC,CAAC;AAEd,qHAAqH;AACrH,eAAO,MAAM,YAAY,GACvB,IAAI,IAAI,EACR,UAAU,MAAM,KACf;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAkBjC,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,sBAAsB,GACjC,KAAK,IAAI,CAAC,gBAAgB,EAAE,MAAM,GAAG,YAAY,GAAG,UAAU,CAAC,EAC/D,IAAI,IAAI,EACR,UAAU,MAAM,KACf,OASF,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,GAC7B,GAAG,gBAAgB,EAAE,EACrB,GAAG,gBAAgB,EAAE,KACpB,OAKF,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,iBAAiB,GAC5B,MAAM,gBAAgB,EAAE,KACvB,YAAY,GAAG,IAWjB,CAAC"}
@@ -0,0 +1,95 @@
1
+ /** Feed values identifying the row that prices a Tesla vehicle's charging. */
2
+ export const TESLA_VEHICLE_MAKE = "tsla";
3
+ export const CHARGING_FEE_TYPE = "charging";
4
+ export const KWH_UOM = "kwh";
5
+ /** Canonical `HH:MM:SS` for a TIME value (Date → its UTC wall time). */
6
+ export const normalizeTime = (time) => {
7
+ if (time instanceof Date)
8
+ return time.toISOString().slice(11, 19); // transforms build Date(0) + UTC hours
9
+ const [h = "0", m = "0", s = "0"] = time.split(".")[0].split(":");
10
+ return [h, m, s].map((part) => part.padStart(2, "0")).join(":");
11
+ };
12
+ export const normalizeRate = (rate) => Number(rate).toFixed(2);
13
+ /** One row's content columns as a canonical string, order-independent in `days`. */
14
+ export const canonicalRow = (row) => [
15
+ row.currency_code,
16
+ row.uom,
17
+ [...row.days].sort((a, b) => a - b).join(","),
18
+ normalizeTime(row.start_time),
19
+ normalizeTime(row.end_time),
20
+ row.vehicle_make_type,
21
+ row.fee_type,
22
+ row.is_member_pricebook,
23
+ row.is_tou,
24
+ normalizeRate(row.rate_base),
25
+ normalizeRate(row.rate_tier_1),
26
+ normalizeRate(row.rate_tier_2),
27
+ ].join("|");
28
+ /** Local weekday (0=Sunday … 6=Saturday, matching the feed's `days`) and `HH:MM:SS` of `at` in an IANA time zone. */
29
+ export const localDayTime = (at, timeZone) => {
30
+ const parts = new Intl.DateTimeFormat("en-US", {
31
+ timeZone,
32
+ weekday: "short",
33
+ hourCycle: "h23",
34
+ hour: "2-digit",
35
+ minute: "2-digit",
36
+ second: "2-digit",
37
+ }).formatToParts(at);
38
+ const part = (type) => parts.find((p) => p.type === type)?.value ?? "";
39
+ const weekday = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"].indexOf(part("weekday"));
40
+ return {
41
+ weekday,
42
+ time: `${part("hour")}:${part("minute")}:${part("second")}`,
43
+ };
44
+ };
45
+ /**
46
+ * Does this row's time-of-use window cover the instant `at` (in the charger's
47
+ * time zone)? Empty `days` means no day restriction (non-TOU rows). The feed
48
+ * partitions a day into windows that never span into the next day, with
49
+ * `00:00` as `end` meaning end-of-day (e.g. `00:00–16:00`, `16:00–20:00`,
50
+ * `20:00–00:00`); `start == end == 00:00` is a whole-day window. `end` is
51
+ * exclusive — each instant falls into exactly one window of a partition.
52
+ */
53
+ export const pricebookCoversInstant = (row, at, timeZone) => {
54
+ const { weekday, time } = localDayTime(at, timeZone);
55
+ if (row.days.length > 0 && !row.days.includes(weekday))
56
+ return false;
57
+ const start = normalizeTime(row.start_time);
58
+ const end = normalizeTime(row.end_time);
59
+ if (start === "00:00:00" && end === "00:00:00")
60
+ return true; // whole day
61
+ if (start > end)
62
+ return time >= start || time < end; // end 00:00 = end of day
63
+ return time >= start && time < end;
64
+ };
65
+ /**
66
+ * Order-independent multiset equality over the content columns — the "did any
67
+ * price change?" check of the history sync. Meta columns (`fetched_at`,
68
+ * `last_fetch_uuid`, ids) are ignored. Normalization matters: a false
69
+ * "different" here creates a bogus new group on every fetch.
70
+ */
71
+ export const samePricebookGroup = (a, b) => {
72
+ if (a.length !== b.length)
73
+ return false;
74
+ const canonical = (rows) => rows.map(canonicalRow).sort().join("\n");
75
+ return canonical(a) === canonical(b);
76
+ };
77
+ /**
78
+ * The per-kWh rate a Tesla vehicle pays for charging, picked from the
79
+ * variant rows valid at one instant (member/non-member × vehicle make ×
80
+ * fee type, as `findPrices` returns them): fee type "charging", vehicle
81
+ * make "tesla", the non-member row when both memberships are present
82
+ * (Tesla drivers need no membership). Returns null when no such row
83
+ * exists or the row doesn't bill per kWh — per-minute billing depends on
84
+ * charging power, which stored energy totals don't carry.
85
+ */
86
+ export const teslaChargingRate = (rows) => {
87
+ const candidates = rows.filter((row) => row.fee_type.toLowerCase() === CHARGING_FEE_TYPE &&
88
+ row.vehicle_make_type.toLowerCase() === TESLA_VEHICLE_MAKE);
89
+ const row = candidates.find((candidate) => !candidate.is_member_pricebook) ??
90
+ candidates[0];
91
+ if (!row || row.uom.toLowerCase() !== KWH_UOM)
92
+ return null;
93
+ return { rate: Number(row.rate_base), currencyCode: row.currency_code };
94
+ };
95
+ //# sourceMappingURL=pricebooks.common.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pricebooks.common.js","sourceRoot":"","sources":["../../src/common/pricebooks.common.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Minimal Pushover (https://pushover.net) sender. Mirrors the shape used by the
3
+ * sibling deployment scripts (a token/user app message with an optional
4
+ * priority), so it reuses the same PUSHOVER_* credentials the deployment already
5
+ * injects. Sending NEVER throws — a notify failure is logged and reported via
6
+ * the return value so it can't abort the run that is trying to alert. When
7
+ * either credential is missing the whole feature is a no-op.
8
+ */
9
+ import type { PushoverConfig, PushoverMessage } from "../types/pushover.types.js";
10
+ /**
11
+ * Pushover credentials from `PUSHOVER_API_TOKEN` / `PUSHOVER_USER_KEY`, or null
12
+ * when either is unset (so the caller treats alerting as disabled).
13
+ */
14
+ export declare const pushoverConfigFromEnv: () => PushoverConfig | null;
15
+ /** The form body Pushover expects. Pure, so it is unit-tested without a request. */
16
+ export declare const buildPushoverForm: (cfg: PushoverConfig, msg: PushoverMessage) => Record<string, string>;
17
+ /**
18
+ * POST one message to Pushover. Returns true on success; on any failure it logs
19
+ * via `logger` and returns false (never throws), so a notify error can't kill
20
+ * the caller.
21
+ */
22
+ export declare const sendPushover: (cfg: PushoverConfig, msg: PushoverMessage, logger?: (m: string) => void) => Promise<boolean>;
23
+ //# sourceMappingURL=pushover.common.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pushover.common.d.ts","sourceRoot":"","sources":["../../src/common/pushover.common.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EACV,cAAc,EACd,eAAe,EAChB,MAAM,4BAA4B,CAAC;AAIpC;;;GAGG;AACH,eAAO,MAAM,qBAAqB,QAAO,cAAc,GAAG,IAKzD,CAAC;AAEF,oFAAoF;AACpF,eAAO,MAAM,iBAAiB,GAC5B,KAAK,cAAc,EACnB,KAAK,eAAe,KACnB,MAAM,CAAC,MAAM,EAAE,MAAM,CAMtB,CAAC;AAEH;;;;GAIG;AACH,eAAO,MAAM,YAAY,GACvB,KAAK,cAAc,EACnB,KAAK,eAAe,EACpB,SAAQ,CAAC,CAAC,EAAE,MAAM,KAAK,IAAe,KACrC,OAAO,CAAC,OAAO,CAkBjB,CAAC"}
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Minimal Pushover (https://pushover.net) sender. Mirrors the shape used by the
3
+ * sibling deployment scripts (a token/user app message with an optional
4
+ * priority), so it reuses the same PUSHOVER_* credentials the deployment already
5
+ * injects. Sending NEVER throws — a notify failure is logged and reported via
6
+ * the return value so it can't abort the run that is trying to alert. When
7
+ * either credential is missing the whole feature is a no-op.
8
+ */
9
+ const PUSHOVER_URL = "https://api.pushover.net/1/messages.json";
10
+ /**
11
+ * Pushover credentials from `PUSHOVER_API_TOKEN` / `PUSHOVER_USER_KEY`, or null
12
+ * when either is unset (so the caller treats alerting as disabled).
13
+ */
14
+ export const pushoverConfigFromEnv = () => {
15
+ const token = process.env.PUSHOVER_API_TOKEN;
16
+ const user = process.env.PUSHOVER_USER_KEY;
17
+ if (!token || !user)
18
+ return null;
19
+ return { token, user };
20
+ };
21
+ /** The form body Pushover expects. Pure, so it is unit-tested without a request. */
22
+ export const buildPushoverForm = (cfg, msg) => ({
23
+ token: cfg.token,
24
+ user: cfg.user,
25
+ title: msg.title,
26
+ message: msg.message,
27
+ priority: String(msg.priority ?? 0),
28
+ });
29
+ /**
30
+ * POST one message to Pushover. Returns true on success; on any failure it logs
31
+ * via `logger` and returns false (never throws), so a notify error can't kill
32
+ * the caller.
33
+ */
34
+ export const sendPushover = async (cfg, msg, logger = () => { }) => {
35
+ try {
36
+ const res = await fetch(PUSHOVER_URL, {
37
+ method: "POST",
38
+ body: new URLSearchParams(buildPushoverForm(cfg, msg)),
39
+ });
40
+ if (!res.ok) {
41
+ logger(`pushover send failed: HTTP ${res.status}`);
42
+ return false;
43
+ }
44
+ logger(`pushover sent: ${msg.title}`);
45
+ return true;
46
+ }
47
+ catch (e) {
48
+ logger(`pushover send failed: ${e instanceof Error ? e.message : String(e)}`);
49
+ return false;
50
+ }
51
+ };
52
+ //# sourceMappingURL=pushover.common.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pushover.common.js","sourceRoot":"","sources":["../../src/common/pushover.common.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAOH,MAAM,YAAY,GAAG,0CAA0C,CAAC;AAEhE;;;GAGG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,GAA0B,EAAE;IAC/D,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC;IAC7C,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC;IAC3C,IAAI,CAAC,KAAK,IAAI,CAAC,IAAI;QAAE,OAAO,IAAI,CAAC;IACjC,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;AACzB,CAAC,CAAC;AAEF,oFAAoF;AACpF,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAC/B,GAAmB,EACnB,GAAoB,EACI,EAAE,CAAC,CAAC;IAC5B,KAAK,EAAE,GAAG,CAAC,KAAK;IAChB,IAAI,EAAE,GAAG,CAAC,IAAI;IACd,KAAK,EAAE,GAAG,CAAC,KAAK;IAChB,OAAO,EAAE,GAAG,CAAC,OAAO;IACpB,QAAQ,EAAE,MAAM,CAAC,GAAG,CAAC,QAAQ,IAAI,CAAC,CAAC;CACpC,CAAC,CAAC;AAEH;;;;GAIG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,KAAK,EAC/B,GAAmB,EACnB,GAAoB,EACpB,SAA8B,GAAG,EAAE,GAAE,CAAC,EACpB,EAAE;IACpB,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,YAAY,EAAE;YACpC,MAAM,EAAE,MAAM;YACd,IAAI,EAAE,IAAI,eAAe,CAAC,iBAAiB,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;SACvD,CAAC,CAAC;QACH,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;YACZ,MAAM,CAAC,8BAA8B,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC;YACnD,OAAO,KAAK,CAAC;QACf,CAAC;QACD,MAAM,CAAC,kBAAkB,GAAG,CAAC,KAAK,EAAE,CAAC,CAAC;QACtC,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,MAAM,CACJ,yBAAyB,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CACtE,CAAC;QACF,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC,CAAC"}
@@ -0,0 +1,4 @@
1
+ /** Abort the sync when the feed shrinks below this share of the active rows. */
2
+ export declare const CIRCUIT_BREAKER_MIN_RATIO = 0.5;
3
+ export declare const SYNC_META_COLUMNS: string[];
4
+ //# sourceMappingURL=sync.common.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sync.common.d.ts","sourceRoot":"","sources":["../../src/common/sync.common.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,eAAO,MAAM,yBAAyB,MAAM,CAAC;AAE7C,eAAO,MAAM,iBAAiB,UAM7B,CAAC"}
@@ -0,0 +1,10 @@
1
+ /** Abort the sync when the feed shrinks below this share of the active rows. */
2
+ export const CIRCUIT_BREAKER_MIN_RATIO = 0.5;
3
+ export const SYNC_META_COLUMNS = [
4
+ "fetched_at",
5
+ "last_fetch_uuid",
6
+ "failure_count",
7
+ "failure_notified_at",
8
+ "deleted_at",
9
+ ];
10
+ //# sourceMappingURL=sync.common.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sync.common.js","sourceRoot":"","sources":["../../src/common/sync.common.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,MAAM,CAAC,MAAM,yBAAyB,GAAG,GAAG,CAAC;AAE7C,MAAM,CAAC,MAAM,iBAAiB,GAAG;IAC/B,YAAY;IACZ,iBAAiB;IACjB,eAAe;IACf,qBAAqB;IACrB,YAAY;CACb,CAAC"}
@@ -0,0 +1,23 @@
1
+ export interface DropboxEntry {
2
+ name: string;
3
+ /** Lower-cased full path as used by the Dropbox API (e.g. "/invoices/a.csv"). */
4
+ path: string;
5
+ size: number;
6
+ serverModified: Date;
7
+ }
8
+ export interface DropboxFile extends DropboxEntry {
9
+ /** File content decoded as UTF-8. */
10
+ content: string;
11
+ }
12
+ export interface DropboxConfigParams {
13
+ baseDir?: string;
14
+ }
15
+ export interface DropboxConfigAccessParams extends DropboxConfigParams {
16
+ accessToken: string;
17
+ }
18
+ export interface DropboxConfigClientParams extends DropboxConfigParams {
19
+ clientId: string;
20
+ clientSecret: string;
21
+ refreshToken: string;
22
+ }
23
+ //# sourceMappingURL=dropbox.types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dropbox.types.d.ts","sourceRoot":"","sources":["../../src/types/dropbox.types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,iFAAiF;IACjF,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,cAAc,EAAE,IAAI,CAAC;CACtB;AAED,MAAM,WAAW,WAAY,SAAQ,YAAY;IAC/C,qCAAqC;IACrC,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,mBAAmB;IAClC,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,yBAA0B,SAAQ,mBAAmB;IACpE,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,yBAA0B,SAAQ,mBAAmB;IACpE,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;CACtB"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=dropbox.types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dropbox.types.js","sourceRoot":"","sources":["../../src/types/dropbox.types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export * from "./dropbox.types.js";
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AACA,cAAc,oBAAoB,CAAC"}
@@ -0,0 +1,3 @@
1
+ // This file is auto-generated by build-cli. Do not edit manually.
2
+ export * from "./dropbox.types.js";
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA,kEAAkE;AAClE,cAAc,oBAAoB,CAAC"}
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "@mxpicture/tesla-superchargers-cloud-store",
3
+ "version": "0.1.4",
4
+ "description": "Cloud store intergration e.g. dropbox",
5
+ "type": "module",
6
+ "author": "MXPicture",
7
+ "license": "MIT",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/MXPicture/npm-tesla-superchargers.git"
11
+ },
12
+ "exports": {
13
+ "./client": {
14
+ "types": "./src/client/index.ts",
15
+ "default": "./dist/client/index.js"
16
+ },
17
+ "./package.json": "./package.json",
18
+ "./common": {
19
+ "types": "./src/common/index.ts",
20
+ "default": "./dist/common/index.js"
21
+ },
22
+ "./types": {
23
+ "types": "./src/types/index.ts",
24
+ "default": "./dist/types/index.js"
25
+ }
26
+ },
27
+ "files": [
28
+ "dist"
29
+ ],
30
+ "engines": {
31
+ "node": ">=24"
32
+ },
33
+ "scripts": {
34
+ "lint": "eslint \"src/**/*.ts\" --ext .ts --cache --cache-strategy content",
35
+ "lint:ci": "eslint \"src/**/*.ts\" --ext .ts --max-warnings=0",
36
+ "build": "tsc -b .",
37
+ "test": "tsc -b . && vitest run"
38
+ },
39
+ "publishConfig": {
40
+ "access": "public"
41
+ },
42
+ "dependencies": {
43
+ "@mxpicture/tesla-superchargers-generated": "workspace:*",
44
+ "@mxpicture/tesla-superchargers-mate": "workspace:*",
45
+ "csv-parse": "^7.0.1",
46
+ "dropbox": "^10.37.1",
47
+ "json5": "^2.2.3",
48
+ "zod": "^4.4.3"
49
+ },
50
+ "devDependencies": {
51
+ "@types/node": "^25.2.3",
52
+ "typescript": "^5.9.3",
53
+ "vitest": "^4.0.18"
54
+ }
55
+ }