node-type-registry 0.22.0 → 0.24.0

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.
@@ -0,0 +1,2 @@
1
+ import type { NodeTypeDefinition } from '../types';
2
+ export declare const AuthzAppMembership: NodeTypeDefinition;
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AuthzAppMembership = void 0;
4
+ exports.AuthzAppMembership = {
5
+ name: 'AuthzAppMembership',
6
+ slug: 'authz_app_membership_check',
7
+ category: 'authz',
8
+ display_name: 'App Membership Check',
9
+ description: 'App-level membership check (hardcoded membership_type=1). Verifies the user has app membership (optionally with specific permission) without binding to any entity from the row. Uses EXISTS subquery against SPRT table. For entity-scoped checks (org, channel, etc.), use AuthzEntityMembership instead.',
10
+ parameter_schema: {
11
+ type: 'object',
12
+ properties: {
13
+ permission: {
14
+ type: 'string',
15
+ description: 'Single permission name to check (resolved to bitstring mask)',
16
+ },
17
+ permissions: {
18
+ type: 'array',
19
+ items: {
20
+ type: 'string',
21
+ },
22
+ description: 'Multiple permission names to check (ORed together into mask)',
23
+ },
24
+ is_admin: {
25
+ type: 'boolean',
26
+ description: 'If true, require is_admin flag',
27
+ },
28
+ is_owner: {
29
+ type: 'boolean',
30
+ description: 'If true, require is_owner flag',
31
+ },
32
+ },
33
+ required: [],
34
+ },
35
+ tags: ['membership', 'authz'],
36
+ };
package/authz/index.d.ts CHANGED
@@ -1,11 +1,11 @@
1
1
  export { AuthzAllowAll } from './authz-allow-all';
2
+ export { AuthzAppMembership } from './authz-app-membership';
2
3
  export { AuthzComposite } from './authz-composite';
3
4
  export { AuthzDenyAll } from './authz-deny-all';
4
5
  export { AuthzDirectOwner } from './authz-direct-owner';
5
6
  export { AuthzDirectOwnerAny } from './authz-direct-owner-any';
6
7
  export { AuthzEntityMembership } from './authz-entity-membership';
7
8
  export { AuthzMemberList } from './authz-member-list';
8
- export { AuthzMembership } from './authz-membership-check';
9
9
  export { AuthzNotReadOnly } from './authz-not-read-only';
10
10
  export { AuthzOrgHierarchy } from './authz-org-hierarchy';
11
11
  export { AuthzPeerOwnership } from './authz-peer-ownership';
package/authz/index.js CHANGED
@@ -1,8 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.AuthzTemporal = exports.AuthzRelatedPeerOwnership = exports.AuthzRelatedMemberList = exports.AuthzRelatedEntityMembership = exports.AuthzPublishable = exports.AuthzPeerOwnership = exports.AuthzOrgHierarchy = exports.AuthzNotReadOnly = exports.AuthzMembership = exports.AuthzMemberList = exports.AuthzEntityMembership = exports.AuthzDirectOwnerAny = exports.AuthzDirectOwner = exports.AuthzDenyAll = exports.AuthzComposite = exports.AuthzAllowAll = void 0;
3
+ exports.AuthzTemporal = exports.AuthzRelatedPeerOwnership = exports.AuthzRelatedMemberList = exports.AuthzRelatedEntityMembership = exports.AuthzPublishable = exports.AuthzPeerOwnership = exports.AuthzOrgHierarchy = exports.AuthzNotReadOnly = exports.AuthzMemberList = exports.AuthzEntityMembership = exports.AuthzDirectOwnerAny = exports.AuthzDirectOwner = exports.AuthzDenyAll = exports.AuthzComposite = exports.AuthzAppMembership = exports.AuthzAllowAll = void 0;
4
4
  var authz_allow_all_1 = require("./authz-allow-all");
5
5
  Object.defineProperty(exports, "AuthzAllowAll", { enumerable: true, get: function () { return authz_allow_all_1.AuthzAllowAll; } });
6
+ var authz_app_membership_1 = require("./authz-app-membership");
7
+ Object.defineProperty(exports, "AuthzAppMembership", { enumerable: true, get: function () { return authz_app_membership_1.AuthzAppMembership; } });
6
8
  var authz_composite_1 = require("./authz-composite");
7
9
  Object.defineProperty(exports, "AuthzComposite", { enumerable: true, get: function () { return authz_composite_1.AuthzComposite; } });
8
10
  var authz_deny_all_1 = require("./authz-deny-all");
@@ -15,8 +17,6 @@ var authz_entity_membership_1 = require("./authz-entity-membership");
15
17
  Object.defineProperty(exports, "AuthzEntityMembership", { enumerable: true, get: function () { return authz_entity_membership_1.AuthzEntityMembership; } });
16
18
  var authz_member_list_1 = require("./authz-member-list");
17
19
  Object.defineProperty(exports, "AuthzMemberList", { enumerable: true, get: function () { return authz_member_list_1.AuthzMemberList; } });
18
- var authz_membership_check_1 = require("./authz-membership-check");
19
- Object.defineProperty(exports, "AuthzMembership", { enumerable: true, get: function () { return authz_membership_check_1.AuthzMembership; } });
20
20
  var authz_not_read_only_1 = require("./authz-not-read-only");
21
21
  Object.defineProperty(exports, "AuthzNotReadOnly", { enumerable: true, get: function () { return authz_not_read_only_1.AuthzNotReadOnly; } });
22
22
  var authz_org_hierarchy_1 = require("./authz-org-hierarchy");
@@ -38,6 +38,12 @@ export interface DataEntityMembershipParams {
38
38
  include_id?: boolean;
39
39
  include_user_fk?: boolean;
40
40
  }
41
+ /** Gates a table behind a feature flag backed by the cap tables. Attaches a BEFORE INSERT trigger that checks whether the named feature cap value is > 0. Features are modeled as caps with max=0 (disabled) or max=1 (enabled) in limit_caps / limit_caps_defaults tables. Resolution: COALESCE(per-entity cap, scope default, 0). */
42
+ export interface DataFeatureFlagParams {
43
+ feature_name: string;
44
+ scope?: 'app' | 'org';
45
+ entity_field?: string;
46
+ }
41
47
  /** BEFORE INSERT trigger that forces a field to the value of jwt_public.current_user_id(). Prevents clients from spoofing the actor/uploader identity. The field value is always overwritten regardless of what the client provides. */
42
48
  export interface DataForceCurrentUserParams {
43
49
  field_name?: string;
@@ -95,6 +101,13 @@ export interface DataJobTriggerParams {
95
101
  run_at_delay?: string;
96
102
  max_attempts?: number;
97
103
  }
104
+ /** Declaratively attaches limit-tracking triggers to a table. On INSERT the named limit is incremented; on DELETE it is decremented. Requires a provisioned limits_module for the target scope. */
105
+ export interface DataLimitCounterParams {
106
+ limit_name: string;
107
+ scope?: 'app' | 'org';
108
+ actor_field?: string;
109
+ events?: ('INSERT' | 'DELETE' | 'UPDATE')[];
110
+ }
98
111
  /** Adds a JSONB column with optional GIN index for containment queries (@>, ?, ?|, ?&). Standard pattern for semi-structured metadata. */
99
112
  export interface DataJsonbParams {
100
113
  field_name?: string;
@@ -284,6 +297,13 @@ export interface SearchVectorParams {
284
297
  }
285
298
  /** Allows all access. Generates TRUE expression. */
286
299
  export type AuthzAllowAllParams = {};
300
+ /** App-level membership check (hardcoded membership_type=1). Verifies the user has app membership (optionally with specific permission) without binding to any entity from the row. Uses EXISTS subquery against SPRT table. For entity-scoped checks (org, channel, etc.), use AuthzEntityMembership instead. */
301
+ export interface AuthzAppMembershipParams {
302
+ permission?: string;
303
+ permissions?: string[];
304
+ is_admin?: boolean;
305
+ is_owner?: boolean;
306
+ }
287
307
  /** Composite authorization policy that combines multiple authorization nodes using boolean logic (AND/OR). The data field contains a JSONB AST with nested authorization nodes. */
288
308
  export interface AuthzCompositeParams {
289
309
  BoolExpr?: {
@@ -318,15 +338,6 @@ export interface AuthzEntityMembershipParams {
318
338
  export interface AuthzMemberListParams {
319
339
  array_field: string;
320
340
  }
321
- /** Membership check that verifies the user has membership (optionally with specific permission) without binding to any entity from the row. Uses EXISTS subquery against SPRT table. */
322
- export interface AuthzMembershipParams {
323
- membership_type?: number | string;
324
- entity_type?: string;
325
- permission?: string;
326
- permissions?: string[];
327
- is_admin?: boolean;
328
- is_owner?: boolean;
329
- }
330
341
  /** Restrictive policy that blocks read-only members from mutations. Checks actor_id + is_read_only IS NOT TRUE on the SPRT. Designed to run as a restrictive counterpart after a permissive AuthzEntityMembership policy has already verified membership. */
331
342
  export interface AuthzNotReadOnlyParams {
332
343
  entity_field: string;
@@ -525,7 +536,7 @@ export interface BlueprintField {
525
536
  /** An RLS policy entry for a blueprint table. Uses $type to match the blueprint JSON convention. */
526
537
  export interface BlueprintPolicy {
527
538
  /** Authz* policy type name (e.g., "AuthzDirectOwner", "AuthzAllowAll"). */
528
- $type: 'AuthzAllowAll' | 'AuthzComposite' | 'AuthzDenyAll' | 'AuthzDirectOwner' | 'AuthzDirectOwnerAny' | 'AuthzEntityMembership' | 'AuthzMemberList' | 'AuthzMembership' | 'AuthzNotReadOnly' | 'AuthzOrgHierarchy' | 'AuthzPeerOwnership' | 'AuthzPublishable' | 'AuthzRelatedEntityMembership' | 'AuthzRelatedMemberList' | 'AuthzRelatedPeerOwnership' | 'AuthzTemporal';
539
+ $type: 'AuthzAllowAll' | 'AuthzAppMembership' | 'AuthzComposite' | 'AuthzDenyAll' | 'AuthzDirectOwner' | 'AuthzDirectOwnerAny' | 'AuthzEntityMembership' | 'AuthzMemberList' | 'AuthzNotReadOnly' | 'AuthzOrgHierarchy' | 'AuthzPeerOwnership' | 'AuthzPublishable' | 'AuthzRelatedEntityMembership' | 'AuthzRelatedMemberList' | 'AuthzRelatedPeerOwnership' | 'AuthzTemporal';
529
540
  /** Privileges this policy applies to (e.g., ["select"], ["insert", "update", "delete"]). */
530
541
  privileges?: string[];
531
542
  /** Whether this policy is permissive (true) or restrictive (false). Defaults to true. */
@@ -703,11 +714,14 @@ export interface BlueprintEntityType {
703
714
  storage?: BlueprintStorageConfig;
704
715
  }
705
716
  /** String shorthand -- just the node type name. */
706
- export type BlueprintNodeShorthand = 'AuthzAllowAll' | 'AuthzComposite' | 'AuthzDenyAll' | 'AuthzDirectOwner' | 'AuthzDirectOwnerAny' | 'AuthzEntityMembership' | 'AuthzMemberList' | 'AuthzMembership' | 'AuthzNotReadOnly' | 'AuthzOrgHierarchy' | 'AuthzPeerOwnership' | 'AuthzPublishable' | 'AuthzRelatedEntityMembership' | 'AuthzRelatedMemberList' | 'AuthzRelatedPeerOwnership' | 'AuthzTemporal' | 'DataCompositeField' | 'DataDirectOwner' | 'DataEntityMembership' | 'DataForceCurrentUser' | 'DataId' | 'DataImageEmbedding' | 'DataImmutableFields' | 'DataInflection' | 'DataInheritFromParent' | 'DataJobTrigger' | 'DataJsonb' | 'DataOwnedFields' | 'DataOwnershipInEntity' | 'DataPeoplestamps' | 'DataPublishable' | 'DataSlug' | 'DataSoftDelete' | 'DataStatusField' | 'DataTags' | 'DataTimestamps' | 'SearchBm25' | 'SearchFullText' | 'SearchSpatial' | 'SearchSpatialAggregate' | 'SearchTrgm' | 'SearchUnified' | 'SearchVector' | 'TableOrganizationSettings' | 'TableUserProfiles' | 'TableUserSettings';
717
+ export type BlueprintNodeShorthand = 'AuthzAllowAll' | 'AuthzAppMembership' | 'AuthzComposite' | 'AuthzDenyAll' | 'AuthzDirectOwner' | 'AuthzDirectOwnerAny' | 'AuthzEntityMembership' | 'AuthzMemberList' | 'AuthzNotReadOnly' | 'AuthzOrgHierarchy' | 'AuthzPeerOwnership' | 'AuthzPublishable' | 'AuthzRelatedEntityMembership' | 'AuthzRelatedMemberList' | 'AuthzRelatedPeerOwnership' | 'AuthzTemporal' | 'DataCompositeField' | 'DataDirectOwner' | 'DataEntityMembership' | 'DataFeatureFlag' | 'DataForceCurrentUser' | 'DataId' | 'DataImageEmbedding' | 'DataImmutableFields' | 'DataInflection' | 'DataInheritFromParent' | 'DataJobTrigger' | 'DataLimitCounter' | 'DataJsonb' | 'DataOwnedFields' | 'DataOwnershipInEntity' | 'DataPeoplestamps' | 'DataPublishable' | 'DataSlug' | 'DataSoftDelete' | 'DataStatusField' | 'DataTags' | 'DataTimestamps' | 'SearchBm25' | 'SearchFullText' | 'SearchSpatial' | 'SearchSpatialAggregate' | 'SearchTrgm' | 'SearchUnified' | 'SearchVector' | 'TableOrganizationSettings' | 'TableUserProfiles' | 'TableUserSettings';
707
718
  /** Object form -- { $type, data } with typed parameters. */
708
719
  export type BlueprintNodeObject = {
709
720
  $type: 'AuthzAllowAll';
710
721
  data?: Record<string, never>;
722
+ } | {
723
+ $type: 'AuthzAppMembership';
724
+ data: AuthzAppMembershipParams;
711
725
  } | {
712
726
  $type: 'AuthzComposite';
713
727
  data: AuthzCompositeParams;
@@ -726,9 +740,6 @@ export type BlueprintNodeObject = {
726
740
  } | {
727
741
  $type: 'AuthzMemberList';
728
742
  data: AuthzMemberListParams;
729
- } | {
730
- $type: 'AuthzMembership';
731
- data: AuthzMembershipParams;
732
743
  } | {
733
744
  $type: 'AuthzNotReadOnly';
734
745
  data: AuthzNotReadOnlyParams;
@@ -762,6 +773,9 @@ export type BlueprintNodeObject = {
762
773
  } | {
763
774
  $type: 'DataEntityMembership';
764
775
  data: DataEntityMembershipParams;
776
+ } | {
777
+ $type: 'DataFeatureFlag';
778
+ data: DataFeatureFlagParams;
765
779
  } | {
766
780
  $type: 'DataForceCurrentUser';
767
781
  data: DataForceCurrentUserParams;
@@ -783,6 +797,9 @@ export type BlueprintNodeObject = {
783
797
  } | {
784
798
  $type: 'DataJobTrigger';
785
799
  data: DataJobTriggerParams;
800
+ } | {
801
+ $type: 'DataLimitCounter';
802
+ data: DataLimitCounterParams;
786
803
  } | {
787
804
  $type: 'DataJsonb';
788
805
  data: DataJsonbParams;
@@ -0,0 +1,2 @@
1
+ import type { NodeTypeDefinition } from '../types';
2
+ export declare const DataFeatureFlag: NodeTypeDefinition;
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DataFeatureFlag = void 0;
4
+ exports.DataFeatureFlag = {
5
+ name: 'DataFeatureFlag',
6
+ slug: 'data_feature_flag',
7
+ category: 'data',
8
+ display_name: 'Feature Flag',
9
+ description: 'Gates a table behind a feature flag backed by the cap tables. Attaches a BEFORE INSERT trigger that checks whether the named feature cap value is > 0. Features are modeled as caps with max=0 (disabled) or max=1 (enabled) in limit_caps / limit_caps_defaults tables. Resolution: COALESCE(per-entity cap, scope default, 0).',
10
+ parameter_schema: {
11
+ type: 'object',
12
+ properties: {
13
+ feature_name: {
14
+ type: 'string',
15
+ description: 'Cap name representing this feature (must match a limit_caps_defaults entry with max=0 or max=1)',
16
+ },
17
+ scope: {
18
+ type: 'string',
19
+ enum: ['app', 'org'],
20
+ description: 'Feature scope: "app" (membership_type=1, app-level caps) or "org" (membership_type=2, per-entity caps)',
21
+ default: 'app',
22
+ },
23
+ entity_field: {
24
+ type: 'string',
25
+ format: 'column-ref',
26
+ description: 'Column on the target table that holds the entity id for per-entity cap lookups (only used for org scope)',
27
+ default: 'entity_id',
28
+ },
29
+ },
30
+ required: ['feature_name'],
31
+ },
32
+ tags: ['limits', 'triggers', 'feature-flags', 'billing', 'caps'],
33
+ };
@@ -0,0 +1,2 @@
1
+ import type { NodeTypeDefinition } from '../types';
2
+ export declare const DataLimitCounter: NodeTypeDefinition;
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DataLimitCounter = void 0;
4
+ exports.DataLimitCounter = {
5
+ name: 'DataLimitCounter',
6
+ slug: 'data_limit_counter',
7
+ category: 'data',
8
+ display_name: 'Limit Counter',
9
+ description: 'Declaratively attaches limit-tracking triggers to a table. On INSERT the named limit is incremented; on DELETE it is decremented. Requires a provisioned limits_module for the target scope.',
10
+ parameter_schema: {
11
+ type: 'object',
12
+ properties: {
13
+ limit_name: {
14
+ type: 'string',
15
+ description: 'Name of the limit to track (must match a default_limits entry, e.g. "projects", "members")',
16
+ },
17
+ scope: {
18
+ type: 'string',
19
+ enum: ['app', 'org'],
20
+ description: 'Limit scope: "app" (membership_type=1, user-level) or "org" (membership_type=2, entity-level)',
21
+ default: 'app',
22
+ },
23
+ actor_field: {
24
+ type: 'string',
25
+ format: 'column-ref',
26
+ description: 'Column on the target table that holds the actor or entity id used for limit lookup',
27
+ default: 'owner_id',
28
+ },
29
+ events: {
30
+ type: 'array',
31
+ items: {
32
+ type: 'string',
33
+ enum: ['INSERT', 'DELETE', 'UPDATE'],
34
+ },
35
+ description: 'Which DML events to attach triggers for',
36
+ default: ['INSERT', 'DELETE'],
37
+ },
38
+ },
39
+ required: ['limit_name'],
40
+ },
41
+ tags: ['limits', 'triggers', 'billing'],
42
+ };
package/data/index.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  export { DataCompositeField } from './data-composite-field';
2
2
  export { DataDirectOwner } from './data-direct-owner';
3
3
  export { DataEntityMembership } from './data-entity-membership';
4
+ export { DataFeatureFlag } from './data-feature-flag';
4
5
  export { DataForceCurrentUser } from './data-force-current-user';
5
6
  export { DataId } from './data-id';
6
7
  export { DataImageEmbedding } from './data-image-embedding';
@@ -8,6 +9,7 @@ export { DataImmutableFields } from './data-immutable-fields';
8
9
  export { DataInflection } from './data-inflection';
9
10
  export { DataInheritFromParent } from './data-inherit-from-parent';
10
11
  export { DataJobTrigger } from './data-job-trigger';
12
+ export { DataLimitCounter } from './data-limit-counter';
11
13
  export { DataJsonb } from './data-jsonb';
12
14
  export { DataOwnedFields } from './data-owned-fields';
13
15
  export { DataOwnershipInEntity } from './data-ownership-in-entity';
package/data/index.js CHANGED
@@ -1,12 +1,14 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.TableUserSettings = exports.TableUserProfiles = exports.TableOrganizationSettings = exports.SearchVector = exports.SearchUnified = exports.SearchTrgm = exports.SearchSpatialAggregate = exports.SearchSpatial = exports.SearchFullText = exports.SearchBm25 = exports.DataTimestamps = exports.DataTags = exports.DataStatusField = exports.DataSoftDelete = exports.DataSlug = exports.DataPublishable = exports.DataPeoplestamps = exports.DataOwnershipInEntity = exports.DataOwnedFields = exports.DataJsonb = exports.DataJobTrigger = exports.DataInheritFromParent = exports.DataInflection = exports.DataImmutableFields = exports.DataImageEmbedding = exports.DataId = exports.DataForceCurrentUser = exports.DataEntityMembership = exports.DataDirectOwner = exports.DataCompositeField = void 0;
3
+ exports.TableUserSettings = exports.TableUserProfiles = exports.TableOrganizationSettings = exports.SearchVector = exports.SearchUnified = exports.SearchTrgm = exports.SearchSpatialAggregate = exports.SearchSpatial = exports.SearchFullText = exports.SearchBm25 = exports.DataTimestamps = exports.DataTags = exports.DataStatusField = exports.DataSoftDelete = exports.DataSlug = exports.DataPublishable = exports.DataPeoplestamps = exports.DataOwnershipInEntity = exports.DataOwnedFields = exports.DataJsonb = exports.DataLimitCounter = exports.DataJobTrigger = exports.DataInheritFromParent = exports.DataInflection = exports.DataImmutableFields = exports.DataImageEmbedding = exports.DataId = exports.DataForceCurrentUser = exports.DataFeatureFlag = exports.DataEntityMembership = exports.DataDirectOwner = exports.DataCompositeField = void 0;
4
4
  var data_composite_field_1 = require("./data-composite-field");
5
5
  Object.defineProperty(exports, "DataCompositeField", { enumerable: true, get: function () { return data_composite_field_1.DataCompositeField; } });
6
6
  var data_direct_owner_1 = require("./data-direct-owner");
7
7
  Object.defineProperty(exports, "DataDirectOwner", { enumerable: true, get: function () { return data_direct_owner_1.DataDirectOwner; } });
8
8
  var data_entity_membership_1 = require("./data-entity-membership");
9
9
  Object.defineProperty(exports, "DataEntityMembership", { enumerable: true, get: function () { return data_entity_membership_1.DataEntityMembership; } });
10
+ var data_feature_flag_1 = require("./data-feature-flag");
11
+ Object.defineProperty(exports, "DataFeatureFlag", { enumerable: true, get: function () { return data_feature_flag_1.DataFeatureFlag; } });
10
12
  var data_force_current_user_1 = require("./data-force-current-user");
11
13
  Object.defineProperty(exports, "DataForceCurrentUser", { enumerable: true, get: function () { return data_force_current_user_1.DataForceCurrentUser; } });
12
14
  var data_id_1 = require("./data-id");
@@ -21,6 +23,8 @@ var data_inherit_from_parent_1 = require("./data-inherit-from-parent");
21
23
  Object.defineProperty(exports, "DataInheritFromParent", { enumerable: true, get: function () { return data_inherit_from_parent_1.DataInheritFromParent; } });
22
24
  var data_job_trigger_1 = require("./data-job-trigger");
23
25
  Object.defineProperty(exports, "DataJobTrigger", { enumerable: true, get: function () { return data_job_trigger_1.DataJobTrigger; } });
26
+ var data_limit_counter_1 = require("./data-limit-counter");
27
+ Object.defineProperty(exports, "DataLimitCounter", { enumerable: true, get: function () { return data_limit_counter_1.DataLimitCounter; } });
24
28
  var data_jsonb_1 = require("./data-jsonb");
25
29
  Object.defineProperty(exports, "DataJsonb", { enumerable: true, get: function () { return data_jsonb_1.DataJsonb; } });
26
30
  var data_owned_fields_1 = require("./data-owned-fields");
@@ -0,0 +1,2 @@
1
+ import type { NodeTypeDefinition } from '../types';
2
+ export declare const AuthzAppMembership: NodeTypeDefinition;
@@ -0,0 +1,33 @@
1
+ export const AuthzAppMembership = {
2
+ name: 'AuthzAppMembership',
3
+ slug: 'authz_app_membership_check',
4
+ category: 'authz',
5
+ display_name: 'App Membership Check',
6
+ description: 'App-level membership check (hardcoded membership_type=1). Verifies the user has app membership (optionally with specific permission) without binding to any entity from the row. Uses EXISTS subquery against SPRT table. For entity-scoped checks (org, channel, etc.), use AuthzEntityMembership instead.',
7
+ parameter_schema: {
8
+ type: 'object',
9
+ properties: {
10
+ permission: {
11
+ type: 'string',
12
+ description: 'Single permission name to check (resolved to bitstring mask)',
13
+ },
14
+ permissions: {
15
+ type: 'array',
16
+ items: {
17
+ type: 'string',
18
+ },
19
+ description: 'Multiple permission names to check (ORed together into mask)',
20
+ },
21
+ is_admin: {
22
+ type: 'boolean',
23
+ description: 'If true, require is_admin flag',
24
+ },
25
+ is_owner: {
26
+ type: 'boolean',
27
+ description: 'If true, require is_owner flag',
28
+ },
29
+ },
30
+ required: [],
31
+ },
32
+ tags: ['membership', 'authz'],
33
+ };
@@ -1,11 +1,11 @@
1
1
  export { AuthzAllowAll } from './authz-allow-all';
2
+ export { AuthzAppMembership } from './authz-app-membership';
2
3
  export { AuthzComposite } from './authz-composite';
3
4
  export { AuthzDenyAll } from './authz-deny-all';
4
5
  export { AuthzDirectOwner } from './authz-direct-owner';
5
6
  export { AuthzDirectOwnerAny } from './authz-direct-owner-any';
6
7
  export { AuthzEntityMembership } from './authz-entity-membership';
7
8
  export { AuthzMemberList } from './authz-member-list';
8
- export { AuthzMembership } from './authz-membership-check';
9
9
  export { AuthzNotReadOnly } from './authz-not-read-only';
10
10
  export { AuthzOrgHierarchy } from './authz-org-hierarchy';
11
11
  export { AuthzPeerOwnership } from './authz-peer-ownership';
@@ -1,11 +1,11 @@
1
1
  export { AuthzAllowAll } from './authz-allow-all';
2
+ export { AuthzAppMembership } from './authz-app-membership';
2
3
  export { AuthzComposite } from './authz-composite';
3
4
  export { AuthzDenyAll } from './authz-deny-all';
4
5
  export { AuthzDirectOwner } from './authz-direct-owner';
5
6
  export { AuthzDirectOwnerAny } from './authz-direct-owner-any';
6
7
  export { AuthzEntityMembership } from './authz-entity-membership';
7
8
  export { AuthzMemberList } from './authz-member-list';
8
- export { AuthzMembership } from './authz-membership-check';
9
9
  export { AuthzNotReadOnly } from './authz-not-read-only';
10
10
  export { AuthzOrgHierarchy } from './authz-org-hierarchy';
11
11
  export { AuthzPeerOwnership } from './authz-peer-ownership';
@@ -38,6 +38,12 @@ export interface DataEntityMembershipParams {
38
38
  include_id?: boolean;
39
39
  include_user_fk?: boolean;
40
40
  }
41
+ /** Gates a table behind a feature flag backed by the cap tables. Attaches a BEFORE INSERT trigger that checks whether the named feature cap value is > 0. Features are modeled as caps with max=0 (disabled) or max=1 (enabled) in limit_caps / limit_caps_defaults tables. Resolution: COALESCE(per-entity cap, scope default, 0). */
42
+ export interface DataFeatureFlagParams {
43
+ feature_name: string;
44
+ scope?: 'app' | 'org';
45
+ entity_field?: string;
46
+ }
41
47
  /** BEFORE INSERT trigger that forces a field to the value of jwt_public.current_user_id(). Prevents clients from spoofing the actor/uploader identity. The field value is always overwritten regardless of what the client provides. */
42
48
  export interface DataForceCurrentUserParams {
43
49
  field_name?: string;
@@ -95,6 +101,13 @@ export interface DataJobTriggerParams {
95
101
  run_at_delay?: string;
96
102
  max_attempts?: number;
97
103
  }
104
+ /** Declaratively attaches limit-tracking triggers to a table. On INSERT the named limit is incremented; on DELETE it is decremented. Requires a provisioned limits_module for the target scope. */
105
+ export interface DataLimitCounterParams {
106
+ limit_name: string;
107
+ scope?: 'app' | 'org';
108
+ actor_field?: string;
109
+ events?: ('INSERT' | 'DELETE' | 'UPDATE')[];
110
+ }
98
111
  /** Adds a JSONB column with optional GIN index for containment queries (@>, ?, ?|, ?&). Standard pattern for semi-structured metadata. */
99
112
  export interface DataJsonbParams {
100
113
  field_name?: string;
@@ -284,6 +297,13 @@ export interface SearchVectorParams {
284
297
  }
285
298
  /** Allows all access. Generates TRUE expression. */
286
299
  export type AuthzAllowAllParams = {};
300
+ /** App-level membership check (hardcoded membership_type=1). Verifies the user has app membership (optionally with specific permission) without binding to any entity from the row. Uses EXISTS subquery against SPRT table. For entity-scoped checks (org, channel, etc.), use AuthzEntityMembership instead. */
301
+ export interface AuthzAppMembershipParams {
302
+ permission?: string;
303
+ permissions?: string[];
304
+ is_admin?: boolean;
305
+ is_owner?: boolean;
306
+ }
287
307
  /** Composite authorization policy that combines multiple authorization nodes using boolean logic (AND/OR). The data field contains a JSONB AST with nested authorization nodes. */
288
308
  export interface AuthzCompositeParams {
289
309
  BoolExpr?: {
@@ -318,15 +338,6 @@ export interface AuthzEntityMembershipParams {
318
338
  export interface AuthzMemberListParams {
319
339
  array_field: string;
320
340
  }
321
- /** Membership check that verifies the user has membership (optionally with specific permission) without binding to any entity from the row. Uses EXISTS subquery against SPRT table. */
322
- export interface AuthzMembershipParams {
323
- membership_type?: number | string;
324
- entity_type?: string;
325
- permission?: string;
326
- permissions?: string[];
327
- is_admin?: boolean;
328
- is_owner?: boolean;
329
- }
330
341
  /** Restrictive policy that blocks read-only members from mutations. Checks actor_id + is_read_only IS NOT TRUE on the SPRT. Designed to run as a restrictive counterpart after a permissive AuthzEntityMembership policy has already verified membership. */
331
342
  export interface AuthzNotReadOnlyParams {
332
343
  entity_field: string;
@@ -525,7 +536,7 @@ export interface BlueprintField {
525
536
  /** An RLS policy entry for a blueprint table. Uses $type to match the blueprint JSON convention. */
526
537
  export interface BlueprintPolicy {
527
538
  /** Authz* policy type name (e.g., "AuthzDirectOwner", "AuthzAllowAll"). */
528
- $type: 'AuthzAllowAll' | 'AuthzComposite' | 'AuthzDenyAll' | 'AuthzDirectOwner' | 'AuthzDirectOwnerAny' | 'AuthzEntityMembership' | 'AuthzMemberList' | 'AuthzMembership' | 'AuthzNotReadOnly' | 'AuthzOrgHierarchy' | 'AuthzPeerOwnership' | 'AuthzPublishable' | 'AuthzRelatedEntityMembership' | 'AuthzRelatedMemberList' | 'AuthzRelatedPeerOwnership' | 'AuthzTemporal';
539
+ $type: 'AuthzAllowAll' | 'AuthzAppMembership' | 'AuthzComposite' | 'AuthzDenyAll' | 'AuthzDirectOwner' | 'AuthzDirectOwnerAny' | 'AuthzEntityMembership' | 'AuthzMemberList' | 'AuthzNotReadOnly' | 'AuthzOrgHierarchy' | 'AuthzPeerOwnership' | 'AuthzPublishable' | 'AuthzRelatedEntityMembership' | 'AuthzRelatedMemberList' | 'AuthzRelatedPeerOwnership' | 'AuthzTemporal';
529
540
  /** Privileges this policy applies to (e.g., ["select"], ["insert", "update", "delete"]). */
530
541
  privileges?: string[];
531
542
  /** Whether this policy is permissive (true) or restrictive (false). Defaults to true. */
@@ -703,11 +714,14 @@ export interface BlueprintEntityType {
703
714
  storage?: BlueprintStorageConfig;
704
715
  }
705
716
  /** String shorthand -- just the node type name. */
706
- export type BlueprintNodeShorthand = 'AuthzAllowAll' | 'AuthzComposite' | 'AuthzDenyAll' | 'AuthzDirectOwner' | 'AuthzDirectOwnerAny' | 'AuthzEntityMembership' | 'AuthzMemberList' | 'AuthzMembership' | 'AuthzNotReadOnly' | 'AuthzOrgHierarchy' | 'AuthzPeerOwnership' | 'AuthzPublishable' | 'AuthzRelatedEntityMembership' | 'AuthzRelatedMemberList' | 'AuthzRelatedPeerOwnership' | 'AuthzTemporal' | 'DataCompositeField' | 'DataDirectOwner' | 'DataEntityMembership' | 'DataForceCurrentUser' | 'DataId' | 'DataImageEmbedding' | 'DataImmutableFields' | 'DataInflection' | 'DataInheritFromParent' | 'DataJobTrigger' | 'DataJsonb' | 'DataOwnedFields' | 'DataOwnershipInEntity' | 'DataPeoplestamps' | 'DataPublishable' | 'DataSlug' | 'DataSoftDelete' | 'DataStatusField' | 'DataTags' | 'DataTimestamps' | 'SearchBm25' | 'SearchFullText' | 'SearchSpatial' | 'SearchSpatialAggregate' | 'SearchTrgm' | 'SearchUnified' | 'SearchVector' | 'TableOrganizationSettings' | 'TableUserProfiles' | 'TableUserSettings';
717
+ export type BlueprintNodeShorthand = 'AuthzAllowAll' | 'AuthzAppMembership' | 'AuthzComposite' | 'AuthzDenyAll' | 'AuthzDirectOwner' | 'AuthzDirectOwnerAny' | 'AuthzEntityMembership' | 'AuthzMemberList' | 'AuthzNotReadOnly' | 'AuthzOrgHierarchy' | 'AuthzPeerOwnership' | 'AuthzPublishable' | 'AuthzRelatedEntityMembership' | 'AuthzRelatedMemberList' | 'AuthzRelatedPeerOwnership' | 'AuthzTemporal' | 'DataCompositeField' | 'DataDirectOwner' | 'DataEntityMembership' | 'DataFeatureFlag' | 'DataForceCurrentUser' | 'DataId' | 'DataImageEmbedding' | 'DataImmutableFields' | 'DataInflection' | 'DataInheritFromParent' | 'DataJobTrigger' | 'DataLimitCounter' | 'DataJsonb' | 'DataOwnedFields' | 'DataOwnershipInEntity' | 'DataPeoplestamps' | 'DataPublishable' | 'DataSlug' | 'DataSoftDelete' | 'DataStatusField' | 'DataTags' | 'DataTimestamps' | 'SearchBm25' | 'SearchFullText' | 'SearchSpatial' | 'SearchSpatialAggregate' | 'SearchTrgm' | 'SearchUnified' | 'SearchVector' | 'TableOrganizationSettings' | 'TableUserProfiles' | 'TableUserSettings';
707
718
  /** Object form -- { $type, data } with typed parameters. */
708
719
  export type BlueprintNodeObject = {
709
720
  $type: 'AuthzAllowAll';
710
721
  data?: Record<string, never>;
722
+ } | {
723
+ $type: 'AuthzAppMembership';
724
+ data: AuthzAppMembershipParams;
711
725
  } | {
712
726
  $type: 'AuthzComposite';
713
727
  data: AuthzCompositeParams;
@@ -726,9 +740,6 @@ export type BlueprintNodeObject = {
726
740
  } | {
727
741
  $type: 'AuthzMemberList';
728
742
  data: AuthzMemberListParams;
729
- } | {
730
- $type: 'AuthzMembership';
731
- data: AuthzMembershipParams;
732
743
  } | {
733
744
  $type: 'AuthzNotReadOnly';
734
745
  data: AuthzNotReadOnlyParams;
@@ -762,6 +773,9 @@ export type BlueprintNodeObject = {
762
773
  } | {
763
774
  $type: 'DataEntityMembership';
764
775
  data: DataEntityMembershipParams;
776
+ } | {
777
+ $type: 'DataFeatureFlag';
778
+ data: DataFeatureFlagParams;
765
779
  } | {
766
780
  $type: 'DataForceCurrentUser';
767
781
  data: DataForceCurrentUserParams;
@@ -783,6 +797,9 @@ export type BlueprintNodeObject = {
783
797
  } | {
784
798
  $type: 'DataJobTrigger';
785
799
  data: DataJobTriggerParams;
800
+ } | {
801
+ $type: 'DataLimitCounter';
802
+ data: DataLimitCounterParams;
786
803
  } | {
787
804
  $type: 'DataJsonb';
788
805
  data: DataJsonbParams;
@@ -0,0 +1,2 @@
1
+ import type { NodeTypeDefinition } from '../types';
2
+ export declare const DataFeatureFlag: NodeTypeDefinition;
@@ -0,0 +1,30 @@
1
+ export const DataFeatureFlag = {
2
+ name: 'DataFeatureFlag',
3
+ slug: 'data_feature_flag',
4
+ category: 'data',
5
+ display_name: 'Feature Flag',
6
+ description: 'Gates a table behind a feature flag backed by the cap tables. Attaches a BEFORE INSERT trigger that checks whether the named feature cap value is > 0. Features are modeled as caps with max=0 (disabled) or max=1 (enabled) in limit_caps / limit_caps_defaults tables. Resolution: COALESCE(per-entity cap, scope default, 0).',
7
+ parameter_schema: {
8
+ type: 'object',
9
+ properties: {
10
+ feature_name: {
11
+ type: 'string',
12
+ description: 'Cap name representing this feature (must match a limit_caps_defaults entry with max=0 or max=1)',
13
+ },
14
+ scope: {
15
+ type: 'string',
16
+ enum: ['app', 'org'],
17
+ description: 'Feature scope: "app" (membership_type=1, app-level caps) or "org" (membership_type=2, per-entity caps)',
18
+ default: 'app',
19
+ },
20
+ entity_field: {
21
+ type: 'string',
22
+ format: 'column-ref',
23
+ description: 'Column on the target table that holds the entity id for per-entity cap lookups (only used for org scope)',
24
+ default: 'entity_id',
25
+ },
26
+ },
27
+ required: ['feature_name'],
28
+ },
29
+ tags: ['limits', 'triggers', 'feature-flags', 'billing', 'caps'],
30
+ };
@@ -0,0 +1,2 @@
1
+ import type { NodeTypeDefinition } from '../types';
2
+ export declare const DataLimitCounter: NodeTypeDefinition;
@@ -0,0 +1,39 @@
1
+ export const DataLimitCounter = {
2
+ name: 'DataLimitCounter',
3
+ slug: 'data_limit_counter',
4
+ category: 'data',
5
+ display_name: 'Limit Counter',
6
+ description: 'Declaratively attaches limit-tracking triggers to a table. On INSERT the named limit is incremented; on DELETE it is decremented. Requires a provisioned limits_module for the target scope.',
7
+ parameter_schema: {
8
+ type: 'object',
9
+ properties: {
10
+ limit_name: {
11
+ type: 'string',
12
+ description: 'Name of the limit to track (must match a default_limits entry, e.g. "projects", "members")',
13
+ },
14
+ scope: {
15
+ type: 'string',
16
+ enum: ['app', 'org'],
17
+ description: 'Limit scope: "app" (membership_type=1, user-level) or "org" (membership_type=2, entity-level)',
18
+ default: 'app',
19
+ },
20
+ actor_field: {
21
+ type: 'string',
22
+ format: 'column-ref',
23
+ description: 'Column on the target table that holds the actor or entity id used for limit lookup',
24
+ default: 'owner_id',
25
+ },
26
+ events: {
27
+ type: 'array',
28
+ items: {
29
+ type: 'string',
30
+ enum: ['INSERT', 'DELETE', 'UPDATE'],
31
+ },
32
+ description: 'Which DML events to attach triggers for',
33
+ default: ['INSERT', 'DELETE'],
34
+ },
35
+ },
36
+ required: ['limit_name'],
37
+ },
38
+ tags: ['limits', 'triggers', 'billing'],
39
+ };
@@ -1,6 +1,7 @@
1
1
  export { DataCompositeField } from './data-composite-field';
2
2
  export { DataDirectOwner } from './data-direct-owner';
3
3
  export { DataEntityMembership } from './data-entity-membership';
4
+ export { DataFeatureFlag } from './data-feature-flag';
4
5
  export { DataForceCurrentUser } from './data-force-current-user';
5
6
  export { DataId } from './data-id';
6
7
  export { DataImageEmbedding } from './data-image-embedding';
@@ -8,6 +9,7 @@ export { DataImmutableFields } from './data-immutable-fields';
8
9
  export { DataInflection } from './data-inflection';
9
10
  export { DataInheritFromParent } from './data-inherit-from-parent';
10
11
  export { DataJobTrigger } from './data-job-trigger';
12
+ export { DataLimitCounter } from './data-limit-counter';
11
13
  export { DataJsonb } from './data-jsonb';
12
14
  export { DataOwnedFields } from './data-owned-fields';
13
15
  export { DataOwnershipInEntity } from './data-ownership-in-entity';
package/esm/data/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  export { DataCompositeField } from './data-composite-field';
2
2
  export { DataDirectOwner } from './data-direct-owner';
3
3
  export { DataEntityMembership } from './data-entity-membership';
4
+ export { DataFeatureFlag } from './data-feature-flag';
4
5
  export { DataForceCurrentUser } from './data-force-current-user';
5
6
  export { DataId } from './data-id';
6
7
  export { DataImageEmbedding } from './data-image-embedding';
@@ -8,6 +9,7 @@ export { DataImmutableFields } from './data-immutable-fields';
8
9
  export { DataInflection } from './data-inflection';
9
10
  export { DataInheritFromParent } from './data-inherit-from-parent';
10
11
  export { DataJobTrigger } from './data-job-trigger';
12
+ export { DataLimitCounter } from './data-limit-counter';
11
13
  export { DataJsonb } from './data-jsonb';
12
14
  export { DataOwnedFields } from './data-owned-fields';
13
15
  export { DataOwnershipInEntity } from './data-ownership-in-entity';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-type-registry",
3
- "version": "0.22.0",
3
+ "version": "0.24.0",
4
4
  "description": "Node type definitions for the Constructive blueprint system. Single source of truth for all Authz*, Data*, Relation*, and View* node types.",
5
5
  "author": "Constructive <developers@constructive.io>",
6
6
  "main": "index.js",
@@ -47,5 +47,5 @@
47
47
  "registry",
48
48
  "graphile"
49
49
  ],
50
- "gitHead": "0238640b70fed4b203eb84f48315c2bd807923b9"
50
+ "gitHead": "04633dc47399ffc9a5cefacfb0a90451acee988d"
51
51
  }
@@ -1,2 +0,0 @@
1
- import type { NodeTypeDefinition } from '../types';
2
- export declare const AuthzMembership: NodeTypeDefinition;
@@ -1,50 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.AuthzMembership = void 0;
4
- exports.AuthzMembership = {
5
- name: 'AuthzMembership',
6
- slug: 'authz_membership_check',
7
- category: 'authz',
8
- display_name: 'Membership Check',
9
- description: 'Membership check that verifies the user has membership (optionally with specific permission) without binding to any entity from the row. Uses EXISTS subquery against SPRT table.',
10
- parameter_schema: {
11
- type: 'object',
12
- properties: {
13
- membership_type: {
14
- type: [
15
- 'integer',
16
- 'string'
17
- ],
18
- description: 'Scope: 1=app, 2=org, 3+=dynamic entity types (or string name resolved via membership_types_module)'
19
- },
20
- entity_type: {
21
- type: 'string',
22
- description: "Entity type prefix (e.g. 'channel', 'department'). Resolved to membership_type integer via memberships_module lookup. Use instead of membership_type for readability."
23
- },
24
- permission: {
25
- type: 'string',
26
- description: 'Single permission name to check (resolved to bitstring mask)'
27
- },
28
- permissions: {
29
- type: 'array',
30
- items: {
31
- type: 'string'
32
- },
33
- description: 'Multiple permission names to check (ORed together into mask)'
34
- },
35
- is_admin: {
36
- type: 'boolean',
37
- description: 'If true, require is_admin flag'
38
- },
39
- is_owner: {
40
- type: 'boolean',
41
- description: 'If true, require is_owner flag'
42
- }
43
- },
44
- required: []
45
- },
46
- tags: [
47
- 'membership',
48
- 'authz'
49
- ]
50
- };
@@ -1,2 +0,0 @@
1
- import type { NodeTypeDefinition } from '../types';
2
- export declare const AuthzMembership: NodeTypeDefinition;
@@ -1,47 +0,0 @@
1
- export const AuthzMembership = {
2
- name: 'AuthzMembership',
3
- slug: 'authz_membership_check',
4
- category: 'authz',
5
- display_name: 'Membership Check',
6
- description: 'Membership check that verifies the user has membership (optionally with specific permission) without binding to any entity from the row. Uses EXISTS subquery against SPRT table.',
7
- parameter_schema: {
8
- type: 'object',
9
- properties: {
10
- membership_type: {
11
- type: [
12
- 'integer',
13
- 'string'
14
- ],
15
- description: 'Scope: 1=app, 2=org, 3+=dynamic entity types (or string name resolved via membership_types_module)'
16
- },
17
- entity_type: {
18
- type: 'string',
19
- description: "Entity type prefix (e.g. 'channel', 'department'). Resolved to membership_type integer via memberships_module lookup. Use instead of membership_type for readability."
20
- },
21
- permission: {
22
- type: 'string',
23
- description: 'Single permission name to check (resolved to bitstring mask)'
24
- },
25
- permissions: {
26
- type: 'array',
27
- items: {
28
- type: 'string'
29
- },
30
- description: 'Multiple permission names to check (ORed together into mask)'
31
- },
32
- is_admin: {
33
- type: 'boolean',
34
- description: 'If true, require is_admin flag'
35
- },
36
- is_owner: {
37
- type: 'boolean',
38
- description: 'If true, require is_owner flag'
39
- }
40
- },
41
- required: []
42
- },
43
- tags: [
44
- 'membership',
45
- 'authz'
46
- ]
47
- };