npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@civic/storage

v0.1.16

Published

The storage adapter system allows different pieces of functionality to be composable together to make storing complex data easier.

Downloads

556

Readme

storage

The storage adapter system allows different pieces of functionality to be composable together to make storing complex data easier.

Usage

A simple example, using local storage:

import { LocalStorageAdapter } from "@civic/storage";

const localStorage = new LocalStorageAdapter();
await localStorage.init();

await localStorage.set("contentKey", "All the content you want to store");
const stringContent = await localStorage.get("contentKey");

More complex example using local storage and Schema validation

import { LocalStorageAdapter, CompositeAdapter, ValidationSchemaAdapter } from "@civic/storage";

// uses jsonschema validation
const validationAdapter = new ValidationSchemaAdapter(
  await import("schema.json"),
  new JsonSerializerAdapter(new LocalStorageAdapter())
);
await validationAdapter.init();

await validationAdapter.set("contentKey", {
  "message": "All the content you want to store"
});
const objectContent = await validationAdapter.get("contentKey");

Storage Adapter design

The storage adapter was designed so you can easily extend it and plug your adapters from data manipulation to storage driver. Even though the adapter is responsible for normalization of data across multiple storage locations, there are some gaps in the current implementation. For example, the list of wallets is not de-duplicated if they are stored in both ipfs and the DID document. These improvements would be made with ticket CM-1857 as starting point.

Interface

interface StorageAdapter<DocumentType, ReceiptType> {
  init(): Promise<void>;
  set(contentId: string, content: DocumentType): Promise<ReceiptType>;
  get(contentId: string): Promise<DocumentType | null>;
  has(contentId: string): Promise<boolean>;
}

Blank diagram - Page 1 (5)

StorageDriver

Storage drivers are the final Adapter in the chain, It communicates to third parties to store or retrieve data. examples of drivers are IPFS, LocalStorage.

StorageTransformer

Storage transformers encode or change the contents of strings, they are used for encryption or base64 encoding/decoding.

StorageSerialiser

The Storage serializers encode an object into a string or decode a string into an object. examples of this would be JSON decoding/encoding.

StorageObjectActor

Storage object actors act on objects before or after they’ve been encoded/decoded, they validate objects, log out details, or cache objects to compare when writing or reading. Storage object actors should also be able to wrap storage composers.

StorageComposer

Object composers can contain routes between object adapters, they can fragment a larger document using JsonPaths, or multiplex the same documents to different adapters.

CivicMeAdapter

The CivicMeAdapter provides the adapter used on Civic.me frontend. It includes:

  • data encryption using Lexi.
  • Attach adapters to the DID.

The storageAdapter used on the CivicMeAdapter are created through the storageAdapterFactory. If the adapter setting is encrypted, the storageAdapter will be inside an EncryptionAdapter using Lexi for encription.

Blank diagram - Page 1 (3)

Example

const storageAdapter = new CivicMeAdapter(
  didDocument,
  {
    localProfile: {
      didAware: false,
      encrypted: true,
      locationType: LocationTypes.LOCAL_STORAGE,
    },
    privateRemoteProfile: {
      didAware: {
        fragmentId: "privateRemoteProfile",
        urlPrefix: "ipfs://",
      },
      encrypted: true,
      locationType: LocationTypes.IPFS,
    },
  },
  {
    "$.localProfile": "localProfile",
    "$.privateRemoteProfile": "privateRemoteProfile",
  },
  {
    did: didDocument.id,
    signMessage,
  }
);

await storageAdapter.init();

API

constructor
constructor(
    didDocument: DIDDocument,
    adapterSettings: Record<string, AdapterFactoryInitData>,
    jsonPaths: Record<string, string>,
    globalEncryptionSettings: EncryptionAdapterSettings | null = null
)

didDocument: The did document object.

adapterSettings: The settings for the adapters that will be used on the store. Each adapter has its identifier and own settings.

jsonPaths: The paths used to index the data.

globalEncryptionSettings: Optional encryption settings.

Adapters have a key, the key is used to identify individual adapter instances. In the case of adapters that are attached to the DID, the key is used as the fragment ID, for local storage the key is the content item ID.

When loading the adapters an index is created that stores the adapters against the keys.

DID attached adapters are loaded into a list, when they are used a transaction is generated when an update is made.

get
  get(contentPath: string): Promise<Record<string, unknown> | null>

contentPath: The path of the content to be retrieved.

When getting the content all the content is initially fetched, the fetching is done by passing the key into each instance of the adapter. By doing so the adapter can know where to fetch the content. For DID attached adapters the service endpoint is first checked and the content location is resolved against the DID fragment ID. All the content is fetched and merged into one document.

set
  set(
    contentPath: string,
    content: Record<string, unknown>
  ): Promise<{
    results: Record<string, string>;
    didDocument: DIDDocument;
    transactionInstructions: ((
      connection: Connection
    ) => Promise<TransactionInstruction>)[];
  }> {

contentPath: The path where the content will be set.

content: The data object.

When set is called, the stored JSON paths are ordered by JSON path part length, the part of the document is copied out into a other documents linked adapters, that part of the document is deleted from the clone document. the split of documents are passed to their adapters set method, the returned content id is then mapped to the key of the adapter for further use, ex. used for generating transactions for DID attached adapters.

SchemaMigrationAdapter

The schema migration adapter provides means to migrate from pre-defined schema versions through a migration function. This adapter guarantees the data retrieved and storage is always using the most recent schema.

Example

  new SchemaMigrationAdapter(
      [v1Schema as Schema, v2Schema as Schema],
      (_oldVersion, oldData) => {
        const typedOldData = oldData as unknown as CreatorProfile;
        return {
          localProfile: {
            name: {
              value:
                typedOldData?.localProfile?.name?.value ??
                typedOldData?.localProfile?.name ??
                "",
              status: ProfilePropertySyncStatus.Private,
            },
            image: {
              value:
                typedOldData?.localProfile?.image?.value ??
                typedOldData?.localProfile?.image ??
                "",
              status: ProfilePropertySyncStatus.Private,
            },
          },
          privateRemoteProfile: {
            name: {
              value: typedOldData?.privateRemoteProfile?.name ?? "",
              status: ProfilePropertySyncStatus.Private,
            },
            image: {
              value: typedOldData?.privateRemoteProfile?.image ?? "",
              status: ProfilePropertySyncStatus.Private,
            },
          },
        };
      },
      civicMeStorageAdapter
    )

Types

AdapterFactoryInitData type
export type AdapterFactoryInitData = {
  didAware:
    | false
    | {
        fragmentId: string;
        urlPrefix: string;
      };
  encrypted: boolean | EncryptionAdapterSettings;
  locationType: LocationTypes;
};
EncryptionAdapterSettings type
export type EncryptionAdapterSettings = {
  // the method used to sign the message
  signMessage(messageToSign: Uint8Array): Promise<Uint8Array>;
  // the DID
  did: string;
  // a string used to signing the message. if empty, a random string will be
  // used
  publicSigningString?: string;
};