@harperfast/harper 5.2.0-alpha.5 → 5.2.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/agent/agent.ts +152 -10
- package/agent/bestPractices.ts +58 -0
- package/agent/mcpTools.ts +122 -0
- package/agent/operations.ts +10 -1
- package/agent/registryTools.ts +117 -0
- package/agent/session.ts +16 -7
- package/agent/tools/fsTools.ts +74 -27
- package/agent/tools/inspectorTool.ts +459 -0
- package/agent/toolset.ts +27 -7
- package/agent/types.ts +2 -0
- package/components/Application.ts +566 -85
- package/components/OptionsWatcher.ts +62 -2
- package/components/Scope.ts +31 -8
- package/components/componentLoader.ts +5 -1
- package/components/componentSecrets.ts +514 -55
- package/components/deploymentRecorder.ts +7 -6
- package/components/gitCredentialHelper.js +115 -0
- package/components/gitCredentialServer.ts +251 -0
- package/components/mcp/tools/application.ts +197 -23
- package/components/mcp/tools/operations.ts +1 -1
- package/components/mcp/tools/schemas/derive.ts +6 -2
- package/components/operations.js +21 -19
- package/components/operationsValidation.js +86 -21
- package/components/secretOperations.ts +110 -30
- package/config/harperConfigEnvVars.ts +90 -5
- package/config-root.schema.json +4 -0
- package/dataLayer/harperBridge/ResourceBridge.ts +7 -0
- package/dist/agent/agent.d.ts +24 -3
- package/dist/agent/agent.js +172 -10
- package/dist/agent/agent.js.map +1 -1
- package/dist/agent/bestPractices.d.ts +24 -0
- package/dist/agent/bestPractices.js +60 -0
- package/dist/agent/bestPractices.js.map +1 -0
- package/dist/agent/mcpTools.d.ts +24 -0
- package/dist/agent/mcpTools.js +115 -0
- package/dist/agent/mcpTools.js.map +1 -0
- package/dist/agent/operations.js +10 -1
- package/dist/agent/operations.js.map +1 -1
- package/dist/agent/registryTools.d.ts +45 -0
- package/dist/agent/registryTools.js +113 -0
- package/dist/agent/registryTools.js.map +1 -0
- package/dist/agent/session.js +16 -7
- package/dist/agent/session.js.map +1 -1
- package/dist/agent/tools/fsTools.js +70 -28
- package/dist/agent/tools/fsTools.js.map +1 -1
- package/dist/agent/tools/inspectorTool.d.ts +40 -0
- package/dist/agent/tools/inspectorTool.js +428 -0
- package/dist/agent/tools/inspectorTool.js.map +1 -0
- package/dist/agent/toolset.d.ts +16 -6
- package/dist/agent/toolset.js +17 -7
- package/dist/agent/toolset.js.map +1 -1
- package/dist/agent/types.d.ts +2 -0
- package/dist/components/Application.d.ts +73 -18
- package/dist/components/Application.js +475 -64
- package/dist/components/Application.js.map +1 -1
- package/dist/components/OptionsWatcher.d.ts +1 -1
- package/dist/components/OptionsWatcher.js +65 -2
- package/dist/components/OptionsWatcher.js.map +1 -1
- package/dist/components/Scope.d.ts +8 -6
- package/dist/components/Scope.js +22 -6
- package/dist/components/Scope.js.map +1 -1
- package/dist/components/componentLoader.js +5 -1
- package/dist/components/componentLoader.js.map +1 -1
- package/dist/components/componentSecrets.d.ts +33 -7
- package/dist/components/componentSecrets.js +473 -59
- package/dist/components/componentSecrets.js.map +1 -1
- package/dist/components/deploymentRecorder.d.ts +2 -2
- package/dist/components/deploymentRecorder.js +1 -1
- package/dist/components/deploymentRecorder.js.map +1 -1
- package/dist/components/gitCredentialHelper.d.ts +1 -0
- package/dist/components/gitCredentialHelper.js +113 -0
- package/dist/components/gitCredentialHelper.js.map +1 -0
- package/dist/components/gitCredentialServer.d.ts +33 -0
- package/dist/components/gitCredentialServer.js +236 -0
- package/dist/components/gitCredentialServer.js.map +1 -0
- package/dist/components/mcp/tools/application.d.ts +19 -0
- package/dist/components/mcp/tools/application.js +180 -22
- package/dist/components/mcp/tools/application.js.map +1 -1
- package/dist/components/mcp/tools/operations.d.ts +13 -0
- package/dist/components/mcp/tools/operations.js +1 -0
- package/dist/components/mcp/tools/operations.js.map +1 -1
- package/dist/components/mcp/tools/schemas/derive.js +6 -2
- package/dist/components/mcp/tools/schemas/derive.js.map +1 -1
- package/dist/components/operations.js +23 -21
- package/dist/components/operations.js.map +1 -1
- package/dist/components/operationsValidation.js +84 -21
- package/dist/components/operationsValidation.js.map +1 -1
- package/dist/components/secretOperations.d.ts +33 -11
- package/dist/components/secretOperations.js +90 -26
- package/dist/components/secretOperations.js.map +1 -1
- package/dist/config/harperConfigEnvVars.d.ts +21 -0
- package/dist/config/harperConfigEnvVars.js +95 -5
- package/dist/config/harperConfigEnvVars.js.map +1 -1
- package/dist/dataLayer/harperBridge/ResourceBridge.js +8 -0
- package/dist/dataLayer/harperBridge/ResourceBridge.js.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.js +14 -1
- package/dist/index.js.map +1 -1
- package/dist/resources/Resource.d.ts +19 -0
- package/dist/resources/Resource.js +98 -4
- package/dist/resources/Resource.js.map +1 -1
- package/dist/resources/Table.d.ts +12 -1
- package/dist/resources/Table.js +269 -53
- package/dist/resources/Table.js.map +1 -1
- package/dist/resources/analytics/read.js +28 -25
- package/dist/resources/analytics/read.js.map +1 -1
- package/dist/resources/databases.js +21 -0
- package/dist/resources/databases.js.map +1 -1
- package/dist/resources/defineResource.d.ts +180 -0
- package/dist/resources/defineResource.js +505 -0
- package/dist/resources/defineResource.js.map +1 -0
- package/dist/resources/defineTable.d.ts +221 -0
- package/dist/resources/defineTable.js +227 -0
- package/dist/resources/defineTable.js.map +1 -0
- package/dist/resources/jsonSchemaTypes.d.ts +2 -0
- package/dist/resources/jsonSchemaTypes.js +12 -4
- package/dist/resources/jsonSchemaTypes.js.map +1 -1
- package/dist/resources/openApi.js +69 -13
- package/dist/resources/openApi.js.map +1 -1
- package/dist/resources/search.js +6 -8
- package/dist/resources/search.js.map +1 -1
- package/dist/server/graphqlQuerying.js +4 -2
- package/dist/server/graphqlQuerying.js.map +1 -1
- package/dist/server/http.d.ts +12 -0
- package/dist/server/http.js +35 -15
- package/dist/server/http.js.map +1 -1
- package/dist/server/serverHelpers/serverUtilities.js +8 -5
- package/dist/server/serverHelpers/serverUtilities.js.map +1 -1
- package/dist/server/serverHelpers/uwsServer.js +23 -0
- package/dist/server/serverHelpers/uwsServer.js.map +1 -1
- package/dist/sqlEngine/binder/bind.d.ts +72 -0
- package/dist/sqlEngine/binder/bind.js +289 -0
- package/dist/sqlEngine/binder/bind.js.map +1 -0
- package/dist/sqlEngine/config.d.ts +35 -0
- package/dist/sqlEngine/config.js +62 -0
- package/dist/sqlEngine/config.js.map +1 -0
- package/dist/sqlEngine/diff/differential.d.ts +23 -0
- package/dist/sqlEngine/diff/differential.js +90 -0
- package/dist/sqlEngine/diff/differential.js.map +1 -0
- package/dist/sqlEngine/errors.d.ts +22 -0
- package/dist/sqlEngine/errors.js +36 -0
- package/dist/sqlEngine/errors.js.map +1 -0
- package/dist/sqlEngine/executor/runMutation.d.ts +35 -0
- package/dist/sqlEngine/executor/runMutation.js +352 -0
- package/dist/sqlEngine/executor/runMutation.js.map +1 -0
- package/dist/sqlEngine/executor/runSelect.d.ts +7 -0
- package/dist/sqlEngine/executor/runSelect.js +14 -0
- package/dist/sqlEngine/executor/runSelect.js.map +1 -0
- package/dist/sqlEngine/expressions/compile.d.ts +24 -0
- package/dist/sqlEngine/expressions/compile.js +326 -0
- package/dist/sqlEngine/expressions/compile.js.map +1 -0
- package/dist/sqlEngine/functions/aggregates.d.ts +12 -0
- package/dist/sqlEngine/functions/aggregates.js +299 -0
- package/dist/sqlEngine/functions/aggregates.js.map +1 -0
- package/dist/sqlEngine/functions/registry.d.ts +36 -0
- package/dist/sqlEngine/functions/registry.js +33 -0
- package/dist/sqlEngine/functions/registry.js.map +1 -0
- package/dist/sqlEngine/functions/standard.d.ts +9 -0
- package/dist/sqlEngine/functions/standard.js +86 -0
- package/dist/sqlEngine/functions/standard.js.map +1 -0
- package/dist/sqlEngine/index.d.ts +29 -0
- package/dist/sqlEngine/index.js +62 -0
- package/dist/sqlEngine/index.js.map +1 -0
- package/dist/sqlEngine/logical/build.d.ts +20 -0
- package/dist/sqlEngine/logical/build.js +326 -0
- package/dist/sqlEngine/logical/build.js.map +1 -0
- package/dist/sqlEngine/logical/op.d.ts +105 -0
- package/dist/sqlEngine/logical/op.js +10 -0
- package/dist/sqlEngine/logical/op.js.map +1 -0
- package/dist/sqlEngine/optimizer/joinAnalysis.d.ts +37 -0
- package/dist/sqlEngine/optimizer/joinAnalysis.js +144 -0
- package/dist/sqlEngine/optimizer/joinAnalysis.js.map +1 -0
- package/dist/sqlEngine/optimizer/optimize.d.ts +13 -0
- package/dist/sqlEngine/optimizer/optimize.js +53 -0
- package/dist/sqlEngine/optimizer/optimize.js.map +1 -0
- package/dist/sqlEngine/optimizer/ruleEngine.d.ts +7 -0
- package/dist/sqlEngine/optimizer/ruleEngine.js +26 -0
- package/dist/sqlEngine/optimizer/ruleEngine.js.map +1 -0
- package/dist/sqlEngine/optimizer/rules/limitPushdown.d.ts +19 -0
- package/dist/sqlEngine/optimizer/rules/limitPushdown.js +55 -0
- package/dist/sqlEngine/optimizer/rules/limitPushdown.js.map +1 -0
- package/dist/sqlEngine/optimizer/rules/planJoins.d.ts +19 -0
- package/dist/sqlEngine/optimizer/rules/planJoins.js +62 -0
- package/dist/sqlEngine/optimizer/rules/planJoins.js.map +1 -0
- package/dist/sqlEngine/optimizer/rules/predicateNormalize.d.ts +18 -0
- package/dist/sqlEngine/optimizer/rules/predicateNormalize.js +80 -0
- package/dist/sqlEngine/optimizer/rules/predicateNormalize.js.map +1 -0
- package/dist/sqlEngine/optimizer/rules/predicatePushdown.d.ts +11 -0
- package/dist/sqlEngine/optimizer/rules/predicatePushdown.js +48 -0
- package/dist/sqlEngine/optimizer/rules/predicatePushdown.js.map +1 -0
- package/dist/sqlEngine/optimizer/rules/projectionPushdown.d.ts +23 -0
- package/dist/sqlEngine/optimizer/rules/projectionPushdown.js +160 -0
- package/dist/sqlEngine/optimizer/rules/projectionPushdown.js.map +1 -0
- package/dist/sqlEngine/optimizer/rules/sortFromIndex.d.ts +14 -0
- package/dist/sqlEngine/optimizer/rules/sortFromIndex.js +49 -0
- package/dist/sqlEngine/optimizer/rules/sortFromIndex.js.map +1 -0
- package/dist/sqlEngine/optimizer/rules/validateScannable.d.ts +15 -0
- package/dist/sqlEngine/optimizer/rules/validateScannable.js +60 -0
- package/dist/sqlEngine/optimizer/rules/validateScannable.js.map +1 -0
- package/dist/sqlEngine/optimizer/whereToConditions.d.ts +85 -0
- package/dist/sqlEngine/optimizer/whereToConditions.js +484 -0
- package/dist/sqlEngine/optimizer/whereToConditions.js.map +1 -0
- package/dist/sqlEngine/parser/ast.d.ts +139 -0
- package/dist/sqlEngine/parser/ast.js +10 -0
- package/dist/sqlEngine/parser/ast.js.map +1 -0
- package/dist/sqlEngine/parser/normalizer.d.ts +28 -0
- package/dist/sqlEngine/parser/normalizer.js +413 -0
- package/dist/sqlEngine/parser/normalizer.js.map +1 -0
- package/dist/sqlEngine/parser/parse.d.ts +14 -0
- package/dist/sqlEngine/parser/parse.js +20 -0
- package/dist/sqlEngine/parser/parse.js.map +1 -0
- package/dist/sqlEngine/physical/PhysicalDistinct.d.ts +9 -0
- package/dist/sqlEngine/physical/PhysicalDistinct.js +29 -0
- package/dist/sqlEngine/physical/PhysicalDistinct.js.map +1 -0
- package/dist/sqlEngine/physical/PhysicalFilter.d.ts +10 -0
- package/dist/sqlEngine/physical/PhysicalFilter.js +25 -0
- package/dist/sqlEngine/physical/PhysicalFilter.js.map +1 -0
- package/dist/sqlEngine/physical/PhysicalHashAggregate.d.ts +27 -0
- package/dist/sqlEngine/physical/PhysicalHashAggregate.js +100 -0
- package/dist/sqlEngine/physical/PhysicalHashAggregate.js.map +1 -0
- package/dist/sqlEngine/physical/PhysicalHashJoin.d.ts +29 -0
- package/dist/sqlEngine/physical/PhysicalHashJoin.js +93 -0
- package/dist/sqlEngine/physical/PhysicalHashJoin.js.map +1 -0
- package/dist/sqlEngine/physical/PhysicalIndexNestedLoopJoin.d.ts +36 -0
- package/dist/sqlEngine/physical/PhysicalIndexNestedLoopJoin.js +78 -0
- package/dist/sqlEngine/physical/PhysicalIndexNestedLoopJoin.js.map +1 -0
- package/dist/sqlEngine/physical/PhysicalIndexScan.d.ts +22 -0
- package/dist/sqlEngine/physical/PhysicalIndexScan.js +77 -0
- package/dist/sqlEngine/physical/PhysicalIndexScan.js.map +1 -0
- package/dist/sqlEngine/physical/PhysicalLimit.d.ts +5 -0
- package/dist/sqlEngine/physical/PhysicalLimit.js +28 -0
- package/dist/sqlEngine/physical/PhysicalLimit.js.map +1 -0
- package/dist/sqlEngine/physical/PhysicalNestedLoopJoin.d.ts +20 -0
- package/dist/sqlEngine/physical/PhysicalNestedLoopJoin.js +51 -0
- package/dist/sqlEngine/physical/PhysicalNestedLoopJoin.js.map +1 -0
- package/dist/sqlEngine/physical/PhysicalProject.d.ts +18 -0
- package/dist/sqlEngine/physical/PhysicalProject.js +117 -0
- package/dist/sqlEngine/physical/PhysicalProject.js.map +1 -0
- package/dist/sqlEngine/physical/PhysicalQualify.d.ts +13 -0
- package/dist/sqlEngine/physical/PhysicalQualify.js +29 -0
- package/dist/sqlEngine/physical/PhysicalQualify.js.map +1 -0
- package/dist/sqlEngine/physical/PhysicalSort.d.ts +12 -0
- package/dist/sqlEngine/physical/PhysicalSort.js +59 -0
- package/dist/sqlEngine/physical/PhysicalSort.js.map +1 -0
- package/dist/sqlEngine/physical/op.d.ts +11 -0
- package/dist/sqlEngine/physical/op.js +9 -0
- package/dist/sqlEngine/physical/op.js.map +1 -0
- package/dist/sqlEngine/physical/plan.d.ts +23 -0
- package/dist/sqlEngine/physical/plan.js +162 -0
- package/dist/sqlEngine/physical/plan.js.map +1 -0
- package/dist/sqlEngine/router.d.ts +26 -0
- package/dist/sqlEngine/router.js +48 -0
- package/dist/sqlEngine/router.js.map +1 -0
- package/dist/sqlEngine/types.d.ts +37 -0
- package/dist/sqlEngine/types.js +13 -0
- package/dist/sqlEngine/types.js.map +1 -0
- package/dist/sqlTranslator/index.js +7 -1
- package/dist/sqlTranslator/index.js.map +1 -1
- package/dist/utility/errors/hdbError.d.ts +21 -0
- package/dist/utility/errors/hdbError.js +23 -1
- package/dist/utility/errors/hdbError.js.map +1 -1
- package/dist/validation/configValidator.js +22 -6
- package/dist/validation/configValidator.js.map +1 -1
- package/dist/validation/deleteValidator.js +10 -2
- package/dist/validation/deleteValidator.js.map +1 -1
- package/index.ts +33 -0
- package/npm-shrinkwrap.json +9197 -15066
- package/package.json +3 -1
- package/resources/DESIGN.md +43 -15
- package/resources/Resource.ts +104 -4
- package/resources/Table.ts +298 -74
- package/resources/analytics/read.ts +30 -25
- package/resources/databases.ts +24 -0
- package/resources/defineResource.ts +651 -0
- package/resources/defineTable.ts +407 -0
- package/resources/jsonSchemaTypes.ts +12 -4
- package/resources/openApi.ts +68 -16
- package/resources/search.ts +5 -8
- package/server/graphqlQuerying.ts +4 -2
- package/server/http.ts +40 -16
- package/server/serverHelpers/serverUtilities.ts +19 -6
- package/server/serverHelpers/uwsServer.ts +24 -0
- package/sqlTranslator/index.ts +16 -6
- package/studio/web/assets/{Chat-Cv_2paZE.js → Chat-BZks8dVF.js} +2 -2
- package/studio/web/assets/{Chat-Cv_2paZE.js.map → Chat-BZks8dVF.js.map} +1 -1
- package/studio/web/assets/{FloatingChat-CPyPIcVR.js → FloatingChat-Dic8paVO.js} +4 -4
- package/studio/web/assets/{FloatingChat-CPyPIcVR.js.map → FloatingChat-Dic8paVO.js.map} +1 -1
- package/studio/web/assets/{applications-C0jT2vdZ.js → applications-uOXkeUIN.js} +2 -2
- package/studio/web/assets/{applications-C0jT2vdZ.js.map → applications-uOXkeUIN.js.map} +1 -1
- package/studio/web/assets/{index-D_GKOkhn.js → index-i-2wrKhv.js} +6 -6
- package/studio/web/assets/{index-D_GKOkhn.js.map → index-i-2wrKhv.js.map} +1 -1
- package/studio/web/assets/{index.lazy-CAmCIA65.js → index.lazy-Csk8eCoB.js} +4 -4
- package/studio/web/assets/{index.lazy-CAmCIA65.js.map → index.lazy-Csk8eCoB.js.map} +1 -1
- package/studio/web/assets/{profile-CaF-4aZe.js → profile-Sb3mGDl6.js} +2 -2
- package/studio/web/assets/{profile-CaF-4aZe.js.map → profile-Sb3mGDl6.js.map} +1 -1
- package/studio/web/assets/{setComponentFile-DLW1DHCt.js → setComponentFile-BgZcaPJ2.js} +2 -2
- package/studio/web/assets/{setComponentFile-DLW1DHCt.js.map → setComponentFile-BgZcaPJ2.js.map} +1 -1
- package/studio/web/assets/{setup-TGCErIhq.js → setup-DKtlLgmT.js} +2 -2
- package/studio/web/assets/{setup-TGCErIhq.js.map → setup-DKtlLgmT.js.map} +1 -1
- package/studio/web/assets/{status-DG0Maoao.js → status-B45iLeug.js} +2 -2
- package/studio/web/assets/{status-DG0Maoao.js.map → status-B45iLeug.js.map} +1 -1
- package/studio/web/assets/{swagger-ui-react-8T4SgQ1m.js → swagger-ui-react-Csu4026e.js} +2 -2
- package/studio/web/assets/{swagger-ui-react-8T4SgQ1m.js.map → swagger-ui-react-Csu4026e.js.map} +1 -1
- package/studio/web/assets/{tsMode-DJ6Sl24Q.js → tsMode-DVgxUr_l.js} +2 -2
- package/studio/web/assets/{tsMode-DJ6Sl24Q.js.map → tsMode-DVgxUr_l.js.map} +1 -1
- package/studio/web/assets/{useEntityRestURL-JO2mfWTQ.js → useEntityRestURL-yfDQMV1f.js} +2 -2
- package/studio/web/assets/{useEntityRestURL-JO2mfWTQ.js.map → useEntityRestURL-yfDQMV1f.js.map} +1 -1
- package/studio/web/index.html +1 -1
- package/utility/errors/hdbError.ts +32 -0
- package/validation/configValidator.ts +23 -6
- package/validation/deleteValidator.ts +11 -2
package/dist/resources/Table.js
CHANGED
|
@@ -43,6 +43,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
43
43
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
44
44
|
exports.EVICTED = exports.INVALIDATED = void 0;
|
|
45
45
|
exports.freezeRecord = freezeRecord;
|
|
46
|
+
exports.frozenRecordView = frozenRecordView;
|
|
46
47
|
exports.makeTable = makeTable;
|
|
47
48
|
exports.coerceType = coerceType;
|
|
48
49
|
const hdbTerms_ts_1 = require("../utility/hdbTerms.js");
|
|
@@ -133,6 +134,45 @@ function freezeRecord(value) {
|
|
|
133
134
|
if (value !== null && typeof value === 'object' && !ArrayBuffer.isView(value) && !(value instanceof ArrayBuffer))
|
|
134
135
|
Object.freeze(value);
|
|
135
136
|
}
|
|
137
|
+
// Returns a read-only VIEW of `record` for evaluation (e.g. a record-scoped allowRead guard)
|
|
138
|
+
// without mutating the original. Locally-loaded records are already frozen by loadLocalRecord, so
|
|
139
|
+
// this is a no-op there; the source-revalidation path hands back the SAME object its deferred
|
|
140
|
+
// commit still writes to (createdAt/updatedAt) and persists, so that object can't be frozen
|
|
141
|
+
// directly. A write-blocking Proxy is used instead of a shallow copy: a copy would eagerly invoke
|
|
142
|
+
// every getter (breaking a caching table's lazy structPrototype decode) and silently drop
|
|
143
|
+
// non-enumerable properties like Array.length. Only ordinary objects/arrays are wrapped — Date,
|
|
144
|
+
// Map, Set, RegExp, etc. carry internal slots that throw "incompatible receiver" when their
|
|
145
|
+
// methods run against anything but the exact original instance (a Proxy included), so those are
|
|
146
|
+
// returned unwrapped rather than risk breaking an override that calls a method on one; a cached
|
|
147
|
+
// record CAN legitimately be one of these (getFromSource only requires `typeof === 'object'`).
|
|
148
|
+
function frozenRecordView(record) {
|
|
149
|
+
if (record === null || typeof record !== 'object' || ArrayBuffer.isView(record) || record instanceof ArrayBuffer)
|
|
150
|
+
return record;
|
|
151
|
+
if (Object.isFrozen(record))
|
|
152
|
+
return record;
|
|
153
|
+
const tag = Object.prototype.toString.call(record);
|
|
154
|
+
if (tag !== '[object Object]' && tag !== '[object Array]')
|
|
155
|
+
return record;
|
|
156
|
+
return new Proxy(record, {
|
|
157
|
+
get(target, prop) {
|
|
158
|
+
// receiver = target (not this proxy) so a lazy-decode getter runs with `this` bound to
|
|
159
|
+
// the real record, matching its behavior when read directly.
|
|
160
|
+
return Reflect.get(target, prop, target);
|
|
161
|
+
},
|
|
162
|
+
set() {
|
|
163
|
+
return false;
|
|
164
|
+
},
|
|
165
|
+
defineProperty() {
|
|
166
|
+
return false;
|
|
167
|
+
},
|
|
168
|
+
deleteProperty() {
|
|
169
|
+
return false;
|
|
170
|
+
},
|
|
171
|
+
setPrototypeOf() {
|
|
172
|
+
return false;
|
|
173
|
+
},
|
|
174
|
+
});
|
|
175
|
+
}
|
|
136
176
|
exports.INVALIDATED = 1;
|
|
137
177
|
exports.EVICTED = 8; // note that 2 is reserved for timestamps
|
|
138
178
|
const TEST_WRITE_KEY_BUFFER = Buffer.allocUnsafeSlow(8192);
|
|
@@ -144,6 +184,12 @@ const REPLAY_YIELD_INTERVAL = 100; // yield to the event loop every N records du
|
|
|
144
184
|
// buffer the entire backward chain per record, synchronously, on every worker — pinning the JS heap
|
|
145
185
|
// until the worker OOMs (issue #1114). Beyond this depth we fall back to a bounded reconciliation.
|
|
146
186
|
const MAX_OUT_OF_ORDER_AUDIT_DEPTH = 1000;
|
|
187
|
+
// Cap on audit records inspected while backfilling a subscription's `previousCount` history,
|
|
188
|
+
// independent of `count` (the number of AUTHORIZED events collected). `count` only decrements on
|
|
189
|
+
// records that pass the row-level allowRead filter, so an all-deny override would otherwise force
|
|
190
|
+
// a full walk of the retained audit log (#1786) — this bounds that walk regardless of how many
|
|
191
|
+
// records the filter accepts.
|
|
192
|
+
const MAX_PREVIOUS_COUNT_SCAN = 10_000;
|
|
147
193
|
const FULL_PERMISSIONS = {
|
|
148
194
|
read: true,
|
|
149
195
|
insert: true,
|
|
@@ -2459,18 +2505,44 @@ function makeTable(options) {
|
|
|
2459
2505
|
throw new Error('No query provided');
|
|
2460
2506
|
if (target.parseError)
|
|
2461
2507
|
throw target.parseError; // if there was a parse error, we can throw it now
|
|
2508
|
+
// An application-overridden allowRead is a RECORD-scoped check (#1422 gap 2 / #1241): it is
|
|
2509
|
+
// evaluated once per record during query execution with `this` = the record, instead of once
|
|
2510
|
+
// at entry where `this` is a collection resource with nothing loaded. The framework defaults
|
|
2511
|
+
// (marked isDefaultAllowRead) are this-free table/RBAC checks and keep the entry evaluation.
|
|
2512
|
+
// Record-scoping is SYNC-only: async-declared overrides keep entry-check semantics (the
|
|
2513
|
+
// authorize wrapper awaits them and fails closed) rather than entering the sync traversal.
|
|
2514
|
+
const recordScopedAllowRead = target.checkPermission &&
|
|
2515
|
+
!this.allowRead?.isDefaultAllowRead &&
|
|
2516
|
+
this.allowRead?.constructor?.name !== 'AsyncFunction'
|
|
2517
|
+
? this.allowRead
|
|
2518
|
+
: undefined;
|
|
2462
2519
|
if (target.checkPermission) {
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2520
|
+
if (recordScopedAllowRead) {
|
|
2521
|
+
// Compose with role-level column RBAC: the DEFAULT table allowRead is also the
|
|
2522
|
+
// enforcement point for attribute_permissions (it narrows target.select). Run it here
|
|
2523
|
+
// for that side effect — before `select` is captured below — with its verdict
|
|
2524
|
+
// superseded by the per-record override. A per-record super.allowRead call could NOT
|
|
2525
|
+
// restore this: the select-driven output transform is built before iteration starts.
|
|
2526
|
+
try {
|
|
2527
|
+
TableResource.prototype.allowRead.call(this, context.user, target, context);
|
|
2528
|
+
}
|
|
2529
|
+
catch {
|
|
2530
|
+
// narrowing is best-effort under an override; access is governed per record
|
|
2531
|
+
}
|
|
2471
2532
|
}
|
|
2472
|
-
|
|
2473
|
-
|
|
2533
|
+
else {
|
|
2534
|
+
// requesting authorization verification
|
|
2535
|
+
let allowed;
|
|
2536
|
+
try {
|
|
2537
|
+
allowed = this.allowRead(context.user, target, context);
|
|
2538
|
+
}
|
|
2539
|
+
catch {
|
|
2540
|
+
// allow* threw — fail closed rather than letting the request proceed
|
|
2541
|
+
throw new hdbError_ts_1.AccessViolation(context.user);
|
|
2542
|
+
}
|
|
2543
|
+
if (!allowed) {
|
|
2544
|
+
throw new hdbError_ts_1.AccessViolation(context.user);
|
|
2545
|
+
}
|
|
2474
2546
|
}
|
|
2475
2547
|
}
|
|
2476
2548
|
if (context)
|
|
@@ -2672,28 +2744,63 @@ function makeTable(options) {
|
|
|
2672
2744
|
// scans), the read transaction reads against the latest committed data without pinning a
|
|
2673
2745
|
// consistent snapshot, so the scan doesn't hold a snapshot that blocks compaction.
|
|
2674
2746
|
const readTxn = txn.useReadTxn(target.snapshot === false);
|
|
2675
|
-
// Record-level
|
|
2676
|
-
//
|
|
2677
|
-
//
|
|
2678
|
-
//
|
|
2679
|
-
//
|
|
2680
|
-
//
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2747
|
+
// Record-level allowRead guard (#1241/#1422): an application-overridden allowRead runs once
|
|
2748
|
+
// per record with `this` = the (frozen) record, participating in query execution — pushed
|
|
2749
|
+
// into HNSW traversal for vector sorts, applied as a post-filter otherwise. It must be
|
|
2750
|
+
// synchronous, side-effect free, and fast; a throw denies that record (fail closed, #1422
|
|
2751
|
+
// gap 1 parity). Dispatched via the method resolved from the resource — never via a
|
|
2752
|
+
// `record.allowRead` property lookup, so a record attribute named allowRead can't shadow it.
|
|
2753
|
+
let recordGuard;
|
|
2754
|
+
if (recordScopedAllowRead) {
|
|
2755
|
+
const user = context?.user;
|
|
2756
|
+
let warnedAsync = false;
|
|
2757
|
+
recordGuard = (record) => {
|
|
2758
|
+
let allowed;
|
|
2759
|
+
try {
|
|
2760
|
+
allowed = recordScopedAllowRead.call(record, user, target, context);
|
|
2761
|
+
}
|
|
2762
|
+
catch {
|
|
2763
|
+
return false; // fail closed on a throwing check
|
|
2764
|
+
}
|
|
2765
|
+
if (typeof allowed?.then === 'function') {
|
|
2766
|
+
// A sync-declared override that returns a thenable can't be awaited mid-traversal:
|
|
2767
|
+
// deny the record (fail closed, #1422 gap 1 semantics) rather than fail open on
|
|
2768
|
+
// promise truthiness or abort the whole query. Declared-async overrides never get
|
|
2769
|
+
// here — they keep the awaited entry check. We're not observing this rejection, so
|
|
2770
|
+
// attach a no-op handler — otherwise a rejected thenable reaches the global
|
|
2771
|
+
// unhandledRejection logger once per denied candidate. Use `.then(undefined, ...)`,
|
|
2772
|
+
// not `.catch` — a thenable is only guaranteed to have `.then`.
|
|
2773
|
+
allowed.then(undefined, () => { });
|
|
2774
|
+
if (!warnedAsync) {
|
|
2775
|
+
warnedAsync = true;
|
|
2776
|
+
logger_ts_1.logger.warn?.(`allowRead on ${tableName} returned a promise during per-record evaluation; records are denied (record-scoped allowRead must be synchronous)`);
|
|
2777
|
+
}
|
|
2778
|
+
return false;
|
|
2779
|
+
}
|
|
2780
|
+
return Boolean(allowed);
|
|
2781
|
+
};
|
|
2782
|
+
}
|
|
2783
|
+
const recordAccess = recordGuard || typeof target.vectorFilter === 'function'
|
|
2784
|
+
? { recordGuard, vectorFilter: target.vectorFilter }
|
|
2684
2785
|
: undefined;
|
|
2685
2786
|
const entries = (0, search_ts_1.executeConditions)(conditions, operator, TableResource, readTxn, target, context, (results, filters) => transformToEntries(results, select, context, readTxn, filters), filtered, recordAccess);
|
|
2686
2787
|
const ensure_loaded = target.ensureLoaded !== false;
|
|
2687
|
-
// Authoritative
|
|
2688
|
-
// record, but on a caching table transformEntryForSelect may then revalidate an
|
|
2689
|
-
// row from source and return a DIFFERENT record. An authorization check
|
|
2690
|
-
// actually returned, so
|
|
2691
|
-
// evaluation stays as a prune that also bounds HNSW
|
|
2692
|
-
// intentionally keep the local-record
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2788
|
+
// Authoritative enforcement point (#1241): the guards inside executeConditions evaluate the
|
|
2789
|
+
// LOCAL record, but on a caching table transformEntryForSelect may then revalidate an
|
|
2790
|
+
// expired/invalidated row from source and return a DIFFERENT record. An authorization check
|
|
2791
|
+
// must hold on the record actually returned, so the record-scoped allowRead is re-checked
|
|
2792
|
+
// there, after materialization (the earlier evaluation stays as a prune that also bounds HNSW
|
|
2793
|
+
// traversal). vectorFilter and condition filters intentionally keep the local-record
|
|
2794
|
+
// semantics all query filters have on caching tables.
|
|
2795
|
+
//
|
|
2796
|
+
// A row that is past its TTL but not yet swept by the background eviction
|
|
2797
|
+
// scan is still physically present. A write that is about to overwrite it
|
|
2798
|
+
// anyway (e.g. the SQL engine locating UPDATE/DELETE targets) needs to see
|
|
2799
|
+
// it as a match — the same leniency a direct by-id put/patch already gets,
|
|
2800
|
+
// since those never run the ensureLoaded-gated freshness check this transform
|
|
2801
|
+
// otherwise applies unconditionally to every read.
|
|
2802
|
+
const includeExpired = target.includeExpired === true;
|
|
2803
|
+
const transformToRecord = TableResource.transformEntryForSelect(select, context, readTxn, filtered, ensure_loaded, true, recordGuard, includeExpired);
|
|
2697
2804
|
let results = TableResource.transformToOrderedSelect(entries, select, postOrdering, context, readTxn, transformToRecord);
|
|
2698
2805
|
// apply any offset/limit after all the sorting and filtering
|
|
2699
2806
|
if (target.offset || target.limit !== undefined)
|
|
@@ -2910,9 +3017,11 @@ function makeTable(options) {
|
|
|
2910
3017
|
* @param recordGuard record-level read check (#1241) applied to the record actually being
|
|
2911
3018
|
* returned — i.e. AFTER any caching-source revalidation replaces a stale local copy — so an
|
|
2912
3019
|
* authorization verdict can't be made on bytes that differ from what the caller receives.
|
|
3020
|
+
* @param includeExpired when true, a row past its TTL but not yet swept is treated as a live
|
|
3021
|
+
* match rather than gone (used by the SQL engine's UPDATE/DELETE row-finder).
|
|
2913
3022
|
* @returns
|
|
2914
3023
|
*/
|
|
2915
|
-
static transformEntryForSelect(select, context, readTxn, filtered, ensure_loaded, canSkip, recordGuard) {
|
|
3024
|
+
static transformEntryForSelect(select, context, readTxn, filtered, ensure_loaded, canSkip, recordGuard, includeExpired) {
|
|
2916
3025
|
let checkLoaded;
|
|
2917
3026
|
if (ensure_loaded &&
|
|
2918
3027
|
hasSourceGet &&
|
|
@@ -2955,7 +3064,7 @@ function makeTable(options) {
|
|
|
2955
3064
|
record = entry?.value;
|
|
2956
3065
|
}
|
|
2957
3066
|
if ((checkLoaded && entry?.metadataFlags & (exports.INVALIDATED | exports.EVICTED)) || // invalidated or evicted should go to load from source
|
|
2958
|
-
(entry?.expiresAt != undefined && entry?.expiresAt < Date.now())) {
|
|
3067
|
+
(!includeExpired && entry?.expiresAt != undefined && entry?.expiresAt < Date.now())) {
|
|
2959
3068
|
// should expiration really apply?
|
|
2960
3069
|
if (context.onlyIfCached) {
|
|
2961
3070
|
return {
|
|
@@ -2989,8 +3098,10 @@ function makeTable(options) {
|
|
|
2989
3098
|
return canSkip ? extended_iterable_1.SKIP : record;
|
|
2990
3099
|
// Record-level RBAC (#1241): enforced here because `record` is now the final, materialized
|
|
2991
3100
|
// record — a caching table's source revalidation (above) may have replaced the local copy the
|
|
2992
|
-
// query filters evaluated. Fail closed on the record actually being returned.
|
|
2993
|
-
|
|
3101
|
+
// query filters evaluated. Fail closed on the record actually being returned. The guard sees a
|
|
3102
|
+
// frozen view (frozenRecordView) so an allowRead override that writes through `this` can't
|
|
3103
|
+
// mutate a record the source-revalidation path's deferred commit still needs to write and encode.
|
|
3104
|
+
if (recordGuard && !recordGuard(frozenRecordView(record)))
|
|
2994
3105
|
return canSkip ? extended_iterable_1.SKIP : undefined;
|
|
2995
3106
|
if (select && !(select[0] === '*' && select.length === 1)) {
|
|
2996
3107
|
let promises;
|
|
@@ -3162,6 +3273,58 @@ function makeTable(options) {
|
|
|
3162
3273
|
let reloadResnapshotRunning = false;
|
|
3163
3274
|
let reloadResnapshotPending = false;
|
|
3164
3275
|
const thisId = requestTargetToId(request) ?? null; // treat undefined and null as the root
|
|
3276
|
+
// Record-scoped allowRead on subscription delivery (#1419, reviving #1524 on the unified
|
|
3277
|
+
// model): the subscribe entry check already granted the connection (topic ACL / collection
|
|
3278
|
+
// scope); here we ADDITIONALLY re-evaluate a sync application-overridden allowRead per
|
|
3279
|
+
// record-bearing event with `this` = the event's record, the delivery-path analog of the
|
|
3280
|
+
// per-record query guard. Gated to (1) an overridden allowRead (the default is
|
|
3281
|
+
// record-independent and already enforced at connect, so the common case stays zero-overhead)
|
|
3282
|
+
// and (2) an authorization-checked subscription — the entry check clears checkPermission
|
|
3283
|
+
// before we get here and an anonymous-but-checked subscription has no user to key on, so the
|
|
3284
|
+
// wrapper stamps a durable `rowLevelAuthChecked` flag; internal subscribers (replication,
|
|
3285
|
+
// system watchers) never set it and keep full delivery.
|
|
3286
|
+
// Fails closed: a throwing or thenable-returning override drops the event rather than leaks it.
|
|
3287
|
+
//
|
|
3288
|
+
// Known limitation (as in #1524): only put/invalidate events carry the authoritative full
|
|
3289
|
+
// record. delete (tombstone, null value), message payloads, and rawEvents partial values may
|
|
3290
|
+
// mis-decide an override keyed on row fields; closing the primary leak (record updates) is
|
|
3291
|
+
// the goal here — authorizing non-record-bearing event types against the full row is deferred.
|
|
3292
|
+
const subContext = this.getContext();
|
|
3293
|
+
const subAllowRead = this.allowRead;
|
|
3294
|
+
const filterRowReads = request?.rowLevelAuthChecked && !subAllowRead?.isDefaultAllowRead;
|
|
3295
|
+
let warnedAsyncEvent = false;
|
|
3296
|
+
const allowsEvent = filterRowReads
|
|
3297
|
+
? (event) => {
|
|
3298
|
+
if (event.type === 'end_txn' || event.type === 'reload')
|
|
3299
|
+
return true; // control markers, no record
|
|
3300
|
+
// Freeze the record before the override sees it, mirroring the query path — event.value
|
|
3301
|
+
// is the shared primaryStore object, so an override that writes to `this` would otherwise
|
|
3302
|
+
// mutate the cache for every reader on the node.
|
|
3303
|
+
if (event.value)
|
|
3304
|
+
freezeRecord(event.value);
|
|
3305
|
+
let decision;
|
|
3306
|
+
try {
|
|
3307
|
+
// Evaluate against the LIVE context user, not a subscribe-time snapshot: #1414 re-auth
|
|
3308
|
+
// updates subContext.user in place, so a role/claims change must be reflected per event.
|
|
3309
|
+
decision = subAllowRead.call(event.value ?? null, subContext.user, request, subContext);
|
|
3310
|
+
}
|
|
3311
|
+
catch {
|
|
3312
|
+
return false; // fail closed: a throwing override drops the event
|
|
3313
|
+
}
|
|
3314
|
+
if (decision != null && typeof decision.then === 'function') {
|
|
3315
|
+
// Not observing this rejection — attach a no-op handler so it doesn't reach the
|
|
3316
|
+
// global unhandledRejection logger once per dropped event. Use `.then(undefined,
|
|
3317
|
+
// ...)`, not `.catch` — a thenable is only guaranteed to have `.then`.
|
|
3318
|
+
decision.then(undefined, () => { });
|
|
3319
|
+
if (!warnedAsyncEvent) {
|
|
3320
|
+
warnedAsyncEvent = true;
|
|
3321
|
+
logger_ts_1.logger.warn?.(`allowRead on ${tableName} returned a promise during subscription event evaluation; events are dropped (record-scoped allowRead must be synchronous)`);
|
|
3322
|
+
}
|
|
3323
|
+
return false;
|
|
3324
|
+
}
|
|
3325
|
+
return Boolean(decision);
|
|
3326
|
+
}
|
|
3327
|
+
: null;
|
|
3165
3328
|
const subscription = (0, transactionBroadcast_ts_1.addSubscription)(TableResource, thisId, function (id, auditRecord, localTime, beginTxn) {
|
|
3166
3329
|
if (dropDuringReplay)
|
|
3167
3330
|
return;
|
|
@@ -3206,9 +3369,13 @@ function makeTable(options) {
|
|
|
3206
3369
|
type,
|
|
3207
3370
|
beginTxn,
|
|
3208
3371
|
};
|
|
3372
|
+
// Queued events are filtered when the queue drains through send() below; events sent
|
|
3373
|
+
// directly (queue already drained) are filtered here. Each event is filtered once.
|
|
3209
3374
|
if (pendingRealTimeQueue)
|
|
3210
3375
|
pendingRealTimeQueue.push(event);
|
|
3211
3376
|
else {
|
|
3377
|
+
if (allowsEvent && !allowsEvent(event))
|
|
3378
|
+
return;
|
|
3212
3379
|
if (databaseName !== 'system') {
|
|
3213
3380
|
(0, write_ts_1.recordAction)(auditRecord.size ?? 1, 'db-message', tableName, null);
|
|
3214
3381
|
}
|
|
@@ -3287,25 +3454,45 @@ function makeTable(options) {
|
|
|
3287
3454
|
}
|
|
3288
3455
|
else if (count) {
|
|
3289
3456
|
const history = [];
|
|
3457
|
+
let inspected = 0;
|
|
3290
3458
|
// we are collecting the history in reverse order to get the right count, then reversing to send
|
|
3291
3459
|
for (const auditRecord of auditStore.getRange({ start: 'z', end: false, reverse: true })) {
|
|
3292
3460
|
if (++recordsSinceYield >= REPLAY_YIELD_INTERVAL) {
|
|
3293
3461
|
recordsSinceYield = 0;
|
|
3294
3462
|
await rest();
|
|
3463
|
+
// Subscription.end() nulls `subscriptions` on close; stop backfilling a dead
|
|
3464
|
+
// subscriber rather than continuing to walk the retained audit log.
|
|
3465
|
+
if (!subscription.subscriptions)
|
|
3466
|
+
break;
|
|
3295
3467
|
}
|
|
3296
3468
|
try {
|
|
3297
3469
|
if (auditRecord.tableId !== tableId)
|
|
3298
3470
|
continue;
|
|
3299
3471
|
const id = auditRecord.recordId;
|
|
3300
3472
|
if (thisId == null || isDescendantId(thisId, id)) {
|
|
3473
|
+
// Bound entries INSPECTED for THIS scope, independent of `count` (entries
|
|
3474
|
+
// AUTHORIZED) — an all-deny allowRead override must not force a full walk of
|
|
3475
|
+
// the retained audit log (#1786), even though it returns fewer than `count`
|
|
3476
|
+
// authorized events. Counted only once a record is known to be in scope (right
|
|
3477
|
+
// table, right id) so unrelated cross-table/cross-scope audit traffic in a busy
|
|
3478
|
+
// shared log can't spuriously cut a backfill short.
|
|
3479
|
+
if (++inspected > MAX_PREVIOUS_COUNT_SCAN) {
|
|
3480
|
+
logger_ts_1.logger.warn?.(`previousCount backfill on ${tableName} stopped after inspecting ${MAX_PREVIOUS_COUNT_SCAN} in-scope audit records without collecting ${request.previousCount} authorized event(s); returning ${history.length} instead`);
|
|
3481
|
+
break;
|
|
3482
|
+
}
|
|
3301
3483
|
const value = auditRecord.getValue(primaryStore, getFullRecord, auditRecord.localTime);
|
|
3302
|
-
|
|
3484
|
+
const historyEntry = {
|
|
3303
3485
|
id,
|
|
3304
3486
|
localTime: auditRecord.localTime,
|
|
3305
3487
|
value,
|
|
3306
3488
|
version: auditRecord.version,
|
|
3307
3489
|
type: auditRecord.type,
|
|
3308
|
-
}
|
|
3490
|
+
};
|
|
3491
|
+
// Filter denied rows BEFORE they consume a previousCount slot, so an authorized
|
|
3492
|
+
// subscriber still receives up to `count` readable events (#1419).
|
|
3493
|
+
if (allowsEvent && !allowsEvent(historyEntry))
|
|
3494
|
+
continue;
|
|
3495
|
+
history.push(historyEntry);
|
|
3309
3496
|
if (--count <= 0)
|
|
3310
3497
|
break;
|
|
3311
3498
|
}
|
|
@@ -3458,6 +3645,9 @@ function makeTable(options) {
|
|
|
3458
3645
|
subscription.send(error);
|
|
3459
3646
|
});
|
|
3460
3647
|
function send(event) {
|
|
3648
|
+
// Covers the pendingRealTimeQueue drain and the reload re-snapshot (#495) delivery.
|
|
3649
|
+
if (allowsEvent && !allowsEvent(event))
|
|
3650
|
+
return;
|
|
3461
3651
|
if (databaseName !== 'system') {
|
|
3462
3652
|
(0, write_ts_1.recordAction)(event.size ?? 1, 'db-message', tableName, null);
|
|
3463
3653
|
}
|
|
@@ -3663,21 +3853,27 @@ function makeTable(options) {
|
|
|
3663
3853
|
}
|
|
3664
3854
|
// #section: validation
|
|
3665
3855
|
validate(record, patch) {
|
|
3856
|
+
// Accumulate structured per-field issues so the 400 carries `{ path, code,
|
|
3857
|
+
// message }[]` matching the emitted OpenAPI, instead of a single joined string. The joined
|
|
3858
|
+
// message is still built for the HTTP title, preserving back-compat for callers that read it.
|
|
3666
3859
|
let validationErrors;
|
|
3860
|
+
const addError = (path, code, message) => {
|
|
3861
|
+
(validationErrors || (validationErrors = [])).push({ path, code, message });
|
|
3862
|
+
};
|
|
3667
3863
|
const validateValue = (value, attribute, name) => {
|
|
3668
3864
|
if (attribute.type && value != null) {
|
|
3669
3865
|
if (patch && value.__op__)
|
|
3670
3866
|
value = value.value;
|
|
3671
3867
|
if (attribute.properties) {
|
|
3672
3868
|
if (typeof value !== 'object') {
|
|
3673
|
-
(
|
|
3869
|
+
addError(name, 'type', `Value ${stringify(value)} in property ${name} must be an object${attribute.type ? ' (' + attribute.type + ')' : ''}`);
|
|
3674
3870
|
}
|
|
3675
3871
|
const properties = attribute.properties;
|
|
3676
3872
|
for (let i = 0, l = properties.length; i < l; i++) {
|
|
3677
3873
|
const attribute = properties[i];
|
|
3678
3874
|
if (attribute.relationship || attribute.computed) {
|
|
3679
3875
|
if (record.hasOwnProperty(attribute.name)) {
|
|
3680
|
-
(
|
|
3876
|
+
addError(`${name}.${attribute.name}`, 'computed', `Computed property ${name}.${attribute.name} may not be directly assigned a value`);
|
|
3681
3877
|
}
|
|
3682
3878
|
continue;
|
|
3683
3879
|
}
|
|
@@ -3688,7 +3884,7 @@ function makeTable(options) {
|
|
|
3688
3884
|
if (attribute.sealed && value != null && typeof value === 'object') {
|
|
3689
3885
|
for (const key in value) {
|
|
3690
3886
|
if (!properties.find((property) => property.name === key)) {
|
|
3691
|
-
(
|
|
3887
|
+
addError(`${name}.${key}`, 'unknown_property', `Property ${key} is not allowed within object in property ${name}`);
|
|
3692
3888
|
}
|
|
3693
3889
|
}
|
|
3694
3890
|
}
|
|
@@ -3697,35 +3893,35 @@ function makeTable(options) {
|
|
|
3697
3893
|
switch (attribute.type) {
|
|
3698
3894
|
case 'Int':
|
|
3699
3895
|
if (typeof value !== 'number' || value >> 0 !== value)
|
|
3700
|
-
(
|
|
3896
|
+
addError(name, 'type', `Value ${stringify(value)} in property ${name} must be an integer (from -2147483648 to 2147483647)`);
|
|
3701
3897
|
break;
|
|
3702
3898
|
case 'Long':
|
|
3703
3899
|
if (typeof value !== 'number' || !(Math.floor(value) === value && Math.abs(value) <= 9007199254740992))
|
|
3704
|
-
(
|
|
3900
|
+
addError(name, 'type', `Value ${stringify(value)} in property ${name} must be an integer (from -9007199254740992 to 9007199254740992)`);
|
|
3705
3901
|
break;
|
|
3706
3902
|
case 'Float':
|
|
3707
3903
|
if (typeof value !== 'number')
|
|
3708
|
-
(
|
|
3904
|
+
addError(name, 'type', `Value ${stringify(value)} in property ${name} must be a number`);
|
|
3709
3905
|
break;
|
|
3710
3906
|
case 'ID':
|
|
3711
3907
|
if (!(typeof value === 'string' ||
|
|
3712
3908
|
(value?.length > 0 && value.every?.((value) => typeof value === 'string'))))
|
|
3713
|
-
(
|
|
3909
|
+
addError(name, 'type', `Value ${stringify(value)} in property ${name} must be a string, or an array of strings`);
|
|
3714
3910
|
break;
|
|
3715
3911
|
case 'String':
|
|
3716
3912
|
if (typeof value !== 'string')
|
|
3717
|
-
(
|
|
3913
|
+
addError(name, 'type', `Value ${stringify(value)} in property ${name} must be a string`);
|
|
3718
3914
|
break;
|
|
3719
3915
|
case 'Boolean':
|
|
3720
3916
|
if (typeof value !== 'boolean')
|
|
3721
|
-
(
|
|
3917
|
+
addError(name, 'type', `Value ${stringify(value)} in property ${name} must be a boolean`);
|
|
3722
3918
|
break;
|
|
3723
3919
|
case 'Date':
|
|
3724
3920
|
if (!(value instanceof Date)) {
|
|
3725
3921
|
if (typeof value === 'string' || typeof value === 'number')
|
|
3726
3922
|
return new Date(value);
|
|
3727
3923
|
else
|
|
3728
|
-
(
|
|
3924
|
+
addError(name, 'type', `Value ${stringify(value)} in property ${name} must be a Date`);
|
|
3729
3925
|
}
|
|
3730
3926
|
break;
|
|
3731
3927
|
case 'BigInt':
|
|
@@ -3733,14 +3929,14 @@ function makeTable(options) {
|
|
|
3733
3929
|
// do coercion because otherwise it is rather difficult to get numbers to consistently be bigints
|
|
3734
3930
|
if (typeof value === 'string' || typeof value === 'number')
|
|
3735
3931
|
return BigInt(value);
|
|
3736
|
-
(
|
|
3932
|
+
addError(name, 'type', `Value ${stringify(value)} in property ${name} must be a bigint`);
|
|
3737
3933
|
}
|
|
3738
3934
|
break;
|
|
3739
3935
|
case 'Bytes':
|
|
3740
3936
|
if (!(value instanceof Uint8Array)) {
|
|
3741
3937
|
if (typeof value === 'string')
|
|
3742
3938
|
return Buffer.from(value);
|
|
3743
|
-
(
|
|
3939
|
+
addError(name, 'type', `Value ${stringify(value)} in property ${name} must be a Buffer or Uint8Array`);
|
|
3744
3940
|
}
|
|
3745
3941
|
break;
|
|
3746
3942
|
case 'Blob':
|
|
@@ -3750,7 +3946,7 @@ function makeTable(options) {
|
|
|
3750
3946
|
if (value instanceof Buffer) {
|
|
3751
3947
|
return createBlob(value, { type: 'text/plain' });
|
|
3752
3948
|
}
|
|
3753
|
-
(
|
|
3949
|
+
addError(name, 'type', `Value ${stringify(value)} in property ${name} must be a Blob`);
|
|
3754
3950
|
}
|
|
3755
3951
|
break;
|
|
3756
3952
|
case 'array':
|
|
@@ -3765,20 +3961,20 @@ function makeTable(options) {
|
|
|
3765
3961
|
}
|
|
3766
3962
|
}
|
|
3767
3963
|
else
|
|
3768
|
-
(
|
|
3964
|
+
addError(name, 'type', `Value ${stringify(value)} in property ${name} must be an Array`);
|
|
3769
3965
|
break;
|
|
3770
3966
|
}
|
|
3771
3967
|
}
|
|
3772
3968
|
}
|
|
3773
3969
|
if (attribute.nullable === false && value == null) {
|
|
3774
|
-
(
|
|
3970
|
+
addError(name, 'required', `Property ${name} is required (and not does not allow null values)`);
|
|
3775
3971
|
}
|
|
3776
3972
|
};
|
|
3777
3973
|
for (let i = 0, l = attributes.length; i < l; i++) {
|
|
3778
3974
|
const attribute = attributes[i];
|
|
3779
3975
|
if (attribute.relationship || attribute.computed) {
|
|
3780
3976
|
if (Object.hasOwn(record, attribute.name)) {
|
|
3781
|
-
(
|
|
3977
|
+
addError(attribute.name, 'computed', `Computed property ${attribute.name} may not be directly assigned a value`);
|
|
3782
3978
|
}
|
|
3783
3979
|
continue;
|
|
3784
3980
|
}
|
|
@@ -3791,12 +3987,12 @@ function makeTable(options) {
|
|
|
3791
3987
|
if (sealed) {
|
|
3792
3988
|
for (const key in record) {
|
|
3793
3989
|
if (!attributes.find((attribute) => attribute.name === key)) {
|
|
3794
|
-
(
|
|
3990
|
+
addError(key, 'unknown_property', `Property ${key} is not allowed`);
|
|
3795
3991
|
}
|
|
3796
3992
|
}
|
|
3797
3993
|
}
|
|
3798
3994
|
if (validationErrors) {
|
|
3799
|
-
throw new hdbError_ts_1.
|
|
3995
|
+
throw new hdbError_ts_1.ValidationError(validationErrors, validationErrors.map((issue) => issue.message).join('. '));
|
|
3800
3996
|
}
|
|
3801
3997
|
}
|
|
3802
3998
|
// #section: stats-admin
|
|
@@ -4284,6 +4480,26 @@ function makeTable(options) {
|
|
|
4284
4480
|
}, () => {
|
|
4285
4481
|
throw new hdbError_ts_1.ServerError('Service unavailable, exceeded request queue limit for resolving cache record', 503);
|
|
4286
4482
|
});
|
|
4483
|
+
// Mark the table-level allowRead as a framework default (it is a this-free table/RBAC check):
|
|
4484
|
+
// only an APPLICATION override is record-scoped and evaluated per record during query execution
|
|
4485
|
+
// (#1422 gap 2 / #1241). supportsRowLevelAllowRead tells the authorization wrapper in
|
|
4486
|
+
// Resource.ts that this class has the per-record machinery to defer collection reads to.
|
|
4487
|
+
TableResource.prototype.allowRead.isDefaultAllowRead = true;
|
|
4488
|
+
TableResource.supportsRowLevelAllowRead = true;
|
|
4489
|
+
// Records can be asked directly (`record.allowRead(user, target, context)`), delegating to the
|
|
4490
|
+
// table's allowRead with `this` = the record. Non-enumerable so it never serializes; defined on
|
|
4491
|
+
// the per-table record prototype alongside computed properties. Engine-level enforcement does
|
|
4492
|
+
// NOT route through this (it resolves the active resource class's allowRead, which honors
|
|
4493
|
+
// endpoint subclass overrides and can't be shadowed by a record attribute of the same name).
|
|
4494
|
+
if (primaryStore?.encoder?.structPrototype) {
|
|
4495
|
+
Object.defineProperty(primaryStore.encoder.structPrototype, 'allowRead', {
|
|
4496
|
+
value: function (user, target, context) {
|
|
4497
|
+
return TableResource.prototype.allowRead.call(this, user, target, context);
|
|
4498
|
+
},
|
|
4499
|
+
configurable: true,
|
|
4500
|
+
writable: true,
|
|
4501
|
+
});
|
|
4502
|
+
}
|
|
4287
4503
|
TableResource.updatedAttributes(); // on creation, update accessors as well
|
|
4288
4504
|
if (expirationMs)
|
|
4289
4505
|
TableResource.setTTLExpiration(expirationMs / 1000);
|