@mauroandre/weave-sdk 0.0.2 → 0.0.3

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/README.md CHANGED
@@ -27,20 +27,21 @@ const weave = createClient({
27
27
  const cat = await weave.category.create({ name: "Books" });
28
28
  const p = await weave.product.create({ name: "Clean Code", price: 80, categoryId: cat.id });
29
29
 
30
- const found = await weave.product.find({
31
- where: { price: { gte: 50 }, category: { name: { ilike: "%book%" } } },
32
- orderBy: { price: "desc" },
33
- expand: { category: true },
34
- });
30
+ const found = await weave.product.findMany(
31
+ { price: { gte: 50 }, category: { name: { ilike: "%book%" } } },
32
+ { orderBy: { price: "desc" }, expand: { category: true } },
33
+ );
35
34
 
36
35
  found[0].price; // number — inferred
37
36
  found[0].createdAt; // Date — revived from JSON
38
37
  found[0].category.name; // string — typed & present, only because you expanded it
39
38
  ```
40
39
 
41
- CRUD per entity: `create` · `get` · `find` · `findOne` · `paginate` · `update` ·
42
- `delete`. The return type **self-types by your `expand`**, so you never write a
43
- result type by hand.
40
+ The verbs per entity: `create` · `findOne` · `findMany` · `paginate` ·
41
+ `updateOne` · `updateMany` · `deleteOne` · `deleteMany`. You target rows with a bare
42
+ **where** (`{ id: "123" }` is shorthand for `{ id: { eq: "123" } }`); `One` hits the
43
+ first match, `Many` operates in bulk and returns `{ count }`. The read return type
44
+ **self-types by your `expand`**, so you never write a result type by hand.
44
45
 
45
46
  ## One query language
46
47
 
@@ -48,13 +49,13 @@ result type by hand.
48
49
  click, the SDK call, and the stored access rule all speak it. A taste:
49
50
 
50
51
  ```ts
51
- await weave.order.find({
52
- where: {
53
- or: [{ status: { eq: "paid" } }, { total: { gte: 1000 } }],
52
+ await weave.order.findMany(
53
+ {
54
+ or: [{ status: "paid" }, { total: { gte: 1000 } }],
54
55
  items: { some: { product: { name: { ilike: "%pro%" } } } }, // any item matches
55
56
  },
56
- orderBy: { customer: { name: "asc" } }, // nested sort
57
- });
57
+ { orderBy: { customer: { name: "asc" } } }, // nested sort
58
+ );
58
59
  ```
59
60
 
60
61
  ## Entities as code
@@ -116,7 +117,7 @@ Act under a scope at request time:
116
117
 
117
118
  ```ts
118
119
  const tenant = weave.as("storefront", { tenantId: ctx.tenant });
119
- await tenant.product.find(); // server enforces the scope's rows + fields
120
+ await tenant.product.findMany(); // server enforces the scope's rows + fields
120
121
  ```
121
122
 
122
123
  ## License
package/dist/cli.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { Entity, ShapeRecord } from '@mauroandre/weave-core';
3
- import { S as ScopeDef, F as FetchLike } from './scope-DrcCzdf-.js';
3
+ import { S as ScopeDef, F as FetchLike } from './scope-S2VyyW6B.js';
4
4
 
5
5
  /** Importa um módulo por caminho absoluto. Injetável (a CLI usa um loader de TS). */
6
6
  type ModuleLoader = (absPath: string) => Promise<{
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { F as FetchLike } from './scope-DrcCzdf-.js';
2
- export { C as ClientOptions, E as EntityClient, a as FindArgs, P as PageResult, b as PushScopesOptions, S as ScopeDef, c as ScopeEntityRule, V as Verb, W as WeaveClient, d as createClient, e as defineScope, p as pushScopes } from './scope-DrcCzdf-.js';
1
+ import { F as FetchLike } from './scope-S2VyyW6B.js';
2
+ export { C as ClientOptions, E as EntityClient, P as PageOpts, a as PageResult, b as PushScopesOptions, R as ReadOpts, S as ScopeDef, c as ScopeEntityRule, V as Verb, W as WeaveClient, d as createClient, e as defineScope, p as pushScopes } from './scope-S2VyyW6B.js';
3
3
  import { Entity, ShapeRecord, InferEntity, InferInsert, EntityIR } from '@mauroandre/weave-core';
4
4
  export { Entity, InferEntity, InferInsert, InferRead, array, bool, bpchar, bytea, date, defineEntity, float4, float8, int2, int4, int8, interval, json, jsonb, numeric, owned, reference, text, time, timestamp, timestamptz, uuid, varchar } from '@mauroandre/weave-core';
5
5
 
package/dist/index.js CHANGED
@@ -96,39 +96,37 @@ function createClient(options) {
96
96
  }
97
97
  return json2;
98
98
  }
99
- const queryFrom = (o) => ({
99
+ const readQuery = (where, o) => ({
100
+ where: JSON.stringify(where ?? {}),
100
101
  expand: JSON.stringify(o.expand ?? {}),
101
- where: JSON.stringify(o.where ?? {}),
102
102
  orderBy: o.orderBy !== void 0 ? JSON.stringify(o.orderBy) : void 0,
103
103
  page: o.page !== void 0 ? String(o.page) : void 0,
104
104
  perPage: o.perPage !== void 0 ? String(o.perPage) : void 0
105
105
  });
106
+ const mutQuery = (where, o, mode) => ({
107
+ where: JSON.stringify(where ?? {}),
108
+ orderBy: o.orderBy !== void 0 ? JSON.stringify(o.orderBy) : void 0,
109
+ mode
110
+ });
106
111
  const client = {};
107
112
  for (const [key, entity] of Object.entries(options.entities)) {
108
113
  const shape = entity.columns;
109
114
  const path = `/api/${entity.name}`;
110
115
  const revive = (o) => reviveShape(shape, o);
111
- const list = async (o) => await request("GET", path, { query: queryFrom(o) });
116
+ const list = async (where, o) => await request("GET", path, { query: readQuery(where, o) });
112
117
  client[key] = {
113
118
  async create(input) {
114
119
  return revive(await request("POST", path, { body: input }));
115
120
  },
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];
121
+ async findOne(where = {}, o = {}) {
122
+ const d = (await list(where, { ...o, perPage: 1 })).docs?.[0];
128
123
  return d === void 0 ? null : revive(d);
129
124
  },
130
- async paginate(o = {}) {
131
- const page = await list(o);
125
+ async findMany(where = {}, o = {}) {
126
+ return (await list(where, o)).docs?.map(revive) ?? [];
127
+ },
128
+ async paginate(where = {}, o = {}) {
129
+ const page = await list(where, o);
132
130
  return {
133
131
  docs: page.docs?.map(revive) ?? [],
134
132
  docsQuantity: page.docsQuantity,
@@ -136,11 +134,21 @@ function createClient(options) {
136
134
  currentPage: page.currentPage
137
135
  };
138
136
  },
139
- async update(id, patch) {
140
- return revive(await request("PATCH", `${path}/${encodeURIComponent(id)}`, { body: patch }));
137
+ async updateOne(where, patch, o = {}) {
138
+ const r = await request("PATCH", path, { query: mutQuery(where, o), body: patch, allowNull: true });
139
+ return r === null ? null : revive(r);
140
+ },
141
+ async updateMany(where, patch) {
142
+ const r = await request("PATCH", path, { query: mutQuery(where, {}, "many"), body: patch });
143
+ return { count: r.count };
144
+ },
145
+ async deleteOne(where, o = {}) {
146
+ const r = await request("DELETE", path, { query: mutQuery(where, o), allowNull: true });
147
+ return r === null ? null : revive(r);
141
148
  },
142
- async delete(id) {
143
- await request("DELETE", `${path}/${encodeURIComponent(id)}`);
149
+ async deleteMany(where) {
150
+ const r = await request("DELETE", path, { query: mutQuery(where, {}, "many") });
151
+ return { count: r.count };
144
152
  }
145
153
  };
146
154
  }
package/dist/index.js.map CHANGED
@@ -1 +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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AASO,SAAS,YAAY,OAAoB,OAAyB;AACvE,MAAI,CAAC,SAAS,OAAO,UAAU,SAAU,QAAO;AAChD,QAAM,MAAM;AAEZ,aAAW,CAAC,KAAK,IAAI,KAAK,OAAO,QAAQ,KAAK,GAAG;AAC/C,UAAM,IAAI,IAAI,GAAG;AACjB,QAAI,gBAAgB,QAAQ;AAC1B,UAAI,KAAK,OAAO,OAAO,YAAY,UAAU,OAAO,MAAM,UAAU;AAClE,YAAI,GAAG,IAAI,IAAI,KAAK,CAAC;AAAA,MACvB;AAAA,IACF,WAAW,gBAAgB,OAAO;AAChC,UAAI,MAAM,QAAQ,CAAC,EAAG,GAAE,QAAQ,CAAC,MAAM,YAAY,KAAK,OAAO,CAAC,CAAC;AAAA,eACxD,EAAG,aAAY,KAAK,OAAO,CAAC;AAAA,IACvC,WAAW,gBAAgB,WAAW;AAEpC,UAAI,KAAK,gBAAgB,UAAU,MAAM,QAAQ,CAAC,GAAG;AACnD,UAAE,QAAQ,CAAC,MAAM,YAAY,KAAK,OAAO,SAAS,CAAC,CAAC;AAAA,MACtD,WAAW,KAAK,OAAO,MAAM,UAAU;AACrC,oBAAY,KAAK,OAAO,SAAS,CAAC;AAAA,MACpC;AAAA,IACF;AAAA,EACF;AAEA,MAAI,OAAO,IAAI,WAAW,MAAM,SAAU,KAAI,WAAW,IAAI,IAAI,KAAK,IAAI,WAAW,CAAC;AACtF,MAAI,OAAO,IAAI,WAAW,MAAM,SAAU,KAAI,WAAW,IAAI,IAAI,KAAK,IAAI,WAAW,CAAC;AACtF,SAAO;AACT;;;AC2DO,SAAS,aACd,SACgB;AAChB,QAAM,YAAuB,QAAQ,UAAU,CAAC,QAAQ,WAAW,MAAM,GAAG;AAC5E,QAAM,OAAO,QAAQ,IAAI,QAAQ,OAAO,EAAE;AAE1C,iBAAe,QACb,QACA,MACA,OAA4F,CAAC,GAC3E;AAClB,QAAI,MAAM,GAAG,IAAI,GAAG,IAAI;AACxB,QAAI,KAAK,OAAO;AACd,YAAM,KAAK,OAAO,QAAQ,KAAK,KAAK,EACjC,OAAO,CAAC,MAA6B,EAAE,CAAC,MAAM,MAAS,EACvD,IAAI,CAAC,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,mBAAmB,CAAC,CAAC,EAAE,EAC/C,KAAK,GAAG;AACX,UAAI,GAAI,QAAO,IAAI,EAAE;AAAA,IACvB;AAEA,UAAM,UAAkC,EAAE,aAAa,QAAQ,IAAI;AACnE,QAAI,QAAQ,OAAO;AACjB,cAAQ,eAAe,IAAI,QAAQ;AACnC,UAAI,QAAQ,OAAQ,SAAQ,gBAAgB,IAAI,KAAK,UAAU,QAAQ,MAAM;AAAA,IAC/E;AACA,UAAM,OAAoB,EAAE,QAAQ,QAAQ;AAC5C,QAAI,KAAK,SAAS,QAAW;AAC3B,cAAQ,cAAc,IAAI;AAC1B,WAAK,OAAO,KAAK,UAAU,KAAK,IAAI;AAAA,IACtC;AAEA,UAAM,MAAM,MAAM,UAAU,IAAI,QAAQ,KAAK,IAAI,CAAC;AAClD,QAAI,IAAI,WAAW,OAAO,KAAK,UAAW,QAAO;AACjD,UAAMA,QAAO,IAAI,WAAW,MAAM,OAAO,MAAM,IAAI,KAAK,EAAE,MAAM,MAAM,IAAI;AAC1E,QAAI,CAAC,IAAI,IAAI;AACX,YAAM,IACJA,SAAQ,OAAOA,UAAS,YAAY,WAAWA,QAC3C,OAAQA,MAA4B,KAAK,IACzC,yBAAyB,IAAI,MAAM;AACzC,YAAM,SAAS,IAAI,QAAQ,CAAC;AAAA,IAC9B;AACA,WAAOA;AAAA,EACT;AAIA,QAAM,YAAY,CAAC,OAAoD;AAAA,IACrE,QAAQ,KAAK,UAAU,EAAE,UAAU,CAAC,CAAC;AAAA,IACrC,OAAO,KAAK,UAAU,EAAE,SAAS,CAAC,CAAC;AAAA,IACnC,SAAS,EAAE,YAAY,SAAY,KAAK,UAAU,EAAE,OAAO,IAAI;AAAA,IAC/D,MAAM,EAAE,SAAS,SAAY,OAAO,EAAE,IAAI,IAAI;AAAA,IAC9C,SAAS,EAAE,YAAY,SAAY,OAAO,EAAE,OAAO,IAAI;AAAA,EACzD;AAEA,QAAM,SAAkC,CAAC;AAEzC,aAAW,CAAC,KAAK,MAAM,KAAK,OAAO,QAAQ,QAAQ,QAAQ,GAAG;AAC5D,UAAM,QAAQ,OAAO;AACrB,UAAM,OAAO,QAAQ,OAAO,IAAI;AAChC,UAAM,SAAS,CAAC,MAAe,YAAY,OAAO,CAAC;AACnD,UAAM,OAAO,OAAO,MACjB,MAAM,QAAQ,OAAO,MAAM,EAAE,OAAO,UAAU,CAAC,EAAE,CAAC;AAErD,WAAO,GAAG,IAAI;AAAA,MACZ,MAAM,OAAO,OAAgB;AAC3B,eAAO,OAAO,MAAM,QAAQ,QAAQ,MAAM,EAAE,MAAM,MAAM,CAAC,CAAC;AAAA,MAC5D;AAAA,MACA,MAAM,IAAI,IAAY,IAA0B,CAAC,GAAG;AAClD,cAAM,IAAI,MAAM,QAAQ,OAAO,GAAG,IAAI,IAAI,mBAAmB,EAAE,CAAC,IAAI;AAAA,UAClE,OAAO,EAAE,QAAQ,KAAK,UAAU,EAAE,UAAU,CAAC,CAAC,EAAE;AAAA,UAChD,WAAW;AAAA,QACb,CAAC;AACD,eAAO,MAAM,OAAO,OAAO,OAAO,CAAC;AAAA,MACrC;AAAA,MACA,MAAM,KAAK,IAAa,CAAC,GAAG;AAC1B,gBAAQ,MAAM,KAAK,CAAC,GAAG,MAAM,IAAI,MAAM,KAAK,CAAC;AAAA,MAC/C;AAAA,MACA,MAAM,QAAQ,IAAa,CAAC,GAAG;AAC7B,cAAM,KAAK,MAAM,KAAK,EAAE,GAAG,GAAG,SAAS,EAAE,CAAC,GAAG,OAAO,CAAC;AACrD,eAAO,MAAM,SAAY,OAAO,OAAO,CAAC;AAAA,MAC1C;AAAA,MACA,MAAM,SAAS,IAAa,CAAC,GAAG;AAC9B,cAAM,OAAO,MAAM,KAAK,CAAC;AACzB,eAAO;AAAA,UACL,MAAM,KAAK,MAAM,IAAI,MAAM,KAAK,CAAC;AAAA,UACjC,cAAc,KAAK;AAAA,UACnB,cAAc,KAAK;AAAA,UACnB,aAAa,KAAK;AAAA,QACpB;AAAA,MACF;AAAA,MACA,MAAM,OAAO,IAAY,OAAgB;AACvC,eAAO,OAAO,MAAM,QAAQ,SAAS,GAAG,IAAI,IAAI,mBAAmB,EAAE,CAAC,IAAI,EAAE,MAAM,MAAM,CAAC,CAAC;AAAA,MAC5F;AAAA,MACA,MAAM,OAAO,IAAY;AACvB,cAAM,QAAQ,UAAU,GAAG,IAAI,IAAI,mBAAmB,EAAE,CAAC,EAAE;AAAA,MAC7D;AAAA,IACF;AAAA,EACF;AAGA,SAAO,IAAI,IAAI,CAAC,OAAe,WAC7B,aAAa,EAAE,GAAG,SAAS,OAAO,GAAI,SAAS,EAAE,OAAO,IAAI,CAAC,EAAG,CAAC;AAEnE,SAAO;AACT;","names":["json"]}
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 * Modificadores de leitura, **tipados pela entidade**: `orderBy` (`OrderByInput`) e\n * `expand` (`ExpandInput`), que ainda **dirige o tipo do retorno** (`InferRead<E, X>`).\n * O `where` NÃO vem aqui — é o 1º argumento cru do método.\n */\nexport interface ReadOpts<E extends Entity<string, ShapeRecord>, X> {\n orderBy?: OrderByInput<E>;\n expand?: X & ExpandInput<E>;\n}\n\nexport interface PageOpts<E extends Entity<string, ShapeRecord>, X> extends ReadOpts<E, X> {\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. Uma linha se mira por **`where` cru** (1º arg) —\n * `{ id: \"123\" }` é açúcar pra `{ id: { eq: \"123\" } }`. Verbos com `One` pegam o\n * **primeiro match** (`orderBy` desempata); com `Many` operam em massa e devolvem\n * `{ count }`. Os reads se **auto-tipam pelo `expand`** (`const X` → `InferRead`).\n */\nexport interface EntityClient<E extends Entity<string, ShapeRecord>> {\n create(input: InferInsert<E>): Promise<InferEntity<E>>;\n\n findOne<const X = {}>(where?: WhereInput<E>, opts?: ReadOpts<E, X>): Promise<InferRead<E, X> | null>;\n findMany<const X = {}>(where?: WhereInput<E>, opts?: ReadOpts<E, X>): Promise<InferRead<E, X>[]>;\n paginate<const X = {}>(where?: WhereInput<E>, opts?: PageOpts<E, X>): Promise<PageResult<InferRead<E, X>>>;\n\n updateOne(\n where: WhereInput<E>,\n patch: Partial<InferInsert<E>>,\n opts?: { orderBy?: OrderByInput<E> },\n ): Promise<InferEntity<E> | null>;\n updateMany(where: WhereInput<E>, patch: Partial<InferInsert<E>>): Promise<{ count: number }>;\n\n deleteOne(where: WhereInput<E>, opts?: { orderBy?: OrderByInput<E> }): Promise<InferEntity<E> | null>;\n deleteMany(where: WhereInput<E>): Promise<{ count: number }>;\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// Formas frouxas usadas SÓ na implementação (a interface dá os tipos).\ntype AnyOpts = { 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 readQuery = (where: unknown, o: AnyOpts): Record<string, string | undefined> => ({\n where: JSON.stringify(where ?? {}),\n expand: JSON.stringify(o.expand ?? {}),\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 // Mutação por where: `where` (+ `orderBy` pro *One desempatar) + `mode` (`many` no bulk).\n const mutQuery = (where: unknown, o: AnyOpts, mode?: \"many\"): Record<string, string | undefined> => ({\n where: JSON.stringify(where ?? {}),\n orderBy: o.orderBy !== undefined ? JSON.stringify(o.orderBy) : undefined,\n mode,\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 (where: unknown, o: AnyOpts): Promise<ListResponse> =>\n (await request(\"GET\", path, { query: readQuery(where, o) })) as ListResponse;\n\n client[key] = {\n async create(input: unknown) {\n return revive(await request(\"POST\", path, { body: input }));\n },\n async findOne(where: unknown = {}, o: AnyOpts = {}) {\n const d = (await list(where, { ...o, perPage: 1 })).docs?.[0];\n return d === undefined ? null : revive(d);\n },\n async findMany(where: unknown = {}, o: AnyOpts = {}) {\n return (await list(where, o)).docs?.map(revive) ?? [];\n },\n async paginate(where: unknown = {}, o: AnyOpts = {}) {\n const page = await list(where, 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 updateOne(where: unknown, patch: unknown, o: AnyOpts = {}) {\n const r = await request(\"PATCH\", path, { query: mutQuery(where, o), body: patch, allowNull: true });\n return r === null ? null : revive(r);\n },\n async updateMany(where: unknown, patch: unknown) {\n const r = (await request(\"PATCH\", path, { query: mutQuery(where, {}, \"many\"), body: patch })) as {\n count: number;\n };\n return { count: r.count };\n },\n async deleteOne(where: unknown, o: AnyOpts = {}) {\n const r = await request(\"DELETE\", path, { query: mutQuery(where, o), allowNull: true });\n return r === null ? null : revive(r);\n },\n async deleteMany(where: unknown) {\n const r = (await request(\"DELETE\", path, { query: mutQuery(where, {}, \"many\") })) as { count: number };\n return { count: r.count };\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"]}
@@ -1,4 +1,4 @@
1
- import { Entity, ShapeRecord, InferInsert, InferEntity, ExpandInput, InferRead, WhereInput, OrderByInput } from '@mauroandre/weave-core';
1
+ import { Entity, ShapeRecord, InferInsert, InferEntity, WhereInput, OrderByInput, ExpandInput, InferRead } from '@mauroandre/weave-core';
2
2
 
3
3
  /** Função de transporte: recebe um `Request`, devolve um `Response` (WHATWG fetch).
4
4
  * Aceita retorno síncrono ou Promise (o `app.hono.fetch` pode ser síncrono). */
@@ -18,14 +18,15 @@ interface ClientOptions<S> {
18
18
  params?: Record<string, unknown>;
19
19
  }
20
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.
21
+ * Modificadores de leitura, **tipados pela entidade**: `orderBy` (`OrderByInput`) e
22
+ * `expand` (`ExpandInput`), que ainda **dirige o tipo do retorno** (`InferRead<E, X>`).
23
+ * O `where` NÃO vem aqui é o argumento cru do método.
24
24
  */
25
- interface FindArgs<E extends Entity<string, ShapeRecord>, X> {
26
- where?: WhereInput<E>;
25
+ interface ReadOpts<E extends Entity<string, ShapeRecord>, X> {
27
26
  orderBy?: OrderByInput<E>;
28
27
  expand?: X & ExpandInput<E>;
28
+ }
29
+ interface PageOpts<E extends Entity<string, ShapeRecord>, X> extends ReadOpts<E, X> {
29
30
  page?: number;
30
31
  perPage?: number;
31
32
  }
@@ -36,20 +37,28 @@ interface PageResult<T> {
36
37
  currentPage: number;
37
38
  }
38
39
  /**
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 } })` devolve o
41
- * objeto com `category` expandido e tipado sem escrever nenhum `Infer`.
40
+ * Client tipado de UMA entidade. Uma linha se mira por **`where` cru** (1º arg) —
41
+ * `{ id: "123" }` é açúcar pra `{ id: { eq: "123" } }`. Verbos com `One` pegam o
42
+ * **primeiro match** (`orderBy` desempata); com `Many` operam em massa e devolvem
43
+ * `{ count }`. Os reads se **auto-tipam pelo `expand`** (`const X` → `InferRead`).
42
44
  */
43
45
  interface EntityClient<E extends Entity<string, ShapeRecord>> {
44
46
  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>;
47
+ findOne<const X = {}>(where?: WhereInput<E>, opts?: ReadOpts<E, X>): Promise<InferRead<E, X> | null>;
48
+ findMany<const X = {}>(where?: WhereInput<E>, opts?: ReadOpts<E, X>): Promise<InferRead<E, X>[]>;
49
+ paginate<const X = {}>(where?: WhereInput<E>, opts?: PageOpts<E, X>): Promise<PageResult<InferRead<E, X>>>;
50
+ updateOne(where: WhereInput<E>, patch: Partial<InferInsert<E>>, opts?: {
51
+ orderBy?: OrderByInput<E>;
52
+ }): Promise<InferEntity<E> | null>;
53
+ updateMany(where: WhereInput<E>, patch: Partial<InferInsert<E>>): Promise<{
54
+ count: number;
55
+ }>;
56
+ deleteOne(where: WhereInput<E>, opts?: {
57
+ orderBy?: OrderByInput<E>;
58
+ }): Promise<InferEntity<E> | null>;
59
+ deleteMany(where: WhereInput<E>): Promise<{
60
+ count: number;
61
+ }>;
53
62
  }
54
63
  /** O client completo: uma propriedade por entidade do entities + `as` (scope). */
55
64
  type WeaveClient<S extends Record<string, Entity<string, ShapeRecord>>> = {
@@ -97,4 +106,4 @@ declare function pushScopes(scopes: Record<string, ScopeDef>, options: PushScope
97
106
  pushed: string[];
98
107
  }>;
99
108
 
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 };
109
+ export { type ClientOptions as C, type EntityClient as E, type FetchLike as F, type PageOpts as P, type ReadOpts as R, type ScopeDef as S, type Verb as V, type WeaveClient as W, type PageResult as a, type PushScopesOptions as b, type ScopeEntityRule as c, createClient as d, defineScope as e, pushScopes as p };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mauroandre/weave-sdk",
3
- "version": "0.0.2",
3
+ "version": "0.0.3",
4
4
  "description": "Weave SDK — typed object client over the Weave HTTP API. The glue: entities-as-code in, objects out, HTTP/JSON invisible.",
5
5
  "type": "module",
6
6
  "license": "MIT",