diff --git a/README.md b/README.md index b90a498..080eb47 100644 --- a/README.md +++ b/README.md @@ -244,6 +244,29 @@ The module enable / disable hooks were always mutations, so nothing changes in y The gateway still answers the old `GET` on these paths for now (it logs a deprecation warning) and will drop it after gateway 0.2, so upgrade the core SDK before then. The scheduler switch moved to `PUT` earlier (core SDK 4.6.0). The code module (`hub.code`) has no hooks in this package yet; call `useNorbix().hub.code.enableCode(...)` directly. +### Logs (core SDK 4.31.0) + +Every `hub.logs` method has a hook, and `api.logs.writeLogs` writes your app's own lines. + +| Hook | SDK method | Cache | +| ----------------------------------- | --------------------------------- | ------------------------------ | +| `useGetLogsQuery` | `hub.logs.getLogs` | provides `Logs` | +| `useGetLogQuery` | `hub.logs.getLog` (`{ id }`) | provides `Logs` | +| `useGetLogsByCorrelationIdQuery` | `hub.logs.getLogsByCorrelationId` | provides `Logs` | +| `useCleanLogsMutation` | `hub.logs.cleanLogs` | invalidates `Logs` | +| `useGetLogSettingsQuery` | `hub.logs.getLogSettings` | provides `LogsSettings` | +| `useSaveLogSettingsMutation` | `hub.logs.saveLogSettings` | invalidates `LogsSettings` | +| `useTestLoggingIntegrationMutation` | `hub.logs.testLoggingIntegration` | invalidates `LogsIntegrations` | +| `useWriteLogsMutation` | `api.logs.writeLogs` | invalidates `Logs` | + +`useSaveLogSettingsMutation` changes only the settings you send; a field left out keeps its value. After +`useSaveLoggingIntegrationMutation`, call `useTestLoggingIntegrationMutation` with the integration id — +nothing is delivered to a logging integration until its last test passed. `useWriteLogsMutation` takes +`{ entries }` (1–100 lines: `title` required, `level`, `message`, `eventCode`, `correlationId`, +`timestamp`, `meta`); on a 429 `CM-ERRORS-LOGGING-003` its `error.retryAfterSeconds` says how many seconds +to wait before sending again. `useEndUserOwnRecordsReportMutation` (`hub.database.endUserOwnRecordsReport`, +new in the same core SDK) lists every schema's end-user-own-records switch, or turns it on with `dryRun: false`. + ### Bulk record writes (core SDK 4.11.0) `useUpdateManyRecordsMutation` / `useDeleteManyRecordsMutation` (Hub) and `useUpdateManyMutation` / `useDeleteManyMutation` (Api) take a new optional `allRecords` argument. An empty filter (`{}`, or no filter on update) matches the whole collection and the gateway refuses it with `CM-ERRORS-DATABASE-037` unless you pass `allRecords: true`: @@ -1161,7 +1184,7 @@ norbixApi.injectEndpoints({ prefix: 'Logging', tag: 'LogsIntegrations', namespace: (n) => n.hub.logs, - include: { test: false }, + include: { test: true }, }), }), overrideExisting: false, @@ -1179,7 +1202,7 @@ This produces all hooks automatically — `useGetEmailIntegrationsQuery`, `useSa | Payments | `Payments` | `n.hub.payments` | `PaymentIntegrations` | has `test` + `confirmHumanDelivery` | | Code | `Code` | `n.hub.code` | `CodeIntegrations` | has `test` | | Membership | `Membership` | `n.hub.membership` | `MembershipIntegrations` | no `test` | -| Logs | `Logging` _(uses `Logging`, not `Logs`)_ | `n.hub.logs` | `LogsIntegrations` | no `test` | +| Logs | `Logging` _(uses `Logging`, not `Logs`)_ | `n.hub.logs` | `LogsIntegrations` | has `test` (deliveries start after a passed test) | | Database | `Database` | `n.hub.database` | `DatabaseIntegrations` | already wired in package | **Trade-off.** The helper is terse but loses static request/response typing — generated hooks return `any` data, because endpoint keys are computed at runtime. If you need full types on a specific integration surface (autocompletion in your IDE), define those endpoints manually instead, following the canonical shape used by `hub.database` integrations in `src/hooks/hub/database.ts`. diff --git a/package-lock.json b/package-lock.json index e9f2370..8031a22 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,7 @@ "@commitlint/cli": "^21.2.2", "@commitlint/config-conventional": "^21.2.2", "@eslint/js": "^10.0.1", - "@norbix.ai/ts": "^4.29.0", + "@norbix.ai/ts": "^4.31.0", "@reduxjs/toolkit": "^2.12.0", "@semantic-release/commit-analyzer": "^13.0.1", "@semantic-release/github": "^12.0.9", @@ -45,7 +45,7 @@ "node": ">=18" }, "peerDependencies": { - "@norbix.ai/ts": ">=4.26.0", + "@norbix.ai/ts": ">=4.31.0", "@reduxjs/toolkit": ">=2.0.0", "react": ">=18.0.0", "react-redux": ">=9.0.0" @@ -1490,9 +1490,9 @@ } }, "node_modules/@norbix.ai/ts": { - "version": "4.29.0", - "resolved": "https://registry.npmjs.org/@norbix.ai/ts/-/ts-4.29.0.tgz", - "integrity": "sha512-3iKNGlLdDjoGhGcdom7Fm6i/8Rna7+oLBaBT4p7dvcYz+iDpvkpzSXpzvDpDkrsfWfWxumwb3nUgMocEmTULVQ==", + "version": "4.31.0", + "resolved": "https://registry.npmjs.org/@norbix.ai/ts/-/ts-4.31.0.tgz", + "integrity": "sha512-0nCic6VbLdmCDeJGhEwJaSo1O6Sj+p4M9xtww7Nbo/F1HDnqE7uBVL21MRoNCvZNpEui1fqgf0+j0vBGwIUfuw==", "dev": true, "license": "MIT", "engines": { @@ -1708,9 +1708,9 @@ } }, "node_modules/@oxc-project/types": { - "version": "0.152.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.152.0.tgz", - "integrity": "sha512-oM/5rLBm2tPkg0iBgkH/FOeR3PCDpY19GTgAZjMFM8h9WI9VW7cLgzp6nwtarYKmovavIQZ+Fe/RKX/8C8O/Rw==", + "version": "0.155.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.155.0.tgz", + "integrity": "sha512-SeE8JPwW1xs1tHeNycjUtUBdhorRa8o4jlvjwk49R7/Z291i+4DQa7JJIgOm/TaYQZx+QrPQbVJ/9c8dMIZN8Q==", "dev": true, "license": "MIT", "peer": true, @@ -1804,9 +1804,9 @@ } }, "node_modules/@rolldown/binding-android-arm-eabi": { - "version": "1.2.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.12.tgz", - "integrity": "sha512-dB/a1214qKfHMXCpgqR4OZT+jS4kTyEXbQGJPqzobt5EwH5rX080pxE37alt3RzvR1bf1Yz/yGqRfrYAxuPw0A==", + "version": "1.2.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.15.tgz", + "integrity": "sha512-aLcgK9gIJ3YRvrrtfSBbRa27F5WrpiTaPulnFpsHPxpUHgFlSgnU4854MHwoRGEBhfRJDWCS2WQ7lIjskE9Olw==", "cpu": [ "arm" ], @@ -1822,9 +1822,9 @@ } }, "node_modules/@rolldown/binding-android-arm64": { - "version": "1.2.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.12.tgz", - "integrity": "sha512-7KHFgQ5VJxIHcLlrwrc3Xbds7oTNQT7Pgi9gQCJKrd2VGab/UksIOYp6VD8MzCstGxOKMgNamPwUCfxPdP1OHg==", + "version": "1.2.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.15.tgz", + "integrity": "sha512-xD7DdZCrbThKuxNg7ooQmvp07ys4J/pnSEMTG3/OdOWl6daC67m/Q4fL78sRHMxTH/N4fGUbYhcPM0MF5WFHkg==", "cpu": [ "arm64" ], @@ -1840,9 +1840,9 @@ } }, "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.2.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.12.tgz", - "integrity": "sha512-3YIhqHD96nA5SaYNRBR16HnGv4oavZvXfD/ayHM+oYZ0WD/8lBAtf6zQua4kEyAvpqrluKXl0lnOBoiNby7x9w==", + "version": "1.2.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.15.tgz", + "integrity": "sha512-m2WuQ6r0B4OTYxyPpNLtXaMRbRDUbG8YQV2Q3KTdSSK8Th6KHCnC8jVCJt8bvYeSB5WivkkNbc85mKadaSdOxA==", "cpu": [ "arm64" ], @@ -1858,9 +1858,9 @@ } }, "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.2.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.12.tgz", - "integrity": "sha512-UuuJ35MFw4gmFOrE9pEqIV+K3syIKveph+Qc1/ljHZVdoDW4pz/JHR/eMVom+TZGl/5OOvGJOWaOCVt3ZfqhxA==", + "version": "1.2.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.15.tgz", + "integrity": "sha512-5RtnsHM36nodM9WbMy+zip0LGS/G+y5wro5RHrpOlr4ac24+1zSi9kiV2MP1myZeZ/ifEHfulQcWjnAXzc2C2A==", "cpu": [ "x64" ], @@ -1876,9 +1876,9 @@ } }, "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.2.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.12.tgz", - "integrity": "sha512-uMvssit0a4W+/7D8CbHUvG719mH3R2jwXAlh/XcPvuHTE0g++LymF88DCGNX0HM2rBOn0xrzgXktIB6fLSJBTQ==", + "version": "1.2.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.15.tgz", + "integrity": "sha512-g3l09pyFqhu+135JKkbkwiKGQmwU5hUMUPJ4VdBADvuqR8d33L6H578rh8m7rtxbdp+VmX/+nzI7nvhSZlzpUQ==", "cpu": [ "x64" ], @@ -1894,9 +1894,9 @@ } }, "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.2.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.12.tgz", - "integrity": "sha512-XcFu0R0xWnwzSf4IQgFH1rJIckPN1pLy2R+4r9IDB7Yfu/ys9cVqfa4pBrMHj7a3gl8mIR4nRNPg0e5IvEVs6g==", + "version": "1.2.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.15.tgz", + "integrity": "sha512-nIe3VugI97nGQtspPxDqA4sXMUpeiocKIP/CqpY/u/vgbT+alX+cVxu1C/oVBPKg4feNTmYovE91v4DgXvXNwQ==", "cpu": [ "arm" ], @@ -1912,9 +1912,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.2.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.12.tgz", - "integrity": "sha512-260UrKgn8tz39ak+SMDOirKzr7V04M9dWPw5llW00SwBivCZoWcRBKV1d8cXnRkUmSZA3BdiUmBHWk7734Ulpw==", + "version": "1.2.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.15.tgz", + "integrity": "sha512-iTBjXx+B5QSvgI89lJfmqh4wTh8NabR1YZIIN3hNx4zSG0EVfVqkOvLulNpG4251pbd6/IAmZ/LefVk1Y4rPhw==", "cpu": [ "arm64" ], @@ -1933,9 +1933,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.2.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.12.tgz", - "integrity": "sha512-5YK1I9SqDkbPgc1IA8BgDl34suqUS2q0KWnBrirm0E51YjOs6eo6dV6jbQfNE/argHRSvd0QUGgtpIoYx+WWpw==", + "version": "1.2.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.15.tgz", + "integrity": "sha512-uN26XOnVR7rqQupG3V9oAG2EGGatWjcJHOeDOHUFCh4TBWmP2tHTFoVN13aYbwfm0aHqJAEmcwhAu6sooIjfqQ==", "cpu": [ "arm64" ], @@ -1954,9 +1954,9 @@ } }, "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.2.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.12.tgz", - "integrity": "sha512-Rkcrmp7eFRg74yL5fXEU91JEWbdEPLevWwGtXpmhbjlD1StScbWTmO94Bhly+Mo+ketKYkdmM1vNUKeWSlx8cQ==", + "version": "1.2.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.15.tgz", + "integrity": "sha512-9nWkcdd+H+XhaA3fQIwpHnKIrfx/PQ6rO0pivr78Og07tXlUlctBBJoEYLPfhTmCR/QxEVdn6uPjHV5SymPhdw==", "cpu": [ "ppc64" ], @@ -1975,9 +1975,9 @@ } }, "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.2.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.12.tgz", - "integrity": "sha512-qvK4DuAsQc2BSjlx+Xr+IzOIvvxbGZqxFwdWfG6F518Erj0GGISyQbJ6pIappnOxlNPzNHvo/L0BwB30GZ+zVw==", + "version": "1.2.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.15.tgz", + "integrity": "sha512-Ggh0ciFjLwIusLSLz7LZYEaGdxOAUJXI+Qu5JCbhPJJHEt8vP+IjRATTJheiJsdzzTYgUiHUiw3X1Ea3ZpMBdA==", "cpu": [ "s390x" ], @@ -1996,9 +1996,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.2.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.12.tgz", - "integrity": "sha512-Q9uLBO53Xd4QIq1WOycVQyPP1O4HhraEV2qqb3uTrnVw6QZih9duY4vNXOivL1xoUS1/z+W8eF4NMfl2a8Sdjw==", + "version": "1.2.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.15.tgz", + "integrity": "sha512-FHg60rS6DBePAYZ9X9F67x+JObFdmgn6tupA1C/a3B+CufFmTD4mX+e8M75tCiAO3foR/6xwVBI0KQiI7sn/rw==", "cpu": [ "x64" ], @@ -2017,9 +2017,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.2.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.12.tgz", - "integrity": "sha512-3IBxWFMjbOZskDPKv8Lf9BCnahlKuHthWkYnyIxOH/QcJrFcS4EmcenthApkwr/5+nEqZlLzeYbxeMaX7A5u4g==", + "version": "1.2.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.15.tgz", + "integrity": "sha512-/cvV/7v5VGz/moiz0XKRj2dE8DzsKyhIu1KpOvJGPjRywg3yscON889UGvAe8zfiJLHup7VLjBduoGWi3HusWQ==", "cpu": [ "x64" ], @@ -2038,9 +2038,9 @@ } }, "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.2.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.12.tgz", - "integrity": "sha512-xtX61xg4LKPkPWilZU1ynKClz5Gj4bf74LML4r3eVLWumKnGjoEr1OSHQhMdbBDoYTi+yjrujvpZe2pUnqCrrA==", + "version": "1.2.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.15.tgz", + "integrity": "sha512-MyzC5Hrv/sm+Z7TSGKP/j8001RO2xQm1zOpCNVL4j0cY/VhBZviludA2YFQ7i4lH6ZAXxQm0lcMdBzM+99mE7w==", "cpu": [ "arm64" ], @@ -2056,9 +2056,9 @@ } }, "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.2.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.12.tgz", - "integrity": "sha512-At7fPB6PCaIjzgIhEZFxuT+BBFqiQibJDT4d3PhiR3f4E7bbMZF4aKblbFfEM3sETRDd1YiQx/+U/g/B/ou5Ew==", + "version": "1.2.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.15.tgz", + "integrity": "sha512-+PgYiS+AH5omy6L45ZLxybasauJaj3M8buoT83D+nzPmCayXlxUV9hydhciCKVyIg0B/v0BtXPDE1uGqNwCPpg==", "cpu": [ "arm64" ], @@ -2074,9 +2074,9 @@ } }, "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.2.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.12.tgz", - "integrity": "sha512-WIw2haVKwjuYdXkHaoC0mF8Le71TuCBxjrdKqLbJGctbBABj+ClfmNvtbOnzpq3RokNo5+V1qhtSzJyXorsklQ==", + "version": "1.2.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.15.tgz", + "integrity": "sha512-HFZd2QNQYn1JbIi+Hpd7H+r/lkWxcfIu28nirZ6eCn2LafuvmExMlb/P96Sfx5tf7pPArw39IdhmCF8vmV+NhQ==", "cpu": [ "x64" ], @@ -6894,9 +6894,9 @@ } }, "node_modules/nanoid": { - "version": "3.3.19", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.19.tgz", - "integrity": "sha512-Y2tUNy4ouw6tq5oDSKeQYGOyhkUBhNOcGV/02KC+6kd9eDGqdZd++mjMiIDilrBYvjEnCYvVtsuHCuP+okSfug==", + "version": "3.3.20", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.20.tgz", + "integrity": "sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ==", "dev": true, "funding": [ { @@ -9361,9 +9361,9 @@ } }, "node_modules/postcss": { - "version": "8.5.28", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.28.tgz", - "integrity": "sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==", + "version": "8.5.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.31.tgz", + "integrity": "sha512-YUbWYCgErwnMAVkjUnYa+KlMBzGpHwiIt/DE7f0G33AuX2LH9UcTk6KgWxd3zRU+pN3u/C+Xl3EnMLM4Hea7Sg==", "dev": true, "funding": [ { @@ -9382,9 +9382,9 @@ "license": "MIT", "peer": true, "dependencies": { - "nanoid": "^3.3.18", + "nanoid": "^3.3.20", "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" + "source-map-js": "^1.2.2" }, "engines": { "node": "^10 || ^12 || >=14" @@ -9936,14 +9936,14 @@ } }, "node_modules/rolldown": { - "version": "1.2.12", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.12.tgz", - "integrity": "sha512-8wafseiaG80xmXSfqidUNqZcylTlhmPZZt+za2m+js2sFZ8dTNlhIOV2WcbIPx2hgwPBJpEUGFAMZ9bgBBLTSQ==", + "version": "1.2.15", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.15.tgz", + "integrity": "sha512-6odND+fel5zVw3I4Yc2e69gvguDnX0CAAGiNDZSq4ik5WUcaLrkD2GLuog0XcODbor31hZ5ayEMgVDno+KPV5g==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "@oxc-project/types": "=0.152.0", + "@oxc-project/types": "=0.155.0", "@rolldown/pluginutils": "^1.0.0" }, "bin": { @@ -9953,21 +9953,21 @@ "node": "^20.19.0 || >=22.12.0" }, "optionalDependencies": { - "@rolldown/binding-android-arm-eabi": "1.2.12", - "@rolldown/binding-android-arm64": "1.2.12", - "@rolldown/binding-darwin-arm64": "1.2.12", - "@rolldown/binding-darwin-x64": "1.2.12", - "@rolldown/binding-freebsd-x64": "1.2.12", - "@rolldown/binding-linux-arm-gnueabihf": "1.2.12", - "@rolldown/binding-linux-arm64-gnu": "1.2.12", - "@rolldown/binding-linux-arm64-musl": "1.2.12", - "@rolldown/binding-linux-ppc64-gnu": "1.2.12", - "@rolldown/binding-linux-s390x-gnu": "1.2.12", - "@rolldown/binding-linux-x64-gnu": "1.2.12", - "@rolldown/binding-linux-x64-musl": "1.2.12", - "@rolldown/binding-openharmony-arm64": "1.2.12", - "@rolldown/binding-win32-arm64-msvc": "1.2.12", - "@rolldown/binding-win32-x64-msvc": "1.2.12" + "@rolldown/binding-android-arm-eabi": "1.2.15", + "@rolldown/binding-android-arm64": "1.2.15", + "@rolldown/binding-darwin-arm64": "1.2.15", + "@rolldown/binding-darwin-x64": "1.2.15", + "@rolldown/binding-freebsd-x64": "1.2.15", + "@rolldown/binding-linux-arm-gnueabihf": "1.2.15", + "@rolldown/binding-linux-arm64-gnu": "1.2.15", + "@rolldown/binding-linux-arm64-musl": "1.2.15", + "@rolldown/binding-linux-ppc64-gnu": "1.2.15", + "@rolldown/binding-linux-s390x-gnu": "1.2.15", + "@rolldown/binding-linux-x64-gnu": "1.2.15", + "@rolldown/binding-linux-x64-musl": "1.2.15", + "@rolldown/binding-openharmony-arm64": "1.2.15", + "@rolldown/binding-win32-arm64-msvc": "1.2.15", + "@rolldown/binding-win32-x64-msvc": "1.2.15" } }, "node_modules/rollup": { @@ -11152,17 +11152,17 @@ } }, "node_modules/vite": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.3.2.tgz", - "integrity": "sha512-SQr1x6W5vVSbROg7vsyXIaxK9b0G7zsT68acdWWRmnBUsgDieLCRG+Rep9WdZgcposvv/GSnr4GUUBqB3vXq6w==", + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.3.5.tgz", + "integrity": "sha512-z1YpjieJ9g0ebTmQpLBv/oq9E3F9EIlOe+7LahW8SrE3LTiG3udBRUXQOlYXP9b+IC2q2phHeVxj9xGlw34OEQ==", "dev": true, "license": "MIT", "peer": true, "dependencies": { "lightningcss": "^1.33.0", "picomatch": "^4.0.7", - "postcss": "^8.5.28", - "rolldown": "~1.2.11", + "postcss": "^8.5.30", + "rolldown": "~1.2.14", "tinyglobby": "^0.2.17" }, "bin": { @@ -11179,7 +11179,7 @@ }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", - "@vitejs/devtools": "^0.7.1", + "@vitejs/devtools": "^0.7.1 || ^0.8.0", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", diff --git a/package.json b/package.json index 15ff60f..1dff8d6 100644 --- a/package.json +++ b/package.json @@ -59,7 +59,7 @@ "prepublishOnly": "npm run build" }, "peerDependencies": { - "@norbix.ai/ts": ">=4.29.0", + "@norbix.ai/ts": ">=4.31.0", "@reduxjs/toolkit": ">=2.0.0", "react": ">=18.0.0", "react-redux": ">=9.0.0" @@ -68,7 +68,7 @@ "@commitlint/cli": "^21.2.2", "@commitlint/config-conventional": "^21.2.2", "@eslint/js": "^10.0.1", - "@norbix.ai/ts": "^4.29.0", + "@norbix.ai/ts": "^4.31.0", "@reduxjs/toolkit": "^2.12.0", "@semantic-release/commit-analyzer": "^13.0.1", "@semantic-release/github": "^12.0.9", diff --git a/src/createNorbixApi.ts b/src/createNorbixApi.ts index 4c923bd..3e9ee78 100644 --- a/src/createNorbixApi.ts +++ b/src/createNorbixApi.ts @@ -115,6 +115,7 @@ export function createNorbixApi( 'Scheduler', 'Triggers', 'Logs', + 'LogsSettings', 'LogsIntegrations', ] satisfies AllTags[], endpoints: (builder) => buildEndpoints(builder), diff --git a/src/errors.ts b/src/errors.ts index c3f8661..45dcafa 100644 --- a/src/errors.ts +++ b/src/errors.ts @@ -37,6 +37,11 @@ export interface SerializedNorbixError { errors: SerializedNorbixErrorItem[]; /** Originating URL when known, useful for debugging. */ url?: string; + /** + * Seconds to wait before sending again, from the answer's `Retry-After` + * header (e.g. a 429 from `writeLogs`). Only when the gateway sent one. + */ + retryAfterSeconds?: number; } /** @@ -57,6 +62,7 @@ export function serializeNorbixError(err: unknown): SerializedNorbixError { fieldErrors?: SerializedNorbixErrorItem[]; errors?: SerializedNorbixErrorItem[]; url?: string; + retryAfterSeconds?: number; }; const status = typeof e.status === 'number' ? e.status : typeof e.httpStatus === 'number' ? e.httpStatus : 0; @@ -75,6 +81,9 @@ export function serializeNorbixError(err: unknown): SerializedNorbixError { fieldErrors: list, errors: list, url: e.url, + ...(typeof e.retryAfterSeconds === 'number' + ? { retryAfterSeconds: e.retryAfterSeconds } + : {}), }; } const message = typeof err === 'string' ? err : 'Unknown Norbix error'; diff --git a/src/hooks/api/logs.ts b/src/hooks/api/logs.ts new file mode 100644 index 0000000..06c9b4f --- /dev/null +++ b/src/hooks/api/logs.ts @@ -0,0 +1,23 @@ +import type { Norbix } from '@norbix.ai/ts'; + +import type { Builder } from '../index.js'; +import type { Arg, Result } from '../types.js'; + +type WriteLogs = Norbix['api']['logs']['writeLogs']; + +/** + * `api.logs` — your app's own log lines (API host), 1:1 with the core SDK. + * + * `writeLogs({ entries })` sends 1–100 lines; the answer `{ accepted }` comes + * at once (the lines are queued). A 429 `CM-ERRORS-LOGGING-003` means the + * project's per-minute quota is used up: the hook's `error` carries + * `retryAfterSeconds` (the `Retry-After` header). + * + * Tags: invalidates `Logs`, so an open log list refetches. + */ +export const apiLogs = (b: Builder) => ({ + writeLogs: b.mutation, Arg>({ + query: (args) => (norbix) => norbix.api.logs.writeLogs(args), + invalidatesTags: ['Logs'], + }), +}); diff --git a/src/hooks/hub/database.ts b/src/hooks/hub/database.ts index 7ee9016..02b09c1 100644 --- a/src/hooks/hub/database.ts +++ b/src/hooks/hub/database.ts @@ -1,4 +1,4 @@ -// AUTO-GENERATED — full coverage of `norbix.hub.database` (73 endpoints). +// AUTO-GENERATED — full coverage of `norbix.hub.database` (74 endpoints). // Synced from the norbix core SDK surface. Re-run the hook sync to refresh. import type { Norbix } from '@norbix.ai/ts'; @@ -34,6 +34,7 @@ type RenameDatabaseSchema = Norbix['hub']['database']['renameDatabaseSchema']; type SaveDatabaseSchema = Norbix['hub']['database']['saveDatabaseSchema']; type UpdateDatabaseSchemaDraft = Norbix['hub']['database']['updateDatabaseSchemaDraft']; type UpdateDatabaseSchemaSettings = Norbix['hub']['database']['updateDatabaseSchemaSettings']; +type EndUserOwnRecordsReport = Norbix['hub']['database']['endUserOwnRecordsReport']; type DeleteDatabaseIntegration = Norbix['hub']['database']['deleteDatabaseIntegration']; type DisableDatabaseIntegration = Norbix['hub']['database']['disableDatabaseIntegration']; type EnableDatabaseIntegration = Norbix['hub']['database']['enableDatabaseIntegration']; @@ -563,4 +564,14 @@ export const hubDatabase = (b: Builder) => ({ requestImportUploadUrl: b.mutation, Arg>({ query: (args) => (norbix) => norbix.hub.database.requestImportUploadUrl(args), }), + + // Lists every schema's endUserOwnRecords switch; with dryRun: false it turns + // the switch on for the schemas named in `enable`, so it is a mutation. + endUserOwnRecordsReport: b.mutation< + Result, + Arg + >({ + query: (args) => (norbix) => norbix.hub.database.endUserOwnRecordsReport(args), + invalidatesTags: ['DatabaseSchemas'], + }), }); diff --git a/src/hooks/hub/logs.ts b/src/hooks/hub/logs.ts index 4556a77..5f99127 100644 --- a/src/hooks/hub/logs.ts +++ b/src/hooks/hub/logs.ts @@ -1,5 +1,6 @@ -// AUTO-GENERATED — full coverage of `norbix.hub.logs` (10 endpoints). -// Synced from the norbix core SDK surface. Re-run the hook sync to refresh. +// Full coverage of `norbix.hub.logs` (15 endpoints), 1:1 with the core SDK. +// Written by hand: this repo has no hook sync script (the header used to say +// "re-run the hook sync"; there is none — add a row here for a new SDK method). import type { Norbix } from '@norbix.ai/ts'; import type { Builder } from '../index.js'; @@ -15,9 +16,21 @@ type GetLoggingIntegrations = Norbix['hub']['logs']['getLoggingIntegrations']; type SaveLoggingIntegration = Norbix['hub']['logs']['saveLoggingIntegration']; type TestLoggingIntegration = Norbix['hub']['logs']['testLoggingIntegration']; type GetLogsByCorrelationId = Norbix['hub']['logs']['getLogsByCorrelationId']; +type GetLogs = Norbix['hub']['logs']['getLogs']; +type GetLog = Norbix['hub']['logs']['getLog']; +type CleanLogs = Norbix['hub']['logs']['cleanLogs']; +type GetLogSettings = Norbix['hub']['logs']['getLogSettings']; +type SaveLogSettings = Norbix['hub']['logs']['saveLogSettings']; /** - * `hub.logs` — 10 endpoints, 1:1 with the core SDK. + * `hub.logs` — 15 endpoints, 1:1 with the core SDK. + * + * Tags: + * - `Logs` — log entries and the module switch. + * - `LogsSettings` — the project's log settings. + * - `LogsIntegrations` — logging integrations. Nothing is delivered to an + * integration until its last test passed: call `testLoggingIntegration` + * after every `saveLoggingIntegration`. */ export const hubLogs = (b: Builder) => ({ disableLogging: b.mutation, Arg>({ @@ -30,17 +43,26 @@ export const hubLogs = (b: Builder) => ({ invalidatesTags: ['Logs'], }), - deleteLoggingIntegration: b.mutation, Arg>({ + deleteLoggingIntegration: b.mutation< + Result, + Arg + >({ query: (args) => (norbix) => norbix.hub.logs.deleteLoggingIntegration(args), invalidatesTags: ['LogsIntegrations'], }), - disableLoggingIntegration: b.mutation, Arg>({ + disableLoggingIntegration: b.mutation< + Result, + Arg + >({ query: (args) => (norbix) => norbix.hub.logs.disableLoggingIntegration(args), invalidatesTags: ['LogsIntegrations'], }), - enableLoggingIntegration: b.mutation, Arg>({ + enableLoggingIntegration: b.mutation< + Result, + Arg + >({ query: (args) => (norbix) => norbix.hub.logs.enableLoggingIntegration(args), invalidatesTags: ['LogsIntegrations'], }), @@ -69,4 +91,32 @@ export const hubLogs = (b: Builder) => ({ query: (args) => (norbix) => norbix.hub.logs.getLogsByCorrelationId(args), providesTags: ['Logs'], }), + + getLogs: b.query, Arg>({ + query: (args) => (norbix) => norbix.hub.logs.getLogs(args), + providesTags: ['Logs'], + }), + + /** Arg: `{ id }` — the id of a `getLogs` row. */ + getLog: b.query, Arg>({ + query: (args) => (norbix) => norbix.hub.logs.getLog(args), + providesTags: ['Logs'], + }), + + /** Deletes every entry stored in the project's Norbix Logging. */ + cleanLogs: b.mutation, Arg>({ + query: (args) => (norbix) => norbix.hub.logs.cleanLogs(args), + invalidatesTags: ['Logs'], + }), + + getLogSettings: b.query, Arg>({ + query: (args) => (norbix) => norbix.hub.logs.getLogSettings(args), + providesTags: ['LogsSettings'], + }), + + /** Only the settings you send change; a field left out keeps its value. */ + saveLogSettings: b.mutation, Arg>({ + query: (args) => (norbix) => norbix.hub.logs.saveLogSettings(args), + invalidatesTags: ['LogsSettings'], + }), }); diff --git a/src/hooks/index.ts b/src/hooks/index.ts index 384f39c..1f5fd40 100644 --- a/src/hooks/index.ts +++ b/src/hooks/index.ts @@ -8,6 +8,7 @@ import { apiApikeys } from './api/apikeys.js'; import { apiAuth } from './api/auth.js'; import { apiDatabase } from './api/database.js'; import { apiFiles } from './api/files.js'; +import { apiLogs } from './api/logs.js'; import { apiMembership } from './api/membership.js'; import { apiPayments } from './api/payments.js'; import { apiPublic } from './api/public.js'; @@ -116,6 +117,7 @@ export type AllTags = | 'Scheduler' | 'Triggers' | 'Logs' + | 'LogsSettings' | 'LogsIntegrations'; /** Builder type the per-module endpoint factories accept. */ @@ -145,6 +147,7 @@ export function buildEndpoints(builder: Builder) { ...apiDatabase(builder), ...apiApikeys(builder), ...apiFiles(builder), + ...apiLogs(builder), ...apiPayments(builder), ...apiPublic(builder), ...apiPush(builder), diff --git a/tests/database.test.ts b/tests/database.test.ts index 25c2c1d..cdc5ec6 100644 --- a/tests/database.test.ts +++ b/tests/database.test.ts @@ -75,9 +75,9 @@ const RECORD_WRITES = [ describe('hubDatabase', () => { const endpoints = hubDatabase(fakeBuilder()) as unknown as Record; - it('has one hook per hub.database method of @norbix.ai/ts, same name (74)', () => { + it('has one hook per hub.database method of @norbix.ai/ts, same name (75)', () => { const methods = sdkMethods('hub'); - expect(methods).toHaveLength(74); + expect(methods).toHaveLength(75); expect(Object.keys(endpoints).sort()).toEqual(methods); }); @@ -262,7 +262,7 @@ describe('database hooks in the flat endpoint map', () => { await all[key].query({})(viaFlatMap.client); } expect(viaFlatMap.calls).toEqual(viaFactory.calls); - expect(viaFlatMap.calls.filter((c) => c.method.startsWith('hub.'))).toHaveLength(74); + expect(viaFlatMap.calls.filter((c) => c.method.startsWith('hub.'))).toHaveLength(75); }); }); diff --git a/tests/logs.test.ts b/tests/logs.test.ts new file mode 100644 index 0000000..cd32c2e --- /dev/null +++ b/tests/logs.test.ts @@ -0,0 +1,201 @@ +import { Norbix } from '@norbix.ai/ts'; +import { describe, expect, it, vi } from 'vitest'; + +import { createNorbixBaseQuery } from '../src/baseQuery.js'; +import { apiLogs } from '../src/hooks/api/logs.js'; +import { hubLogs } from '../src/hooks/hub/logs.js'; + +/** + * The logs hooks — every `norbix.hub.logs` method (15) and `norbix.api.logs` + * writeLogs. The hooks are a 1:1 wrapper: each reaches its SDK method with the + * caller's arguments, is a query or a mutation, and carries its cache tags. + * The last describe drives the real `@norbix.ai/ts` client with a fake fetch — + * never a real server. + */ +function fakeBuilder() { + return { + query: (def: Record) => ({ ...def, __kind: 'query' }), + mutation: (def: Record) => ({ ...def, __kind: 'mutation' }), + } as never; +} + +type Def = { + __kind: 'query' | 'mutation'; + query: (args: unknown) => (norbix: unknown) => unknown; + providesTags?: unknown[]; + invalidatesTags?: unknown[]; +}; + +/** hook name → [kind, tags it provides (query) or invalidates (mutation)]. */ +const HUB: Record = { + disableLogging: ['mutation', ['Logs']], + enableLogging: ['mutation', ['Logs']], + deleteLoggingIntegration: ['mutation', ['LogsIntegrations']], + disableLoggingIntegration: ['mutation', ['LogsIntegrations']], + enableLoggingIntegration: ['mutation', ['LogsIntegrations']], + getLoggingIntegration: ['query', ['LogsIntegrations']], + getLoggingIntegrations: ['query', ['LogsIntegrations']], + saveLoggingIntegration: ['mutation', ['LogsIntegrations']], + testLoggingIntegration: ['mutation', ['LogsIntegrations']], + getLogsByCorrelationId: ['query', ['Logs']], + getLogs: ['query', ['Logs']], + getLog: ['query', ['Logs']], + cleanLogs: ['mutation', ['Logs']], + getLogSettings: ['query', ['LogsSettings']], + saveLogSettings: ['mutation', ['LogsSettings']], +}; +const NAMES = Object.keys(HUB); + +const hub = hubLogs(fakeBuilder()) as unknown as Record; +const api = apiLogs(fakeBuilder()) as unknown as Record; + +function fakeNorbix() { + const calls: Array<{ method: string; args: unknown }> = []; + const record = (method: string) => (args: unknown) => { + calls.push({ method, args }); + return Promise.resolve({ ok: true }); + }; + const logs = Object.fromEntries([...NAMES, 'writeLogs'].map((n) => [n, record(n)])); + return { calls, client: { hub: { logs }, api: { logs } } }; +} + +describe('hubLogs', () => { + it('covers all 15 logs endpoints of the core SDK', () => { + expect(Object.keys(hub).sort()).toEqual([...NAMES].sort()); + const sdk = Object.keys(new Norbix({ apiKey: 'k', projectId: 'p' }).hub.logs) + .filter((k) => k !== 'transport') + .sort(); + expect(sdk).toEqual([...NAMES].sort()); + }); + + it.each(NAMES)('%s reaches the matching SDK method with the caller args', async (key) => { + const { calls, client } = fakeNorbix(); + const args = { id: 'log_1' }; + await hub[key]!.query(args)(client); + expect(calls).toEqual([{ method: key, args }]); + }); + + it.each(NAMES)('%s is a %s with its tags', (key) => { + const [kind, tags] = HUB[key]!; + expect(hub[key]!.__kind).toBe(kind); + expect(kind === 'query' ? hub[key]!.providesTags : hub[key]!.invalidatesTags).toEqual(tags); + }); +}); + +describe('apiLogs', () => { + it('writeLogs is a mutation on norbix.api.logs that invalidates Logs', async () => { + const { calls, client } = fakeNorbix(); + const args = { entries: [{ title: 'Order paid' }] }; + await api['writeLogs']!.query(args)(client); + expect(calls).toEqual([{ method: 'writeLogs', args }]); + expect(api['writeLogs']!.__kind).toBe('mutation'); + expect(api['writeLogs']!.invalidatesTags).toEqual(['Logs']); + }); +}); + +/** + * Through the real `@norbix.ai/ts` client and this package's base query with a + * fake fetch: verb, host, route and body. + */ +describe('logs hooks through the real SDK (fake fetch)', () => { + function run( + def: Def, + args: unknown, + answer: { status: number; body: unknown; headers?: Record } = { + status: 200, + body: { responseStatus: { isSuccess: true } }, + }, + ) { + type FetchFn = typeof globalThis.fetch; + const seen: Array<{ url: string; method: string; body: string | undefined }> = []; + const fetchImpl = vi.fn(async (...[input, init]: Parameters) => { + seen.push({ + url: String(input), + method: (init?.method ?? 'GET').toUpperCase(), + body: typeof init?.body === 'string' ? init.body : undefined, + }); + return new globalThis.Response(JSON.stringify(answer.body), { + status: answer.status, + headers: { 'Content-Type': 'application/json', ...answer.headers }, + }); + }) as unknown as FetchFn; + const norbix = new Norbix({ + bearerToken: 'test-token', + projectId: 'test-project', + apiVersion: 'v2', + hubVersion: 'v2', + baseUrl: { api: 'https://api.norbix.io', hub: 'https://hub.norbix.io' }, + fetch: fetchImpl, + }); + const baseQuery = createNorbixBaseQuery(() => norbix); + const result = baseQuery(def.query(args) as never, {} as never, {}) as Promise<{ + data?: unknown; + error?: Record; + }>; + return { seen, result }; + } + + it('getLog sends GET /v2/logs/ on the Hub', async () => { + const { seen, result } = run(hub['getLog']!, { id: '66b2f0a1c3d4e5f6a7b8c9d0' }); + expect((await result).error).toBeUndefined(); + expect(seen[0]!.method).toBe('GET'); + expect(seen[0]!.url).toBe('https://hub.norbix.io/v2/logs/66b2f0a1c3d4e5f6a7b8c9d0'); + }); + + it('saveLogSettings sends only the given setting', async () => { + const { seen, result } = run(hub['saveLogSettings']!, { aiChatLoggingEnabled: true }); + expect((await result).error).toBeUndefined(); + expect(seen[0]!.method).toBe('POST'); + expect(seen[0]!.url).toBe('https://hub.norbix.io/v2/logs/settings'); + expect(JSON.parse(seen[0]!.body!)).toEqual({ aiChatLoggingEnabled: true }); + }); + + it('cleanLogs sends POST /v2/logs/clean', async () => { + const { seen, result } = run(hub['cleanLogs']!, {}); + expect((await result).error).toBeUndefined(); + expect(seen[0]!.method).toBe('POST'); + expect(seen[0]!.url).toBe('https://hub.norbix.io/v2/logs/clean'); + }); + + it('writeLogs sends POST /v2/logs on the Api host with the entries', async () => { + const entries = [ + { + title: 'Order paid', + level: 'information', + eventCode: 'order:paid', + meta: { orderId: 'o1' }, + }, + ]; + const { seen, result } = run( + api['writeLogs']!, + { entries }, + { status: 200, body: { accepted: 1 } }, + ); + expect((await result).data).toEqual({ accepted: 1 }); + expect(seen[0]!.method).toBe('POST'); + expect(seen[0]!.url).toBe('https://api.norbix.io/v2/logs'); + expect(JSON.parse(seen[0]!.body!)).toEqual({ entries }); + }); + + it('writeLogs on a 429 gives an error with retryAfterSeconds', async () => { + const { seen, result } = run( + api['writeLogs']!, + { entries: [{ title: 'x' }] }, + { + status: 429, + body: { + responseStatus: { + isSuccess: false, + errors: [{ errorCode: 'CM-ERRORS-LOGGING-003', message: 'Log quota used up' }], + }, + }, + headers: { 'Retry-After': '12' }, + }, + ); + const { error } = await result; + expect(seen).toHaveLength(1); + expect(error?.['httpStatus']).toBe(429); + expect(error?.['errorCode']).toBe('CM-ERRORS-LOGGING-003'); + expect(error?.['retryAfterSeconds']).toBe(12); + }); +});