@mauroandre/weave-sdk 0.0.2 → 0.0.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.
- package/README.md +15 -14
- package/dist/cli.d.ts +3 -1
- package/dist/cli.js +45 -6
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +29 -21
- package/dist/index.js.map +1 -1
- package/dist/{scope-DrcCzdf-.d.ts → scope-S2VyyW6B.d.ts} +27 -18
- package/package.json +1 -1
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.
|
|
31
|
-
|
|
32
|
-
orderBy: { price: "desc" },
|
|
33
|
-
|
|
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
|
-
|
|
42
|
-
`
|
|
43
|
-
|
|
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.
|
|
52
|
-
|
|
53
|
-
or: [{ status:
|
|
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" } },
|
|
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.
|
|
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-
|
|
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<{
|
|
@@ -23,6 +23,8 @@ interface ParsedArgs {
|
|
|
23
23
|
command: string;
|
|
24
24
|
config: string;
|
|
25
25
|
confirm: Record<string, string[]>;
|
|
26
|
+
/** `--confirm all`: aceita TODAS as remoções (risco confirm) de uma vez. */
|
|
27
|
+
confirmAll: boolean;
|
|
26
28
|
fill: Record<string, Record<string, unknown>>;
|
|
27
29
|
renames: Record<string, Record<string, string>>;
|
|
28
30
|
/** `--no-gen`: após o push, NÃO re-sincroniza os arquivos locais (CI, read-only). */
|
package/dist/cli.js
CHANGED
|
@@ -72,14 +72,18 @@ function splitEntity(s) {
|
|
|
72
72
|
return i < 0 ? [s, ""] : [s.slice(0, i), s.slice(i + 1)];
|
|
73
73
|
}
|
|
74
74
|
function parseArgs(argv) {
|
|
75
|
-
const out = { command: argv[0] ?? "", config: "weave.config.ts", confirm: {}, fill: {}, renames: {}, noGen: false };
|
|
75
|
+
const out = { command: argv[0] ?? "", config: "weave.config.ts", confirm: {}, confirmAll: false, fill: {}, renames: {}, noGen: false };
|
|
76
76
|
for (let i = 1; i < argv.length; i++) {
|
|
77
77
|
const a = argv[i];
|
|
78
78
|
if (a === "--config") out.config = argv[++i] ?? out.config;
|
|
79
79
|
else if (a === "--no-gen") out.noGen = true;
|
|
80
80
|
else if (a === "--confirm") {
|
|
81
|
-
const
|
|
82
|
-
if (
|
|
81
|
+
const val = argv[++i];
|
|
82
|
+
if (val === "all") out.confirmAll = true;
|
|
83
|
+
else {
|
|
84
|
+
const [e, p] = splitEntity(val);
|
|
85
|
+
if (e && p) (out.confirm[e] ??= []).push(p);
|
|
86
|
+
}
|
|
83
87
|
} else if (a === "--fill") {
|
|
84
88
|
const [ep, v = ""] = (argv[++i] ?? "").split(/=(.*)/s);
|
|
85
89
|
const [e, p] = splitEntity(ep);
|
|
@@ -143,19 +147,54 @@ async function runCli(argv, deps = {}) {
|
|
|
143
147
|
log(`No entities found in ${dirRel}/entities.`);
|
|
144
148
|
return 1;
|
|
145
149
|
}
|
|
146
|
-
|
|
150
|
+
let res = await pushEntities(entities, {
|
|
147
151
|
...net,
|
|
148
152
|
confirm: args.confirm,
|
|
149
153
|
fill: args.fill,
|
|
150
154
|
renames: args.renames
|
|
151
155
|
});
|
|
156
|
+
if (args.confirmAll && res.review.some((r) => r.plan.changes.some((c) => c.risk === "confirm"))) {
|
|
157
|
+
const confirm = { ...args.confirm };
|
|
158
|
+
for (const r of res.review) {
|
|
159
|
+
for (const c of r.plan.changes) {
|
|
160
|
+
if (c.risk === "confirm") (confirm[r.name] ??= []).push(c.path);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
res = await pushEntities(entities, { ...net, confirm, fill: args.fill, renames: args.renames });
|
|
164
|
+
}
|
|
152
165
|
for (const n of res.applied) log(` \u{1F7E2} ${n} applied`);
|
|
166
|
+
const cmds = [];
|
|
167
|
+
let hasBlocked = false;
|
|
168
|
+
let hasRemove = false;
|
|
153
169
|
for (const r of res.review) {
|
|
154
170
|
log(` \u26A0 ${r.name} \u2014 needs review:`);
|
|
155
|
-
for (const c of r.plan.changes)
|
|
171
|
+
for (const c of r.plan.changes) {
|
|
172
|
+
const target = `${r.name}.${c.path}`;
|
|
173
|
+
const flag = c.risk === "confirm" ? `--confirm ${target}` : c.risk === "needsValue" ? `--fill ${target}=<value>` : "";
|
|
174
|
+
if (flag) cmds.push(flag);
|
|
175
|
+
if (c.op === "removeField") hasRemove = true;
|
|
176
|
+
if (c.risk === "blocked") hasBlocked = true;
|
|
177
|
+
const hint = c.risk === "confirm" ? `deletes its data \u2014 allow with ${flag}` : c.risk === "needsValue" ? `needs a value \u2014 provide with ${flag}` : c.risk === "blocked" ? "not supported by push \u2014 revert it, or fix the data in the browser" : "";
|
|
178
|
+
log(` ${riskIcon(c.risk)} ${c.op} ${target}${hint ? ` \u2014 ${hint}` : ""}`);
|
|
179
|
+
}
|
|
156
180
|
}
|
|
157
181
|
if (res.review.length > 0) {
|
|
158
|
-
|
|
182
|
+
if (cmds.length) {
|
|
183
|
+
log("");
|
|
184
|
+
log("Re-run with the flag(s) above to apply \u2014 the target is always `entity.field`, e.g.:");
|
|
185
|
+
log(` weave push ${cmds.join(" ")}`);
|
|
186
|
+
if (cmds.filter((f) => f.startsWith("--confirm")).length > 1) {
|
|
187
|
+
log(" (or weave push --confirm all to accept every drop at once)");
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
if (hasRemove) {
|
|
191
|
+
log("");
|
|
192
|
+
log("Renamed, not removed? Use --rename entity.oldField=newField to keep the data.");
|
|
193
|
+
}
|
|
194
|
+
if (hasBlocked) {
|
|
195
|
+
log("");
|
|
196
|
+
log("\u26D4 Blocked changes can't be forced \u2014 they hold the whole entity. Revert them or fix the data first.");
|
|
197
|
+
}
|
|
159
198
|
return 1;
|
|
160
199
|
}
|
|
161
200
|
log(`\u2713 pushed ${res.applied.length} ${res.applied.length === 1 ? "entity" : "entities"}.`);
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cli.ts","../src/discover.ts"],"sourcesContent":["#!/usr/bin/env node\nimport path from \"node:path\";\nimport { realpathSync } from \"node:fs\";\nimport { pathToFileURL } from \"node:url\";\nimport { createJiti } from \"jiti\";\nimport { discoverEntities, discoverScopes, type ModuleLoader } from \"./discover.js\";\nimport { pushEntities } from \"./push.js\";\nimport { pushScopes } from \"./scope.js\";\nimport { pullEntities, genProject } from \"./gen.js\";\nimport { DEFAULT_DIR, type WeaveConfig } from \"./config.js\";\n\n// Barrel node-only (`@mauroandre/weave-sdk/cli`): a descoberta usa `node:fs`, então\n// fica fora do barrel principal (que é portável p/ browser).\nexport { discoverEntities, discoverScopes, type ModuleLoader } from \"./discover.js\";\n\n/** Escreve um arquivo (criando dirs). Injetável pra teste. */\nasync function defaultWrite(file: string, content: string): Promise<void> {\n const fs = await import(\"node:fs/promises\");\n await fs.mkdir(path.dirname(file), { recursive: true });\n await fs.writeFile(file, content, \"utf8\");\n}\n\n/** Apaga uma pasta recursivamente (idempotente). Injetável pra teste. */\nasync function defaultClean(dir: string): Promise<void> {\n const fs = await import(\"node:fs/promises\");\n await fs.rm(dir, { recursive: true, force: true });\n}\n\n// Loader padrão: jiti carrega os `.ts` do dev (weave.config.ts + entidades) em\n// runtime — resolve type-stripping e o `import \"./x.js\"` → `x.ts` que o Node puro\n// não faz. Criado uma vez, sob demanda. Injetável (os testes passam o próprio load).\nlet jiti: ReturnType<typeof createJiti> | null = null;\nconst defaultLoad: ModuleLoader = (p) => {\n jiti ??= createJiti(import.meta.url);\n return jiti.import(p) as Promise<{ default?: unknown }>;\n};\n\n/** Carrega o `.env` do projeto pro process.env (built-in do Node). Silencioso se não houver. */\nfunction loadEnv(cwd: string): Record<string, string | undefined> {\n try {\n process.loadEnvFile(path.resolve(cwd, \".env\"));\n } catch {\n /* sem .env — segue com o ambiente atual */\n }\n return process.env;\n}\nimport type { FetchLike } from \"./client.js\";\n\n// CLI `weave`. Comandos: `gen` (server → pasta weave/: entidades com $id, scopes,\n// barrels e client — overwrite cego), `push` (código → server, plan/apply em ordem\n// de dep), `pull` (legado: só entidades). url/key vêm do ambiente (WEAVE_URL/\n// WEAVE_KEY); a pasta de destino do `weave.config.ts` (`dir`, default \"weave\").\n// Flags: --config, --confirm, --fill, --rename. Carrega TS via runtime TS-capaz\n// (Node 22.6+ com --experimental-strip-types, ou tsx/jiti).\n\nexport interface ParsedArgs {\n command: string;\n config: string;\n confirm: Record<string, string[]>;\n fill: Record<string, Record<string, unknown>>;\n renames: Record<string, Record<string, string>>;\n /** `--no-gen`: após o push, NÃO re-sincroniza os arquivos locais (CI, read-only). */\n noGen: boolean;\n}\n\n/** \"product.legacy\" → [\"product\", \"legacy\"]; \"product.items.qty\" → [\"product\",\"items.qty\"]. */\nfunction splitEntity(s: string | undefined): [string, string] {\n if (!s) return [\"\", \"\"];\n const i = s.indexOf(\".\");\n return i < 0 ? [s, \"\"] : [s.slice(0, i), s.slice(i + 1)];\n}\n\nexport function parseArgs(argv: string[]): ParsedArgs {\n const out: ParsedArgs = { command: argv[0] ?? \"\", config: \"weave.config.ts\", confirm: {}, fill: {}, renames: {}, noGen: false };\n for (let i = 1; i < argv.length; i++) {\n const a = argv[i];\n if (a === \"--config\") out.config = argv[++i] ?? out.config;\n else if (a === \"--no-gen\") out.noGen = true;\n else if (a === \"--confirm\") {\n const [e, p] = splitEntity(argv[++i]);\n if (e && p) (out.confirm[e] ??= []).push(p);\n } else if (a === \"--fill\") {\n const [ep, v = \"\"] = (argv[++i] ?? \"\").split(/=(.*)/s);\n const [e, p] = splitEntity(ep);\n if (e && p) (out.fill[e] ??= {})[p] = v;\n } else if (a === \"--rename\") {\n const [ep, to = \"\"] = (argv[++i] ?? \"\").split(/=(.*)/s);\n const [e, p] = splitEntity(ep);\n if (e && p && to) (out.renames[e] ??= {})[p] = to;\n }\n }\n return out;\n}\n\nexport interface CliDeps {\n /** Importador de módulo (config + entidades). Default: dynamic import. */\n load?: ModuleLoader;\n /** Transporte HTTP. Default: globalThis.fetch. */\n fetch?: FetchLike;\n /** Escreve arquivo (pull/gen). Default: fs. */\n write?: (file: string, content: string) => Promise<void>;\n /** Apaga pasta (gen, antes de reescrever). Default: fs.rm. */\n clean?: (dir: string) => Promise<void>;\n /** Variáveis de ambiente (WEAVE_URL/WEAVE_KEY). Default: process.env. */\n env?: Record<string, string | undefined>;\n cwd?: string;\n log?: (msg: string) => void;\n}\n\nconst riskIcon = (r: string): string =>\n r === \"auto\" ? \"🟢\" : r === \"confirm\" ? \"🔴\" : r === \"needsValue\" ? \"🟡\" : \"⛔\";\n\n/** Roda o CLI. Devolve o exit code (0 ok; 1 erro / precisa de revisão). */\nexport async function runCli(argv: string[], deps: CliDeps = {}): Promise<number> {\n const args = parseArgs(argv);\n const log = deps.log ?? ((m: string) => console.log(m));\n const cwd = deps.cwd ?? process.cwd();\n const load: ModuleLoader = deps.load ?? defaultLoad;\n const env = deps.env ?? loadEnv(cwd);\n\n if (![\"push\", \"pull\", \"gen\"].includes(args.command)) {\n log(`Unknown command '${args.command}'. Try: weave push | pull | gen`);\n return 1;\n }\n\n const url = env[\"WEAVE_URL\"];\n const key = env[\"WEAVE_KEY\"];\n if (!url || !key) {\n log(\"Set WEAVE_URL and WEAVE_KEY in the environment.\");\n return 1;\n }\n\n // Config é opcional (só `dir`, default \"weave\"); ausente/ilegível → defaults.\n const configPath = path.resolve(cwd, args.config);\n let config: WeaveConfig = {};\n try {\n config = ((await load(configPath)).default ?? {}) as WeaveConfig;\n } catch {\n /* sem weave.config.ts — usa defaults */\n }\n const dirRel = config.dir ?? DEFAULT_DIR;\n const dir = path.resolve(cwd, dirRel);\n const entitiesDir = path.join(dir, \"entities\");\n const net = { url, key, ...(deps.fetch ? { fetch: deps.fetch } : {}) };\n const write = deps.write ?? defaultWrite;\n const clean = deps.clean ?? defaultClean;\n\n // Regenera a pasta weave/ a partir do server (overwrite cego). Usado pelo `gen`\n // e ao fim do `push` (re-sincroniza os $id recém-cunhados), salvo `--no-gen`.\n const regen = async (): Promise<void> => {\n const { files, entities, scopes } = await genProject(net);\n await clean(path.join(dir, \"entities\"));\n await clean(path.join(dir, \"scopes\"));\n for (const [rel, content] of Object.entries(files)) await write(path.join(dir, rel), content);\n log(`✓ generated ${entities.length} ${entities.length === 1 ? \"entity\" : \"entities\"}, ${scopes.length} ${scopes.length === 1 ? \"scope\" : \"scopes\"} → ${dirRel}/`);\n };\n\n // gen: server → pasta weave/ inteira (arquivos com $id, scopes resolvidos, barrels, client).\n if (args.command === \"gen\") {\n await regen();\n return 0;\n }\n\n // pull (legado): puxa os IRs remotos → escreve os arquivos de entidade (sem $id).\n if (args.command === \"pull\") {\n const { files, names } = await pullEntities(net);\n for (const [file, content] of Object.entries(files)) await write(path.join(entitiesDir, file), content);\n log(`✓ pulled ${names.length} ${names.length === 1 ? \"entity\" : \"entities\"} → ${dirRel}/entities`);\n return 0;\n }\n\n // push: tudo vai — entidades primeiro (cunham/fixam ids), depois scopes (resolvem\n // nome→id contra o server), e por fim o gen re-sincroniza os arquivos locais.\n const entities = await discoverEntities(entitiesDir, load);\n if (Object.keys(entities).length === 0) {\n log(`No entities found in ${dirRel}/entities.`);\n return 1;\n }\n\n const res = await pushEntities(entities, {\n ...net,\n confirm: args.confirm,\n fill: args.fill,\n renames: args.renames,\n });\n\n for (const n of res.applied) log(` 🟢 ${n} applied`);\n for (const r of res.review) {\n log(` ⚠ ${r.name} — needs review:`);\n for (const c of r.plan.changes) log(` ${riskIcon(c.risk)} ${c.op} ${c.path} (${c.risk})`);\n }\n // Mudanças bloqueadas no gate → para aqui; scopes/gen ficam pra quando aplicar.\n if (res.review.length > 0) {\n log(\"Run again with --confirm / --fill / --rename to apply the gated changes.\");\n return 1;\n }\n log(`✓ pushed ${res.applied.length} ${res.applied.length === 1 ? \"entity\" : \"entities\"}.`);\n\n // Scopes (só depois das entidades aplicadas — o push resolve nome→id no server).\n const scopes = await discoverScopes(path.join(dir, \"scopes\"), load);\n if (Object.keys(scopes).length > 0) {\n const { pushed } = await pushScopes(scopes, net);\n log(`✓ pushed ${pushed.length} ${pushed.length === 1 ? \"scope\" : \"scopes\"}.`);\n }\n\n // Re-sincroniza os arquivos locais (ids recém-cunhados), salvo --no-gen.\n if (!args.noGen) await regen();\n return 0;\n}\n\nexport async function main(): Promise<void> {\n process.exit(await runCli(process.argv.slice(2)));\n}\n\n// Executado direto (bin) → roda o main. `realpathSync` resolve o symlink do bin\n// (node_modules/.bin/weave → dist/cli.js); sem isso, rodar via o symlink não bate\n// com `import.meta.url` (caminho real) e o main nunca rodava.\nif (process.argv[1]) {\n try {\n const invoked = pathToFileURL(realpathSync(process.argv[1])).href;\n if (invoked === import.meta.url) void main();\n } catch {\n /* não foi possível resolver o caminho — não é uma invocação direta */\n }\n}\n","import fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { pathToFileURL } from \"node:url\";\nimport type { Entity, ShapeRecord } from \"@mauroandre/weave-core\";\nimport type { ScopeDef } from \"./scope.js\";\n\n// Descoberta por pasta (file-based, igual o VeloJS acha rotas): cada arquivo é uma\n// entidade, exportada como `default`. Node-only (usa fs) — fora do barrel do SDK.\n\n/** Importa um módulo por caminho absoluto. Injetável (a CLI usa um loader de TS). */\nexport type ModuleLoader = (absPath: string) => Promise<{ default?: unknown }>;\n\nconst isEntity = (v: unknown): v is Entity<string, ShapeRecord> =>\n !!v && typeof v === \"object\" && \"name\" in v && \"columns\" in v;\n\n/**\n * Lê a pasta de entidades, importa o `default` de cada arquivo, e monta o objeto\n * `entities` chaveado pelo nome da entidade — o mesmo que o `pushEntities`/`createClient`\n * consomem. Ignora arquivos sem `export default defineEntity(...)`.\n */\nexport async function discoverEntities(\n entitiesDir: string,\n load: ModuleLoader = (p) => import(pathToFileURL(p).href),\n): Promise<Record<string, Entity<string, ShapeRecord>>> {\n const files = (await fs.readdir(entitiesDir))\n .filter((f) => /\\.(ts|tsx|mts|js|mjs)$/.test(f) && !f.endsWith(\".d.ts\"))\n .sort();\n\n const entities: Record<string, Entity<string, ShapeRecord>> = {};\n for (const f of files) {\n const mod = await load(path.resolve(entitiesDir, f));\n if (isEntity(mod.default)) entities[mod.default.name] = mod.default;\n }\n return entities;\n}\n\nconst isScope = (v: unknown): v is ScopeDef =>\n !!v && typeof v === \"object\" && \"name\" in v && \"entities\" in v && !(\"columns\" in v);\n\n/**\n * Lê a pasta de scopes (1 arquivo = 1 scope, `export default defineScope(...)`),\n * chaveando pelo nome do scope. Pasta ausente → `{}` (scopes são opcionais). O\n * barrel `index.ts` não tem default export, então é ignorado naturalmente.\n */\nexport async function discoverScopes(\n scopesDir: string,\n load: ModuleLoader = (p) => import(pathToFileURL(p).href),\n): Promise<Record<string, ScopeDef>> {\n let names: string[];\n try {\n names = await fs.readdir(scopesDir);\n } catch {\n return {}; // sem pasta de scopes\n }\n const files = names.filter((f) => /\\.(ts|tsx|mts|js|mjs)$/.test(f) && !f.endsWith(\".d.ts\")).sort();\n\n const scopes: Record<string, ScopeDef> = {};\n for (const f of files) {\n const mod = await load(path.resolve(scopesDir, f));\n if (isScope(mod.default)) scopes[mod.default.name] = mod.default;\n }\n return scopes;\n}\n"],"mappings":"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","names":["path","pathToFileURL","fs","path","entities","scopes","pathToFileURL"]}
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts","../src/discover.ts"],"sourcesContent":["#!/usr/bin/env node\nimport path from \"node:path\";\nimport { realpathSync } from \"node:fs\";\nimport { pathToFileURL } from \"node:url\";\nimport { createJiti } from \"jiti\";\nimport { discoverEntities, discoverScopes, type ModuleLoader } from \"./discover.js\";\nimport { pushEntities } from \"./push.js\";\nimport { pushScopes } from \"./scope.js\";\nimport { pullEntities, genProject } from \"./gen.js\";\nimport { DEFAULT_DIR, type WeaveConfig } from \"./config.js\";\n\n// Barrel node-only (`@mauroandre/weave-sdk/cli`): a descoberta usa `node:fs`, então\n// fica fora do barrel principal (que é portável p/ browser).\nexport { discoverEntities, discoverScopes, type ModuleLoader } from \"./discover.js\";\n\n/** Escreve um arquivo (criando dirs). Injetável pra teste. */\nasync function defaultWrite(file: string, content: string): Promise<void> {\n const fs = await import(\"node:fs/promises\");\n await fs.mkdir(path.dirname(file), { recursive: true });\n await fs.writeFile(file, content, \"utf8\");\n}\n\n/** Apaga uma pasta recursivamente (idempotente). Injetável pra teste. */\nasync function defaultClean(dir: string): Promise<void> {\n const fs = await import(\"node:fs/promises\");\n await fs.rm(dir, { recursive: true, force: true });\n}\n\n// Loader padrão: jiti carrega os `.ts` do dev (weave.config.ts + entidades) em\n// runtime — resolve type-stripping e o `import \"./x.js\"` → `x.ts` que o Node puro\n// não faz. Criado uma vez, sob demanda. Injetável (os testes passam o próprio load).\nlet jiti: ReturnType<typeof createJiti> | null = null;\nconst defaultLoad: ModuleLoader = (p) => {\n jiti ??= createJiti(import.meta.url);\n return jiti.import(p) as Promise<{ default?: unknown }>;\n};\n\n/** Carrega o `.env` do projeto pro process.env (built-in do Node). Silencioso se não houver. */\nfunction loadEnv(cwd: string): Record<string, string | undefined> {\n try {\n process.loadEnvFile(path.resolve(cwd, \".env\"));\n } catch {\n /* sem .env — segue com o ambiente atual */\n }\n return process.env;\n}\nimport type { FetchLike } from \"./client.js\";\n\n// CLI `weave`. Comandos: `gen` (server → pasta weave/: entidades com $id, scopes,\n// barrels e client — overwrite cego), `push` (código → server, plan/apply em ordem\n// de dep), `pull` (legado: só entidades). url/key vêm do ambiente (WEAVE_URL/\n// WEAVE_KEY); a pasta de destino do `weave.config.ts` (`dir`, default \"weave\").\n// Flags: --config, --confirm, --fill, --rename. Carrega TS via runtime TS-capaz\n// (Node 22.6+ com --experimental-strip-types, ou tsx/jiti).\n\nexport interface ParsedArgs {\n command: string;\n config: string;\n confirm: Record<string, string[]>;\n /** `--confirm all`: aceita TODAS as remoções (risco confirm) de uma vez. */\n confirmAll: boolean;\n fill: Record<string, Record<string, unknown>>;\n renames: Record<string, Record<string, string>>;\n /** `--no-gen`: após o push, NÃO re-sincroniza os arquivos locais (CI, read-only). */\n noGen: boolean;\n}\n\n/** \"product.legacy\" → [\"product\", \"legacy\"]; \"product.items.qty\" → [\"product\",\"items.qty\"]. */\nfunction splitEntity(s: string | undefined): [string, string] {\n if (!s) return [\"\", \"\"];\n const i = s.indexOf(\".\");\n return i < 0 ? [s, \"\"] : [s.slice(0, i), s.slice(i + 1)];\n}\n\nexport function parseArgs(argv: string[]): ParsedArgs {\n const out: ParsedArgs = { command: argv[0] ?? \"\", config: \"weave.config.ts\", confirm: {}, confirmAll: false, fill: {}, renames: {}, noGen: false };\n for (let i = 1; i < argv.length; i++) {\n const a = argv[i];\n if (a === \"--config\") out.config = argv[++i] ?? out.config;\n else if (a === \"--no-gen\") out.noGen = true;\n else if (a === \"--confirm\") {\n const val = argv[++i];\n if (val === \"all\") out.confirmAll = true; // aceita todas as remoções\n else {\n const [e, p] = splitEntity(val);\n if (e && p) (out.confirm[e] ??= []).push(p);\n }\n } else if (a === \"--fill\") {\n const [ep, v = \"\"] = (argv[++i] ?? \"\").split(/=(.*)/s);\n const [e, p] = splitEntity(ep);\n if (e && p) (out.fill[e] ??= {})[p] = v;\n } else if (a === \"--rename\") {\n const [ep, to = \"\"] = (argv[++i] ?? \"\").split(/=(.*)/s);\n const [e, p] = splitEntity(ep);\n if (e && p && to) (out.renames[e] ??= {})[p] = to;\n }\n }\n return out;\n}\n\nexport interface CliDeps {\n /** Importador de módulo (config + entidades). Default: dynamic import. */\n load?: ModuleLoader;\n /** Transporte HTTP. Default: globalThis.fetch. */\n fetch?: FetchLike;\n /** Escreve arquivo (pull/gen). Default: fs. */\n write?: (file: string, content: string) => Promise<void>;\n /** Apaga pasta (gen, antes de reescrever). Default: fs.rm. */\n clean?: (dir: string) => Promise<void>;\n /** Variáveis de ambiente (WEAVE_URL/WEAVE_KEY). Default: process.env. */\n env?: Record<string, string | undefined>;\n cwd?: string;\n log?: (msg: string) => void;\n}\n\nconst riskIcon = (r: string): string =>\n r === \"auto\" ? \"🟢\" : r === \"confirm\" ? \"🔴\" : r === \"needsValue\" ? \"🟡\" : \"⛔\";\n\n/** Roda o CLI. Devolve o exit code (0 ok; 1 erro / precisa de revisão). */\nexport async function runCli(argv: string[], deps: CliDeps = {}): Promise<number> {\n const args = parseArgs(argv);\n const log = deps.log ?? ((m: string) => console.log(m));\n const cwd = deps.cwd ?? process.cwd();\n const load: ModuleLoader = deps.load ?? defaultLoad;\n const env = deps.env ?? loadEnv(cwd);\n\n if (![\"push\", \"pull\", \"gen\"].includes(args.command)) {\n log(`Unknown command '${args.command}'. Try: weave push | pull | gen`);\n return 1;\n }\n\n const url = env[\"WEAVE_URL\"];\n const key = env[\"WEAVE_KEY\"];\n if (!url || !key) {\n log(\"Set WEAVE_URL and WEAVE_KEY in the environment.\");\n return 1;\n }\n\n // Config é opcional (só `dir`, default \"weave\"); ausente/ilegível → defaults.\n const configPath = path.resolve(cwd, args.config);\n let config: WeaveConfig = {};\n try {\n config = ((await load(configPath)).default ?? {}) as WeaveConfig;\n } catch {\n /* sem weave.config.ts — usa defaults */\n }\n const dirRel = config.dir ?? DEFAULT_DIR;\n const dir = path.resolve(cwd, dirRel);\n const entitiesDir = path.join(dir, \"entities\");\n const net = { url, key, ...(deps.fetch ? { fetch: deps.fetch } : {}) };\n const write = deps.write ?? defaultWrite;\n const clean = deps.clean ?? defaultClean;\n\n // Regenera a pasta weave/ a partir do server (overwrite cego). Usado pelo `gen`\n // e ao fim do `push` (re-sincroniza os $id recém-cunhados), salvo `--no-gen`.\n const regen = async (): Promise<void> => {\n const { files, entities, scopes } = await genProject(net);\n await clean(path.join(dir, \"entities\"));\n await clean(path.join(dir, \"scopes\"));\n for (const [rel, content] of Object.entries(files)) await write(path.join(dir, rel), content);\n log(`✓ generated ${entities.length} ${entities.length === 1 ? \"entity\" : \"entities\"}, ${scopes.length} ${scopes.length === 1 ? \"scope\" : \"scopes\"} → ${dirRel}/`);\n };\n\n // gen: server → pasta weave/ inteira (arquivos com $id, scopes resolvidos, barrels, client).\n if (args.command === \"gen\") {\n await regen();\n return 0;\n }\n\n // pull (legado): puxa os IRs remotos → escreve os arquivos de entidade (sem $id).\n if (args.command === \"pull\") {\n const { files, names } = await pullEntities(net);\n for (const [file, content] of Object.entries(files)) await write(path.join(entitiesDir, file), content);\n log(`✓ pulled ${names.length} ${names.length === 1 ? \"entity\" : \"entities\"} → ${dirRel}/entities`);\n return 0;\n }\n\n // push: tudo vai — entidades primeiro (cunham/fixam ids), depois scopes (resolvem\n // nome→id contra o server), e por fim o gen re-sincroniza os arquivos locais.\n const entities = await discoverEntities(entitiesDir, load);\n if (Object.keys(entities).length === 0) {\n log(`No entities found in ${dirRel}/entities.`);\n return 1;\n }\n\n let res = await pushEntities(entities, {\n ...net,\n confirm: args.confirm,\n fill: args.fill,\n renames: args.renames,\n });\n\n // `--confirm all`: coleta TODAS as remoções (risco confirm) do plano e re-empurra\n // já confirmadas. Não força ⛔ blocked nem preenche 🟡 needsValue (esse quer valor).\n if (args.confirmAll && res.review.some((r) => r.plan.changes.some((c) => c.risk === \"confirm\"))) {\n const confirm: Record<string, string[]> = { ...args.confirm };\n for (const r of res.review) {\n for (const c of r.plan.changes) {\n if (c.risk === \"confirm\") (confirm[r.name] ??= []).push(c.path);\n }\n }\n res = await pushEntities(entities, { ...net, confirm, fill: args.fill, renames: args.renames });\n }\n\n for (const n of res.applied) log(` 🟢 ${n} applied`);\n\n // Mostra CADA mudança gated com o caminho `entity.field` e a FLAG EXATA a usar.\n const cmds: string[] = [];\n let hasBlocked = false;\n let hasRemove = false;\n for (const r of res.review) {\n log(` ⚠ ${r.name} — needs review:`);\n for (const c of r.plan.changes) {\n const target = `${r.name}.${c.path}`;\n const flag =\n c.risk === \"confirm\" ? `--confirm ${target}` : c.risk === \"needsValue\" ? `--fill ${target}=<value>` : \"\";\n if (flag) cmds.push(flag);\n if (c.op === \"removeField\") hasRemove = true;\n if (c.risk === \"blocked\") hasBlocked = true;\n const hint =\n c.risk === \"confirm\"\n ? `deletes its data — allow with ${flag}`\n : c.risk === \"needsValue\"\n ? `needs a value — provide with ${flag}`\n : c.risk === \"blocked\"\n ? \"not supported by push — revert it, or fix the data in the browser\"\n : \"\";\n log(` ${riskIcon(c.risk)} ${c.op} ${target}${hint ? ` — ${hint}` : \"\"}`);\n }\n }\n if (res.review.length > 0) {\n if (cmds.length) {\n log(\"\");\n log(\"Re-run with the flag(s) above to apply — the target is always `entity.field`, e.g.:\");\n log(` weave push ${cmds.join(\" \")}`);\n if (cmds.filter((f) => f.startsWith(\"--confirm\")).length > 1) {\n log(\" (or weave push --confirm all to accept every drop at once)\");\n }\n }\n if (hasRemove) {\n log(\"\");\n log(\"Renamed, not removed? Use --rename entity.oldField=newField to keep the data.\");\n }\n if (hasBlocked) {\n log(\"\");\n log(\"⛔ Blocked changes can't be forced — they hold the whole entity. Revert them or fix the data first.\");\n }\n return 1;\n }\n log(`✓ pushed ${res.applied.length} ${res.applied.length === 1 ? \"entity\" : \"entities\"}.`);\n\n // Scopes (só depois das entidades aplicadas — o push resolve nome→id no server).\n const scopes = await discoverScopes(path.join(dir, \"scopes\"), load);\n if (Object.keys(scopes).length > 0) {\n const { pushed } = await pushScopes(scopes, net);\n log(`✓ pushed ${pushed.length} ${pushed.length === 1 ? \"scope\" : \"scopes\"}.`);\n }\n\n // Re-sincroniza os arquivos locais (ids recém-cunhados), salvo --no-gen.\n if (!args.noGen) await regen();\n return 0;\n}\n\nexport async function main(): Promise<void> {\n process.exit(await runCli(process.argv.slice(2)));\n}\n\n// Executado direto (bin) → roda o main. `realpathSync` resolve o symlink do bin\n// (node_modules/.bin/weave → dist/cli.js); sem isso, rodar via o symlink não bate\n// com `import.meta.url` (caminho real) e o main nunca rodava.\nif (process.argv[1]) {\n try {\n const invoked = pathToFileURL(realpathSync(process.argv[1])).href;\n if (invoked === import.meta.url) void main();\n } catch {\n /* não foi possível resolver o caminho — não é uma invocação direta */\n }\n}\n","import fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { pathToFileURL } from \"node:url\";\nimport type { Entity, ShapeRecord } from \"@mauroandre/weave-core\";\nimport type { ScopeDef } from \"./scope.js\";\n\n// Descoberta por pasta (file-based, igual o VeloJS acha rotas): cada arquivo é uma\n// entidade, exportada como `default`. Node-only (usa fs) — fora do barrel do SDK.\n\n/** Importa um módulo por caminho absoluto. Injetável (a CLI usa um loader de TS). */\nexport type ModuleLoader = (absPath: string) => Promise<{ default?: unknown }>;\n\nconst isEntity = (v: unknown): v is Entity<string, ShapeRecord> =>\n !!v && typeof v === \"object\" && \"name\" in v && \"columns\" in v;\n\n/**\n * Lê a pasta de entidades, importa o `default` de cada arquivo, e monta o objeto\n * `entities` chaveado pelo nome da entidade — o mesmo que o `pushEntities`/`createClient`\n * consomem. Ignora arquivos sem `export default defineEntity(...)`.\n */\nexport async function discoverEntities(\n entitiesDir: string,\n load: ModuleLoader = (p) => import(pathToFileURL(p).href),\n): Promise<Record<string, Entity<string, ShapeRecord>>> {\n const files = (await fs.readdir(entitiesDir))\n .filter((f) => /\\.(ts|tsx|mts|js|mjs)$/.test(f) && !f.endsWith(\".d.ts\"))\n .sort();\n\n const entities: Record<string, Entity<string, ShapeRecord>> = {};\n for (const f of files) {\n const mod = await load(path.resolve(entitiesDir, f));\n if (isEntity(mod.default)) entities[mod.default.name] = mod.default;\n }\n return entities;\n}\n\nconst isScope = (v: unknown): v is ScopeDef =>\n !!v && typeof v === \"object\" && \"name\" in v && \"entities\" in v && !(\"columns\" in v);\n\n/**\n * Lê a pasta de scopes (1 arquivo = 1 scope, `export default defineScope(...)`),\n * chaveando pelo nome do scope. Pasta ausente → `{}` (scopes são opcionais). O\n * barrel `index.ts` não tem default export, então é ignorado naturalmente.\n */\nexport async function discoverScopes(\n scopesDir: string,\n load: ModuleLoader = (p) => import(pathToFileURL(p).href),\n): Promise<Record<string, ScopeDef>> {\n let names: string[];\n try {\n names = await fs.readdir(scopesDir);\n } catch {\n return {}; // sem pasta de scopes\n }\n const files = names.filter((f) => /\\.(ts|tsx|mts|js|mjs)$/.test(f) && !f.endsWith(\".d.ts\")).sort();\n\n const scopes: Record<string, ScopeDef> = {};\n for (const f of files) {\n const mod = await load(path.resolve(scopesDir, f));\n if (isScope(mod.default)) scopes[mod.default.name] = mod.default;\n }\n return scopes;\n}\n"],"mappings":"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","names":["path","pathToFileURL","fs","path","entities","scopes","pathToFileURL"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { F as FetchLike } from './scope-
|
|
2
|
-
export { C as ClientOptions, E as EntityClient,
|
|
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
|
|
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:
|
|
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
|
|
117
|
-
const
|
|
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
|
|
131
|
-
|
|
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
|
|
140
|
-
|
|
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
|
|
143
|
-
await request("DELETE",
|
|
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,
|
|
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
|
-
*
|
|
22
|
-
* `
|
|
23
|
-
*
|
|
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 1º argumento cru do método.
|
|
24
24
|
*/
|
|
25
|
-
interface
|
|
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.
|
|
40
|
-
*
|
|
41
|
-
*
|
|
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
|
-
|
|
46
|
-
|
|
47
|
-
}): Promise<InferRead<E, X
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
|
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