@faable/deploy-sdk 2.17.1 → 2.18.0

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 (36) hide show
  1. package/dist/FaableDeployApi.d.ts +89 -0
  2. package/dist/FaableDeployApi.d.ts.map +1 -1
  3. package/dist/FaableDeployApi.js +10 -0
  4. package/dist/api/api-types.d.ts +19 -0
  5. package/dist/api/api-types.d.ts.map +1 -1
  6. package/dist/api/generated-client.d.ts +856 -3
  7. package/dist/api/generated-client.d.ts.map +1 -1
  8. package/dist/api/generated-client.js +510 -3
  9. package/dist/api/types.d.ts +4895 -819
  10. package/dist/api/types.d.ts.map +1 -1
  11. package/dist/index.d.ts +1 -0
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +1 -0
  14. package/dist/informers/Indexer.d.ts +24 -0
  15. package/dist/informers/Indexer.d.ts.map +1 -0
  16. package/dist/informers/Indexer.js +70 -0
  17. package/dist/informers/InformerFactory.d.ts +67 -0
  18. package/dist/informers/InformerFactory.d.ts.map +1 -0
  19. package/dist/informers/InformerFactory.js +184 -0
  20. package/dist/informers/WatchClient.d.ts +38 -0
  21. package/dist/informers/WatchClient.d.ts.map +1 -0
  22. package/dist/informers/WatchClient.js +9 -0
  23. package/dist/informers/deployApiWatchClient.d.ts +4 -0
  24. package/dist/informers/deployApiWatchClient.d.ts.map +1 -0
  25. package/dist/informers/deployApiWatchClient.js +36 -0
  26. package/dist/informers/index.d.ts +6 -0
  27. package/dist/informers/index.d.ts.map +1 -0
  28. package/dist/informers/index.js +5 -0
  29. package/dist/informers/ndjson.d.ts +2 -0
  30. package/dist/informers/ndjson.d.ts.map +1 -0
  31. package/dist/informers/ndjson.js +19 -0
  32. package/dist/informers/rv.d.ts +4 -0
  33. package/dist/informers/rv.d.ts.map +1 -0
  34. package/dist/informers/rv.js +16 -0
  35. package/package.json +1 -1
  36. package/spec/openapi.json +16503 -7626
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export * from "./FaableDeployApi.js";
2
+ export * from "./informers/index.js";
2
3
  export * from "./api/generated-client.js";
3
4
  export * from "./api/custom-types.js";
4
5
  export * from "./api/types.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,sBAAsB,CAAC;AACrC,cAAc,2BAA2B,CAAC;AAG1C,cAAc,uBAAuB,CAAC;AACtC,cAAc,gBAAgB,CAAC;AAM/B,cAAc,kBAAkB,CAAC;AAMjC,OAAO,KAAK,KAAK,MAAM,uBAAuB,CAAC;AAC/C,OAAO,EAAE,KAAK,EAAE,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,2BAA2B,CAAC;AAG1C,cAAc,uBAAuB,CAAC;AACtC,cAAc,gBAAgB,CAAC;AAM/B,cAAc,kBAAkB,CAAC;AAMjC,OAAO,KAAK,KAAK,MAAM,uBAAuB,CAAC;AAC/C,OAAO,EAAE,KAAK,EAAE,CAAC"}
package/dist/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  export * from "./FaableDeployApi.js";
2
+ export * from "./informers/index.js";
2
3
  export * from "./api/generated-client.js";
3
4
  // custom-types re-exports the auto-generated api-types plus the hand-maintained
4
5
  // extras (friendlier event names, the AppTraffic interface).
@@ -0,0 +1,24 @@
1
+ export type IndexFn<T> = (obj: T) => string[];
2
+ export declare class Indexer<T extends {
3
+ id: string;
4
+ }> {
5
+ private objects;
6
+ private indexFns;
7
+ private indexes;
8
+ addIndex(name: string, fn: IndexFn<T>): void;
9
+ get(id: string): T | undefined;
10
+ rvOf(id: string): string | undefined;
11
+ list(): T[];
12
+ ids(): string[];
13
+ byIndex(name: string, key: string): T[];
14
+ /** Upsert if newer. Returns the previous object, or null if dropped. */
15
+ upsert(obj: T, rv: string): {
16
+ applied: boolean;
17
+ old?: T;
18
+ };
19
+ /** Remove unless what is held is newer than the delete. */
20
+ delete(id: string, rv: string): T | undefined;
21
+ private indexObj;
22
+ private unindex;
23
+ }
24
+ //# sourceMappingURL=Indexer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Indexer.d.ts","sourceRoot":"","sources":["../../src/informers/Indexer.ts"],"names":[],"mappings":"AAMA,MAAM,MAAM,OAAO,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,KAAK,MAAM,EAAE,CAAA;AAE7C,qBAAa,OAAO,CAAC,CAAC,SAAS;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE;IAC3C,OAAO,CAAC,OAAO,CAA4C;IAC3D,OAAO,CAAC,QAAQ,CAAgC;IAChD,OAAO,CAAC,OAAO,CAA8C;IAE7D,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,OAAO,CAAC,CAAC,CAAC,GAAG,IAAI;IAO5C,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,CAAC,GAAG,SAAS;IAI9B,IAAI,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAIpC,IAAI,IAAI,CAAC,EAAE;IAIX,GAAG,IAAI,MAAM,EAAE;IAIf,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,CAAC,EAAE;IAMvC,wEAAwE;IACxE,MAAM,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,EAAE,MAAM,GAAG;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,GAAG,CAAC,EAAE,CAAC,CAAA;KAAE;IASzD,2DAA2D;IAC3D,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,CAAC,GAAG,SAAS;IAQ7C,OAAO,CAAC,QAAQ;IAQhB,OAAO,CAAC,OAAO;CAShB"}
@@ -0,0 +1,70 @@
1
+ import { compareRv } from "./rv.js";
2
+ export class Indexer {
3
+ objects = new Map();
4
+ indexFns = new Map();
5
+ indexes = new Map();
6
+ addIndex(name, fn) {
7
+ this.indexFns.set(name, fn);
8
+ const idx = new Map();
9
+ this.indexes.set(name, idx);
10
+ for (const { obj } of this.objects.values())
11
+ this.indexObj(name, fn, obj);
12
+ }
13
+ get(id) {
14
+ return this.objects.get(id)?.obj;
15
+ }
16
+ rvOf(id) {
17
+ return this.objects.get(id)?.rv;
18
+ }
19
+ list() {
20
+ return [...this.objects.values()].map((e) => e.obj);
21
+ }
22
+ ids() {
23
+ return [...this.objects.keys()];
24
+ }
25
+ byIndex(name, key) {
26
+ const ids = this.indexes.get(name)?.get(key);
27
+ if (!ids)
28
+ return [];
29
+ return [...ids].map((id) => this.objects.get(id).obj);
30
+ }
31
+ /** Upsert if newer. Returns the previous object, or null if dropped. */
32
+ upsert(obj, rv) {
33
+ const cur = this.objects.get(obj.id);
34
+ if (cur && compareRv(rv, cur.rv) <= 0)
35
+ return { applied: false };
36
+ if (cur)
37
+ this.unindex(cur.obj);
38
+ this.objects.set(obj.id, { obj, rv });
39
+ for (const [name, fn] of this.indexFns)
40
+ this.indexObj(name, fn, obj);
41
+ return { applied: true, old: cur?.obj };
42
+ }
43
+ /** Remove unless what is held is newer than the delete. */
44
+ delete(id, rv) {
45
+ const cur = this.objects.get(id);
46
+ if (!cur || compareRv(cur.rv, rv) > 0)
47
+ return undefined;
48
+ this.unindex(cur.obj);
49
+ this.objects.delete(id);
50
+ return cur.obj;
51
+ }
52
+ indexObj(name, fn, obj) {
53
+ const idx = this.indexes.get(name);
54
+ for (const key of fn(obj)) {
55
+ if (!idx.has(key))
56
+ idx.set(key, new Set());
57
+ idx.get(key).add(obj.id);
58
+ }
59
+ }
60
+ unindex(obj) {
61
+ for (const [name, fn] of this.indexFns) {
62
+ const idx = this.indexes.get(name);
63
+ for (const key of fn(obj)) {
64
+ idx.get(key)?.delete(obj.id);
65
+ if (idx.get(key)?.size === 0)
66
+ idx.delete(key);
67
+ }
68
+ }
69
+ }
70
+ }
@@ -0,0 +1,67 @@
1
+ import { Indexer, type IndexFn } from "./Indexer.js";
2
+ import { type WatchClient, type WatchResource } from "./WatchClient.js";
3
+ export type InformerEvent = "add" | "update" | "delete";
4
+ type Handler<T> = (obj: T, old?: T) => void;
5
+ export declare class SharedInformer<T extends {
6
+ id: string;
7
+ }> {
8
+ readonly store: Indexer<T>;
9
+ private handlers;
10
+ readonly resource: WatchResource;
11
+ constructor(resource: WatchResource);
12
+ on(event: InformerEvent, handler: Handler<T>): this;
13
+ addIndex(name: string, fn: IndexFn<T>): this;
14
+ get(id: string): T | undefined;
15
+ list(): T[];
16
+ byIndex(name: string, key: string): T[];
17
+ /** @internal */
18
+ emit(event: InformerEvent, obj: T, old?: T): void;
19
+ /** @internal Apply an upsert; emits add/update. */
20
+ apply(obj: T, rv: string): void;
21
+ /** @internal Apply a delete; emits delete. */
22
+ remove(id: string, rv: string): void;
23
+ /** @internal A relist: apply every item, then delete what was not listed. */
24
+ replace(items: {
25
+ rv: string;
26
+ object: T;
27
+ }[], listRv: string): void;
28
+ }
29
+ export type InformerFactoryOptions = {
30
+ resources: WatchResource[];
31
+ logger?: {
32
+ warn: (o: object, m?: string) => void;
33
+ info?: (o: object, m?: string) => void;
34
+ };
35
+ /** Backoff after a failed list or watch: first and max delay. */
36
+ backoffMs?: [number, number];
37
+ };
38
+ export declare class InformerFactory {
39
+ private informers;
40
+ private abort?;
41
+ private loop?;
42
+ private synced;
43
+ private syncWaiters;
44
+ private rv?;
45
+ private stopped;
46
+ readonly stats: {
47
+ relists: number;
48
+ reconnects: number;
49
+ events: number;
50
+ lastEventAt: number;
51
+ };
52
+ private client;
53
+ private options;
54
+ constructor(client: WatchClient, options: InformerFactoryOptions);
55
+ informer<T extends {
56
+ id: string;
57
+ } = any>(resource: WatchResource): SharedInformer<T>;
58
+ hasSynced(): boolean;
59
+ waitForSync(): Promise<void>;
60
+ start(): void;
61
+ stop(): Promise<void>;
62
+ private relist;
63
+ private handle;
64
+ private run;
65
+ }
66
+ export {};
67
+ //# sourceMappingURL=InformerFactory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"InformerFactory.d.ts","sourceRoot":"","sources":["../../src/informers/InformerFactory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,KAAK,OAAO,EAAE,MAAM,cAAc,CAAA;AAEpD,OAAO,EAEL,KAAK,WAAW,EAEhB,KAAK,aAAa,EACnB,MAAM,kBAAkB,CAAA;AAkBzB,MAAM,MAAM,aAAa,GAAG,KAAK,GAAG,QAAQ,GAAG,QAAQ,CAAA;AACvD,KAAK,OAAO,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,KAAK,IAAI,CAAA;AAE3C,qBAAa,cAAc,CAAC,CAAC,SAAS;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE;IAClD,QAAQ,CAAC,KAAK,aAAmB;IACjC,OAAO,CAAC,QAAQ,CAIf;IAED,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAA;gBAEpB,QAAQ,EAAE,aAAa;IAInC,EAAE,CAAC,KAAK,EAAE,aAAa,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,GAAG,IAAI;IAKnD,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,OAAO,CAAC,CAAC,CAAC,GAAG,IAAI;IAK5C,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,CAAC,GAAG,SAAS;IAI9B,IAAI,IAAI,CAAC,EAAE;IAIX,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,CAAC,EAAE;IAIvC,gBAAgB;IAChB,IAAI,CAAC,KAAK,EAAE,aAAa,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC;IAW1C,mDAAmD;IACnD,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,EAAE,MAAM;IAKxB,8CAA8C;IAC9C,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM;IAK7B,6EAA6E;IAC7E,OAAO,CAAC,KAAK,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,CAAC,CAAA;KAAE,EAAE,EAAE,MAAM,EAAE,MAAM;CAQ3D;AAED,MAAM,MAAM,sBAAsB,GAAG;IACnC,SAAS,EAAE,aAAa,EAAE,CAAA;IAC1B,MAAM,CAAC,EAAE;QAAE,IAAI,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;QAAC,IAAI,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,CAAA;KAAE,CAAA;IAC1F,iEAAiE;IACjE,SAAS,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAC7B,CAAA;AAED,qBAAa,eAAe;IAC1B,OAAO,CAAC,SAAS,CAAgD;IACjE,OAAO,CAAC,KAAK,CAAC,CAAiB;IAC/B,OAAO,CAAC,IAAI,CAAC,CAAe;IAC5B,OAAO,CAAC,MAAM,CAAQ;IACtB,OAAO,CAAC,WAAW,CAAqB;IACxC,OAAO,CAAC,EAAE,CAAC,CAAQ;IACnB,OAAO,CAAC,OAAO,CAAO;IACtB,QAAQ,CAAC,KAAK;;;;;MAA2D;IAEzE,OAAO,CAAC,MAAM,CAAa;IAC3B,OAAO,CAAC,OAAO,CAAwB;gBAE3B,MAAM,EAAE,WAAW,EAAE,OAAO,EAAE,sBAAsB;IAMhE,QAAQ,CAAC,CAAC,SAAS;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,GAAG,GAAG,EAAE,QAAQ,EAAE,aAAa,GAAG,cAAc,CAAC,CAAC,CAAC;IAMpF,SAAS,IAAI,OAAO;IAIpB,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC;IAK5B,KAAK,IAAI,IAAI;IAOP,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;YAMb,MAAM;IAiBpB,OAAO,CAAC,MAAM;YAoBA,GAAG;CAoClB"}
@@ -0,0 +1,184 @@
1
+ import { Indexer } from "./Indexer.js";
2
+ import { compareRv, minRv } from "./rv.js";
3
+ import { WatchExpiredError, } from "./WatchClient.js";
4
+ export class SharedInformer {
5
+ store = new Indexer();
6
+ handlers = {
7
+ add: [],
8
+ update: [],
9
+ delete: [],
10
+ };
11
+ resource;
12
+ constructor(resource) {
13
+ this.resource = resource;
14
+ }
15
+ on(event, handler) {
16
+ this.handlers[event].push(handler);
17
+ return this;
18
+ }
19
+ addIndex(name, fn) {
20
+ this.store.addIndex(name, fn);
21
+ return this;
22
+ }
23
+ get(id) {
24
+ return this.store.get(id);
25
+ }
26
+ list() {
27
+ return this.store.list();
28
+ }
29
+ byIndex(name, key) {
30
+ return this.store.byIndex(name, key);
31
+ }
32
+ /** @internal */
33
+ emit(event, obj, old) {
34
+ for (const h of this.handlers[event]) {
35
+ try {
36
+ h(obj, old);
37
+ }
38
+ catch {
39
+ // A handler's failure is the handler's: the store and the other
40
+ // handlers carry on.
41
+ }
42
+ }
43
+ }
44
+ /** @internal Apply an upsert; emits add/update. */
45
+ apply(obj, rv) {
46
+ const { applied, old } = this.store.upsert(obj, rv);
47
+ if (applied)
48
+ this.emit(old ? "update" : "add", obj, old);
49
+ }
50
+ /** @internal Apply a delete; emits delete. */
51
+ remove(id, rv) {
52
+ const gone = this.store.delete(id, rv);
53
+ if (gone)
54
+ this.emit("delete", gone);
55
+ }
56
+ /** @internal A relist: apply every item, then delete what was not listed. */
57
+ replace(items, listRv) {
58
+ const seen = new Set();
59
+ for (const { rv, object } of items) {
60
+ seen.add(object.id);
61
+ this.apply(object, rv);
62
+ }
63
+ for (const id of this.store.ids())
64
+ if (!seen.has(id))
65
+ this.remove(id, listRv);
66
+ }
67
+ }
68
+ export class InformerFactory {
69
+ informers = new Map();
70
+ abort;
71
+ loop;
72
+ synced = false;
73
+ syncWaiters = [];
74
+ rv;
75
+ stopped = true;
76
+ stats = { relists: 0, reconnects: 0, events: 0, lastEventAt: 0 };
77
+ client;
78
+ options;
79
+ constructor(client, options) {
80
+ this.client = client;
81
+ this.options = options;
82
+ for (const r of options.resources)
83
+ this.informers.set(r, new SharedInformer(r));
84
+ }
85
+ informer(resource) {
86
+ const inf = this.informers.get(resource);
87
+ if (!inf)
88
+ throw new Error(`informer for "${resource}" not in this factory`);
89
+ return inf;
90
+ }
91
+ hasSynced() {
92
+ return this.synced;
93
+ }
94
+ waitForSync() {
95
+ if (this.synced)
96
+ return Promise.resolve();
97
+ return new Promise((r) => this.syncWaiters.push(r));
98
+ }
99
+ start() {
100
+ if (!this.stopped)
101
+ return;
102
+ this.stopped = false;
103
+ this.abort = new AbortController();
104
+ this.loop = this.run();
105
+ }
106
+ async stop() {
107
+ this.stopped = true;
108
+ this.abort?.abort();
109
+ await this.loop?.catch(() => { });
110
+ }
111
+ async relist() {
112
+ const rvs = [];
113
+ for (const [resource, inf] of this.informers) {
114
+ const { rv, items } = await this.client.list(resource);
115
+ inf.replace(items, rv);
116
+ rvs.push(rv);
117
+ }
118
+ // Watch from the OLDEST list rv: events already reflected in a newer
119
+ // list are dropped by the per-object rv.
120
+ this.rv = minRv(rvs);
121
+ this.stats.relists++;
122
+ if (!this.synced) {
123
+ this.synced = true;
124
+ for (const w of this.syncWaiters.splice(0))
125
+ w();
126
+ }
127
+ }
128
+ handle(ev) {
129
+ this.stats.events++;
130
+ this.stats.lastEventAt = Date.now();
131
+ switch (ev.type) {
132
+ case "BOOKMARK":
133
+ break;
134
+ case "MODIFIED":
135
+ this.informers.get(ev.resource)?.apply(ev.object, ev.objectRv);
136
+ break;
137
+ case "DELETED":
138
+ this.informers.get(ev.resource)?.remove(ev.object.id, ev.rv);
139
+ break;
140
+ case "ERROR":
141
+ return ev.code === "watch_expired" ? "relist" : "ok";
142
+ }
143
+ // Resume point: the stream position, which only moves forward.
144
+ if ("rv" in ev && ev.rv && (!this.rv || compareRv(ev.rv, this.rv) > 0))
145
+ this.rv = ev.rv;
146
+ return "ok";
147
+ }
148
+ async run() {
149
+ const [first, max] = this.options.backoffMs ?? [500, 30_000];
150
+ let backoff = first;
151
+ let needList = true;
152
+ while (!this.stopped) {
153
+ try {
154
+ if (needList) {
155
+ await this.relist();
156
+ needList = false;
157
+ }
158
+ let relist = false;
159
+ for await (const ev of this.client.watch(this.options.resources, this.rv, this.abort.signal)) {
160
+ if (this.handle(ev) === "relist") {
161
+ relist = true;
162
+ break;
163
+ }
164
+ backoff = first;
165
+ }
166
+ if (relist)
167
+ needList = true;
168
+ else
169
+ this.stats.reconnects++;
170
+ }
171
+ catch (err) {
172
+ if (this.stopped)
173
+ break;
174
+ if (err instanceof WatchExpiredError) {
175
+ needList = true;
176
+ continue;
177
+ }
178
+ this.options.logger?.warn({ err, backoff }, "informers: list/watch failed, retrying");
179
+ await new Promise((r) => setTimeout(r, backoff));
180
+ backoff = Math.min(backoff * 2, max);
181
+ }
182
+ }
183
+ }
184
+ }
@@ -0,0 +1,38 @@
1
+ export type WatchResource = "app" | "deployment" | "domain" | "project";
2
+ export type ListResult<T = any> = {
3
+ rv: string;
4
+ items: {
5
+ rv: string;
6
+ object: T;
7
+ }[];
8
+ };
9
+ export type WatchEvent<T = any> = {
10
+ type: "MODIFIED";
11
+ resource: WatchResource;
12
+ rv: string;
13
+ objectRv: string;
14
+ object: T;
15
+ } | {
16
+ type: "DELETED";
17
+ resource: WatchResource;
18
+ rv: string;
19
+ object: {
20
+ id: string;
21
+ };
22
+ } | {
23
+ type: "BOOKMARK";
24
+ rv: string;
25
+ } | {
26
+ type: "ERROR";
27
+ code: string;
28
+ message?: string;
29
+ };
30
+ /** Thrown by `watch` when the rv cannot be resumed (HTTP 410): relist. */
31
+ export declare class WatchExpiredError extends Error {
32
+ constructor(message?: string);
33
+ }
34
+ export interface WatchClient {
35
+ list(resource: WatchResource): Promise<ListResult>;
36
+ watch(resources: WatchResource[], rv: string, signal: AbortSignal): AsyncIterable<WatchEvent>;
37
+ }
38
+ //# sourceMappingURL=WatchClient.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WatchClient.d.ts","sourceRoot":"","sources":["../../src/informers/WatchClient.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,aAAa,GAAG,KAAK,GAAG,YAAY,GAAG,QAAQ,GAAG,SAAS,CAAA;AAEvE,MAAM,MAAM,UAAU,CAAC,CAAC,GAAG,GAAG,IAAI;IAChC,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,CAAC,CAAA;KAAE,EAAE,CAAA;CACnC,CAAA;AAED,MAAM,MAAM,UAAU,CAAC,CAAC,GAAG,GAAG,IAC1B;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,QAAQ,EAAE,aAAa,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,CAAC,CAAA;CAAE,GACtF;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,QAAQ,EAAE,aAAa,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,GAChF;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GAChC;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,CAAA;AAErD,0EAA0E;AAC1E,qBAAa,iBAAkB,SAAQ,KAAK;gBAC9B,OAAO,SAA0B;CAI9C;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,CAAC,QAAQ,EAAE,aAAa,GAAG,OAAO,CAAC,UAAU,CAAC,CAAA;IAClD,KAAK,CACH,SAAS,EAAE,aAAa,EAAE,EAC1B,EAAE,EAAE,MAAM,EACV,MAAM,EAAE,WAAW,GAClB,aAAa,CAAC,UAAU,CAAC,CAAA;CAC7B"}
@@ -0,0 +1,9 @@
1
+ // The transport under the informers: list a resource, watch from an rv
2
+ // (arch/api/informers.md § 3). An interface so tests can play a server.
3
+ /** Thrown by `watch` when the rv cannot be resumed (HTTP 410): relist. */
4
+ export class WatchExpiredError extends Error {
5
+ constructor(message = "watch expired: relist") {
6
+ super(message);
7
+ this.name = "WatchExpiredError";
8
+ }
9
+ }
@@ -0,0 +1,4 @@
1
+ import type { Fetcher } from "@faable/sdk-base";
2
+ import { type WatchClient } from "./WatchClient.js";
3
+ export declare const deployApiWatchClient: (fetcher: Fetcher) => WatchClient;
4
+ //# sourceMappingURL=deployApiWatchClient.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deployApiWatchClient.d.ts","sourceRoot":"","sources":["../../src/informers/deployApiWatchClient.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAA;AAE/C,OAAO,EAGL,KAAK,WAAW,EAGjB,MAAM,kBAAkB,CAAA;AAWzB,eAAO,MAAM,oBAAoB,YAAa,OAAO,KAAG,WA2BtD,CAAA"}
@@ -0,0 +1,36 @@
1
+ import { ndjson } from "./ndjson.js";
2
+ import { WatchExpiredError, } from "./WatchClient.js";
3
+ // The WatchClient over the deploy API's fetcher (auth, identity headers and
4
+ // baseURL included): GET /watch/list and the NDJSON stream of GET /watch.
5
+ //
6
+ // ⚠️ The client must not have `etagCache` on: the ETag path would treat the
7
+ // stream as a cacheable body. Build a separate DeployApi for informers.
8
+ const isGone = (err) => err?.response?.status === 410 || err?.status === 410 || err?.statusCode === 410;
9
+ export const deployApiWatchClient = (fetcher) => ({
10
+ list: async (resource) => fetcher.request({
11
+ method: "GET",
12
+ url: "/watch/list",
13
+ params: { resource },
14
+ }),
15
+ watch: (resources, rv, signal) => ({
16
+ async *[Symbol.asyncIterator]() {
17
+ let stream;
18
+ try {
19
+ stream = await fetcher.request({
20
+ method: "GET",
21
+ url: "/watch",
22
+ params: { resources: resources.join(","), rv },
23
+ responseType: "stream",
24
+ timeout: 0,
25
+ signal,
26
+ });
27
+ }
28
+ catch (err) {
29
+ if (isGone(err))
30
+ throw new WatchExpiredError();
31
+ throw err;
32
+ }
33
+ yield* ndjson(stream);
34
+ },
35
+ }),
36
+ });
@@ -0,0 +1,6 @@
1
+ export { Indexer, type IndexFn } from "./Indexer.js";
2
+ export { InformerFactory, SharedInformer, type InformerEvent, type InformerFactoryOptions, } from "./InformerFactory.js";
3
+ export { compareRv, minRv } from "./rv.js";
4
+ export { WatchExpiredError, type ListResult, type WatchClient, type WatchEvent, type WatchResource, } from "./WatchClient.js";
5
+ export { deployApiWatchClient } from "./deployApiWatchClient.js";
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/informers/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,KAAK,OAAO,EAAE,MAAM,cAAc,CAAA;AACpD,OAAO,EACL,eAAe,EACf,cAAc,EACd,KAAK,aAAa,EAClB,KAAK,sBAAsB,GAC5B,MAAM,sBAAsB,CAAA;AAC7B,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,SAAS,CAAA;AAC1C,OAAO,EACL,iBAAiB,EACjB,KAAK,UAAU,EACf,KAAK,WAAW,EAChB,KAAK,UAAU,EACf,KAAK,aAAa,GACnB,MAAM,kBAAkB,CAAA;AACzB,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAA"}
@@ -0,0 +1,5 @@
1
+ export { Indexer } from "./Indexer.js";
2
+ export { InformerFactory, SharedInformer, } from "./InformerFactory.js";
3
+ export { compareRv, minRv } from "./rv.js";
4
+ export { WatchExpiredError, } from "./WatchClient.js";
5
+ export { deployApiWatchClient } from "./deployApiWatchClient.js";
@@ -0,0 +1,2 @@
1
+ export declare function ndjson(chunks: AsyncIterable<Uint8Array | string>): AsyncGenerator<any>;
2
+ //# sourceMappingURL=ndjson.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ndjson.d.ts","sourceRoot":"","sources":["../../src/informers/ndjson.ts"],"names":[],"mappings":"AAEA,wBAAuB,MAAM,CAC3B,MAAM,EAAE,aAAa,CAAC,UAAU,GAAG,MAAM,CAAC,GACzC,cAAc,CAAC,GAAG,CAAC,CAcrB"}
@@ -0,0 +1,19 @@
1
+ // Lines of an NDJSON byte stream (Node Readable or any async iterable of
2
+ // chunks), parsed. A partial last line is kept until it completes.
3
+ export async function* ndjson(chunks) {
4
+ const decoder = new TextDecoder();
5
+ let buf = "";
6
+ for await (const chunk of chunks) {
7
+ buf += typeof chunk === "string" ? chunk : decoder.decode(chunk, { stream: true });
8
+ let i;
9
+ while ((i = buf.indexOf("\n")) >= 0) {
10
+ const line = buf.slice(0, i).trim();
11
+ buf = buf.slice(i + 1);
12
+ if (line)
13
+ yield JSON.parse(line);
14
+ }
15
+ }
16
+ const last = buf.trim();
17
+ if (last)
18
+ yield JSON.parse(last);
19
+ }
@@ -0,0 +1,4 @@
1
+ /** <0, 0, >0 like a comparator; an unparsable rv sorts first. */
2
+ export declare const compareRv: (a: string, b: string) => number;
3
+ export declare const minRv: (rvs: string[]) => string;
4
+ //# sourceMappingURL=rv.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rv.d.ts","sourceRoot":"","sources":["../../src/informers/rv.ts"],"names":[],"mappings":"AASA,iEAAiE;AACjE,eAAO,MAAM,SAAS,MAAO,MAAM,KAAK,MAAM,KAAG,MAKhD,CAAA;AAED,eAAO,MAAM,KAAK,QAAS,MAAM,EAAE,KAAG,MACe,CAAA"}
@@ -0,0 +1,16 @@
1
+ // A resourceVersion from the watch API: `<generation>.<streamMs>-<seq>`
2
+ // (arch/api/informers.md § 1). Ordered by generation, then stream position.
3
+ // Opaque to callers; only compared here.
4
+ const parse = (rv) => {
5
+ const m = /^(\d+)\.(\d+)-(\d+)$/.exec(rv);
6
+ return m ? [Number(m[1]), Number(m[2]), Number(m[3])] : null;
7
+ };
8
+ /** <0, 0, >0 like a comparator; an unparsable rv sorts first. */
9
+ export const compareRv = (a, b) => {
10
+ const pa = parse(a);
11
+ const pb = parse(b);
12
+ if (!pa || !pb)
13
+ return pa ? 1 : pb ? -1 : 0;
14
+ return pa[0] - pb[0] || pa[1] - pb[1] || pa[2] - pb[2];
15
+ };
16
+ export const minRv = (rvs) => rvs.reduce((m, r) => (compareRv(r, m) < 0 ? r : m));
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@faable/deploy-sdk",
3
3
  "description": "Codebase for SDK building",
4
- "version": "2.17.1",
4
+ "version": "2.18.0",
5
5
  "author": "Marc Pomar <marc@faable.com>",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",