graphddb 0.4.3 → 0.5.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.
- package/README.md +5 -2
- package/dist/{chunk-BROCT574.js → chunk-7NPG5R7O.js} +1 -1
- package/dist/{chunk-CPTV3H2U.js → chunk-FMJIWFIS.js} +156 -30
- package/dist/{chunk-G5RWWBAL.js → chunk-J5A665UW.js} +23 -3
- package/dist/cli.js +781 -6
- package/dist/index.d.ts +65 -3
- package/dist/index.js +20 -4
- package/dist/testing/index.d.ts +1 -1
- package/dist/testing/index.js +2 -2
- package/dist/{types-BWOrWcbd.d.ts → types-B-F7jw9f.d.ts} +42 -0
- package/docs/cloudformation.md +294 -0
- package/package.json +4 -2
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { S as SelectableOf, g as PrimaryKeyOf, h as RequestContext, i as Middleware, j as ReadRequestKind, k as CtxModel, l as ReadParams, m as ReadRequestCtx, D as DynamoDBOperation, I as Item, E as Executor, n as RetryPolicy, o as ExecutionPlanSpec, p as EntityMetadata, K as KeyDefinition, G as GsiDefinition, q as ModelKind, F as FieldOptions, r as DynamoType, s as ProjectionTransform, t as MaintainEvent, u as MembershipPredicate, v as MaintainConsistency, w as MaintainUpdateMode, x as MembershipPredicateOp, y as RelationOptions, A as AggregateOptions, z as AggregateValue, H as SelectBuilderSpec, J as RawCondition, L as TransactionSpec, N as Manifest, O as RetryOverride, Q as ExecutionPlan, V as FieldMetadata, X as ResolvedKey, R as ReadExecOptions, a as ExecutorResult, B as BatchGetExecInput, P as PutInput, W as WriteExecOptions, b as WriteResult, U as UpdateInput, c as DeleteInput, d as BatchWriteExecItem, e as BatchExecOptions, T as TransactWriteExecItem, Y as CdcEmulatorOptions, Z as ChangeHandler, _ as Unsubscribe, $ as FaultSpec, a0 as ConcurrentRecomputeRef, C as ChangeEvent, a1 as EventLog, a2 as ReplayOptions, a3 as ShardId, a4 as ViewDefinition, a5 as RelationMetadata, a6 as TransactionItemSpec, a7 as MaintainEffect, M as ModelStatic, f as DDBModel, a8 as Param, a9 as ParamDescriptor, aa as DefinitionMap, ab as OperationDefinition, ac as WriteDefinitionOptions, ad as PartialQueryKeyOf, ae as StrictSelectSpec, af as ReadDefinitionOptions, ag as EntityInput, ah as UniqueQueryKeyOf, ai as EntityRef, aj as ConditionInput, ak as QueryModelContract, al as QueryMethodSpec, am as CommandModelContract, an as CommandMethodSpec, ao as ContractSpec, ap as QuerySpec, aq as CommandSpec, ar as ContextSpec, as as OperationsDocument, at as AnyOperationDefinition, au as BridgeBundle, av as ConditionSpec } from './types-
|
|
2
|
-
export { aw as AggregateMetadata, ax as BatchDeleteRequest, ay as BatchGetOptions, az as BatchGetRequest, aA as BatchGetResult, aB as BatchPutRequest, aC as BatchResult, aD as BatchWriteRequest, aE as CONTRACT_RANGE_FANOUT_CONCURRENCY, aF as CdcMode, aG as Change, aH as ChangeBatch, aI as ChangeEventName, aJ as ClockMode, aK as CollectionEffect, aL as CollectionOptions, aM as Column, aN as ColumnMap, aO as CommandContractMethodSpec, aP as CommandInputShape, aQ as CommandMethod, aR as CommandPlan, aS as CommandResolutionTarget, aT as CommandResultKind, aU as CommandSelectShape, aV as CompiledFragment, aW as ComposeSpec, aX as CondSlot, aY as ConditionCheckInput, aZ as Connection, a_ as ContractCallSignature, a$ as ContractCardinality, b0 as ContractCommandParams, b1 as ContractCommandResult, b2 as ContractComposeNode, b3 as ContractFromRef, b4 as ContractInputArity, b5 as ContractItem, b6 as ContractKeyFieldRef, b7 as ContractKeyInput, b8 as ContractKeyRef, b9 as ContractKeySpec, ba as ContractKind, bb as ContractMethodOp, bc as ContractParamRef, bd as ContractQueryParams, be as ContractResolution, bf as CounterAggregate, bg as CounterEffect, bh as CtxBase, bi as DEFAULT_MAX_ATTEMPTS, bj as DEFAULT_RETRY_POLICY, bk as DeleteOptions, bl as DeriveEffect, bm as DerivedEdgeWrite, bn as DerivedUpdate, bo as DescriptorBinding, bp as ENTITY_WRITES_MARKER, bq as EdgeEffect, br as EffectPath, bs as EmbeddedMetadata, bt as EmitEffect, bu as EntityWritesDefinition, bv as EntityWritesShape, bw as ExecutableCommandContract, bx as ExecutableQueryContract, by as FilterInput, bz as FilterSpec, bA as FragmentInput, bB as GsiDefinitionMarker, bC as GsiOptions, bD as IdempotencyEffect, bE as InProcessWriteDescriptor, bF as InputArity, bG as KeyDefinitionMarker, bH as KeySegment, bI as KeySlot, bJ as KeyStructure, bK as KeyedResult, bL as LIFECYCLE_CONTRACT_MARKER, bM as LifecycleContract, bN as LifecycleEffects, bO as LiteralParam, bP as MaintainItem, bQ as MaintainTrigger, bR as MaintenanceGraph, bS as ManifestEntity, bT as ManifestField, bU as ManifestFieldType, bV as ManifestGsi, bW as ManifestKey, bX as ManifestRelation, bY as ManifestTable, bZ as MembershipEffect, b_ as ModelRef, b$ as MutateMode, c0 as MutateOptions, c1 as MutateParallelResult, c2 as MutateTransactionResult, c3 as MutationBody, c4 as MutationDescriptorMap, c5 as MutationFragment, c6 as MutationInputProxy, c7 as MutationInputRef, c8 as MutationIntent, c9 as NumberParam, ca as OperationKind, cb as OperationSpec, cc as ParallelOpResult, cd as ParamKind, ce as ParamSpec, cf as ParamStructure, cg as PersistCtx, ch as PersistOrigin, ci as PlannedCommandMethod, cj as ProjectionMap, ck as ProjectionTransformOp, cl as PutOptions, cm as QueryContractMethodSpec, cn as QueryEnvelopeResult, co as QueryKeyOf, cp as QueryMethod, cq as QueryResult, cr as RangeConditionSpec, cs as ReadEnvelope, ct as ReadOpCtx, cu as ReadOpKind, cv as ReadOperationType, cw as ReadRouteDescriptor, cx as ReadRouteOptions, cy as ReadRouteResult, cz as RecordedCompose, cA as RelationBuilder, cB as RelationConsistency, cC as RelationLimitOptions, cD as RelationPattern, cE as RelationProjection, cF as RelationReadOptions, cG as RelationSelect, cH as RelationSpec, cI as RelationUpdateMode, cJ as RelationWriteOptions, cK as RequiresEffect, cL as Resolution, cM as RetryInfo, cN as RetryOperationKind, cO as SPEC_VERSION, cP as SegmentSpec, cQ as SegmentedKey, cR as SelectBuilder, cS as SelectOf, cT as SnapshotEffect, cU as StartingPosition, cV as StreamViewType, cW as StringParam, cX as TransactionContext, cY as TransactionItemType, cZ as UniqueEffect, c_ as Updatable, c$ as UpdateOptions, d0 as ViewSourceSlice, d1 as WhenSpec, d2 as WriteCtx, d3 as WriteDescriptor, d4 as WriteEnvelope, d5 as WriteInput, d6 as WriteKind, d7 as WriteLifecyclePhase, d8 as WriteMiddleware, d9 as WriteOperationType, da as WriteRecorder, db as WriteResultProjection, dc as attachModelClass, dd as buildDeleteInput, de as buildMaintenanceGraph, df as buildPutInput, dg as buildUpdateInput, dh as collectViewDefinitions, di as compileFragment, dj as compileMutationPlan, dk as compileSingleFragmentPlan, dl as cond, dm as contractOfMethodSpec, dn as definePlan, dp as entityWrites, dq as executeBatchGet, dr as executeBatchWrite, ds as executeCommandMethod, dt as executeDelete, du as executeKeyedBatchGet, dv as executePut, dw as executeQueryMethod, dx as executeRangeFanout, dy as executeTransaction, dz as executeUpdate, dA as from, dB as getEntityWrites, dC as gsi, dD as identity, dE as isColumn, dF as isCommandModelContract, dG as isCommandPlan, dH as isContractComposeNode, dI as isContractFromRef, dJ as isContractKeyFieldRef, dK as isContractKeyRef, dL as isContractParamRef, dM as isEntityWritesDefinition, dN as isKeySegment, dO as isLifecycleContract, dP as isMaintainTrigger, dQ as isMutationFragment, dR as isMutationInputRef, dS as isParam, dT as isPlannedCommandMethod, dU as isQueryModelContract, dV as isRetryableError, dW as isRetryableTransactionCancellation, dX as k, dY as key, dZ as lifecyclePhaseForIntent, d_ as maintainTrigger, d$ as mintContractKeyFieldRef, e0 as mintContractParamRef, e1 as mutation, e2 as param, e3 as preview, e4 as publicCommandModel, e5 as publicQueryModel, e6 as query, e7 as resolveLifecycle, e8 as wholeKeysSentinel } from './types-
|
|
1
|
+
import { S as SelectableOf, g as PrimaryKeyOf, h as RequestContext, i as Middleware, j as ReadRequestKind, k as CtxModel, l as ReadParams, m as ReadRequestCtx, D as DynamoDBOperation, I as Item, E as Executor, n as RetryPolicy, o as ExecutionPlanSpec, p as EntityMetadata, K as KeyDefinition, G as GsiDefinition, q as ModelKind, F as FieldOptions, r as DynamoType, s as ProjectionTransform, t as MaintainEvent, u as MembershipPredicate, v as MaintainConsistency, w as MaintainUpdateMode, x as MembershipPredicateOp, y as RelationOptions, A as AggregateOptions, z as AggregateValue, H as SelectBuilderSpec, J as RawCondition, L as TransactionSpec, N as Manifest, O as RetryOverride, Q as ExecutionPlan, V as FieldMetadata, X as ResolvedKey, R as ReadExecOptions, a as ExecutorResult, B as BatchGetExecInput, P as PutInput, W as WriteExecOptions, b as WriteResult, U as UpdateInput, c as DeleteInput, d as BatchWriteExecItem, e as BatchExecOptions, T as TransactWriteExecItem, Y as CdcEmulatorOptions, Z as ChangeHandler, _ as Unsubscribe, $ as FaultSpec, a0 as ConcurrentRecomputeRef, C as ChangeEvent, a1 as EventLog, a2 as ReplayOptions, a3 as ShardId, a4 as ViewDefinition, a5 as RelationMetadata, a6 as TransactionItemSpec, a7 as MaintainEffect, M as ModelStatic, f as DDBModel, a8 as Param, a9 as ParamDescriptor, aa as DefinitionMap, ab as OperationDefinition, ac as WriteDefinitionOptions, ad as PartialQueryKeyOf, ae as StrictSelectSpec, af as ReadDefinitionOptions, ag as EntityInput, ah as UniqueQueryKeyOf, ai as EntityRef, aj as ConditionInput, ak as QueryModelContract, al as QueryMethodSpec, am as CommandModelContract, an as CommandMethodSpec, ao as ContractSpec, ap as QuerySpec, aq as CommandSpec, ar as ContextSpec, as as OperationsDocument, at as AnyOperationDefinition, au as BridgeBundle, av as ConditionSpec } from './types-B-F7jw9f.js';
|
|
2
|
+
export { aw as AggregateMetadata, ax as BatchDeleteRequest, ay as BatchGetOptions, az as BatchGetRequest, aA as BatchGetResult, aB as BatchPutRequest, aC as BatchResult, aD as BatchWriteRequest, aE as CONTRACT_RANGE_FANOUT_CONCURRENCY, aF as CdcMode, aG as Change, aH as ChangeBatch, aI as ChangeEventName, aJ as ClockMode, aK as CollectionEffect, aL as CollectionOptions, aM as Column, aN as ColumnMap, aO as CommandContractMethodSpec, aP as CommandInputShape, aQ as CommandMethod, aR as CommandPlan, aS as CommandResolutionTarget, aT as CommandResultKind, aU as CommandSelectShape, aV as CompiledFragment, aW as ComposeSpec, aX as CondSlot, aY as ConditionCheckInput, aZ as Connection, a_ as ContractCallSignature, a$ as ContractCardinality, b0 as ContractCommandParams, b1 as ContractCommandResult, b2 as ContractComposeNode, b3 as ContractFromRef, b4 as ContractInputArity, b5 as ContractItem, b6 as ContractKeyFieldRef, b7 as ContractKeyInput, b8 as ContractKeyRef, b9 as ContractKeySpec, ba as ContractKind, bb as ContractMethodOp, bc as ContractParamRef, bd as ContractQueryParams, be as ContractResolution, bf as CounterAggregate, bg as CounterEffect, bh as CtxBase, bi as DEFAULT_MAX_ATTEMPTS, bj as DEFAULT_RETRY_POLICY, bk as DeleteOptions, bl as DeriveEffect, bm as DerivedEdgeWrite, bn as DerivedUpdate, bo as DescriptorBinding, bp as ENTITY_WRITES_MARKER, bq as EdgeEffect, br as EffectPath, bs as EmbeddedMetadata, bt as EmitEffect, bu as EntityWritesDefinition, bv as EntityWritesShape, bw as ExecutableCommandContract, bx as ExecutableQueryContract, by as FilterInput, bz as FilterSpec, bA as FragmentInput, bB as GsiDefinitionMarker, bC as GsiOptions, bD as IdempotencyEffect, bE as InProcessWriteDescriptor, bF as InputArity, bG as KeyDefinitionMarker, bH as KeySegment, bI as KeySlot, bJ as KeyStructure, bK as KeyedResult, bL as LIFECYCLE_CONTRACT_MARKER, bM as LifecycleContract, bN as LifecycleEffects, bO as LiteralParam, bP as MaintainItem, bQ as MaintainTrigger, bR as MaintenanceGraph, bS as ManifestEntity, bT as ManifestField, bU as ManifestFieldType, bV as ManifestGsi, bW as ManifestKey, bX as ManifestRelation, bY as ManifestTable, bZ as MembershipEffect, b_ as ModelRef, b$ as MutateMode, c0 as MutateOptions, c1 as MutateParallelResult, c2 as MutateTransactionResult, c3 as MutationBody, c4 as MutationDescriptorMap, c5 as MutationFragment, c6 as MutationInputProxy, c7 as MutationInputRef, c8 as MutationIntent, c9 as NumberParam, ca as OperationKind, cb as OperationSpec, cc as ParallelOpResult, cd as ParamKind, ce as ParamSpec, cf as ParamStructure, cg as PersistCtx, ch as PersistOrigin, ci as PlannedCommandMethod, cj as ProjectionMap, ck as ProjectionTransformOp, cl as PutOptions, cm as QueryContractMethodSpec, cn as QueryEnvelopeResult, co as QueryKeyOf, cp as QueryMethod, cq as QueryResult, cr as RangeConditionSpec, cs as ReadEnvelope, ct as ReadOpCtx, cu as ReadOpKind, cv as ReadOperationType, cw as ReadRouteDescriptor, cx as ReadRouteOptions, cy as ReadRouteResult, cz as RecordedCompose, cA as RelationBuilder, cB as RelationConsistency, cC as RelationLimitOptions, cD as RelationPattern, cE as RelationProjection, cF as RelationReadOptions, cG as RelationSelect, cH as RelationSpec, cI as RelationUpdateMode, cJ as RelationWriteOptions, cK as RequiresEffect, cL as Resolution, cM as RetryInfo, cN as RetryOperationKind, cO as SPEC_VERSION, cP as SegmentSpec, cQ as SegmentedKey, cR as SelectBuilder, cS as SelectOf, cT as SnapshotEffect, cU as StartingPosition, cV as StreamViewType, cW as StringParam, cX as TransactionContext, cY as TransactionItemType, cZ as UniqueEffect, c_ as Updatable, c$ as UpdateOptions, d0 as ViewSourceSlice, d1 as WhenSpec, d2 as WriteCtx, d3 as WriteDescriptor, d4 as WriteEnvelope, d5 as WriteInput, d6 as WriteKind, d7 as WriteLifecyclePhase, d8 as WriteMiddleware, d9 as WriteOperationType, da as WriteRecorder, db as WriteResultProjection, dc as attachModelClass, dd as buildDeleteInput, de as buildMaintenanceGraph, df as buildPutInput, dg as buildUpdateInput, dh as collectViewDefinitions, di as compileFragment, dj as compileMutationPlan, dk as compileSingleFragmentPlan, dl as cond, dm as contractOfMethodSpec, dn as definePlan, dp as entityWrites, dq as executeBatchGet, dr as executeBatchWrite, ds as executeCommandMethod, dt as executeDelete, du as executeKeyedBatchGet, dv as executePut, dw as executeQueryMethod, dx as executeRangeFanout, dy as executeTransaction, dz as executeUpdate, dA as from, dB as getEntityWrites, dC as gsi, dD as identity, dE as isColumn, dF as isCommandModelContract, dG as isCommandPlan, dH as isContractComposeNode, dI as isContractFromRef, dJ as isContractKeyFieldRef, dK as isContractKeyRef, dL as isContractParamRef, dM as isEntityWritesDefinition, dN as isKeySegment, dO as isLifecycleContract, dP as isMaintainTrigger, dQ as isMutationFragment, dR as isMutationInputRef, dS as isParam, dT as isPlannedCommandMethod, dU as isQueryModelContract, dV as isRetryableError, dW as isRetryableTransactionCancellation, dX as k, dY as key, dZ as lifecyclePhaseForIntent, d_ as maintainTrigger, d$ as mintContractKeyFieldRef, e0 as mintContractParamRef, e1 as mutation, e2 as param, e3 as preview, e4 as publicCommandModel, e5 as publicQueryModel, e6 as query, e7 as resolveLifecycle, e8 as wholeKeysSentinel } from './types-B-F7jw9f.js';
|
|
3
3
|
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
|
|
4
4
|
import { DynamoDBDocumentClient } from '@aws-sdk/lib-dynamodb';
|
|
5
5
|
|
|
@@ -356,6 +356,19 @@ declare class MetadataRegistry {
|
|
|
356
356
|
static get(target: Function): EntityMetadata;
|
|
357
357
|
static has(target: Function): boolean;
|
|
358
358
|
static getAll(): Map<Function, EntityMetadata>;
|
|
359
|
+
/**
|
|
360
|
+
* Snapshot of the entities that are **already finalized**, WITHOUT triggering
|
|
361
|
+
* finalize on any pending entity (unlike {@link getAll}). This is the safe view
|
|
362
|
+
* for a cross-entity lint rule that runs *inside* `finalize`: eagerly
|
|
363
|
+
* finalizing the rest of the registry from within a finalize pass would
|
|
364
|
+
* re-enter (and duplicate) the in-progress entity's finalize. A whole-registry
|
|
365
|
+
* rule instead reasons over the already-finalized peers plus the entity
|
|
366
|
+
* currently being checked; any conflicting pair is caught when the later of the
|
|
367
|
+
* two finalizes (both are present by then).
|
|
368
|
+
*
|
|
369
|
+
* @internal — for cross-entity linter rules.
|
|
370
|
+
*/
|
|
371
|
+
static getFinalized(): Map<Function, EntityMetadata>;
|
|
359
372
|
static get linter(): Linter;
|
|
360
373
|
static set linter(linter: Linter);
|
|
361
374
|
/** Resets linter to the default built-in rule set. */
|
|
@@ -484,6 +497,48 @@ interface FieldDecorator<T> extends FieldDecoratorFn<T> {
|
|
|
484
497
|
* `literals` set.
|
|
485
498
|
*/
|
|
486
499
|
declare function literal<const L extends string>(...values: readonly [L, ...L[]]): FieldDecoratorFn<L>;
|
|
500
|
+
/**
|
|
501
|
+
* `@ttl` — the **DynamoDB Time-To-Live** field decorator (issue #172, Epic #167 —
|
|
502
|
+
* CFn generator C4). It marks the model's TTL attribute: the Number field holding
|
|
503
|
+
* the **epoch-seconds** expiry timestamp DynamoDB reads to auto-delete the item.
|
|
504
|
+
*
|
|
505
|
+
* ## Standalone, number-pinned (not composed with `@number`)
|
|
506
|
+
*
|
|
507
|
+
* `@ttl` is a **standalone** semantic decorator — it records the field's storage
|
|
508
|
+
* type (`dynamoType: 'N'`) itself, exactly as `@datetime` records `'S'` + a format
|
|
509
|
+
* and `@literal` records `'S'` + a literal set. It is NOT composed with `@number`
|
|
510
|
+
* (a `@number @ttl` stack). Two reasons:
|
|
511
|
+
*
|
|
512
|
+
* 1. **A valid TTL attribute is always a Number** (epoch seconds) — there is no
|
|
513
|
+
* string/boolean TTL — so the storage type is not a free choice the author
|
|
514
|
+
* should have to (re)state. Folding `'N'` into `@ttl` removes the redundant
|
|
515
|
+
* second decorator and the ordering/consistency question a two-decorator stack
|
|
516
|
+
* would raise (which wins if they disagreed?).
|
|
517
|
+
* 2. It matches the module idiom: every semantic decorator here is self-contained
|
|
518
|
+
* and pins exactly one storage type; none composes with another.
|
|
519
|
+
*
|
|
520
|
+
* ## Loud-reject (the most-feared silent-drop class in this repo)
|
|
521
|
+
*
|
|
522
|
+
* - **Non-number field → compile error.** The decorator is pinned to `number`
|
|
523
|
+
* (its `context` is `ClassFieldDecoratorContext<unknown, number | undefined>`),
|
|
524
|
+
* so `@ttl expiresAt!: string` fails to type-check — the SAME cross-type
|
|
525
|
+
* mechanism that makes `@number age!: string` an error. A TTL on a non-Number
|
|
526
|
+
* attribute can never be declared (DynamoDB would silently never expire it).
|
|
527
|
+
* - **More than one `@ttl` per (physical) table → throws.** DynamoDB allows
|
|
528
|
+
* exactly one TTL attribute per table. A second `@ttl` on the same model throws
|
|
529
|
+
* at `@model` registration; two `@ttl` fields on DIFFERENT entities sharing one
|
|
530
|
+
* physical table throw at manifest build (where all table siblings are known).
|
|
531
|
+
*
|
|
532
|
+
* ## Runtime behaviour
|
|
533
|
+
*
|
|
534
|
+
* A `@ttl` field is a plain stored Number at runtime — no special serialization,
|
|
535
|
+
* hydration, or key handling (it is not a key attribute). The decorator only
|
|
536
|
+
* records metadata (`dynamoType: 'N'`, `ttl: true`); the TTL *effect* is a table
|
|
537
|
+
* property DynamoDB applies, emitted into the generated CloudFormation
|
|
538
|
+
* `TimeToLiveSpecification` (never into `AttributeDefinitions`, which holds key
|
|
539
|
+
* attributes only). So it needs no runtime-serialization conformance change.
|
|
540
|
+
*/
|
|
541
|
+
declare const ttl: FieldDecoratorFn<number>;
|
|
487
542
|
declare const string: FieldDecorator<string>;
|
|
488
543
|
declare const number: FieldDecorator<number>;
|
|
489
544
|
declare const boolean: FieldDecorator<boolean>;
|
|
@@ -1608,6 +1663,13 @@ declare function decodePerKeyCursor(cursor: string, expectedKey: Record<string,
|
|
|
1608
1663
|
* unaffected. query-boundary is excluded because it flags non-unique GSIs which
|
|
1609
1664
|
* are valid for list() operations; boundary enforcement is handled at runtime by
|
|
1610
1665
|
* the planner's boundary-check.
|
|
1666
|
+
*
|
|
1667
|
+
* Also includes cfn-schema-consistency (issue #169): a whole-table validator
|
|
1668
|
+
* that rejects entities sharing a physical table with an ambiguous base
|
|
1669
|
+
* KeySchema, inconsistently-shared GSI index names, or more than 20 unioned
|
|
1670
|
+
* GSIs — the CFn deploy-consistency user-error class, caught statically at
|
|
1671
|
+
* finalize. It is additive: a single entity on its own table with ≤ 20 GSIs is
|
|
1672
|
+
* never affected.
|
|
1611
1673
|
*/
|
|
1612
1674
|
declare function createDefaultLinter(): Linter;
|
|
1613
1675
|
|
|
@@ -3003,4 +3065,4 @@ declare function assertBundleSerializable(bundle: BridgeBundle): void;
|
|
|
3003
3065
|
*/
|
|
3004
3066
|
declare function buildBridgeBundle(queries?: DefinitionMap, commands?: DefinitionMap, registry?: typeof MetadataRegistry, transactions?: Record<string, TransactionDefinition>, contractInputs?: ContractInputs): BridgeBundle;
|
|
3005
3067
|
|
|
3006
|
-
export { AggregateOptions, AggregateValue, type AnyModelContract, AnyOperationDefinition, BATCH_GET_MAX_KEYS, BATCH_WRITE_MAX_ITEMS, BatchExecOptions, BatchGetExecInput, BatchWriteExecItem, BridgeBundle, type BuiltContracts, CdcEmulator, CdcEmulatorOptions, ChangeEvent, ChangeHandler, ClientManager, type CollectParams, CommandMethodSpec, CommandModelContract, CommandSpec, ConcurrentRecomputeRef, type ConditionExpressionResult, ConditionInput, ConditionSpec, type ContextOwnership, type ContextOwnershipMap, ContextSpec, type ContractBoundaryViolation, type ContractInputs, type ContractMap, type ContractN1Violation, ContractSpec, CtxModel, DDBModel, DefinitionMap, DeleteInput, type DriftReport, DynamoDBOperation, DynamoExecutor, DynamoType, EDGE_WRITES_MARKER, type EdgeLifecycle, type EdgeWriteDeclaration, type EdgeWriteRecorder, type EdgeWritesDefinition, EntityMetadata, EntityRef, EventLog, ExecutionPlan, Executor, ExecutorResult, type ExplainInput, FaultSpec, FieldMetadata, FieldOptions, type FilterExpressionResult, GsiDefinition, type HasManyVersionedCallback, type HasManyVersionedOptions, type HasOneVersionedCallback, type HasOneVersionedOptions, Item, KeyDefinition, type LintResult, type LintRule, Linter, type ListInput, type ListOptions, MAINT_OUTBOX_PK_PREFIX, MAX_TRANSACT_ITEMS, MaintainConsistency, MaintainEffect, MaintainEvent, MaintainUpdateMode, type MaintainedFromCallback, type MaintainedFromOptions, MaintenanceDrain, type MaintenanceDrainOptions, type MaintenanceRebuildOptions, MaintenanceRebuilder, Manifest, MembershipPredicate, MembershipPredicateOp, MetadataRegistry, Middleware, type ModelOptions, ModelStatic, OLD_VALUE_NAMESPACE, OperationDefinition, OperationsDocument, Param, ParamDescriptor, type Parameterize, PartialQueryKeyOf, type PerKeyCursorEnvelope, type PlanInput, PrimaryKeyOf, type ProjectionResult, ProjectionTransform, type ProjectionValue, PutInput, QueryMethodSpec, QueryModelContract, type QueryOptions$1 as QueryOptions, QuerySpec, RawCondition, ReadExecOptions, ReadParams, ReadRequestCtx, ReadRequestKind, type RebuildResult, RelationMetadata, RelationOptions, type RelationTraversalOptions, ReplayOptions, RequestContext, ResolvedKey, RetryOverride, RetryPolicy, RetryingExecutor, SelectableOf, type SelfProxy, type SelfRef, ShardId, type SourceProxy, type SourceRef, TableMapping, TransactWriteExecItem, type TransactionDefinition, TransactionItemSpec, type TransactionParamShape, type TransactionRef, TransactionSpec, type TxConditionCheckOptions, type TxForEachInstruction, type TxForEachOptions, type TxInstruction, type TxRecorder, type TxWriteInstruction, type TxWriteOptions, UniqueQueryKeyOf, type UpdateExpressionResult, UpdateInput, type VersionedHistoryOptions, type VersionedLatestOptions, ViewDefinition, type WhenComparison, WriteDefinitionOptions, WriteExecOptions, WriteResult, aggregate, assertBundleSerializable, assertContractBoundaries, assertContractN1Safe, assertJsonSerializable, assertSupportedCondition, belongsTo, binary, boolean, buildBridgeBundle, buildConditionExpression, buildContexts, buildContracts, buildManifest, buildOperations, buildProjection, buildQuerySpec, buildTransactionSpec, buildTransactions, buildUpdateExpression, collectContractBoundaryViolations, collectContractN1Violations, compileFilterExpression, count, createCdcEmulator, createDefaultLinter, createMaintenanceDrain, createMaintenanceDrainHandler, createMaintenanceRebuilder, datetime, decodeCursor, decodePerKeyCursor, defineCommands, defineDelete, defineList, definePut, defineQueries, defineQuery, defineTransaction, defineTransactions, defineUpdate, deriveEdgeWriteItems, deriveEdgeWriteItemsFor, deriveModelEdgeWriteItems, derivePrefix, detectRelationFields, edgeWrites, embedded, encodeCursor, encodePerKeyCursor, evaluateFilter, execute, executeDeclarativeTransaction, executeExplain, executeList, executeQuery, expandTransaction, field, getEdgeWrites, getImplicitKeyFields, gsiAmbiguityRule, hasMany, hasOne, hydrate, isEdgeWritesDefinition, isSelectBuilder, isTransactionRef, list, literal, maintainedFrom, map, max, missingGsiRule, model, noScanRule, number, numberSet, plan, queryBoundaryRule, relationDepthRule, requireLimitRule, resolveKey, resolveRelations, serializeContractKey, serializeFieldValue, string, stringSet, validateDepth, validateGsiAmbiguity, when, whenMember };
|
|
3068
|
+
export { AggregateOptions, AggregateValue, type AnyModelContract, AnyOperationDefinition, BATCH_GET_MAX_KEYS, BATCH_WRITE_MAX_ITEMS, BatchExecOptions, BatchGetExecInput, BatchWriteExecItem, BridgeBundle, type BuiltContracts, CdcEmulator, CdcEmulatorOptions, ChangeEvent, ChangeHandler, ClientManager, type CollectParams, CommandMethodSpec, CommandModelContract, CommandSpec, ConcurrentRecomputeRef, type ConditionExpressionResult, ConditionInput, ConditionSpec, type ContextOwnership, type ContextOwnershipMap, ContextSpec, type ContractBoundaryViolation, type ContractInputs, type ContractMap, type ContractN1Violation, ContractSpec, CtxModel, DDBModel, DefinitionMap, DeleteInput, type DriftReport, DynamoDBOperation, DynamoExecutor, DynamoType, EDGE_WRITES_MARKER, type EdgeLifecycle, type EdgeWriteDeclaration, type EdgeWriteRecorder, type EdgeWritesDefinition, EntityMetadata, EntityRef, EventLog, ExecutionPlan, Executor, ExecutorResult, type ExplainInput, FaultSpec, FieldMetadata, FieldOptions, type FilterExpressionResult, GsiDefinition, type HasManyVersionedCallback, type HasManyVersionedOptions, type HasOneVersionedCallback, type HasOneVersionedOptions, Item, KeyDefinition, type LintResult, type LintRule, Linter, type ListInput, type ListOptions, MAINT_OUTBOX_PK_PREFIX, MAX_TRANSACT_ITEMS, MaintainConsistency, MaintainEffect, MaintainEvent, MaintainUpdateMode, type MaintainedFromCallback, type MaintainedFromOptions, MaintenanceDrain, type MaintenanceDrainOptions, type MaintenanceRebuildOptions, MaintenanceRebuilder, Manifest, MembershipPredicate, MembershipPredicateOp, MetadataRegistry, Middleware, type ModelOptions, ModelStatic, OLD_VALUE_NAMESPACE, OperationDefinition, OperationsDocument, Param, ParamDescriptor, type Parameterize, PartialQueryKeyOf, type PerKeyCursorEnvelope, type PlanInput, PrimaryKeyOf, type ProjectionResult, ProjectionTransform, type ProjectionValue, PutInput, QueryMethodSpec, QueryModelContract, type QueryOptions$1 as QueryOptions, QuerySpec, RawCondition, ReadExecOptions, ReadParams, ReadRequestCtx, ReadRequestKind, type RebuildResult, RelationMetadata, RelationOptions, type RelationTraversalOptions, ReplayOptions, RequestContext, ResolvedKey, RetryOverride, RetryPolicy, RetryingExecutor, SelectableOf, type SelfProxy, type SelfRef, ShardId, type SourceProxy, type SourceRef, TableMapping, TransactWriteExecItem, type TransactionDefinition, TransactionItemSpec, type TransactionParamShape, type TransactionRef, TransactionSpec, type TxConditionCheckOptions, type TxForEachInstruction, type TxForEachOptions, type TxInstruction, type TxRecorder, type TxWriteInstruction, type TxWriteOptions, UniqueQueryKeyOf, type UpdateExpressionResult, UpdateInput, type VersionedHistoryOptions, type VersionedLatestOptions, ViewDefinition, type WhenComparison, WriteDefinitionOptions, WriteExecOptions, WriteResult, aggregate, assertBundleSerializable, assertContractBoundaries, assertContractN1Safe, assertJsonSerializable, assertSupportedCondition, belongsTo, binary, boolean, buildBridgeBundle, buildConditionExpression, buildContexts, buildContracts, buildManifest, buildOperations, buildProjection, buildQuerySpec, buildTransactionSpec, buildTransactions, buildUpdateExpression, collectContractBoundaryViolations, collectContractN1Violations, compileFilterExpression, count, createCdcEmulator, createDefaultLinter, createMaintenanceDrain, createMaintenanceDrainHandler, createMaintenanceRebuilder, datetime, decodeCursor, decodePerKeyCursor, defineCommands, defineDelete, defineList, definePut, defineQueries, defineQuery, defineTransaction, defineTransactions, defineUpdate, deriveEdgeWriteItems, deriveEdgeWriteItemsFor, deriveModelEdgeWriteItems, derivePrefix, detectRelationFields, edgeWrites, embedded, encodeCursor, encodePerKeyCursor, evaluateFilter, execute, executeDeclarativeTransaction, executeExplain, executeList, executeQuery, expandTransaction, field, getEdgeWrites, getImplicitKeyFields, gsiAmbiguityRule, hasMany, hasOne, hydrate, isEdgeWritesDefinition, isSelectBuilder, isTransactionRef, list, literal, maintainedFrom, map, max, missingGsiRule, model, noScanRule, number, numberSet, plan, queryBoundaryRule, relationDepthRule, requireLimitRule, resolveKey, resolveRelations, serializeContractKey, serializeFieldValue, string, stringSet, ttl, validateDepth, validateGsiAmbiguity, when, whenMember };
|
package/dist/index.js
CHANGED
|
@@ -79,7 +79,7 @@ import {
|
|
|
79
79
|
validateDepth,
|
|
80
80
|
when,
|
|
81
81
|
wholeKeysSentinel
|
|
82
|
-
} from "./chunk-
|
|
82
|
+
} from "./chunk-J5A665UW.js";
|
|
83
83
|
import {
|
|
84
84
|
CdcEmulator,
|
|
85
85
|
MAINT_OUTBOX_PK_PREFIX,
|
|
@@ -91,7 +91,7 @@ import {
|
|
|
91
91
|
orderAndTrimCollection,
|
|
92
92
|
pathField,
|
|
93
93
|
projectFrom
|
|
94
|
-
} from "./chunk-
|
|
94
|
+
} from "./chunk-7NPG5R7O.js";
|
|
95
95
|
import {
|
|
96
96
|
BATCH_GET_MAX_KEYS,
|
|
97
97
|
BATCH_WRITE_MAX_ITEMS,
|
|
@@ -154,7 +154,7 @@ import {
|
|
|
154
154
|
resolveModelClass,
|
|
155
155
|
segmentFieldNames,
|
|
156
156
|
serializeFieldValue
|
|
157
|
-
} from "./chunk-
|
|
157
|
+
} from "./chunk-FMJIWFIS.js";
|
|
158
158
|
|
|
159
159
|
// src/metadata/prefix.ts
|
|
160
160
|
function derivePrefix(customPrefix, className) {
|
|
@@ -267,6 +267,13 @@ function model(options) {
|
|
|
267
267
|
`@model on '${String(context.name)}': \`kind: '${kind}'\` declares a maintained view but no \`@maintainedFrom(...)\` source is stacked. A view with no source is maintained by nothing \u2014 add at least one \`@maintainedFrom\` decorator.`
|
|
268
268
|
);
|
|
269
269
|
}
|
|
270
|
+
const ttlFields = fields.filter((f) => f.ttl === true);
|
|
271
|
+
if (ttlFields.length > 1) {
|
|
272
|
+
throw new Error(
|
|
273
|
+
`@model on '${String(context.name)}': ${ttlFields.length} \`@ttl\` fields (${ttlFields.map((f) => `'${f.propertyName}'`).join(", ")}) are declared, but DynamoDB allows exactly one TTL attribute per table. Mark a single field \`@ttl\`.`
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
const ttlAttribute = ttlFields.length === 1 ? ttlFields[0].propertyName : void 0;
|
|
270
277
|
MetadataRegistry.register(target, {
|
|
271
278
|
tableName: options.table,
|
|
272
279
|
prefix,
|
|
@@ -278,7 +285,8 @@ function model(options) {
|
|
|
278
285
|
embeddedFields,
|
|
279
286
|
kind,
|
|
280
287
|
...options.description !== void 0 ? { description: options.description } : {},
|
|
281
|
-
maintainedFrom: maintainedFrom2
|
|
288
|
+
maintainedFrom: maintainedFrom2,
|
|
289
|
+
...ttlAttribute !== void 0 ? { ttlAttribute } : {}
|
|
282
290
|
});
|
|
283
291
|
};
|
|
284
292
|
}
|
|
@@ -328,6 +336,13 @@ function literal(...values) {
|
|
|
328
336
|
});
|
|
329
337
|
};
|
|
330
338
|
}
|
|
339
|
+
var ttl = (_value, context) => {
|
|
340
|
+
collectField({
|
|
341
|
+
propertyName: String(context.name),
|
|
342
|
+
dynamoType: "N",
|
|
343
|
+
ttl: true
|
|
344
|
+
});
|
|
345
|
+
};
|
|
331
346
|
var string = createFieldDecorator("S");
|
|
332
347
|
var number = createFieldDecorator("N");
|
|
333
348
|
var boolean = createFieldDecorator("BOOL");
|
|
@@ -1973,6 +1988,7 @@ export {
|
|
|
1973
1988
|
serializeFieldValue,
|
|
1974
1989
|
string,
|
|
1975
1990
|
stringSet,
|
|
1991
|
+
ttl,
|
|
1976
1992
|
validateDepth,
|
|
1977
1993
|
validateGsiAmbiguity,
|
|
1978
1994
|
when,
|
package/dist/testing/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { E as Executor, D as DynamoDBOperation, R as ReadExecOptions, a as ExecutorResult, B as BatchGetExecInput, P as PutInput, W as WriteExecOptions, b as WriteResult, U as UpdateInput, c as DeleteInput, d as BatchWriteExecItem, e as BatchExecOptions, T as TransactWriteExecItem, M as ModelStatic, f as DDBModel, C as ChangeEvent } from '../types-
|
|
1
|
+
import { E as Executor, D as DynamoDBOperation, R as ReadExecOptions, a as ExecutorResult, B as BatchGetExecInput, P as PutInput, W as WriteExecOptions, b as WriteResult, U as UpdateInput, c as DeleteInput, d as BatchWriteExecItem, e as BatchExecOptions, T as TransactWriteExecItem, M as ModelStatic, f as DDBModel, C as ChangeEvent } from '../types-B-F7jw9f.js';
|
|
2
2
|
import '@aws-sdk/client-dynamodb';
|
|
3
3
|
|
|
4
4
|
/**
|
package/dist/testing/index.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createCdcEmulator
|
|
3
|
-
} from "../chunk-
|
|
3
|
+
} from "../chunk-7NPG5R7O.js";
|
|
4
4
|
import {
|
|
5
5
|
ClientManager,
|
|
6
6
|
MetadataRegistry,
|
|
7
7
|
TableMapping,
|
|
8
8
|
resolveModelClass
|
|
9
|
-
} from "../chunk-
|
|
9
|
+
} from "../chunk-FMJIWFIS.js";
|
|
10
10
|
|
|
11
11
|
// src/memory/memory-store.ts
|
|
12
12
|
function deepClone(value) {
|
|
@@ -3126,6 +3126,21 @@ interface ManifestEntity {
|
|
|
3126
3126
|
* manifest. Surfaces as the generated Python class docstring.
|
|
3127
3127
|
*/
|
|
3128
3128
|
readonly description?: string;
|
|
3129
|
+
/**
|
|
3130
|
+
* The physical attribute name of the model's DynamoDB **Time-To-Live** field,
|
|
3131
|
+
* when one is declared via `@ttl` (issue #172, Epic #167 — CFn generator C4).
|
|
3132
|
+
* Unlike the maintenance/stream signals (a maintenance-IR concern kept OFF the
|
|
3133
|
+
* manifest), TTL is a **physical schema** fact, and the CloudFormation emitter
|
|
3134
|
+
* consumes the manifest — so the TTL attribute name flows here and the emitter
|
|
3135
|
+
* renders `TimeToLiveSpecification { AttributeName: <this>, Enabled: true }`. The
|
|
3136
|
+
* attribute is deliberately NOT added to the table's key `AttributeDefinitions`
|
|
3137
|
+
* (that list holds ONLY key/index attributes; `TimeToLiveSpecification` legitimately
|
|
3138
|
+
* names a non-key attribute). **Absent** when the model declares no `@ttl`, so a
|
|
3139
|
+
* TTL-free model serializes byte-identically to the pre-#172 manifest (backward
|
|
3140
|
+
* compatible). DynamoDB permits exactly one TTL attribute per physical table; the
|
|
3141
|
+
* single-per-table rule is enforced at manifest build.
|
|
3142
|
+
*/
|
|
3143
|
+
readonly ttlAttribute?: string;
|
|
3129
3144
|
}
|
|
3130
3145
|
interface ManifestTable {
|
|
3131
3146
|
readonly physicalName: string;
|
|
@@ -6230,6 +6245,20 @@ interface FieldMetadata {
|
|
|
6230
6245
|
* `@number`, …), so plain fields are byte-for-byte unchanged (backward compat).
|
|
6231
6246
|
*/
|
|
6232
6247
|
literals?: readonly string[];
|
|
6248
|
+
/**
|
|
6249
|
+
* For an `@ttl` field (issue #172, Epic #167 — CFn generator C4) — marks this
|
|
6250
|
+
* field as the model's **DynamoDB Time-To-Live** attribute. `@ttl` is a
|
|
6251
|
+
* standalone semantic decorator pinned to `number` (it records `dynamoType: 'N'`
|
|
6252
|
+
* itself, so `@ttl expiresAt!: string` is a compile error), because DynamoDB TTL
|
|
6253
|
+
* requires a Number attribute holding **epoch seconds**. It is a **physical
|
|
6254
|
+
* schema** fact (unlike the maintenance signals kept off the manifest): the flag
|
|
6255
|
+
* flows through {@link EntityMetadata} into `manifest.json`
|
|
6256
|
+
* ({@link ManifestEntity.ttlAttribute}), which the CloudFormation emitter consumes
|
|
6257
|
+
* to render `TimeToLiveSpecification`. DynamoDB allows exactly one TTL attribute
|
|
6258
|
+
* per table, enforced loudly at model registration (`@model`). `undefined` for
|
|
6259
|
+
* every other field decorator, so non-TTL fields are byte-for-byte unchanged.
|
|
6260
|
+
*/
|
|
6261
|
+
ttl?: boolean;
|
|
6233
6262
|
}
|
|
6234
6263
|
|
|
6235
6264
|
/**
|
|
@@ -6530,6 +6559,19 @@ interface EntityMetadata {
|
|
|
6530
6559
|
* Absent / empty for a plain entity.
|
|
6531
6560
|
*/
|
|
6532
6561
|
maintainedFrom?: MaintainedFromMetadata[];
|
|
6562
|
+
/**
|
|
6563
|
+
* The physical attribute name of the model's `@ttl` field, when one is declared
|
|
6564
|
+
* (issue #172, Epic #167 — CFn generator C4). DynamoDB TTL requires a single
|
|
6565
|
+
* Number attribute holding epoch seconds; `@model` derives this from the (unique)
|
|
6566
|
+
* field carrying {@link FieldMetadata.ttl} and throws if more than one field on the
|
|
6567
|
+
* SAME model is `@ttl` (the cross-entity, same-physical-table single-TTL rule is
|
|
6568
|
+
* enforced downstream, at manifest build, where all entities on a table are known).
|
|
6569
|
+
* A non-key field's physical attribute name is its property name, so this is the
|
|
6570
|
+
* `@ttl` field's property name. Propagated to the manifest
|
|
6571
|
+
* ({@link ManifestEntity.ttlAttribute}) — the SSoT the CloudFormation emitter reads
|
|
6572
|
+
* to render `TimeToLiveSpecification`. Absent when the model declares no `@ttl`.
|
|
6573
|
+
*/
|
|
6574
|
+
ttlAttribute?: string;
|
|
6533
6575
|
}
|
|
6534
6576
|
|
|
6535
6577
|
/**
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
# GraphDDB — CloudFormation generation
|
|
2
|
+
|
|
3
|
+
`graphddb generate cloudformation` emits an AWS CloudFormation template
|
|
4
|
+
describing the **DynamoDB table(s)** a GraphDDB single-table design maps onto.
|
|
5
|
+
GraphDDB is single-table by design: every entity that shares a `table` name
|
|
6
|
+
contributes to the SAME `AWS::DynamoDB::Table`, and their Global Secondary
|
|
7
|
+
Indexes are **unioned** by index name.
|
|
8
|
+
|
|
9
|
+
The command consumes the same serializable manifest the Python bridge uses (it
|
|
10
|
+
loads your entry model, builds the manifest from the shared metadata registry,
|
|
11
|
+
and emits the template). Output is **deterministic** — the same model set always
|
|
12
|
+
produces byte-identical output — and defaults to YAML (`--format json` for JSON).
|
|
13
|
+
|
|
14
|
+
> This document describes the shipped `generate cloudformation` command. It is
|
|
15
|
+
> the reference for the command's options, the resources it generates, the
|
|
16
|
+
> derivation rules that map a model set onto a table, and its scope boundary.
|
|
17
|
+
|
|
18
|
+
### Related specifications
|
|
19
|
+
|
|
20
|
+
- [`spec.md`](./spec.md) — the core API: entities, structured keys / GSIs, the
|
|
21
|
+
physical key attributes (`PK` / `SK` / `<index>PK` / `<index>SK`).
|
|
22
|
+
- [`design-patterns.md`](./design-patterns.md) — maintained access paths and the
|
|
23
|
+
`updateMode: 'stream'` signal that drives `--stream auto`.
|
|
24
|
+
- [`python-bridge.md`](./python-bridge.md) — the other consumer of the same
|
|
25
|
+
manifest / SSoT.
|
|
26
|
+
|
|
27
|
+
## Overview
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
graphddb generate cloudformation --entry models.ts --out table.yaml
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
- **`--entry`** loads the TypeScript (or pre-compiled `.js`/`.mjs`) module that
|
|
34
|
+
registers your entity models as a side effect of import. TypeScript modules are
|
|
35
|
+
transpiled on demand via `tsx` (an optional peer dependency — install it with
|
|
36
|
+
`npm i -D tsx`).
|
|
37
|
+
- Without `--out`, the template is written verbatim to **stdout** (pipe-friendly).
|
|
38
|
+
With `--out`, it is written to that file and a JSON result
|
|
39
|
+
(`{"status":"ok","outDir":…,"files":[…]}`) is printed to stdout.
|
|
40
|
+
|
|
41
|
+
## Options
|
|
42
|
+
|
|
43
|
+
Every option below matches the shipped CLI contract (`cli-contract.yaml`) and the
|
|
44
|
+
handler validation exactly.
|
|
45
|
+
|
|
46
|
+
| Option | Alias | Type | Default | Notes / validation |
|
|
47
|
+
|--------|-------|------|---------|--------------------|
|
|
48
|
+
| `--entry` | `-e` | file | — (required) | Entry module that registers the entity models. Must exist / be readable; missing → exit `2`. |
|
|
49
|
+
| `--out` | `-o` | file | stdout | Output file. Parent directories are created. Omitted → template to stdout. |
|
|
50
|
+
| `--format` | `-f` | `yaml` \| `json` | `yaml` | Serialization format. Any other value → exit `2`. |
|
|
51
|
+
| `--gsi-projection` | | `ALL` \| `KEYS_ONLY` | `ALL` | GSI `ProjectionType`. `ALL` projects every attribute; `KEYS_ONLY` projects only the key attributes. Other value → exit `2`. |
|
|
52
|
+
| `--stream` | | `auto` \| `on` \| `off` | `auto` | DynamoDB Streams (`StreamSpecification` `NEW_AND_OLD_IMAGES`). `auto` emits a stream only when the model set uses stream-based maintenance (`updateMode: 'stream'`); `on` always emits; `off` never. When enabled, a `StreamArn` Output is added. Other value → exit `2`. |
|
|
53
|
+
| `--billing-mode` | | `PAY_PER_REQUEST` \| `PROVISIONED` | `PAY_PER_REQUEST` | `PAY_PER_REQUEST` is on-demand (no provisioned throughput). `PROVISIONED` emits `BillingMode: PROVISIONED` with `ProvisionedThroughput` on the table **and every GSI**, and unlocks Auto Scaling. Other value → exit `2`. |
|
|
54
|
+
| `--rcu` | | integer ≥ 1 | `5` | Provisioned `ReadCapacityUnits` on the table and every GSI. **Only valid with `--billing-mode PROVISIONED`** — supplying it with `PAY_PER_REQUEST` is a loud reject (exit `2`). Non-integer / `< 1` → exit `2`. |
|
|
55
|
+
| `--wcu` | | integer ≥ 1 | `5` | Provisioned `WriteCapacityUnits`. Same rules as `--rcu`. |
|
|
56
|
+
| `--autoscale` | | boolean flag | off | Enable Application Auto Scaling (a `ScalableTarget` + `ScalingPolicy` pair for the table read/write and each GSI read/write). **Only valid with `--billing-mode PROVISIONED`** — with `PAY_PER_REQUEST` it is a loud reject (exit `2`). |
|
|
57
|
+
| `--autoscale-min` | | integer ≥ 1 | `5` | Auto Scaling `MinCapacity` for every scalable target. Must be `<= --autoscale-max` (else exit `2`). Meaningful only with `--autoscale`. |
|
|
58
|
+
| `--autoscale-max` | | integer ≥ 1 | `100` | Auto Scaling `MaxCapacity` for every scalable target. Must be `>= --autoscale-min`. |
|
|
59
|
+
| `--autoscale-target` | | integer 1–100 | `70` | Target utilization % (`TargetValue` of the target-tracking policy). `> 100` → exit `2`. |
|
|
60
|
+
| `--pitr` | | boolean flag | off | Point-in-time recovery (`PointInTimeRecoverySpecification` `PointInTimeRecoveryEnabled: true`). Omitted entirely when off (never emitted as `false`). |
|
|
61
|
+
| `--table-class` | | `STANDARD` \| `STANDARD_INFREQUENT_ACCESS` | omitted | Emits `TableClass: <value>`. Omitted by default (AWS defaults to `STANDARD`). Other value → exit `2`. |
|
|
62
|
+
| `--sse` | | boolean flag | off | Server-side encryption with the AWS-owned/managed key (`SSESpecification` `SSEEnabled: true` — no KMS key ARN / SSEType). Omitted entirely when off. |
|
|
63
|
+
|
|
64
|
+
### Exit codes
|
|
65
|
+
|
|
66
|
+
- `0` — generation succeeded.
|
|
67
|
+
- `1` — unexpected error (e.g. the entry module threw). JSON error on stderr.
|
|
68
|
+
- `2` — invalid arguments (missing / unreadable `--entry`, an invalid enum value,
|
|
69
|
+
a bad integer, or a loud reject below). JSON error on stderr
|
|
70
|
+
(`{"code":"INVALID_ARGS","message":…}`).
|
|
71
|
+
|
|
72
|
+
## Generated resources
|
|
73
|
+
|
|
74
|
+
For each **physical table** the model set maps onto, the template emits:
|
|
75
|
+
|
|
76
|
+
- **`AWS::DynamoDB::Table`** with:
|
|
77
|
+
- `TableName` — a `!Ref` to a `TableName` parameter whose default is the
|
|
78
|
+
physical table name. (Multi-table designs get one parameter per table, keyed
|
|
79
|
+
by logical id.)
|
|
80
|
+
- `BillingMode` — `PAY_PER_REQUEST` (default) or `PROVISIONED`.
|
|
81
|
+
- `AttributeDefinitions` — **key attributes only**, every one `AttributeType: S`
|
|
82
|
+
(see derivation rules).
|
|
83
|
+
- `KeySchema` — `PK` (HASH) plus `SK` (RANGE) when the base key has a sort key.
|
|
84
|
+
- `GlobalSecondaryIndexes` — one per unioned GSI (omitted when there are none).
|
|
85
|
+
- `ProvisionedThroughput` — on the table **and every GSI**, only under
|
|
86
|
+
`PROVISIONED`.
|
|
87
|
+
- `StreamSpecification` — `NEW_AND_OLD_IMAGES`, only when streams are enabled.
|
|
88
|
+
- `TimeToLiveSpecification` — only when a `@ttl` field is declared (see TTL).
|
|
89
|
+
- `PointInTimeRecoverySpecification` / `TableClass` / `SSESpecification` — only
|
|
90
|
+
when the corresponding `--pitr` / `--table-class` / `--sse` option is given.
|
|
91
|
+
- **`AWS::ApplicationAutoScaling::ScalableTarget` + `::ScalingPolicy`** — separate
|
|
92
|
+
top-level resources (not table properties), only under `--autoscale`.
|
|
93
|
+
- **Parameters** — a `TableName` (String, default = physical name).
|
|
94
|
+
- **Outputs** — the table name (`Ref`) and ARN (`Fn::GetAtt … Arn`), plus a
|
|
95
|
+
`StreamArn` output when streams are enabled.
|
|
96
|
+
|
|
97
|
+
All resources, attribute definitions, and GSIs are emitted in a stable (sorted)
|
|
98
|
+
order, so the output is byte-deterministic.
|
|
99
|
+
|
|
100
|
+
## Derivation rules
|
|
101
|
+
|
|
102
|
+
How a GraphDDB model set is mapped onto the physical DynamoDB shape:
|
|
103
|
+
|
|
104
|
+
1. **One physical table per logical `table` name.** Entities that share a `table`
|
|
105
|
+
are single-table siblings and contribute to the same `AWS::DynamoDB::Table`.
|
|
106
|
+
The CFn logical resource id is `<AlphanumericLogicalName>Table` (e.g.
|
|
107
|
+
`UserPermissions` → `UserPermissionsTable`).
|
|
108
|
+
2. **Physical key attribute names match the runtime exactly.** The base partition
|
|
109
|
+
key is always `PK`; the base sort key (when present) is `SK`. Each GSI uses
|
|
110
|
+
`<indexName>PK` and `<indexName>SK` (e.g. `GSI1PK` / `GSI1SK`).
|
|
111
|
+
3. **`AttributeDefinitions` are key attributes only, all `AttributeType: S`.** It
|
|
112
|
+
contains `PK`, `SK` (when the table has a sort key), and each GSI's `<index>PK`
|
|
113
|
+
plus `<index>SK` (only for GSIs that have a sort key). **Non-key attributes are
|
|
114
|
+
never added** — in particular the TTL attribute is deliberately kept out
|
|
115
|
+
(CloudFormation rejects a non-key attribute there; `TimeToLiveSpecification`
|
|
116
|
+
references it legitimately).
|
|
117
|
+
4. **The base `KeySchema` is `PK` (HASH) + `SK` (RANGE).** `SK` is included when
|
|
118
|
+
**any** entity on the table declares a sort key; a PK-only design omits `SK`
|
|
119
|
+
and its attribute definition.
|
|
120
|
+
5. **GSIs are unioned by index name across all entities on the table.** The same
|
|
121
|
+
`IndexName` declared by several entities collapses to one GSI. Each GSI's
|
|
122
|
+
`KeySchema` is `<index>PK` (HASH) plus `<index>SK` (RANGE) — HASH-only when the
|
|
123
|
+
GSI has no sort key. `Projection.ProjectionType` is `ALL` (default) or
|
|
124
|
+
`KEYS_ONLY`. (Full GSI-union consistency — same sort-key presence across
|
|
125
|
+
entities, ≤ 20 GSIs — is enforced statically at model registration by the
|
|
126
|
+
`cfn-schema-consistency` linter rule; the emitter keeps a backstop that throws
|
|
127
|
+
on a surviving inconsistency.)
|
|
128
|
+
6. **Streams (`--stream auto`) are derived from maintenance metadata.** `auto`
|
|
129
|
+
emits a stream when any registered entity uses stream-based maintenance
|
|
130
|
+
(`updateMode: 'stream'` — on a relation `write`, a versioned relation preset,
|
|
131
|
+
an `@aggregate` `write`, or a view's `@maintainedFrom`). This signal comes from
|
|
132
|
+
the metadata registry, not the manifest. The single view type is
|
|
133
|
+
`NEW_AND_OLD_IMAGES` (stream-based maintenance needs both images).
|
|
134
|
+
7. **TTL is derived from a `@ttl` field.** When an entity on the table declares a
|
|
135
|
+
`@ttl` field, its physical attribute name is carried in the manifest and
|
|
136
|
+
rendered as `TimeToLiveSpecification { AttributeName, Enabled: true }`. DynamoDB
|
|
137
|
+
allows exactly one TTL attribute per table; the manifest build enforces this,
|
|
138
|
+
and the emitter backstops it by throwing if two entities on a table disagree.
|
|
139
|
+
8. **Provisioned throughput + Auto Scaling are pure deploy-time settings** (from
|
|
140
|
+
CLI options, not derived from the model). Under `PROVISIONED`, the table and
|
|
141
|
+
every GSI carry `ProvisionedThroughput` (`--rcu` / `--wcu`, default 5/5).
|
|
142
|
+
Under `--autoscale`, each of the table read/write and every GSI read/write gets
|
|
143
|
+
a `ScalableTarget` (`ServiceNamespace: dynamodb`, the matching
|
|
144
|
+
`ScalableDimension`, an `Fn::Sub` `ResourceId` tying to the table's `Ref`,
|
|
145
|
+
`MinCapacity`/`MaxCapacity`, and the DynamoDB service-linked role ARN) plus a
|
|
146
|
+
`TargetTrackingScaling` `ScalingPolicy` (predefined
|
|
147
|
+
`DynamoDBRead/WriteCapacityUtilization` metric, `TargetValue`). So **N GSIs →
|
|
148
|
+
`2×(1+N)` scalable targets + `2×(1+N)` scaling policies.** GSI targets
|
|
149
|
+
additionally `DependsOn` the table.
|
|
150
|
+
9. **PITR / TableClass / SSE are pure deploy-time settings**, each defaulting to
|
|
151
|
+
OFF/omitted — the emitter omits the whole property rather than emitting a
|
|
152
|
+
`false`/default value.
|
|
153
|
+
|
|
154
|
+
## Scope boundary
|
|
155
|
+
|
|
156
|
+
This command generates **only the GraphDDB-managed DynamoDB resources** — the
|
|
157
|
+
table(s), their GSIs, and the DynamoDB-adjacent settings above (Streams, TTL,
|
|
158
|
+
PITR, TableClass, SSE, provisioned throughput, and the Application Auto Scaling
|
|
159
|
+
resources for that table). It does **not** generate IAM roles/policies for your
|
|
160
|
+
application, Lambda functions, API Gateway, stream consumers, DynamoDB Streams
|
|
161
|
+
event-source mappings, or any other application infrastructure. The one IAM
|
|
162
|
+
reference it emits is the AWS **service-linked** role ARN that Application Auto
|
|
163
|
+
Scaling uses (built with `Fn::Sub` so `${AWS::AccountId}` resolves at deploy
|
|
164
|
+
time) — it does not create an IAM role. Compose the generated template into your
|
|
165
|
+
own stack (nested stack / module) alongside those app resources.
|
|
166
|
+
|
|
167
|
+
## Loud-reject / validation behaviors
|
|
168
|
+
|
|
169
|
+
The CLI validates arguments before generation and **rejects loudly** (exit `2`,
|
|
170
|
+
JSON `INVALID_ARGS` on stderr) rather than silently ignoring a nonsensical
|
|
171
|
+
combination. The pure emitter keeps the same guards as an authoritative backstop
|
|
172
|
+
(it throws). The loud rejects are:
|
|
173
|
+
|
|
174
|
+
- **`--rcu` / `--wcu` with `PAY_PER_REQUEST`** — on-demand tables have no
|
|
175
|
+
provisioned capacity, so accepting the value would mislead you.
|
|
176
|
+
- **`--autoscale` with `PAY_PER_REQUEST`** — Application Auto Scaling on an
|
|
177
|
+
on-demand table is invalid in AWS.
|
|
178
|
+
- **`--autoscale-min` > `--autoscale-max`** — an invalid scalable-target range.
|
|
179
|
+
- **`--autoscale-target` > 100** — it is a utilization percentage (1–100).
|
|
180
|
+
- **A non-integer or `< 1` value for `--rcu` / `--wcu` / `--autoscale-*`.**
|
|
181
|
+
- **An invalid enum value** for `--format` / `--gsi-projection` / `--stream` /
|
|
182
|
+
`--billing-mode` / `--table-class`.
|
|
183
|
+
- **A missing `--entry`** (or one that cannot be imported).
|
|
184
|
+
|
|
185
|
+
Inconsistent single-table declarations (a GSI with conflicting sort-key presence,
|
|
186
|
+
or two `@ttl` attributes on one table) are caught statically by the
|
|
187
|
+
`cfn-schema-consistency` linter at model registration; the emitter throws as a
|
|
188
|
+
backstop if one survives to generation time.
|
|
189
|
+
|
|
190
|
+
## Worked example
|
|
191
|
+
|
|
192
|
+
The [`user-permissions`](../examples/user-permissions/) example is a single
|
|
193
|
+
`UserPermissions` table whose entities (users, groups, memberships, permissions)
|
|
194
|
+
share one physical table, with a `GSI1` **unioned** across `UserModel` (email
|
|
195
|
+
lookup) and `GroupMembershipModel` (the inverted membership index). It exercises
|
|
196
|
+
the base `PK`/`SK` key and a GSI union — it does **not** use streams, TTL, PITR,
|
|
197
|
+
provisioned throughput, or auto scaling (those are opt-in flags / a `@ttl`
|
|
198
|
+
decorator this model does not trigger).
|
|
199
|
+
|
|
200
|
+
Generate it (the committed sample
|
|
201
|
+
[`examples/user-permissions/cloudformation.yaml`](../examples/user-permissions/cloudformation.yaml)
|
|
202
|
+
is produced by exactly this command, and a drift-guard test keeps it in sync):
|
|
203
|
+
|
|
204
|
+
```bash
|
|
205
|
+
graphddb generate cloudformation \
|
|
206
|
+
--entry examples/user-permissions/models.ts \
|
|
207
|
+
--out examples/user-permissions/cloudformation.yaml
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
The generated template (default YAML):
|
|
211
|
+
|
|
212
|
+
```yaml
|
|
213
|
+
AWSTemplateFormatVersion: "2010-09-09"
|
|
214
|
+
Description: DynamoDB table(s) for a GraphDDB single-table design (generated by graphddb generate cloudformation).
|
|
215
|
+
Parameters:
|
|
216
|
+
TableName:
|
|
217
|
+
Type: String
|
|
218
|
+
Default: UserPermissions
|
|
219
|
+
Description: Name of the UserPermissions DynamoDB table.
|
|
220
|
+
Resources:
|
|
221
|
+
UserPermissionsTable:
|
|
222
|
+
Type: "AWS::DynamoDB::Table"
|
|
223
|
+
Properties:
|
|
224
|
+
TableName:
|
|
225
|
+
Ref: TableName
|
|
226
|
+
BillingMode: PAY_PER_REQUEST
|
|
227
|
+
AttributeDefinitions:
|
|
228
|
+
- AttributeName: PK
|
|
229
|
+
AttributeType: S
|
|
230
|
+
- AttributeName: SK
|
|
231
|
+
AttributeType: S
|
|
232
|
+
- AttributeName: GSI1PK
|
|
233
|
+
AttributeType: S
|
|
234
|
+
- AttributeName: GSI1SK
|
|
235
|
+
AttributeType: S
|
|
236
|
+
KeySchema:
|
|
237
|
+
- AttributeName: PK
|
|
238
|
+
KeyType: HASH
|
|
239
|
+
- AttributeName: SK
|
|
240
|
+
KeyType: RANGE
|
|
241
|
+
GlobalSecondaryIndexes:
|
|
242
|
+
- IndexName: GSI1
|
|
243
|
+
KeySchema:
|
|
244
|
+
- AttributeName: GSI1PK
|
|
245
|
+
KeyType: HASH
|
|
246
|
+
- AttributeName: GSI1SK
|
|
247
|
+
KeyType: RANGE
|
|
248
|
+
Projection:
|
|
249
|
+
ProjectionType: ALL
|
|
250
|
+
Outputs:
|
|
251
|
+
TableName:
|
|
252
|
+
Description: Name of the UserPermissions DynamoDB table.
|
|
253
|
+
Value:
|
|
254
|
+
Ref: UserPermissionsTable
|
|
255
|
+
TableArn:
|
|
256
|
+
Description: ARN of the UserPermissions DynamoDB table.
|
|
257
|
+
Value:
|
|
258
|
+
"Fn::GetAtt":
|
|
259
|
+
- UserPermissionsTable
|
|
260
|
+
- Arn
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
### More option combinations
|
|
264
|
+
|
|
265
|
+
```bash
|
|
266
|
+
# JSON output.
|
|
267
|
+
graphddb generate cloudformation --entry models.ts --format json
|
|
268
|
+
|
|
269
|
+
# Key-only GSI projection.
|
|
270
|
+
graphddb generate cloudformation --entry models.ts --gsi-projection KEYS_ONLY --out table.yaml
|
|
271
|
+
|
|
272
|
+
# Force a DynamoDB stream (NEW_AND_OLD_IMAGES) on.
|
|
273
|
+
graphddb generate cloudformation --entry models.ts --stream on --out table.yaml
|
|
274
|
+
|
|
275
|
+
# Deploy-time hardening: PITR + SSE + Infrequent-Access table class.
|
|
276
|
+
graphddb generate cloudformation --entry models.ts --pitr --sse --table-class STANDARD_INFREQUENT_ACCESS --out table.yaml
|
|
277
|
+
|
|
278
|
+
# Provisioned billing with fixed throughput.
|
|
279
|
+
graphddb generate cloudformation --entry models.ts --billing-mode PROVISIONED --rcu 10 --wcu 5 --out table.yaml
|
|
280
|
+
|
|
281
|
+
# Provisioned billing + Application Auto Scaling.
|
|
282
|
+
graphddb generate cloudformation --entry models.ts \
|
|
283
|
+
--billing-mode PROVISIONED --autoscale --autoscale-min 5 --autoscale-max 100 --autoscale-target 70 --out table.yaml
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
## Validating the output
|
|
287
|
+
|
|
288
|
+
Real `aws cloudformation validate-template` requires AWS credentials and the
|
|
289
|
+
`cloudformation:ValidateTemplate` IAM permission. Where those are unavailable
|
|
290
|
+
(this project's CI is IAM-blocked, for example), an equivalent **structural**
|
|
291
|
+
check is enough for a syntax gate: generate the template in both formats, parse
|
|
292
|
+
each (YAML + JSON), assert the two agree, and assert the table structure. The
|
|
293
|
+
repository wires this as `npm run validate:cfn` (see `scripts/validate-cfn.mjs`),
|
|
294
|
+
which the CI runs as its "Validate generated CloudFormation (structural)" step.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "graphddb",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Graph data modeling on DynamoDB with adjacency list pattern",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
"test:py": "python3 -m pytest python/tests -m \"not integration\"",
|
|
38
38
|
"test:py:integration": "python3 -m pytest python/tests -m integration",
|
|
39
39
|
"test:conformance": "tsx conformance/run.ts",
|
|
40
|
+
"validate:cfn": "node scripts/validate-cfn.mjs",
|
|
40
41
|
"gen:cli": "cli-contracts generate",
|
|
41
42
|
"gen:cli:check": "cli-contracts generate --dry-run",
|
|
42
43
|
"clean": "rm -rf dist",
|
|
@@ -70,7 +71,8 @@
|
|
|
70
71
|
"tsup": "^8.0.0",
|
|
71
72
|
"tsx": "^4.22.4",
|
|
72
73
|
"typescript": "^5.5.0",
|
|
73
|
-
"vitest": "^3.0.0"
|
|
74
|
+
"vitest": "^3.0.0",
|
|
75
|
+
"yaml": "^2.9.0"
|
|
74
76
|
},
|
|
75
77
|
"engines": {
|
|
76
78
|
"node": ">=22"
|