@mauroandre/weave-sdk 0.0.1

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.
package/dist/index.js ADDED
@@ -0,0 +1,189 @@
1
+ import {
2
+ Column,
3
+ Owned,
4
+ Reference,
5
+ WeaveAuthError,
6
+ WeaveError,
7
+ WeaveNotFoundError,
8
+ WeaveScopeError,
9
+ WeaveValidationError,
10
+ array,
11
+ bool,
12
+ bpchar,
13
+ bytea,
14
+ date,
15
+ defineConfig,
16
+ defineEntity,
17
+ defineScope,
18
+ errorFor,
19
+ float4,
20
+ float8,
21
+ genProject,
22
+ int2,
23
+ int4,
24
+ int8,
25
+ interval,
26
+ irToSource,
27
+ json,
28
+ jsonb,
29
+ numeric,
30
+ owned,
31
+ pullEntities,
32
+ pushEntities,
33
+ pushScopes,
34
+ reference,
35
+ scopeToSource,
36
+ text,
37
+ time,
38
+ timestamp,
39
+ timestamptz,
40
+ uuid,
41
+ varchar
42
+ } from "./chunk-PWTED7ZO.js";
43
+
44
+ // src/serialize.ts
45
+ function reviveShape(shape, value) {
46
+ if (!value || typeof value !== "object") return value;
47
+ const obj = value;
48
+ for (const [key, node] of Object.entries(shape)) {
49
+ const v = obj[key];
50
+ if (node instanceof Column) {
51
+ if (node.config.pgType.tsLabel === "Date" && typeof v === "string") {
52
+ obj[key] = new Date(v);
53
+ }
54
+ } else if (node instanceof Owned) {
55
+ if (Array.isArray(v)) v.forEach((x) => reviveShape(node.shape, x));
56
+ else if (v) reviveShape(node.shape, v);
57
+ } else if (node instanceof Reference) {
58
+ if (node.cardinality === "many" && Array.isArray(v)) {
59
+ v.forEach((x) => reviveShape(node.target.columns, x));
60
+ } else if (v && typeof v === "object") {
61
+ reviveShape(node.target.columns, v);
62
+ }
63
+ }
64
+ }
65
+ if (typeof obj["createdAt"] === "string") obj["createdAt"] = new Date(obj["createdAt"]);
66
+ if (typeof obj["updatedAt"] === "string") obj["updatedAt"] = new Date(obj["updatedAt"]);
67
+ return obj;
68
+ }
69
+
70
+ // src/client.ts
71
+ function createClient(options) {
72
+ const transport = options.fetch ?? ((req) => globalThis.fetch(req));
73
+ const base = options.url.replace(/\/$/, "");
74
+ async function request(method, path, opts = {}) {
75
+ let url = `${base}${path}`;
76
+ if (opts.query) {
77
+ const qs = Object.entries(opts.query).filter((e) => e[1] !== void 0).map(([k, v]) => `${k}=${encodeURIComponent(v)}`).join("&");
78
+ if (qs) url += `?${qs}`;
79
+ }
80
+ const headers = { "x-api-key": options.key };
81
+ if (options.scope) {
82
+ headers["x-weave-scope"] = options.scope;
83
+ if (options.params) headers["x-weave-params"] = JSON.stringify(options.params);
84
+ }
85
+ const init = { method, headers };
86
+ if (opts.body !== void 0) {
87
+ headers["content-type"] = "application/json";
88
+ init.body = JSON.stringify(opts.body);
89
+ }
90
+ const res = await transport(new Request(url, init));
91
+ if (res.status === 404 && opts.allowNull) return null;
92
+ const json2 = res.status === 204 ? null : await res.json().catch(() => null);
93
+ if (!res.ok) {
94
+ const m = json2 && typeof json2 === "object" && "error" in json2 ? String(json2.error) : `Weave request failed (${res.status}).`;
95
+ throw errorFor(res.status, m);
96
+ }
97
+ return json2;
98
+ }
99
+ const queryFrom = (o) => ({
100
+ expand: JSON.stringify(o.expand ?? {}),
101
+ where: JSON.stringify(o.where ?? {}),
102
+ orderBy: o.orderBy !== void 0 ? JSON.stringify(o.orderBy) : void 0,
103
+ page: o.page !== void 0 ? String(o.page) : void 0,
104
+ perPage: o.perPage !== void 0 ? String(o.perPage) : void 0
105
+ });
106
+ const client = {};
107
+ for (const [key, entity] of Object.entries(options.entities)) {
108
+ const shape = entity.columns;
109
+ const path = `/api/${entity.name}`;
110
+ const revive = (o) => reviveShape(shape, o);
111
+ const list = async (o) => await request("GET", path, { query: queryFrom(o) });
112
+ client[key] = {
113
+ async create(input) {
114
+ return revive(await request("POST", path, { body: input }));
115
+ },
116
+ async get(id, o = {}) {
117
+ const r = await request("GET", `${path}/${encodeURIComponent(id)}`, {
118
+ query: { expand: JSON.stringify(o.expand ?? {}) },
119
+ allowNull: true
120
+ });
121
+ return r === null ? null : revive(r);
122
+ },
123
+ async find(o = {}) {
124
+ return (await list(o)).docs?.map(revive) ?? [];
125
+ },
126
+ async findOne(o = {}) {
127
+ const d = (await list({ ...o, perPage: 1 })).docs?.[0];
128
+ return d === void 0 ? null : revive(d);
129
+ },
130
+ async paginate(o = {}) {
131
+ const page = await list(o);
132
+ return {
133
+ docs: page.docs?.map(revive) ?? [],
134
+ docsQuantity: page.docsQuantity,
135
+ pageQuantity: page.pageQuantity,
136
+ currentPage: page.currentPage
137
+ };
138
+ },
139
+ async update(id, patch) {
140
+ return revive(await request("PATCH", `${path}/${encodeURIComponent(id)}`, { body: patch }));
141
+ },
142
+ async delete(id) {
143
+ await request("DELETE", `${path}/${encodeURIComponent(id)}`);
144
+ }
145
+ };
146
+ }
147
+ client["as"] = (scope, params) => createClient({ ...options, scope, ...params ? { params } : {} });
148
+ return client;
149
+ }
150
+ export {
151
+ WeaveAuthError,
152
+ WeaveError,
153
+ WeaveNotFoundError,
154
+ WeaveScopeError,
155
+ WeaveValidationError,
156
+ array,
157
+ bool,
158
+ bpchar,
159
+ bytea,
160
+ createClient,
161
+ date,
162
+ defineConfig,
163
+ defineEntity,
164
+ defineScope,
165
+ float4,
166
+ float8,
167
+ genProject,
168
+ int2,
169
+ int4,
170
+ int8,
171
+ interval,
172
+ irToSource,
173
+ json,
174
+ jsonb,
175
+ numeric,
176
+ owned,
177
+ pullEntities,
178
+ pushEntities,
179
+ pushScopes,
180
+ reference,
181
+ scopeToSource,
182
+ text,
183
+ time,
184
+ timestamp,
185
+ timestamptz,
186
+ uuid,
187
+ varchar
188
+ };
189
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/serialize.ts","../src/client.ts"],"sourcesContent":["import { Column, Owned, Reference, type ShapeRecord } from \"@mauroandre/weave-core\";\n\n// json → obj: a API devolve datas como string ISO. Aqui revivemos pra `Date`,\n// dirigidos pela FORMA da entidade (mesma ideia do read do engine): colunas de\n// tipo Date, owned aninhado (recursivo), e references expandidas (um nível, via\n// a forma do alvo). Os timestamps managed (createdAt/updatedAt) sempre viram Date.\n//\n// (Write não precisa de tratamento: JSON.stringify já serializa Date → ISO, e a\n// reference vai por `<campo>Id`, que é string.)\nexport function reviveShape(shape: ShapeRecord, value: unknown): unknown {\n if (!value || typeof value !== \"object\") return value;\n const obj = value as Record<string, unknown>;\n\n for (const [key, node] of Object.entries(shape)) {\n const v = obj[key];\n if (node instanceof Column) {\n if (node.config.pgType.tsLabel === \"Date\" && typeof v === \"string\") {\n obj[key] = new Date(v);\n }\n } else if (node instanceof Owned) {\n if (Array.isArray(v)) v.forEach((x) => reviveShape(node.shape, x));\n else if (v) reviveShape(node.shape, v);\n } else if (node instanceof Reference) {\n // Só age se a reference veio EXPANDIDA (objeto/array); id-form é string, ignora.\n if (node.cardinality === \"many\" && Array.isArray(v)) {\n v.forEach((x) => reviveShape(node.target.columns, x));\n } else if (v && typeof v === \"object\") {\n reviveShape(node.target.columns, v);\n }\n }\n }\n\n if (typeof obj[\"createdAt\"] === \"string\") obj[\"createdAt\"] = new Date(obj[\"createdAt\"]);\n if (typeof obj[\"updatedAt\"] === \"string\") obj[\"updatedAt\"] = new Date(obj[\"updatedAt\"]);\n return obj;\n}\n","import type {\n Entity,\n ShapeRecord,\n InferEntity,\n InferInsert,\n InferRead,\n ExpandInput,\n WhereInput,\n OrderByInput,\n} from \"@mauroandre/weave-core\";\nimport { reviveShape } from \"./serialize.js\";\nimport { errorFor } from \"./errors.js\";\n\n/** Função de transporte: recebe um `Request`, devolve um `Response` (WHATWG fetch).\n * Aceita retorno síncrono ou Promise (o `app.hono.fetch` pode ser síncrono). */\nexport type FetchLike = (request: Request) => Response | Promise<Response>;\n\nexport interface ClientOptions<S> {\n /** Base URL do Weave (ex.: `https://weave.minha-loja.com`). */\n url: string;\n /** API key (`x-api-key`). */\n key: string;\n /** O entities-as-code: `{ nome: defineEntity(...) }`. */\n entities: S;\n /** Transporte. Default: `globalThis.fetch`. Nos testes: `app.hono.fetch`. */\n fetch?: FetchLike;\n /** @internal — scope ativo (`x-weave-scope`), definido via `weave.as(...)`. */\n scope?: string;\n /** @internal — params do scope (`x-weave-params`). */\n params?: Record<string, unknown>;\n}\n\n/**\n * Opções de leitura, todas **tipadas pela entidade**: `where` (`WhereInput`),\n * `orderBy` (`OrderByInput`), e `expand` (`ExpandInput`) que ainda **dirige o tipo\n * do retorno** (`InferRead<E, X>`). Mesmo idioma do engine e da GUI.\n */\nexport interface FindArgs<E extends Entity<string, ShapeRecord>, X> {\n where?: WhereInput<E>;\n orderBy?: OrderByInput<E>;\n expand?: X & ExpandInput<E>;\n page?: number;\n perPage?: number;\n}\n\nexport interface PageResult<T> {\n docs: T[];\n docsQuantity: number;\n pageQuantity: number;\n currentPage: number;\n}\n\n/**\n * Client tipado de UMA entidade. Os reads se **auto-tipam pelo `expand`** que você\n * passa (`const X`), então `find({ expand: { category: true } })` já devolve o\n * objeto com `category` expandido e tipado — sem escrever nenhum `Infer`.\n */\nexport interface EntityClient<E extends Entity<string, ShapeRecord>> {\n create(input: InferInsert<E>): Promise<InferEntity<E>>;\n get<const X = {}>(\n id: string,\n opts?: { expand?: X & ExpandInput<E> },\n ): Promise<InferRead<E, X> | null>;\n find<const X = {}>(opts?: FindArgs<E, X>): Promise<InferRead<E, X>[]>;\n findOne<const X = {}>(opts?: FindArgs<E, X>): Promise<InferRead<E, X> | null>;\n paginate<const X = {}>(opts?: FindArgs<E, X>): Promise<PageResult<InferRead<E, X>>>;\n update(id: string, patch: Partial<InferInsert<E>>): Promise<InferEntity<E>>;\n delete(id: string): Promise<void>;\n}\n\n/** O client completo: uma propriedade por entidade do entities + `as` (scope). */\nexport type WeaveClient<S extends Record<string, Entity<string, ShapeRecord>>> = {\n [K in keyof S]: EntityClient<S[K]>;\n} & {\n /** Client escopado: toda requisição leva `x-weave-scope` + `x-weave-params`. */\n as(scope: string, params?: Record<string, unknown>): WeaveClient<S>;\n};\n\ninterface ListResponse {\n docs?: Record<string, unknown>[];\n docsQuantity: number;\n pageQuantity: number;\n currentPage: number;\n}\n\n// Forma frouxa das opções, usada SÓ na implementação (a interface dá os tipos).\ntype AnyArgs = { where?: unknown; orderBy?: unknown; expand?: unknown; page?: number; perPage?: number };\n\n/**\n * Cria o client tipado a partir do entities-as-code. Casca fina sobre a API HTTP do\n * Weave: monta o request, manda o `x-api-key`, revive `obj↔json` (datas) pela forma\n * da entidade, e serializa o `expand` no param. O `fetch` é injetável — em teste,\n * `app.hono.fetch`.\n */\nexport function createClient<S extends Record<string, Entity<string, ShapeRecord>>>(\n options: ClientOptions<S>,\n): WeaveClient<S> {\n const transport: FetchLike = options.fetch ?? ((req) => globalThis.fetch(req));\n const base = options.url.replace(/\\/$/, \"\");\n\n async function request(\n method: string,\n path: string,\n opts: { query?: Record<string, string | undefined>; body?: unknown; allowNull?: boolean } = {},\n ): Promise<unknown> {\n let url = `${base}${path}`;\n if (opts.query) {\n const qs = Object.entries(opts.query)\n .filter((e): e is [string, string] => e[1] !== undefined)\n .map(([k, v]) => `${k}=${encodeURIComponent(v)}`)\n .join(\"&\");\n if (qs) url += `?${qs}`;\n }\n\n const headers: Record<string, string> = { \"x-api-key\": options.key };\n if (options.scope) {\n headers[\"x-weave-scope\"] = options.scope;\n if (options.params) headers[\"x-weave-params\"] = JSON.stringify(options.params);\n }\n const init: RequestInit = { method, headers };\n if (opts.body !== undefined) {\n headers[\"content-type\"] = \"application/json\";\n init.body = JSON.stringify(opts.body);\n }\n\n const res = await transport(new Request(url, init));\n if (res.status === 404 && opts.allowNull) return null;\n const json = res.status === 204 ? null : await res.json().catch(() => null);\n if (!res.ok) {\n const m =\n json && typeof json === \"object\" && \"error\" in json\n ? String((json as { error: unknown }).error)\n : `Weave request failed (${res.status}).`;\n throw errorFor(res.status, m);\n }\n return json;\n }\n\n // `where` e `expand` vão SEMPRE (default `{}`): `where={}` força o caminho\n // WhereInput na API e o `expand={}` deixa o tipo de retorno determinístico.\n const queryFrom = (o: AnyArgs): Record<string, string | undefined> => ({\n expand: JSON.stringify(o.expand ?? {}),\n where: JSON.stringify(o.where ?? {}),\n orderBy: o.orderBy !== undefined ? JSON.stringify(o.orderBy) : undefined,\n page: o.page !== undefined ? String(o.page) : undefined,\n perPage: o.perPage !== undefined ? String(o.perPage) : undefined,\n });\n\n const client: Record<string, unknown> = {};\n\n for (const [key, entity] of Object.entries(options.entities)) {\n const shape = entity.columns;\n const path = `/api/${entity.name}`;\n const revive = (o: unknown) => reviveShape(shape, o);\n const list = async (o: AnyArgs): Promise<ListResponse> =>\n (await request(\"GET\", path, { query: queryFrom(o) })) as ListResponse;\n\n client[key] = {\n async create(input: unknown) {\n return revive(await request(\"POST\", path, { body: input }));\n },\n async get(id: string, o: { expand?: unknown } = {}) {\n const r = await request(\"GET\", `${path}/${encodeURIComponent(id)}`, {\n query: { expand: JSON.stringify(o.expand ?? {}) },\n allowNull: true,\n });\n return r === null ? null : revive(r);\n },\n async find(o: AnyArgs = {}) {\n return (await list(o)).docs?.map(revive) ?? [];\n },\n async findOne(o: AnyArgs = {}) {\n const d = (await list({ ...o, perPage: 1 })).docs?.[0];\n return d === undefined ? null : revive(d);\n },\n async paginate(o: AnyArgs = {}) {\n const page = await list(o);\n return {\n docs: page.docs?.map(revive) ?? [],\n docsQuantity: page.docsQuantity,\n pageQuantity: page.pageQuantity,\n currentPage: page.currentPage,\n };\n },\n async update(id: string, patch: unknown) {\n return revive(await request(\"PATCH\", `${path}/${encodeURIComponent(id)}`, { body: patch }));\n },\n async delete(id: string) {\n await request(\"DELETE\", `${path}/${encodeURIComponent(id)}`);\n },\n };\n }\n\n // `weave.as(scope, params)` → novo client com os headers de scope em toda req.\n client[\"as\"] = (scope: string, params?: Record<string, unknown>) =>\n createClient({ ...options, scope, ...(params ? { params } : {}) });\n\n return client as unknown as WeaveClient<S>;\n}\n"],"mappings":"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","names":["json"]}
@@ -0,0 +1,100 @@
1
+ import { Entity, ShapeRecord, InferInsert, InferEntity, ExpandInput, InferRead, WhereInput, OrderByInput } from '@mauroandre/weave-core';
2
+
3
+ /** Função de transporte: recebe um `Request`, devolve um `Response` (WHATWG fetch).
4
+ * Aceita retorno síncrono ou Promise (o `app.hono.fetch` pode ser síncrono). */
5
+ type FetchLike = (request: Request) => Response | Promise<Response>;
6
+ interface ClientOptions<S> {
7
+ /** Base URL do Weave (ex.: `https://weave.minha-loja.com`). */
8
+ url: string;
9
+ /** API key (`x-api-key`). */
10
+ key: string;
11
+ /** O entities-as-code: `{ nome: defineEntity(...) }`. */
12
+ entities: S;
13
+ /** Transporte. Default: `globalThis.fetch`. Nos testes: `app.hono.fetch`. */
14
+ fetch?: FetchLike;
15
+ /** @internal — scope ativo (`x-weave-scope`), definido via `weave.as(...)`. */
16
+ scope?: string;
17
+ /** @internal — params do scope (`x-weave-params`). */
18
+ params?: Record<string, unknown>;
19
+ }
20
+ /**
21
+ * Opções de leitura, todas **tipadas pela entidade**: `where` (`WhereInput`),
22
+ * `orderBy` (`OrderByInput`), e `expand` (`ExpandInput`) que ainda **dirige o tipo
23
+ * do retorno** (`InferRead<E, X>`). Mesmo idioma do engine e da GUI.
24
+ */
25
+ interface FindArgs<E extends Entity<string, ShapeRecord>, X> {
26
+ where?: WhereInput<E>;
27
+ orderBy?: OrderByInput<E>;
28
+ expand?: X & ExpandInput<E>;
29
+ page?: number;
30
+ perPage?: number;
31
+ }
32
+ interface PageResult<T> {
33
+ docs: T[];
34
+ docsQuantity: number;
35
+ pageQuantity: number;
36
+ currentPage: number;
37
+ }
38
+ /**
39
+ * Client tipado de UMA entidade. Os reads se **auto-tipam pelo `expand`** que você
40
+ * passa (`const X`), então `find({ expand: { category: true } })` já devolve o
41
+ * objeto com `category` expandido e tipado — sem escrever nenhum `Infer`.
42
+ */
43
+ interface EntityClient<E extends Entity<string, ShapeRecord>> {
44
+ create(input: InferInsert<E>): Promise<InferEntity<E>>;
45
+ get<const X = {}>(id: string, opts?: {
46
+ expand?: X & ExpandInput<E>;
47
+ }): Promise<InferRead<E, X> | null>;
48
+ find<const X = {}>(opts?: FindArgs<E, X>): Promise<InferRead<E, X>[]>;
49
+ findOne<const X = {}>(opts?: FindArgs<E, X>): Promise<InferRead<E, X> | null>;
50
+ paginate<const X = {}>(opts?: FindArgs<E, X>): Promise<PageResult<InferRead<E, X>>>;
51
+ update(id: string, patch: Partial<InferInsert<E>>): Promise<InferEntity<E>>;
52
+ delete(id: string): Promise<void>;
53
+ }
54
+ /** O client completo: uma propriedade por entidade do entities + `as` (scope). */
55
+ type WeaveClient<S extends Record<string, Entity<string, ShapeRecord>>> = {
56
+ [K in keyof S]: EntityClient<S[K]>;
57
+ } & {
58
+ /** Client escopado: toda requisição leva `x-weave-scope` + `x-weave-params`. */
59
+ as(scope: string, params?: Record<string, unknown>): WeaveClient<S>;
60
+ };
61
+ /**
62
+ * Cria o client tipado a partir do entities-as-code. Casca fina sobre a API HTTP do
63
+ * Weave: monta o request, manda o `x-api-key`, revive `obj↔json` (datas) pela forma
64
+ * da entidade, e serializa o `expand` no param. O `fetch` é injetável — em teste,
65
+ * `app.hono.fetch`.
66
+ */
67
+ declare function createClient<S extends Record<string, Entity<string, ShapeRecord>>>(options: ClientOptions<S>): WeaveClient<S>;
68
+
69
+ type Verb = "read" | "create" | "update" | "delete";
70
+ interface ScopeEntityRule {
71
+ verbs: Verb[];
72
+ /** Filtro de linhas (WhereInput, por NOME; valores podem ser `{ param: "x" }`). */
73
+ where?: Record<string, unknown>;
74
+ /** Projeção: caminhos por NOME (dot-path, ex.: `"items.secret"`). */
75
+ fields?: {
76
+ include?: string[];
77
+ exclude?: string[];
78
+ };
79
+ }
80
+ interface ScopeDef {
81
+ name: string;
82
+ entities: Record<string, ScopeEntityRule>;
83
+ }
84
+ /** Helper tipado pro scope-as-code (igual `defineEntity`). */
85
+ declare function defineScope(name: string, entities: Record<string, ScopeEntityRule>): ScopeDef;
86
+ interface PushScopesOptions {
87
+ url: string;
88
+ key: string;
89
+ fetch?: FetchLike;
90
+ }
91
+ /**
92
+ * Empurra scopes-as-code: converte cada regra (where + fields por NOME) pro formato
93
+ * por-id e grava via `PUT /admin/scopes/:name`. Busca os IRs das entidades pra
94
+ * resolver os ids (rename-proof no storage).
95
+ */
96
+ declare function pushScopes(scopes: Record<string, ScopeDef>, options: PushScopesOptions): Promise<{
97
+ pushed: string[];
98
+ }>;
99
+
100
+ export { type ClientOptions as C, type EntityClient as E, type FetchLike as F, type PageResult as P, type ScopeDef as S, type Verb as V, type WeaveClient as W, type FindArgs as a, type PushScopesOptions as b, type ScopeEntityRule as c, createClient as d, defineScope as e, pushScopes as p };
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "@mauroandre/weave-sdk",
3
+ "version": "0.0.1",
4
+ "description": "Weave SDK — typed object client over the Weave HTTP API. The glue: entities-as-code in, objects out, HTTP/JSON invisible.",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "author": "Mauro André",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/mauro-andre/weave.git",
11
+ "directory": "packages/sdk"
12
+ },
13
+ "homepage": "https://github.com/mauro-andre/weave#readme",
14
+ "bugs": "https://github.com/mauro-andre/weave/issues",
15
+ "bin": {
16
+ "weave": "dist/cli.js"
17
+ },
18
+ "main": "./dist/index.js",
19
+ "types": "./dist/index.d.ts",
20
+ "files": [
21
+ "dist"
22
+ ],
23
+ "exports": {
24
+ ".": {
25
+ "types": "./dist/index.d.ts",
26
+ "import": "./dist/index.js"
27
+ },
28
+ "./cli": {
29
+ "types": "./dist/cli.d.ts",
30
+ "import": "./dist/cli.js"
31
+ }
32
+ },
33
+ "scripts": {
34
+ "build": "tsup"
35
+ },
36
+ "publishConfig": {
37
+ "access": "public"
38
+ },
39
+ "keywords": [
40
+ "weave",
41
+ "postgres",
42
+ "orm",
43
+ "typed-client",
44
+ "sdk",
45
+ "typescript"
46
+ ]
47
+ }